From 36db9356cce3c7614ae37eec9fe17678ca210afe Mon Sep 17 00:00:00 2001
From: "sorokin.oleg.edu"
Date: Fri, 11 Sep 2026 11:23:20 -0700
Subject: [PATCH] Add bounded definition and reference navigation for current
source drafts
Preserve loaded draft bytes and model history, reject stale workspace and input events, and keep captured Escape under modal priority. Verified 181 focused Windows tests and strict desktop Clippy on exact inputs4; earlier full737 scope and component/native preparation evidence are archived separately. Native navigation acceptance remains pending.
---
crates/oa-core/src/language.rs | 187 +-
crates/oa-core/src/language_outline_tests.rs | 386 ++
crates/oa-desktop/src/app.rs | 100 +-
crates/oa-desktop/src/app/hosted.rs | 16 +
crates/oa-desktop/src/app/source_symbols.rs | 448 ++
.../src/app/source_symbols/tests.rs | 1201 +++++
crates/oa-desktop/src/guide.rs | 10 +-
crates/oa-desktop/src/source_navigation.rs | 5 +
.../src/source_navigation/controller.rs | 125 +
.../src/source_navigation/interaction.rs | 284 ++
.../source_navigation/interaction/tests.rs | 190 +
.../oa-desktop/src/source_navigation/query.rs | 1102 +++++
.../src/source_navigation/query_tests.rs | 989 ++++
.../src/source_navigation/symbols.rs | 2043 ++++++++
crates/oa-desktop/src/source_workspace.rs | 221 +-
crates/oa-desktop/src/tests.rs | 6 +-
crates/oa-desktop/src/views.rs | 82 +-
crates/oa-desktop/src/views/source_symbols.rs | 260 +
docs/language.html | 17 +
docs/language.rst | 19 +
.../.gitattributes | 1 +
.../README.md | 29 +
.../archive-manifest.json | 794 +++
.../component-inputs/language.rs | 2832 +++++++++++
.../language_outline_tests.rs | 386 ++
.../component-inputs/outline-seam-v2.patch | 569 +++
.../component-inputs/query-admission-v2.patch | 129 +
.../source_symbols-baseline.rs | 1043 ++++
.../components/artifacts1-status.json | 16 +
.../components/artifacts1-stderr.log | 1 +
.../components/artifacts1-stdout.log | 44 +
.../components/clippy-tests2-status.json | 18 +
.../components/clippy-tests2.log | 2 +
.../components/clippy1-status.json | 17 +
.../components/clippy1.log | 2 +
.../components/compiler1-status.json | 10 +
.../components/compiler1-stderr.log | 0
.../components/compiler1-stdout.log | 7 +
.../components/core1-status.json | 15 +
.../components/core1.log | 694 +++
.../components/evidence-manifest.json | 270 +
.../components/final-readback.json | 51 +
.../oa-core-781c6973417b169c/lib-oa_core | 1 +
.../oa-core-781c6973417b169c/lib-oa_core.json | 1 +
.../lib-serde_json | 1 +
.../lib-serde_json.json | 1 +
.../components/inputs-supplement.json | 8 +
.../components/inputs.json | 1616 ++++++
...language_outline_tests-pre-encoding-fix.rs | 386 ++
.../components/literal-byte-readback.json | 44 +
.../components/outline1-status.json | 17 +
.../components/outline1.log | 20 +
.../components/outline2-inputs.json | 10 +
.../components/outline2-status.json | 17 +
.../components/outline2.log | 20 +
.../components/query-artifacts3.json | 5 +
.../components/query-binding1-failure.json | 12 +
.../components/query-bindings.json | 137 +
.../components/query-compile1-status.json | 25 +
.../components/query-compile1-stderr.log | 39 +
.../components/query-compile1-stdout.log | 0
.../components/query-compile2-status.json | 25 +
.../components/query-compile2-stderr.log | 42 +
.../components/query-compile2-stdout.log | 0
.../components/query-compile3-status.json | 25 +
.../components/query-compile3-stderr.log | 0
.../components/query-compile3-stdout.log | 0
.../query-source-reconstruction.json | 12 +
.../components/query-tests3-status.json | 10 +
.../components/query-tests3-stderr.log | 0
.../components/query-tests3-stdout.log | 25 +
.../components/query-type-correction.json | 9 +
.../components/run-gates.ps1 | 20 +
.../components/run-query-resume.ps1 | 74 +
.../components/run-query-typed.ps1 | 43 +
.../components/run-query-utf8.ps1 | 43 +
.../components/run-query.ps1 | 56 +
.../components/run-utf8-outline-gates.ps1 | 22 +
.../source_symbols-v2-utf8-typed.rs | 1124 +++++
.../components/source_symbols-v2-utf8.rs | 1124 +++++
.../components/source_symbols-v2.rs | 1124 +++++
.../utf8-reconstruction-correction.json | 9 +
.../native-preparation/README.md | 61 +
.../ambiguous-open/links.arcadia | 4 +
.../ambiguous-open/project.arcadia | 7 +
.../ambiguous-open/救援/assess.arcadia | 4 +
.../ambiguous-open/救援/confirm.arcadia | 2 +
.../ambiguous-rejected-result.json | 14 +
.../ambiguous-rejected.stderr.log | 4 +
.../ambiguous-rejected.stdout.log | 0
.../native-preparation/check-summary.json | 62 +
.../native-preparation/check.py | 43 +
.../expected-locations.json | 385 ++
.../native-preparation/fixture-manifest.json | 452 ++
.../helper-initial-failure.json | 6 +
.../missing-include-open/links.arcadia | 4 +
.../missing-include-open/project.arcadia | 8 +
.../missing-include-open/救援/assess.arcadia | 4 +
.../missing-include-open/救援/confirm.arcadia | 1 +
.../missing-include-rejected-result.json | 14 +
.../missing-include-rejected.stderr.log | 4 +
.../missing-include-rejected.stdout.log | 0
.../native-preparation/prepare.py | 98 +
.../readback-print-initial-failure.json | 6 +
.../release1-baseline/README.md | 86 +
.../evidence/repair-check.md | 15 +
.../expected-diagnostics.json | 9 +
.../release1-baseline/model.json | 4401 +++++++++++++++++
.../release1-baseline/model.rs | 1040 ++++
.../activity_oa_restore_work.arcadia | 45 +
.../process_oa_assessment_procedure.arcadia | 25 +
.../process_oa_field_procedure.arcadia | 67 +
.../oa/behavior/process_oa_response.arcadia | 128 +
.../behavior/scenario_oa_conversation.arcadia | 64 +
.../scenario_oa_full_scenario.arcadia | 41 +
.../behavior/state_machine_oa_monitor.arcadia | 80 +
.../release1-baseline/oa/capabilities.arcadia | 27 +
.../release1-baseline/oa/model.arcadia | 24 +
.../release1-baseline/oa/participants.arcadia | 60 +
.../release1-baseline/oa/types.arcadia | 35 +
.../release1-baseline/project.arcadia | 33 +
.../process_sa_assessment_procedure.arcadia | 25 +
.../process_sa_field_procedure.arcadia | 67 +
.../sa/behavior/process_sa_response.arcadia | 128 +
.../behavior/scenario_sa_conversation.arcadia | 73 +
.../scenario_sa_full_scenario.arcadia | 41 +
.../behavior/state_machine_sa_monitor.arcadia | 80 +
.../release1-baseline/sa/capabilities.arcadia | 27 +
.../function_sa_audit_thresholds.arcadia | 5 +
.../function_sa_restore_work.arcadia | 93 +
.../release1-baseline/sa/model.arcadia | 24 +
.../release1-baseline/sa/participants.arcadia | 126 +
.../release1-baseline/sa/types.arcadia | 50 +
.../release1-baseline/traceability.arcadia | 1156 +++++
.../release1-baseline/transition-before.json | 1198 +++++
.../release1-baseline/transition-request.json | 114 +
.../views/diagram_oa_allocation_view.arcadia | 100 +
.../views/diagram_sa_allocation_view.arcadia | 100 +
.../views/diagram_sa_capability_view.arcadia | 72 +
.../views/diagram_sa_service_view.arcadia | 104 +
.../views/diagram_sa_state_view.arcadia | 90 +
.../release1-same-result.json | 14 +
.../release1-same.stderr.log | 0
.../release1-same.stdout.log | 1 +
.../valid-baseline/links.arcadia | 4 +
.../valid-baseline/project.arcadia | 7 +
.../valid-baseline/救援/assess.arcadia | 4 +
.../valid-baseline/救援/confirm.arcadia | 1 +
.../native-preparation/valid-same-result.json | 14 +
.../native-preparation/valid-same.stderr.log | 0
.../native-preparation/valid-same.stdout.log | 1 +
.../readback.json | 12 +
.../verify_archive.py | 16 +
.../.gitattributes | 1 +
.../README.md | 33 +
.../capture.py | 63 +
.../check1-status.json | 7 +
.../check1.log | 174 +
.../clippy2-status.json | 17 +
.../clippy2.log | 4 +
.../focused2-status.json | 18 +
.../focused2.log | 186 +
.../inputs1-readback.json | 7 +
.../inputs1.json | 2504 ++++++++++
.../inputs1.zip | Bin 0 -> 2073758 bytes
.../inputs2-readback.json | 7 +
.../inputs2.json | 2504 ++++++++++
.../inputs2.zip | Bin 0 -> 2075966 bytes
.../manifest.json | 214 +
.../performance1-status.json | 7 +
.../performance1.log | 7 +
.../readback.json | 25 +
.../run-focused2.ps1 | 18 +
.../run-test1.ps1 | 11 +
.../test1-artifact.json | 7 +
.../test1-status.json | 7 +
.../test1.log | 770 +++
.../verify.py | 82 +
.../.gitattributes | 1 +
.../README.md | 24 +
.../build_gui.py | 47 +
.../clippy3-status.json | 17 +
.../clippy3.log | 2 +
.../clippy4-status.json | 17 +
.../clippy4.log | 2 +
.../focused3-status.json | 18 +
.../focused3.log | 188 +
.../focused4-status.json | 18 +
.../focused4.log | 190 +
.../gui-build-messages.jsonl | 305 ++
.../gui-build-stderr.log | 3 +
.../gui-build.json | 58 +
.../inputs3-readback.json | 7 +
.../inputs3.json | 2504 ++++++++++
.../inputs3.zip | Bin 0 -> 2076360 bytes
.../inputs4-readback.json | 7 +
.../inputs4.json | 2504 ++++++++++
.../inputs4.zip | Bin 0 -> 2078266 bytes
.../manifest.json | 130 +
.../readback.json | 8 +
.../review-final/review-escape.md | 16 +
.../review-final/review.md | 33 +
.../review-final/source-hashes.json | 130 +
.../run-focused3.ps1 | 18 +
.../run-focused4.ps1 | 18 +
...ase-text-navigation-integrated-20260911.md | 119 +
...navigation-outline-integration-20260911.md | 69 +
...t-navigation-outline-review-v2-20260911.md | 41 +
208 files changed, 44983 insertions(+), 62 deletions(-)
create mode 100644 crates/oa-core/src/language_outline_tests.rs
create mode 100644 crates/oa-desktop/src/app/source_symbols.rs
create mode 100644 crates/oa-desktop/src/app/source_symbols/tests.rs
create mode 100644 crates/oa-desktop/src/source_navigation/controller.rs
create mode 100644 crates/oa-desktop/src/source_navigation/interaction.rs
create mode 100644 crates/oa-desktop/src/source_navigation/interaction/tests.rs
create mode 100644 crates/oa-desktop/src/source_navigation/query.rs
create mode 100644 crates/oa-desktop/src/source_navigation/query_tests.rs
create mode 100644 crates/oa-desktop/src/source_navigation/symbols.rs
create mode 100644 crates/oa-desktop/src/views/source_symbols.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/.gitattributes
create mode 100644 harness/experiments/text-navigation-components-20260911/README.md
create mode 100644 harness/experiments/text-navigation-components-20260911/archive-manifest.json
create mode 100644 harness/experiments/text-navigation-components-20260911/component-inputs/language.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/component-inputs/language_outline_tests.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/component-inputs/outline-seam-v2.patch
create mode 100644 harness/experiments/text-navigation-components-20260911/component-inputs/query-admission-v2.patch
create mode 100644 harness/experiments/text-navigation-components-20260911/component-inputs/source_symbols-baseline.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/components/artifacts1-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/artifacts1-stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/artifacts1-stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/clippy-tests2-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/clippy-tests2.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/clippy1-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/clippy1.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/compiler1-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/compiler1-stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/compiler1-stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/core1-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/core1.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/evidence-manifest.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/final-readback.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core
create mode 100644 harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/inputs-supplement.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/inputs.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/language_outline_tests-pre-encoding-fix.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/components/literal-byte-readback.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/outline1-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/outline1.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/outline2-inputs.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/outline2-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/outline2.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-artifacts3.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-binding1-failure.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-bindings.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile1-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile1-stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile1-stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile2-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile2-stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile2-stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile3-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile3-stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-compile3-stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-source-reconstruction.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-tests3-status.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-tests3-stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-tests3-stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/components/query-type-correction.json
create mode 100644 harness/experiments/text-navigation-components-20260911/components/run-gates.ps1
create mode 100644 harness/experiments/text-navigation-components-20260911/components/run-query-resume.ps1
create mode 100644 harness/experiments/text-navigation-components-20260911/components/run-query-typed.ps1
create mode 100644 harness/experiments/text-navigation-components-20260911/components/run-query-utf8.ps1
create mode 100644 harness/experiments/text-navigation-components-20260911/components/run-query.ps1
create mode 100644 harness/experiments/text-navigation-components-20260911/components/run-utf8-outline-gates.ps1
create mode 100644 harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8-typed.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/components/source_symbols-v2.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/components/utf8-reconstruction-correction.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/README.md
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/links.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/project.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/assess.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/confirm.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected-result.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected.stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected.stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/check-summary.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/check.py
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/expected-locations.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/fixture-manifest.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/helper-initial-failure.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/links.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/project.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/assess.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/confirm.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected-result.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected.stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected.stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/prepare.py
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/readback-print-initial-failure.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/README.md
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/evidence/repair-check.md
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/expected-diagnostics.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.rs
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/activities/activity_oa_restore_work.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_assessment_procedure.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_field_procedure.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_response.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_conversation.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_full_scenario.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/state_machine_oa_monitor.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/capabilities.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/model.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/participants.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/types.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/project.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_assessment_procedure.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_field_procedure.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_response.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_conversation.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_full_scenario.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/state_machine_sa_monitor.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/capabilities.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_audit_thresholds.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_restore_work.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/model.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/participants.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/types.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/traceability.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-before.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-request.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_oa_allocation_view.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_allocation_view.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_capability_view.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_service_view.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_state_view.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-same-result.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-same.stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/release1-same.stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/links.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/project.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/assess.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/confirm.arcadia
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/valid-same-result.json
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/valid-same.stderr.log
create mode 100644 harness/experiments/text-navigation-components-20260911/native-preparation/valid-same.stdout.log
create mode 100644 harness/experiments/text-navigation-components-20260911/readback.json
create mode 100644 harness/experiments/text-navigation-components-20260911/verify_archive.py
create mode 100644 harness/experiments/text-navigation-desktop-20260911/.gitattributes
create mode 100644 harness/experiments/text-navigation-desktop-20260911/README.md
create mode 100644 harness/experiments/text-navigation-desktop-20260911/capture.py
create mode 100644 harness/experiments/text-navigation-desktop-20260911/check1-status.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/check1.log
create mode 100644 harness/experiments/text-navigation-desktop-20260911/clippy2-status.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/clippy2.log
create mode 100644 harness/experiments/text-navigation-desktop-20260911/focused2-status.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/focused2.log
create mode 100644 harness/experiments/text-navigation-desktop-20260911/inputs1-readback.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/inputs1.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/inputs1.zip
create mode 100644 harness/experiments/text-navigation-desktop-20260911/inputs2-readback.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/inputs2.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/inputs2.zip
create mode 100644 harness/experiments/text-navigation-desktop-20260911/manifest.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/performance1-status.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/performance1.log
create mode 100644 harness/experiments/text-navigation-desktop-20260911/readback.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/run-focused2.ps1
create mode 100644 harness/experiments/text-navigation-desktop-20260911/run-test1.ps1
create mode 100644 harness/experiments/text-navigation-desktop-20260911/test1-artifact.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/test1-status.json
create mode 100644 harness/experiments/text-navigation-desktop-20260911/test1.log
create mode 100644 harness/experiments/text-navigation-desktop-20260911/verify.py
create mode 100644 harness/experiments/text-navigation-followup-20260911/.gitattributes
create mode 100644 harness/experiments/text-navigation-followup-20260911/README.md
create mode 100644 harness/experiments/text-navigation-followup-20260911/build_gui.py
create mode 100644 harness/experiments/text-navigation-followup-20260911/clippy3-status.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/clippy3.log
create mode 100644 harness/experiments/text-navigation-followup-20260911/clippy4-status.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/clippy4.log
create mode 100644 harness/experiments/text-navigation-followup-20260911/focused3-status.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/focused3.log
create mode 100644 harness/experiments/text-navigation-followup-20260911/focused4-status.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/focused4.log
create mode 100644 harness/experiments/text-navigation-followup-20260911/gui-build-messages.jsonl
create mode 100644 harness/experiments/text-navigation-followup-20260911/gui-build-stderr.log
create mode 100644 harness/experiments/text-navigation-followup-20260911/gui-build.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/inputs3-readback.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/inputs3.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/inputs3.zip
create mode 100644 harness/experiments/text-navigation-followup-20260911/inputs4-readback.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/inputs4.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/inputs4.zip
create mode 100644 harness/experiments/text-navigation-followup-20260911/manifest.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/readback.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/review-final/review-escape.md
create mode 100644 harness/experiments/text-navigation-followup-20260911/review-final/review.md
create mode 100644 harness/experiments/text-navigation-followup-20260911/review-final/source-hashes.json
create mode 100644 harness/experiments/text-navigation-followup-20260911/run-focused3.ps1
create mode 100644 harness/experiments/text-navigation-followup-20260911/run-focused4.ps1
create mode 100644 harness/release-text-navigation-integrated-20260911.md
create mode 100644 harness/release-text-navigation-outline-integration-20260911.md
create mode 100644 harness/release-text-navigation-outline-review-v2-20260911.md
diff --git a/crates/oa-core/src/language.rs b/crates/oa-core/src/language.rs
index 37d42e7f..24e3f891 100644
--- a/crates/oa-core/src/language.rs
+++ b/crates/oa-core/src/language.rs
@@ -142,12 +142,87 @@ pub struct Analysis {
pub declarations: Vec,
}
+/// Check the decoded spelling required by declaration-header UUID clauses.
+/// This is the shared 36-byte/hyphen shape rule, not a hexadecimal parser:
+/// callers must additionally parse the UUID. Uppercase hex and JSON escapes
+/// that decode to this spelling remain accepted. URN/braced/compact forms do not.
+pub fn has_uuid_header_shape(decoded: &str) -> bool {
+ decoded.len() == 36
+ && [8, 13, 18, 23]
+ .into_iter()
+ .all(|index| decoded.as_bytes()[index] == b'-')
+}
+
+/// Budgets for one original source buffer. Policy is caller-owned; these
+/// limits do not change the grammar or perform model admission.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OutlineLimits {
+ pub max_source_bytes: usize,
+ pub max_tokens: usize,
+ pub max_declarations: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum OutlineResource {
+ SourceBytes,
+ Tokens,
+ Declarations,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OutlineLimit {
+ pub resource: OutlineResource,
+ pub limit: usize,
+ pub observed_at_least: usize,
+ /// Original byte offset where admission stopped; never a rewritten offset.
+ pub offset: usize,
+}
+
+/// Roles discovered by the existing tolerant declaration walk. Keeping the
+/// wrapper preserves DeclarationSpan and Analysis as existing public APIs.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct OutlineDeclaration {
+ pub declaration: DeclarationSpan,
+ pub kind_span: Span,
+ pub alias_span: Span,
+ /// Whole quoted header ID token, even if it is not a valid UUID. In that
+ /// case declaration.id is None. Missing/non-string ID values have no span.
+ pub id_literal_span: Option,
+}
+
+/// A full-file tolerant scan, not a successful parse or validated model. Invalid
+/// text remains available. Lexical errors and unmatched braces are facts only:
+/// their absence does not establish syntactic or semantic validity.
+///
+/// A budget failure returns Err and no partial Outline. Include reachability,
+/// project completeness and editor revision are intentionally not inferred here.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Outline<'a> {
+ pub source: &'a str,
+ pub tokens: Vec,
+ pub declarations: Vec,
+ pub has_lexical_errors: bool,
+ pub has_unmatched_braces: bool,
+}
+
/// Tolerant highlighting tokens cover the whole input, including incomplete
/// strings and unexpected characters. No model is created or modified.
pub fn tokenize(source: &str) -> Vec {
+ tokenize_with_limit(source, usize::MAX).expect("unlimited token scan")
+}
+
+fn tokenize_with_limit(source: &str, max_tokens: usize) -> Result, OutlineLimit> {
let mut tokens = Vec::new();
let mut cursor = 0;
while cursor < source.len() {
+ if tokens.len() == max_tokens {
+ return Err(OutlineLimit {
+ resource: OutlineResource::Tokens,
+ limit: max_tokens,
+ observed_at_least: tokens.len().saturating_add(1),
+ offset: cursor,
+ });
+ }
let start = cursor;
let ch = source[cursor..].chars().next().expect("in bounds");
let class = if matches!(ch, ' ' | '\t' | '\r' | '\n') {
@@ -224,7 +299,7 @@ pub fn tokenize(source: &str) -> Vec {
span: Span { start, end: cursor },
});
}
- tokens
+ Ok(tokens)
}
pub fn lint(source: &str) -> Vec {
@@ -1230,11 +1305,7 @@ impl Parser<'_> {
self.cursor += 1;
let offset = self.current().span.start;
let value = self.quoted()?;
- if value.len() != 36
- || ![8, 13, 18, 23]
- .into_iter()
- .all(|i| value.as_bytes()[i] == b'-')
- {
+ if !has_uuid_header_shape(&value) {
return Err(Error {
code: "invalid_source",
offset,
@@ -1962,8 +2033,49 @@ fn take_required_id(fields: &mut Map, key: &str) -> Result Analysis {
let tokens = tokenize(source);
+ let (declarations, _) =
+ declaration_outline(source, &tokens, usize::MAX).expect("unlimited declaration walk");
+ Analysis {
+ tokens,
+ diagnostics: lint(source),
+ declarations: declarations.into_iter().map(|d| d.declaration).collect(),
+ }
+}
+
+/// Read exact current text without lint, parsing a model, stabilization or I/O.
+/// Source bytes are admitted before token allocation; token/declaration limits
+/// are checked before lexing the next token or materializing the next record.
+pub fn outline(source: &str, limits: OutlineLimits) -> Result, OutlineLimit> {
+ if source.len() > limits.max_source_bytes {
+ return Err(OutlineLimit {
+ resource: OutlineResource::SourceBytes,
+ limit: limits.max_source_bytes,
+ observed_at_least: source.len(),
+ offset: 0,
+ });
+ }
+ let tokens = tokenize_with_limit(source, limits.max_tokens)?;
+ let has_lexical_errors = tokens.iter().any(|token| token.class == TokenClass::Error);
+ let (declarations, has_unmatched_braces) =
+ declaration_outline(source, &tokens, limits.max_declarations)?;
+ Ok(Outline {
+ source,
+ tokens,
+ declarations,
+ has_lexical_errors,
+ has_unmatched_braces,
+ })
+}
+
+fn declaration_outline(
+ source: &str,
+ tokens: &[TokenSpan],
+ max_declarations: usize,
+) -> Result<(Vec, bool), OutlineLimit> {
let significant = tokens
.iter()
.filter(|t| !matches!(t.class, TokenClass::Whitespace | TokenClass::Comment))
@@ -1972,17 +2084,21 @@ pub fn analyze(source: &str) -> Analysis {
// again would make tolerant navigation quadratic on deeply nested drafts.
let mut body_ends = vec![None; significant.len()];
let mut bodies = Vec::new();
+ let mut unmatched_close = false;
for (index, token) in significant.iter().enumerate() {
match &source[token.span.start..token.span.end] {
"{" => bodies.push(index),
"}" => {
if let Some(open) = bodies.pop() {
body_ends[open] = Some(token.span.end);
+ } else {
+ unmatched_close = true;
}
}
_ => {}
}
}
+ let has_unmatched_braces = unmatched_close || !bodies.is_empty();
let mut declarations = Vec::new();
for (i, t) in significant.iter().enumerate() {
let word = &source[t.span.start..t.span.end];
@@ -2007,7 +2123,16 @@ pub fn analyze(source: &str) -> Analysis {
}
j += 1;
}
+ if declarations.len() == max_declarations {
+ return Err(OutlineLimit {
+ resource: OutlineResource::Declarations,
+ limit: max_declarations,
+ observed_at_least: declarations.len().saturating_add(1),
+ offset: t.span.start,
+ });
+ }
let mut id = None;
+ let mut id_literal_span = None;
if significant
.get(j)
.is_some_and(|t| &source[t.span.start..t.span.end] == "id")
@@ -2015,39 +2140,51 @@ pub fn analyze(source: &str) -> Analysis {
if let Some(value) = significant.get(j + 1)
&& value.class == TokenClass::String
{
+ id_literal_span = Some(value.span);
let text =
serde_json::from_str::(&source[value.span.start..value.span.end])
.unwrap();
- id = Id::parse_str(&text).ok();
+ if has_uuid_header_shape(&text) {
+ id = Id::parse_str(&text).ok();
+ }
}
j += 2;
}
- let mut end = significant
- .get(j)
- .map_or(alias_token.span.end, |t| t.span.end);
+ // A recognized header ID at EOF must remain inside the whole span.
+ // Keep the old next-token/body behavior; only the EOF fallback changes.
+ let header_end = significant
+ .get(j.saturating_sub(1))
+ .or_else(|| significant.last())
+ .map_or(alias_token.span.end, |token| token.span.end);
+ let mut end = significant.get(j).map_or(header_end, |t| t.span.end);
if significant
.get(j)
.is_some_and(|t| &source[t.span.start..t.span.end] == "{")
{
end = body_ends[j].unwrap_or(source.len());
}
- declarations.push(DeclarationSpan {
- alias: alias.into(),
- id,
- kind: word.into(),
- span: Span {
- start: t.span.start,
- end,
+ declarations.push(OutlineDeclaration {
+ declaration: DeclarationSpan {
+ alias: alias.into(),
+ id,
+ kind: word.into(),
+ span: Span {
+ start: t.span.start,
+ end,
+ },
},
+ kind_span: t.span,
+ alias_span: alias_token.span,
+ id_literal_span,
});
}
- Analysis {
- tokens,
- diagnostics: lint(source),
- declarations,
- }
+ Ok((declarations, has_unmatched_braces))
}
+#[cfg(test)]
+#[path = "language_outline_tests.rs"]
+mod outline_tests;
+
#[cfg(test)]
#[path = "language_nesting_tests.rs"]
mod nesting_tests;
@@ -2442,11 +2579,7 @@ pub(crate) mod legacy {
let value = self.quoted()?;
// Require the portable 8-4-4-4-12 spelling, not the UUID crate's URN,
// braces, or compact alternatives. Uppercase hexadecimal is accepted.
- if value.len() != 36
- || ![8, 13, 18, 23]
- .into_iter()
- .all(|index| value.as_bytes()[index] == b'-')
- {
+ if !super::has_uuid_header_shape(&value) {
return Err(Error {
code: "invalid_source",
offset,
diff --git a/crates/oa-core/src/language_outline_tests.rs b/crates/oa-core/src/language_outline_tests.rs
new file mode 100644
index 00000000..a29f180f
--- /dev/null
+++ b/crates/oa-core/src/language_outline_tests.rs
@@ -0,0 +1,386 @@
+//! Prepared outline tests. Explicit compatibility tests additionally exercise
+//! the existing parsers/analyze; the outline entry itself never calls them.
+use super::*;
+
+fn limits() -> OutlineLimits {
+ OutlineLimits {
+ max_source_bytes: 1024 * 1024,
+ max_tokens: 100_000,
+ max_declarations: 1_000,
+ }
+}
+
+fn declaration<'a>(result: &'a Outline<'_>, alias: &str) -> &'a OutlineDeclaration {
+ result
+ .declarations
+ .iter()
+ .find(|d| d.declaration.alias == alias)
+ .unwrap()
+}
+
+fn text(source: &str, span: Span) -> &str {
+ &source[span.start..span.end]
+}
+
+#[test]
+fn outline_roles_preserve_unicode_crlf_and_exact_escaped_uuid_literal() {
+ let source = "// 検査 🚒\r\noa_actor responder \"救助\" id \"\\u00300000000-0000-0000-0000-000000000001\" {}\r\nsource @ // 使用\r\n responder;";
+ let before = source.as_bytes().to_vec();
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.source.as_bytes(), before);
+ assert_eq!(result.source.as_ptr(), source.as_ptr());
+ let d = declaration(&result, "responder");
+ assert_eq!(text(source, d.kind_span), "oa_actor");
+ assert_eq!(text(source, d.alias_span), "responder");
+ assert_eq!(
+ text(source, d.id_literal_span.unwrap()),
+ "\"\\u00300000000-0000-0000-0000-000000000001\""
+ );
+ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
+ assert_eq!(d.declaration.span.start, source.find("oa_actor").unwrap());
+ assert_eq!(d.declaration.span.end, source.find("{}\r\n").unwrap() + 2);
+ let mut end = 0;
+ for token in &result.tokens {
+ assert_eq!(token.span.start, end);
+ assert!(token.span.start < token.span.end);
+ assert!(source.is_char_boundary(token.span.start));
+ assert!(source.is_char_boundary(token.span.end));
+ end = token.span.end;
+ }
+ assert_eq!(end, source.len());
+ assert!(!result.has_lexical_errors);
+ assert!(!result.has_unmatched_braces);
+}
+
+#[test]
+fn closed_id_at_eof_is_inside_the_declaration_extent() {
+ for source in [
+ "oa_actor a \"Actor\" id \"00000000-0000-0000-0000-000000000001\"",
+ "node placed id \"00000000-0000-0000-0000-000000000001\"",
+ ] {
+ let result = outline(source, limits()).unwrap();
+ let d = &result.declarations[0];
+ let literal = d.id_literal_span.unwrap();
+ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
+ assert_eq!(d.declaration.span.end, source.len());
+ assert!(d.declaration.span.start <= literal.start);
+ assert!(literal.end <= d.declaration.span.end);
+ }
+ for source in ["oa_actor a \"Actor\"", "oa_actor a \"Actor\" id"] {
+ let result = outline(source, limits()).unwrap();
+ let d = &result.declarations[0];
+ assert_eq!(d.declaration.span.end, source.len());
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(d.id_literal_span, None);
+ }
+}
+
+#[test]
+fn missing_bad_and_historical_ids_never_allocate_authored_identity() {
+ let source = "oa_actor missing \"No ID\" {}\noa_actor bad \"Bad ID\" id \"not-a-uuid\" {}\noa_actor historical \"00000000-0000-0000-0000-000000000001\" { history uuid \"00000000-0000-0000-0000-000000000001\"; }";
+ let first = outline(source, limits()).unwrap();
+ let second = outline(source, limits()).unwrap();
+ assert_eq!(first, second);
+ assert!(
+ first
+ .declarations
+ .iter()
+ .all(|d| d.declaration.id.is_none())
+ );
+ assert_eq!(declaration(&first, "missing").id_literal_span, None);
+ assert_eq!(declaration(&first, "historical").id_literal_span, None);
+ assert_eq!(
+ text(source, declaration(&first, "bad").id_literal_span.unwrap()),
+ "\"not-a-uuid\""
+ );
+ // Balanced tokens are not a successful syntax/semantic validation claim.
+ assert!(!first.has_lexical_errors);
+ assert!(!first.has_unmatched_braces);
+}
+
+#[test]
+fn invalid_full_scan_keeps_closed_children_and_reports_lexical_body_facts() {
+ let source = "oa_entity outer \"Outer\" {\n oa_actor ready \"Ready\" { description \"} stays text\"; }\n oa_actor pending \"Pending\" { description \"unfinished 🌊";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.source, source);
+ assert_eq!(result.tokens.last().unwrap().span.end, source.len());
+ assert!(result.has_lexical_errors);
+ assert!(result.has_unmatched_braces);
+ let ready = declaration(&result, "ready");
+ assert_eq!(
+ text(source, ready.declaration.span),
+ "oa_actor ready \"Ready\" { description \"} stays text\"; }"
+ );
+ assert_eq!(
+ declaration(&result, "outer").declaration.span.end,
+ source.len()
+ );
+ assert_eq!(
+ declaration(&result, "pending").declaration.span.end,
+ source.len()
+ );
+ let unmatched_close = outline("}\n// brace in comment {\n", limits()).unwrap();
+ assert!(unmatched_close.has_unmatched_braces);
+ assert!(!unmatched_close.has_lexical_errors);
+}
+
+#[test]
+fn missing_body_syntax_is_unchecked_even_when_known_error_flags_are_clear() {
+ let source = "oa_actor a \"A\" id \"00000000-0000-0000-0000-000000000001\"";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 1);
+ assert!(!result.has_lexical_errors);
+ assert!(!result.has_unmatched_braces);
+ // There is deliberately no `valid` flag: the known-complete fact is scanning
+ // these bytes, not the missing body becoming legal or the draft being applied.
+ assert_eq!(result.source, source);
+}
+
+#[test]
+fn keyword_alias_and_collisions_keep_every_source_ordered_header() {
+ let source = "project root_model \"Model\" {\n oa_actor oa_activity \"First\" id \"00000000-0000-0000-0000-000000000001\" {}\n oa_actor oa_activity \"Second\" id \"00000000-0000-0000-0000-000000000001\" {}\n diagram view_one \"View\" {}\n node placement { diagram @view_one; }\n}";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 5);
+ let duplicates: Vec<_> = result
+ .declarations
+ .iter()
+ .filter(|d| d.declaration.alias == "oa_activity")
+ .collect();
+ assert_eq!(duplicates.len(), 2);
+ assert!(duplicates[0].alias_span.start < duplicates[1].alias_span.start);
+ assert_eq!(duplicates[0].declaration.id, duplicates[1].declaration.id);
+ assert!(
+ result
+ .tokens
+ .iter()
+ .any(|t| t.span == duplicates[0].alias_span && t.class == TokenClass::Keyword)
+ );
+ assert_eq!(
+ text(source, declaration(&result, "placement").kind_span),
+ "node"
+ );
+ assert_eq!(declaration(&result, "placement").declaration.id, None);
+}
+
+#[test]
+fn bounds_refuse_before_returning_a_partial_outline() {
+ let source = "oa_actor a \"A\" {}\noa_actor b \"B\" {}";
+ let admitted = outline(source, limits()).unwrap();
+ let exact = OutlineLimits {
+ max_source_bytes: source.len(),
+ max_tokens: admitted.tokens.len(),
+ max_declarations: 2,
+ };
+ assert_eq!(outline(source, exact).unwrap(), admitted);
+ for resource in [
+ OutlineResource::SourceBytes,
+ OutlineResource::Tokens,
+ OutlineResource::Declarations,
+ ] {
+ let mut lower = exact;
+ match resource {
+ OutlineResource::SourceBytes => lower.max_source_bytes -= 1,
+ OutlineResource::Tokens => lower.max_tokens -= 1,
+ OutlineResource::Declarations => lower.max_declarations -= 1,
+ }
+ let error = outline(source, lower).unwrap_err();
+ assert_eq!(error.resource, resource);
+ assert!(error.observed_at_least > error.limit);
+ assert!(source.is_char_boundary(error.offset));
+ if resource == OutlineResource::Declarations {
+ assert_eq!(error.offset, source.find("oa_actor b").unwrap());
+ }
+ }
+ let empty = outline(
+ "",
+ OutlineLimits {
+ max_source_bytes: 0,
+ max_tokens: 0,
+ max_declarations: 0,
+ },
+ )
+ .unwrap();
+ assert!(empty.tokens.is_empty() && empty.declarations.is_empty());
+ let unicode = "// 🌊\r\noa_actor a \"A\" {}";
+ let error = outline(
+ unicode,
+ OutlineLimits {
+ max_source_bytes: 4,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::SourceBytes);
+ assert_eq!(error.offset, 0);
+ let error = outline(
+ unicode,
+ OutlineLimits {
+ max_tokens: 1,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::Tokens);
+ assert_eq!(error.offset, unicode.find('\n').unwrap());
+}
+
+#[test]
+fn outline_ranges_follow_current_bytes_without_identity_stabilization() {
+ let old = "oa_actor a \"A\" {}\nsource @a;";
+ let current = format!("// 航海 🚒\r\n{old}");
+ let old_outline = outline(old, limits()).unwrap();
+ let current_outline = outline(¤t, limits()).unwrap();
+ let shift = current.len() - old.len();
+ let old_role = &old_outline.declarations[0];
+ let current_role = ¤t_outline.declarations[0];
+ assert_eq!(
+ current_role.alias_span.start,
+ old_role.alias_span.start + shift
+ );
+ assert_eq!(
+ current_role.kind_span.start,
+ old_role.kind_span.start + shift
+ );
+ assert_eq!(
+ current_role.declaration.span.end,
+ old_role.declaration.span.end + shift
+ );
+ assert_eq!(current_outline.source, current);
+ assert_eq!(old_outline.source, old);
+ assert_eq!(current_role.declaration.id, None);
+ assert!(!current_outline.source.contains(" id "));
+ // Workspace revision/stale-handle rejection belongs to the query/adapter,
+ // not a claim that an old owned span can be applied to any other string.
+}
+
+#[test]
+fn deeply_nested_bounds_use_the_same_iterative_walk_and_declaration_budget() {
+ let mut source = String::new();
+ for index in 0..600 {
+ source.push_str(&format!("oa_activity work_{index} \"Work\" {{\n"));
+ }
+ source.push_str(&"}\n".repeat(600));
+ let result = outline(&source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 600);
+ assert!(!result.has_unmatched_braces);
+ assert_eq!(
+ result.declarations[0].declaration.span.end,
+ source.len() - 1
+ );
+ let error = outline(
+ &source,
+ OutlineLimits {
+ max_declarations: 599,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::Declarations);
+ assert_eq!(error.observed_at_least, 600);
+ assert_eq!(error.offset, source.find("oa_activity work_599").unwrap());
+}
+
+#[test]
+fn noncanonical_uuid_spellings_have_no_outline_or_analysis_identity() {
+ for raw in [
+ "00000000000000000000000000000001",
+ "{00000000-0000-0000-0000-000000000001}",
+ "urn:uuid:00000000-0000-0000-0000-000000000001",
+ ] {
+ assert!(
+ Id::parse_str(raw).is_ok(),
+ "The UUID crate alone is too permissive"
+ );
+ assert!(!has_uuid_header_shape(raw));
+ let literal = format!("\"{raw}\"");
+ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let before = modern.clone();
+ let result = outline(&modern, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
+ assert_eq!(modern, before);
+ let errors = parse(&modern).unwrap_err();
+ assert!(
+ errors
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12")
+ && error.span.start == modern.find(&literal).unwrap())
+ );
+ // Intentional compatibility correction: old analyze yielded Some(Id(1))
+ // here despite lint rejecting the header. It now shares outline's None.
+ let analysis = analyze(&modern);
+ assert_eq!(
+ analysis
+ .declarations
+ .iter()
+ .find(|d| d.alias == "model")
+ .unwrap()
+ .id,
+ None
+ );
+ assert!(
+ analysis
+ .diagnostics
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12"))
+ );
+ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
+ let legacy_errors = legacy::parse(&legacy_source).unwrap_err();
+ assert!(
+ legacy_errors
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12"))
+ );
+ }
+}
+
+#[test]
+fn uppercase_and_json_escaped_header_ids_survive_both_parsers_and_outline() {
+ let expected = Id::from_u128(0xabcdef);
+ let canonical = expected.to_string();
+ let uppercase = canonical.to_uppercase();
+ assert_ne!(uppercase, canonical);
+ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
+ let literal = format!("\"{raw}\"");
+ let decoded: String = serde_json::from_str(&literal).unwrap();
+ assert!(has_uuid_header_shape(&decoded));
+ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let result = outline(&modern, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, Some(expected));
+ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
+ assert_eq!(result.source.as_bytes(), modern.as_bytes());
+ assert_eq!(parse(&modern).unwrap().id, expected);
+ let analysis = analyze(&modern);
+ assert_eq!(analysis.declarations[0].id, Some(expected));
+ assert!(
+ !analysis
+ .diagnostics
+ .iter()
+ .any(|error| error.severity == crate::validation::Severity::Error)
+ );
+ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
+ assert_eq!(legacy::parse(&legacy_source).unwrap().id, expected);
+ }
+}
+
+#[test]
+fn header_shape_still_requires_valid_hexadecimal_uuid_data() {
+ let raw = "00000000-0000-0000-0000-00000000000Z";
+ assert!(has_uuid_header_shape(raw));
+ assert!(Id::parse_str(raw).is_err());
+ let literal = format!("\"{raw}\"");
+ let source = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let result = outline(&source, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(text(&source, d.id_literal_span.unwrap()), literal);
+ assert!(
+ parse(&source)
+ .unwrap_err()
+ .iter()
+ .any(|error| error.message == "Invalid UUID identity.")
+ );
+ assert_eq!(analyze(&source).declarations[0].id, None);
+}
diff --git a/crates/oa-desktop/src/app.rs b/crates/oa-desktop/src/app.rs
index 8859132a..7e5eee23 100644
--- a/crates/oa-desktop/src/app.rs
+++ b/crates/oa-desktop/src/app.rs
@@ -7,6 +7,7 @@ mod diagram_selection_tests;
mod hosted;
#[cfg(test)]
mod review_navigation_tests;
+mod source_symbols;
#[cfg(test)]
mod transition_context_tests;
#[cfg(test)]
@@ -415,7 +416,12 @@ pub enum Message {
Linted(u64, Result, String>),
SourceDiagnostic(usize),
SourceRelated(SourceLocation),
- SourceViewport,
+ SourceViewport(Option),
+ SourceSymbols(
+ crate::source_navigation::SymbolContext,
+ crate::source_navigation::SymbolEvent,
+ ),
+ SourceSymbolsReady(crate::source_navigation::Completion),
SourceForSelection,
SourceNavigation(crate::source_navigation::Event),
Save(bool),
@@ -491,6 +497,7 @@ pub struct App {
pub source_jump: Option,
source_select_line: bool,
pub source_navigation: crate::source_navigation::State,
+ pub source_symbols: crate::source_navigation::SymbolState,
pub model_issues: Vec,
pub migration: Option,
source_version: u64,
@@ -596,6 +603,7 @@ impl App {
source_jump: None,
source_select_line: false,
source_navigation: crate::source_navigation::State::default(),
+ source_symbols: crate::source_navigation::SymbolState::default(),
model_issues,
migration: None,
source_version: 0,
@@ -649,6 +657,7 @@ impl App {
self.lint_requested = None;
}
fn show_source_file(&mut self, restore_cursor: bool) {
+ self.source_symbols.reveal = None;
self.source_tree.reveal(&self.source_workspace.active);
self.source_select_line = false;
let source = self.source_workspace.text().to_owned();
@@ -663,6 +672,9 @@ impl App {
.map(|b| b.cursor)
})
.flatten();
+ if let Some(cursor) = self.source_jump {
+ self.queue_source_reveal(cursor, false);
+ }
}
fn selected_architecture_placement(&self) -> Option {
self.active_diagram
@@ -916,6 +928,7 @@ impl App {
let canvas_size = self.canvas_size;
let source_version = self.source_version + 1;
let lint_running = self.lint_running;
+ let navigation_in_flight = self.source_symbols.in_flight;
let guide = std::mem::take(&mut self.guide);
let mut hosted = std::mem::take(&mut self.hosted);
hosted.leave_document();
@@ -927,6 +940,7 @@ impl App {
self.canvas_size = canvas_size;
self.source_version = source_version;
self.lint_running = lint_running;
+ self.source_symbols.in_flight = navigation_in_flight;
self.guide = guide;
self.path = path;
self.status = "Project ready".into();
@@ -1027,7 +1041,10 @@ impl App {
crate::model_menu::popup(self),
)
.into();
- crate::hosted::view::overlay(self, base)
+ crate::source_navigation::interaction::observe(
+ crate::hosted::view::overlay(self, base),
+ self.source_symbols.raw_interaction.clone(),
+ )
}
pub fn subscription(&self) -> Subscription {
Subscription::batch([
@@ -1035,8 +1052,14 @@ impl App {
window::close_requests().map(|id| Message::Request(Pending::Close(id))),
window::resize_events().map(|(_, size)| Message::WindowResized(size)),
architecture_authoring::subscription(self),
+ // Use one captured-Escape bridge: a focused Find input consumes
+ // Escape, while modal priority remains in the ordinary handler.
behavior::creation::subscription(
- self.behavior.creation.has_pending() || self.behavior.source_active(),
+ self.behavior.creation.has_pending()
+ || self.behavior.source_active()
+ || (self.page == Page::Text
+ && self.source_symbols.open
+ && self.architecture.is_none()),
)
.map(|()| Message::Escape),
iced::event::listen_with(|event, status, _| {
@@ -1083,6 +1106,30 @@ impl App {
])
}
pub fn update(&mut self, message: Message) -> Task {
+ if let Message::SourceSymbols(context, _) = &message
+ && (context.session != self.source_workspace.session()
+ || context.path != self.source_workspace.active)
+ {
+ return Task::none();
+ }
+ // Internal results and geometry do not constitute a new user intent.
+ // Process query completion even behind a modal to release its worker slot;
+ // its handler independently refuses navigation outside the live editor.
+ if let Message::SourceSymbolsReady(completion) = message {
+ return self.source_symbols_ready(completion);
+ }
+ if !matches!(
+ &message,
+ Message::CheckpointTick
+ | Message::CheckpointWritten(..)
+ | Message::Lint(_)
+ | Message::Linted(..)
+ | Message::SourceViewport(_)
+ | Message::WindowResized(_)
+ | Message::CanvasResized(_)
+ ) {
+ self.source_interaction();
+ }
if matches!(&message, Message::Connection(_))
&& self
.architecture
@@ -1147,7 +1194,7 @@ impl App {
| Message::CanvasResized(_)
| Message::Lint(_)
| Message::Linted(_, _)
- | Message::SourceViewport
+ | Message::SourceViewport(_)
| Message::Request(Pending::Close(_))
)
{
@@ -1193,7 +1240,7 @@ impl App {
| Message::Exported(_)
| Message::WindowResized(_)
| Message::CanvasResized(_)
- | Message::SourceViewport
+ | Message::SourceViewport(_)
)
{
return Task::none();
@@ -1231,7 +1278,7 @@ impl App {
| Message::CheckpointLoaded(..)
| Message::WindowResized(_)
| Message::CanvasResized(_)
- | Message::SourceViewport
+ | Message::SourceViewport(_)
| Message::Lint(_)
| Message::Linted(_, _)
| Message::Saved(_)
@@ -1807,6 +1854,9 @@ impl App {
self.status = "Drawing cancelled · Select / move is active".into();
}
self.source_navigation.open = false;
+ self.source_symbols.open = false;
+ self.source_symbols.results = None;
+ self.source_symbols.notice = None;
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
self.selected_bend = None;
}
@@ -2855,6 +2905,9 @@ impl App {
if self.path.is_none()
|| self.path.as_ref() == Some(&receipt.target)
{
+ if self.path.as_ref() != Some(&receipt.target) {
+ self.source_workspace.reset_navigation_session();
+ }
self.path = Some(receipt.target);
self.disk_project = Some(disk);
}
@@ -2917,14 +2970,10 @@ impl App {
}
}
Message::SourceRelated(location) => self.jump_to_source(location),
- Message::SourceViewport => {
- if let Some(cursor) = self.source_jump.take() {
- self.source_editor.move_to(cursor);
- if self.source_select_line && cursor.selection.is_none() {
- self.source_editor.perform(text_editor::Action::SelectLine);
- }
- return iced::widget::operation::focus("source-editor");
- }
+ Message::SourceViewport(ticket) => return self.source_viewport(ticket),
+ Message::SourceSymbols(_, event) => return self.source_symbol_event(event),
+ Message::SourceSymbolsReady(completion) => {
+ return self.source_symbols_ready(completion);
}
Message::ApplySource => {
self.apply_source();
@@ -2985,6 +3034,9 @@ impl App {
}
Event::Next(backwards) if self.page == Page::Text => {
self.source_jump = self.source_navigation.next(backwards);
+ if let Some(cursor) = self.source_jump {
+ self.queue_source_reveal(cursor, false);
+ }
if self.source_jump.is_none() {
self.status = "No matching source text.".into();
}
@@ -2995,8 +3047,7 @@ impl App {
.destination(self.source_editor.line_count())
{
Ok(cursor) => {
- self.source_jump = Some(cursor);
- self.source_select_line = true;
+ self.queue_source_reveal(cursor, true);
}
Err(error) => self.status = error,
},
@@ -3079,6 +3130,9 @@ impl App {
}
self.sync_source();
}
+ if self.path.as_ref() != Some(&receipt.path) {
+ self.source_workspace.reset_navigation_session();
+ }
self.disk_project = Some(disk);
self.path = Some(receipt.path);
self.hosted.session = None;
@@ -3638,7 +3692,7 @@ impl App {
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
self.path = Some(path);
self.source_workspace = Workspace::new(&disk.bundle, false);
- self.source_workspace.reachable = disk.reachable.clone();
+ self.source_workspace.set_reachable(disk.reachable.clone());
self.show_source_file(false);
self.source_diagnostics = error.diagnostics().to_vec();
self.source_version += 1;
@@ -3690,11 +3744,13 @@ impl App {
let column = prefix
.rfind('\n')
.map_or(offset, |start| offset - start - 1);
- self.source_jump = Some(text_editor::Cursor {
- position: text_editor::Position { line, column },
- selection: None,
- });
- self.source_select_line = true;
+ self.queue_source_reveal(
+ text_editor::Cursor {
+ position: text_editor::Position { line, column },
+ selection: None,
+ },
+ true,
+ );
}
fn create_source_file(&mut self) -> Result<(), String> {
diff --git a/crates/oa-desktop/src/app/hosted.rs b/crates/oa-desktop/src/app/hosted.rs
index 8b6fd16c..7cea3328 100644
--- a/crates/oa-desktop/src/app/hosted.rs
+++ b/crates/oa-desktop/src/app/hosted.rs
@@ -213,6 +213,14 @@ impl App {
self.pending = None;
return Task::none();
}
+ if self.path.is_some()
+ || self.hosted.session.as_ref().is_none_or(|session| {
+ session.resource_id != remote.resource_id
+ || session.connection.base() != request.connection.base()
+ })
+ {
+ self.source_workspace.reset_navigation_session();
+ }
self.hosted.session = Some(Session {
connection: request.connection.clone(),
resource_id: remote.resource_id,
@@ -345,6 +353,14 @@ impl App {
match self.document.apply_compiled((*remote.compiled).clone()) {
Ok(_) => {
self.document.mark_saved(remote.compiled.clone());
+ if self.path.is_some()
+ || self.hosted.session.as_ref().is_none_or(|session| {
+ session.resource_id != remote.resource_id
+ || session.connection.base() != connection.base()
+ })
+ {
+ self.source_workspace.reset_navigation_session();
+ }
self.hosted.session = Some(Session {
connection,
resource_id: remote.resource_id,
diff --git a/crates/oa-desktop/src/app/source_symbols.rs b/crates/oa-desktop/src/app/source_symbols.rs
new file mode 100644
index 00000000..9516a2b3
--- /dev/null
+++ b/crates/oa-desktop/src/app/source_symbols.rs
@@ -0,0 +1,448 @@
+//! Current draft navigation, isolated from model application and disk discovery.
+use super::*;
+use crate::source_navigation::{self as nav, query};
+use std::sync::atomic::Ordering;
+
+fn admission_notice(error: query::AdmissionError) -> String {
+ use nav::symbols::{BuildError, InputProblem, Resource};
+ let (limit, resource) = match error {
+ query::AdmissionError::Index(BuildError::Limit { limit, resource, .. }) => (limit, resource),
+ query::AdmissionError::Outline { error, .. } => (error.limit, match error.resource {
+ language::OutlineResource::SourceBytes => Resource::SourceBytes,
+ language::OutlineResource::Tokens => Resource::Tokens,
+ language::OutlineResource::Declarations => Resource::Records,
+ }),
+ query::AdmissionError::Index(BuildError::Input { problem, .. }) => return match problem {
+ InputProblem::NonCanonicalPath | InputProblem::DuplicateOrCaseCollidingPath =>
+ "Navigation needs distinct relative source paths. Check file names and include paths.".into(),
+ _ => "Navigation could not verify the current source locations. Correct source errors and try again.".into(),
+ },
+ };
+ let unit = match resource {
+ Resource::Files => "loaded files",
+ Resource::SourceBytes => "source bytes",
+ Resource::PathBytes => "bytes in file paths",
+ Resource::Tokens => "source tokens",
+ Resource::Records => "declarations and references",
+ };
+ format!(
+ "Navigation reached its limit of {limit} {unit}. Reduce the loaded source size and try again."
+ )
+}
+
+impl App {
+ pub fn source_symbol_context(&self) -> nav::SymbolContext {
+ nav::SymbolContext {
+ session: self.source_workspace.session(),
+ path: self.source_workspace.active.clone(),
+ }
+ }
+
+ pub fn source_symbol_message(&self, event: nav::SymbolEvent) -> Message {
+ Message::SourceSymbols(self.source_symbol_context(), event)
+ }
+ pub fn source_key(&self) -> query::SnapshotKey {
+ query::SnapshotKey {
+ workspace: self.source_workspace.session(),
+ revision: self.source_workspace.revision(),
+ }
+ }
+
+ fn with_source_input(&self, use_input: impl FnOnce(query::Input<'_>) -> T) -> T {
+ let files: Vec<_> = self
+ .source_workspace
+ .files
+ .iter()
+ .map(|(path, buffer)| query::DraftFile {
+ path,
+ text: &buffer.text,
+ })
+ .collect();
+ let accepted = self.document.bundle();
+ let validated: Vec<_> = accepted
+ .files
+ .iter()
+ .map(|(path, text)| query::DraftFile { path, text })
+ .collect();
+ use_input(query::Input {
+ key: self.source_key(),
+ entry: &self.source_workspace.entry,
+ files: &files,
+ applied: self.source_workspace.applied,
+ validation: self
+ .source_workspace
+ .applied
+ .then_some(query::ValidatedSources {
+ entry: &accepted.entry,
+ files: &validated,
+ }),
+ })
+ }
+
+ fn source_snapshot_matches(&self, snapshot: &query::Snapshot) -> bool {
+ snapshot.key() == self.source_key()
+ && self.with_source_input(|input| snapshot.matches(input))
+ }
+
+ pub fn source_navigation_available(&self) -> bool {
+ self.page == Page::Text
+ && !self.busy
+ && self.pending.is_none()
+ && self.file_prompt.is_none()
+ && !self.delete_pending
+ && self.workflow.is_none()
+ && !self.hosted.visible
+ && self.model_menu.is_none()
+ && self.architecture.is_none()
+ && !self.behavior.has_pending()
+ }
+
+ /// Mounted widget IDs change when another interaction supersedes a reveal.
+ /// A queued focus operation therefore cannot find a subsequently reused editor.
+ pub fn source_editor_id(&self) -> iced::widget::Id {
+ iced::widget::Id::from(format!(
+ "source-editor-{}-{}-{}",
+ self.source_workspace.session(),
+ self.source_workspace.active,
+ self.source_symbols.interaction
+ ))
+ }
+
+ pub fn source_reveal_ticket(&self) -> Option {
+ self.source_symbols
+ .reveal
+ .as_ref()
+ .map(|reveal| reveal.ticket)
+ }
+
+ pub(super) fn source_interaction(&mut self) {
+ self.source_symbols.interaction = Id::new_v4();
+ self.source_symbols.request = None;
+ self.source_symbols.reveal = None;
+ self.source_jump = None;
+ }
+
+ pub(super) fn queue_source_reveal(&mut self, cursor: text_editor::Cursor, select_line: bool) {
+ self.source_jump = Some(cursor);
+ self.source_select_line = select_line;
+ self.source_symbols.reveal = Some(nav::Reveal {
+ ticket: Id::new_v4(),
+ interaction: self.source_symbols.interaction,
+ raw_interaction: self.source_symbols.raw_interaction.load(Ordering::SeqCst),
+ snapshot: self.source_key(),
+ path: self.source_workspace.active.clone(),
+ });
+ }
+
+ pub(super) fn source_viewport(&mut self, ticket: Option) -> Task {
+ let Some(reveal) = &self.source_symbols.reveal else {
+ return Task::none();
+ };
+ if Some(reveal.ticket) != ticket
+ || reveal.interaction != self.source_symbols.interaction
+ || !nav::interaction::is_current(
+ &self.source_symbols.raw_interaction,
+ reveal.raw_interaction,
+ )
+ || reveal.snapshot != self.source_key()
+ || reveal.path != self.source_workspace.active
+ || !self.source_navigation_available()
+ {
+ return Task::none();
+ }
+ let raw_interaction = reveal.raw_interaction;
+ self.source_symbols.reveal = None;
+ if let Some(cursor) = self.source_jump.take() {
+ if !nav::exact_cursor(self.source_workspace.text(), cursor) {
+ return Task::none();
+ }
+ self.source_editor.move_to(cursor);
+ if self.source_select_line && cursor.selection.is_none() {
+ self.source_editor.perform(text_editor::Action::SelectLine);
+ }
+ return nav::interaction::guarded_focus(
+ self.source_editor_id(),
+ self.source_symbols.raw_interaction.clone(),
+ raw_interaction,
+ );
+ }
+ Task::none()
+ }
+
+ pub(super) fn source_symbol_event(&mut self, event: nav::SymbolEvent) -> Task {
+ if !self.source_navigation_available() {
+ return Task::none();
+ }
+ match event {
+ nav::SymbolEvent::Close => {
+ self.source_symbols.open = false;
+ self.source_symbols.results = None;
+ self.source_symbols.notice = None;
+ }
+ nav::SymbolEvent::Back => {
+ self.source_symbols.open = true;
+ self.source_symbol_back();
+ }
+ nav::SymbolEvent::Choose {
+ stamp,
+ reference,
+ index,
+ } => {
+ let Some(results) = self.source_symbols.results.clone() else {
+ return Task::none();
+ };
+ if results.stamp != stamp
+ || stamp.snapshot != self.source_key()
+ || self
+ .source_symbols
+ .cache
+ .as_ref()
+ .is_none_or(|cache| !self.source_snapshot_matches(cache))
+ {
+ self.source_symbols.notice =
+ Some("Source changed. Run navigation again for current locations.".into());
+ return Task::none();
+ }
+ let destination = if reference {
+ results
+ .references
+ .get(index)
+ .map(|row| (&row.path, row.position, row.end_position))
+ } else {
+ results
+ .definitions
+ .get(index)
+ .map(|row| (&row.path, row.position, row.end_position))
+ };
+ if let Some((path, start, end)) = destination {
+ self.source_symbol_jump(path, start, end);
+ }
+ }
+ nav::SymbolEvent::Query(action) => return self.start_source_query(action),
+ }
+ Task::none()
+ }
+
+ fn start_source_query(&mut self, action: query::Action) -> Task {
+ self.source_symbols.open = true;
+ if !nav::interaction::is_current(
+ &self.source_symbols.raw_interaction,
+ self.source_symbols.raw_interaction.load(Ordering::SeqCst),
+ ) {
+ self.source_symbols.notice = Some("Reopen this workspace to reset navigation.".into());
+ return Task::none();
+ }
+ if self.source_symbols.in_flight.is_some() {
+ self.source_symbols.notice = Some("Navigation is finishing. Try again shortly.".into());
+ return Task::none();
+ }
+ let cursor = self.source_editor.cursor();
+ let Some(offset) = nav::cursor_offset(self.source_workspace.text(), cursor) else {
+ self.source_symbols.notice =
+ Some("Place the cursor on an alias or its @ reference.".into());
+ return Task::none();
+ };
+ self.source_symbols.serial = match self.source_symbols.serial.checked_add(1) {
+ Some(serial) => serial,
+ None => {
+ self.source_workspace.reset_navigation_session();
+ self.source_symbols.back.clear();
+ self.source_symbols.cache = None;
+ 0
+ }
+ };
+ let request = nav::Request {
+ job: Id::new_v4(),
+ interaction: self.source_symbols.interaction,
+ raw_interaction: self.source_symbols.raw_interaction.load(Ordering::SeqCst),
+ stamp: query::QueryStamp {
+ snapshot: self.source_key(),
+ serial: self.source_symbols.serial,
+ },
+ path: self.source_workspace.active.clone(),
+ offset,
+ cursor,
+ action,
+ };
+ let cache = self
+ .source_symbols
+ .cache
+ .as_ref()
+ .filter(|cache| self.source_snapshot_matches(cache))
+ .cloned();
+ let input = if cache.is_none() {
+ match self
+ .with_source_input(|input| query::admit_owned(input, query::Limits::default()))
+ {
+ Ok(input) => Some(input),
+ Err(error) => {
+ self.source_symbols.notice = Some(admission_notice(error));
+ return Task::none();
+ }
+ }
+ } else {
+ None
+ };
+ self.source_symbols.in_flight = Some(request.job);
+ self.source_symbols.request = Some(request.clone());
+ self.source_symbols.results = None;
+ self.source_symbols.notice = None;
+ Task::perform(
+ async move {
+ let worker_request = request.clone();
+ let result = tokio::task::spawn_blocking(move || {
+ let snapshot = match cache {
+ Some(cache) => cache,
+ None => Arc::new(
+ input
+ .expect("admitted input for uncached request")
+ .capture()
+ .map_err(admission_notice)?,
+ ),
+ };
+ let results = snapshot
+ .query(query::Query {
+ stamp: worker_request.stamp,
+ path: &worker_request.path,
+ byte_offset: worker_request.offset,
+ action: worker_request.action,
+ })
+ .map_err(|error| match error {
+ query::QueryError::ResultEnvelopeLimit => "These result details exceed the display limit. Try literal Find for this alias.".to_owned(),
+ _ => "Navigation could not verify this location. Place the cursor on an alias and try again.".to_owned(),
+ })?;
+ Ok((snapshot, Arc::new(results)))
+ })
+ .await
+ .unwrap_or_else(|error| Err(format!("Navigation task failed: {error}")));
+ nav::Completion { request, result }
+ },
+ Message::SourceSymbolsReady,
+ )
+ }
+
+ pub(super) fn source_symbols_ready(&mut self, completion: nav::Completion) -> Task {
+ let request = completion.request;
+ if self.source_symbols.in_flight != Some(request.job) {
+ return Task::none();
+ }
+ self.source_symbols.in_flight = None;
+ let wanted = self
+ .source_symbols
+ .request
+ .take()
+ .is_some_and(|current| current.job == request.job)
+ && request.interaction == self.source_symbols.interaction
+ && request.stamp.snapshot == self.source_key()
+ && nav::interaction::is_current(
+ &self.source_symbols.raw_interaction,
+ request.raw_interaction,
+ )
+ && request.path == self.source_workspace.active
+ && request.cursor == self.source_editor.cursor()
+ && self.source_navigation_available();
+ if !wanted {
+ return Task::none();
+ }
+ match completion.result {
+ Ok((snapshot, results)) if self.source_snapshot_matches(&snapshot) => {
+ self.source_symbols.cache = Some(snapshot);
+ self.source_symbols.results = Some(results.clone());
+ if results.disposition == query::Disposition::Direct
+ && let Some(row) = results.definitions.first()
+ {
+ self.source_symbol_jump(&row.path, row.position, row.end_position);
+ }
+ }
+ Ok(_) => {
+ self.source_symbols.notice = Some("Source changed. Run navigation again.".into())
+ }
+ Err(error) => self.source_symbols.notice = Some(error),
+ }
+ Task::none()
+ }
+
+ fn source_symbol_jump(&mut self, path: &str, start: query::Position, end: query::Position) {
+ let cursor = text_editor::Cursor {
+ position: text_editor::Position {
+ line: start.line,
+ column: start.byte_column,
+ },
+ selection: Some(text_editor::Position {
+ line: end.line,
+ column: end.byte_column,
+ }),
+ };
+ if self
+ .source_workspace
+ .files
+ .get(path)
+ .is_none_or(|buffer| !nav::exact_cursor(&buffer.text, cursor))
+ {
+ self.source_symbols.notice =
+ Some("That source location is no longer available. Run navigation again.".into());
+ return;
+ }
+ if let Some(buffer) = self
+ .source_workspace
+ .files
+ .get(&self.source_workspace.active)
+ {
+ let origin = nav::HistoryEntry {
+ session: self.source_workspace.session(),
+ path: self.source_workspace.active.clone(),
+ generation: buffer.generation(),
+ cursor: self.source_editor.cursor(),
+ };
+ self.source_symbols
+ .back
+ .retain(|entry| entry.session == origin.session);
+ if self.source_symbols.back.len() == 64 {
+ self.source_symbols.back.remove(0);
+ }
+ self.source_symbols.back.push(origin);
+ }
+ self.source_workspace
+ .switch(path, self.source_editor.cursor());
+ self.show_source_file(false);
+ self.queue_source_reveal(cursor, false);
+ }
+
+ fn source_symbol_back(&mut self) {
+ while let Some(origin) = self.source_symbols.back.pop() {
+ if origin.session != self.source_workspace.session() {
+ continue;
+ }
+ let Some(buffer) = self.source_workspace.files.get(&origin.path) else {
+ self.source_symbols.notice = Some("The previous file is no longer loaded.".into());
+ return;
+ };
+ let unchanged = origin.generation == buffer.generation();
+ let cursor = if unchanged {
+ origin.cursor
+ } else if origin.path == self.source_workspace.active {
+ self.source_editor.cursor()
+ } else {
+ buffer.cursor
+ };
+ if !nav::exact_cursor(&buffer.text, cursor) {
+ self.source_symbols.notice = Some(
+ "The previous location changed. Open the file to choose a current position."
+ .into(),
+ );
+ return;
+ }
+ self.source_workspace
+ .switch(&origin.path, self.source_editor.cursor());
+ self.show_source_file(false);
+ self.queue_source_reveal(cursor, false);
+ self.source_symbols.notice =
+ (!unchanged).then(|| "This draft changed; its current cursor was restored.".into());
+ return;
+ }
+ self.source_symbols.notice = Some("No previous source location in this workspace.".into());
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/crates/oa-desktop/src/app/source_symbols/tests.rs b/crates/oa-desktop/src/app/source_symbols/tests.rs
new file mode 100644
index 00000000..2c3f342c
--- /dev/null
+++ b/crates/oa-desktop/src/app/source_symbols/tests.rs
@@ -0,0 +1,1201 @@
+use super::*;
+use crate::source_workspace::Workspace;
+use oa_core::source_project::SourceBundle;
+use std::collections::BTreeMap;
+use std::time::Instant;
+
+const DEFINITIONS: &str = "defs/活動.arcadia";
+const USES: &str = "uses.arcadia";
+
+fn fixture_bundle() -> SourceBundle {
+ SourceBundle {
+ entry: "project.arcadia".into(),
+ files: BTreeMap::from([
+ ("project.arcadia".into(), concat!(
+ "arcadia 7;\r\nproject model \"Navigation\" id \"00000000-0000-0000-0000-000000000001\" {\r\n",
+ " include \"defs/活動.arcadia\";\r\n include \"uses.arcadia\";\r\n}\r\n"
+ ).into()),
+ (DEFINITIONS.into(), concat!(
+ "// 船 and exact CRLF\r\n",
+ "oa_activity pump \"送水 🚀\" id \"00000000-0000-0000-0000-000000000002\" {}\r\n",
+ "oa_activity backup \"送水 🚀\" id \"00000000-0000-0000-0000-000000000004\" {}\r\n"
+ ).into()),
+ (USES.into(), concat!(
+ "// Reference names remain lexical\r\n",
+ "interaction first \"先\" id \"00000000-0000-0000-0000-000000000003\" {\r\n",
+ " description \"船 @pump\"; source @pump; target @backup;\r\n}\r\n",
+ "interaction second \"後\" id \"00000000-0000-0000-0000-000000000005\" {\r\n",
+ " source @backup; target @ // @pump is a comment\r\n pump;\r\n}\r\n"
+ ).into()),
+ ]),
+ }
+}
+
+fn fixture() -> App {
+ let mut app = App::with_project(Project::empty());
+ app.document = Document::from_bundle(fixture_bundle(), LIMITS).unwrap();
+ app.source_workspace = Workspace::new(app.document.bundle(), true);
+ app.selected = app.project().id;
+ app.refresh_draft();
+ app.page = Page::Text;
+ app.show_source_file(false);
+ show(&mut app, USES);
+ let text = app.source_workspace.text();
+ let offset = text.find("source @pump").unwrap() + "source @".len();
+ app.source_editor
+ .move_to(selection(text, offset, offset + 4, true));
+ assert!(app.source_navigation_available());
+ app
+}
+
+fn send(app: &mut App, message: Message) {
+ // Dropping the returned Task does not poll its worker/lint future. Completion
+ // below is deterministic and still traverses the real App message handler.
+ let _ = app.update(message);
+}
+
+fn symbol(app: &mut App, event: nav::SymbolEvent) {
+ let message = app.source_symbol_message(event);
+ send(app, message);
+}
+
+fn show(app: &mut App, path: &str) {
+ send(app, Message::SourceFile(path.into()));
+ reveal(app);
+}
+
+fn reveal(app: &mut App) {
+ if let Some(ticket) = app.source_reveal_ticket() {
+ send(app, Message::SourceViewport(Some(ticket)));
+ }
+}
+
+fn position(text: &str, offset: usize) -> text_editor::Position {
+ assert!(text.is_char_boundary(offset));
+ let prefix = &text[..offset];
+ text_editor::Position {
+ line: prefix.bytes().filter(|byte| *byte == b'\n').count(),
+ column: prefix
+ .rsplit_once('\n')
+ .map_or(prefix.len(), |(_, tail)| tail.len()),
+ }
+}
+
+fn selection(text: &str, start: usize, end: usize, reversed: bool) -> text_editor::Cursor {
+ let (start, end) = (position(text, start), position(text, end));
+ text_editor::Cursor {
+ position: if reversed { end } else { start },
+ selection: Some(if reversed { start } else { end }),
+ }
+}
+
+fn begin(app: &mut App, action: query::Action) -> nav::Request {
+ symbol(app, nav::SymbolEvent::Query(action));
+ let request = app
+ .source_symbols
+ .request
+ .clone()
+ .expect("request admitted");
+ assert_eq!(app.source_symbols.in_flight, Some(request.job));
+ request
+}
+
+fn completion(app: &App, request: nav::Request) -> nav::Completion {
+ let snapshot = Arc::new(app.with_source_input(|input| {
+ query::admit_owned(input, query::Limits::default())
+ .unwrap()
+ .capture()
+ .unwrap()
+ }));
+ let result = Arc::new(
+ snapshot
+ .query(query::Query {
+ stamp: request.stamp,
+ path: &request.path,
+ byte_offset: request.offset,
+ action: request.action,
+ })
+ .unwrap(),
+ );
+ nav::Completion {
+ request,
+ result: Ok((snapshot, result)),
+ }
+}
+
+fn finish(app: &mut App, action: query::Action) -> Arc {
+ let request = begin(app, action);
+ let ready = completion(app, request);
+ let results = ready.result.as_ref().unwrap().1.clone();
+ send(app, Message::SourceSymbolsReady(ready));
+ assert!(app.source_symbols.in_flight.is_none());
+ results
+}
+
+#[test]
+fn valid_definition_and_reference_choice_keep_whole_model_and_source_unchanged() {
+ let mut app = fixture();
+ let accepted = app.document.snapshot();
+ let raw = app.source_workspace.bundle();
+ let results = finish(&mut app, query::Action::Definition);
+ assert_eq!(results.disposition, query::Disposition::Direct);
+ assert_eq!(results.total_definitions, 1);
+ assert_eq!(app.source_workspace.active, DEFINITIONS);
+ assert_eq!(app.source_symbols.back.len(), 1);
+ let row = &results.definitions[0];
+ assert_eq!(row.declaration.alias, "pump");
+ assert_eq!(row.declaration.name.as_deref(), Some("送水 🚀"));
+ let expected = selection(&raw.files[DEFINITIONS], row.span.start, row.span.end, false);
+ assert_eq!(
+ app.source_jump,
+ Some(expected),
+ "jump waits for the matching viewport"
+ );
+ reveal(&mut app);
+ assert_eq!(app.source_editor.cursor(), expected);
+ let uses = finish(&mut app, query::Action::References);
+ assert_eq!(
+ uses.total_references, 2,
+ "comment/string references must not leak into App results"
+ );
+ assert_eq!(
+ app.source_workspace.active, DEFINITIONS,
+ "references require a choice"
+ );
+ let second = uses.references[1].clone();
+ symbol(
+ &mut app,
+ nav::SymbolEvent::Choose {
+ stamp: uses.stamp,
+ reference: true,
+ index: 1,
+ },
+ );
+ reveal(&mut app);
+ assert_eq!(app.source_workspace.active, USES);
+ assert_eq!(
+ app.source_editor.cursor(),
+ selection(&raw.files[USES], second.span.start, second.span.end, false)
+ );
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
+ assert_eq!(app.source_workspace.bundle(), raw);
+ assert_eq!(app.document.revision(), 0);
+ assert!(!app.document.can_undo() && !app.document.can_redo());
+}
+
+#[test]
+fn detached_duplicate_requires_an_explicit_choice_even_when_included_source_is_validated() {
+ let mut app = fixture();
+ let mut loaded = app.source_workspace.bundle();
+ loaded.files.insert(
+ "detached.arcadia".into(),
+ "oa_activity pump \"Different source\" id \"00000000-0000-0000-0000-000000000099\" {}\r\n"
+ .into(),
+ );
+ let cursor = app.source_editor.cursor();
+ app.source_workspace = Workspace::new(&loaded, true);
+ app.source_workspace.active = USES.into();
+ app.show_source_file(false);
+ app.source_editor.move_to(cursor);
+ let results = finish(&mut app, query::Action::Definition);
+ assert_eq!(results.total_definitions, 2);
+ assert_eq!(results.disposition, query::Disposition::Choices);
+ assert_eq!(app.source_workspace.active, USES);
+ assert!(app.source_symbols.back.is_empty());
+ let index = results
+ .definitions
+ .iter()
+ .position(|row| row.path == "detached.arcadia")
+ .unwrap();
+ assert_eq!(
+ results.definitions[index].membership,
+ query::Membership::Detached
+ );
+ symbol(
+ &mut app,
+ nav::SymbolEvent::Choose {
+ stamp: results.stamp,
+ reference: false,
+ index,
+ },
+ );
+ reveal(&mut app);
+ assert_eq!(app.source_workspace.active, "detached.arcadia");
+ assert_eq!(app.source_workspace.bundle(), loaded);
+ assert_eq!(
+ app.document.bundle().files.len(),
+ 3,
+ "choice must not adopt the detached source"
+ );
+}
+
+#[test]
+fn invalid_unapplied_drafts_navigate_without_apply_or_losing_either_history() {
+ let mut app = fixture();
+ let base = app.document.snapshot();
+ let id = app.project().id;
+ app.document
+ .execute(Command::Rename(id, "Accepted one".into()))
+ .unwrap();
+ let one = app.document.snapshot();
+ app.document
+ .execute(Command::Rename(id, "Accepted two".into()))
+ .unwrap();
+ let two = app.document.snapshot();
+ assert!(app.document.undo());
+ app.sync_source();
+ show(&mut app, USES);
+ let prior_raw = app.source_workspace.text().to_owned();
+ let invalid = format!("{prior_raw}\r\n? unfinished raw edit 雨\r\n");
+ let _ = app.update(Message::SourceEdit(text_editor::Action::SelectAll));
+ let _ = app.update(Message::SourceEdit(text_editor::Action::Edit(
+ text_editor::Edit::Paste(Arc::new(invalid)),
+ )));
+ app.source_workspace.applied = false;
+ let text = app.source_workspace.text();
+ let alias = text.find("source @pump").unwrap() + "source @".len();
+ app.source_editor
+ .move_to(selection(text, alias, alias + 4, true));
+ let raw = app.source_workspace.bundle();
+ let revision = app.document.revision();
+ let results = finish(&mut app, query::Action::Definition);
+ assert_eq!(results.disposition, query::Disposition::Choices);
+ assert!(results.scope.partial() && !results.scope.validated);
+ let index = results
+ .definitions
+ .iter()
+ .position(|row| row.declaration.alias == "pump")
+ .unwrap();
+ symbol(
+ &mut app,
+ nav::SymbolEvent::Choose {
+ stamp: results.stamp,
+ reference: false,
+ index,
+ },
+ );
+ reveal(&mut app);
+ symbol(&mut app, nav::SymbolEvent::Back);
+ reveal(&mut app);
+ assert!(Arc::ptr_eq(&one, &app.document.snapshot()));
+ assert_eq!(app.project(), &one.decoded.project);
+ assert_eq!(app.document.bundle(), &one.bundle);
+ assert_eq!(app.document.revision(), revision);
+ assert_eq!(app.source_workspace.bundle(), raw);
+ assert!(!app.source_workspace.applied);
+ assert!(app.document.can_undo() && app.document.can_redo());
+ // Actual round trips, not only Boolean history flags.
+ assert!(app.document.undo());
+ assert_eq!(app.document.bundle(), &base.bundle);
+ assert!(app.document.redo());
+ assert_eq!(app.document.bundle(), &one.bundle);
+ assert!(app.document.redo());
+ assert_eq!(app.document.bundle(), &two.bundle);
+ assert!(app.source_workspace.undo(false));
+ assert_eq!(app.source_workspace.text(), prior_raw);
+ assert!(app.source_workspace.undo(true));
+ assert_eq!(app.source_workspace.bundle(), raw);
+}
+
+#[test]
+fn completion_rejects_file_edit_session_cursor_interaction_and_raw_input_changes() {
+ for change in 0..7 {
+ let mut app = fixture();
+ let request = begin(&mut app, query::Action::Definition);
+ let ready = completion(&app, request.clone());
+ let accepted = app.document.snapshot();
+ match change {
+ 0 => show(&mut app, "project.arcadia"),
+ 1 => send(
+ &mut app,
+ Message::SourceEdit(text_editor::Action::Edit(text_editor::Edit::Insert('x'))),
+ ),
+ 2 => app.source_workspace.reset_navigation_session(),
+ 3 => app.source_editor.move_to(text_editor::Cursor {
+ position: text_editor::Position { line: 0, column: 0 },
+ selection: None,
+ }),
+ 4 => app.source_interaction(),
+ 5 => {
+ app.source_symbols
+ .raw_interaction
+ .fetch_add(1, Ordering::SeqCst);
+ }
+ _ => app.page = Page::Diagram,
+ }
+ let after = app.source_workspace.bundle();
+ let active = app.source_workspace.active.clone();
+ let cursor = app.source_editor.cursor();
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ assert!(
+ app.source_symbols.in_flight.is_none(),
+ "case {change}: release worker slot"
+ );
+ assert!(
+ app.source_symbols.results.is_none(),
+ "case {change}: stale result surfaced"
+ );
+ assert!(app.source_symbols.back.is_empty());
+ assert_eq!(app.source_workspace.active, active);
+ assert_eq!(app.source_editor.cursor(), cursor);
+ assert_eq!(app.source_workspace.bundle(), after);
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
+ }
+}
+
+#[test]
+fn unknown_job_cannot_release_current_worker_and_old_duplicate_cannot_replace_new_results() {
+ let mut app = fixture();
+ let request = begin(&mut app, query::Action::References);
+ let ready = completion(&app, request.clone());
+ let mut unrelated = ready.clone();
+ unrelated.request.job = Id::new_v4();
+ send(&mut app, Message::SourceSymbolsReady(unrelated));
+ assert_eq!(app.source_symbols.in_flight, Some(request.job));
+ assert!(app.source_symbols.results.is_none());
+ send(&mut app, Message::SourceSymbolsReady(ready.clone()));
+ let newer = begin(&mut app, query::Action::References);
+ let newer_ready = completion(&app, newer.clone());
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ assert_eq!(app.source_symbols.in_flight, Some(newer.job));
+ send(&mut app, Message::SourceSymbolsReady(newer_ready));
+ assert_eq!(
+ app.source_symbols.results.as_ref().unwrap().stamp,
+ newer.stamp
+ );
+}
+
+#[test]
+fn close_during_busy_hides_panel_and_completion_does_not_reopen_it() {
+ let mut app = fixture();
+ let request = begin(&mut app, query::Action::Definition);
+ let ready = completion(&app, request.clone());
+ assert!(app.source_symbols.open);
+ symbol(&mut app, nav::SymbolEvent::Close);
+ assert!(!app.source_symbols.open);
+ assert_eq!(app.source_symbols.in_flight, Some(request.job));
+ assert!(app.source_symbols.request.is_none());
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ assert!(!app.source_symbols.open);
+ assert!(app.source_symbols.in_flight.is_none());
+ assert!(app.source_symbols.results.is_none());
+ assert_eq!(app.source_workspace.active, USES);
+ let next = begin(&mut app, query::Action::References);
+ assert_ne!(next.job, request.job);
+ assert!(app.source_symbols.open);
+}
+
+#[test]
+fn matching_reveal_ticket_is_required_and_raw_input_invalidates_pending_reveal() {
+ let mut app = fixture();
+ finish(&mut app, query::Action::Definition);
+ let ticket = app.source_reveal_ticket().unwrap();
+ let destination = app.source_jump.unwrap();
+ let before = app.source_editor.cursor();
+ assert_ne!(before, destination);
+ for wrong in [None, Some(Id::new_v4())] {
+ send(&mut app, Message::SourceViewport(wrong));
+ assert_eq!(app.source_editor.cursor(), before);
+ assert_eq!(app.source_reveal_ticket(), Some(ticket));
+ }
+ app.source_symbols
+ .raw_interaction
+ .fetch_add(1, Ordering::SeqCst);
+ send(&mut app, Message::SourceViewport(Some(ticket)));
+ assert_eq!(
+ app.source_editor.cursor(),
+ before,
+ "raw input arrived before App event dispatch"
+ );
+ app.queue_source_reveal(destination, false);
+ let fresh = app.source_reveal_ticket().unwrap();
+ assert_ne!(ticket, fresh);
+ send(&mut app, Message::SourceViewport(Some(ticket)));
+ assert_eq!(app.source_editor.cursor(), before);
+ send(&mut app, Message::SourceViewport(Some(fresh)));
+ assert_eq!(app.source_editor.cursor(), destination);
+ assert!(app.source_reveal_ticket().is_none() && app.source_jump.is_none());
+}
+
+#[test]
+fn escape_dismisses_results_and_cancels_busy_navigation_without_changing_sources() {
+ for pending in [false, true] {
+ let mut app = fixture();
+ let accepted = app.document.snapshot();
+ let raw = app.source_workspace.bundle();
+ let cursor = app.source_editor.cursor();
+ let request = begin(&mut app, query::Action::References);
+ let ready = completion(&app, request.clone());
+ if !pending {
+ send(&mut app, Message::SourceSymbolsReady(ready.clone()));
+ assert!(app.source_symbols.results.is_some());
+ }
+ app.source_navigation.open = true;
+ send(&mut app, Message::Escape);
+ assert!(!app.source_symbols.open && !app.source_navigation.open);
+ assert!(app.source_symbols.results.is_none());
+ assert!(app.source_symbols.notice.is_none());
+ assert!(app.source_symbols.request.is_none());
+ if pending {
+ assert_eq!(app.source_symbols.in_flight, Some(request.job));
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ }
+ assert!(app.source_symbols.in_flight.is_none());
+ assert!(!app.source_symbols.open);
+ assert!(app.source_symbols.results.is_none());
+ assert_eq!(app.source_workspace.active, USES);
+ assert_eq!(app.source_editor.cursor(), cursor);
+ assert_eq!(app.source_workspace.bundle(), raw);
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
+ assert!(app.source_symbols.back.is_empty());
+ assert!(!app.document.can_undo() && !app.document.can_redo());
+ }
+}
+
+#[test]
+fn escape_dismisses_modal_before_underlying_source_results() {
+ let mut app = fixture();
+ let accepted = app.document.snapshot();
+ let raw = app.source_workspace.bundle();
+ let results = finish(&mut app, query::Action::References);
+ app.hosted.visible = true;
+ send(&mut app, Message::Escape);
+ assert!(!app.hosted.visible);
+ assert!(app.source_symbols.open);
+ assert!(Arc::ptr_eq(
+ app.source_symbols.results.as_ref().unwrap(),
+ &results
+ ));
+ send(&mut app, Message::Escape);
+ assert!(!app.source_symbols.open);
+ assert!(app.source_symbols.results.is_none());
+ assert_eq!(app.source_workspace.bundle(), raw);
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
+}
+
+/// Feed the real App subscription recipes the status produced by a mounted
+/// TextInput. Poll only immediately available output: no timer wait, worker,
+/// file dialog or App task is executed by this helper.
+fn captured_input_subscription_messages(app: &App) -> Vec {
+ use iced::advanced::{Layout, Shell, layout, subscription, widget};
+ use iced::futures::{StreamExt, stream, task::noop_waker_ref};
+ use iced::keyboard::{self, key::Named};
+ use std::task::{Context, Poll};
+
+ let mut input: iced::Element<'_, (), iced::Theme, ()> =
+ iced::widget::text_input("Find / modal input", "Retain exact text 雨")
+ .on_input(|_| ())
+ .into();
+ let mut tree = widget::Tree::new(input.as_widget());
+ let size = iced::Size::new(300., 40.);
+ let node =
+ input
+ .as_widget_mut()
+ .layout(&mut tree, &(), &layout::Limits::new(iced::Size::ZERO, size));
+ tree.state
+ .downcast_mut::>()
+ .focus();
+ let event = iced::Event::Keyboard(keyboard::Event::KeyPressed {
+ key: keyboard::Key::Named(Named::Escape),
+ modified_key: keyboard::Key::Named(Named::Escape),
+ physical_key: keyboard::key::Physical::Code(keyboard::key::Code::Escape),
+ location: keyboard::Location::Standard,
+ modifiers: keyboard::Modifiers::empty(),
+ text: None,
+ repeat: false,
+ });
+ let mut input_messages = Vec::new();
+ let mut shell = Shell::new(&mut input_messages);
+ input.as_widget_mut().update(
+ &mut tree,
+ &event,
+ Layout::new(&node),
+ iced::mouse::Cursor::Unavailable,
+ &(),
+ &mut iced::advanced::clipboard::Null,
+ &mut shell,
+ &iced::Rectangle::new(iced::Point::ORIGIN, size),
+ );
+ assert!(shell.is_event_captured());
+ assert!(input_messages.is_empty());
+ assert!(
+ !tree
+ .state
+ .downcast_ref::>()
+ .is_focused()
+ );
+ let event = subscription::Event::Interaction {
+ window: iced::window::Id::unique(),
+ event,
+ status: iced::event::Status::Captured,
+ };
+ let runtime = tokio::runtime::Builder::new_current_thread()
+ .enable_time()
+ .build()
+ .unwrap();
+ let _entered = runtime.enter();
+ let mut messages = Vec::new();
+ for recipe in subscription::into_recipes(app.subscription()) {
+ let mut output = recipe.stream(Box::pin(stream::iter([event.clone()])));
+ let mut context = Context::from_waker(noop_waker_ref());
+ while let Poll::Ready(Some(message)) = output.poll_next_unpin(&mut context) {
+ assert!(
+ !matches!(message, Message::CheckpointTick),
+ "timer must remain pending"
+ );
+ messages.push(message);
+ }
+ }
+ messages
+}
+
+#[test]
+fn captured_find_and_modal_escape_use_one_actual_subscription_and_preserve_pending_work() {
+ for pending in [false, true] {
+ for modal in 0..4 {
+ let mut app = fixture();
+ let accepted = app.document.snapshot();
+ let raw = app.source_workspace.bundle();
+ let cursor = app.source_editor.cursor();
+ let request = begin(&mut app, query::Action::References);
+ let ready = completion(&app, request.clone());
+ if !pending {
+ send(&mut app, Message::SourceSymbolsReady(ready.clone()));
+ }
+ let results = app.source_symbols.results.clone();
+ app.source_navigation.open = true;
+ match modal {
+ 0 => {}
+ 1 => app.hosted.visible = true,
+ 2 => {
+ app.file_prompt = Some(FilePrompt {
+ operation: FileOperation::Save,
+ path: "unchanged-destination.arcadia".into(),
+ overwrite: false,
+ })
+ }
+ 3 => app.pending = Some(Pending::Walkthrough),
+ _ => unreachable!(),
+ }
+ let mut messages = captured_input_subscription_messages(&app);
+ assert_eq!(messages.len(), 1, "pending={pending}, modal={modal}");
+ assert!(matches!(messages[0], Message::Escape));
+ send(&mut app, messages.remove(0));
+ if modal != 0 {
+ assert!(!app.hosted.visible && app.file_prompt.is_none() && app.pending.is_none());
+ assert!(app.source_symbols.open && app.source_navigation.open);
+ match (&results, &app.source_symbols.results) {
+ (Some(before), Some(after)) => assert!(Arc::ptr_eq(before, after)),
+ (None, None) => {}
+ _ => panic!("modal Escape must retain the underlying results"),
+ }
+ let mut messages = captured_input_subscription_messages(&app);
+ assert_eq!(messages.len(), 1);
+ assert!(matches!(messages[0], Message::Escape));
+ send(&mut app, messages.remove(0));
+ }
+ assert!(!app.source_symbols.open && !app.source_navigation.open);
+ assert!(app.source_symbols.results.is_none() && app.source_symbols.request.is_none());
+ if pending {
+ assert_eq!(app.source_symbols.in_flight, Some(request.job));
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ }
+ assert!(app.source_symbols.in_flight.is_none());
+ assert!(!app.source_symbols.open && app.source_symbols.results.is_none());
+ assert_eq!(app.source_workspace.active, USES);
+ assert_eq!(app.source_editor.cursor(), cursor);
+ assert_eq!(app.source_workspace.bundle(), raw);
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
+ assert!(!app.document.can_undo() && !app.document.can_redo());
+ assert!(
+ captured_input_subscription_messages(&app).is_empty(),
+ "closed panel opts out"
+ );
+ }
+ }
+}
+
+#[test]
+fn captured_subscription_does_not_duplicate_existing_behavior_or_architecture_routes() {
+ let mut app = fixture();
+ app.source_symbols.open = true;
+ let project = app.project().clone();
+ assert!(
+ app.behavior
+ .creation
+ .update(&project, crate::behavior::creation::Event::Open(Layer::Oa))
+ .unwrap()
+ .is_none()
+ );
+ assert!(app.behavior.creation.has_pending());
+ let messages = captured_input_subscription_messages(&app);
+ assert_eq!(
+ messages.len(),
+ 1,
+ "behavior and symbols share the same bridge"
+ );
+ assert!(matches!(messages[0], Message::Escape));
+
+ let mut app = App::with_project(Project::empty());
+ send(&mut app, Message::AddDiagram);
+ send(
+ &mut app,
+ Message::Architecture(crate::app::architecture_authoring::Event::Start(
+ Kind::Actor,
+ Some(oa_core::Position { x: 120., y: 160. }),
+ )),
+ );
+ assert!(app.architecture.as_ref().unwrap().inline());
+ // A retained Text panel must not add a second route to inline architecture.
+ app.page = Page::Text;
+ app.source_symbols.open = true;
+ let messages = captured_input_subscription_messages(&app);
+ assert_eq!(messages.len(), 1);
+ assert!(matches!(
+ messages[0],
+ Message::Architecture(crate::app::architecture_authoring::Event::CapturedEscape)
+ ));
+ app.architecture = None;
+ app.page = Page::Behavior;
+ assert!(
+ captured_input_subscription_messages(&app).is_empty(),
+ "off-page symbols opt out"
+ );
+}
+
+#[test]
+fn saturated_raw_clock_never_authorizes_completion_or_reveal() {
+ let mut app = fixture();
+ app.source_symbols
+ .raw_interaction
+ .store(u64::MAX, Ordering::SeqCst);
+ symbol(&mut app, nav::SymbolEvent::Query(query::Action::Definition));
+ if let Some(request) = app.source_symbols.request.clone() {
+ let ready = completion(&app, request);
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ }
+ assert!(app.source_symbols.results.is_none());
+ assert_eq!(app.source_workspace.active, USES);
+ let before = app.source_editor.cursor();
+ let cursor = text_editor::Cursor {
+ position: text_editor::Position { line: 0, column: 0 },
+ selection: None,
+ };
+ app.queue_source_reveal(cursor, false);
+ reveal(&mut app);
+ assert_eq!(app.source_editor.cursor(), before);
+}
+
+#[test]
+fn back_restores_exact_reversed_utf8_selection_and_crlf_source() {
+ let mut app = fixture();
+ let origin = app.source_editor.cursor();
+ assert!(
+ origin.position.column > origin.selection.unwrap().column,
+ "reverse selection fixture"
+ );
+ let raw = app.source_workspace.bundle();
+ assert!(raw.files[USES].contains("\r\n"));
+ finish(&mut app, query::Action::Definition);
+ reveal(&mut app);
+ symbol(&mut app, nav::SymbolEvent::Back);
+ assert_eq!(app.source_workspace.active, USES);
+ assert_eq!(app.source_jump, Some(origin));
+ reveal(&mut app);
+ assert_eq!(app.source_editor.cursor(), origin);
+ assert_eq!(app.source_workspace.bundle(), raw);
+ assert!(app.source_symbols.back.is_empty());
+}
+
+#[test]
+fn back_after_origin_edit_uses_its_current_cursor_instead_of_the_old_range() {
+ let mut app = fixture();
+ let old_origin = app.source_editor.cursor();
+ finish(&mut app, query::Action::Definition);
+ reveal(&mut app);
+ show(&mut app, USES);
+ send(
+ &mut app,
+ Message::SourceEdit(text_editor::Action::SelectAll),
+ );
+ send(
+ &mut app,
+ Message::SourceEdit(text_editor::Action::Edit(text_editor::Edit::Paste(
+ Arc::new("// changed\n@pump\n".into()),
+ ))),
+ );
+ let current = text_editor::Cursor {
+ position: text_editor::Position { line: 1, column: 5 },
+ selection: Some(text_editor::Position { line: 1, column: 1 }),
+ };
+ app.source_editor.move_to(current);
+ show(&mut app, DEFINITIONS);
+ assert_ne!(old_origin, current);
+ let changed = app.source_workspace.bundle();
+ symbol(&mut app, nav::SymbolEvent::Back);
+ assert_eq!(app.source_jump, Some(current));
+ reveal(&mut app);
+ assert_eq!(app.source_editor.cursor(), current);
+ assert_eq!(app.source_workspace.bundle(), changed);
+ assert!(
+ app.source_symbols
+ .notice
+ .as_deref()
+ .is_some_and(|notice| notice.contains("changed"))
+ );
+}
+
+#[test]
+fn reset_retains_worker_slot_until_old_completion_then_admits_new_session_query() {
+ let mut app = fixture();
+ let request = begin(&mut app, query::Action::Definition);
+ let ready = completion(&app, request.clone());
+ let old_session = app.source_workspace.session();
+ let project = app.project().clone();
+ app.reset(project, None);
+ assert_ne!(app.source_workspace.session(), old_session);
+ assert_eq!(app.source_symbols.in_flight, Some(request.job));
+ assert!(app.source_symbols.request.is_none());
+ app.page = Page::Text;
+ let text = app.source_workspace.text();
+ let alias = text.find("project model").unwrap() + "project ".len();
+ app.source_editor
+ .move_to(selection(text, alias, alias + 5, false));
+ symbol(&mut app, nav::SymbolEvent::Query(query::Action::Definition));
+ assert_eq!(app.source_symbols.in_flight, Some(request.job));
+ assert!(
+ app.source_symbols.request.is_none(),
+ "reset must not create a second active worker"
+ );
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ assert!(app.source_symbols.in_flight.is_none());
+ assert!(app.source_symbols.results.is_none());
+ let fresh = begin(&mut app, query::Action::Definition);
+ assert_eq!(
+ fresh.stamp.snapshot.workspace,
+ app.source_workspace.session()
+ );
+ assert_ne!(fresh.stamp.snapshot.workspace, old_session);
+ let ready = completion(&app, fresh);
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ assert!(app.source_symbols.results.is_some());
+}
+
+#[test]
+fn back_keeps_the_latest_64_actual_locations_without_reusing_dropped_history() {
+ let mut app = fixture();
+ let text: String = (0..80).map(|line| format!("// 行 {line}\r\n")).collect();
+ let loaded = SourceBundle {
+ entry: "trail.arcadia".into(),
+ files: BTreeMap::from([("trail.arcadia".into(), text)]),
+ };
+ app.source_workspace = Workspace::new(&loaded, false);
+ app.show_source_file(false);
+ let cursor = |line| text_editor::Cursor {
+ position: text_editor::Position { line, column: 0 },
+ selection: Some(text_editor::Position { line, column: 1 }),
+ };
+ app.source_editor.move_to(cursor(0));
+ for line in 1..=66 {
+ app.source_symbol_jump(
+ "trail.arcadia",
+ query::Position {
+ line,
+ byte_column: 0,
+ },
+ query::Position {
+ line,
+ byte_column: 1,
+ },
+ );
+ reveal(&mut app);
+ assert_eq!(app.source_editor.cursor(), cursor(line));
+ }
+ assert_eq!(app.source_symbols.back.len(), 64);
+ assert_eq!(app.source_symbols.back.first().unwrap().cursor, cursor(2));
+ for line in (2..=65).rev() {
+ symbol(&mut app, nav::SymbolEvent::Back);
+ reveal(&mut app);
+ assert_eq!(app.source_editor.cursor(), cursor(line));
+ }
+ assert!(app.source_symbols.back.is_empty());
+ symbol(&mut app, nav::SymbolEvent::Back);
+ assert_eq!(app.source_editor.cursor(), cursor(2));
+ assert_eq!(app.source_workspace.bundle(), loaded);
+}
+
+/// Replay frozen widget messages without rebuilding their context. In a real
+/// Iced batch these can follow a reset or file switch before the next view diff.
+fn assert_stale_envelopes_preserve_current(app: &mut App, messages: &[Message]) {
+ let interaction = app.source_symbols.interaction;
+ let raw_interaction = app.source_symbols.raw_interaction.load(Ordering::SeqCst);
+ let request = app.source_symbols.request.as_ref().map(|request| {
+ (
+ request.job,
+ request.interaction,
+ request.raw_interaction,
+ request.stamp,
+ request.path.clone(),
+ request.offset,
+ request.cursor,
+ request.action,
+ )
+ });
+ let in_flight = app.source_symbols.in_flight;
+ let serial = app.source_symbols.serial;
+ let reveal = app.source_symbols.reveal.as_ref().map(|reveal| {
+ (
+ reveal.ticket,
+ reveal.interaction,
+ reveal.raw_interaction,
+ reveal.snapshot,
+ reveal.path.clone(),
+ )
+ });
+ let jump = (app.source_jump, app.source_select_line);
+ let back: Vec<_> = app
+ .source_symbols
+ .back
+ .iter()
+ .map(|entry| {
+ (
+ entry.session,
+ entry.path.clone(),
+ entry.generation,
+ entry.cursor,
+ )
+ })
+ .collect();
+ let results = app.source_symbols.results.clone();
+ let cache = app.source_symbols.cache.clone();
+ let panel = (app.source_symbols.open, app.source_symbols.notice.clone());
+ let key = app.source_key();
+ let active = app.source_workspace.active.clone();
+ let cursor = app.source_editor.cursor();
+ let editor = app.source_editor.text();
+ let raw = app.source_workspace.bundle();
+ let accepted = app.document.snapshot();
+ let history = (
+ app.document.revision(),
+ app.document.can_undo(),
+ app.document.can_redo(),
+ app.document.dirty(),
+ );
+ for message in messages.iter().cloned() {
+ send(app, message);
+ assert_eq!(
+ app.source_symbols.interaction, interaction,
+ "reject stale context before invalidating current intent"
+ );
+ assert_eq!(
+ app.source_symbols.raw_interaction.load(Ordering::SeqCst),
+ raw_interaction
+ );
+ assert_eq!(
+ app.source_symbols.request.as_ref().map(|request| (
+ request.job,
+ request.interaction,
+ request.raw_interaction,
+ request.stamp,
+ request.path.clone(),
+ request.offset,
+ request.cursor,
+ request.action,
+ )),
+ request
+ );
+ assert_eq!(
+ (app.source_symbols.in_flight, app.source_symbols.serial),
+ (in_flight, serial)
+ );
+ assert_eq!(
+ app.source_symbols.reveal.as_ref().map(|reveal| (
+ reveal.ticket,
+ reveal.interaction,
+ reveal.raw_interaction,
+ reveal.snapshot,
+ reveal.path.clone(),
+ )),
+ reveal
+ );
+ assert_eq!((app.source_jump, app.source_select_line), jump);
+ assert_eq!(
+ app.source_symbols
+ .back
+ .iter()
+ .map(|entry| (
+ entry.session,
+ entry.path.clone(),
+ entry.generation,
+ entry.cursor,
+ ))
+ .collect::>(),
+ back
+ );
+ assert_eq!(
+ (app.source_symbols.open, app.source_symbols.notice.clone()),
+ panel
+ );
+ match (&results, &app.source_symbols.results) {
+ (Some(before), Some(after)) => assert!(Arc::ptr_eq(before, after)),
+ (None, None) => {}
+ _ => panic!("stale envelope replaced or cleared current results"),
+ }
+ match (&cache, &app.source_symbols.cache) {
+ (Some(before), Some(after)) => assert!(Arc::ptr_eq(before, after)),
+ (None, None) => {}
+ _ => panic!("stale envelope replaced or cleared current snapshot"),
+ }
+ assert_eq!(app.source_key(), key);
+ assert_eq!(app.source_workspace.active, active);
+ assert_eq!(app.source_editor.cursor(), cursor);
+ assert_eq!(app.source_editor.text(), editor);
+ assert_eq!(app.source_workspace.bundle(), raw);
+ assert!(Arc::ptr_eq(&accepted, &app.document.snapshot()));
+ assert_eq!(app.project(), &accepted.decoded.project);
+ assert_eq!(app.document.bundle(), &accepted.bundle);
+ assert_eq!(
+ (
+ app.document.revision(),
+ app.document.can_undo(),
+ app.document.can_redo(),
+ app.document.dirty()
+ ),
+ history
+ );
+ }
+}
+
+fn stale_envelope_trial(reset: bool) {
+ let mut app = fixture();
+ if reset {
+ show(&mut app, "project.arcadia");
+ let text = app.source_workspace.text();
+ let alias = text.find("project model").unwrap() + "project ".len();
+ app.source_editor
+ .move_to(selection(text, alias, alias + 5, false));
+ }
+ let old_results = finish(&mut app, query::Action::References);
+ let old_context = app.source_symbol_context();
+ let old_messages = [
+ app.source_symbol_message(nav::SymbolEvent::Query(query::Action::Definition)),
+ app.source_symbol_message(nav::SymbolEvent::Back),
+ app.source_symbol_message(nav::SymbolEvent::Close),
+ app.source_symbol_message(nav::SymbolEvent::Choose {
+ stamp: old_results.stamp,
+ reference: false,
+ index: 0,
+ }),
+ ];
+ if reset {
+ let project = app.project().clone();
+ app.reset(project, None);
+ app.page = Page::Text;
+ let new_context = app.source_symbol_context();
+ assert_eq!(
+ new_context.path, old_context.path,
+ "same path isolates the session guard"
+ );
+ assert_ne!(new_context.session, old_context.session);
+ } else {
+ show(&mut app, DEFINITIONS);
+ let new_context = app.source_symbol_context();
+ assert_ne!(new_context.path, old_context.path);
+ assert_eq!(
+ new_context.session, old_context.session,
+ "file switch isolates the path guard"
+ );
+ assert_eq!(app.source_key(), old_results.stamp.snapshot);
+ }
+ let text = app.source_workspace.text();
+ let (alias, length) = if reset {
+ (text.find("project model").unwrap() + "project ".len(), 5)
+ } else {
+ (
+ text.find("oa_activity pump").unwrap() + "oa_activity ".len(),
+ 4,
+ )
+ };
+ let cursor = selection(text, alias, alias + length, false);
+ app.source_editor.move_to(cursor);
+ // A real jump creates useful current Back history before the next request.
+ let path = app.source_workspace.active.clone();
+ app.source_symbol_jump(
+ &path,
+ query::Position {
+ line: cursor.position.line,
+ byte_column: cursor.position.column,
+ },
+ query::Position {
+ line: cursor.selection.unwrap().line,
+ byte_column: cursor.selection.unwrap().column,
+ },
+ );
+ reveal(&mut app);
+ let current = begin(&mut app, query::Action::References);
+ let ready = completion(&app, current.clone());
+ app.queue_source_reveal(cursor, false);
+ assert_stale_envelopes_preserve_current(&mut app, &old_messages);
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ assert_eq!(
+ app.source_symbols.results.as_ref().unwrap().stamp,
+ current.stamp
+ );
+ assert!(app.source_symbols.in_flight.is_none());
+ // Replay once more with no busy-slot fallback and visible current results.
+ assert_stale_envelopes_preserve_current(&mut app, &old_messages);
+ let interaction = app.source_symbols.interaction;
+ symbol(&mut app, nav::SymbolEvent::Close);
+ assert_ne!(app.source_symbols.interaction, interaction);
+ assert!(!app.source_symbols.open && app.source_symbols.results.is_none());
+ let next = begin(&mut app, query::Action::References);
+ assert_ne!(
+ next.job, current.job,
+ "a freshly wrapped current-context Query remains usable"
+ );
+ let ready = completion(&app, next);
+ send(&mut app, Message::SourceSymbolsReady(ready));
+ assert!(app.source_symbols.results.is_some());
+}
+
+#[test]
+fn old_symbol_widget_envelopes_after_reset_preserve_new_same_path_intent() {
+ stale_envelope_trial(true);
+}
+
+#[test]
+fn old_symbol_widget_envelopes_after_file_switch_preserve_same_session_intent() {
+ stale_envelope_trial(false);
+}
+
+#[test]
+#[ignore = "manual 100-module latency/process-memory trial; run alone with --ignored --nocapture --test-threads=1"]
+fn source_symbols_100_modules_1000_definitions_10000_uses() {
+ let mut files = BTreeMap::new();
+ let mut entry = "arcadia 7; project performance \"Current text trial\" {\n".to_owned();
+ for module in 0..100 {
+ let path = format!("modules/m{module:03}.arcadia");
+ entry.push_str(&format!(" include \"{path}\";\n"));
+ let mut text = String::new();
+ for declaration in 0..10 {
+ text.push_str(&format!("oa_process p_{module}_{declaration} \"Process {module}/{declaration} 雨\" {{ steps ["));
+ for reference in 0..10 {
+ if reference != 0 {
+ text.push_str(", ");
+ }
+ text.push_str("@p_0_0");
+ }
+ text.push_str("]; description \"@p_0_0 is literal documentation\"; }\n");
+ }
+ files.insert(path, text);
+ }
+ entry.push_str("}\n");
+ files.insert("project.arcadia".into(), entry);
+ let original = files.clone();
+ let input_bytes: usize = files.values().map(String::len).sum();
+ let drafts: Vec<_> = files
+ .iter()
+ .map(|(path, text)| query::DraftFile { path, text })
+ .collect();
+ let key = query::SnapshotKey {
+ workspace: Id::new_v4(),
+ revision: 1,
+ };
+ let memory_before = process_memory();
+ let started = Instant::now();
+ let owned = query::admit_owned(
+ query::Input {
+ key,
+ entry: "project.arcadia",
+ files: &drafts,
+ applied: false,
+ validation: None,
+ },
+ query::Limits::default(),
+ )
+ .unwrap();
+ let admitted = started.elapsed();
+ let started = Instant::now();
+ let snapshot = owned.capture().unwrap();
+ let captured = started.elapsed();
+ let path = "modules/m000.arcadia";
+ let offset = files[path].find("p_0_0").unwrap();
+ let started = Instant::now();
+ let result = snapshot
+ .query(query::Query {
+ stamp: query::QueryStamp {
+ snapshot: key,
+ serial: 1,
+ },
+ path,
+ byte_offset: offset,
+ action: query::Action::References,
+ })
+ .unwrap();
+ let queried = started.elapsed();
+ let memory_after = process_memory();
+ assert_eq!(files.len(), 101, "100 modules plus entry");
+ assert_eq!(
+ files
+ .values()
+ .map(|text| text.matches("oa_process ").count())
+ .sum::(),
+ 1000
+ );
+ assert_eq!(result.total_definitions, 1);
+ assert_eq!(result.total_references, 10_000);
+ assert_eq!(result.references.len() + result.omitted_references, 10_000);
+ assert!(
+ result.definitions.len() + result.references.len() <= query::Limits::default().result_rows
+ );
+ assert!(result.scope.closure_complete && !result.scope.validated);
+ assert_eq!(files, original);
+ eprintln!(
+ "100-module current-draft trial: bytes={input_bytes}, admission={admitted:?}, capture={captured:?}, references={queried:?}, rows={}, omitted={}, process_memory_before={memory_before:?}, process_memory_after={memory_after:?}",
+ result.references.len(),
+ result.omitted_references
+ );
+ // This fixture intentionally has process-to-process placeholder steps. It
+ // measures lexical draft navigation, not engineering validity or GUI latency.
+}
+
+#[cfg(not(windows))]
+fn process_memory() -> Option<(usize, usize)> {
+ None
+}
+
+#[cfg(windows)]
+fn process_memory() -> Option<(usize, usize)> {
+ #[repr(C)]
+ #[derive(Default)]
+ struct Counters {
+ cb: u32,
+ page_fault_count: u32,
+ peak_working_set: usize,
+ working_set: usize,
+ quota_peak_paged_pool: usize,
+ quota_paged_pool: usize,
+ quota_peak_non_paged_pool: usize,
+ quota_non_paged_pool: usize,
+ pagefile_usage: usize,
+ peak_pagefile_usage: usize,
+ }
+ #[link(name = "kernel32")]
+ unsafe extern "system" {
+ fn GetCurrentProcess() -> *mut std::ffi::c_void;
+ fn K32GetProcessMemoryInfo(
+ process: *mut std::ffi::c_void,
+ counters: *mut Counters,
+ size: u32,
+ ) -> i32;
+ }
+ let size = u32::try_from(std::mem::size_of::()).unwrap();
+ let mut counters = Counters {
+ cb: size,
+ ..Counters::default()
+ };
+ // Windows writes one correctly sized PROCESS_MEMORY_COUNTERS for this
+ // process. These are process-wide resident/peak bytes, not exact allocations.
+ let ok = unsafe { K32GetProcessMemoryInfo(GetCurrentProcess(), &mut counters, size) };
+ (ok != 0).then_some((counters.working_set, counters.peak_working_set))
+}
diff --git a/crates/oa-desktop/src/guide.rs b/crates/oa-desktop/src/guide.rs
index 22ef0fa9..3cb4cbce 100644
--- a/crates/oa-desktop/src/guide.rs
+++ b/crates/oa-desktop/src/guide.rs
@@ -785,7 +785,7 @@ fn article(topic: Topic) -> Article {
const {
Article {
summary: "The text editor and graphical forms edit the same model. Source projects preserve file organization and comments for human review, automation and version control.",
- keywords: "text textual source language syntax highlighting lint parser git aliases uuid identity rename diff merge restructuredtext spec canonical parity",
+ keywords: "text textual source language syntax highlighting lint parser git aliases uuid identity rename diff merge restructuredtext spec canonical parity definition references lexical navigation F12 back",
sections: &[
walkthrough(
"Edit and review source",
@@ -798,6 +798,14 @@ fn article(topic: Topic) -> Article {
"Save all writes the entry and its included UTF-8 files. Review the Git diff before committing, including relationships, traces and diagram changes that belong to the same engineering edit.",
],
),
+ explain(
+ "Follow definitions and lexical references",
+ "Place the caret on a declaration alias or an @reference. Choose Definition (F12) to locate its declaration, or References (Shift+F12) to list lexical uses. Choose a row when a definition is ambiguous, draft or detached. Lexical uses exclude comments and strings; they do not establish typed relationships or resolve an ambiguous alias to one declaration. Back (Alt+Left) returns to the previous source location; if that draft changed, it restores the current cursor instead of an obsolete selection. These shortcuts work while the source editor has focus; the Text toolbar also offers the actions.",
+ ),
+ explain(
+ "Read the navigation scope",
+ "Queries inspect bounded snapshots of currently loaded drafts, without applying them or loading missing included files. The panel identifies partial scope, draft evidence, detached or unverified files, and alias or ID collisions. Counts show total, displayed and omitted rows. A missing result in partial scope does not establish that the whole project has no match. If sources change, stale rows are disabled; run Definition or References again. Row locations use one-based lines and UTF-8 byte columns. Navigation preserves your source drafts and the accepted model.",
+ ),
explain(
"Names, aliases and IDs serve different purposes",
"The displayed name is engineering prose. A stable @alias is the readable source reference and need not change when the name changes. UUID identity distinguishes the persistent record. Preserve IDs when revising the same engineering object; changing identity is a deliberate operation that affects references.",
diff --git a/crates/oa-desktop/src/source_navigation.rs b/crates/oa-desktop/src/source_navigation.rs
index 4b7c50b1..ced9ac1b 100644
--- a/crates/oa-desktop/src/source_navigation.rs
+++ b/crates/oa-desktop/src/source_navigation.rs
@@ -1,4 +1,9 @@
//! Literal source search and line navigation, independent of accepted model state.
+mod controller;
+pub mod interaction;
+pub mod query;
+pub mod symbols;
+pub use controller::*;
use iced::widget::text_editor::{Cursor, Position};
#[derive(Debug, Clone)]
diff --git a/crates/oa-desktop/src/source_navigation/controller.rs b/crates/oa-desktop/src/source_navigation/controller.rs
new file mode 100644
index 00000000..935fd08c
--- /dev/null
+++ b/crates/oa-desktop/src/source_navigation/controller.rs
@@ -0,0 +1,125 @@
+//! Ephemeral editor navigation; no model commands or filesystem operations.
+use super::query;
+use iced::widget::text_editor::{Cursor, Position};
+use oa_core::Id;
+use std::sync::Arc;
+use std::sync::atomic::AtomicU64;
+
+#[derive(Debug, Clone)]
+pub enum SymbolEvent {
+ Query(query::Action),
+ Back,
+ Close,
+ Choose {
+ stamp: query::QueryStamp,
+ reference: bool,
+ index: usize,
+ },
+}
+
+/// Identity of the mounted source view that emitted an action. Message queues
+/// may outlive that view; request-time stamping alone cannot detect replay.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct SymbolContext {
+ pub session: Id,
+ pub path: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct HistoryEntry {
+ pub session: Id,
+ pub path: String,
+ pub generation: u64,
+ pub cursor: Cursor,
+}
+
+#[derive(Debug, Clone)]
+pub struct Request {
+ pub job: Id,
+ pub interaction: Id,
+ pub raw_interaction: u64,
+ pub stamp: query::QueryStamp,
+ pub path: String,
+ pub offset: usize,
+ pub cursor: Cursor,
+ pub action: query::Action,
+}
+
+#[derive(Debug, Clone)]
+pub struct Completion {
+ pub request: Request,
+ pub result: Result<(Arc, Arc), String>,
+}
+
+pub struct Reveal {
+ pub ticket: Id,
+ pub interaction: Id,
+ pub raw_interaction: u64,
+ pub snapshot: query::SnapshotKey,
+ pub path: String,
+}
+
+pub struct SymbolState {
+ pub open: bool,
+ pub interaction: Id,
+ pub raw_interaction: Arc,
+ pub serial: u64,
+ pub in_flight: Option,
+ pub request: Option,
+ pub cache: Option>,
+ pub results: Option>,
+ pub notice: Option,
+ pub back: Vec,
+ pub reveal: Option,
+}
+
+impl Default for SymbolState {
+ fn default() -> Self {
+ Self {
+ open: false,
+ interaction: Id::new_v4(),
+ raw_interaction: Arc::new(AtomicU64::new(0)),
+ serial: 0,
+ in_flight: None,
+ request: None,
+ cache: None,
+ results: None,
+ notice: None,
+ back: Vec::new(),
+ reveal: None,
+ }
+ }
+}
+
+/// Iced uses zero-based UTF-8 byte columns. Reject stale or split-codepoint
+/// coordinates; do not normalize CRLF or silently clamp query destinations.
+pub fn byte_offset(text: &str, position: Position) -> Option {
+ let start = if position.line == 0 {
+ 0
+ } else {
+ text.match_indices('\n').nth(position.line - 1)?.0 + 1
+ };
+ let end = text[start..]
+ .find('\n')
+ .map_or(text.len(), |end| start + end);
+ let offset = start.checked_add(position.column)?;
+ (offset <= end && text.is_char_boundary(offset)).then_some(offset)
+}
+
+pub fn cursor_offset(text: &str, cursor: Cursor) -> Option {
+ let position = cursor.selection.map_or(cursor.position, |selection| {
+ if (selection.line, selection.column) < (cursor.position.line, cursor.position.column) {
+ selection
+ } else {
+ cursor.position
+ }
+ });
+ byte_offset(text, position)
+}
+
+pub fn exact_cursor(text: &str, cursor: Cursor) -> bool {
+ byte_offset(text, cursor.position).is_some()
+ && cursor
+ .selection
+ .is_none_or(|position| byte_offset(text, position).is_some())
+}
diff --git a/crates/oa-desktop/src/source_navigation/interaction.rs b/crates/oa-desktop/src/source_navigation/interaction.rs
new file mode 100644
index 00000000..394a1c49
--- /dev/null
+++ b/crates/oa-desktop/src/source_navigation/interaction.rs
@@ -0,0 +1,284 @@
+//! Raw input invalidation and focus checks at widget-operation execution time.
+use iced::advanced::{Clipboard, Layout, Shell, Widget, layout, overlay, renderer, widget};
+use iced::{Element, Event, Length, Rectangle, Size, Task, Vector, keyboard, mouse, touch};
+use std::sync::{
+ Arc,
+ atomic::{AtomicU64, Ordering},
+};
+
+/// Exhaustion fails closed: a saturated clock can never authorize navigation.
+/// Replace the clock with a new workspace/controller, rather than wrapping it.
+pub fn is_current(clock: &AtomicU64, expected: u64) -> bool {
+ expected != u64::MAX && clock.load(Ordering::Acquire) == expected
+}
+
+fn invalidate(clock: &AtomicU64, event: &Event) {
+ if matches!(
+ event,
+ Event::Keyboard(keyboard::Event::KeyPressed { .. })
+ | Event::Mouse(mouse::Event::ButtonPressed(_) | mouse::Event::WheelScrolled { .. })
+ | Event::Touch(touch::Event::FingerPressed { .. })
+ | Event::Window(iced::window::Event::Unfocused)
+ ) {
+ let _ = clock.fetch_update(Ordering::AcqRel, Ordering::Acquire, |value| {
+ Some(value.saturating_add(1))
+ });
+ }
+}
+
+/// Transparent, including overlays: invalidate before the child can capture input.
+pub fn observe<'a, Message: 'a, Theme: 'a, Renderer: renderer::Renderer + 'a>(
+ content: Element<'a, Message, Theme, Renderer>,
+ clock: Arc,
+) -> Element<'a, Message, Theme, Renderer> {
+ Element::new(Observed { content, clock })
+}
+
+struct Observed<'a, Message, Theme, Renderer> {
+ content: Element<'a, Message, Theme, Renderer>,
+ clock: Arc,
+}
+
+impl Widget
+ for Observed<'_, Message, Theme, Renderer>
+{
+ fn size(&self) -> Size {
+ self.content.as_widget().size()
+ }
+ fn size_hint(&self) -> Size {
+ self.content.as_widget().size_hint()
+ }
+ fn tag(&self) -> widget::tree::Tag {
+ self.content.as_widget().tag()
+ }
+ fn state(&self) -> widget::tree::State {
+ self.content.as_widget().state()
+ }
+ fn children(&self) -> Vec {
+ self.content.as_widget().children()
+ }
+ fn diff(&self, tree: &mut widget::Tree) {
+ self.content.as_widget().diff(tree);
+ }
+ fn layout(
+ &mut self,
+ tree: &mut widget::Tree,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ self.content.as_widget_mut().layout(tree, renderer, limits)
+ }
+ fn draw(
+ &self,
+ tree: &widget::Tree,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ ) {
+ self.content
+ .as_widget()
+ .draw(tree, renderer, theme, style, layout, cursor, viewport);
+ }
+ fn operate(
+ &mut self,
+ tree: &mut widget::Tree,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation,
+ ) {
+ self.content
+ .as_widget_mut()
+ .operate(tree, layout, renderer, operation);
+ }
+ fn update(
+ &mut self,
+ tree: &mut widget::Tree,
+ event: &Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ viewport: &Rectangle,
+ ) {
+ invalidate(&self.clock, event);
+ self.content.as_widget_mut().update(
+ tree, event, layout, cursor, renderer, clipboard, shell, viewport,
+ );
+ }
+ fn mouse_interaction(
+ &self,
+ tree: &widget::Tree,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content
+ .as_widget()
+ .mouse_interaction(tree, layout, cursor, viewport, renderer)
+ }
+ fn overlay<'a>(
+ &'a mut self,
+ tree: &'a mut widget::Tree,
+ layout: Layout<'a>,
+ renderer: &Renderer,
+ viewport: &Rectangle,
+ translation: Vector,
+ ) -> Option> {
+ let clock = self.clock.clone();
+ self.content
+ .as_widget_mut()
+ .overlay(tree, layout, renderer, viewport, translation)
+ .map(|content| overlay::Element::new(Box::new(ObservedOverlay { content, clock })))
+ }
+}
+
+struct ObservedOverlay<'a, Message, Theme, Renderer> {
+ content: overlay::Element<'a, Message, Theme, Renderer>,
+ clock: Arc,
+}
+impl overlay::Overlay
+ for ObservedOverlay<'_, Message, Theme, Renderer>
+{
+ fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
+ self.content.as_overlay_mut().layout(renderer, bounds)
+ }
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ theme: &Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ ) {
+ self.content
+ .as_overlay()
+ .draw(renderer, theme, style, layout, cursor);
+ }
+ fn operate(
+ &mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation,
+ ) {
+ self.content
+ .as_overlay_mut()
+ .operate(layout, renderer, operation);
+ }
+ fn update(
+ &mut self,
+ event: &Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) {
+ invalidate(&self.clock, event);
+ self.content
+ .as_overlay_mut()
+ .update(event, layout, cursor, renderer, clipboard, shell);
+ }
+ fn mouse_interaction(
+ &self,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ self.content
+ .as_overlay()
+ .mouse_interaction(layout, cursor, renderer)
+ }
+ fn overlay<'a>(
+ &'a mut self,
+ layout: Layout<'a>,
+ renderer: &Renderer,
+ ) -> Option> {
+ let clock = self.clock.clone();
+ self.content
+ .as_overlay_mut()
+ .overlay(layout, renderer)
+ .map(|content| overlay::Element::new(Box::new(ObservedOverlay { content, clock })))
+ }
+ fn index(&self) -> f32 {
+ self.content.as_overlay().index()
+ }
+}
+
+/// Inspect all focus owners, then focus only the mounted target. Iced executes
+/// Operation::Chain synchronously, without an intervening input event. Both
+/// traversals recheck the raw clock; no result message authorizes later focus.
+pub fn guarded_focus(
+ target: widget::Id,
+ clock: Arc,
+ expected: u64,
+) -> Task {
+ widget::operate(GuardedFocus::new(target, clock, expected))
+}
+
+struct GuardedFocus {
+ target: widget::Id,
+ clock: Arc,
+ expected: u64,
+ target_count: usize,
+ foreign_focus: bool,
+ apply: bool,
+}
+impl GuardedFocus {
+ fn new(target: widget::Id, clock: Arc, expected: u64) -> Self {
+ Self {
+ target,
+ clock,
+ expected,
+ target_count: 0,
+ foreign_focus: false,
+ apply: false,
+ }
+ }
+}
+impl widget::Operation for GuardedFocus {
+ fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
+ if is_current(&self.clock, self.expected) {
+ visit(self);
+ }
+ }
+ fn focusable(
+ &mut self,
+ id: Option<&widget::Id>,
+ _: Rectangle,
+ state: &mut dyn widget::operation::Focusable,
+ ) {
+ if !is_current(&self.clock, self.expected) {
+ return;
+ }
+ if id == Some(&self.target) {
+ self.target_count += 1;
+ if self.apply {
+ state.focus();
+ }
+ } else if state.is_focused() {
+ self.foreign_focus = true;
+ }
+ }
+ fn finish(&self) -> widget::operation::Outcome {
+ if !self.apply
+ && self.target_count == 1
+ && !self.foreign_focus
+ && is_current(&self.clock, self.expected)
+ {
+ widget::operation::Outcome::Chain(Box::new(Self {
+ apply: true,
+ ..Self::new(self.target.clone(), self.clock.clone(), self.expected)
+ }))
+ } else {
+ widget::operation::Outcome::None
+ }
+ }
+}
+
+#[cfg(all(test, debug_assertions))]
+mod tests;
diff --git a/crates/oa-desktop/src/source_navigation/interaction/tests.rs b/crates/oa-desktop/src/source_navigation/interaction/tests.rs
new file mode 100644
index 00000000..ed9b83f3
--- /dev/null
+++ b/crates/oa-desktop/src/source_navigation/interaction/tests.rs
@@ -0,0 +1,190 @@
+use super::*;
+use iced::advanced::widget::operation::{Focusable, Outcome};
+use iced::widget::{column, text_input};
+use iced::{Point, Theme};
+
+#[derive(Default)]
+struct FocusMap(Vec<(Option, Rectangle, bool)>);
+impl widget::Operation for FocusMap {
+ fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
+ visit(self);
+ }
+ fn focusable(&mut self, id: Option<&widget::Id>, bounds: Rectangle, state: &mut dyn Focusable) {
+ self.0.push((id.cloned(), bounds, state.is_focused()));
+ }
+}
+
+struct Mounted {
+ element: Element<'static, (), Theme, ()>,
+ tree: widget::Tree,
+ node: layout::Node,
+}
+impl Mounted {
+ fn new(clock: Arc) -> Self {
+ let content = column![
+ text_input("Editor", "source")
+ .id("editor")
+ .on_input(|_| ())
+ .padding(12),
+ text_input("Find", "")
+ .id("find")
+ .on_input(|_| ())
+ .padding(12),
+ ]
+ .spacing(10)
+ .into();
+ let mut element = observe(content, clock);
+ let mut tree = widget::Tree::new(element.as_widget());
+ let node = element.as_widget_mut().layout(
+ &mut tree,
+ &(),
+ &layout::Limits::new(Size::ZERO, Size::new(400., 200.)),
+ );
+ Self {
+ element,
+ tree,
+ node,
+ }
+ }
+ fn pass(&mut self, operation: &mut dyn widget::Operation) {
+ self.element.as_widget_mut().operate(
+ &mut self.tree,
+ Layout::new(&self.node),
+ &(),
+ operation,
+ );
+ }
+ fn run(&mut self, operation: impl widget::Operation + 'static) {
+ let mut next: Box = Box::new(operation);
+ loop {
+ self.pass(next.as_mut());
+ match next.finish() {
+ Outcome::Chain(chained) => next = chained,
+ Outcome::None => break,
+ Outcome::Some(()) => panic!("focus must not publish an application message"),
+ }
+ }
+ }
+ fn focus(&mut self) -> FocusMap {
+ let mut focus = FocusMap::default();
+ self.pass(&mut focus);
+ focus
+ }
+ fn click(&mut self, id: &str) {
+ let bounds = self
+ .focus()
+ .0
+ .into_iter()
+ .find(|(found, _, _)| found.as_ref() == Some(&widget::Id::from(id.to_owned())))
+ .unwrap()
+ .1;
+ let mut messages = Vec::new();
+ let mut shell = Shell::new(&mut messages);
+ self.element.as_widget_mut().update(
+ &mut self.tree,
+ &Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
+ Layout::new(&self.node),
+ mouse::Cursor::Available(bounds.center()),
+ &(),
+ &mut iced::advanced::clipboard::Null,
+ &mut shell,
+ &Rectangle::with_size(Size::new(400., 200.)),
+ );
+ assert!(shell.is_event_captured());
+ assert!(
+ messages.is_empty(),
+ "focus-only click must not rely on App messages"
+ );
+ }
+ fn focused(&mut self, id: &str) -> bool {
+ self.focus()
+ .0
+ .into_iter()
+ .find(|(found, _, _)| found.as_ref() == Some(&widget::Id::from(id.to_owned())))
+ .unwrap()
+ .2
+ }
+}
+
+#[test]
+fn captured_find_click_invalidates_without_app_message_and_preserves_focus() {
+ let clock = Arc::new(AtomicU64::new(0));
+ let mut ui = Mounted::new(clock.clone());
+ ui.run(GuardedFocus::new("editor".into(), clock.clone(), 0));
+ assert!(ui.focused("editor"));
+ ui.click("find");
+ assert!(!is_current(&clock, 0));
+ assert!(ui.focused("find"));
+ ui.run(GuardedFocus::new("editor".into(), clock.clone(), 0));
+ assert!(ui.focused("find"));
+ // Even a matching clock cannot authorize stealing another widget's focus.
+ ui.run(GuardedFocus::new(
+ "editor".into(),
+ clock.clone(),
+ clock.load(Ordering::Acquire),
+ ));
+ assert!(ui.focused("find"));
+ assert!(!ui.focused("editor"));
+}
+
+#[test]
+fn focus_rechecks_clock_between_inspection_and_application_and_missing_target_is_inert() {
+ let clock = Arc::new(AtomicU64::new(0));
+ let mut ui = Mounted::new(clock.clone());
+ let mut operation = GuardedFocus::new("editor".into(), clock.clone(), 0);
+ ui.pass(&mut operation);
+ let Outcome::Chain(mut apply) = >::finish(&operation)
+ else {
+ panic!("mounted, unopposed target must be eligible");
+ };
+ clock.store(1, Ordering::Release);
+ ui.pass(apply.as_mut());
+ assert!(!ui.focused("editor"));
+ ui.run(GuardedFocus::new("editor".into(), clock.clone(), 1));
+ assert!(ui.focused("editor"));
+ ui.run(GuardedFocus::new("removed-editor".into(), clock, 1));
+ assert!(ui.focused("editor"));
+}
+
+#[test]
+fn raw_navigation_inputs_advance_but_passive_geometry_does_not_and_exhaustion_refuses() {
+ let clock = AtomicU64::new(0);
+ let events = [
+ Event::Keyboard(keyboard::Event::KeyPressed {
+ key: keyboard::Key::Named(keyboard::key::Named::ArrowLeft),
+ modified_key: keyboard::Key::Named(keyboard::key::Named::ArrowLeft),
+ physical_key: keyboard::key::Physical::Code(keyboard::key::Code::ArrowLeft),
+ location: keyboard::Location::Standard,
+ modifiers: keyboard::Modifiers::empty(),
+ text: None,
+ repeat: false,
+ }),
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)),
+ Event::Mouse(mouse::Event::WheelScrolled {
+ delta: mouse::ScrollDelta::Lines { x: 0., y: 1. },
+ }),
+ Event::Touch(touch::Event::FingerPressed {
+ id: touch::Finger(1),
+ position: Point::ORIGIN,
+ }),
+ Event::Window(iced::window::Event::Unfocused),
+ ];
+ for (index, event) in events.iter().enumerate() {
+ invalidate(&clock, event);
+ assert_eq!(clock.load(Ordering::Acquire), index as u64 + 1);
+ }
+ invalidate(
+ &clock,
+ &Event::Mouse(mouse::Event::CursorMoved {
+ position: Point::ORIGIN,
+ }),
+ );
+ invalidate(&clock, &Event::Window(iced::window::Event::Focused));
+ assert!(is_current(&clock, 5));
+ clock.store(u64::MAX - 1, Ordering::Release);
+ invalidate(&clock, &events[0]);
+ assert!(!is_current(&clock, u64::MAX));
+ invalidate(&clock, &events[0]);
+ assert_eq!(clock.load(Ordering::Acquire), u64::MAX);
+ assert!(!is_current(&clock, 0));
+}
diff --git a/crates/oa-desktop/src/source_navigation/query.rs b/crates/oa-desktop/src/source_navigation/query.rs
new file mode 100644
index 00000000..3044fff3
--- /dev/null
+++ b/crates/oa-desktop/src/source_navigation/query.rs
@@ -0,0 +1,1102 @@
+//! Owned, bounded queries over exact current source buffers.
+//!
+//! Includes are inspected from these bytes only. A previously validated bundle
+//! is an equality witness, never a source of missing text or navigation spans.
+//! The caller must advance the workspace key on every indexed-input change and
+//! reject asynchronous results whose key, query serial or byte readback changed.
+
+use super::symbols::{self, Index};
+use oa_core::Id;
+use oa_core::language::{self, OutlineLimits, Span, TokenClass, TokenSpan};
+use oa_core::source_project::{self, IncludeLocation, ProjectLimits};
+use std::collections::BTreeMap;
+
+pub use super::symbols::{Evidence, Identity, Membership, Position, SnapshotKey};
+
+#[derive(Debug, Clone, Copy)]
+pub struct DraftFile<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+}
+
+/// The caller asserts that this complete source bundle passed explicit semantic
+/// validation. Capture independently compares its entry, membership and bytes.
+#[derive(Debug, Clone, Copy)]
+pub struct ValidatedSources<'a> {
+ pub entry: &'a str,
+ pub files: &'a [DraftFile<'a>],
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Input<'a> {
+ pub key: SnapshotKey,
+ pub entry: &'a str,
+ pub files: &'a [DraftFile<'a>],
+ pub applied: bool,
+ pub validation: Option>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Limits {
+ pub index: symbols::Limits,
+ pub include_depth: usize,
+ pub include_edges: usize,
+ pub result_rows: usize,
+ /// Upper bound on copied result strings, including quoted name input before
+ /// JSON decoding. Row/vector overhead is separately bounded by result_rows.
+ pub result_bytes: usize,
+}
+
+impl Default for Limits {
+ fn default() -> Self {
+ Self {
+ index: symbols::Limits {
+ files: 256,
+ source_bytes: 64 * 1024 * 1024,
+ path_bytes: 64 * 1024,
+ tokens: 1_000_000,
+ records: 250_000,
+ },
+ include_depth: 32,
+ include_edges: 1024,
+ result_rows: 200,
+ result_bytes: 256 * 1024,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum AdmissionError {
+ Index(symbols::BuildError),
+ Outline {
+ file: usize,
+ error: language::OutlineLimit,
+ },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ScopeIssueKind {
+ MissingEntry,
+ InvalidIncludes,
+ MissingInclude,
+ IncludeCycle,
+ IncludeDepth,
+ IncludeBudget,
+}
+
+/// File indices refer to Snapshot::file. Include spans retain the original
+/// quoted include location; diagnostics do not copy unbounded source strings.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ScopeIssue {
+ pub kind: ScopeIssueKind,
+ pub file: Option,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Scope {
+ pub closure_complete: bool,
+ pub applied: bool,
+ pub validated: bool,
+ pub included_files: usize,
+ pub detached_files: usize,
+ pub unverified_files: usize,
+ pub issues: Vec,
+}
+
+impl Scope {
+ pub fn partial(&self) -> bool {
+ !self.closure_complete || !self.applied
+ }
+
+ fn index_scope(&self) -> symbols::Scope {
+ if !self.applied {
+ symbols::Scope::Partial(symbols::PartialReason::UnappliedWorkspace)
+ } else if !self.closure_complete {
+ let missing = self.issues.iter().any(|issue| {
+ matches!(
+ issue.kind,
+ ScopeIssueKind::MissingEntry | ScopeIssueKind::MissingInclude
+ )
+ });
+ symbols::Scope::Partial(if missing {
+ symbols::PartialReason::MissingFiles
+ } else {
+ symbols::PartialReason::IncludesUnverified
+ })
+ } else {
+ symbols::Scope::CompleteProject
+ }
+ }
+}
+
+#[derive(Debug)]
+struct File {
+ path: String,
+ text: String,
+ tokens: Vec,
+ declarations: Vec,
+ membership: Membership,
+ evidence: Evidence,
+}
+
+/// Immutable and Send: callers can retain it in an Arc for asynchronous work.
+/// No Workspace, accepted Project, filesystem handle or mutation is retained.
+#[derive(Debug)]
+pub struct Snapshot {
+ key: SnapshotKey,
+ entry: String,
+ files: Vec,
+ scope: Scope,
+ limits: Limits,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct QueryStamp {
+ pub snapshot: SnapshotKey,
+ pub serial: u64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Action {
+ Definition,
+ References,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Query<'a> {
+ pub stamp: QueryStamp,
+ pub path: &'a str,
+ pub byte_offset: usize,
+ pub action: Action,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Disposition {
+ NoSymbol,
+ NoDefinition,
+ /// All candidates require an explicit choice, including a sole draft or
+ /// detached candidate. Truncation can never imply unique resolution.
+ Choices,
+ Direct,
+ LexicalReferences,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Declaration {
+ pub alias: String,
+ pub kind: String,
+ pub name: Option,
+ pub span: Span,
+ pub authored_id: Option,
+ pub identity: Identity,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct DefinitionRow {
+ pub path: String,
+ /// The alias token to select, rather than an enclosing declaration body.
+ pub span: Span,
+ pub position: Position,
+ pub end_position: Position,
+ pub declaration: Declaration,
+ pub membership: Membership,
+ pub evidence: Evidence,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ReferenceRow {
+ pub path: String,
+ pub alias: String,
+ pub span: Span,
+ /// Separate live @ token: intervening comments are never selected as a hit.
+ pub marker: Span,
+ pub position: Position,
+ pub end_position: Position,
+ /// Innermost tolerant declaration containing this occurrence, if available.
+ /// This describes its lexical location, not its semantic target or owner.
+ pub context: Option,
+ pub membership: Membership,
+ pub evidence: Evidence,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Results {
+ pub stamp: QueryStamp,
+ pub action: Action,
+ pub origin_path: String,
+ pub origin_offset: usize,
+ pub alias: Option,
+ pub scope: Scope,
+ pub disposition: Disposition,
+ pub definitions: Vec,
+ pub references: Vec,
+ pub total_definitions: usize,
+ pub total_references: usize,
+ pub omitted_definitions: usize,
+ pub omitted_references: usize,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum QueryError {
+ Index(symbols::BuildError),
+ Query(symbols::QueryError),
+ MissingFile,
+ ResultEnvelopeLimit,
+}
+
+impl From for QueryError {
+ fn from(error: symbols::QueryError) -> Self {
+ Self::Query(error)
+ }
+}
+
+fn charge(
+ total: &mut usize,
+ value: usize,
+ limit: usize,
+ resource: symbols::Resource,
+) -> Result<(), AdmissionError> {
+ let next =
+ total
+ .checked_add(value)
+ .ok_or(AdmissionError::Index(symbols::BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: usize::MAX,
+ }))?;
+ if next > limit {
+ return Err(AdmissionError::Index(symbols::BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: next,
+ }));
+ }
+ *total = next;
+ Ok(())
+}
+
+/// No path normalization, outline extraction, accepted-source copy or index
+/// allocation takes place until aggregate raw input admission succeeds.
+fn admit_raw(input: Input<'_>, limits: Limits) -> Result<(), AdmissionError> {
+ charge(
+ &mut 0,
+ input.files.len(),
+ limits.index.files,
+ symbols::Resource::Files,
+ )?;
+ let mut bytes = 0;
+ let mut paths = 0;
+ charge(
+ &mut paths,
+ input.entry.len(),
+ limits.index.path_bytes,
+ symbols::Resource::PathBytes,
+ )?;
+ for file in input.files {
+ charge(
+ &mut bytes,
+ file.text.len(),
+ limits.index.source_bytes,
+ symbols::Resource::SourceBytes,
+ )?;
+ charge(
+ &mut paths,
+ file.path.len(),
+ limits.index.path_bytes,
+ symbols::Resource::PathBytes,
+ )?;
+ }
+ Ok(())
+}
+
+struct Extracted {
+ tokens: Vec,
+ declarations: Vec,
+}
+
+#[derive(Debug)]
+struct OwnedFile {
+ path: String,
+ text: String,
+}
+
+#[derive(Debug, Default)]
+struct ValidationProof {
+ admitted: bool,
+ file_count: usize,
+ matches: Vec,
+}
+
+impl ValidationProof {
+ fn capture(input: Input<'_>, limits: Limits) -> Self {
+ let Some(validation) = input.validation else {
+ return Self::default();
+ };
+ if validation.entry != input.entry
+ || admit_raw(
+ Input {
+ files: validation.files,
+ entry: validation.entry,
+ validation: None,
+ ..input
+ },
+ limits,
+ )
+ .is_err()
+ {
+ return Self::default();
+ }
+ let files: BTreeMap<_, _> = validation
+ .files
+ .iter()
+ .map(|file| (file.path, file.text))
+ .collect();
+ if files.len() != validation.files.len() {
+ return Self::default();
+ }
+ Self {
+ admitted: true,
+ file_count: files.len(),
+ matches: input
+ .files
+ .iter()
+ .map(|file| files.get(file.path).is_some_and(|text| *text == file.text))
+ .collect(),
+ }
+ }
+
+ fn matches(&self, memberships: &[Membership], scope: &Scope) -> bool {
+ self.admitted
+ && scope.closure_complete
+ && scope.applied
+ && self.file_count == scope.included_files
+ && self.matches.len() == memberships.len()
+ && memberships
+ .iter()
+ .zip(&self.matches)
+ .all(|(membership, equal)| *membership != Membership::Included || *equal)
+ }
+}
+
+/// Request-time data for spawn_blocking: raw admission plus one current-text
+/// clone, with no lexer/parser work. The private equality proof contains only
+/// booleans/counts, never a second accepted SourceBundle. These private fields
+/// prevent changing the bytes after their equality evidence was captured.
+#[derive(Debug)]
+pub struct OwnedInput {
+ key: SnapshotKey,
+ entry: String,
+ files: Vec,
+ applied: bool,
+ validation: ValidationProof,
+ limits: Limits,
+}
+
+pub fn admit_owned(input: Input<'_>, limits: Limits) -> Result {
+ admit_raw(input, limits)?;
+ let validation = ValidationProof::capture(input, limits);
+ Ok(OwnedInput {
+ key: input.key,
+ entry: input.entry.to_owned(),
+ files: input
+ .files
+ .iter()
+ .map(|file| OwnedFile {
+ path: file.path.to_owned(),
+ text: file.text.to_owned(),
+ })
+ .collect(),
+ applied: input.applied,
+ validation,
+ limits,
+ })
+}
+
+impl OwnedInput {
+ /// Run on the blocking worker. Token/declaration/reference budgets are
+ /// enforced before extraction/index allocation, then strings are moved into
+ /// Snapshot without another copy. A limit error never returns partial data.
+ pub fn capture(self) -> Result {
+ let files: Vec<_> = self
+ .files
+ .iter()
+ .map(|file| DraftFile {
+ path: &file.path,
+ text: &file.text,
+ })
+ .collect();
+ let input = Input {
+ key: self.key,
+ entry: &self.entry,
+ files: &files,
+ applied: self.applied,
+ validation: None,
+ };
+ let prepared = prepare(input, self.limits, Some(&self.validation))?;
+ Ok(finish(
+ self.key,
+ self.entry,
+ self.files,
+ self.limits,
+ prepared,
+ ))
+ }
+}
+
+struct Prepared {
+ extracted: Vec,
+ memberships: Vec,
+ scope: Scope,
+}
+
+#[cfg(test)]
+pub fn capture(input: Input<'_>, limits: Limits) -> Result {
+ let prepared = prepare(input, limits, None)?;
+ let files = input
+ .files
+ .iter()
+ .map(|file| OwnedFile {
+ path: file.path.to_owned(),
+ text: file.text.to_owned(),
+ })
+ .collect();
+ Ok(finish(
+ input.key,
+ input.entry.to_owned(),
+ files,
+ limits,
+ prepared,
+ ))
+}
+
+fn prepare(
+ input: Input<'_>,
+ limits: Limits,
+ proof: Option<&ValidationProof>,
+) -> Result {
+ admit_raw(input, limits)?;
+ let mut extracted = Vec::with_capacity(input.files.len());
+ let mut tokens_left = limits.index.tokens;
+ let mut records_left = limits.index.records;
+ for (file, draft) in input.files.iter().enumerate() {
+ // Remaining aggregate budgets are passed before this file is lexed.
+ // A stopped outline returns an error, never a complete zero-result file.
+ let outline = language::outline(
+ draft.text,
+ OutlineLimits {
+ max_source_bytes: limits.index.source_bytes,
+ max_tokens: tokens_left,
+ max_declarations: records_left,
+ },
+ )
+ .map_err(|error| AdmissionError::Outline { file, error })?;
+ tokens_left -= outline.tokens.len();
+ records_left -= outline.declarations.len();
+ extracted.push(Extracted {
+ tokens: outline.tokens,
+ declarations: outline
+ .declarations
+ .into_iter()
+ .map(|declaration| symbols::DeclarationInput {
+ span: declaration.declaration.span,
+ kind_span: declaration.kind_span,
+ alias_span: declaration.alias_span,
+ identity: declaration
+ .declaration
+ .id
+ .zip(declaration.id_literal_span)
+ .map(|(id, literal_span)| symbols::AuthoredIdentity { id, literal_span }),
+ })
+ .collect(),
+ });
+ }
+ // Reuse the reviewed live-reference counter and structural admission, rather
+ // than maintain a second lexer/reference recognizer. No text has been copied.
+ let inputs: Vec<_> = input
+ .files
+ .iter()
+ .zip(&extracted)
+ .map(|(file, outline)| symbols::FileInput {
+ path: file.path,
+ text: file.text,
+ tokens: &outline.tokens,
+ declarations: &outline.declarations,
+ syntax: symbols::SyntaxCoverage::FullFile,
+ membership: Membership::Unverified,
+ evidence: Evidence::Draft,
+ })
+ .collect();
+ Index::build(
+ input.key,
+ &inputs,
+ symbols::Scope::Partial(symbols::PartialReason::IncludesUnverified),
+ limits.index,
+ )
+ .map_err(AdmissionError::Index)?;
+ let (memberships, mut scope) = inspect_closure(input, limits);
+ scope.validated = proof.map_or_else(
+ || validation_matches(input, &memberships, &scope, limits),
+ |proof| proof.matches(&memberships, &scope),
+ );
+ Ok(Prepared {
+ extracted,
+ memberships,
+ scope,
+ })
+}
+
+fn finish(
+ key: SnapshotKey,
+ entry: String,
+ files: Vec,
+ limits: Limits,
+ prepared: Prepared,
+) -> Snapshot {
+ let Prepared {
+ extracted,
+ memberships,
+ scope,
+ } = prepared;
+ let files = files
+ .into_iter()
+ .zip(extracted)
+ .zip(memberships)
+ .map(|((draft, outline), membership)| File {
+ path: draft.path,
+ text: draft.text,
+ tokens: outline.tokens,
+ declarations: outline.declarations,
+ membership,
+ evidence: if scope.validated && membership == Membership::Included {
+ Evidence::Validated
+ } else {
+ Evidence::Draft
+ },
+ })
+ .collect();
+ Snapshot {
+ key,
+ entry,
+ files,
+ scope,
+ limits,
+ }
+}
+
+fn validation_matches(
+ input: Input<'_>,
+ memberships: &[Membership],
+ scope: &Scope,
+ limits: Limits,
+) -> bool {
+ if !scope.closure_complete || !input.applied {
+ return false;
+ }
+ ValidationProof::capture(input, limits).matches(memberships, scope)
+}
+
+struct Frame {
+ file: usize,
+ depth: usize,
+ includes: std::vec::IntoIter,
+}
+
+fn inspect_closure(input: Input<'_>, limits: Limits) -> (Vec, Scope) {
+ let paths: BTreeMap<_, _> = input
+ .files
+ .iter()
+ .enumerate()
+ .map(|(index, file)| (file.path, index))
+ .collect();
+ let mut memberships = vec![Membership::Unverified; input.files.len()];
+ let mut states = vec![0_u8; input.files.len()];
+ let mut issues = Vec::new();
+ let mut stack: Vec = Vec::new();
+ let mut edges_left = limits.include_edges;
+ let mut pending = paths.get(input.entry).copied().map(|file| (file, 0, None));
+ if pending.is_none() {
+ issues.push(ScopeIssue {
+ kind: ScopeIssueKind::MissingEntry,
+ file: None,
+ span: Span::default(),
+ });
+ }
+ loop {
+ if let Some((file, depth, origin)) = pending.take() {
+ memberships[file] = Membership::Included;
+ let (at_file, at_span) = origin.unwrap_or((file, Span::default()));
+ let problem = if states[file] == 1 {
+ Some(ScopeIssueKind::IncludeCycle)
+ } else if depth > limits.include_depth {
+ Some(ScopeIssueKind::IncludeDepth)
+ } else {
+ None
+ };
+ if let Some(kind) = problem {
+ issues.push(ScopeIssue {
+ kind,
+ file: Some(at_file),
+ span: at_span,
+ });
+ } else if states[file] == 0 {
+ states[file] = 1;
+ let draft = input.files[file];
+ let project_limits = ProjectLimits {
+ max_files: limits.index.files,
+ max_include_depth: limits.include_depth,
+ max_include_edges: edges_left,
+ model: language::ParseLimits {
+ max_source_bytes: limits.index.source_bytes,
+ max_graph_objects: limits.index.records,
+ max_records: limits.index.records,
+ max_step_references: limits.index.records,
+ },
+ };
+ match source_project::inspect_includes(
+ draft.path,
+ draft.text,
+ draft.path == input.entry,
+ project_limits,
+ ) {
+ Ok(includes) => {
+ edges_left -= includes.len();
+ stack.push(Frame {
+ file,
+ depth,
+ includes: includes.into_iter(),
+ });
+ }
+ Err(diagnostics) => {
+ let first = diagnostics.first();
+ let kind = if first.is_some_and(|d| d.diagnostic.code == "project_limit") {
+ ScopeIssueKind::IncludeBudget
+ } else {
+ ScopeIssueKind::InvalidIncludes
+ };
+ issues.push(ScopeIssue {
+ kind,
+ file: Some(file),
+ span: first.map_or(Span::default(), |d| d.diagnostic.span),
+ });
+ states[file] = 2;
+ }
+ }
+ }
+ }
+ let Some(frame) = stack.last_mut() else {
+ break;
+ };
+ if let Some(include) = frame.includes.next() {
+ if let Some(&file) = paths.get(include.target.as_str()) {
+ pending = Some((
+ file,
+ frame.depth.saturating_add(1),
+ Some((frame.file, include.location.span)),
+ ));
+ } else {
+ issues.push(ScopeIssue {
+ kind: ScopeIssueKind::MissingInclude,
+ file: Some(frame.file),
+ span: include.location.span,
+ });
+ }
+ } else {
+ states[frame.file] = 2;
+ stack.pop();
+ }
+ }
+ let complete = issues.is_empty();
+ if complete {
+ for membership in &mut memberships {
+ if *membership == Membership::Unverified {
+ *membership = Membership::Detached;
+ }
+ }
+ }
+ let scope = Scope {
+ closure_complete: complete,
+ applied: input.applied,
+ validated: false,
+ included_files: memberships
+ .iter()
+ .filter(|m| **m == Membership::Included)
+ .count(),
+ detached_files: memberships
+ .iter()
+ .filter(|m| **m == Membership::Detached)
+ .count(),
+ unverified_files: memberships
+ .iter()
+ .filter(|m| **m == Membership::Unverified)
+ .count(),
+ issues,
+ };
+ (memberships, scope)
+}
+
+impl Snapshot {
+ pub fn key(&self) -> SnapshotKey {
+ self.key
+ }
+
+ #[cfg(test)]
+ pub fn scope(&self) -> &Scope {
+ &self.scope
+ }
+
+ #[cfg(test)]
+ pub fn file(&self, index: usize) -> Option> {
+ self.files.get(index).map(|file| DraftFile {
+ path: &file.path,
+ text: &file.text,
+ })
+ }
+
+ /// Exact loaded file-set/byte comparison, independent of input order. The
+ /// evidence outcome is rechecked too; irrelevant old witness text is not kept.
+ /// The key check is mandatory even if bytes happen to match after Undo.
+ pub fn matches(&self, input: Input<'_>) -> bool {
+ if input.key != self.key
+ || input.entry != self.entry
+ || input.applied != self.scope.applied
+ || input.files.len() != self.files.len()
+ || admit_raw(input, self.limits).is_err()
+ {
+ return false;
+ }
+ let mut memberships = Vec::with_capacity(input.files.len());
+ for draft in input.files {
+ let Some(file) = self
+ .files
+ .iter()
+ .find(|file| file.path == draft.path && file.text == draft.text)
+ else {
+ return false;
+ };
+ // Duplicate paths are not the same file set, even if their bytes match.
+ if input
+ .files
+ .iter()
+ .filter(|other| other.path == draft.path)
+ .count()
+ != 1
+ {
+ return false;
+ }
+ memberships.push(file.membership);
+ }
+ validation_matches(input, &memberships, &self.scope, self.limits) == self.scope.validated
+ }
+
+ fn index(&self) -> Result, QueryError> {
+ let inputs: Vec<_> = self
+ .files
+ .iter()
+ .map(|file| symbols::FileInput {
+ path: &file.path,
+ text: &file.text,
+ tokens: &file.tokens,
+ declarations: &file.declarations,
+ syntax: symbols::SyntaxCoverage::FullFile,
+ membership: file.membership,
+ evidence: file.evidence,
+ })
+ .collect();
+ Index::build(
+ self.key,
+ &inputs,
+ self.scope.index_scope(),
+ self.limits.index,
+ )
+ .map_err(QueryError::Index)
+ }
+
+ pub fn query(&self, query: Query<'_>) -> Result {
+ if query.stamp.snapshot != self.key {
+ return Err(symbols::QueryError::StaleSnapshot.into());
+ }
+ let file = self
+ .files
+ .iter()
+ .position(|file| file.path == query.path)
+ .ok_or(QueryError::MissingFile)?;
+ let index = self.index()?;
+ let hit = index.hit(file, query.byte_offset)?;
+ let alias = match hit {
+ Some(symbols::Hit::Definition(handle)) => Some(index.definition(handle)?.alias),
+ Some(symbols::Hit::Reference(handle)) => Some(index.reference(handle)?.alias),
+ None => None,
+ };
+ let mut budget = RowBudget {
+ rows: self.limits.result_rows,
+ bytes: self.limits.result_bytes,
+ };
+ if !budget.strings(query.path.len().saturating_add(alias.map_or(0, str::len))) {
+ return Err(QueryError::ResultEnvelopeLimit);
+ }
+ let mut result = Results {
+ stamp: query.stamp,
+ action: query.action,
+ origin_path: query.path.to_owned(),
+ origin_offset: query.byte_offset,
+ alias: alias.map(str::to_owned),
+ scope: self.scope.clone(),
+ disposition: Disposition::NoSymbol,
+ definitions: Vec::new(),
+ references: Vec::new(),
+ total_definitions: 0,
+ total_references: 0,
+ omitted_definitions: 0,
+ omitted_references: 0,
+ };
+ let Some(alias) = alias else {
+ return Ok(result);
+ };
+ result.total_definitions = index
+ .definitions_for_alias(alias, symbols::Domain::Loaded)
+ .count();
+ for handle in index.definitions_for_alias(alias, symbols::Domain::Loaded) {
+ let definition = index.definition(handle)?;
+ let source = &self.files[definition.declaration.file];
+ let display = self.display(&index, handle)?;
+ if !budget.row(source.path.len().saturating_add(display.bytes())) {
+ break;
+ }
+ result.definitions.push(DefinitionRow {
+ path: source.path.clone(),
+ span: definition.alias_location.span,
+ position: Position {
+ line: 0,
+ byte_column: 0,
+ },
+ end_position: Position {
+ line: 0,
+ byte_column: 0,
+ },
+ declaration: display.owned(),
+ membership: source.membership,
+ evidence: source.evidence,
+ });
+ }
+ result.omitted_definitions = result.total_definitions - result.definitions.len();
+ result.disposition = if result.total_definitions == 0 {
+ Disposition::NoDefinition
+ } else {
+ Disposition::Choices
+ };
+ if query.action == Action::Definition
+ && result.total_definitions == 1
+ && result.definitions.len() == 1
+ && self.scope.validated
+ && self.files[file].membership == Membership::Included
+ {
+ let only = &result.definitions[0];
+ if only.membership == Membership::Included
+ && only.evidence == Evidence::Validated
+ && !matches!(only.declaration.identity, Identity::Collision { .. })
+ {
+ result.disposition = Disposition::Direct;
+ }
+ }
+ if query.action == Action::References {
+ result.disposition = Disposition::LexicalReferences;
+ result.total_references = index
+ .references_for_alias(alias, symbols::Domain::Loaded)
+ .count();
+ for handle in index.references_for_alias(alias, symbols::Domain::Loaded) {
+ let reference = index.reference(handle)?;
+ let source = &self.files[reference.alias_location.file];
+ let context = self.context(&index, reference.alias_location)?;
+ let bytes = source
+ .path
+ .len()
+ .saturating_add(alias.len())
+ .saturating_add(context.as_ref().map_or(0, Display::bytes));
+ if !budget.row(bytes) {
+ break;
+ }
+ result.references.push(ReferenceRow {
+ path: source.path.clone(),
+ alias: alias.to_owned(),
+ span: reference.alias_location.span,
+ marker: reference.at.span,
+ position: Position {
+ line: 0,
+ byte_column: 0,
+ },
+ end_position: Position {
+ line: 0,
+ byte_column: 0,
+ },
+ context: context.map(Display::owned),
+ membership: source.membership,
+ evidence: source.evidence,
+ });
+ }
+ result.omitted_references = result.total_references - result.references.len();
+ }
+ self.locate_rows(&mut result);
+ Ok(result)
+ }
+
+ fn locate_rows(&self, result: &mut Results) {
+ // Index admission already proved every row span and UTF-8 boundary.
+ // Scan each selected source prefix once instead of rescanning a large
+ // buffer for every occurrence. Scratch has at most two entries per row.
+ for file in &self.files {
+ let mut offsets = Vec::new();
+ for (row, definition) in result.definitions.iter().enumerate() {
+ if definition.path == file.path {
+ offsets.push((definition.span.start, true, row, false));
+ offsets.push((definition.span.end, true, row, true));
+ }
+ }
+ for (row, reference) in result.references.iter().enumerate() {
+ if reference.path == file.path {
+ offsets.push((reference.span.start, false, row, false));
+ offsets.push((reference.span.end, false, row, true));
+ }
+ }
+ offsets.sort_unstable_by_key(|&(offset, _, _, _)| offset);
+ let (mut scanned, mut line, mut line_start) = (0, 0, 0);
+ for (offset, definition, row, end) in offsets {
+ for (relative, byte) in file.text.as_bytes()[scanned..offset].iter().enumerate() {
+ if *byte == b'\n' {
+ line += 1;
+ line_start = scanned + relative + 1;
+ }
+ }
+ scanned = offset;
+ let position = Position {
+ line,
+ byte_column: offset - line_start,
+ };
+ match (definition, end) {
+ (true, false) => result.definitions[row].position = position,
+ (true, true) => result.definitions[row].end_position = position,
+ (false, false) => result.references[row].position = position,
+ (false, true) => result.references[row].end_position = position,
+ }
+ }
+ }
+ }
+
+ fn display<'a>(
+ &'a self,
+ index: &Index<'a>,
+ handle: symbols::DefinitionHandle,
+ ) -> Result, QueryError> {
+ let definition = index.definition(handle)?;
+ let file = &self.files[definition.declaration.file];
+ let alias_end = definition.alias_location.span.end;
+ let next = file
+ .tokens
+ .partition_point(|token| token.span.start < alias_end);
+ // The coordinated outline has already proved the declaration and alias
+ // roles. This optional decoration only decodes its immediate name token.
+ let name = (definition.kind != "node")
+ .then(|| {
+ file.tokens[next..].iter().find(|token| {
+ !matches!(token.class, TokenClass::Whitespace | TokenClass::Comment)
+ })
+ })
+ .flatten()
+ .filter(|token| {
+ token.class == TokenClass::String
+ && token.span.end <= definition.declaration.span.end
+ })
+ .map(|token| &file.text[token.span.start..token.span.end]);
+ Ok(Display {
+ alias: definition.alias,
+ kind: definition.kind,
+ quoted_name: name,
+ span: definition.declaration.span,
+ authored_id: definition.authored_id,
+ identity: index.identity(handle)?,
+ })
+ }
+
+ fn context<'a>(
+ &'a self,
+ index: &Index<'a>,
+ location: symbols::Location,
+ ) -> Result>, QueryError> {
+ let file = &self.files[location.file];
+ let inner = file
+ .declarations
+ .iter()
+ .filter(|declaration| {
+ declaration.span.start <= location.span.start
+ && location.span.end <= declaration.span.end
+ })
+ .min_by_key(|declaration| declaration.span.end - declaration.span.start);
+ let Some(inner) = inner else {
+ return Ok(None);
+ };
+ let Some(symbols::Hit::Definition(handle)) =
+ index.hit(location.file, inner.alias_span.start)?
+ else {
+ return Ok(None);
+ };
+ self.display(index, handle).map(Some)
+ }
+}
+
+struct Display<'a> {
+ alias: &'a str,
+ kind: &'a str,
+ quoted_name: Option<&'a str>,
+ span: Span,
+ authored_id: Option,
+ identity: Identity,
+}
+
+impl Display<'_> {
+ fn bytes(&self) -> usize {
+ self.alias
+ .len()
+ .saturating_add(self.kind.len())
+ .saturating_add(self.quoted_name.map_or(0, str::len))
+ }
+
+ fn owned(self) -> Declaration {
+ Declaration {
+ alias: self.alias.to_owned(),
+ kind: self.kind.to_owned(),
+ name: self
+ .quoted_name
+ .and_then(|name| serde_json::from_str(name).ok()),
+ span: self.span,
+ authored_id: self.authored_id,
+ identity: self.identity,
+ }
+ }
+}
+
+struct RowBudget {
+ rows: usize,
+ bytes: usize,
+}
+
+impl RowBudget {
+ fn strings(&mut self, bytes: usize) -> bool {
+ if bytes > self.bytes {
+ return false;
+ }
+ self.bytes -= bytes;
+ true
+ }
+
+ fn row(&mut self, bytes: usize) -> bool {
+ if self.rows == 0 || !self.strings(bytes) {
+ return false;
+ }
+ self.rows -= 1;
+ true
+ }
+}
+
+#[cfg(test)]
+#[path = "query_tests.rs"]
+mod tests;
diff --git a/crates/oa-desktop/src/source_navigation/query_tests.rs b/crates/oa-desktop/src/source_navigation/query_tests.rs
new file mode 100644
index 00000000..e0c9cde6
--- /dev/null
+++ b/crates/oa-desktop/src/source_navigation/query_tests.rs
@@ -0,0 +1,989 @@
+use super::*;
+
+const PROJECT: &str = "00000000-0000-0000-0000-000000000001";
+const PUMP: &str = "00000000-0000-0000-0000-000000000002";
+const LINK: &str = "00000000-0000-0000-0000-000000000003";
+
+#[derive(Clone)]
+struct Fixture {
+ entry: String,
+ files: Vec<(String, String)>,
+}
+
+impl Fixture {
+ fn standard() -> Self {
+ Self {
+ entry: "project.arcadia".into(),
+ files: vec![
+ (
+ "project.arcadia".into(),
+ format!(
+ "arcadia 7;\r\nproject model \"Harbour\" id \"{PROJECT}\" {{\r\n include \"模块/活動.arcadia\";\r\n include \"links.arcadia\";\r\n}}\r\n"
+ ),
+ ),
+ (
+ "模块/活動.arcadia".into(),
+ format!(
+ "// 原文 remains exact\r\noa_activity pump \"泵 🚀\" id \"{PUMP}\" {{}}\r\n"
+ ),
+ ),
+ (
+ "links.arcadia".into(),
+ format!(
+ "// 航海\r\ninteraction connection \"流れ\" id \"{LINK}\" {{\r\n source @ // separator @pump\r\n pump;\r\n description \"船 @pump\"; target @pump;\r\n}}\r\n"
+ ),
+ ),
+ ],
+ }
+ }
+
+ fn borrowed(&self) -> Vec> {
+ self.files
+ .iter()
+ .map(|(path, text)| DraftFile { path, text })
+ .collect()
+ }
+
+ fn text(&self, path: &str) -> &str {
+ &self
+ .files
+ .iter()
+ .find(|(candidate, _)| candidate == path)
+ .unwrap()
+ .1
+ }
+
+ fn text_mut(&mut self, path: &str) -> &mut String {
+ &mut self
+ .files
+ .iter_mut()
+ .find(|(candidate, _)| candidate == path)
+ .unwrap()
+ .1
+ }
+
+ fn snapshot(&self, validated: Option<&Self>, applied: bool, limits: Limits) -> Snapshot {
+ let current = self.borrowed();
+ let old = validated.map(Self::borrowed);
+ capture(
+ Input {
+ key: key(1),
+ entry: &self.entry,
+ files: ¤t,
+ applied,
+ validation: old.as_ref().map(|files| ValidatedSources {
+ entry: &validated.unwrap().entry,
+ files,
+ }),
+ },
+ limits,
+ )
+ .unwrap()
+ }
+}
+
+fn key(revision: u64) -> SnapshotKey {
+ SnapshotKey {
+ workspace: Id::from_u128(100),
+ revision,
+ }
+}
+
+fn query(snapshot: &Snapshot, path: &str, byte_offset: usize, action: Action) -> Results {
+ snapshot
+ .query(Query {
+ stamp: QueryStamp {
+ snapshot: snapshot.key(),
+ serial: 37,
+ },
+ path,
+ byte_offset,
+ action,
+ })
+ .unwrap()
+}
+
+fn reference_offset(fixture: &Fixture) -> usize {
+ fixture.text("links.arcadia").find("source @").unwrap() + "source ".len()
+}
+
+#[test]
+fn exact_multifile_unicode_crlf_definition_and_repeated_lexical_references() {
+ let fixture = Fixture::standard();
+ let original = fixture.files.clone();
+ let snapshot = fixture.snapshot(Some(&fixture), true, Limits::default());
+ assert!(snapshot.scope().closure_complete && snapshot.scope().validated);
+ assert_eq!(
+ (
+ snapshot.scope().included_files,
+ snapshot.scope().detached_files
+ ),
+ (3, 0)
+ );
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::Definition,
+ );
+ assert_eq!(result.disposition, Disposition::Direct);
+ assert_eq!(
+ (result.total_definitions, result.omitted_definitions),
+ (1, 0)
+ );
+ let definition = &result.definitions[0];
+ assert_eq!(definition.path, "模块/活動.arcadia");
+ assert_eq!(definition.declaration.kind, "oa_activity");
+ assert_eq!(definition.declaration.name.as_deref(), Some("泵 🚀"));
+ assert_eq!(
+ definition.declaration.authored_id,
+ Some(Id::parse_str(PUMP).unwrap())
+ );
+ assert_eq!(
+ definition.position,
+ Position {
+ line: 1,
+ byte_column: "oa_activity ".len()
+ }
+ );
+ assert_eq!(
+ &fixture.text(&definition.path)[definition.span.start..definition.span.end],
+ "pump"
+ );
+ let uses = query(
+ &snapshot,
+ &definition.path,
+ definition.span.start,
+ Action::References,
+ );
+ assert_eq!(uses.disposition, Disposition::LexicalReferences);
+ assert_eq!((uses.total_references, uses.omitted_references), (2, 0));
+ assert_eq!(
+ uses.references[0].position,
+ Position {
+ line: 3,
+ byte_column: 3
+ }
+ );
+ let target_prefix = " description \"船 @pump\"; target @";
+ assert_ne!(target_prefix.len(), target_prefix.chars().count());
+ assert_eq!(
+ uses.references[1].position,
+ Position {
+ line: 4,
+ byte_column: target_prefix.len()
+ }
+ );
+ for row in &uses.references {
+ let text = fixture.text(&row.path);
+ assert_eq!(&text[row.span.start..row.span.end], "pump");
+ assert_eq!(&text[row.marker.start..row.marker.end], "@");
+ let context = row.context.as_ref().unwrap();
+ assert_eq!(context.alias, "connection");
+ assert_eq!(context.kind, "interaction");
+ assert_eq!(context.name.as_deref(), Some("流れ"));
+ assert_eq!(row.evidence, Evidence::Validated);
+ }
+ assert_eq!(
+ uses.stamp,
+ QueryStamp {
+ snapshot: key(1),
+ serial: 37
+ }
+ );
+ assert_eq!(
+ fixture.files, original,
+ "no stabilization, newline rewrite or accepted adoption"
+ );
+ for (index, (path, text)) in original.iter().enumerate() {
+ let owned = snapshot.file(index).unwrap();
+ assert_eq!((owned.path, owned.text), (path.as_str(), text.as_str()));
+ }
+}
+
+#[test]
+fn comments_strings_and_comment_gap_are_not_navigation_hits() {
+ let fixture = Fixture::standard();
+ let snapshot = fixture.snapshot(None, true, Limits::default());
+ let text = fixture.text("links.arcadia");
+ for offset in [
+ text.find("separator @pump").unwrap() + "separator ".len(),
+ text.find("船 @pump").unwrap() + "船 ".len(),
+ text.find("@ //").unwrap() + 1,
+ ] {
+ let result = query(&snapshot, "links.arcadia", offset, Action::Definition);
+ assert_eq!(result.disposition, Disposition::NoSymbol);
+ assert_eq!(result.total_definitions, 0);
+ }
+ assert_eq!(
+ query(
+ &snapshot,
+ "links.arcadia",
+ text.find(" pump;").unwrap() + 3,
+ Action::Definition
+ )
+ .total_definitions,
+ 1
+ );
+ let inside_utf8 = text.find('船').unwrap() + 1;
+ assert_eq!(
+ snapshot.query(Query {
+ stamp: QueryStamp {
+ snapshot: key(1),
+ serial: 1
+ },
+ path: "links.arcadia",
+ byte_offset: inside_utf8,
+ action: Action::Definition
+ }),
+ Err(QueryError::Query(symbols::QueryError::InvalidBytePosition))
+ );
+}
+
+#[test]
+fn dirty_but_parseable_text_has_current_names_and_no_validated_fallback() {
+ let original = Fixture::standard();
+ let mut dirty = original.clone();
+ *dirty.text_mut("模块/活動.arcadia") = dirty
+ .text("模块/活動.arcadia")
+ .replace("泵 🚀", "Edited 泵\\nwith detail");
+ let snapshot = dirty.snapshot(Some(&original), true, Limits::default());
+ assert!(snapshot.scope().closure_complete);
+ assert!(!snapshot.scope().validated);
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&dirty),
+ Action::Definition,
+ );
+ assert_eq!(result.disposition, Disposition::Choices);
+ assert_eq!(
+ result.definitions[0].declaration.name.as_deref(),
+ Some("Edited 泵\nwith detail")
+ );
+ assert_eq!(result.definitions[0].evidence, Evidence::Draft);
+ assert_eq!(
+ snapshot.file(1).unwrap().text,
+ dirty.text("模块/活動.arcadia")
+ );
+ assert_ne!(
+ snapshot.file(1).unwrap().text,
+ original.text("模块/活動.arcadia")
+ );
+}
+
+#[test]
+fn invalid_include_syntax_never_borrows_the_old_reachable_file_set() {
+ let original = Fixture::standard();
+ let mut invalid = original.clone();
+ *invalid.text_mut("project.arcadia") = invalid
+ .text("project.arcadia")
+ .replace("include \"links.arcadia\";", "include \"links.arcadia\"");
+ let snapshot = invalid.snapshot(Some(&original), true, Limits::default());
+ assert!(snapshot.scope().partial());
+ assert!(!snapshot.scope().validated);
+ assert!(
+ snapshot
+ .scope()
+ .issues
+ .iter()
+ .any(|issue| issue.kind == ScopeIssueKind::InvalidIncludes)
+ );
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&invalid),
+ Action::Definition,
+ );
+ assert_eq!(result.total_definitions, 1);
+ assert_eq!(result.disposition, Disposition::Choices);
+ assert_eq!(result.definitions[0].membership, Membership::Unverified);
+}
+
+#[test]
+fn missing_include_partial_scope_does_not_resurrect_witness_text() {
+ let original = Fixture::standard();
+ let mut missing = original.clone();
+ missing
+ .files
+ .retain(|(path, _)| path != "模块/活動.arcadia");
+ let snapshot = missing.snapshot(Some(&original), true, Limits::default());
+ assert!(snapshot.scope().partial());
+ assert!(
+ snapshot
+ .scope()
+ .issues
+ .iter()
+ .any(|issue| issue.kind == ScopeIssueKind::MissingInclude)
+ );
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&missing),
+ Action::Definition,
+ );
+ assert_eq!(result.disposition, Disposition::NoDefinition);
+ assert!(result.definitions.is_empty());
+ assert!(
+ result.scope.partial(),
+ "zero means no loaded definition, not no project definition"
+ );
+ assert!(snapshot.file(2).is_none());
+}
+
+#[test]
+fn detached_alias_and_identity_collisions_never_auto_select() {
+ let accepted = Fixture::standard();
+ for (alias, id) in [
+ ("pump", "00000000-0000-0000-0000-000000000099"),
+ ("other", PUMP),
+ ] {
+ let mut loaded = accepted.clone();
+ loaded.files.push((
+ "detached.arcadia".into(),
+ format!("oa_activity {alias} \"Detached\" id \"{id}\" {{}}"),
+ ));
+ let snapshot = loaded.snapshot(Some(&accepted), true, Limits::default());
+ assert!(snapshot.scope().validated);
+ assert_eq!(snapshot.scope().detached_files, 1);
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&loaded),
+ Action::Definition,
+ );
+ assert_eq!(result.disposition, Disposition::Choices);
+ if alias == "pump" {
+ assert_eq!(result.total_definitions, 2);
+ assert!(result.definitions.iter().any(
+ |row| row.membership == Membership::Detached && row.evidence == Evidence::Draft
+ ));
+ } else {
+ assert_eq!(result.total_definitions, 1);
+ assert_eq!(
+ result.definitions[0].declaration.identity,
+ Identity::Collision { count: 2 }
+ );
+ }
+ }
+}
+
+#[test]
+fn sole_detached_candidate_and_unapplied_equal_bytes_require_choice() {
+ let accepted = Fixture::standard();
+ let mut detached = accepted.clone();
+ *detached.text_mut("project.arcadia") = detached
+ .text("project.arcadia")
+ .replace(" include \"模块/活動.arcadia\";\r\n", "");
+ let snapshot = detached.snapshot(None, true, Limits::default());
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&detached),
+ Action::Definition,
+ );
+ assert_eq!(result.disposition, Disposition::Choices);
+ assert_eq!(result.definitions[0].membership, Membership::Detached);
+ let unapplied = accepted.snapshot(Some(&accepted), false, Limits::default());
+ assert!(unapplied.scope().closure_complete && unapplied.scope().partial());
+ assert!(!unapplied.scope().validated);
+ assert_eq!(
+ query(
+ &unapplied,
+ "links.arcadia",
+ reference_offset(&accepted),
+ Action::Definition
+ )
+ .disposition,
+ Disposition::Choices
+ );
+}
+
+#[test]
+fn include_cycle_depth_and_aggregate_edges_are_explicit_partial_outcomes() {
+ let mut fixture = Fixture::standard();
+ fixture
+ .text_mut("模块/活動.arcadia")
+ .push_str("include \"../project.arcadia\";\r\n");
+ let cycle = fixture.snapshot(None, true, Limits::default());
+ assert!(
+ cycle
+ .scope()
+ .issues
+ .iter()
+ .any(|issue| issue.kind == ScopeIssueKind::IncludeCycle)
+ );
+ let fixture = Fixture::standard();
+ for (depth, edges, expected) in [
+ (0, 1024, ScopeIssueKind::IncludeDepth),
+ (32, 1, ScopeIssueKind::IncludeBudget),
+ ] {
+ let snapshot = fixture.snapshot(
+ None,
+ true,
+ Limits {
+ include_depth: depth,
+ include_edges: edges,
+ ..Limits::default()
+ },
+ );
+ assert!(snapshot.scope().partial());
+ assert!(
+ snapshot
+ .scope()
+ .issues
+ .iter()
+ .any(|issue| issue.kind == expected)
+ );
+ assert_eq!(
+ query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::Definition
+ )
+ .disposition,
+ Disposition::Choices
+ );
+ }
+}
+
+#[test]
+fn exact_snapshot_readback_rejects_same_key_byte_membership_and_evidence_changes() {
+ let fixture = Fixture::standard();
+ let files = fixture.borrowed();
+ let validation = Some(ValidatedSources {
+ entry: &fixture.entry,
+ files: &files,
+ });
+ let input = Input {
+ key: key(1),
+ entry: &fixture.entry,
+ files: &files,
+ applied: true,
+ validation,
+ };
+ let snapshot = capture(input, Limits::default()).unwrap();
+ assert!(snapshot.matches(input));
+ assert!(!snapshot.matches(Input {
+ key: key(2),
+ ..input
+ }));
+ assert!(!snapshot.matches(Input {
+ validation: None,
+ ..input
+ }));
+ assert!(!snapshot.matches(Input {
+ applied: false,
+ ..input
+ }));
+ let mut reversed = files.clone();
+ reversed.reverse();
+ assert!(snapshot.matches(Input {
+ files: &reversed,
+ ..input
+ }));
+ let mut changed = fixture.clone();
+ changed.text_mut("links.arcadia").push(' ');
+ let altered = changed.borrowed();
+ assert!(!snapshot.matches(Input {
+ files: &altered,
+ ..input
+ }));
+ let missing = &files[..files.len() - 1];
+ assert!(!snapshot.matches(Input {
+ files: missing,
+ ..input
+ }));
+ let duplicated = [files[0], files[0], files[2]];
+ assert!(!snapshot.matches(Input {
+ files: &duplicated,
+ ..input
+ }));
+ assert_eq!(
+ snapshot.query(Query {
+ stamp: QueryStamp {
+ snapshot: key(2),
+ serial: 37
+ },
+ path: "links.arcadia",
+ byte_offset: 0,
+ action: Action::Definition
+ }),
+ Err(QueryError::Query(symbols::QueryError::StaleSnapshot))
+ );
+}
+
+#[test]
+fn omitted_rows_are_counted_before_result_allocation_and_never_confer_uniqueness() {
+ let fixture = Fixture::standard();
+ let snapshot = fixture.snapshot(
+ Some(&fixture),
+ true,
+ Limits {
+ result_rows: 2,
+ ..Limits::default()
+ },
+ );
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::References,
+ );
+ assert_eq!((result.total_definitions, result.total_references), (1, 2));
+ assert_eq!(
+ (
+ result.definitions.len(),
+ result.references.len(),
+ result.omitted_references
+ ),
+ (1, 1, 1)
+ );
+ let zero = fixture.snapshot(
+ Some(&fixture),
+ true,
+ Limits {
+ result_rows: 0,
+ ..Limits::default()
+ },
+ );
+ let result = query(
+ &zero,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::Definition,
+ );
+ assert_eq!(
+ (
+ result.definitions.len(),
+ result.total_definitions,
+ result.omitted_definitions
+ ),
+ (0, 1, 1)
+ );
+ assert_eq!(result.disposition, Disposition::Choices);
+ let envelope = fixture.snapshot(
+ Some(&fixture),
+ true,
+ Limits {
+ result_bytes: 0,
+ ..Limits::default()
+ },
+ );
+ assert_eq!(
+ envelope.query(Query {
+ stamp: QueryStamp {
+ snapshot: key(1),
+ serial: 1
+ },
+ path: "links.arcadia",
+ byte_offset: reference_offset(&fixture),
+ action: Action::Definition
+ }),
+ Err(QueryError::ResultEnvelopeLimit)
+ );
+}
+
+#[test]
+fn large_display_name_cannot_escape_the_owned_row_string_budget() {
+ let mut fixture = Fixture::standard();
+ *fixture.text_mut("模块/活動.arcadia") = fixture
+ .text("模块/活動.arcadia")
+ .replace("泵 🚀", &"長".repeat(8192));
+ let original = fixture.files.clone();
+ let snapshot = fixture.snapshot(
+ None,
+ true,
+ Limits {
+ result_bytes: 64,
+ ..Limits::default()
+ },
+ );
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::Definition,
+ );
+ assert_eq!(
+ (result.total_definitions, result.omitted_definitions),
+ (1, 1)
+ );
+ assert!(result.definitions.is_empty());
+ assert_eq!(result.disposition, Disposition::Choices);
+ assert_eq!(fixture.files, original);
+}
+
+#[test]
+fn validation_witness_must_match_entry_and_complete_included_file_set() {
+ let fixture = Fixture::standard();
+ for mutation in 0..3 {
+ let mut witness = fixture.clone();
+ match mutation {
+ 0 => witness.entry = "other.arcadia".into(),
+ 1 => {
+ witness.files.pop();
+ }
+ _ => witness.files.push(("unused.arcadia".into(), String::new())),
+ }
+ let snapshot = fixture.snapshot(Some(&witness), true, Limits::default());
+ assert!(snapshot.scope().closure_complete);
+ assert!(!snapshot.scope().validated);
+ assert_eq!(
+ query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::Definition
+ )
+ .disposition,
+ Disposition::Choices
+ );
+ }
+}
+
+#[test]
+fn raw_aggregate_limits_refuse_before_outline_or_copied_snapshot() {
+ // Token budget zero and malformed paths/text would fail later. Raw byte/path
+ // limits must win, including a large second file; no partial Snapshot exists.
+ let large = "not a model \0".repeat(16_384);
+ let files = [
+ DraftFile {
+ path: "../bad.arcadia",
+ text: "?",
+ },
+ DraftFile {
+ path: "large.arcadia",
+ text: &large,
+ },
+ ];
+ let input = Input {
+ key: key(1),
+ entry: "project.arcadia",
+ files: &files,
+ applied: true,
+ validation: None,
+ };
+ for (resource, files_limit, bytes_limit, paths_limit) in [
+ (symbols::Resource::Files, 1, usize::MAX, usize::MAX),
+ (symbols::Resource::SourceBytes, 8, 1, usize::MAX),
+ (symbols::Resource::PathBytes, 8, usize::MAX, 1),
+ ] {
+ let limits = Limits {
+ index: symbols::Limits {
+ files: files_limit,
+ source_bytes: bytes_limit,
+ path_bytes: paths_limit,
+ tokens: 0,
+ records: 0,
+ },
+ ..Limits::default()
+ };
+ assert!(
+ matches!(capture(input, limits), Err(AdmissionError::Index(symbols::BuildError::Limit { resource: actual, .. })) if actual == resource)
+ );
+ }
+ assert_eq!(files[1].text, large);
+}
+
+#[test]
+fn token_declaration_and_live_reference_limits_are_aggregate_not_per_file() {
+ let files = [
+ DraftFile {
+ path: "a.arcadia",
+ text: "oa_activity a \"A\" {}",
+ },
+ DraftFile {
+ path: "b.arcadia",
+ text: "oa_activity b \"B\" {}",
+ },
+ ];
+ let input = Input {
+ key: key(1),
+ entry: "a.arcadia",
+ files: &files,
+ applied: false,
+ validation: None,
+ };
+ let limits = Limits {
+ index: symbols::Limits {
+ tokens: 10,
+ ..Limits::default().index
+ },
+ ..Limits::default()
+ };
+ assert!(matches!(
+ capture(input, limits),
+ Err(AdmissionError::Outline {
+ file: 1,
+ error: language::OutlineLimit {
+ resource: language::OutlineResource::Tokens,
+ ..
+ }
+ })
+ ));
+ let limits = Limits {
+ index: symbols::Limits {
+ records: 1,
+ ..Limits::default().index
+ },
+ ..Limits::default()
+ };
+ assert!(matches!(
+ capture(input, limits),
+ Err(AdmissionError::Outline {
+ file: 1,
+ error: language::OutlineLimit {
+ resource: language::OutlineResource::Declarations,
+ ..
+ }
+ })
+ ));
+ let use_files = [
+ files[0],
+ DraftFile {
+ path: "uses.arcadia",
+ text: "source @a; target @a; // @a ignored\n",
+ },
+ ];
+ let limits = Limits {
+ index: symbols::Limits {
+ records: 2,
+ ..Limits::default().index
+ },
+ ..Limits::default()
+ };
+ assert!(matches!(
+ capture(
+ Input {
+ files: &use_files,
+ ..input
+ },
+ limits
+ ),
+ Err(AdmissionError::Index(symbols::BuildError::Limit {
+ resource: symbols::Resource::Records,
+ observed_at_least: 3,
+ ..
+ }))
+ ));
+}
+
+#[test]
+fn missing_and_noncanonical_authored_ids_remain_draft_spans_without_generated_anchors() {
+ let fixture = Fixture::standard();
+ for replacement in [
+ String::new(),
+ " id \"00000000000000000000000000000002\"".into(),
+ ] {
+ let mut draft = fixture.clone();
+ let original_id = format!(" id \"{PUMP}\"");
+ *draft.text_mut("模块/活動.arcadia") = draft
+ .text("模块/活動.arcadia")
+ .replace(&original_id, &replacement);
+ draft
+ .text_mut("模块/活動.arcadia")
+ .push_str(&format!("// Historical UUID {PUMP} and @pump\r\n"));
+ let before = draft.files.clone();
+ let snapshot = draft.snapshot(None, false, Limits::default());
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&draft),
+ Action::Definition,
+ );
+ assert_eq!(result.definitions[0].declaration.authored_id, None);
+ assert_eq!(
+ result.definitions[0].declaration.identity,
+ Identity::Missing
+ );
+ assert_eq!(result.disposition, Disposition::Choices);
+ assert_eq!(draft.files, before);
+ assert_eq!(
+ snapshot.file(1).unwrap().text,
+ draft.text("模块/活動.arcadia")
+ );
+ }
+}
+
+#[test]
+fn keyword_alias_and_loaded_path_case_collisions_use_reviewed_index_rules() {
+ let mut fixture = Fixture::standard();
+ for (_, text) in &mut fixture.files {
+ *text = text.replace("pump", "source");
+ }
+ let snapshot = fixture.snapshot(None, true, Limits::default());
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::Definition,
+ );
+ assert_eq!(result.alias.as_deref(), Some("source"));
+ assert_eq!(result.total_definitions, 1);
+ fixture.files.push(("LINKS.arcadia".into(), String::new()));
+ let files = fixture.borrowed();
+ assert!(matches!(
+ capture(
+ Input {
+ key: key(1),
+ entry: &fixture.entry,
+ files: &files,
+ applied: true,
+ validation: None
+ },
+ Limits::default()
+ ),
+ Err(AdmissionError::Index(symbols::BuildError::Input {
+ problem: symbols::InputProblem::DuplicateOrCaseCollidingPath,
+ ..
+ }))
+ ));
+}
+
+#[test]
+fn owned_request_moves_exact_text_into_worker_snapshot_and_matches_sync_results() {
+ let fixture = Fixture::standard();
+ let files = fixture.borrowed();
+ let input = Input {
+ key: key(1),
+ entry: &fixture.entry,
+ files: &files,
+ applied: true,
+ validation: Some(ValidatedSources {
+ entry: &fixture.entry,
+ files: &files,
+ }),
+ };
+ let owned = admit_owned(input, Limits::default()).unwrap();
+ let allocations: Vec<_> = owned.files.iter().map(|file| file.text.as_ptr()).collect();
+ let snapshot = owned.capture().unwrap();
+ for (file, allocation) in allocations.iter().enumerate() {
+ assert_eq!(
+ snapshot.file(file).unwrap().text.as_ptr(),
+ *allocation,
+ "worker moves the one current-text copy"
+ );
+ }
+ assert!(snapshot.matches(input));
+ let synchronous = capture(input, Limits::default()).unwrap();
+ assert_eq!(
+ query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::References
+ ),
+ query(
+ &synchronous,
+ "links.arcadia",
+ reference_offset(&fixture),
+ Action::References
+ )
+ );
+ let mut edited = fixture.clone();
+ edited
+ .text_mut("links.arcadia")
+ .push_str("// edited after request\r\n");
+ let changed = edited.borrowed();
+ assert!(!snapshot.matches(Input {
+ files: &changed,
+ ..input
+ }));
+ assert!(!snapshot.matches(Input {
+ validation: None,
+ ..input
+ }));
+}
+
+#[test]
+fn owned_request_checks_raw_limits_immediately_and_extracts_only_on_worker() {
+ let fixture = Fixture::standard();
+ let files = fixture.borrowed();
+ let input = Input {
+ key: key(1),
+ entry: &fixture.entry,
+ files: &files,
+ applied: true,
+ validation: None,
+ };
+ let no_tokens = Limits {
+ index: symbols::Limits {
+ tokens: 0,
+ ..Limits::default().index
+ },
+ ..Limits::default()
+ };
+ let owned = admit_owned(input, no_tokens).expect("request admission does not lex or parse");
+ assert!(matches!(
+ owned.capture(),
+ Err(AdmissionError::Outline {
+ file: 0,
+ error: language::OutlineLimit {
+ resource: language::OutlineResource::Tokens,
+ ..
+ }
+ })
+ ));
+ let no_bytes = Limits {
+ index: symbols::Limits {
+ source_bytes: 1,
+ ..Limits::default().index
+ },
+ ..Limits::default()
+ };
+ assert!(matches!(
+ admit_owned(input, no_bytes),
+ Err(AdmissionError::Index(symbols::BuildError::Limit {
+ resource: symbols::Resource::SourceBytes,
+ ..
+ }))
+ ));
+ assert_eq!(files[1].text, fixture.text("模块/活動.arcadia"));
+}
+
+#[test]
+fn owned_validation_proof_preserves_full_witness_count_and_partial_scope() {
+ let accepted = Fixture::standard();
+ for missing_include in [false, true] {
+ let mut current = accepted.clone();
+ current
+ .files
+ .retain(|(path, _)| path != "模块/活動.arcadia");
+ if !missing_include {
+ *current.text_mut("project.arcadia") = current
+ .text("project.arcadia")
+ .replace(" include \"模块/活動.arcadia\";\r\n", "");
+ }
+ let files = current.borrowed();
+ let old = accepted.borrowed();
+ let input = Input {
+ key: key(1),
+ entry: ¤t.entry,
+ files: &files,
+ applied: true,
+ validation: Some(ValidatedSources {
+ entry: &accepted.entry,
+ files: &old,
+ }),
+ };
+ let snapshot = admit_owned(input, Limits::default())
+ .unwrap()
+ .capture()
+ .unwrap();
+ assert_eq!(snapshot.scope().closure_complete, !missing_include);
+ assert!(
+ !snapshot.scope().validated,
+ "the old witness is not silently reduced to matching loaded files"
+ );
+ let result = query(
+ &snapshot,
+ "links.arcadia",
+ reference_offset(¤t),
+ Action::Definition,
+ );
+ assert_eq!(result.disposition, Disposition::NoDefinition);
+ assert!(snapshot.matches(input));
+ }
+}
diff --git a/crates/oa-desktop/src/source_navigation/symbols.rs b/crates/oa-desktop/src/source_navigation/symbols.rs
new file mode 100644
index 00000000..9cf8e523
--- /dev/null
+++ b/crates/oa-desktop/src/source_navigation/symbols.rs
@@ -0,0 +1,2043 @@
+//! Current-text symbol queries, with no model or editor mutation.
+//!
+//! The caller supplies one immutable snapshot of current text, full lexer tokens,
+//! and syntax-only declaration role spans. This module does not lex, parse
+//! Arcadia declarations, lint, compile, allocate model IDs, discover includes,
+//! or read an accepted Project.
+//! Its matches are authored lexical uses, not typed semantic dependency edges.
+
+use oa_core::Id;
+use oa_core::language::{Span, TokenClass, TokenSpan};
+use std::collections::{BTreeMap, BTreeSet};
+use std::ops::Range;
+
+/// Fresh editor/workspace session identity plus a revision changed for EVERY
+/// indexed input change, including membership, outline and validation evidence.
+/// Reusing this key for different input violates the caller contract.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SnapshotKey {
+ pub workspace: Id,
+ pub revision: u64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Membership {
+ Included,
+ Detached,
+ Unverified,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Evidence {
+ /// The caller verified these exact bytes in the current source scope.
+ Validated,
+ /// Current editor bytes; no successful semantic validation is claimed.
+ Draft,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SyntaxCoverage {
+ /// The tolerant walk considered the entire file, even if its text is invalid.
+ FullFile,
+ /// A stopped/budget-limited outline cannot masquerade as a complete result.
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Defensive input contract exercised by index tests."
+ )
+ )]
+ Incomplete,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PartialReason {
+ IncludesUnverified,
+ MissingFiles,
+ UnappliedWorkspace,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Scope {
+ CompleteProject,
+ Partial(PartialReason),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Domain {
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested included-only queries; editor presents all loaded candidates."
+ )
+ )]
+ Included,
+ Loaded,
+}
+
+impl Domain {
+ fn contains(self, membership: Membership) -> bool {
+ self == Self::Loaded || membership == Membership::Included
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredIdentity {
+ pub id: Id,
+ /// Whole quoted JSON string token in the declaration header, not a UUID
+ /// mentioned in a body, history record, comment or display name.
+ pub literal_span: Span,
+}
+
+/// These header roles must come from the coordinated core outline seam. The
+/// existing public DeclarationSpan alone lacks alias/kind/ID literal spans.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DeclarationInput {
+ pub span: Span,
+ pub kind_span: Span,
+ pub alias_span: Span,
+ pub identity: Option,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct FileInput<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ /// Exact lexer output for text, in order and covering the entire file.
+ pub tokens: &'a [TokenSpan],
+ /// Tolerant outline in alias-source order. Whole spans may nest.
+ pub declarations: &'a [DeclarationInput],
+ pub syntax: SyntaxCoverage,
+ pub membership: Membership,
+ pub evidence: Evidence,
+}
+
+/// Explicit experiment limits; production must select its own shared budgets.
+/// Caller-owned text/tokens/outline must already have bounded extraction.
+#[derive(Debug, Clone, Copy)]
+pub struct Limits {
+ pub files: usize,
+ pub source_bytes: usize,
+ pub path_bytes: usize,
+ pub tokens: usize,
+ pub records: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Resource {
+ Files,
+ SourceBytes,
+ PathBytes,
+ Tokens,
+ Records,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum InputProblem {
+ NonCanonicalPath,
+ DuplicateOrCaseCollidingPath,
+ InconsistentCompleteScope,
+ IncompleteSyntax,
+ TokenCoverage,
+ TokenBounds,
+ DeclarationBounds,
+ DeclarationOrder,
+ HeaderRoleToken,
+ InvalidAlias,
+ IdentityLiteral,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum BuildError {
+ /// No partial Index is returned. This is not a zero-result query.
+ Limit {
+ resource: Resource,
+ limit: usize,
+ observed_at_least: usize,
+ },
+ Input {
+ file: usize,
+ record: Option,
+ problem: InputProblem,
+ },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Location {
+ pub file: usize,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DefinitionHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ReferenceHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+/// Carries only an authored identity, never an identity generated by compilation.
+/// It may be resolved across revisions of the same workspace. Resolution still
+/// checks global loaded-file uniqueness every time.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredAnchor {
+ workspace: Id,
+ id: Id,
+}
+
+impl AuthoredAnchor {
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested authored-anchor API; editor Back uses exact buffer generations."
+ )
+ )]
+ pub fn id(self) -> Id {
+ self.id
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Identity {
+ Missing,
+ Unique(AuthoredAnchor),
+ Collision { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested unique-reference and authored-anchor projections."
+ )
+)]
+pub enum Lookup {
+ Missing,
+ Unique(DefinitionHandle),
+ Ambiguous { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Hit {
+ Definition(DefinitionHandle),
+ Reference(ReferenceHandle),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum QueryError {
+ StaleSnapshot,
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Authored-anchor projection rejects cross-workspace identity."
+ )
+ )]
+ WrongWorkspace,
+ InvalidHandle,
+ InvalidFile,
+ InvalidBytePosition,
+ #[cfg_attr(
+ not(test),
+ expect(dead_code, reason = "Tested included-only reference projection.")
+ )]
+ OutsideDomain,
+ #[cfg_attr(
+ not(test),
+ expect(dead_code, reason = "Tested unique-declaration reference projection.")
+ )]
+ AmbiguousAlias {
+ count: usize,
+ },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested primitive scope; editor exposes adapter include inspection."
+ )
+)]
+pub struct QueryScope {
+ pub declared: Scope,
+ pub domain: Domain,
+ /// True only for the complete included project, not loaded detached files.
+ pub project_complete: bool,
+ pub all_selected_files_validated: bool,
+ pub selected_files: usize,
+ pub detached_files: usize,
+ pub unverified_files: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Position {
+ pub line: usize,
+ /// Zero-based UTF-8 byte column. The editor adapter must convert explicitly
+ /// if it needs character/UTF-16 columns; CRLF bytes are never normalized.
+ pub byte_column: usize,
+}
+
+#[derive(Debug)]
+pub struct Definition<'a> {
+ pub alias: &'a str,
+ pub kind: &'a str,
+ pub declaration: Location,
+ pub alias_location: Location,
+ pub authored_id: Option,
+}
+
+#[derive(Debug)]
+pub struct Reference<'a> {
+ pub alias: &'a str,
+ pub at: Location,
+ pub alias_location: Location,
+}
+
+#[derive(Debug)]
+pub struct SourceFile<'a> {
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Retained source projection for independently tested index callers."
+ )
+ )]
+ pub path: &'a str,
+ pub text: &'a str,
+ pub membership: Membership,
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Included-only scope projection retains validation evidence."
+ )
+ )]
+ pub evidence: Evidence,
+ definitions: Range,
+ references: Range,
+}
+
+#[derive(Debug)]
+pub struct Index<'a> {
+ snapshot: SnapshotKey,
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested domain scope projection; adapter owns the UI scope."
+ )
+ )]
+ scope: Scope,
+ files: Vec>,
+ definitions: Vec>,
+ references: Vec>,
+ aliases: BTreeMap<&'a str, Vec>,
+ uses: BTreeMap<&'a str, Vec>,
+ identities: BTreeMap>,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Counts {
+ definitions: usize,
+ references: usize,
+}
+
+fn charge(
+ total: &mut usize,
+ value: usize,
+ limit: usize,
+ resource: Resource,
+) -> Result<(), BuildError> {
+ let next = total.checked_add(value).ok_or(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: usize::MAX,
+ })?;
+ if next > limit {
+ return Err(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: next,
+ });
+ }
+ *total = next;
+ Ok(())
+}
+
+fn input_error(file: usize, record: Option, problem: InputProblem) -> BuildError {
+ BuildError::Input {
+ file,
+ record,
+ problem,
+ }
+}
+
+fn valid_span(text: &str, span: Span) -> bool {
+ span.start < span.end
+ && span.end <= text.len()
+ && text.is_char_boundary(span.start)
+ && text.is_char_boundary(span.end)
+}
+
+fn contains(outer: Span, inner: Span) -> bool {
+ outer.start <= inner.start && inner.end <= outer.end
+}
+
+fn token_at(tokens: &[TokenSpan], span: Span) -> Option {
+ let index = tokens
+ .binary_search_by_key(&span.start, |token| token.span.start)
+ .ok()?;
+ let token = tokens[index];
+ (token.span == span).then_some(token)
+}
+
+fn word(class: TokenClass) -> bool {
+ matches!(class, TokenClass::Identifier | TokenClass::Keyword)
+}
+
+/// Only a live punctuation @ followed by a legal alias outside trivia qualifies.
+/// Trivia can separate @ and alias, but its bytes never become a clickable hit.
+fn reference_spans<'a>(file: FileInput<'a>) -> impl Iterator- + 'a {
+ file.tokens
+ .iter()
+ .enumerate()
+ .filter_map(move |(index, token)| {
+ if token.class != TokenClass::Punctuation
+ || &file.text[token.span.start..token.span.end] != "@"
+ {
+ return None;
+ }
+ let next = file.tokens[index + 1..]
+ .iter()
+ .find(|next| !matches!(next.class, TokenClass::Whitespace | TokenClass::Comment))?;
+ let alias = &file.text[next.span.start..next.span.end];
+ (word(next.class) && oa_core::valid_alias(alias)).then_some((token.span, next.span))
+ })
+}
+
+/// Runs all aggregate limits and structural checks before allocating Index maps
+/// or vectors. Path normalization/quoted-ID decoding may allocate bounded scratch
+/// strings. Scratch storage is proportional to admitted input bytes and file
+/// count, including Unicode expansion and collection overhead.
+fn preflight(files: &[FileInput<'_>], scope: Scope, limits: Limits) -> Result
{
+ let mut file_count = 0;
+ charge(&mut file_count, files.len(), limits.files, Resource::Files)?;
+ let mut bytes = 0;
+ let mut path_bytes = 0;
+ let mut tokens = 0;
+ let mut records = 0;
+ for file in files {
+ charge(
+ &mut bytes,
+ file.text.len(),
+ limits.source_bytes,
+ Resource::SourceBytes,
+ )?;
+ charge(
+ &mut path_bytes,
+ file.path.len(),
+ limits.path_bytes,
+ Resource::PathBytes,
+ )?;
+ charge(
+ &mut tokens,
+ file.tokens.len(),
+ limits.tokens,
+ Resource::Tokens,
+ )?;
+ charge(
+ &mut records,
+ file.declarations.len(),
+ limits.records,
+ Resource::Records,
+ )?;
+ }
+ let definitions = records;
+ if scope == Scope::CompleteProject
+ && (!files
+ .iter()
+ .any(|file| file.membership == Membership::Included)
+ || files
+ .iter()
+ .any(|file| file.membership == Membership::Unverified))
+ {
+ return Err(input_error(
+ 0,
+ None,
+ InputProblem::InconsistentCompleteScope,
+ ));
+ }
+ // Scratch storage is allocated only after aggregate file/path admission.
+ // Each admitted path is folded once, retaining core's Unicode policy.
+ let mut folded_paths = BTreeSet::new();
+ for (file_index, file) in files.iter().copied().enumerate() {
+ if file.syntax != SyntaxCoverage::FullFile {
+ return Err(input_error(
+ file_index,
+ None,
+ InputProblem::IncompleteSyntax,
+ ));
+ }
+ if oa_core::source_project::resolve_include("", file.path).as_deref() != Ok(file.path) {
+ return Err(input_error(
+ file_index,
+ None,
+ InputProblem::NonCanonicalPath,
+ ));
+ }
+ if !folded_paths.insert(file.path.to_lowercase()) {
+ return Err(input_error(
+ file_index,
+ None,
+ InputProblem::DuplicateOrCaseCollidingPath,
+ ));
+ }
+ let mut end = 0;
+ for (token_index, token) in file.tokens.iter().enumerate() {
+ if !valid_span(file.text, token.span) {
+ return Err(input_error(
+ file_index,
+ Some(token_index),
+ InputProblem::TokenBounds,
+ ));
+ }
+ if token.span.start != end {
+ return Err(input_error(
+ file_index,
+ Some(token_index),
+ InputProblem::TokenCoverage,
+ ));
+ }
+ end = token.span.end;
+ }
+ if end != file.text.len() {
+ return Err(input_error(file_index, None, InputProblem::TokenCoverage));
+ }
+ let mut previous_alias_end = 0;
+ for (declaration_index, declaration) in file.declarations.iter().enumerate() {
+ let problem = |problem| input_error(file_index, Some(declaration_index), problem);
+ if !valid_span(file.text, declaration.span)
+ || !valid_span(file.text, declaration.kind_span)
+ || !valid_span(file.text, declaration.alias_span)
+ || declaration.kind_span.start != declaration.span.start
+ || !contains(declaration.span, declaration.kind_span)
+ || !contains(declaration.span, declaration.alias_span)
+ || declaration.alias_span.start < declaration.kind_span.end
+ {
+ return Err(problem(InputProblem::DeclarationBounds));
+ }
+ if declaration.alias_span.start < previous_alias_end {
+ return Err(problem(InputProblem::DeclarationOrder));
+ }
+ previous_alias_end = declaration.alias_span.end;
+ if !token_at(file.tokens, declaration.kind_span).is_some_and(|token| word(token.class))
+ || !token_at(file.tokens, declaration.alias_span)
+ .is_some_and(|token| word(token.class))
+ {
+ return Err(problem(InputProblem::HeaderRoleToken));
+ }
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ if !oa_core::valid_alias(alias) {
+ return Err(problem(InputProblem::InvalidAlias));
+ }
+ if let Some(identity) = declaration.identity {
+ if !valid_span(file.text, identity.literal_span)
+ || !contains(declaration.span, identity.literal_span)
+ || identity.literal_span.start < declaration.alias_span.end
+ || token_at(file.tokens, identity.literal_span).map(|token| token.class)
+ != Some(TokenClass::String)
+ {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ let literal = &file.text[identity.literal_span.start..identity.literal_span.end];
+ let decoded = serde_json::from_str::(literal)
+ .ok()
+ .filter(|value| oa_core::language::has_uuid_header_shape(value))
+ .and_then(|value| Id::parse_str(&value).ok());
+ if decoded != Some(identity.id) {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ }
+ }
+ // Token bounds and coverage are proven before reference_spans slices.
+ for _ in reference_spans(file) {
+ charge(&mut records, 1, limits.records, Resource::Records)?;
+ }
+ }
+ Ok(Counts {
+ definitions,
+ references: records - definitions,
+ })
+}
+
+impl<'a> Index<'a> {
+ pub fn build(
+ snapshot: SnapshotKey,
+ files: &[FileInput<'a>],
+ scope: Scope,
+ limits: Limits,
+ ) -> Result {
+ let counts = preflight(files, scope, limits)?;
+ let mut index = Self {
+ snapshot,
+ scope,
+ files: Vec::with_capacity(files.len()),
+ definitions: Vec::with_capacity(counts.definitions),
+ references: Vec::with_capacity(counts.references),
+ aliases: BTreeMap::new(),
+ uses: BTreeMap::new(),
+ identities: BTreeMap::new(),
+ };
+ for (file_index, file) in files.iter().copied().enumerate() {
+ let definition_start = index.definitions.len();
+ let reference_start = index.references.len();
+ for declaration in file.declarations {
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ let number = index.definitions.len();
+ index.aliases.entry(alias).or_default().push(number);
+ if let Some(identity) = declaration.identity {
+ index
+ .identities
+ .entry(identity.id)
+ .or_default()
+ .push(number);
+ }
+ index.definitions.push(Definition {
+ alias,
+ kind: &file.text[declaration.kind_span.start..declaration.kind_span.end],
+ declaration: Location {
+ file: file_index,
+ span: declaration.span,
+ },
+ alias_location: Location {
+ file: file_index,
+ span: declaration.alias_span,
+ },
+ authored_id: declaration.identity.map(|identity| identity.id),
+ });
+ }
+ for (at, alias_span) in reference_spans(file) {
+ let alias = &file.text[alias_span.start..alias_span.end];
+ index
+ .uses
+ .entry(alias)
+ .or_default()
+ .push(index.references.len());
+ index.references.push(Reference {
+ alias,
+ at: Location {
+ file: file_index,
+ span: at,
+ },
+ alias_location: Location {
+ file: file_index,
+ span: alias_span,
+ },
+ });
+ }
+ index.files.push(SourceFile {
+ path: file.path,
+ text: file.text,
+ membership: file.membership,
+ evidence: file.evidence,
+ definitions: definition_start..index.definitions.len(),
+ references: reference_start..index.references.len(),
+ });
+ }
+ Ok(index)
+ }
+
+ pub fn source(&self, file: usize) -> Result<&SourceFile<'a>, QueryError> {
+ self.files.get(file).ok_or(QueryError::InvalidFile)
+ }
+
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested included/loaded scope projection; editor uses adapter scope."
+ )
+ )]
+ pub fn scope(&self, domain: Domain) -> QueryScope {
+ let selected_files = self
+ .files
+ .iter()
+ .filter(|file| domain.contains(file.membership))
+ .count();
+ QueryScope {
+ declared: self.scope,
+ domain,
+ project_complete: self.scope == Scope::CompleteProject && domain == Domain::Included,
+ all_selected_files_validated: selected_files != 0
+ && self
+ .files
+ .iter()
+ .filter(|file| domain.contains(file.membership))
+ .all(|file| file.evidence == Evidence::Validated),
+ selected_files,
+ detached_files: self
+ .files
+ .iter()
+ .filter(|file| file.membership == Membership::Detached)
+ .count(),
+ unverified_files: self
+ .files
+ .iter()
+ .filter(|file| file.membership == Membership::Unverified)
+ .count(),
+ }
+ }
+
+ fn definition_handle(&self, index: usize) -> DefinitionHandle {
+ DefinitionHandle {
+ snapshot: self.snapshot,
+ index,
+ }
+ }
+
+ fn reference_handle(&self, index: usize) -> ReferenceHandle {
+ ReferenceHandle {
+ snapshot: self.snapshot,
+ index,
+ }
+ }
+
+ pub fn definition(&self, handle: DefinitionHandle) -> Result<&Definition<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.definitions
+ .get(handle.index)
+ .ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn reference(&self, handle: ReferenceHandle) -> Result<&Reference<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.references
+ .get(handle.index)
+ .ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn definitions_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> Definitions<'i, 'a> {
+ Definitions {
+ index: self,
+ indices: self
+ .aliases
+ .get(alias)
+ .map_or(&[][..], Vec::as_slice)
+ .iter(),
+ domain,
+ }
+ }
+
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested authored-identity lookup; editor uses aliases and collision checks."
+ )
+ )]
+ pub fn definitions_for_id(&self, id: Id, domain: Domain) -> Definitions<'_, 'a> {
+ Definitions {
+ index: self,
+ indices: self
+ .identities
+ .get(&id)
+ .map_or(&[][..], Vec::as_slice)
+ .iter(),
+ domain,
+ }
+ }
+
+ pub fn references_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> References<'i, 'a> {
+ References {
+ index: self,
+ indices: self.uses.get(alias).map_or(&[][..], Vec::as_slice).iter(),
+ domain,
+ }
+ }
+
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested unique-reference projection; UI shows bounded candidate rows."
+ )
+ )]
+ pub fn resolve_reference(
+ &self,
+ handle: ReferenceHandle,
+ domain: Domain,
+ ) -> Result {
+ let reference = self.reference(handle)?;
+ if !domain.contains(self.files[reference.at.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let mut definitions = self.definitions_for_alias(reference.alias, domain);
+ let Some(first) = definitions.next() else {
+ return Ok(Lookup::Missing);
+ };
+ let count = 1 + definitions.count();
+ Ok(if count == 1 {
+ Lookup::Unique(first)
+ } else {
+ Lookup::Ambiguous { count }
+ })
+ }
+
+ pub fn identity(&self, handle: DefinitionHandle) -> Result {
+ let Some(id) = self.definition(handle)?.authored_id else {
+ return Ok(Identity::Missing);
+ };
+ let count = self.identities[&id].len();
+ Ok(if count == 1 {
+ Identity::Unique(AuthoredAnchor {
+ workspace: self.snapshot.workspace,
+ id,
+ })
+ } else {
+ Identity::Collision { count }
+ })
+ }
+
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested identity-safe anchor API; editor Back uses buffer generations."
+ )
+ )]
+ pub fn resolve_anchor(
+ &self,
+ anchor: AuthoredAnchor,
+ domain: Domain,
+ ) -> Result {
+ if anchor.workspace != self.snapshot.workspace {
+ return Err(QueryError::WrongWorkspace);
+ }
+ let Some(candidates) = self.identities.get(&anchor.id) else {
+ return Ok(Lookup::Missing);
+ };
+ // A detached duplicate must not silently confer identity uniqueness.
+ if candidates.len() != 1 {
+ return Ok(Lookup::Ambiguous {
+ count: candidates.len(),
+ });
+ }
+ let candidate = candidates[0];
+ Ok(
+ if domain.contains(self.files[self.definitions[candidate].declaration.file].membership)
+ {
+ Lookup::Unique(self.definition_handle(candidate))
+ } else {
+ Lookup::Missing
+ },
+ )
+ }
+
+ /// Exact authored alias-use projection for a unique current declaration in
+ /// the requested domain. Duplicate aliases require candidate inspection;
+ /// this API never attributes all matching uses to the first declaration.
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested unique-target projection; UI labels lexical uses explicitly."
+ )
+ )]
+ pub fn references_to(
+ &self,
+ handle: DefinitionHandle,
+ domain: Domain,
+ ) -> Result, QueryError> {
+ let definition = self.definition(handle)?;
+ if !domain.contains(self.files[definition.declaration.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let count = self.definitions_for_alias(definition.alias, domain).count();
+ if count != 1 {
+ return Err(QueryError::AmbiguousAlias { count });
+ }
+ Ok(self.references_for_alias(definition.alias, domain))
+ }
+
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested exact single-location projection; adapter batches projections."
+ )
+ )]
+ pub fn position(&self, file: usize, offset: usize) -> Result {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let prefix = &source.text[..offset];
+ Ok(Position {
+ line: prefix.bytes().filter(|byte| *byte == b'\n').count(),
+ byte_column: prefix
+ .rsplit_once('\n')
+ .map_or(prefix.len(), |(_, tail)| tail.len()),
+ })
+ }
+
+ pub fn hit(&self, file: usize, offset: usize) -> Result, QueryError> {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let definitions = &self.definitions[source.definitions.clone()];
+ let before = definitions
+ .partition_point(|definition| definition.alias_location.span.start <= offset);
+ if before != 0 && offset < definitions[before - 1].alias_location.span.end {
+ return Ok(Some(Hit::Definition(
+ self.definition_handle(source.definitions.start + before - 1),
+ )));
+ }
+ let references = &self.references[source.references.clone()];
+ let before = references.partition_point(|reference| reference.at.span.start <= offset);
+ if before != 0 {
+ let reference = &references[before - 1];
+ if offset < reference.at.span.end
+ || (reference.alias_location.span.start <= offset
+ && offset < reference.alias_location.span.end)
+ {
+ return Ok(Some(Hit::Reference(
+ self.reference_handle(source.references.start + before - 1),
+ )));
+ }
+ }
+ Ok(None)
+ }
+}
+
+pub struct Definitions<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl Iterator for Definitions<'_, '_> {
+ type Item = DefinitionHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let definition = &self.index.definitions[number];
+ if self
+ .domain
+ .contains(self.index.files[definition.declaration.file].membership)
+ {
+ return Some(self.index.definition_handle(number));
+ }
+ }
+ None
+ }
+}
+
+pub struct References<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl References<'_, '_> {
+ #[cfg_attr(
+ not(test),
+ expect(
+ dead_code,
+ reason = "Tested primitive scope; editor displays adapter results scope."
+ )
+ )]
+ pub fn scope(&self) -> QueryScope {
+ self.index.scope(self.domain)
+ }
+}
+
+impl Iterator for References<'_, '_> {
+ type Item = ReferenceHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let reference = &self.index.references[number];
+ if self
+ .domain
+ .contains(self.index.files[reference.at.file].membership)
+ {
+ return Some(self.index.reference_handle(number));
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const A: Id = Id::from_u128(1);
+ const B: Id = Id::from_u128(2);
+ const C: Id = Id::from_u128(3);
+
+ struct Fixture {
+ path: String,
+ text: String,
+ tokens: Vec,
+ declarations: Vec,
+ }
+
+ impl Fixture {
+ /// Test-only literal role proof. Production never searches declaration
+ /// headers: its caller supplies the coordinated syntax outline roles.
+ fn new(path: &str, text: String, roles: &[(&str, &str, Option)]) -> Self {
+ let tokens = oa_core::language::tokenize(&text);
+ let declarations = roles
+ .iter()
+ .map(|&(kind, alias, id)| {
+ let header = format!("{kind} {alias} ");
+ let start = text.find(&header).expect("authored fixture header");
+ let end = text[start..]
+ .find('}')
+ .map_or(text.len(), |end| start + end + 1);
+ let alias_start = start + kind.len() + 1;
+ DeclarationInput {
+ span: Span { start, end },
+ kind_span: Span {
+ start,
+ end: start + kind.len(),
+ },
+ alias_span: Span {
+ start: alias_start,
+ end: alias_start + alias.len(),
+ },
+ identity: id.map(|id| {
+ let literal = format!("\"{id}\"");
+ let literal_start = start
+ + text[start..end]
+ .find(&literal)
+ .expect("authored fixture UUID");
+ AuthoredIdentity {
+ id,
+ literal_span: Span {
+ start: literal_start,
+ end: literal_start + literal.len(),
+ },
+ }
+ }),
+ }
+ })
+ .collect();
+ Self {
+ path: path.into(),
+ text,
+ tokens,
+ declarations,
+ }
+ }
+
+ fn input(&self, membership: Membership, evidence: Evidence) -> FileInput<'_> {
+ FileInput {
+ path: &self.path,
+ text: &self.text,
+ tokens: &self.tokens,
+ declarations: &self.declarations,
+ syntax: SyntaxCoverage::FullFile,
+ membership,
+ evidence,
+ }
+ }
+
+ fn included(&self) -> FileInput<'_> {
+ self.input(Membership::Included, Evidence::Draft)
+ }
+ }
+
+ fn snapshot(revision: u64) -> SnapshotKey {
+ SnapshotKey {
+ workspace: Id::from_u128(1000),
+ revision,
+ }
+ }
+
+ fn limits() -> Limits {
+ Limits {
+ files: 16,
+ source_bytes: 1024 * 1024,
+ path_bytes: 8192,
+ tokens: 200_000,
+ records: 100_000,
+ }
+ }
+
+ fn index<'a>(files: &[FileInput<'a>]) -> Index<'a> {
+ Index::build(snapshot(1), files, Scope::CompleteProject, limits()).unwrap()
+ }
+
+ fn definition(index: &Index<'_>, alias: &str) -> DefinitionHandle {
+ let mut found = index.definitions_for_alias(alias, Domain::Loaded);
+ let result = found.next().expect("fixture definition");
+ assert!(found.next().is_none(), "fixture expects unique alias");
+ result
+ }
+
+ fn anchor(index: &Index<'_>, handle: DefinitionHandle) -> AuthoredAnchor {
+ match index.identity(handle).unwrap() {
+ Identity::Unique(anchor) => anchor,
+ identity => panic!("fixture expects unique authored identity: {identity:?}"),
+ }
+ }
+
+ #[test]
+ fn repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded() {
+ let fixture = Fixture::new(
+ "project.arcadia",
+ format!(
+ "oa_actor tank \"Same display\" id \"{A}\" {{}}\n\
+ oa_actor tank_extra \"Same display\" id \"{B}\" {{}}\n\
+ oa_interaction flow \"Flow\" id \"{C}\" {{ source @tank; target @tank; other @tank_extra; \
+ description \"@tank @tank_extra\"; historical uuid \"{A}\"; }}\n\
+ // @tank @tank_extra and UUID {A}\n"
+ ),
+ &[
+ ("oa_actor", "tank", Some(A)),
+ ("oa_actor", "tank_extra", Some(B)),
+ ("oa_interaction", "flow", Some(C)),
+ ],
+ );
+ let original = (
+ fixture.text.clone(),
+ fixture.tokens.clone(),
+ fixture.declarations.clone(),
+ );
+ let index = index(&[fixture.included()]);
+ let tank = definition(&index, "tank");
+ let uses: Vec<_> = index
+ .references_to(tank, Domain::Included)
+ .unwrap()
+ .collect();
+ assert_eq!(uses.len(), 2);
+ assert_ne!(uses[0], uses[1]);
+ assert_eq!(
+ index
+ .references_for_alias("tank_extra", Domain::Loaded)
+ .count(),
+ 1
+ );
+ for usage in uses {
+ let record = index.reference(usage).unwrap();
+ assert_eq!(&fixture.text[record.at.span.start..record.at.span.end], "@");
+ assert_eq!(
+ &fixture.text[record.alias_location.span.start..record.alias_location.span.end],
+ "tank"
+ );
+ assert_eq!(
+ index.resolve_reference(usage, Domain::Included),
+ Ok(Lookup::Unique(tank))
+ );
+ assert_eq!(
+ index.hit(0, record.alias_location.span.start),
+ Ok(Some(Hit::Reference(usage)))
+ );
+ }
+ for ignored in [
+ fixture.text.find("Same display").unwrap(),
+ fixture.text.find("// @tank").unwrap() + 3,
+ fixture.text.find("\"@tank @tank_extra\"").unwrap() + 1,
+ fixture.text.find("historical uuid").unwrap() + "historical uuid \"".len(),
+ ] {
+ assert_eq!(index.hit(0, ignored), Ok(None));
+ }
+ assert_eq!(index.hit(0, fixture.text.len()), Ok(None));
+ assert_eq!(
+ original,
+ (
+ fixture.text.clone(),
+ fixture.tokens.clone(),
+ fixture.declarations.clone()
+ )
+ );
+ }
+
+ #[test]
+ fn comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans() {
+ let fixture = Fixture::new(
+ "parts/actors.arcadia",
+ format!(
+ "oa_actor a \"救助 🚒\" id \"{A}\" {{}}\r\nsource @ // 注释 @a\r\n a; target @a;\r\n"
+ ),
+ &[("oa_actor", "a", Some(A))],
+ );
+ let index = index(&[fixture.included()]);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert_eq!(uses.len(), 2);
+ let first = index.reference(uses[0]).unwrap();
+ assert_eq!(
+ index.position(0, first.at.span.start),
+ Ok(Position {
+ line: 1,
+ byte_column: 7
+ })
+ );
+ assert_eq!(
+ index.position(0, first.alias_location.span.start),
+ Ok(Position {
+ line: 2,
+ byte_column: 2
+ })
+ );
+ assert_eq!(
+ index.hit(0, first.at.span.start),
+ Ok(Some(Hit::Reference(uses[0])))
+ );
+ assert_eq!(
+ index.hit(0, first.alias_location.span.start),
+ Ok(Some(Hit::Reference(uses[0])))
+ );
+ for byte in [
+ first.at.span.end,
+ fixture.text.find("注释").unwrap(),
+ fixture.text.find("// 注释 @a").unwrap() + "// 注释 ".len(),
+ first.alias_location.span.end,
+ ] {
+ assert_eq!(index.hit(0, byte), Ok(None));
+ }
+ let unicode_inside = fixture.text.find('救').unwrap() + 1;
+ assert_eq!(
+ index.hit(0, unicode_inside),
+ Err(QueryError::InvalidBytePosition)
+ );
+ assert_eq!(
+ index.position(0, unicode_inside),
+ Err(QueryError::InvalidBytePosition)
+ );
+ assert_eq!(
+ index.hit(0, fixture.text.len() + 1),
+ Err(QueryError::InvalidBytePosition)
+ );
+ assert_eq!(index.hit(1, 0), Err(QueryError::InvalidFile));
+ assert!(index.source(0).unwrap().text.contains("\r\n"));
+ }
+
+ #[test]
+ fn legal_keyword_classified_alias_is_not_discarded() {
+ let fixture = Fixture::new(
+ "project.arcadia",
+ format!(
+ "oa_actor oa_activity \"Participant\" id \"{A}\" {{}}\nsource @oa_activity; source @parent;"
+ ),
+ &[("oa_actor", "oa_activity", Some(A))],
+ );
+ assert!(oa_core::valid_alias("oa_activity"));
+ assert!(!oa_core::valid_alias("parent"));
+ assert_eq!(
+ token_at(&fixture.tokens, fixture.declarations[0].alias_span)
+ .unwrap()
+ .class,
+ TokenClass::Keyword
+ );
+ let index = index(&[fixture.included()]);
+ let found = definition(&index, "oa_activity");
+ let usage = index
+ .references_for_alias("oa_activity", Domain::Included)
+ .next()
+ .unwrap();
+ assert_eq!(
+ index.resolve_reference(usage, Domain::Included),
+ Ok(Lookup::Unique(found))
+ );
+ assert_eq!(
+ index.references_for_alias("parent", Domain::Loaded).count(),
+ 0
+ );
+ assert_eq!(
+ index.hit(0, fixture.declarations[0].alias_span.start),
+ Ok(Some(Hit::Definition(found)))
+ );
+ }
+
+ #[test]
+ fn duplicate_aliases_are_candidates_without_false_reference_attribution() {
+ let first = Fixture::new(
+ "project.arcadia",
+ format!("oa_actor a \"Same display\" id \"{A}\" {{}}\nsource @a;"),
+ &[("oa_actor", "a", Some(A))],
+ );
+ let second = Fixture::new(
+ "parts/other.arcadia",
+ format!("oa_actor a \"Same display\" id \"{B}\" {{}}\nsource @a;"),
+ &[("oa_actor", "a", Some(B))],
+ );
+ let index = index(&[first.included(), second.included()]);
+ let candidates: Vec<_> = index.definitions_for_alias("a", Domain::Included).collect();
+ assert_eq!(candidates.len(), 2);
+ assert_eq!(index.definition(candidates[0]).unwrap().declaration.file, 0);
+ assert_eq!(index.definition(candidates[1]).unwrap().declaration.file, 1);
+ assert_ne!(anchor(&index, candidates[0]), anchor(&index, candidates[1]));
+ assert!(matches!(
+ index.references_to(candidates[0], Domain::Included),
+ Err(QueryError::AmbiguousAlias { count: 2 })
+ ));
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Included).collect();
+ assert_eq!(uses.len(), 2);
+ for usage in uses {
+ assert_eq!(
+ index.resolve_reference(usage, Domain::Included),
+ Ok(Lookup::Ambiguous { count: 2 })
+ );
+ }
+ }
+
+ #[test]
+ fn detached_uuid_clone_disables_anchor_even_for_included_query() {
+ let first = Fixture::new(
+ "project.arcadia",
+ format!("oa_actor a \"First\" id \"{A}\" {{}}"),
+ &[("oa_actor", "a", Some(A))],
+ );
+ let original = index(&[first.included()]);
+ let old_anchor = anchor(&original, definition(&original, "a"));
+ let detached = Fixture::new(
+ "retained.arcadia",
+ format!("oa_actor old \"Retained\" id \"{A}\" {{}}"),
+ &[("oa_actor", "old", Some(A))],
+ );
+ let current = Index::build(
+ snapshot(2),
+ &[
+ first.included(),
+ detached.input(Membership::Detached, Evidence::Draft),
+ ],
+ Scope::CompleteProject,
+ limits(),
+ )
+ .unwrap();
+ assert_eq!(
+ current.identity(definition(¤t, "a")),
+ Ok(Identity::Collision { count: 2 })
+ );
+ assert_eq!(
+ current.identity(definition(¤t, "old")),
+ Ok(Identity::Collision { count: 2 })
+ );
+ assert_eq!(
+ current.resolve_anchor(old_anchor, Domain::Included),
+ Ok(Lookup::Ambiguous { count: 2 })
+ );
+ assert_eq!(
+ current.resolve_anchor(old_anchor, Domain::Loaded),
+ Ok(Lookup::Ambiguous { count: 2 })
+ );
+ assert_eq!(
+ current
+ .definitions_for_alias("old", Domain::Included)
+ .count(),
+ 0
+ );
+ assert_eq!(current.definitions_for_id(A, Domain::Included).count(), 1);
+ assert_eq!(current.definitions_for_id(A, Domain::Loaded).count(), 2);
+ }
+
+ #[test]
+ fn reused_alias_cannot_steal_old_identity_and_old_locations_are_stale() {
+ let first = Fixture::new(
+ "project.arcadia",
+ format!("oa_actor a \"First\" id \"{A}\" {{}}\nsource @a;"),
+ &[("oa_actor", "a", Some(A))],
+ );
+ let original = index(&[first.included()]);
+ let old_definition = definition(&original, "a");
+ let old_reference = original
+ .references_for_alias("a", Domain::Included)
+ .next()
+ .unwrap();
+ let old_anchor = anchor(&original, old_definition);
+ let edited = Fixture::new(
+ "project.arcadia",
+ format!(
+ "oa_actor b \"First renamed\" id \"{A}\" {{}}\noa_actor a \"New actor\" id \"{B}\" {{}}\nsource @a; target @b;"
+ ),
+ &[("oa_actor", "b", Some(A)), ("oa_actor", "a", Some(B))],
+ );
+ let current = Index::build(
+ snapshot(2),
+ &[edited.included()],
+ Scope::CompleteProject,
+ limits(),
+ )
+ .unwrap();
+ assert_eq!(
+ current
+ .definition(old_definition)
+ .map(|definition| definition.alias),
+ Err(QueryError::StaleSnapshot)
+ );
+ assert_eq!(
+ current
+ .reference(old_reference)
+ .map(|reference| reference.alias),
+ Err(QueryError::StaleSnapshot)
+ );
+ assert_eq!(
+ current.resolve_anchor(old_anchor, Domain::Included),
+ Ok(Lookup::Unique(definition(¤t, "b")))
+ );
+ let new_use = current
+ .references_for_alias("a", Domain::Included)
+ .next()
+ .unwrap();
+ assert_eq!(
+ current.resolve_reference(new_use, Domain::Included),
+ Ok(Lookup::Unique(definition(¤t, "a")))
+ );
+ assert_eq!(anchor(¤t, definition(¤t, "a")).id(), B);
+ let other_workspace = Index::build(
+ SnapshotKey {
+ workspace: Id::from_u128(2000),
+ revision: 2,
+ },
+ &[edited.included()],
+ Scope::CompleteProject,
+ limits(),
+ )
+ .unwrap();
+ assert_eq!(
+ other_workspace.resolve_anchor(old_anchor, Domain::Loaded),
+ Err(QueryError::WrongWorkspace)
+ );
+ assert_eq!(
+ other_workspace
+ .definition(definition(¤t, "b"))
+ .map(|definition| definition.alias),
+ Err(QueryError::StaleSnapshot)
+ );
+ let deleted = Fixture::new(
+ "project.arcadia",
+ format!("oa_actor a \"New actor\" id \"{B}\" {{}}"),
+ &[("oa_actor", "a", Some(B))],
+ );
+ let after_delete = Index::build(
+ snapshot(3),
+ &[deleted.included()],
+ Scope::CompleteProject,
+ limits(),
+ )
+ .unwrap();
+ assert_eq!(
+ after_delete.resolve_anchor(old_anchor, Domain::Loaded),
+ Ok(Lookup::Missing)
+ );
+ }
+
+ #[test]
+ fn missing_ids_produce_only_current_snapshot_locations_without_stabilization() {
+ let fixture = Fixture::new(
+ "project.arcadia",
+ format!("oa_actor a \"No UUID\" {{ historical uuid \"{A}\"; }}\r\nsource @a;"),
+ &[("oa_actor", "a", None)],
+ );
+ let original = fixture.text.clone();
+ let index = index(&[fixture.included()]);
+ let declaration = definition(&index, "a");
+ assert_eq!(index.identity(declaration), Ok(Identity::Missing));
+ assert_eq!(index.definition(declaration).unwrap().authored_id, None);
+ let usage = index
+ .references_to(declaration, Domain::Included)
+ .unwrap()
+ .next()
+ .unwrap();
+ assert_eq!(
+ index.resolve_reference(usage, Domain::Included),
+ Ok(Lookup::Unique(declaration))
+ );
+ assert_eq!(fixture.text, original);
+ assert_eq!(
+ index.source(0).unwrap().text.as_bytes(),
+ original.as_bytes()
+ );
+ assert!(!index.source(0).unwrap().text.contains(" id "));
+ assert_eq!(
+ index.hit(0, fixture.text.find(&A.to_string()).unwrap()),
+ Ok(None)
+ );
+ }
+
+ #[test]
+ fn scope_provenance_and_detached_alias_collision_are_explicit() {
+ let included = Fixture::new(
+ "project.arcadia",
+ format!("oa_actor a \"Included\" id \"{A}\" {{}}\nsource @a;"),
+ &[("oa_actor", "a", Some(A))],
+ );
+ let detached = Fixture::new(
+ "old.arcadia",
+ format!("oa_actor a \"Detached\" id \"{B}\" {{}}\nsource @a;"),
+ &[("oa_actor", "a", Some(B))],
+ );
+ let unknown = Fixture::new("new.arcadia", "source @a;".into(), &[]);
+ let index = Index::build(
+ snapshot(1),
+ &[
+ included.input(Membership::Included, Evidence::Validated),
+ detached.input(Membership::Detached, Evidence::Draft),
+ unknown.input(Membership::Unverified, Evidence::Draft),
+ ],
+ Scope::Partial(PartialReason::IncludesUnverified),
+ limits(),
+ )
+ .unwrap();
+ assert_eq!(index.references_for_alias("a", Domain::Included).count(), 1);
+ assert_eq!(index.references_for_alias("a", Domain::Loaded).count(), 3);
+ let scope = index.references_for_alias("a", Domain::Included).scope();
+ assert_eq!(
+ scope.declared,
+ Scope::Partial(PartialReason::IncludesUnverified)
+ );
+ assert!(!scope.project_complete);
+ assert!(scope.all_selected_files_validated);
+ assert_eq!(
+ (
+ scope.selected_files,
+ scope.detached_files,
+ scope.unverified_files
+ ),
+ (1, 1, 1)
+ );
+ assert!(!index.scope(Domain::Loaded).all_selected_files_validated);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert!(matches!(
+ index.resolve_reference(uses[0], Domain::Included),
+ Ok(Lookup::Unique(_))
+ ));
+ assert_eq!(
+ index.resolve_reference(uses[0], Domain::Loaded),
+ Ok(Lookup::Ambiguous { count: 2 })
+ );
+ assert_eq!(
+ index.resolve_reference(uses[1], Domain::Included),
+ Err(QueryError::OutsideDomain)
+ );
+ assert_eq!(
+ index.resolve_reference(uses[2], Domain::Included),
+ Err(QueryError::OutsideDomain)
+ );
+ assert_eq!(index.source(2).unwrap().membership, Membership::Unverified);
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[
+ included.included(),
+ unknown.input(Membership::Unverified, Evidence::Draft)
+ ],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::InconsistentCompleteScope,
+ ..
+ })
+ ));
+ }
+
+ #[test]
+ fn incomplete_draft_is_lexical_and_error_token_does_not_leak_references() {
+ let fixture = Fixture::new(
+ "project.arcadia",
+ "oa_actor a \"Draft\" {}\nsource @a; ? \"unfinished @a".into(),
+ &[("oa_actor", "a", None)],
+ );
+ assert!(
+ fixture
+ .tokens
+ .iter()
+ .any(|token| token.class == TokenClass::Error)
+ );
+ let index = Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::Partial(PartialReason::UnappliedWorkspace),
+ limits(),
+ )
+ .unwrap();
+ let matches = index
+ .references_to(definition(&index, "a"), Domain::Included)
+ .unwrap();
+ assert!(!matches.scope().project_complete);
+ assert!(!matches.scope().all_selected_files_validated);
+ assert_eq!(matches.count(), 1);
+ assert_eq!(index.hit(0, fixture.text.rfind("@a").unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn header_metadata_must_match_current_token_boundaries_and_authored_id() {
+ let mut fixture = Fixture::new(
+ "project.arcadia",
+ format!("oa_actor alpha \"救助\" id \"{A}\" {{}}"),
+ &[("oa_actor", "alpha", Some(A))],
+ );
+ let original = fixture.declarations[0];
+ fixture.declarations[0].alias_span.start += 1;
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::HeaderRoleToken,
+ ..
+ })
+ ));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].identity.as_mut().unwrap().id = B;
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::IdentityLiteral,
+ ..
+ })
+ ));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].alias_span = Span {
+ start: fixture.text.find('救').unwrap() + 1,
+ end: fixture.text.find('救').unwrap() + 3,
+ };
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::DeclarationBounds,
+ ..
+ })
+ ));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].span.end = fixture.text.len() + 1;
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::DeclarationBounds,
+ ..
+ })
+ ));
+ }
+
+ #[test]
+ fn truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial() {
+ let mut fixture = Fixture::new(
+ "project.arcadia",
+ "oa_actor a \"Text\" {}\nsource @a;".into(),
+ &[("oa_actor", "a", None)],
+ );
+ let final_token = fixture.tokens.pop().unwrap();
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::Partial(PartialReason::MissingFiles),
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::TokenCoverage,
+ ..
+ })
+ ));
+ fixture.tokens.push(final_token);
+ fixture.tokens.remove(1);
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::TokenCoverage,
+ ..
+ })
+ ));
+ let mut unicode = Fixture::new("project.arcadia", "// 救助\n".into(), &[]);
+ unicode.tokens[0].span.end = unicode.text.find('救').unwrap() + 1;
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[unicode.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::TokenBounds,
+ ..
+ })
+ ));
+ }
+
+ #[test]
+ fn stopped_outline_is_not_a_successful_empty_query() {
+ let fixture = Fixture::new(
+ "project.arcadia",
+ "oa_actor a \"Text\" {}\nsource @a;".into(),
+ &[("oa_actor", "a", None)],
+ );
+ let mut stopped = fixture.included();
+ stopped.declarations = &[];
+ stopped.syntax = SyntaxCoverage::Incomplete;
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[stopped],
+ Scope::Partial(PartialReason::MissingFiles),
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::IncompleteSyntax,
+ ..
+ })
+ ));
+ }
+
+ #[test]
+ fn nested_declaration_extents_do_not_turn_body_text_into_definition_hits() {
+ let mut fixture = Fixture::new(
+ "project.arcadia",
+ "oa_entity outer \"Outer\" {\n oa_actor inner \"Inner\" {}\n}\nsource @inner;".into(),
+ &[("oa_entity", "outer", None), ("oa_actor", "inner", None)],
+ );
+ // Supply the actual outer whole span; nesting does not change alias order.
+ fixture.declarations[0].span.end = fixture.text.rfind('}').unwrap() + 1;
+ let index = index(&[fixture.included()]);
+ let inner = definition(&index, "inner");
+ assert_eq!(
+ index.hit(0, fixture.declarations[1].alias_span.start),
+ Ok(Some(Hit::Definition(inner)))
+ );
+ assert_eq!(
+ index.hit(0, fixture.text.find("\"Inner\"").unwrap() + 1),
+ Ok(None)
+ );
+ let usage = index
+ .references_for_alias("inner", Domain::Included)
+ .next()
+ .unwrap();
+ assert_eq!(
+ index.resolve_reference(usage, Domain::Included),
+ Ok(Lookup::Unique(inner))
+ );
+ }
+
+ #[test]
+ fn path_identity_rejects_normalization_and_case_collisions() {
+ let mut first = Fixture::new("project.arcadia", "source @a;".into(), &[]);
+ let second = Fixture::new("PROJECT.arcadia", "source @b;".into(), &[]);
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[first.included(), second.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::DuplicateOrCaseCollidingPath,
+ ..
+ })
+ ));
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[first.included(), first.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::DuplicateOrCaseCollidingPath,
+ ..
+ })
+ ));
+ for invalid in [
+ "./project.arcadia",
+ "parts/../project.arcadia",
+ "../project.arcadia",
+ "C:/project.arcadia",
+ "parts\\project.arcadia",
+ ] {
+ first.path = invalid.into();
+ assert!(
+ matches!(
+ Index::build(
+ snapshot(1),
+ &[first.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::NonCanonicalPath,
+ ..
+ })
+ ),
+ "{invalid}"
+ );
+ }
+ }
+
+ #[test]
+ fn unicode_path_collisions_match_core_lowercase_policy() {
+ for (first_path, second_path) in [
+ ("Ä.arcadia", "ä.arcadia"),
+ ("ΟΣ/model.arcadia", "ος/model.arcadia"),
+ ] {
+ let first = Fixture::new(first_path, "source @a;".into(), &[]);
+ let second = Fixture::new(second_path, "source @b;".into(), &[]);
+ assert_eq!(first_path.to_lowercase(), second_path.to_lowercase());
+ assert!(!first_path.eq_ignore_ascii_case(second_path));
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[first.included(), second.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::DuplicateOrCaseCollidingPath,
+ ..
+ })
+ ));
+ }
+ }
+
+ #[test]
+ fn noncanonical_header_identity_cannot_enter_through_query_metadata() {
+ for raw in [
+ "00000000000000000000000000000001",
+ "{00000000-0000-0000-0000-000000000001}",
+ "urn:uuid:00000000-0000-0000-0000-000000000001",
+ ] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new(
+ "project.arcadia",
+ source.clone(),
+ &[("oa_actor", "a", None)],
+ );
+ let start = source.find(&literal).unwrap();
+ // This fixture has one explicit empty body. A brace inside the
+ // quoted UUID must not become the test's declaration end.
+ let body_start = source.find("{}\n").unwrap();
+ fixture.declarations[0].span.end = body_start + 2;
+ assert!(start + literal.len() <= body_start);
+ // An adapter passes None from the corrected outline; lexical
+ // definition/reference navigation survives without an ID anchor.
+ {
+ let admitted = index(&[fixture.included()]);
+ let d = definition(&admitted, "a");
+ assert_eq!(admitted.identity(d), Ok(Identity::Missing));
+ assert_eq!(
+ admitted.references_to(d, Domain::Included).unwrap().count(),
+ 1
+ );
+ assert_eq!(admitted.source(0).unwrap().text, source);
+ }
+ // A forged/stale caller cannot put the UUID crate's permissive
+ // interpretation back into an authored anchor via the raw API.
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: A,
+ literal_span: Span {
+ start,
+ end: start + literal.len(),
+ },
+ });
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::IdentityLiteral,
+ ..
+ })
+ ));
+ }
+ }
+
+ #[test]
+ fn query_header_admission_preserves_uppercase_and_json_escape_bytes() {
+ let expected = Id::from_u128(0xabcdef);
+ let uppercase = expected.to_string().to_uppercase();
+ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new(
+ "project.arcadia",
+ source.clone(),
+ &[("oa_actor", "a", None)],
+ );
+ let start = source.find(&literal).unwrap();
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: expected,
+ literal_span: Span {
+ start,
+ end: start + literal.len(),
+ },
+ });
+ let admitted = index(&[fixture.included()]);
+ assert_eq!(anchor(&admitted, definition(&admitted, "a")).id(), expected);
+ assert_eq!(
+ admitted.source(0).unwrap().text.as_bytes(),
+ source.as_bytes()
+ );
+ }
+ }
+
+ #[test]
+ fn admitted_unicode_path_budget_keeps_original_policy_after_single_fold() {
+ let fixture = Fixture::new("İ.arcadia", String::new(), &[]);
+ assert!(fixture.path.to_lowercase().len() > fixture.path.len());
+ let exact = Limits {
+ files: 1,
+ source_bytes: 0,
+ path_bytes: fixture.path.len(),
+ tokens: 0,
+ records: 0,
+ };
+ let admitted = Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ exact,
+ )
+ .unwrap();
+ assert_eq!(admitted.source(0).unwrap().path, "İ.arcadia");
+ // PathBytes continues to count authored input. One lowercase key's
+ // Unicode expansion is bounded scratch, not a new path rejection rule.
+ }
+
+ #[test]
+ fn every_budget_admits_boundary_and_returns_no_index_one_below() {
+ let fixture = Fixture::new(
+ "project.arcadia",
+ "oa_actor a \"Text\" {}\nsource @a; target @a;".into(),
+ &[("oa_actor", "a", None)],
+ );
+ let exact = Limits {
+ files: 1,
+ source_bytes: fixture.text.len(),
+ path_bytes: fixture.path.len(),
+ tokens: fixture.tokens.len(),
+ records: 3,
+ };
+ assert!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ exact
+ )
+ .is_ok()
+ );
+ for resource in [
+ Resource::Files,
+ Resource::SourceBytes,
+ Resource::PathBytes,
+ Resource::Tokens,
+ Resource::Records,
+ ] {
+ let mut reduced = exact;
+ match resource {
+ Resource::Files => reduced.files -= 1,
+ Resource::SourceBytes => reduced.source_bytes -= 1,
+ Resource::PathBytes => reduced.path_bytes -= 1,
+ Resource::Tokens => reduced.tokens -= 1,
+ Resource::Records => reduced.records -= 1,
+ }
+ assert!(
+ matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, reduced), Err(BuildError::Limit { resource: actual, .. }) if actual == resource)
+ );
+ }
+ // Aggregate bytes/tokens/records, not a per-file budget reset.
+ let second = Fixture::new("other.arcadia", "source @a;".into(), &[]);
+ let mut aggregate = limits();
+ aggregate.records = 3;
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included(), second.included()],
+ Scope::CompleteProject,
+ aggregate
+ ),
+ Err(BuildError::Limit {
+ resource: Resource::Records,
+ observed_at_least: 4,
+ ..
+ })
+ ));
+ let mut maximum = usize::MAX;
+ assert!(matches!(
+ charge(&mut maximum, 1, usize::MAX, Resource::SourceBytes),
+ Err(BuildError::Limit {
+ resource: Resource::SourceBytes,
+ ..
+ })
+ ));
+ assert_eq!(maximum, usize::MAX);
+ }
+
+ #[test]
+ fn declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search() {
+ let mut fixture = Fixture::new(
+ "project.arcadia",
+ "oa_actor a \"First\" {}\noa_actor b \"Second\" {}".into(),
+ &[("oa_actor", "a", None), ("oa_actor", "b", None)],
+ );
+ fixture.declarations.reverse();
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::DeclarationOrder,
+ ..
+ })
+ ));
+ fixture.declarations.reverse();
+ fixture.declarations.insert(1, fixture.declarations[0]);
+ assert!(matches!(
+ Index::build(
+ snapshot(1),
+ &[fixture.included()],
+ Scope::CompleteProject,
+ limits()
+ ),
+ Err(BuildError::Input {
+ problem: InputProblem::DeclarationOrder,
+ ..
+ })
+ ));
+ }
+}
diff --git a/crates/oa-desktop/src/source_workspace.rs b/crates/oa-desktop/src/source_workspace.rs
index 266cacc8..7e267999 100644
--- a/crates/oa-desktop/src/source_workspace.rs
+++ b/crates/oa-desktop/src/source_workspace.rs
@@ -1,7 +1,7 @@
//! Per-file drafts and navigation. Accepted model/source history lives in Document.
use iced::widget::text_editor::Cursor;
use oa_core::{
- language,
+ Id, language,
source_project::{self, CompiledSourceProject, ProjectLimits, SourceBundle, SourceDiagnostic},
};
use std::collections::{BTreeMap, BTreeSet};
@@ -86,6 +86,7 @@ pub struct Buffer {
pub cursor: Cursor,
past: Vec,
future: Vec,
+ generation: u64,
}
impl Buffer {
@@ -99,14 +100,20 @@ impl Buffer {
},
past: Vec::new(),
future: Vec::new(),
+ generation: 0,
}
}
pub fn dirty(&self) -> bool {
self.text != self.baseline
}
+ pub fn generation(&self) -> u64 {
+ self.generation
+ }
}
pub struct Workspace {
+ session: Id,
+ revision: u64,
pub entry: String,
pub active: String,
pub files: BTreeMap,
@@ -117,6 +124,35 @@ pub struct Workspace {
}
impl Workspace {
+ /// Ephemeral editor identity, never a model identity or recovery payload.
+ pub fn session(&self) -> Id {
+ self.session
+ }
+ pub fn revision(&self) -> u64 {
+ self.revision
+ }
+ fn advance(&mut self) -> u64 {
+ if let Some(revision) = self.revision.checked_add(1) {
+ self.revision = revision;
+ } else {
+ self.reset_navigation_session();
+ }
+ self.revision
+ }
+ /// A changed filesystem root must not reuse queued navigation from Save As.
+ pub fn reset_navigation_session(&mut self) {
+ self.session = Id::new_v4();
+ self.revision = 0;
+ for buffer in self.files.values_mut() {
+ buffer.generation = 0;
+ }
+ }
+ pub fn set_reachable(&mut self, reachable: BTreeSet) {
+ if self.reachable != reachable {
+ self.advance();
+ self.reachable = reachable;
+ }
+ }
pub fn recovery_snapshot(&self) -> RecoveryWorkspace {
RecoveryWorkspace {
entry: self.entry.clone(),
@@ -136,6 +172,8 @@ impl Workspace {
pub fn from_recovery(saved: RecoveryWorkspace) -> Result {
saved.validate()?;
Ok(Self {
+ session: Id::new_v4(),
+ revision: 0,
entry: saved.entry,
active: saved.active,
files: saved
@@ -154,6 +192,8 @@ impl Workspace {
}
pub fn new(bundle: &SourceBundle, applied: bool) -> Self {
Self {
+ session: Id::new_v4(),
+ revision: 0,
entry: bundle.entry.clone(),
active: bundle.entry.clone(),
files: bundle
@@ -191,11 +231,20 @@ impl Workspace {
}
}
pub fn edit(&mut self, text: String, cursor: Cursor) {
+ let changed = self.files.get(&self.active).is_none_or(|b| b.text != text);
+ let generation = if changed {
+ self.advance()
+ } else {
+ self.revision
+ };
let buffer = self
.files
.entry(self.active.clone())
.or_insert_with(|| Buffer::new(String::new()));
buffer.cursor = cursor;
+ if changed {
+ buffer.generation = generation;
+ }
if text != buffer.text {
buffer.past.push(std::mem::replace(&mut buffer.text, text));
if buffer.past.len() > 100 {
@@ -268,6 +317,8 @@ impl Workspace {
} else {
buffer.future.push(previous);
}
+ let generation = self.advance();
+ self.files.get_mut(&self.active).unwrap().generation = generation;
self.trim_history(DRAFT_HISTORY_BYTES);
true
}
@@ -281,6 +332,7 @@ impl Workspace {
}
}
pub fn accept(&mut self, bundle: &SourceBundle) {
+ let generation = self.advance();
if self.entry != bundle.entry
&& !bundle.files.contains_key(&self.entry)
&& self.files.get(&self.entry).is_some_and(|b| !b.dirty())
@@ -291,7 +343,13 @@ impl Workspace {
self.reachable = bundle.files.keys().cloned().collect();
for (path, text) in &bundle.files {
let cursor = self.files.get(path).map(|b| b.cursor);
+ let previous_generation = self
+ .files
+ .get(path)
+ .filter(|b| b.text == *text)
+ .map(|b| b.generation);
let mut buffer = Buffer::new(text.clone());
+ buffer.generation = previous_generation.unwrap_or(generation);
if let Some(cursor) = cursor {
buffer.cursor = cursor;
}
@@ -303,10 +361,22 @@ impl Workspace {
self.applied = true;
}
pub fn discovered(&mut self, bundle: &SourceBundle, reachable: BTreeSet) {
+ let changed = self.reachable != reachable
+ || bundle
+ .files
+ .keys()
+ .any(|path| !self.files.contains_key(path));
+ let generation = if changed {
+ self.advance()
+ } else {
+ self.revision
+ };
for (path, text) in &bundle.files {
- self.files
- .entry(path.clone())
- .or_insert_with(|| Buffer::new(text.clone()));
+ self.files.entry(path.clone()).or_insert_with(|| {
+ let mut buffer = Buffer::new(text.clone());
+ buffer.generation = generation;
+ buffer
+ });
}
self.reachable = reachable;
}
@@ -314,6 +384,7 @@ impl Workspace {
if self.reachable.contains(path) || self.files.get(path).is_none_or(Buffer::dirty) {
return false;
}
+ self.advance();
self.files.remove(path);
if self.active == path {
self.active = self.entry.clone();
@@ -376,6 +447,148 @@ pub fn lint(bundle: &SourceBundle) -> Vec {
mod tests {
use super::*;
use oa_core::Project;
+
+ #[test]
+ fn fresh_session_cannot_reuse_a_retained_buffer_generation() {
+ let bundle = SourceBundle {
+ entry: "main.arcadia".into(),
+ files: BTreeMap::from([
+ ("main.arcadia".into(), "first".into()),
+ ("other.arcadia".into(), "other".into()),
+ ]),
+ };
+ let mut workspace = Workspace::new(&bundle, true);
+ let cursor = workspace.files["main.arcadia"].cursor;
+ workspace.edit("second".into(), cursor);
+ workspace.reset_navigation_session();
+ let before = workspace.files["main.arcadia"].generation();
+ workspace.edit("third".into(), cursor);
+ assert_ne!(workspace.files["main.arcadia"].generation(), before);
+ workspace.switch("other.arcadia", cursor);
+ workspace.revision = u64::MAX;
+ workspace.edit("changed other".into(), cursor);
+ let after_wrap = workspace.files["main.arcadia"].generation();
+ workspace.switch("main.arcadia", cursor);
+ workspace.edit("fourth".into(), cursor);
+ assert_ne!(workspace.files["main.arcadia"].generation(), after_wrap);
+ assert!(workspace.undo(false));
+ assert_eq!(workspace.text(), "third");
+ }
+
+ #[test]
+ fn navigation_versions_distinguish_reopened_recovered_and_changed_drafts() {
+ let bundle = SourceBundle {
+ entry: "project.arcadia".into(),
+ files: BTreeMap::from([
+ ("project.arcadia".into(), "entry\r\n".into()),
+ ("part.arcadia".into(), "雨 draft\r\n".into()),
+ ]),
+ };
+ let mut workspace = Workspace::new(&bundle, true);
+ let reopened = Workspace::new(&bundle, true);
+ let recovered = Workspace::from_recovery(workspace.recovery_snapshot()).unwrap();
+ assert_ne!(workspace.session(), reopened.session());
+ assert_ne!(workspace.session(), recovered.session());
+ assert_eq!(workspace.bundle(), recovered.bundle());
+ let saved = serde_json::to_value(workspace.recovery_snapshot()).unwrap();
+ assert!(saved.get("session").is_none() && saved.get("revision").is_none());
+
+ let cursor = Cursor {
+ position: iced::widget::text_editor::Position { line: 0, column: 3 },
+ selection: None,
+ };
+ let revision = workspace.revision();
+ workspace.edit(workspace.text().into(), cursor);
+ assert_eq!(
+ workspace.revision(),
+ revision,
+ "selection is not a text change"
+ );
+ workspace.switch("part.arcadia", cursor);
+ assert_eq!(
+ workspace.revision(),
+ revision,
+ "switching retains the snapshot"
+ );
+ let untouched = workspace.files["project.arcadia"].generation();
+ workspace.edit("雨 unfinished !\r\n".into(), cursor);
+ let edited = workspace.files["part.arcadia"].generation();
+ assert!(workspace.revision() > revision);
+ assert_eq!(workspace.files["project.arcadia"].generation(), untouched);
+ assert!(workspace.undo(false));
+ assert_eq!(workspace.bundle(), bundle);
+ assert!(workspace.files["part.arcadia"].generation() > edited);
+ assert!(workspace.undo(true));
+ assert_eq!(workspace.text(), "雨 unfinished !\r\n");
+ let previous_session = workspace.session();
+ let previous_text = workspace.bundle();
+ workspace.reset_navigation_session();
+ assert_ne!(workspace.session(), previous_session);
+ assert_eq!(workspace.bundle(), previous_text);
+ assert!(
+ workspace.undo(false),
+ "session reset preserves text history"
+ );
+ }
+
+ #[test]
+ fn navigation_scope_changes_invalidate_without_changing_unchanged_positions() {
+ let bundle = SourceBundle {
+ entry: "project.arcadia".into(),
+ files: BTreeMap::from([
+ ("project.arcadia".into(), "entry".into()),
+ ("part.arcadia".into(), "part".into()),
+ ]),
+ };
+ let mut workspace = Workspace::new(&bundle, true);
+ let entry_generation = workspace.files[&bundle.entry].generation();
+ let initial_revision = workspace.revision();
+ workspace.set_reachable(BTreeSet::from([bundle.entry.clone()]));
+ assert!(workspace.revision() > initial_revision);
+ let membership_revision = workspace.revision();
+ workspace.set_reachable(workspace.reachable.clone());
+ assert_eq!(workspace.revision(), membership_revision);
+ assert!(workspace.discard_detached("part.arcadia"));
+ assert!(workspace.revision() > membership_revision);
+ let closed_revision = workspace.revision();
+ workspace.discovered(&bundle, BTreeSet::from([bundle.entry.clone()]));
+ assert!(workspace.revision() > closed_revision);
+ assert_eq!(
+ workspace.files[&bundle.entry].generation(),
+ entry_generation
+ );
+ assert!(workspace.files["part.arcadia"].generation() > closed_revision);
+ let discovered_revision = workspace.revision();
+ workspace.discovered(&bundle, workspace.reachable.clone());
+ assert_eq!(workspace.revision(), discovered_revision);
+ workspace.accept(&bundle);
+ assert!(
+ workspace.revision() > discovered_revision,
+ "validation evidence changed"
+ );
+ assert_eq!(
+ workspace.files[&bundle.entry].generation(),
+ entry_generation
+ );
+ assert_eq!(workspace.bundle(), bundle);
+ }
+
+ #[test]
+ fn navigation_revision_wrap_cannot_reuse_an_old_workspace_key() {
+ let bundle = SourceBundle {
+ entry: "project.arcadia".into(),
+ files: BTreeMap::from([("project.arcadia".into(), "original".into())]),
+ };
+ let mut workspace = Workspace::new(&bundle, true);
+ workspace.revision = u64::MAX;
+ let before = workspace.session();
+ let cursor = workspace.files[&bundle.entry].cursor;
+ workspace.edit("changed".into(), cursor);
+ assert_ne!(workspace.session(), before);
+ assert_eq!(workspace.revision(), 0);
+ assert!(workspace.undo(false));
+ assert_eq!(workspace.bundle(), bundle);
+ }
#[test]
fn draft_history_budget_covers_inactive_files_without_discarding_drafts() {
let bundle = SourceBundle {
diff --git a/crates/oa-desktop/src/tests.rs b/crates/oa-desktop/src/tests.rs
index fe0476fd..ddaef449 100644
--- a/crates/oa-desktop/src/tests.rs
+++ b/crates/oa-desktop/src/tests.rs
@@ -377,7 +377,8 @@ fn source_navigation_waits_for_editor_layout_and_cancels_on_edit() {
send(&mut app, Message::SourceForSelection);
assert_eq!(app.page, Page::Text);
assert!(app.source_jump.is_some());
- send(&mut app, Message::SourceViewport);
+ let ticket = app.source_reveal_ticket();
+ send(&mut app, Message::SourceViewport(ticket));
assert!(app.source_jump.is_none());
send(&mut app, Message::SourceForSelection);
assert!(app.source_jump.is_some());
@@ -409,7 +410,8 @@ fn source_find_navigates_an_invalid_draft_without_applying_or_losing_it() {
cursor.selection,
Some(text_editor::Position { line: 0, column: 9 })
);
- send(&mut app, Message::SourceViewport);
+ let ticket = app.source_reveal_ticket();
+ send(&mut app, Message::SourceViewport(ticket));
assert_eq!(app.source_editor.cursor(), cursor);
send(
&mut app,
diff --git a/crates/oa-desktop/src/views.rs b/crates/oa-desktop/src/views.rs
index 54608744..1a74bc65 100644
--- a/crates/oa-desktop/src/views.rs
+++ b/crates/oa-desktop/src/views.rs
@@ -1,3 +1,4 @@
+mod source_symbols;
pub(crate) mod traceability;
use crate::{
app::{App, Choice, FileOperation, Message, Page, Pending},
@@ -1113,8 +1114,12 @@ fn source(app: &App) -> View<'_, Message> {
}
}
let editor = widget::text_editor(&app.source_editor)
- .id("source-editor")
+ .id(app.source_editor_id())
.on_action(Message::SourceEdit)
+ .key_binding({
+ let context = app.source_symbol_context();
+ move |press| source_binding(press, &context)
+ })
.font(Font::MONOSPACE)
.size(14)
.padding(16)
@@ -1123,8 +1128,7 @@ fn source(app: &App) -> View<'_, Message> {
app.source_snapshot.clone(),
crate::highlight::format,
);
- column![
- container(
+ let header = container(
column![
row![
container(heading(
@@ -1167,17 +1171,83 @@ fn source(app: &App) -> View<'_, Message> {
"Undo text affects this file's draft. Model Undo restores accepted files together (100 changes / about 32 MiB)."
}),
navigation,
+ row![
+ action("Definition · F12", app.source_symbol_message(crate::source_navigation::SymbolEvent::Query(crate::source_navigation::query::Action::Definition))),
+ action("References · Shift F12", app.source_symbol_message(crate::source_navigation::SymbolEvent::Query(crate::source_navigation::query::Action::References))),
+ button(text("Back · Alt ←").size(12)).padding([8, 10]).style(style::nav)
+ .on_press_maybe((app.source_symbols.back.iter().any(|entry| entry.session == app.source_workspace.session())).then(|| app.source_symbol_message(crate::source_navigation::SymbolEvent::Back))),
+ ].spacing(7),
scrollable(diagnostics)
.height((visible_diagnostics as f32 * 32.).min(132.))
]
.spacing(12)
)
- .padding(18),
- widget::sensor(editor).key((app.source_workspace.active.clone(),app.source_jump))
- .on_show(|_| Message::SourceViewport).on_resize(|_| Message::SourceViewport)
+ .padding(18);
+ let editor = widget::sensor(editor)
+ .key((
+ app.source_workspace.active.clone(),
+ app.source_reveal_ticket(),
+ ))
+ .on_show({
+ let ticket = app.source_reveal_ticket();
+ move |_| Message::SourceViewport(ticket)
+ })
+ .on_resize({
+ let ticket = app.source_reveal_ticket();
+ move |_| Message::SourceViewport(ticket)
+ });
+ let workspace: View<'_, Message> = if app.source_symbols.open {
+ row![
+ container(editor).width(Fill).height(Fill),
+ container(scrollable(source_symbols::panel(app)))
+ .width(300)
+ .height(Fill),
+ ]
+ .height(Fill)
+ .into()
+ } else {
+ container(editor).width(Fill).height(Fill).into()
+ };
+ column![
+ container(scrollable(header)).max_height((app.window_size.height * 0.42).max(180.)),
+ workspace,
]
+ .height(Fill)
.into()
}
+fn source_binding(
+ press: widget::text_editor::KeyPress,
+ context: &crate::source_navigation::SymbolContext,
+) -> Option> {
+ use crate::source_navigation::{SymbolEvent, query::Action};
+ use iced::keyboard::{Key, key::Named};
+ use widget::text_editor::{Binding, Status};
+ if matches!(press.status, Status::Focused { .. })
+ && !press.modifiers.control()
+ && !press.modifiers.logo()
+ {
+ let event = match &press.key {
+ Key::Named(Named::F12) if !press.modifiers.alt() => {
+ Some(SymbolEvent::Query(if press.modifiers.shift() {
+ Action::References
+ } else {
+ Action::Definition
+ }))
+ }
+ Key::Named(Named::ArrowLeft) if press.modifiers.alt() && !press.modifiers.shift() => {
+ Some(SymbolEvent::Back)
+ }
+ _ => None,
+ };
+ if let Some(event) = event {
+ return Some(Binding::Custom(Message::SourceSymbols(
+ context.clone(),
+ event,
+ )));
+ }
+ }
+ Binding::from_key_press(press)
+}
fn checks(app: &App) -> View<'_, Message> {
let issues = &app.model_issues;
let mut out = column![heading(
diff --git a/crates/oa-desktop/src/views/source_symbols.rs b/crates/oa-desktop/src/views/source_symbols.rs
new file mode 100644
index 00000000..bdcbe734
--- /dev/null
+++ b/crates/oa-desktop/src/views/source_symbols.rs
@@ -0,0 +1,260 @@
+use crate::{
+ app::{App, Message},
+ source_navigation::{
+ SymbolEvent,
+ query::{self, Declaration, Disposition, Evidence, Identity, Membership, Position},
+ },
+ style,
+};
+use iced::{
+ Element, Fill,
+ widget::{Space, button, column, container, row, scrollable, text, tooltip},
+};
+
+pub(super) fn panel(app: &App) -> Element<'_, Message> {
+ let state = &app.source_symbols;
+ if !state.open
+ || (state.results.is_none() && state.in_flight.is_none() && state.notice.is_none())
+ {
+ return column![].into();
+ }
+ let title = state.results.as_ref().map_or_else(
+ || "Source symbols".to_owned(),
+ |results| {
+ let action = match results.action {
+ query::Action::Definition => "Definitions",
+ query::Action::References => "References",
+ };
+ results.alias.as_ref().map_or_else(
+ || action.to_owned(),
+ |alias| format!("{action} · @{}", preview(alias, 40)),
+ )
+ },
+ );
+ let mut content = column![
+ row![
+ text(title).size(13),
+ Space::new().width(Fill),
+ button(text("Close").size(11))
+ .padding([3, 8])
+ .style(style::nav)
+ .on_press(app.source_symbol_message(SymbolEvent::Close)),
+ ]
+ .align_y(iced::Center)
+ ]
+ .spacing(5);
+
+ if let Some(notice) = &state.notice {
+ content = content.push(super::label(preview(notice, 240)));
+ }
+ if state.in_flight.is_some() {
+ content = content.push(super::label("Finding source symbols…"));
+ } else if let Some(results) = &state.results {
+ let stale = results.stamp.snapshot != app.source_key();
+ if stale {
+ content = content.push(super::label(
+ "Stale results — source changed. Run Definition or References again.",
+ ));
+ }
+ let scope = &results.scope;
+ content = content.push(super::label(format!(
+ "{} · {} · {} included / {} detached / {} unverified files · {} scope issues",
+ if scope.partial() {
+ "Partial scope"
+ } else {
+ "Complete include scope"
+ },
+ if !scope.applied {
+ "Unapplied drafts"
+ } else if scope.validated {
+ "Validated sources"
+ } else {
+ "Draft sources"
+ },
+ scope.included_files,
+ scope.detached_files,
+ scope.unverified_files,
+ scope.issues.len(),
+ )));
+ let explanation = match results.disposition {
+ Disposition::NoSymbol => "Place the caret on a declaration alias or an @reference.",
+ Disposition::NoDefinition => "No matching declaration in the loaded sources.",
+ Disposition::Choices => {
+ "Choose a declaration; draft and detached candidates need an explicit choice."
+ }
+ Disposition::Direct => "Definition in the current sources.",
+ Disposition::LexicalReferences => {
+ "Lexical uses of this alias; these do not establish a semantic target."
+ }
+ };
+ content = content.push(super::label(explanation));
+ if results.total_definitions > 1 {
+ content = content.push(super::label(format!(
+ "Alias collision: {} declarations. Uses are not attributed to a single declaration.",
+ results.total_definitions,
+ )));
+ }
+ let enabled = !stale && !app.busy;
+ let mut rows = column![super::label(counts(
+ "Declarations",
+ results.definitions.len(),
+ results.total_definitions,
+ results.omitted_definitions,
+ ))]
+ .spacing(4);
+ for (index, definition) in results.definitions.iter().enumerate() {
+ rows = rows.push(location_row(
+ declaration_title(&definition.declaration),
+ &definition.path,
+ definition.position,
+ provenance(
+ definition.membership,
+ definition.evidence,
+ definition.declaration.identity,
+ false,
+ ),
+ enabled.then(|| {
+ app.source_symbol_message(SymbolEvent::Choose {
+ stamp: results.stamp,
+ reference: false,
+ index,
+ })
+ }),
+ ));
+ }
+ rows = rows.push(super::label(counts(
+ "Lexical uses",
+ results.references.len(),
+ results.total_references,
+ results.omitted_references,
+ )));
+ for (index, reference) in results.references.iter().enumerate() {
+ let title = reference.context.as_ref().map_or_else(
+ || format!("@{} · lexical use", preview(&reference.alias, 48)),
+ |context| {
+ format!(
+ "@{} · inside {}",
+ preview(&reference.alias, 48),
+ declaration_title(context)
+ )
+ },
+ );
+ rows = rows.push(location_row(
+ title,
+ &reference.path,
+ reference.position,
+ provenance(
+ reference.membership,
+ reference.evidence,
+ reference
+ .context
+ .as_ref()
+ .map_or(Identity::Missing, |context| context.identity),
+ true,
+ ),
+ enabled.then(|| {
+ app.source_symbol_message(SymbolEvent::Choose {
+ stamp: results.stamp,
+ reference: true,
+ index,
+ })
+ }),
+ ));
+ }
+ let row_count = results.definitions.len() + results.references.len();
+ content =
+ content.push(scrollable(rows).height((44.0 + row_count as f32 * 48.0).min(180.0)));
+ }
+ container(content)
+ .padding(8)
+ .width(Fill)
+ .style(style::subtle)
+ .into()
+}
+
+fn counts(label: &str, shown: usize, total: usize, omitted: usize) -> String {
+ format!("{label}: {total} total · {shown} shown · {omitted} omitted")
+}
+
+fn declaration_title(declaration: &Declaration) -> String {
+ let name = declaration
+ .name
+ .as_ref()
+ .map_or_else(String::new, |name| format!(" \"{}\"", preview(name, 48)));
+ format!(
+ "{}{} · @{}",
+ preview(&declaration.kind, 24),
+ name,
+ preview(&declaration.alias, 48)
+ )
+}
+
+fn provenance(
+ membership: Membership,
+ evidence: Evidence,
+ identity: Identity,
+ context: bool,
+) -> String {
+ let membership = match membership {
+ Membership::Included => "Included",
+ Membership::Detached => "Detached",
+ Membership::Unverified => "Unverified",
+ };
+ let evidence = match evidence {
+ Evidence::Validated => "Validated",
+ Evidence::Draft => "Draft",
+ };
+ let collision = match identity {
+ Identity::Collision { count } => format!(
+ " · {}ID collision ({count})",
+ if context { "Context " } else { "" },
+ ),
+ Identity::Missing | Identity::Unique(_) => String::new(),
+ };
+ format!("{membership} · {evidence}{collision}")
+}
+
+fn location_row(
+ title: String,
+ path: &str,
+ position: Position,
+ status: String,
+ event: Option,
+) -> Element<'static, Message> {
+ let line = position.line.saturating_add(1);
+ let column = position.byte_column.saturating_add(1);
+ let location = format!("{}:{line}:{column}", preview(path, 72));
+ tooltip(
+ button(
+ column![
+ text(title).size(12),
+ super::label(format!("{location} · {status}"))
+ ]
+ .spacing(2),
+ )
+ .padding([4, 6])
+ .width(Fill)
+ .style(style::nav)
+ .on_press_maybe(event),
+ text(format!(
+ "{path}:{line}:{column} (1-based line and UTF-8 byte column)"
+ ))
+ .size(11),
+ tooltip::Position::Top,
+ )
+ .into()
+}
+
+/// Keep headings compact without copying the remainder of a long admitted value.
+fn preview(value: &str, limit: usize) -> String {
+ let mut chars = value.chars();
+ let mut result: String = chars
+ .by_ref()
+ .take(limit)
+ .map(|ch| if ch.is_whitespace() { ' ' } else { ch })
+ .collect();
+ if chars.next().is_some() {
+ result.push('…');
+ }
+ result
+}
diff --git a/docs/language.html b/docs/language.html
index 4b7f5d7c..a9d889f5 100644
--- a/docs/language.html
+++ b/docs/language.html
@@ -2633,6 +2633,23 @@ trace links, occurrence identity, comments and file ownership. Each open file
retains its own unapplied draft across file navigation. Located lint diagnostics
refer to the raw draft bytes rather than an identity-stabilized intermediate.
Property editing MUST NOT silently discard a pending source draft.
+In Textual model, place the caret on a declaration alias or an @reference
+and choose Definition (F12 ) or References (Shift+F12 ).
+Back (Alt+Left ) returns to the previous source location in this
+workspace. These shortcuts act while the source editor has focus; the Text
+toolbar also exposes the actions. If the previous draft changed, Back restores
+its current cursor rather than reusing an obsolete selection.
+Symbol navigation reads bounded snapshots of the currently loaded source
+drafts. It does not apply drafts or load missing included files. The results
+identify partial scope, draft evidence, detached or unverified files, alias
+ambiguity and authored-ID collisions. Draft, detached and ambiguous definition
+candidates require an explicit row choice. References are lexical uses of an
+alias, not a list of resolved typed relationships; comments and strings are
+excluded. Declaration and use counts include any rows omitted by the result
+limits. If sources change, stale rows cannot navigate: run the query again.
+Navigation MUST preserve the accepted model and every source draft, and select
+locations in the original UTF-8 bytes. Displayed locations use one-based lines
+and UTF-8 byte columns.
Changing a graphical UUID must atomically update map keys and every reference.
Alias changes preserve UUID identity. Deletion must account for owned elements,
incident relationships, ordered occurrences, and diagram placements without
diff --git a/docs/language.rst b/docs/language.rst
index f195aceb..9ea3a7f6 100644
--- a/docs/language.rst
+++ b/docs/language.rst
@@ -2054,6 +2054,25 @@ retains its own unapplied draft across file navigation. Located lint diagnostics
refer to the raw draft bytes rather than an identity-stabilized intermediate.
Property editing MUST NOT silently discard a pending source draft.
+In Textual model, place the caret on a declaration alias or an ``@reference``
+and choose **Definition** (``F12``) or **References** (``Shift+F12``).
+**Back** (``Alt+Left``) returns to the previous source location in this
+workspace. These shortcuts act while the source editor has focus; the Text
+toolbar also exposes the actions. If the previous draft changed, Back restores
+its current cursor rather than reusing an obsolete selection.
+
+Symbol navigation reads bounded snapshots of the currently loaded source
+drafts. It does not apply drafts or load missing included files. The results
+identify partial scope, draft evidence, detached or unverified files, alias
+ambiguity and authored-ID collisions. Draft, detached and ambiguous definition
+candidates require an explicit row choice. References are lexical uses of an
+alias, not a list of resolved typed relationships; comments and strings are
+excluded. Declaration and use counts include any rows omitted by the result
+limits. If sources change, stale rows cannot navigate: run the query again.
+Navigation MUST preserve the accepted model and every source draft, and select
+locations in the original UTF-8 bytes. Displayed locations use one-based lines
+and UTF-8 byte columns.
+
Changing a graphical UUID must atomically update map keys and every reference.
Alias changes preserve UUID identity. Deletion must account for owned elements,
incident relationships, ordered occurrences, and diagram placements without
diff --git a/harness/experiments/text-navigation-components-20260911/.gitattributes b/harness/experiments/text-navigation-components-20260911/.gitattributes
new file mode 100644
index 00000000..fa1385d9
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/.gitattributes
@@ -0,0 +1 @@
+* -text
diff --git a/harness/experiments/text-navigation-components-20260911/README.md b/harness/experiments/text-navigation-components-20260911/README.md
new file mode 100644
index 00000000..b853e3b3
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/README.md
@@ -0,0 +1,29 @@
+# Text navigation component evidence — 2026-09-11
+
+This archive preserves component execution and native **input preparation** from Codex-9e8r6t `/root/linux_readiness`. It does not establish mounted editor behavior, native acceptance, desktop integration or navigation performance. Linux remains user-paused. Root owns integrated desktop/native evidence and publication; live coordination is [Gitea #6](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/6).
+
+Authoritative explanations are the [core/query integration report](../../release-text-navigation-outline-integration-20260911.md) and [independent v2 source review](../../release-text-navigation-outline-review-v2-20260911.md). These reports remain in place rather than being duplicated here.
+
+## What passed, and on which inputs
+
+| Evidence | Actual scope |
+| --- | --- |
+| `components/core1*`, `clippy1*` | Windows core: 445 passed, 0 failed, 1 ignored; strict all-target Clippy passed. Four newly added Unicode fixture strings still had the preparation mojibake defect during these runs. |
+| `components/outline2*`, `clippy-tests2*` | Corrected explicit-UTF-8 fixtures: 12 focused tests and applicable core lib/tests Clippy passed. Full core was not rerun; production `language.rs` was byte-unchanged. |
+| `components/query-compile3*`, `query-tests3*` | Corrected private query: direct rustc compilation with warnings denied and 20 tests passed. Exact Cargo artifact/fingerprint binding is preserved. This is not the later production App/adapter gate. |
+| `native-preparation/valid-same*` | Existing pinned oracle compared complete model/identity and four exact reachable/disk sources against a separate copy; exit 0. |
+| `native-preparation/release1-same*` | Existing release walkthrough and separate copy: complete model/identity and 30 exact reachable/disk sources; exit 0. Not a 100-module performance trial. |
+| `native-preparation/*-rejected*` | Expected loader failures, exit 101: duplicate declaration and missing include. GUI repair reachability was not tested. |
+
+The original broader source base is `b2c3e54001fc19a05423bcd5808ceea78d13ff59`; later instruction-only HEAD `b70bf1d` does not change that provenance. Executions ran in the shared working tree under the recorded compiler lease, not an isolated checkout.
+
+## Contents and limitations
+
+- `components/` contains every exact member of the prior `evidence-records.zip`, including its internal manifest. The original ZIP SHA256 is `7E17DAA1F718344CC17E5A8090FBC1AB16CF3158634EAEB0452549F4D2549CA5`; no second ZIP copy is retained.
+- `component-inputs/` adds the exact reviewed v2 patches/original private query and final core implementation/corrected tests. These are historical test inputs, not another production source location. Their hashes match the integration report. Reconstructing every historical full-core input still requires the identified repository state plus the manifests; duplicate 322-file input copies and dependency artifacts are deliberately omitted.
+- Failed query candidates, bad-encoding outline tests, E0271 stderr, artifact-binding helper failure, literal verifier corrections and successful replacements remain distinguishable. `query-source-reconstruction.json` is the original incorrect reconstruction claim, superseded by `utf8-reconstruction-correction.json`.
+- `native-preparation/` retains original helpers, logs, manifests, expected Unicode/CRLF byte positions and one baseline of each fixture, including the complete 37-file release walkthrough. Original `valid-work` and `release1-work` copies are omitted. To follow its unchanged historical README, copy `valid-baseline` to a fresh `valid-work` and `release1-baseline` to a fresh `release1-work`; copy invalid fixtures before GUI repair. The original 90-file fixture manifest describes both baseline and work copies at preparation time, not this archive's entry set.
+- Original helper paths identify the execution environment and may refer to `.local` inputs. No runtime executable is included. The existing oracle compares full decoded model values but exposes no full-model JSON export. Its hash and compatible runtime hash are in the receipts.
+- `native-preparation/helper-initial-failure.json` and `readback-print-initial-failure.json` preserve pre-oracle environment-key and read-only stdout-encoding failures; neither modified fixture bytes.
+
+`archive-manifest.json` is the exact payload index with byte counts, SHA256 and origins. `readback.json` records successful entry-set, byte and source-equality checks. The manifest and readback receipt are control files outside the payload count. `.gitattributes` keeps this directory's raw evidence bytes unchanged when staged. Root owns staging and the separate Git-index byte check. No executable, rlib, Cargo cache, duplicate work tree or GUI capture was added here.
diff --git a/harness/experiments/text-navigation-components-20260911/archive-manifest.json b/harness/experiments/text-navigation-components-20260911/archive-manifest.json
new file mode 100644
index 00000000..c25b8f1f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/archive-manifest.json
@@ -0,0 +1,794 @@
+{
+ "scope": "Exact component evidence and native fixture preparation; no desktop/native acceptance",
+ "source_zip_sha256": "7e17daa1f718344cc17e5a8090fbc1ab16cf3158634eaeb0452549f4d2549ca5",
+ "payload_files": 131,
+ "payload_bytes": 934554,
+ "records": [
+ {
+ "path": ".gitattributes",
+ "bytes": 8,
+ "sha256": "705fd4d6451a31d36b3df7de96f83f30ac976c9b4a6d1e51671d8e2f33e2d0da",
+ "origin": "archive documentation/verification control authored for this consolidation"
+ },
+ {
+ "path": "README.md",
+ "bytes": 4855,
+ "sha256": "2b82742dc797aada7e2351e54f4f43da29790846cfd04b4d7fe0329af0049517",
+ "origin": "archive documentation/verification control authored for this consolidation"
+ },
+ {
+ "path": "component-inputs/language.rs",
+ "bytes": 105312,
+ "sha256": "34cbb144a111e6f0ddadfca7d40636a8e3323fe65ea8920fd2402f0352b41f57",
+ "origin": "crates/oa-core/src/language.rs"
+ },
+ {
+ "path": "component-inputs/language_outline_tests.rs",
+ "bytes": 14877,
+ "sha256": "4c79a01e8aea243dd9ef66ce71b9fae263d0e79c319a46c51223679897e8b9df",
+ "origin": "crates/oa-core/src/language_outline_tests.rs"
+ },
+ {
+ "path": "component-inputs/outline-seam-v2.patch",
+ "bytes": 25126,
+ "sha256": "2b37d404fd60bc871554cec2fa4493063f3cc0134d13e7ea90dd1361a394eec1",
+ "origin": ".local/text-navigation-probe-20260911/outline-seam-v2.patch"
+ },
+ {
+ "path": "component-inputs/query-admission-v2.patch",
+ "bytes": 7285,
+ "sha256": "f38b2d5bab3ca6ab66b8bd865f3b01562299fb961c48c8172d39e1d35d90e329",
+ "origin": ".local/text-navigation-probe-20260911/query-admission-v2.patch"
+ },
+ {
+ "path": "component-inputs/source_symbols-baseline.rs",
+ "bytes": 49800,
+ "sha256": "cf82e34544ce508e446787b6335aefe97e146ea5e7cca0a9788deb9097f8f7bb",
+ "origin": ".local/text-navigation-probe-20260911/source_symbols.rs"
+ },
+ {
+ "path": "components/artifacts1-status.json",
+ "bytes": 299,
+ "sha256": "6c162b92780232b5b3bfd24159cfef52d3bb4fb786485572f091ec2383735053",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/artifacts1-status.json"
+ },
+ {
+ "path": "components/artifacts1-stderr.log",
+ "bytes": 61,
+ "sha256": "d8b07b9fdfbce4832604bee2b69736dbb7054a3433d972cb15ad8c8fdc7a65c6",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/artifacts1-stderr.log"
+ },
+ {
+ "path": "components/artifacts1-stdout.log",
+ "bytes": 35777,
+ "sha256": "5b4441c8f4862d0bad438bbc8b8dddd3d4628586283c0add445248b4a12d85be",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/artifacts1-stdout.log"
+ },
+ {
+ "path": "components/clippy-tests2-status.json",
+ "bytes": 299,
+ "sha256": "04910578164f5dce73f8348caccd0590ac9f24150e2b331c554b9c287f2dc191",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/clippy-tests2-status.json"
+ },
+ {
+ "path": "components/clippy-tests2.log",
+ "bytes": 151,
+ "sha256": "78c52948b2dd53aca55947ccd1039fec7e054f0d8e61de56b9bd54960e883f61",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/clippy-tests2.log"
+ },
+ {
+ "path": "components/clippy1-status.json",
+ "bytes": 291,
+ "sha256": "60818516ec45371a8f4766b4936e11d7b53552f1d1e423846d5c02b55a9acea6",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/clippy1-status.json"
+ },
+ {
+ "path": "components/clippy1.log",
+ "bytes": 151,
+ "sha256": "5d3c946198a0870c7245b8ebd8eecef6bff21765f6d98a72221dd1dded3e233e",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/clippy1.log"
+ },
+ {
+ "path": "components/compiler1-status.json",
+ "bytes": 191,
+ "sha256": "865fbfc81fa2e58b5b8d6a9d666c18dfb6515f129a026f9cc5df2db6a94b01d9",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/compiler1-status.json"
+ },
+ {
+ "path": "components/compiler1-stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/compiler1-stderr.log"
+ },
+ {
+ "path": "components/compiler1-stdout.log",
+ "bytes": 197,
+ "sha256": "cdaaa400be47332d69e678836251d4635b5ff2ecd34aa962b69d71f8961d274b",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/compiler1-stdout.log"
+ },
+ {
+ "path": "components/core1-status.json",
+ "bytes": 264,
+ "sha256": "fcd9fae539fa0f82ce0906576971e0b16aec709155363dc1f0a4a201b986edb9",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/core1-status.json"
+ },
+ {
+ "path": "components/core1.log",
+ "bytes": 48639,
+ "sha256": "4556049fbfc4ccecc118cad85afb2b1f92af10bfd787ed416ec2aa4389b7bf24",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/core1.log"
+ },
+ {
+ "path": "components/evidence-manifest.json",
+ "bytes": 8557,
+ "sha256": "cd55f663f126460e3ef857b55a24595349e28771537d94cfd4d36036b6b3f671",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/evidence-manifest.json"
+ },
+ {
+ "path": "components/final-readback.json",
+ "bytes": 1775,
+ "sha256": "29a07e2be5b608d4950b6728398d8f64c0c5c7cb38a014c8dde821ee4b18a390",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/final-readback.json"
+ },
+ {
+ "path": "components/fingerprints/oa-core-781c6973417b169c/lib-oa_core",
+ "bytes": 16,
+ "sha256": "32d7fadb58b89d0877ab923522a0bbb715ac42c34526de01cd3cec0be61df760",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/fingerprints/oa-core-781c6973417b169c/lib-oa_core"
+ },
+ {
+ "path": "components/fingerprints/oa-core-781c6973417b169c/lib-oa_core.json",
+ "bytes": 776,
+ "sha256": "7664e5d9695d989d99873c6937409b188ee00489f6a808051d8fbafdaf826baa",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/fingerprints/oa-core-781c6973417b169c/lib-oa_core.json"
+ },
+ {
+ "path": "components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json",
+ "bytes": 16,
+ "sha256": "9202101a31af6f8d24edceacbb8300c10dddd94fb496cbf6451c8860133ce784",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/fingerprints/serde_json-b48005beba7e4267/lib-serde_json"
+ },
+ {
+ "path": "components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json.json",
+ "bytes": 826,
+ "sha256": "310bedb04f5f35eef3a04e8019f0a204cc0f30b94ca808cb4d702cd7c6403737",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/fingerprints/serde_json-b48005beba7e4267/lib-serde_json.json"
+ },
+ {
+ "path": "components/inputs-supplement.json",
+ "bytes": 388,
+ "sha256": "480e04d38ec1fda35727cc0b6f2f2837964484c90cffc0199bfe5bcef4236745",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/inputs-supplement.json"
+ },
+ {
+ "path": "components/inputs.json",
+ "bytes": 61752,
+ "sha256": "e0dd3e617c1c365e1d315b498ce032f375b734341d2eb87d6dc89867b636196e",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/inputs.json"
+ },
+ {
+ "path": "components/language_outline_tests-pre-encoding-fix.rs",
+ "bytes": 14922,
+ "sha256": "f64bd89959554cd5842609c031d442f3d9e27b73a216dd936715284c9fe9fb97",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/language_outline_tests-pre-encoding-fix.rs"
+ },
+ {
+ "path": "components/literal-byte-readback.json",
+ "bytes": 1291,
+ "sha256": "0030e88a1a74e2bbf25c1bb3b7dad6dc25ce0477762f8cb71a5034344573d577",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/literal-byte-readback.json"
+ },
+ {
+ "path": "components/outline1-status.json",
+ "bytes": 312,
+ "sha256": "3efbfd96f6b5921fa2079b8732d3c32705528989051629b357fb389b4a20933c",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/outline1-status.json"
+ },
+ {
+ "path": "components/outline1.log",
+ "bytes": 1593,
+ "sha256": "2582a9e5894d4caee7d092e6b0aa15bd02f49261a6beebaaca91d94affae9230",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/outline1.log"
+ },
+ {
+ "path": "components/outline2-inputs.json",
+ "bytes": 399,
+ "sha256": "5eeddbcef8643a75b1582575089405f717bae429c9bf3bf7614a846c712d45ec",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/outline2-inputs.json"
+ },
+ {
+ "path": "components/outline2-status.json",
+ "bytes": 312,
+ "sha256": "434a9410460a7c4bf1dedf948a76015b0b0cc63ae19843b834fd02df44ed6d1c",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/outline2-status.json"
+ },
+ {
+ "path": "components/outline2.log",
+ "bytes": 1593,
+ "sha256": "0e045f4287675661fde873950bce2387584b377ee747b663fa41ec7ffacd1bc7",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/outline2.log"
+ },
+ {
+ "path": "components/query-artifacts3.json",
+ "bytes": 206,
+ "sha256": "25298a1f6b40fd0e60e4bcbda610c81dc851ec37e4d0d58c00099ca1b4128018",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-artifacts3.json"
+ },
+ {
+ "path": "components/query-binding1-failure.json",
+ "bytes": 905,
+ "sha256": "b41e3478707a7b3aeb91ee9fd62ca3c2ee45401aff615fd202ffed7fc13c43b1",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-binding1-failure.json"
+ },
+ {
+ "path": "components/query-bindings.json",
+ "bytes": 6727,
+ "sha256": "158f1c1797abb634f74b2cbc8f09656871d999bfb6aaa9220feacb443bc48889",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-bindings.json"
+ },
+ {
+ "path": "components/query-compile1-status.json",
+ "bytes": 987,
+ "sha256": "a3f9acff4713d9491cbaf7d66180d36e19fa44f8617d6449f9d7f8b4c5a8a383",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile1-status.json"
+ },
+ {
+ "path": "components/query-compile1-stderr.log",
+ "bytes": 1857,
+ "sha256": "82b4baefdbb06c4b744cb750268c02dd0d540c0ae77dbd36359403b03dfbf5d0",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile1-stderr.log"
+ },
+ {
+ "path": "components/query-compile1-stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile1-stdout.log"
+ },
+ {
+ "path": "components/query-compile2-status.json",
+ "bytes": 997,
+ "sha256": "a64df6b29650a5b9a4d990e1c6ef65a22c8b0068b0e2fa151f912116521b7884",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile2-status.json"
+ },
+ {
+ "path": "components/query-compile2-stderr.log",
+ "bytes": 2819,
+ "sha256": "b0206bb2a0be17e6e6c27934566d145425bcf99cb3cc22f7f15143a5733d44fc",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile2-stderr.log"
+ },
+ {
+ "path": "components/query-compile2-stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile2-stdout.log"
+ },
+ {
+ "path": "components/query-compile3-status.json",
+ "bytes": 1009,
+ "sha256": "8bff40eed5dbf499ea41119cc2696fe998a7071ac5ce15fecf447d6a86eb127b",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile3-status.json"
+ },
+ {
+ "path": "components/query-compile3-stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile3-stderr.log"
+ },
+ {
+ "path": "components/query-compile3-stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-compile3-stdout.log"
+ },
+ {
+ "path": "components/query-source-reconstruction.json",
+ "bytes": 673,
+ "sha256": "7692eaf4ed52e0de5f95d1ea26b0b3c3d91b8b89cda7a0c0198394a0403ea03e",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-source-reconstruction.json"
+ },
+ {
+ "path": "components/query-tests3-status.json",
+ "bytes": 332,
+ "sha256": "e2edbb9cdb986ba5215a53830393e2c11f5d8e428a81ebbaede0e06bb395db9d",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-tests3-status.json"
+ },
+ {
+ "path": "components/query-tests3-stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-tests3-stderr.log"
+ },
+ {
+ "path": "components/query-tests3-stdout.log",
+ "bytes": 1785,
+ "sha256": "faec6eb5a3be074a755dfb0ce0bf7b34d2358ff543e0999ee51a27d7a8bcabc9",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-tests3-stdout.log"
+ },
+ {
+ "path": "components/query-type-correction.json",
+ "bytes": 414,
+ "sha256": "eb3675d734354dd8dcb2682b77276d4f2d08790fa98c20c045b0286959ecfcf7",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/query-type-correction.json"
+ },
+ {
+ "path": "components/run-gates.ps1",
+ "bytes": 1231,
+ "sha256": "070c986b1ca9de6c01750597d68478201736093b70bba76812752cafb522e867",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/run-gates.ps1"
+ },
+ {
+ "path": "components/run-query-resume.ps1",
+ "bytes": 5572,
+ "sha256": "07c707cfe4c143af8a64a5ba88de24c866d9a9929f23206f39e21df589c0977b",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/run-query-resume.ps1"
+ },
+ {
+ "path": "components/run-query-typed.ps1",
+ "bytes": 3119,
+ "sha256": "92cde88b43382b6b7557bf341d7168f98e3838d3d2f3bd25e802780d400cb05c",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/run-query-typed.ps1"
+ },
+ {
+ "path": "components/run-query-utf8.ps1",
+ "bytes": 3107,
+ "sha256": "ebb7b16ed431df7df6885a5aff6dbbe55db5b01cc899821e1a4af665ed2d3c18",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/run-query-utf8.ps1"
+ },
+ {
+ "path": "components/run-query.ps1",
+ "bytes": 4240,
+ "sha256": "c112d1a496ed9d39e202f7704548863af45e532dfe89c675bb84b2569201b1ec",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/run-query.ps1"
+ },
+ {
+ "path": "components/run-utf8-outline-gates.ps1",
+ "bytes": 1505,
+ "sha256": "742695aa1b1aadebfb475306fdcbfbb31af5a7f5dd4920e7c5c49df757d89962",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/run-utf8-outline-gates.ps1"
+ },
+ {
+ "path": "components/source_symbols-v2-utf8-typed.rs",
+ "bytes": 54554,
+ "sha256": "fe37cd55b95c892c05aec6c37a113154cab7646dcfc333646fa8218345919751",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/source_symbols-v2-utf8-typed.rs"
+ },
+ {
+ "path": "components/source_symbols-v2-utf8.rs",
+ "bytes": 54542,
+ "sha256": "86666c8390f0c63ec75b045366494a80d0ecde82924f2bac312afd01675aee56",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/source_symbols-v2-utf8.rs"
+ },
+ {
+ "path": "components/source_symbols-v2.rs",
+ "bytes": 54637,
+ "sha256": "8f3f20a7e72c870cfd1ad46f0aa8b8998ffc1ebf3357911957ed4995758e7520",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/source_symbols-v2.rs"
+ },
+ {
+ "path": "components/utf8-reconstruction-correction.json",
+ "bytes": 776,
+ "sha256": "3140aab739ad4351f8538b8169b3fe972b975138cc95c6e12be82a0d5f76c7a3",
+ "origin": ".local/text-navigation-integration-20260911/evidence-records.zip!/utf8-reconstruction-correction.json"
+ },
+ {
+ "path": "native-preparation/README.md",
+ "bytes": 6775,
+ "sha256": "92b5de00ae6aed2c1434bee304b5c348bc154985ba972c3058165b9bd65a8659",
+ "origin": ".local/text-navigation-native-20260911-preparation/README.md"
+ },
+ {
+ "path": "native-preparation/ambiguous-open/links.arcadia",
+ "bytes": 211,
+ "sha256": "d3229ecf6300804103eb7f85bd5891c022f6f6c194dbdbbdcb54e9550338dc3b",
+ "origin": ".local/text-navigation-native-20260911-preparation/ambiguous-open/links.arcadia"
+ },
+ {
+ "path": "native-preparation/ambiguous-open/project.arcadia",
+ "bytes": 277,
+ "sha256": "79e26836c048a35aaf98b21ff006ae8ac931160abd9913b9e67b542303215414",
+ "origin": ".local/text-navigation-native-20260911-preparation/ambiguous-open/project.arcadia"
+ },
+ {
+ "path": "native-preparation/ambiguous-open/救援/assess.arcadia",
+ "bytes": 192,
+ "sha256": "d99593537e67b2dfd293623f467ffbd46af4ea259dffae55d1fe9995c86e6992",
+ "origin": ".local/text-navigation-native-20260911-preparation/ambiguous-open/救援/assess.arcadia"
+ },
+ {
+ "path": "native-preparation/ambiguous-open/救援/confirm.arcadia",
+ "bytes": 186,
+ "sha256": "ba3e222bdc7dfa94e4f1b16df091447835da6b5bea8a72701e12e077e77319a6",
+ "origin": ".local/text-navigation-native-20260911-preparation/ambiguous-open/救援/confirm.arcadia"
+ },
+ {
+ "path": "native-preparation/ambiguous-rejected-result.json",
+ "bytes": 709,
+ "sha256": "ec41f8661711bc85890748f23f7cb40682c35f03de0203ceb22e673a369351f6",
+ "origin": ".local/text-navigation-native-20260911-preparation/ambiguous-rejected-result.json"
+ },
+ {
+ "path": "native-preparation/ambiguous-rejected.stderr.log",
+ "bytes": 527,
+ "sha256": "85bd641d9e3eac3ef5c6a7746906fc498f2b17bc71248b612ee0083f5c3a99d6",
+ "origin": ".local/text-navigation-native-20260911-preparation/ambiguous-rejected.stderr.log"
+ },
+ {
+ "path": "native-preparation/ambiguous-rejected.stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-native-20260911-preparation/ambiguous-rejected.stdout.log"
+ },
+ {
+ "path": "native-preparation/check-summary.json",
+ "bytes": 3221,
+ "sha256": "c92add757cd1e2e80a2c04407ec2f928339167cc8d710e5141d9528a6dddad7a",
+ "origin": ".local/text-navigation-native-20260911-preparation/check-summary.json"
+ },
+ {
+ "path": "native-preparation/check.py",
+ "bytes": 2611,
+ "sha256": "9357d11a16e63ad495fab48ca63f4e7ccf3c81fecf5475e29666a3723477ea53",
+ "origin": ".local/text-navigation-native-20260911-preparation/check.py"
+ },
+ {
+ "path": "native-preparation/expected-locations.json",
+ "bytes": 8654,
+ "sha256": "81574d2c9c0aec1657a293af52a469506ccddfa2914e820c61af619d5622837f",
+ "origin": ".local/text-navigation-native-20260911-preparation/expected-locations.json"
+ },
+ {
+ "path": "native-preparation/fixture-manifest.json",
+ "bytes": 15760,
+ "sha256": "edd4157fc31823f4494dee91862728b87f12ef51c49b8d1fd86a954bc9ecc1ed",
+ "origin": ".local/text-navigation-native-20260911-preparation/fixture-manifest.json"
+ },
+ {
+ "path": "native-preparation/helper-initial-failure.json",
+ "bytes": 211,
+ "sha256": "b76221bc6f9bc6ac9359f1ad82a99f71001abc8e9f7bf6d2776e28599e79f951",
+ "origin": ".local/text-navigation-native-20260911-preparation/helper-initial-failure.json"
+ },
+ {
+ "path": "native-preparation/missing-include-open/links.arcadia",
+ "bytes": 211,
+ "sha256": "d3229ecf6300804103eb7f85bd5891c022f6f6c194dbdbbdcb54e9550338dc3b",
+ "origin": ".local/text-navigation-native-20260911-preparation/missing-include-open/links.arcadia"
+ },
+ {
+ "path": "native-preparation/missing-include-open/project.arcadia",
+ "bytes": 320,
+ "sha256": "573357995f0a68fef39aa3eef3125e2271242d1ce01607c129624399b77e682e",
+ "origin": ".local/text-navigation-native-20260911-preparation/missing-include-open/project.arcadia"
+ },
+ {
+ "path": "native-preparation/missing-include-open/救援/assess.arcadia",
+ "bytes": 192,
+ "sha256": "d99593537e67b2dfd293623f467ffbd46af4ea259dffae55d1fe9995c86e6992",
+ "origin": ".local/text-navigation-native-20260911-preparation/missing-include-open/救援/assess.arcadia"
+ },
+ {
+ "path": "native-preparation/missing-include-open/救援/confirm.arcadia",
+ "bytes": 92,
+ "sha256": "591b32db3b1282825750d002d9293cfc450d55252506623302ce36c949fe9919",
+ "origin": ".local/text-navigation-native-20260911-preparation/missing-include-open/救援/confirm.arcadia"
+ },
+ {
+ "path": "native-preparation/missing-include-rejected-result.json",
+ "bytes": 721,
+ "sha256": "ba00c3411a62c1eb56fe3c095c38a728f51a26b06f74345ea61d7ae013ad1468",
+ "origin": ".local/text-navigation-native-20260911-preparation/missing-include-rejected-result.json"
+ },
+ {
+ "path": "native-preparation/missing-include-rejected.stderr.log",
+ "bytes": 695,
+ "sha256": "88899097199b509e9e2854588f524e532aa2d21521254d993b9955572c9d65c8",
+ "origin": ".local/text-navigation-native-20260911-preparation/missing-include-rejected.stderr.log"
+ },
+ {
+ "path": "native-preparation/missing-include-rejected.stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-native-20260911-preparation/missing-include-rejected.stdout.log"
+ },
+ {
+ "path": "native-preparation/prepare.py",
+ "bytes": 4821,
+ "sha256": "2c291f227f74b8c625b6bee9ac05ea7639cdca25dd0fafcd61b685bbda6d5404",
+ "origin": ".local/text-navigation-native-20260911-preparation/prepare.py"
+ },
+ {
+ "path": "native-preparation/readback-print-initial-failure.json",
+ "bytes": 262,
+ "sha256": "92751765c7a21f7e201bf65f5453691916c8e932bd3f9d431ff24f4c3aaed8bc",
+ "origin": ".local/text-navigation-native-20260911-preparation/readback-print-initial-failure.json"
+ },
+ {
+ "path": "native-preparation/release1-baseline/README.md",
+ "bytes": 11137,
+ "sha256": "113cfb1cf38a98b51bff60442d86a35d636a3d80ef206bece7d53d0aae67f2d5",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/README.md"
+ },
+ {
+ "path": "native-preparation/release1-baseline/evidence/repair-check.md",
+ "bytes": 1419,
+ "sha256": "f4db17bdca4ecbb1bf0caef01b2da5f1f7f798ab3152b648ce6dd8696a6a68e5",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/evidence/repair-check.md"
+ },
+ {
+ "path": "native-preparation/release1-baseline/expected-diagnostics.json",
+ "bytes": 288,
+ "sha256": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/expected-diagnostics.json"
+ },
+ {
+ "path": "native-preparation/release1-baseline/model.json",
+ "bytes": 134520,
+ "sha256": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/model.json"
+ },
+ {
+ "path": "native-preparation/release1-baseline/model.rs",
+ "bytes": 28762,
+ "sha256": "15f30f6bde655808fe754665c26d560e36f2a9bad340f346b09137d0d05045f5",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/model.rs"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/activities/activity_oa_restore_work.arcadia",
+ "bytes": 1307,
+ "sha256": "83e73d0a013d5c3e1a4af0d9139ab0cfa362d25a846671fc2dac59181061a9d4",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/activities/activity_oa_restore_work.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/behavior/process_oa_assessment_procedure.arcadia",
+ "bytes": 697,
+ "sha256": "172c32c044ef4b5bc58a1f4635cc81f48dabb4014479b47dce7b25fedf2dbd0d",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/behavior/process_oa_assessment_procedure.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/behavior/process_oa_field_procedure.arcadia",
+ "bytes": 1781,
+ "sha256": "d2552dc5383e2e91cb302a790364e197f663c2edc013a2ab1978789825c9c324",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/behavior/process_oa_field_procedure.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/behavior/process_oa_response.arcadia",
+ "bytes": 3225,
+ "sha256": "cf86f46fa5c5d70592ef43f1bc1cb4d968efd8add384ba88036bef6b88aa4abc",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/behavior/process_oa_response.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/behavior/scenario_oa_conversation.arcadia",
+ "bytes": 1795,
+ "sha256": "b25be524e98a35ebac5e4481b7609c027b32baf9605e8097f807560f94a77ca6",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/behavior/scenario_oa_conversation.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/behavior/scenario_oa_full_scenario.arcadia",
+ "bytes": 1261,
+ "sha256": "00a1edf2c9707a0eb62ac070c2d02467874f2c9c1f273cc6ddb53d6d65999a1c",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/behavior/scenario_oa_full_scenario.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/behavior/state_machine_oa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "447c9ad1359f4a31ecf975ad07c0b08d8e7e03191fb0858e2fc56f377b223cd5",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/behavior/state_machine_oa_monitor.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "fc8012e5554350b12e1cb80080f8feab80d933c9b33c0f2a7bb500cb7d609936",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/capabilities.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/model.arcadia",
+ "bytes": 661,
+ "sha256": "f7cf914766ea73ec53b85e32d117ea9a9dba52d3e038246863541ae7069b1a15",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/model.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/participants.arcadia",
+ "bytes": 1834,
+ "sha256": "951764c14179f27c10e315acf2ea37d1fff54c77b8441e23addd239e8c715ce3",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/participants.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/oa/types.arcadia",
+ "bytes": 788,
+ "sha256": "9d0396fb82dd5733edd8d1cf52f968ca84906927c704f9cf0944a7fae215cf49",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/oa/types.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/project.arcadia",
+ "bytes": 1958,
+ "sha256": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/project.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/behavior/process_sa_assessment_procedure.arcadia",
+ "bytes": 706,
+ "sha256": "73928ced61c06cbc08728d7956676a50fd4c05aa5b7f1ff4f29ce27a0b0f2088",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/behavior/process_sa_assessment_procedure.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/behavior/process_sa_field_procedure.arcadia",
+ "bytes": 1790,
+ "sha256": "886d462af83110cbfa55dd232a667ea1446761234471357cbb878794e98e8768",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/behavior/process_sa_field_procedure.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/behavior/process_sa_response.arcadia",
+ "bytes": 3234,
+ "sha256": "041785fc22842423822431bd5e6e8d16094ebd0716965e7c765fb8c1d7549e8a",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/behavior/process_sa_response.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/behavior/scenario_sa_conversation.arcadia",
+ "bytes": 2127,
+ "sha256": "97276f5757ee9aa12a0dbd9d3ccd58174b3091162bb8bcce357cec20a45f5d85",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/behavior/scenario_sa_conversation.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/behavior/scenario_sa_full_scenario.arcadia",
+ "bytes": 1256,
+ "sha256": "e12fc406b99d60a2f7768aaa0b25ba560e8605f0b8e37f3a7cdd3b8443562b74",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/behavior/scenario_sa_full_scenario.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/behavior/state_machine_sa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "05ce29af99424c491c47fe2450dc6f5f8ce995511c01ec7e17bb5855be3c9736",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/behavior/state_machine_sa_monitor.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "b6912fb3ece439912345fa71724acd462e716a1e5a88203a1058a5c3b2c20a4f",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/capabilities.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/functions/function_sa_audit_thresholds.arcadia",
+ "bytes": 297,
+ "sha256": "e5a04325d52183956dca814dd90f4e56c96c6b7db0f4f202f203ecb49c3e0f4f",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/functions/function_sa_audit_thresholds.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/functions/function_sa_restore_work.arcadia",
+ "bytes": 2576,
+ "sha256": "fddeb67308051325041fee641fe758ddf43345b7c47b66d433616ad07fdccdd6",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/functions/function_sa_restore_work.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/model.arcadia",
+ "bytes": 661,
+ "sha256": "d431509251acb859fbb98733b6d18add952495e10a78703fbe8f31e40bc083c4",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/model.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/participants.arcadia",
+ "bytes": 4012,
+ "sha256": "780d2eb08b217b67b55febb3d34846f727b8ee9018df6c5741748647e36b2ff8",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/participants.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/sa/types.arcadia",
+ "bytes": 1227,
+ "sha256": "101fc0fbd99f1101514aa7b6da4f8e535aae0bb94c9e2b9e7e6c151135b28660",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/sa/types.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/traceability.arcadia",
+ "bytes": 29506,
+ "sha256": "48a96f543456c9b5a5798fc795594e4119147fe45f11a6f109f141b7f514560f",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/traceability.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/transition-before.json",
+ "bytes": 35801,
+ "sha256": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/transition-before.json"
+ },
+ {
+ "path": "native-preparation/release1-baseline/transition-request.json",
+ "bytes": 4276,
+ "sha256": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/transition-request.json"
+ },
+ {
+ "path": "native-preparation/release1-baseline/views/diagram_oa_allocation_view.arcadia",
+ "bytes": 2176,
+ "sha256": "dfc0eb61e0e2aa5bda45524571fbfa3494f44ce19696f3e15e07438074a07bc7",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/views/diagram_oa_allocation_view.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/views/diagram_sa_allocation_view.arcadia",
+ "bytes": 2173,
+ "sha256": "b78b43a26a82efb8fbd5374b09c329fa341f57b29f635d397b9ef8aede71d389",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/views/diagram_sa_allocation_view.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/views/diagram_sa_capability_view.arcadia",
+ "bytes": 1620,
+ "sha256": "75e8dc9fb30f25d6f8fd3c6b595593ab4f98958a25798f961e336e0fac557649",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/views/diagram_sa_capability_view.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/views/diagram_sa_service_view.arcadia",
+ "bytes": 2209,
+ "sha256": "b08c84f3ea633e7c78dfdad3272f75415f6256cf3614b6d99bfd4f0269076c9b",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/views/diagram_sa_service_view.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-baseline/views/diagram_sa_state_view.arcadia",
+ "bytes": 1931,
+ "sha256": "5f42f4c605e57b817d8400ed21f2b14bfc4e1592aebe941ff113767504c2b869",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-baseline/views/diagram_sa_state_view.arcadia"
+ },
+ {
+ "path": "native-preparation/release1-same-result.json",
+ "bytes": 707,
+ "sha256": "7b2423cd8a860195ebfee1393abf72610219669fef4e80f92308eddba46ef142",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-same-result.json"
+ },
+ {
+ "path": "native-preparation/release1-same.stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-same.stderr.log"
+ },
+ {
+ "path": "native-preparation/release1-same.stdout.log",
+ "bytes": 88,
+ "sha256": "61b58865f8e6253e9fff5bfe7d7a812a3ea455df84edd503323b3741b36dadba",
+ "origin": ".local/text-navigation-native-20260911-preparation/release1-same.stdout.log"
+ },
+ {
+ "path": "native-preparation/valid-baseline/links.arcadia",
+ "bytes": 211,
+ "sha256": "d3229ecf6300804103eb7f85bd5891c022f6f6c194dbdbbdcb54e9550338dc3b",
+ "origin": ".local/text-navigation-native-20260911-preparation/valid-baseline/links.arcadia"
+ },
+ {
+ "path": "native-preparation/valid-baseline/project.arcadia",
+ "bytes": 277,
+ "sha256": "79e26836c048a35aaf98b21ff006ae8ac931160abd9913b9e67b542303215414",
+ "origin": ".local/text-navigation-native-20260911-preparation/valid-baseline/project.arcadia"
+ },
+ {
+ "path": "native-preparation/valid-baseline/救援/assess.arcadia",
+ "bytes": 192,
+ "sha256": "d99593537e67b2dfd293623f467ffbd46af4ea259dffae55d1fe9995c86e6992",
+ "origin": ".local/text-navigation-native-20260911-preparation/valid-baseline/救援/assess.arcadia"
+ },
+ {
+ "path": "native-preparation/valid-baseline/救援/confirm.arcadia",
+ "bytes": 92,
+ "sha256": "591b32db3b1282825750d002d9293cfc450d55252506623302ce36c949fe9919",
+ "origin": ".local/text-navigation-native-20260911-preparation/valid-baseline/救援/confirm.arcadia"
+ },
+ {
+ "path": "native-preparation/valid-same-result.json",
+ "bytes": 701,
+ "sha256": "14316e6dda215780b807abeb4e351438f0ccb321e9dd6eda266da7172ca8ceca",
+ "origin": ".local/text-navigation-native-20260911-preparation/valid-same-result.json"
+ },
+ {
+ "path": "native-preparation/valid-same.stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "origin": ".local/text-navigation-native-20260911-preparation/valid-same.stderr.log"
+ },
+ {
+ "path": "native-preparation/valid-same.stdout.log",
+ "bytes": 86,
+ "sha256": "5ac622cfcc05242dca7c1368aba77293d42ab9ce5e56df7b2aedd2287d66d5e7",
+ "origin": ".local/text-navigation-native-20260911-preparation/valid-same.stdout.log"
+ },
+ {
+ "path": "verify_archive.py",
+ "bytes": 830,
+ "sha256": "357b9bee26014b9f6cbc88cf5b3f196a528c7cb822940560cdfead1c663a8acc",
+ "origin": "archive documentation/verification control authored for this consolidation"
+ }
+ ]
+}
diff --git a/harness/experiments/text-navigation-components-20260911/component-inputs/language.rs b/harness/experiments/text-navigation-components-20260911/component-inputs/language.rs
new file mode 100644
index 00000000..24e3f891
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/component-inputs/language.rs
@@ -0,0 +1,2832 @@
+//! Typed OA/SA language, tolerant source analysis, and explicit V1/V2/V3/V4 migration.
+#[path = "language/semantic_source.rs"]
+mod semantic_source;
+#[path = "migration_v2/source.rs"]
+mod v2_source;
+use crate::*;
+use serde_json::{Map, Value};
+use std::{collections::BTreeMap, fmt};
+/// A located syntax or engineering diagnostic. Positions are one-based and
+/// columns count Unicode scalars.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Diagnostic {
+ pub severity: validation::Severity,
+ pub code: String,
+ pub object_id: Option,
+ pub field: Option,
+ pub line: usize,
+ pub column: usize,
+ pub message: String,
+ pub span: Span,
+}
+
+/// Admission budgets applied before structural validation. Limits are caller
+/// policy, not a restriction on the engineering language's semantics.
+#[derive(Debug, Clone, Copy)]
+pub struct ParseLimits {
+ pub max_source_bytes: usize,
+ pub max_graph_objects: usize,
+ pub max_records: usize,
+ pub max_step_references: usize,
+}
+
+impl ParseLimits {
+ pub const UNLIMITED: Self = Self {
+ max_source_bytes: usize::MAX,
+ max_graph_objects: usize::MAX,
+ max_records: usize::MAX,
+ max_step_references: usize::MAX,
+ };
+
+ pub(crate) fn check_counts(
+ self,
+ graph: usize,
+ records: usize,
+ steps: usize,
+ ) -> Result<(), String> {
+ if graph > self.max_graph_objects
+ || records > self.max_records
+ || steps > self.max_step_references
+ {
+ return Err(format!(
+ "Model exceeds admission limits ({} graph objects, {} total records, {} ordered references).",
+ self.max_graph_objects, self.max_records, self.max_step_references
+ ));
+ }
+ Ok(())
+ }
+
+ pub fn check_model(self, project: &Project) -> Result<(), String> {
+ let graph = project
+ .elements
+ .len()
+ .saturating_add(project.relations.len());
+ let records = graph
+ .saturating_add(1)
+ .saturating_add(project.diagrams.len())
+ .saturating_add(
+ project
+ .diagrams
+ .values()
+ .map(|d| {
+ d.nodes.len().saturating_add(d.routes.len()).saturating_add(
+ d.nodes
+ .values()
+ .map(|n| n.port_anchors.len())
+ .sum::(),
+ )
+ })
+ .sum::(),
+ )
+ .saturating_add(project.artifact_links.len())
+ .saturating_add(project.traces.len());
+ let steps = project
+ .elements
+ .values()
+ .map(|e| e.steps().map_or(0, Vec::len))
+ .sum::()
+ .saturating_add(
+ project
+ .relations
+ .values()
+ .map(|r| match &r.body {
+ RelationBody::ContextualLink { source, target, .. } => source
+ .references
+ .len()
+ .saturating_add(target.references.len()),
+ _ => 0,
+ })
+ .sum::(),
+ );
+ self.check_counts(graph, records, steps)
+ }
+}
+
+/// Half-open UTF-8 byte offsets into the exact analyzed source buffer.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Span {
+ pub start: usize,
+ pub end: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TokenClass {
+ Keyword,
+ Identifier,
+ String,
+ Number,
+ Comment,
+ Punctuation,
+ Whitespace,
+ Error,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct TokenSpan {
+ pub class: TokenClass,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct DeclarationSpan {
+ pub alias: String,
+ pub id: Option,
+ pub kind: String,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Analysis {
+ pub tokens: Vec,
+ pub diagnostics: Vec,
+ pub declarations: Vec,
+}
+
+/// Check the decoded spelling required by declaration-header UUID clauses.
+/// This is the shared 36-byte/hyphen shape rule, not a hexadecimal parser:
+/// callers must additionally parse the UUID. Uppercase hex and JSON escapes
+/// that decode to this spelling remain accepted. URN/braced/compact forms do not.
+pub fn has_uuid_header_shape(decoded: &str) -> bool {
+ decoded.len() == 36
+ && [8, 13, 18, 23]
+ .into_iter()
+ .all(|index| decoded.as_bytes()[index] == b'-')
+}
+
+/// Budgets for one original source buffer. Policy is caller-owned; these
+/// limits do not change the grammar or perform model admission.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OutlineLimits {
+ pub max_source_bytes: usize,
+ pub max_tokens: usize,
+ pub max_declarations: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum OutlineResource {
+ SourceBytes,
+ Tokens,
+ Declarations,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct OutlineLimit {
+ pub resource: OutlineResource,
+ pub limit: usize,
+ pub observed_at_least: usize,
+ /// Original byte offset where admission stopped; never a rewritten offset.
+ pub offset: usize,
+}
+
+/// Roles discovered by the existing tolerant declaration walk. Keeping the
+/// wrapper preserves DeclarationSpan and Analysis as existing public APIs.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct OutlineDeclaration {
+ pub declaration: DeclarationSpan,
+ pub kind_span: Span,
+ pub alias_span: Span,
+ /// Whole quoted header ID token, even if it is not a valid UUID. In that
+ /// case declaration.id is None. Missing/non-string ID values have no span.
+ pub id_literal_span: Option,
+}
+
+/// A full-file tolerant scan, not a successful parse or validated model. Invalid
+/// text remains available. Lexical errors and unmatched braces are facts only:
+/// their absence does not establish syntactic or semantic validity.
+///
+/// A budget failure returns Err and no partial Outline. Include reachability,
+/// project completeness and editor revision are intentionally not inferred here.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Outline<'a> {
+ pub source: &'a str,
+ pub tokens: Vec,
+ pub declarations: Vec,
+ pub has_lexical_errors: bool,
+ pub has_unmatched_braces: bool,
+}
+
+/// Tolerant highlighting tokens cover the whole input, including incomplete
+/// strings and unexpected characters. No model is created or modified.
+pub fn tokenize(source: &str) -> Vec {
+ tokenize_with_limit(source, usize::MAX).expect("unlimited token scan")
+}
+
+fn tokenize_with_limit(source: &str, max_tokens: usize) -> Result, OutlineLimit> {
+ let mut tokens = Vec::new();
+ let mut cursor = 0;
+ while cursor < source.len() {
+ if tokens.len() == max_tokens {
+ return Err(OutlineLimit {
+ resource: OutlineResource::Tokens,
+ limit: max_tokens,
+ observed_at_least: tokens.len().saturating_add(1),
+ offset: cursor,
+ });
+ }
+ let start = cursor;
+ let ch = source[cursor..].chars().next().expect("in bounds");
+ let class = if matches!(ch, ' ' | '\t' | '\r' | '\n') {
+ cursor += 1;
+ while source
+ .as_bytes()
+ .get(cursor)
+ .is_some_and(|next| matches!(*next, b' ' | b'\t' | b'\r' | b'\n'))
+ {
+ cursor += 1;
+ }
+ TokenClass::Whitespace
+ } else if source[cursor..].starts_with("//") {
+ cursor += source[cursor..].find('\n').unwrap_or(source.len() - cursor);
+ TokenClass::Comment
+ } else if ch == '"' {
+ cursor += 1;
+ let mut escaped = false;
+ let mut closed = false;
+ while cursor < source.len() {
+ let next = source[cursor..].chars().next().expect("in bounds");
+ cursor += next.len_utf8();
+ if escaped {
+ escaped = false;
+ } else if next == '\\' {
+ escaped = true;
+ } else if next == '"' {
+ closed = true;
+ break;
+ }
+ }
+ if closed && serde_json::from_str::(&source[start..cursor]).is_ok() {
+ TokenClass::String
+ } else {
+ TokenClass::Error
+ }
+ } else if ch.is_ascii_alphabetic() || ch == '_' {
+ cursor += 1;
+ while source
+ .as_bytes()
+ .get(cursor)
+ .is_some_and(|next| next.is_ascii_alphanumeric() || *next == b'_')
+ {
+ cursor += 1;
+ }
+ if reserved(&source[start..cursor])
+ || matches!(&source[start..cursor], "true" | "false" | "none")
+ {
+ TokenClass::Keyword
+ } else {
+ TokenClass::Identifier
+ }
+ } else if ch.is_ascii_digit() || matches!(ch, '-' | '+' | '.') {
+ cursor += 1;
+ while source.as_bytes().get(cursor).is_some_and(|next| {
+ next.is_ascii_alphanumeric() || matches!(*next, b'.' | b'+' | b'-' | b'_')
+ }) {
+ cursor += 1;
+ }
+ if serde_json::from_str::(&source[start..cursor]).is_ok() {
+ TokenClass::Number
+ } else {
+ TokenClass::Error
+ }
+ } else if "{}();,[]:=@".contains(ch) {
+ cursor += ch.len_utf8();
+ TokenClass::Punctuation
+ } else {
+ cursor += ch.len_utf8();
+ TokenClass::Error
+ };
+ tokens.push(TokenSpan {
+ class,
+ span: Span { start, end: cursor },
+ });
+ }
+ Ok(tokens)
+}
+
+pub fn lint(source: &str) -> Vec {
+ match parse_document(source, ParseLimits::UNLIMITED) {
+ Ok(parsed) => parsed.diagnostics(source),
+ Err(diagnostics) => diagnostics,
+ }
+}
+
+impl fmt::Display for Diagnostic {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}:{}: {}", self.line, self.column, self.message)
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Error {
+ pub(crate) code: &'static str,
+ pub(crate) offset: usize,
+ pub(crate) message: String,
+}
+pub(crate) fn diagnostic(source: &str, error: Error) -> Diagnostic {
+ let start = error.offset.min(source.len());
+ let prefix = &source[..start];
+ Diagnostic {
+ severity: validation::Severity::Error,
+ code: error.code.into(),
+ object_id: None,
+ field: None,
+ line: prefix.bytes().filter(|b| *b == b'\n').count() + 1,
+ column: prefix.rsplit('\n').next().unwrap_or("").chars().count() + 1,
+ message: error.message,
+ span: Span {
+ start,
+ end: start + source[start..].chars().next().map_or(0, char::len_utf8),
+ },
+ }
+}
+fn reserved(word: &str) -> bool {
+ crate::reserved_alias(word)
+ || declaration_kind(word).is_some()
+ || matches!(word, "none" | "node" | "include")
+}
+
+#[derive(Debug, Clone, Copy)]
+enum DeclKind {
+ Element(Kind, Layer),
+ Relation(RelationKind),
+ Diagram,
+ Node,
+ Artifact,
+ Trace,
+}
+fn snake(value: &str) -> String {
+ let mut out = String::new();
+ for (i, c) in value.chars().enumerate() {
+ if c.is_ascii_uppercase() {
+ if i > 0 {
+ out.push('_');
+ }
+ out.push(c.to_ascii_lowercase());
+ } else {
+ out.push(c)
+ }
+ }
+ out
+}
+fn element_word(kind: Kind, layer: Layer) -> String {
+ let stem = match kind {
+ Kind::SystemActor => "actor".into(),
+ Kind::SystemCapability => "capability".into(),
+ Kind::OperationalMission => "mission".into(),
+ _ => snake(serde_json::to_value(kind).unwrap().as_str().unwrap()),
+ };
+ std::format!("{}_{stem}", if layer == Layer::Oa { "oa" } else { "sa" })
+}
+fn relation_word(kind: RelationKind) -> String {
+ snake(serde_json::to_value(kind).unwrap().as_str().unwrap())
+}
+fn declaration_kind(word: &str) -> Option {
+ static KINDS: std::sync::OnceLock> = std::sync::OnceLock::new();
+ KINDS
+ .get_or_init(|| {
+ let mut kinds = BTreeMap::from([
+ ("diagram".into(), DeclKind::Diagram),
+ ("node".into(), DeclKind::Node),
+ ("artifact".into(), DeclKind::Artifact),
+ ("trace".into(), DeclKind::Trace),
+ ]);
+ for layer in [Layer::Oa, Layer::Sa] {
+ for kind in Kind::ALL {
+ if kind.supports(layer) {
+ kinds.insert(element_word(kind, layer), DeclKind::Element(kind, layer));
+ }
+ }
+ }
+ for kind in RelationKind::ALL {
+ kinds.insert(relation_word(kind), DeclKind::Relation(kind));
+ }
+ kinds
+ })
+ .get(word)
+ .copied()
+}
+fn quote(value: &str) -> String {
+ serde_json::to_string(value).expect("string serialization")
+}
+fn reference_field(key: &str) -> bool {
+ matches!(
+ key,
+ "parent"
+ | "steps"
+ | "occurrences"
+ | "branches"
+ | "Named"
+ | "items"
+ | "incoming_items"
+ | "outgoing_items"
+ | "triggers"
+ | "effects"
+ | "interface"
+ | "context"
+ | "represented"
+ | "exchange"
+ | "sequence"
+ | "scenario"
+ | "reply_to"
+ | "referenced"
+ | "local"
+ | "field"
+ | "path"
+ | "references"
+ | "occurrence"
+ | "allocation"
+ | "work"
+ | "supertype"
+ | "sender"
+ | "receiver"
+ | "action"
+ | "lifeline"
+ | "state"
+ | "constrained"
+ | "exchanges"
+ | "hidden_edges"
+ | "Any"
+ | "source"
+ | "target"
+ | "source_port"
+ | "target_port"
+ | "element"
+ | "owner"
+ | "oa"
+ | "sa"
+ | "diagram"
+ | "edge"
+ | "port"
+ | "machine"
+ )
+}
+fn enum_field(key: &str) -> bool {
+ matches!(
+ key,
+ "kind"
+ | "direction"
+ | "orientation"
+ | "contract"
+ | "layer"
+ | "projection"
+ | "relation"
+ | "phase"
+ | "operator"
+ | "definition"
+ | "type_ref"
+ | "viewpoint"
+ | "message_kind"
+ | "transition_kind"
+ | "side"
+ | "mechanism"
+ | "storage"
+ | "transmission"
+ | "acquisition"
+ )
+}
+
+/// Parse V5, or explicitly migrate a valid V1/V2/V3/V4 source candidate. Parsing never
+/// adopts a model and never overwrites an editor draft.
+pub fn parse(source: &str) -> Result> {
+ parse_limited(source, ParseLimits::UNLIMITED)
+}
+
+pub fn parse_limited(source: &str, limits: ParseLimits) -> Result> {
+ parse_with_report_limited(source, limits).map(|decoded| decoded.project)
+}
+pub fn parse_with_report(
+ source: &str,
+) -> Result> {
+ parse_with_report_limited(source, ParseLimits::UNLIMITED)
+}
+pub fn parse_with_report_limited(
+ source: &str,
+ limits: ParseLimits,
+) -> Result> {
+ let parsed = parse_document(source, limits)?;
+ let errors: Vec<_> = parsed
+ .diagnostics(source)
+ .into_iter()
+ .filter(|d| d.severity == validation::Severity::Error)
+ .collect();
+ if errors.is_empty() {
+ Ok(crate::persistence::DecodeResult {
+ project: parsed.project,
+ migration: parsed.migration,
+ })
+ } else {
+ Err(errors)
+ }
+}
+
+struct ParsedDocument {
+ project: Project,
+ migration: Option,
+ locations: BTreeMap,
+ fields: BTreeMap<(Id, String), usize>,
+}
+impl ParsedDocument {
+ fn diagnostics(&self, source: &str) -> Vec {
+ // Semantic analysis can report thousands of warnings in a large draft.
+ // Index line starts once rather than rescanning the entire source prefix
+ // for every issue. Columns still count scalars, never UTF-8 bytes.
+ let line_starts: Vec<_> = std::iter::once(0)
+ .chain(source.match_indices('\n').map(|(offset, _)| offset + 1))
+ .collect();
+ validation::validate(&self.project)
+ .into_iter()
+ .map(|issue| {
+ let offset = issue
+ .element
+ .and_then(|id| {
+ issue
+ .field
+ .as_ref()
+ .and_then(|field| self.fields.get(&(id, field.clone())).copied())
+ .or_else(|| self.locations.get(&id).copied())
+ })
+ .unwrap_or(0);
+ let line = line_starts.partition_point(|start| *start <= offset);
+ Diagnostic {
+ severity: issue.severity,
+ code: issue.code,
+ object_id: issue.element,
+ field: issue.field,
+ line,
+ column: source[line_starts[line - 1]..offset].chars().count() + 1,
+ message: issue.message,
+ span: Span {
+ start: offset,
+ end: offset + source[offset..].chars().next().map_or(0, char::len_utf8),
+ },
+ }
+ })
+ .collect()
+ }
+}
+
+fn parse_document(source: &str, limits: ParseLimits) -> Result> {
+ if source.len() > limits.max_source_bytes {
+ let mut result = diagnostic(
+ source,
+ Error {
+ code: "invalid_source",
+ offset: 0,
+ message: format!("Source exceeds {} bytes.", limits.max_source_bytes),
+ },
+ );
+ result.code = "source_limit".into();
+ return Err(vec![result]);
+ }
+ let tokens = strict_tokens(source).map_err(|e| vec![diagnostic(source, e)])?;
+ if tokens.get(1).is_some_and(|t| t.text(source) == "1") {
+ let old = legacy::parse_limited(source, limits).map_err(|errors| {
+ errors
+ .into_iter()
+ .map(|e| {
+ let start = offset_for(source, e.line, e.column);
+ diagnostic(
+ source,
+ Error {
+ code: e.code,
+ offset: start,
+ message: e.message,
+ },
+ )
+ })
+ .collect::>()
+ })?;
+ let decoded =
+ crate::persistence::migrate_v1_with_report_limited(old, limits).map_err(|error| {
+ vec![diagnostic(
+ source,
+ Error {
+ code: if matches!(error, crate::persistence::FileError::Limit(_)) {
+ "model_limit"
+ } else {
+ "invalid_source"
+ },
+ offset: 0,
+ message: error.to_string(),
+ },
+ )]
+ })?;
+ return Ok(ParsedDocument {
+ project: decoded.project,
+ migration: decoded.migration,
+ locations: BTreeMap::new(),
+ fields: BTreeMap::new(),
+ });
+ }
+ if tokens.get(1).is_some_and(|t| t.text(source) == "2") {
+ return v2_source::parse(source, tokens, limits);
+ }
+ Parser {
+ source,
+ tokens,
+ cursor: 0,
+ value_depth: 0,
+ limits,
+ }
+ .document()
+ .map_err(|e| vec![diagnostic(source, e)])
+}
+fn offset_for(source: &str, line: usize, column: usize) -> usize {
+ let mut offset = 0;
+ for (i, part) in source.split_inclusive('\n').enumerate() {
+ if i + 1 == line {
+ return offset
+ + part
+ .char_indices()
+ .nth(column.saturating_sub(1))
+ .map_or(part.len(), |(n, _)| n);
+ }
+ offset += part.len();
+ }
+ source.len()
+}
+
+/// Canonical output uses persistent aliases and explicit identities. Typed
+/// nested records are ordinary language fields; JSON documents are not embedded.
+pub fn format(project: &Project) -> String {
+ let mut out = std::format!(
+ "arcadia {SCHEMA_VERSION};\nproject {} {} id \"{}\" {{\n description {};\n\n",
+ project.alias,
+ quote(&project.name),
+ project.id,
+ quote(&project.description)
+ );
+ for (_, declaration) in format_declarations(project) {
+ out.push_str(&declaration);
+ }
+ out.push_str("}\n");
+ out
+}
+
+/// The same typed declaration writer is used by flat and modular source output.
+pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
+ let aliases = project
+ .identities()
+ .into_iter()
+ .map(|(id, alias)| (id, alias.to_owned()))
+ .collect();
+ let mut declarations = Vec::new();
+ for e in project.elements.values() {
+ let mut out = String::new();
+ let mut fields = payload(&serde_json::to_value(&e.body).unwrap());
+ fields.insert("description".into(), Value::String(e.description.clone()));
+ fields.insert("parent".into(), serde_json::to_value(e.parent).unwrap());
+ write_declaration(
+ &mut out,
+ &element_word(e.kind(), e.layer()),
+ &e.alias,
+ Some(&e.name),
+ e.id,
+ &fields,
+ &aliases,
+ );
+ declarations.push((e.id, out));
+ }
+ for r in project.relations.values() {
+ let mut out = String::new();
+ let mut fields = payload(&serde_json::to_value(&r.body).unwrap());
+ fields.insert("description".into(), Value::String(r.description.clone()));
+ write_declaration(
+ &mut out,
+ &relation_word(r.kind()),
+ &r.alias,
+ Some(&r.name),
+ r.id,
+ &fields,
+ &aliases,
+ );
+ declarations.push((r.id, out));
+ }
+ for d in project.diagrams.values() {
+ let mut out = String::new();
+ let mut fields = record_fields(d);
+ fields.remove("nodes");
+ write_declaration(
+ &mut out,
+ "diagram",
+ &d.alias,
+ Some(&d.name),
+ d.id,
+ &fields,
+ &aliases,
+ );
+ declarations.push((d.id, out));
+ for n in d.nodes.values() {
+ let mut out = String::new();
+ let mut fields = record_fields(n);
+ fields.insert("diagram".into(), Value::String(d.id.to_string()));
+ write_declaration(&mut out, "node", &n.alias, None, n.id, &fields, &aliases);
+ declarations.push((n.id, out));
+ }
+ }
+ for a in project.artifact_links.values() {
+ let mut out = String::new();
+ write_declaration(
+ &mut out,
+ "artifact",
+ &a.alias,
+ Some(&a.name),
+ a.id,
+ &record_fields(a),
+ &aliases,
+ );
+ declarations.push((a.id, out));
+ }
+ for t in project.traces.values() {
+ let mut out = String::new();
+ write_declaration(
+ &mut out,
+ "trace",
+ &t.alias,
+ Some(&t.name),
+ t.id,
+ &record_fields(t),
+ &aliases,
+ );
+ declarations.push((t.id, out));
+ }
+ declarations
+}
+
+fn record_fields(value: &T) -> Map {
+ let mut fields = serde_json::to_value(value)
+ .unwrap()
+ .as_object()
+ .unwrap()
+ .clone();
+ for field in ["id", "alias", "name"] {
+ fields.remove(field);
+ }
+ fields
+}
+fn payload(value: &Value) -> Map {
+ match value {
+ Value::Object(m)
+ if m.len() == 1
+ && m.keys()
+ .next()
+ .unwrap()
+ .chars()
+ .next()
+ .unwrap()
+ .is_ascii_uppercase() =>
+ {
+ payload(m.values().next().unwrap())
+ }
+ Value::Object(m) => m.clone(),
+ Value::String(_) => Map::new(),
+ _ => unreachable!("typed enum body"),
+ }
+}
+fn replace_payload(value: &mut Value, fields: Map) {
+ match value {
+ Value::Object(m)
+ if m.len() == 1
+ && m.keys()
+ .next()
+ .unwrap()
+ .chars()
+ .next()
+ .unwrap()
+ .is_ascii_uppercase() =>
+ {
+ replace_payload(m.values_mut().next().unwrap(), fields)
+ }
+ Value::Object(m) => *m = fields,
+ Value::String(_) => debug_assert!(fields.is_empty()),
+ _ => unreachable!("typed enum body"),
+ }
+}
+fn write_declaration(
+ out: &mut String,
+ kind: &str,
+ alias: &str,
+ name: Option<&str>,
+ id: Id,
+ fields: &Map,
+ aliases: &BTreeMap,
+) {
+ out.push_str(&std::format!(" {kind} {alias}"));
+ if let Some(name) = name {
+ out.push(' ');
+ out.push_str("e(name));
+ }
+ out.push_str(&std::format!(" id \"{id}\" {{\n"));
+ write_fields(out, fields, 2, aliases);
+ out.push_str(" }\n\n");
+}
+fn write_fields(
+ out: &mut String,
+ fields: &Map,
+ indent: usize,
+ aliases: &BTreeMap,
+) {
+ for (key, value) in fields {
+ out.push_str(&" ".repeat(indent));
+ out.push_str(key);
+ out.push(' ');
+ write_value(out, value, key, indent, aliases);
+ out.push_str(";\n");
+ }
+}
+fn write_value(
+ out: &mut String,
+ value: &Value,
+ key: &str,
+ indent: usize,
+ aliases: &BTreeMap,
+) {
+ if key == "semantic" {
+ semantic_source::write(out, value, indent);
+ return;
+ }
+ match value {
+ Value::Null => out.push_str("none"),
+ Value::Bool(v) => out.push_str(if *v { "true" } else { "false" }),
+ Value::Number(v) => out.push_str(&v.to_string()),
+ Value::String(v) => {
+ if reference_field(key)
+ && let Ok(id) = Id::parse_str(v)
+ && let Some(alias) = aliases.get(&id)
+ {
+ out.push('@');
+ out.push_str(alias);
+ return;
+ }
+ if enum_field(key) && v.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
+ out.push_str(v)
+ } else {
+ out.push_str("e(v))
+ }
+ }
+ Value::Array(values) => {
+ out.push('[');
+ for (i, v) in values.iter().enumerate() {
+ if i > 0 {
+ out.push_str(", ");
+ }
+ write_value(out, v, key, indent, aliases);
+ }
+ out.push(']');
+ }
+ Value::Object(fields) => {
+ out.push_str("{\n");
+ write_fields(out, fields, indent + 1, aliases);
+ out.push_str(&" ".repeat(indent));
+ out.push('}');
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+enum Ast {
+ Literal(Value, usize),
+ Word(String, usize),
+ Reference(String, usize),
+ HistoricalUuid(Id, usize),
+ Array(Vec, usize),
+ Object(BTreeMap, usize),
+}
+impl Ast {
+ fn offset(&self) -> usize {
+ match self {
+ Self::Literal(_, p)
+ | Self::Word(_, p)
+ | Self::Reference(_, p)
+ | Self::HistoricalUuid(_, p)
+ | Self::Array(_, p)
+ | Self::Object(_, p) => *p,
+ }
+ }
+ fn resolve(&self, key: &str, aliases: &BTreeMap) -> Result {
+ match self {
+ Self::HistoricalUuid(_, _) => Err(Error {
+ code: "invalid_source",
+ offset: self.offset(),
+ message:
+ "A uuid literal is allowed only in a typed historical semantic reference slot."
+ .into(),
+ }),
+ Self::Word(word, offset) => {
+ if !enum_field(key) {
+ return Err(Error {
+ code: "invalid_source",
+ offset: *offset,
+ message: std::format!(
+ "Field '{key}' requires a quoted string or a typed value; '{word}' is not an enum field."
+ ),
+ });
+ }
+ Ok(Value::String(word.clone()))
+ }
+ Self::Reference(alias, offset) => {
+ if !reference_field(key) {
+ return Err(Error {
+ code: "invalid_source",
+ offset: *offset,
+ message: std::format!("Field '{key}' does not accept an @alias reference."),
+ });
+ }
+ aliases
+ .get(alias)
+ .map(|id| Value::String(id.to_string()))
+ .ok_or_else(|| Error {
+ code: "invalid_source",
+ offset: *offset,
+ message: std::format!("Unknown reference '@{alias}'."),
+ })
+ }
+ Self::Literal(value, offset) => {
+ if reference_field(key) && value.is_string() {
+ return Err(Error {
+ code: "invalid_source",
+ offset: *offset,
+ message: std::format!(
+ "Field '{key}' requires an @alias reference, not a quoted UUID or enum."
+ ),
+ });
+ }
+ if enum_field(key) && value.is_string() {
+ return Err(Error {
+ code: "invalid_source",
+ offset: *offset,
+ message: std::format!(
+ "Field '{key}' requires an unquoted enum word or typed constructor."
+ ),
+ });
+ }
+ Ok(value.clone())
+ }
+ Self::Array(items, _) => Ok(Value::Array(
+ items
+ .iter()
+ .map(|v| v.resolve(key, aliases))
+ .collect::>()?,
+ )),
+ Self::Object(fields, _) => Ok(Value::Object(
+ fields
+ .iter()
+ .map(|(k, v)| Ok((k.clone(), v.resolve(k, aliases)?)))
+ .collect::>()?,
+ )),
+ }
+ }
+}
+#[derive(Clone, Copy)]
+struct Lexeme {
+ class: TokenClass,
+ span: Span,
+}
+impl Lexeme {
+ fn text<'a>(&self, source: &'a str) -> &'a str {
+ &source[self.span.start..self.span.end]
+ }
+}
+fn strict_tokens(source: &str) -> Result, Error> {
+ let mut out = Vec::new();
+ for t in tokenize(source) {
+ if t.class == TokenClass::Error {
+ return Err(Error {
+ code: "invalid_source",
+ offset: t.span.start,
+ message: if source[t.span.start..].starts_with('"') {
+ "Invalid or unterminated quoted string.".into()
+ } else {
+ std::format!("Invalid token '{}'.", &source[t.span.start..t.span.end])
+ },
+ });
+ }
+ if !matches!(t.class, TokenClass::Whitespace | TokenClass::Comment) {
+ out.push(Lexeme {
+ class: t.class,
+ span: t.span,
+ });
+ }
+ }
+ out.push(Lexeme {
+ class: TokenClass::Punctuation,
+ span: Span {
+ start: source.len(),
+ end: source.len(),
+ },
+ });
+ Ok(out)
+}
+pub(crate) struct Declaration {
+ kind: DeclKind,
+ pub(crate) alias: String,
+ name: String,
+ pub(crate) id: Id,
+ fields: BTreeMap,
+ offset: usize,
+ pub(crate) span: Span,
+ /// The containing element's identity, independent of an authored parent field.
+ pub(crate) lexical_owner: Option,
+ /// Exact body interior, excluding the opening and closing braces.
+ pub(crate) body: Span,
+ pub(crate) id_insertion: Option,
+ pub(crate) field_spans: BTreeMap,
+}
+impl Declaration {
+ pub(crate) fn is_graph_object(&self) -> bool {
+ matches!(self.kind, DeclKind::Element(..) | DeclKind::Relation(..))
+ }
+}
+pub(crate) struct SourceHeader {
+ pub(crate) project: Project,
+ pub(crate) span: Span,
+ pub(crate) id_insertion: Option,
+ pub(crate) description: Option,
+ pub(crate) body: Span,
+}
+pub(crate) struct SourceUnit {
+ pub(crate) header: Option,
+ pub(crate) declarations: Vec,
+ pub(crate) includes: Vec<(String, Span)>,
+}
+pub(crate) fn parse_unit(
+ source: &str,
+ entry: bool,
+ limits: ParseLimits,
+) -> Result> {
+ let tokens = strict_tokens(source).map_err(|e| Box::new(diagnostic(source, e)))?;
+ Parser {
+ source,
+ tokens,
+ cursor: 0,
+ value_depth: 0,
+ limits,
+ }
+ .unit(entry, true)
+ .map_err(|e| Box::new(diagnostic(source, e)))
+}
+
+// Editing positions are derived from the existing, validated parser AST. They
+// do not define another grammar or change the shared V2 migration AST.
+pub(crate) struct EditField {
+ pub(crate) span: Span,
+ pub(crate) value: EditValue,
+}
+pub(crate) struct EditValue {
+ pub(crate) span: Span,
+ pub(crate) kind: EditKind,
+}
+pub(crate) enum EditKind {
+ Atom,
+ Reference(String),
+ HistoricalUuid(Id),
+ Record(BTreeMap),
+ List(Vec),
+}
+pub(crate) struct EditDeclaration {
+ pub(crate) span: Span,
+ pub(crate) lexical_owner: Option,
+ pub(crate) body: Span,
+ pub(crate) header: BTreeMap<&'static str, Span>,
+ pub(crate) fields: EditValue,
+}
+
+pub(crate) fn edit_header(source: &str) -> Result, Box> {
+ let tokens = strict_tokens(source).map_err(|e| Box::new(diagnostic(source, e)))?;
+ let named = tokens
+ .first()
+ .is_none_or(|token| token.text(source) != "node");
+ let roles: &[&str] = if named {
+ &["kind", "alias", "name", "id_keyword", "id"]
+ } else {
+ &["kind", "alias", "id_keyword", "id"]
+ };
+ if tokens.len() <= roles.len() {
+ return Err(Box::new(diagnostic(
+ source,
+ Error {
+ code: "invalid_source",
+ offset: 0,
+ message: "Expected a complete stabilized declaration header.".into(),
+ },
+ )));
+ }
+ Ok(roles
+ .iter()
+ .zip(tokens)
+ .map(|(role, token)| (*role, token.span))
+ .collect())
+}
+
+pub(crate) fn edit_declaration(source: &str) -> Result> {
+ let unit = parse_unit(source, false, ParseLimits::UNLIMITED)?;
+ if unit.declarations.len() != 1 || !unit.includes.is_empty() {
+ return Err(Box::new(diagnostic(
+ source,
+ Error {
+ code: "invalid_source",
+ offset: 0,
+ message: "Expected one declaration for a source edit.".into(),
+ },
+ )));
+ }
+ let mut declarations = edit_positions(source, unit)?;
+ Ok(declarations.pop().expect("one declaration").1)
+}
+
+/// Editing positions for every occurrence in lexical preorder. This is syntax
+/// analysis only: intermediate command edits may have duplicate identities or
+/// parent fields that disagree with their as-yet unmoved lexical owner. Callers
+/// retain occurrence provenance across such edits, then compile the final text.
+pub(crate) fn edit_declarations(
+ source: &str,
+ entry: bool,
+ limits: ParseLimits,
+) -> Result, Box> {
+ let unit = parse_unit(source, entry, limits)?;
+ edit_positions(source, unit)
+}
+
+fn edit_positions(
+ source: &str,
+ unit: SourceUnit,
+) -> Result, Box> {
+ let tokens = strict_tokens(source).map_err(|e| Box::new(diagnostic(source, e)))?;
+ let mut ends: Vec<_> = tokens.iter().map(|token| token.span.end).collect();
+ let mut stack = Vec::new();
+ for (index, token) in tokens.iter().enumerate() {
+ match token.text(source) {
+ "{" | "[" => stack.push(index),
+ "}" | "]" => ends[stack.pop().expect("parser checked delimiters")] = token.span.end,
+ _ => {}
+ }
+ }
+ struct Positions<'a> {
+ tokens: &'a [Lexeme],
+ ends: Vec,
+ }
+ impl Positions<'_> {
+ fn token(&self, start: usize) -> usize {
+ self.tokens
+ .partition_point(|token| token.span.start < start)
+ }
+ fn fields(&self, fields: &BTreeMap) -> BTreeMap {
+ fields
+ .iter()
+ .map(|(key, ast)| {
+ let value = self.value(ast);
+ let start = self.tokens[self.token(value.span.start) - 1].span.start;
+ let end = self.tokens[self.token(value.span.end)].span.end;
+ (
+ key.clone(),
+ EditField {
+ span: Span { start, end },
+ value,
+ },
+ )
+ })
+ .collect()
+ }
+ fn value(&self, ast: &Ast) -> EditValue {
+ let index = self.token(ast.offset());
+ let span = Span {
+ start: ast.offset(),
+ end: match ast {
+ Ast::Reference(..) | Ast::HistoricalUuid(..) => self.tokens[index + 1].span.end,
+ _ => self.ends[index],
+ },
+ };
+ let kind = match ast {
+ Ast::Object(fields, _) => EditKind::Record(self.fields(fields)),
+ Ast::Array(items, _) => {
+ EditKind::List(items.iter().map(|item| self.value(item)).collect())
+ }
+ Ast::Reference(alias, _) => EditKind::Reference(alias.clone()),
+ Ast::HistoricalUuid(id, _) => EditKind::HistoricalUuid(*id),
+ _ => EditKind::Atom,
+ };
+ EditValue { span, kind }
+ }
+ }
+ let positions = Positions {
+ tokens: &tokens,
+ ends,
+ };
+ unit.declarations
+ .into_iter()
+ .map(|declaration| {
+ if declaration.id_insertion.is_some() {
+ return Err(Box::new(diagnostic(
+ source,
+ Error {
+ code: "invalid_source",
+ offset: declaration.span.start,
+ message: "Stabilize declaration identities before editing source.".into(),
+ },
+ )));
+ }
+ let roles: &[&str] = if matches!(declaration.kind, DeclKind::Node) {
+ &["kind", "alias", "id_keyword", "id"]
+ } else {
+ &["kind", "alias", "name", "id_keyword", "id"]
+ };
+ let start = positions.token(declaration.span.start);
+ let header = roles
+ .iter()
+ .zip(&tokens[start..])
+ .map(|(role, token)| (*role, token.span))
+ .collect();
+ Ok((
+ declaration.id,
+ EditDeclaration {
+ span: declaration.span,
+ lexical_owner: declaration.lexical_owner,
+ body: declaration.body,
+ header,
+ fields: EditValue {
+ span: Span {
+ start: declaration.body.start - 1,
+ end: declaration.body.end + 1,
+ },
+ kind: EditKind::Record(positions.fields(&declaration.fields)),
+ },
+ },
+ ))
+ })
+ .collect()
+}
+struct Parser<'a> {
+ source: &'a str,
+ tokens: Vec,
+ cursor: usize,
+ value_depth: usize,
+ limits: ParseLimits,
+}
+impl Parser<'_> {
+ fn current(&self) -> Lexeme {
+ self.tokens[self.cursor.min(self.tokens.len() - 1)]
+ }
+ fn text(&self) -> &str {
+ self.current().text(self.source)
+ }
+ fn error(&self, message: impl Into) -> Error {
+ Error {
+ code: "invalid_source",
+ offset: self.current().span.start,
+ message: message.into(),
+ }
+ }
+ fn take(&mut self, word: &str) -> Result<(), Error> {
+ if self.text() != word {
+ return Err(self.error(std::format!("Expected '{word}'.")));
+ }
+ self.cursor += 1;
+ Ok(())
+ }
+ fn quoted(&mut self) -> Result {
+ if self.current().class != TokenClass::String {
+ return Err(self.error("Expected a double-quoted string."));
+ }
+ let value = serde_json::from_str(self.text()).unwrap();
+ self.cursor += 1;
+ Ok(value)
+ }
+ fn identifier(&mut self) -> Result {
+ let value = self.text().to_owned();
+ if !crate::valid_alias(&value) {
+ return Err(self.error(std::format!("Invalid or reserved alias '{value}'.")));
+ }
+ self.cursor += 1;
+ Ok(value)
+ }
+ fn optional_id(&mut self) -> Result {
+ if self.text() != "id" {
+ return Ok(Id::new_v4());
+ }
+ self.cursor += 1;
+ let offset = self.current().span.start;
+ let value = self.quoted()?;
+ if !has_uuid_header_shape(&value) {
+ return Err(Error {
+ code: "invalid_source",
+ offset,
+ message: "Expected UUID in 8-4-4-4-12 hexadecimal form.".into(),
+ });
+ }
+ Id::parse_str(&value).map_err(|_| Error {
+ code: "invalid_source",
+ offset,
+ message: "Invalid UUID identity.".into(),
+ })
+ }
+ fn fields(&mut self) -> Result, Error> {
+ self.take("{")?;
+ let mut fields = BTreeMap::new();
+ while self.text() != "}" {
+ let offset = self.current().span.start;
+ let (key, value, _) = self.field()?;
+ if fields.insert(key.clone(), value).is_some() {
+ return Err(Error {
+ code: "invalid_source",
+ offset,
+ message: std::format!("Duplicate field '{key}'."),
+ });
+ }
+ }
+ self.take("}")?;
+ Ok(fields)
+ }
+ fn field(&mut self) -> Result<(String, Ast, Span), Error> {
+ let key = self.text().to_owned();
+ if !matches!(
+ self.current().class,
+ TokenClass::Keyword | TokenClass::Identifier
+ ) {
+ return Err(self.error("Expected a field name or '}'."));
+ }
+ self.cursor += 1;
+ let start = self.current().span.start;
+ let value = self.value()?;
+ let end = self.tokens[self.cursor - 1].span.end;
+ self.take(";")?;
+ Ok((key, value, Span { start, end }))
+ }
+
+ fn nested_header_kind(&self) -> Option {
+ let kind = declaration_kind(self.text())?;
+ let alias = self.tokens.get(self.cursor + 1)?;
+ let following = self.tokens.get(self.cursor + 2)?;
+ // A node's ordinary `diagram @view;` field is not a declaration.
+ if !crate::valid_alias(alias.text(self.source)) {
+ return None;
+ }
+ if matches!(kind, DeclKind::Node) {
+ matches!(following.text(self.source), "id" | "{").then_some(kind)
+ } else {
+ (following.class == TokenClass::String).then_some(kind)
+ }
+ }
+ fn value(&mut self) -> Result {
+ if self.value_depth >= 64 {
+ return Err(self.error("Typed values may not nest more than 64 levels."));
+ }
+ self.value_depth += 1;
+ let result = self.value_inner();
+ self.value_depth -= 1;
+ result
+ }
+ fn value_inner(&mut self) -> Result {
+ let token = self.current();
+ let offset = token.span.start;
+ match self.text() {
+ "uuid" => {
+ self.cursor += 1;
+ let value = self.quoted()?;
+ let id = Id::parse_str(&value).map_err(|_| Error {
+ code: "invalid_source",
+ offset,
+ message: "Expected a UUID after uuid.".into(),
+ })?;
+ Ok(Ast::HistoricalUuid(id, offset))
+ }
+ "@" => {
+ self.cursor += 1;
+ Ok(Ast::Reference(self.identifier()?, offset))
+ }
+ "{" => Ok(Ast::Object(self.fields()?, offset)),
+ "[" => {
+ self.cursor += 1;
+ let mut values = Vec::new();
+ if self.text() != "]" {
+ loop {
+ values.push(self.value()?);
+ if self.text() != "," {
+ break;
+ }
+ self.cursor += 1;
+ }
+ }
+ self.take("]")?;
+ Ok(Ast::Array(values, offset))
+ }
+ "none" => {
+ self.cursor += 1;
+ Ok(Ast::Literal(Value::Null, offset))
+ }
+ "true" | "false" => {
+ let value = self.text() == "true";
+ self.cursor += 1;
+ Ok(Ast::Literal(Value::Bool(value), offset))
+ }
+ _ => match token.class {
+ TokenClass::String => Ok(Ast::Literal(Value::String(self.quoted()?), offset)),
+ TokenClass::Number => {
+ let value = serde_json::from_str(self.text())
+ .map_err(|_| self.error("Expected a finite JSON decimal number."))?;
+ self.cursor += 1;
+ Ok(Ast::Literal(value, offset))
+ }
+ TokenClass::Identifier | TokenClass::Keyword => {
+ let value = self.text().to_owned();
+ self.cursor += 1;
+ Ok(Ast::Word(value, offset))
+ }
+ _ => Err(self.error("Expected a value, @alias reference, list, or typed record.")),
+ },
+ }
+ }
+ fn document(&mut self) -> Result {
+ let unit = self.unit(true, false)?;
+ let header = unit.header.expect("entry header");
+ let mut aliases = BTreeMap::from([(header.project.alias.clone(), header.project.id)]);
+ let mut locations = BTreeMap::from([(header.project.id, header.span.start)]);
+ let mut fields = BTreeMap::new();
+ for d in &unit.declarations {
+ if aliases.insert(d.alias.clone(), d.id).is_some() {
+ return Err(Error {
+ code: "invalid_source",
+ offset: d.offset,
+ message: format!("Duplicate alias '{}'.", d.alias),
+ });
+ }
+ if locations.insert(d.id, d.offset).is_some() {
+ return Err(Error {
+ code: "invalid_source",
+ offset: d.offset,
+ message: format!("Duplicate identity '{}'.", d.id),
+ });
+ }
+ fields.extend(
+ d.field_spans
+ .iter()
+ .map(|(key, span)| ((d.id, key.clone()), span.start)),
+ );
+ }
+ let decoded =
+ resolve_declarations(header.project, &unit.declarations, &aliases, self.limits)
+ .map_err(|(_, error)| error)?;
+ Ok(ParsedDocument {
+ project: decoded.project,
+ migration: decoded.migration,
+ locations,
+ fields,
+ })
+ }
+ fn unit(&mut self, entry: bool, includes_allowed: bool) -> Result {
+ let mut header = if entry {
+ self.take("arcadia")?;
+ if !matches!(self.text(), "3" | "4" | "5" | "6" | "7") {
+ return Err(
+ self.error("Unsupported schema version: expected arcadia 3, 4, 5, 6 or 7.")
+ );
+ }
+ let schema_version = self.text().parse().expect("checked source version");
+ self.cursor += 1;
+ self.take(";")?;
+ let start = self.current().span.start;
+ self.take("project")?;
+ let alias = self.identifier()?;
+ let name = self.quoted()?;
+ let id_insertion = (self.text() != "id").then_some(self.current().span.start);
+ let id = self.optional_id()?;
+ let end = self.current().span.start;
+ self.take("{")?;
+ Some(SourceHeader {
+ project: Project {
+ schema_version,
+ id,
+ alias,
+ name,
+ description: String::new(),
+ elements: BTreeMap::new(),
+ relations: BTreeMap::new(),
+ diagrams: BTreeMap::new(),
+ artifact_links: BTreeMap::new(),
+ traces: BTreeMap::new(),
+ },
+ span: Span { start, end },
+ id_insertion,
+ description: None,
+ body: Span {
+ start: end + 1,
+ end: end + 1,
+ },
+ })
+ } else {
+ None
+ };
+ let mut declarations: Vec = Vec::new();
+ let mut includes = Vec::new();
+ let mut graph_count = 0usize;
+ // Declarations are a flat preorder with a stack of open body indices.
+ // Semantic nesting can be deep without recursing through the Rust stack
+ // or conflating declaration depth with the typed-value depth budget.
+ let mut open: Vec = Vec::new();
+ loop {
+ let offset = self.current().span.start;
+ if let Some(&owner) = open.last() {
+ if self.text() == "}" {
+ declarations[owner].body.end = offset;
+ declarations[owner].span.end = self.current().span.end;
+ self.cursor += 1;
+ open.pop();
+ continue;
+ }
+ if self.text().is_empty() {
+ return Err(self.error("Expected a field, element declaration, or '}'."));
+ }
+ if self.text() == "include" {
+ return Err(
+ self.error("Includes must occur at document level, outside declarations.")
+ );
+ }
+ if let Some(kind) = self.nested_header_kind() {
+ if !matches!(declarations[owner].kind, DeclKind::Element(..))
+ || !matches!(kind, DeclKind::Element(..))
+ {
+ return Err(self
+ .error("Only element declarations may nest inside an element body."));
+ }
+ } else {
+ let (key, value, span) = self.field()?;
+ let declaration = &mut declarations[owner];
+ if declaration.fields.insert(key.clone(), value).is_some() {
+ return Err(Error {
+ code: "invalid_source",
+ offset,
+ message: format!("Duplicate field '{key}'."),
+ });
+ }
+ declaration.field_spans.insert(key, span);
+ continue;
+ }
+ } else if (entry && self.text() == "}") || (!entry && self.text().is_empty()) {
+ break;
+ }
+ if self.text() == "include" {
+ if !includes_allowed {
+ return Err(self.error("An include requires a source bundle; single-string parsing never reads files."));
+ }
+ self.cursor += 1;
+ let path = self.quoted()?;
+ self.take(";")?;
+ includes.push((
+ path,
+ Span {
+ start: offset,
+ end: self.tokens[self.cursor - 1].span.end,
+ },
+ ));
+ continue;
+ }
+ if open.is_empty()
+ && self.text() == "description"
+ && let Some(mut entry_header) = header.take()
+ {
+ if entry_header.description.is_some() {
+ return Err(self.error("Duplicate project description."));
+ }
+ self.cursor += 1;
+ let description = self.quoted()?;
+ self.take(";")?;
+ entry_header.project.description = description;
+ entry_header.description = Some(Span {
+ start: offset,
+ end: self.tokens[self.cursor - 1].span.end,
+ });
+ header = Some(entry_header);
+ continue;
+ }
+ let word = self.text().to_owned();
+ let kind = declaration_kind(&word)
+ .ok_or_else(|| self.error(format!("Unknown declaration '{word}'.")))?;
+ graph_count += usize::from(matches!(
+ kind,
+ DeclKind::Element(..) | DeclKind::Relation(..)
+ ));
+ self.limits
+ .check_counts(
+ graph_count,
+ declarations.len().saturating_add(1 + usize::from(entry)),
+ 0,
+ )
+ .map_err(|message| Error {
+ code: "model_limit",
+ offset,
+ message,
+ })?;
+ self.cursor += 1;
+ let alias = self.identifier()?;
+ let name = if matches!(kind, DeclKind::Node) {
+ String::new()
+ } else {
+ self.quoted()?
+ };
+ let id_insertion = (self.text() != "id").then_some(self.current().span.start);
+ let id = self.optional_id()?;
+ let body_start = self.current().span.end;
+ self.take("{")?;
+ let lexical_owner = open.last().map(|&owner| declarations[owner].id);
+ declarations.push(Declaration {
+ kind,
+ alias,
+ name,
+ id,
+ fields: BTreeMap::new(),
+ offset,
+ span: Span {
+ start: offset,
+ end: body_start,
+ },
+ lexical_owner,
+ body: Span {
+ start: body_start,
+ end: body_start,
+ },
+ id_insertion,
+ field_spans: BTreeMap::new(),
+ });
+ open.push(declarations.len() - 1);
+ }
+ if entry {
+ header.as_mut().expect("entry header").body.end = self.current().span.start;
+ self.take("}")?;
+ }
+ if !self.text().is_empty() {
+ return Err(self.error("Unexpected content after the project closing brace."));
+ }
+ Ok(SourceUnit {
+ header,
+ declarations,
+ includes,
+ })
+ }
+}
+
+pub(crate) fn resolve_declarations(
+ mut project: Project,
+ declarations: &[Declaration],
+ aliases: &BTreeMap,
+ limits: ParseLimits,
+) -> Result {
+ let legacy = project.schema_version == 3;
+ let mut legacy_elements = serde_json::Map::new();
+ let mut nodes = Vec::new();
+ for (index, declaration) in declarations.iter().enumerate() {
+ (|| -> Result<(), Error> {
+ let mut fields = declaration
+ .fields
+ .iter()
+ .map(|(k, v)| {
+ let value = if matches!(declaration.kind, DeclKind::Trace) && k == "baseline" {
+ semantic_source::resolve_baseline(v, aliases, project.schema_version)?
+ } else { v.resolve(k, aliases)? };
+ Ok((k.clone(), value))
+ })
+ .collect::, Error>>()?;
+ let d = declaration;
+ let conversion_error = |message: String| Error {
+ code: "invalid_source",
+ offset: d
+ .fields
+ .iter()
+ .find(|(field, _)| message.contains(&std::format!("'{field}'")))
+ .map_or(d.offset, |(_, value)| value.offset()),
+ message,
+ };
+ match d.kind {
+ DeclKind::Element(kind, layer) => {
+ if project.schema_version < 7 { crate::persistence::reject_source7_fields(&Value::Object(fields.clone())).map_err(&conversion_error)?; }
+ if project.schema_version < 6 && !crate::persistence::pre6_element_kind(kind) {
+ return Err(conversion_error(std::format!("{kind:?} requires schema/source 6.")));
+ }
+ if legacy && !crate::persistence::v3::element_allowed(serde_json::to_value(kind).unwrap().as_str().unwrap()) {
+ return Err(conversion_error("Element constructor was not part of schema3.".into()));
+ }
+ let description =
+ take_string(&mut fields, "description", "").map_err(&conversion_error)?;
+ let explicit_parent = d.fields.contains_key("parent");
+ let mut parent =
+ take_optional_id(&mut fields, "parent").map_err(&conversion_error)?;
+ if let Some(owner) = d.lexical_owner {
+ if explicit_parent && parent != Some(owner) {
+ return Err(Error {
+ code: "conflicting_parent",
+ offset: d.fields["parent"].offset(),
+ message: "Explicit parent must equal the containing element; move the declaration to change its lexical parent.".into(),
+ });
+ }
+ parent = Some(owner);
+ }
+ let mut initial = if kind == Kind::Port {
+ if !fields.contains_key("contract") {
+ return Err(conversion_error(
+ "Missing required field 'contract'.".into(),
+ ));
+ }
+ ElementBody::shared_in(
+ layer,
+ SharedBody::Port {
+ contract: PortContract::Legacy,
+ legacy: None,
+ },
+ )
+ } else {
+ ElementBody::new(kind, layer).unwrap()
+ };
+ // Schema-2 sources predate explicit scenario viewpoints.
+ // Preserve their mixed lifelines instead of reinterpreting
+ // old source using a new graphical factory's typed default.
+ if !fields.contains_key("viewpoint")
+ && let Some(SharedBody::Scenario { viewpoint, .. }) = initial.shared_mut()
+ {
+ *viewpoint = ScenarioViewpoint::MixedLegacy;
+ }
+ let mut body = serde_json::to_value(initial).unwrap();
+ let defaults = if legacy {
+ crate::persistence::v3::legacy_defaults(kind)
+ .and_then(|value| value.as_object().cloned())
+ .unwrap_or_else(|| payload(&body))
+ } else { payload(&body) };
+ let body_fields = merge_fields(defaults, fields).map_err(&conversion_error)?;
+ replace_payload(&mut body, body_fields);
+ if legacy {
+ legacy_elements.insert(d.id.to_string(), serde_json::json!({"id":d.id,"alias":d.alias,"name":d.name,"description":description,"parent":parent,"body":body}));
+ return Ok(());
+ }
+ let body: ElementBody = serde_json::from_value(body)
+ .map_err(|e| conversion_error(e.to_string()))?;
+ if body.kind() != kind || body.layer() != layer {
+ return Err(conversion_error(
+ "Declaration kind disagrees with its typed body.".into(),
+ ));
+ }
+ project.elements.insert(
+ d.id,
+ Element {
+ id: d.id,
+ alias: d.alias.clone(),
+ name: d.name.clone(),
+ description,
+ parent,
+ body,
+ },
+ );
+ }
+ DeclKind::Relation(kind) => {
+ if project.schema_version < 7 && kind == RelationKind::ContextualLink { return Err(conversion_error("ContextualLink requires schema/source 7.".into())); }
+ if project.schema_version < 6 && !crate::persistence::pre6_relation_kind(kind) {
+ return Err(conversion_error(std::format!("{kind:?} requires schema/source 6.")));
+ }
+ if legacy && !crate::persistence::v3::relation_allowed(serde_json::to_value(kind).unwrap().as_str().unwrap()) {
+ return Err(conversion_error("Relation constructor was not part of schema3.".into()));
+ }
+ let description =
+ take_string(&mut fields, "description", "").map_err(&conversion_error)?;
+ let required: &[&str] = if kind == RelationKind::ContextualLink {
+ &["owner", "source", "target", "purpose"]
+ } else if kind == RelationKind::FunctionalExchange {
+ &["source", "target", "source_port", "target_port"]
+ } else {
+ &["source", "target"]
+ };
+ for &required in required {
+ if !fields.contains_key(required) {
+ return Err(conversion_error(std::format!(
+ "Missing required field '{required}'."
+ )));
+ }
+ }
+ let mut body =
+ serde_json::to_value(RelationBody::new(kind, Id::nil(), Id::nil()))
+ .unwrap();
+ let merged = merge_fields(payload(&body), fields).map_err(&conversion_error)?;
+ replace_payload(&mut body, merged);
+ let body = serde_json::from_value(body)
+ .map_err(|e| conversion_error(e.to_string()))?;
+ project.relations.insert(
+ d.id,
+ Relation {
+ id: d.id,
+ alias: d.alias.clone(),
+ name: d.name.clone(),
+ description,
+ body,
+ },
+ );
+ }
+ DeclKind::Diagram => {
+ if project.schema_version < 7 { crate::persistence::reject_source7_fields(&Value::Object(fields.clone())).map_err(&conversion_error)?; }
+ if project.schema_version < 6 && fields.contains_key("view_options") {
+ return Err(conversion_error("Diagram.view_options requires schema/source 6, including an explicitly supplied default value.".into()));
+ }
+ if project.schema_version < 6 && let Some(key)=fields.keys().find(|key| !matches!(key.as_str(),"description"|"layer"|"projection"|"routes")) {
+ return Err(conversion_error(std::format!("Unknown diagram field '{key}' in the frozen pre6 contract.")));
+ }
+ let defaults =
+ serde_json::json!({"description":"","layer":"Oa","projection":"Overview","routes":[],"view_options":crate::view_options::ViewOptions::default()});
+ let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
+ .map_err(&conversion_error)?;
+ header(&mut record, d, true);
+ record.insert("nodes".into(), serde_json::json!({}));
+ let diagram: Diagram = serde_json::from_value(Value::Object(record))
+ .map_err(|e| conversion_error(e.to_string()))?;
+ project.diagrams.insert(d.id, diagram);
+ }
+ DeclKind::Node => {
+ let diagram =
+ take_required_id(&mut fields, "diagram").map_err(&conversion_error)?;
+ if !fields.contains_key("element") {
+ return Err(conversion_error("Missing required field 'element'.".into()));
+ }
+ let defaults = serde_json::json!({"element":null,"position":{"x":0.0,"y":0.0},"size":{"width":224.0,"height":94.0},"collapsed":false,"port_anchors":[]});
+ let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
+ .map_err(&conversion_error)?;
+ header(&mut record, d, false);
+ let node: NodePlacement = serde_json::from_value(Value::Object(record))
+ .map_err(|e| conversion_error(e.to_string()))?;
+ nodes.push((diagram, node, d.offset, index));
+ }
+ DeclKind::Artifact => {
+ if !fields.contains_key("owner") {
+ return Err(conversion_error("Missing required field 'owner'.".into()));
+ }
+ let defaults = serde_json::json!({"description":"","owner":null,"relation":"References","uri":"","revision":null,"fragment":null});
+ let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
+ .map_err(&conversion_error)?;
+ header(&mut record, d, true);
+ let artifact = serde_json::from_value(Value::Object(record))
+ .map_err(|e| conversion_error(e.to_string()))?;
+ project.artifact_links.insert(d.id, artifact);
+ }
+ DeclKind::Trace => {
+ for required in ["oa", "sa"] {
+ if !fields.contains_key(required) {
+ return Err(conversion_error(std::format!(
+ "Missing required field '{required}'."
+ )));
+ }
+ }
+ let defaults = serde_json::json!({"description":"","oa":null,"sa":null,"kind":"Refinement","baseline":null});
+ let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
+ .map_err(&conversion_error)?;
+ header(&mut record, d, true);
+ let trace = if project.schema_version < 5 {
+ let old: crate::persistence::v4::Trace4 = serde_json::from_value(Value::Object(record)).map_err(|e| conversion_error(e.to_string()))?;
+ Trace::from(old)
+ } else {
+ serde_json::from_value(Value::Object(record)).map_err(|e| conversion_error(e.to_string()))?
+ };
+ project.traces.insert(d.id, trace);
+ }
+ }
+ Ok(())
+ })().map_err(|error| (index, error))?;
+ }
+ for (diagram, node, offset, index) in nodes {
+ let target = project
+ .diagrams
+ .get_mut(&diagram)
+ .ok_or_else(|| Error {
+ code: "invalid_source",
+ offset,
+ message: "Node 'diagram' must reference a diagram declaration.".into(),
+ })
+ .map_err(|error| (index, error))?;
+ target.nodes.insert(node.id, node);
+ }
+ if legacy {
+ let mut value = serde_json::to_value(&project).expect("project serialization");
+ value["elements"] = Value::Object(legacy_elements);
+ let migrated = serde_json::from_value(value)
+ .map_err(crate::persistence::FileError::Json)
+ .and_then(|old| crate::persistence::v3::migrate(old, limits))
+ .map_err(|error| {
+ let issue = if let crate::persistence::FileError::Model { issues, .. } = &error {
+ issues.first()
+ } else {
+ None
+ };
+ let index = issue
+ .and_then(|issue| issue.element)
+ .and_then(|id| declarations.iter().position(|d| d.id == id))
+ .unwrap_or(usize::MAX);
+ let offset = declarations.get(index).map_or(0, |d| {
+ issue
+ .and_then(|issue| issue.field.as_ref())
+ .and_then(|field| d.field_spans.get(field))
+ .map_or(d.offset, |span| span.start)
+ });
+ (
+ index,
+ Error {
+ code: if matches!(error, crate::persistence::FileError::Limit(_)) {
+ "model_limit"
+ } else {
+ "migration_error"
+ },
+ offset,
+ message: error.to_string(),
+ },
+ )
+ })?;
+ for (index, d) in declarations.iter().enumerate() {
+ if let DeclKind::Element(kind, layer) = d.kind {
+ let body = &migrated.project.elements[&d.id].body;
+ if body.kind() != kind || body.layer() != layer {
+ return Err((
+ index,
+ Error {
+ code: "invalid_source",
+ offset: d.offset,
+ message: "Declaration kind disagrees with its typed body.".into(),
+ },
+ ));
+ }
+ }
+ }
+ return Ok(migrated);
+ }
+ if matches!(project.schema_version, 4..=6) {
+ return (if project.schema_version == 4 {
+ crate::persistence::v4::finish(project, limits)
+ } else if project.schema_version == 5 {
+ crate::persistence::migrate_v5(project, limits)
+ } else {
+ crate::persistence::migrate_v6(project, limits)
+ })
+ .map_err(|error| {
+ (
+ usize::MAX,
+ Error {
+ code: if matches!(error, crate::persistence::FileError::Limit(_)) {
+ "model_limit"
+ } else {
+ "migration_error"
+ },
+ offset: 0,
+ message: error.to_string(),
+ },
+ )
+ });
+ }
+ limits.check_model(&project).map_err(|message| {
+ (
+ usize::MAX,
+ Error {
+ code: "model_limit",
+ offset: 0,
+ message,
+ },
+ )
+ })?;
+ Ok(crate::persistence::DecodeResult {
+ project,
+ migration: None,
+ })
+}
+
+fn header(fields: &mut Map, d: &Declaration, named: bool) {
+ fields.insert("id".into(), Value::String(d.id.to_string()));
+ fields.insert("alias".into(), Value::String(d.alias.clone()));
+ if named {
+ fields.insert("name".into(), Value::String(d.name.clone()));
+ }
+}
+fn merge_fields(
+ mut defaults: Map,
+ fields: Map,
+) -> Result, String> {
+ for (key, value) in fields {
+ if !defaults.contains_key(&key) {
+ return Err(std::format!("Unknown field '{key}' for this declaration."));
+ }
+ defaults.insert(key, value);
+ }
+ Ok(defaults)
+}
+fn take_string(
+ fields: &mut Map,
+ key: &str,
+ default: &str,
+) -> Result {
+ match fields.remove(key) {
+ None => Ok(default.into()),
+ Some(Value::String(value)) => Ok(value),
+ _ => Err(std::format!("Field '{key}' requires a string.")),
+ }
+}
+fn take_optional_id(fields: &mut Map, key: &str) -> Result, String> {
+ match fields.remove(key) {
+ None | Some(Value::Null) => Ok(None),
+ Some(Value::String(value)) => Id::parse_str(&value)
+ .map(Some)
+ .map_err(|_| std::format!("Field '{key}' requires an @alias reference.")),
+ _ => Err(std::format!(
+ "Field '{key}' requires an @alias reference or none."
+ )),
+ }
+}
+fn take_required_id(fields: &mut Map, key: &str) -> Result {
+ take_optional_id(fields, key)?
+ .ok_or_else(|| std::format!("Missing required @alias field '{key}'."))
+}
+
+/// Analyze an incomplete source buffer without adopting a candidate. Declaration
+/// spans remain available even when a later declaration contains errors.
+/// Invalid compact/braced/URN header IDs retain no authored identity, matching
+/// the declaration parser rather than the UUID crate's broader accepted forms.
+pub fn analyze(source: &str) -> Analysis {
+ let tokens = tokenize(source);
+ let (declarations, _) =
+ declaration_outline(source, &tokens, usize::MAX).expect("unlimited declaration walk");
+ Analysis {
+ tokens,
+ diagnostics: lint(source),
+ declarations: declarations.into_iter().map(|d| d.declaration).collect(),
+ }
+}
+
+/// Read exact current text without lint, parsing a model, stabilization or I/O.
+/// Source bytes are admitted before token allocation; token/declaration limits
+/// are checked before lexing the next token or materializing the next record.
+pub fn outline(source: &str, limits: OutlineLimits) -> Result, OutlineLimit> {
+ if source.len() > limits.max_source_bytes {
+ return Err(OutlineLimit {
+ resource: OutlineResource::SourceBytes,
+ limit: limits.max_source_bytes,
+ observed_at_least: source.len(),
+ offset: 0,
+ });
+ }
+ let tokens = tokenize_with_limit(source, limits.max_tokens)?;
+ let has_lexical_errors = tokens.iter().any(|token| token.class == TokenClass::Error);
+ let (declarations, has_unmatched_braces) =
+ declaration_outline(source, &tokens, limits.max_declarations)?;
+ Ok(Outline {
+ source,
+ tokens,
+ declarations,
+ has_lexical_errors,
+ has_unmatched_braces,
+ })
+}
+
+fn declaration_outline(
+ source: &str,
+ tokens: &[TokenSpan],
+ max_declarations: usize,
+) -> Result<(Vec, bool), OutlineLimit> {
+ let significant = tokens
+ .iter()
+ .filter(|t| !matches!(t.class, TokenClass::Whitespace | TokenClass::Comment))
+ .collect::>();
+ // Recover matched body bounds once. Scanning each aggregate's descendants
+ // again would make tolerant navigation quadratic on deeply nested drafts.
+ let mut body_ends = vec![None; significant.len()];
+ let mut bodies = Vec::new();
+ let mut unmatched_close = false;
+ for (index, token) in significant.iter().enumerate() {
+ match &source[token.span.start..token.span.end] {
+ "{" => bodies.push(index),
+ "}" => {
+ if let Some(open) = bodies.pop() {
+ body_ends[open] = Some(token.span.end);
+ } else {
+ unmatched_close = true;
+ }
+ }
+ _ => {}
+ }
+ }
+ let has_unmatched_braces = unmatched_close || !bodies.is_empty();
+ let mut declarations = Vec::new();
+ for (i, t) in significant.iter().enumerate() {
+ let word = &source[t.span.start..t.span.end];
+ if declaration_kind(word).is_none() && word != "project" {
+ continue;
+ }
+ let Some(alias_token) = significant.get(i + 1) else {
+ continue;
+ };
+ let alias = &source[alias_token.span.start..alias_token.span.end];
+ if !crate::valid_alias(alias) {
+ continue;
+ }
+ let mut j = i + 2;
+ let named = word != "node";
+ if named {
+ if significant
+ .get(j)
+ .is_none_or(|t| t.class != TokenClass::String)
+ {
+ continue;
+ }
+ j += 1;
+ }
+ if declarations.len() == max_declarations {
+ return Err(OutlineLimit {
+ resource: OutlineResource::Declarations,
+ limit: max_declarations,
+ observed_at_least: declarations.len().saturating_add(1),
+ offset: t.span.start,
+ });
+ }
+ let mut id = None;
+ let mut id_literal_span = None;
+ if significant
+ .get(j)
+ .is_some_and(|t| &source[t.span.start..t.span.end] == "id")
+ {
+ if let Some(value) = significant.get(j + 1)
+ && value.class == TokenClass::String
+ {
+ id_literal_span = Some(value.span);
+ let text =
+ serde_json::from_str::(&source[value.span.start..value.span.end])
+ .unwrap();
+ if has_uuid_header_shape(&text) {
+ id = Id::parse_str(&text).ok();
+ }
+ }
+ j += 2;
+ }
+ // A recognized header ID at EOF must remain inside the whole span.
+ // Keep the old next-token/body behavior; only the EOF fallback changes.
+ let header_end = significant
+ .get(j.saturating_sub(1))
+ .or_else(|| significant.last())
+ .map_or(alias_token.span.end, |token| token.span.end);
+ let mut end = significant.get(j).map_or(header_end, |t| t.span.end);
+ if significant
+ .get(j)
+ .is_some_and(|t| &source[t.span.start..t.span.end] == "{")
+ {
+ end = body_ends[j].unwrap_or(source.len());
+ }
+ declarations.push(OutlineDeclaration {
+ declaration: DeclarationSpan {
+ alias: alias.into(),
+ id,
+ kind: word.into(),
+ span: Span {
+ start: t.span.start,
+ end,
+ },
+ },
+ kind_span: t.span,
+ alias_span: alias_token.span,
+ id_literal_span,
+ });
+ }
+ Ok((declarations, has_unmatched_braces))
+}
+
+#[cfg(test)]
+#[path = "language_outline_tests.rs"]
+mod outline_tests;
+
+#[cfg(test)]
+#[path = "language_nesting_tests.rs"]
+mod nesting_tests;
+
+pub(crate) mod legacy {
+ //! Arcadia text: a readable, lossless representation of a valid OA project.
+ //!
+ //! Parsing creates a candidate only. Callers adopt it through `Session` so that
+ //! graphical and textual edits share validation, history, and saved-state rules.
+ use crate::persistence::v1::{
+ Element, Id, Kind, Position, Project, Relation, RelationKind, validation,
+ };
+ use std::collections::{BTreeMap, BTreeSet};
+ use std::fmt;
+
+ /// A source error. Positions are one-based and columns count Unicode scalars.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct Diagnostic {
+ pub code: &'static str,
+ pub line: usize,
+ pub column: usize,
+ pub message: String,
+ }
+
+ impl fmt::Display for Diagnostic {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}:{}: {}", self.line, self.column, self.message)
+ }
+ }
+
+ /// Parse an entire document, resolve forward references, and validate its model.
+ /// Missing `id` clauses generate fresh identities; canonical output includes them.
+ /// Incomplete models with warnings are accepted. Syntax and structural errors are
+ /// rejected, without returning a partially usable model.
+ pub fn parse(source: &str) -> Result> {
+ parse_limited(source, super::ParseLimits::UNLIMITED)
+ }
+
+ pub(super) fn parse_limited(
+ source: &str,
+ limits: super::ParseLimits,
+ ) -> Result> {
+ let tokens = lex(source).map_err(|error| vec![diagnostic(source, error)])?;
+ Parser {
+ tokens,
+ cursor: 0,
+ limits,
+ }
+ .document()
+ .map_err(|error| vec![diagnostic(source, error)])
+ }
+
+ /// Produce deterministic canonical text for a structurally valid project.
+ /// All persistent model values and identities are represented. Local aliases are
+ /// derived from display names and are not persistent model identity. Formatting
+ /// discards source comments and whitespace. Invalid projects must be validated
+ /// before formatting; only valid projects have the round-trip guarantee.
+ pub fn format(project: &Project) -> String {
+ let mut used = BTreeSet::new();
+ let mut aliases = BTreeMap::new();
+ for element in project.elements.values() {
+ aliases.insert(
+ element.id,
+ alias(&element.name, kind_word(element.kind), &mut used),
+ );
+ }
+ for relation in project.relations.values() {
+ aliases.insert(
+ relation.id,
+ alias(&relation.name, relation_word(relation.kind), &mut used),
+ );
+ }
+ let reference = |id: Id| {
+ aliases
+ .get(&id)
+ .cloned()
+ .unwrap_or_else(|| std::format!("missing_{}", id.simple()))
+ };
+ let mut text = std::format!(
+ "arcadia {};\nproject {} id \"{}\" {{\n",
+ project.schema_version,
+ quote(&project.name),
+ project.id,
+ );
+ for element in project.elements.values() {
+ text.push_str(&std::format!(
+ " {} {} {} id \"{}\" {{\n description {};\n position ({}, {});\n",
+ kind_word(element.kind),
+ reference(element.id),
+ quote(&element.name),
+ element.id,
+ quote(&element.description),
+ element.position.x,
+ element.position.y,
+ ));
+ if element.kind == Kind::Process {
+ text.push_str(&std::format!(
+ " steps [{}];\n",
+ element
+ .steps
+ .iter()
+ .map(|id| reference(*id))
+ .collect::>()
+ .join(", ")
+ ));
+ }
+ text.push_str(" }\n\n");
+ }
+ for relation in project.relations.values() {
+ text.push_str(&std::format!(
+ " {} {} {} id \"{}\" from {} to {};\n",
+ relation_word(relation.kind),
+ reference(relation.id),
+ quote(&relation.name),
+ relation.id,
+ reference(relation.source),
+ reference(relation.target),
+ ));
+ }
+ text.push_str("}\n");
+ text
+ }
+
+ fn quote(value: &str) -> String {
+ serde_json::to_string(value).expect("serializing a Rust string cannot fail")
+ }
+
+ fn alias(name: &str, fallback: &str, used: &mut BTreeSet) -> String {
+ let mut stem = String::new();
+ for ch in name.chars() {
+ if ch.is_ascii_alphanumeric() {
+ stem.push(ch.to_ascii_lowercase());
+ } else if !stem.is_empty() && !stem.ends_with('_') {
+ stem.push('_');
+ }
+ }
+ let mut stem = stem.trim_end_matches('_').to_owned();
+ if stem.is_empty() {
+ stem = std::format!("{fallback}_item");
+ } else if stem.as_bytes()[0].is_ascii_digit() || reserved(&stem) {
+ stem = std::format!("{fallback}_{stem}");
+ }
+ let mut candidate = stem.clone();
+ let mut suffix = 2;
+ while !used.insert(candidate.clone()) {
+ candidate = std::format!("{stem}_{suffix}");
+ suffix += 1;
+ }
+ candidate
+ }
+
+ fn kind_word(kind: Kind) -> &'static str {
+ match kind {
+ Kind::Entity => "entity",
+ Kind::Actor => "actor",
+ Kind::Activity => "activity",
+ Kind::Capability => "capability",
+ Kind::Process => "process",
+ }
+ }
+
+ fn relation_word(kind: RelationKind) -> &'static str {
+ match kind {
+ RelationKind::Allocation => "allocation",
+ RelationKind::Interaction => "interaction",
+ RelationKind::Contribution => "contribution",
+ RelationKind::Realization => "realization",
+ }
+ }
+
+ fn reserved(word: &str) -> bool {
+ matches!(
+ word,
+ "arcadia"
+ | "project"
+ | "id"
+ | "entity"
+ | "actor"
+ | "activity"
+ | "capability"
+ | "process"
+ | "allocation"
+ | "interaction"
+ | "contribution"
+ | "realization"
+ | "description"
+ | "position"
+ | "steps"
+ | "from"
+ | "to"
+ )
+ }
+
+ struct Error {
+ code: &'static str,
+ offset: usize,
+ message: String,
+ }
+
+ fn diagnostic(source: &str, error: Error) -> Diagnostic {
+ let prefix = &source[..error.offset];
+ Diagnostic {
+ code: error.code,
+ line: prefix.bytes().filter(|byte| *byte == b'\n').count() + 1,
+ column: prefix.rsplit('\n').next().unwrap_or("").chars().count() + 1,
+ message: error.message,
+ }
+ }
+
+ #[derive(Clone)]
+ enum TokenKind {
+ Word(String),
+ Quoted(String),
+ Number(String),
+ Symbol(char),
+ End,
+ }
+
+ struct Token {
+ kind: TokenKind,
+ offset: usize,
+ }
+
+ fn lex(source: &str) -> Result, Error> {
+ let mut result = Vec::new();
+ let mut cursor = 0;
+ while cursor < source.len() {
+ let start = cursor;
+ let ch = source[cursor..].chars().next().unwrap();
+ if matches!(ch, ' ' | '\t' | '\r' | '\n') {
+ cursor += 1;
+ continue;
+ }
+ if source[cursor..].starts_with("//") {
+ cursor += source[cursor..].find('\n').unwrap_or(source.len() - cursor);
+ continue;
+ }
+ let kind = if ch == '"' {
+ cursor += 1;
+ let mut escaped = false;
+ let mut closed = false;
+ for next in source[cursor..].chars() {
+ cursor += next.len_utf8();
+ if escaped {
+ escaped = false;
+ } else if next == '\\' {
+ escaped = true;
+ } else if next == '"' {
+ closed = true;
+ break;
+ }
+ }
+ if !closed {
+ return Err(Error {
+ code: "invalid_source",
+ offset: start,
+ message: "Unterminated quoted string.".into(),
+ });
+ }
+ TokenKind::Quoted(serde_json::from_str(&source[start..cursor]).map_err(
+ |error| Error {
+ code: "invalid_source",
+ offset: start,
+ message: std::format!("Invalid quoted string: {error}"),
+ },
+ )?)
+ } else if ch.is_ascii_alphabetic() || ch == '_' {
+ cursor += 1;
+ while source
+ .as_bytes()
+ .get(cursor)
+ .is_some_and(|next| next.is_ascii_alphanumeric() || *next == b'_')
+ {
+ cursor += 1;
+ }
+ TokenKind::Word(source[start..cursor].into())
+ } else if ch.is_ascii_digit() || matches!(ch, '-' | '+' | '.') {
+ cursor += 1;
+ while source.as_bytes().get(cursor).is_some_and(|next| {
+ next.is_ascii_alphanumeric() || matches!(*next, b'.' | b'+' | b'-' | b'_')
+ }) {
+ cursor += 1;
+ }
+ TokenKind::Number(source[start..cursor].into())
+ } else if "{}();,[]".contains(ch) {
+ cursor += 1;
+ TokenKind::Symbol(ch)
+ } else {
+ return Err(Error {
+ code: "invalid_source",
+ offset: start,
+ message: std::format!("Unexpected character {ch:?}."),
+ });
+ };
+ result.push(Token {
+ kind,
+ offset: start,
+ });
+ }
+ result.push(Token {
+ kind: TokenKind::End,
+ offset: source.len(),
+ });
+ Ok(result)
+ }
+
+ struct Reference {
+ alias: String,
+ offset: usize,
+ }
+
+ enum Declaration {
+ Element(Element, Vec),
+ Relation(Id, RelationKind, String, Reference, Reference),
+ }
+
+ struct Parser {
+ tokens: Vec,
+ cursor: usize,
+ limits: super::ParseLimits,
+ }
+
+ impl Parser {
+ fn current(&self) -> &Token {
+ &self.tokens[self.cursor]
+ }
+
+ fn error(&self, message: impl Into) -> Error {
+ Error {
+ code: "invalid_source",
+ offset: self.current().offset,
+ message: message.into(),
+ }
+ }
+
+ fn word_is(&self, word: &str) -> bool {
+ matches!(&self.current().kind, TokenKind::Word(actual) if actual == word)
+ }
+
+ fn symbol_is(&self, symbol: char) -> bool {
+ matches!(self.current().kind, TokenKind::Symbol(actual) if actual == symbol)
+ }
+
+ fn word(&mut self, word: &str) -> Result<(), Error> {
+ if !self.word_is(word) {
+ return Err(self.error(std::format!("Expected '{word}'.")));
+ }
+ self.cursor += 1;
+ Ok(())
+ }
+
+ fn symbol(&mut self, symbol: char) -> Result<(), Error> {
+ if !self.symbol_is(symbol) {
+ return Err(self.error(std::format!("Expected '{symbol}'.")));
+ }
+ self.cursor += 1;
+ Ok(())
+ }
+
+ fn quoted(&mut self) -> Result {
+ let TokenKind::Quoted(value) = &self.current().kind else {
+ return Err(self.error("Expected a double-quoted string."));
+ };
+ let value = value.clone();
+ self.cursor += 1;
+ Ok(value)
+ }
+
+ fn reference(&mut self) -> Result {
+ let TokenKind::Word(alias) = &self.current().kind else {
+ return Err(self.error("Expected an alias (letters, digits, or underscores)."));
+ };
+ if reserved(alias) {
+ return Err(
+ self.error(std::format!("'{alias}' is reserved; choose another alias."))
+ );
+ }
+ let reference = Reference {
+ alias: alias.clone(),
+ offset: self.current().offset,
+ };
+ self.cursor += 1;
+ Ok(reference)
+ }
+
+ fn optional_id(&mut self) -> Result {
+ if !self.word_is("id") {
+ return Ok(Id::new_v4());
+ }
+ self.cursor += 1;
+ let offset = self.current().offset;
+ let value = self.quoted()?;
+ // Require the portable 8-4-4-4-12 spelling, not the UUID crate's URN,
+ // braces, or compact alternatives. Uppercase hexadecimal is accepted.
+ if !super::has_uuid_header_shape(&value) {
+ return Err(Error {
+ code: "invalid_source",
+ offset,
+ message: "Expected a UUID string in 8-4-4-4-12 hexadecimal form.".into(),
+ });
+ }
+ Id::parse_str(&value).map_err(|_| Error {
+ code: "invalid_source",
+ offset,
+ message: "Invalid UUID: use 8-4-4-4-12 hexadecimal form.".into(),
+ })
+ }
+
+ fn number(&mut self) -> Result {
+ let TokenKind::Number(value) = &self.current().kind else {
+ return Err(self.error("Expected a finite decimal coordinate."));
+ };
+ // JSON establishes an exact lexical grammar; Rust's f32 parser retains
+ // negative zero and supplies the actual model's IEEE-754 rounding.
+ if serde_json::from_str::(value).is_err() {
+ return Err(self.error("Invalid number: use JSON decimal or exponent notation."));
+ }
+ let parsed = value
+ .parse::()
+ .ok()
+ .filter(|number| number.is_finite())
+ .ok_or_else(|| self.error("Coordinate must be a finite 32-bit number."))?;
+ self.cursor += 1;
+ Ok(parsed)
+ }
+
+ fn document(&mut self) -> Result {
+ self.word("arcadia")?;
+ let version_offset = self.current().offset;
+ let TokenKind::Number(version) = &self.current().kind else {
+ return Err(self.error("Expected the language version number 1."));
+ };
+ let schema_version = version
+ .parse::()
+ .map_err(|_| self.error("Expected an unsigned integer language version."))?;
+ if version != &schema_version.to_string() {
+ return Err(self.error("Use an unsigned decimal version without leading zeros."));
+ }
+ self.cursor += 1;
+ self.symbol(';')?;
+ self.word("project")?;
+ let project_offset = self.current().offset;
+ let name = self.quoted()?;
+ let id = self.optional_id()?;
+ let mut project = Project {
+ schema_version,
+ id,
+ name,
+ elements: BTreeMap::new(),
+ relations: BTreeMap::new(),
+ aliases: BTreeMap::new(),
+ };
+ self.symbol('{')?;
+ let mut aliases = BTreeMap::new();
+ let mut locations = BTreeMap::from([(id, project_offset)]);
+ let mut declarations = Vec::new();
+ while !self.symbol_is('}') {
+ self.limits
+ .check_counts(
+ declarations.len().saturating_add(1),
+ declarations.len().saturating_add(2),
+ 0,
+ )
+ .map_err(|message| Error {
+ code: "model_limit",
+ offset: self.current().offset,
+ message,
+ })?;
+ let offset = self.current().offset;
+ let (kind, relation_kind) = match &self.current().kind {
+ TokenKind::Word(word) => match word.as_str() {
+ "entity" => (Some(Kind::Entity), None),
+ "actor" => (Some(Kind::Actor), None),
+ "activity" => (Some(Kind::Activity), None),
+ "capability" => (Some(Kind::Capability), None),
+ "process" => (Some(Kind::Process), None),
+ "allocation" => (None, Some(RelationKind::Allocation)),
+ "interaction" => (None, Some(RelationKind::Interaction)),
+ "contribution" => (None, Some(RelationKind::Contribution)),
+ "realization" => (None, Some(RelationKind::Realization)),
+ _ => return Err(self.error(std::format!("Unknown declaration '{word}'."))),
+ },
+ _ => return Err(self.error("Expected an element, relationship, or '}'.")),
+ };
+ self.cursor += 1;
+ let alias = self.reference()?;
+ let name = self.quoted()?;
+ let id = self.optional_id()?;
+ if aliases.insert(alias.alias.clone(), id).is_some() {
+ return Err(Error {
+ code: "invalid_source",
+ offset: alias.offset,
+ message: std::format!("Duplicate alias '{}'.", alias.alias),
+ });
+ }
+ if locations.insert(id, offset).is_some() {
+ return Err(Error {
+ code: "invalid_source",
+ offset: alias.offset,
+ message: std::format!(
+ "Duplicate identity '{id}'; all IDs must be unique, including the project ID."
+ ),
+ });
+ }
+ if let Some(kind) = kind {
+ let (element, steps) = self.element(id, kind, name)?;
+ declarations.push(Declaration::Element(element, steps));
+ } else if let Some(kind) = relation_kind {
+ self.word("from")?;
+ let source = self.reference()?;
+ self.word("to")?;
+ let target = self.reference()?;
+ self.symbol(';')?;
+ declarations.push(Declaration::Relation(id, kind, name, source, target));
+ }
+ }
+ self.symbol('}')?;
+ if !matches!(self.current().kind, TokenKind::End) {
+ return Err(self.error("Unexpected content after the project closing brace."));
+ }
+ let resolve = |reference: &Reference| {
+ aliases.get(&reference.alias).copied().ok_or_else(|| Error {
+ code: "invalid_source",
+ offset: reference.offset,
+ message: std::format!("Unknown reference '{}'.", reference.alias),
+ })
+ };
+ project.aliases = aliases
+ .iter()
+ .map(|(alias, id)| (*id, alias.clone()))
+ .collect();
+ for declaration in declarations {
+ match declaration {
+ Declaration::Element(mut element, steps) => {
+ element.steps = steps.iter().map(resolve).collect::>()?;
+ project.elements.insert(element.id, element);
+ }
+ Declaration::Relation(id, kind, name, source, target) => {
+ project.relations.insert(
+ id,
+ Relation {
+ id,
+ kind,
+ name,
+ source: resolve(&source)?,
+ target: resolve(&target)?,
+ },
+ );
+ }
+ }
+ }
+ crate::persistence::check_v1_limits(&project, self.limits).map_err(|message| {
+ Error {
+ code: "model_limit",
+ offset: self.current().offset,
+ message,
+ }
+ })?;
+ if let Some(issue) = validation::validate(&project)
+ .into_iter()
+ .find(|issue| issue.severity == validation::Severity::Error)
+ {
+ let offset = if schema_version != 1 {
+ version_offset
+ } else {
+ issue
+ .element
+ .and_then(|id| locations.get(&id).copied())
+ .unwrap_or(project_offset)
+ };
+ return Err(Error {
+ code: "invalid_source",
+ offset,
+ message: issue.message,
+ });
+ }
+ Ok(project)
+ }
+
+ fn element(
+ &mut self,
+ id: Id,
+ kind: Kind,
+ name: String,
+ ) -> Result<(Element, Vec), Error> {
+ self.symbol('{')?;
+ let mut element = Element {
+ id,
+ kind,
+ name,
+ description: String::new(),
+ position: Position { x: 0., y: 0. },
+ steps: Vec::new(),
+ };
+ let mut fields = BTreeSet::new();
+ let mut steps = Vec::new();
+ while !self.symbol_is('}') {
+ let TokenKind::Word(field) = self.current().kind.clone() else {
+ return Err(self.error("Expected 'description', 'position', 'steps', or '}'."));
+ };
+ if !fields.insert(field.clone()) {
+ return Err(self.error(std::format!("Duplicate field '{field}'.")));
+ }
+ match field.as_str() {
+ "description" => {
+ self.cursor += 1;
+ element.description = self.quoted()?;
+ }
+ "position" => {
+ self.cursor += 1;
+ self.symbol('(')?;
+ element.position.x = self.number()?;
+ self.symbol(',')?;
+ element.position.y = self.number()?;
+ self.symbol(')')?;
+ }
+ "steps" if kind == Kind::Process => {
+ self.cursor += 1;
+ self.symbol('[')?;
+ if !self.symbol_is(']') {
+ loop {
+ steps.push(self.reference()?);
+ if !self.symbol_is(',') {
+ break;
+ }
+ self.cursor += 1;
+ }
+ }
+ self.symbol(']')?;
+ }
+ "steps" => return Err(self.error("Only processes can contain steps.")),
+ _ => return Err(self.error(std::format!("Unknown field '{field}'."))),
+ }
+ self.symbol(';')?;
+ }
+ self.symbol('}')?;
+ Ok((element, steps))
+ }
+ }
+}
+
+/// Closed historical facet identity for the targeted source writer.
+pub(crate) fn semantic_row_key(source: &str, origins: &BTreeMap) -> Option {
+ semantic_source::row_key(source, origins)
+}
diff --git a/harness/experiments/text-navigation-components-20260911/component-inputs/language_outline_tests.rs b/harness/experiments/text-navigation-components-20260911/component-inputs/language_outline_tests.rs
new file mode 100644
index 00000000..a29f180f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/component-inputs/language_outline_tests.rs
@@ -0,0 +1,386 @@
+//! Prepared outline tests. Explicit compatibility tests additionally exercise
+//! the existing parsers/analyze; the outline entry itself never calls them.
+use super::*;
+
+fn limits() -> OutlineLimits {
+ OutlineLimits {
+ max_source_bytes: 1024 * 1024,
+ max_tokens: 100_000,
+ max_declarations: 1_000,
+ }
+}
+
+fn declaration<'a>(result: &'a Outline<'_>, alias: &str) -> &'a OutlineDeclaration {
+ result
+ .declarations
+ .iter()
+ .find(|d| d.declaration.alias == alias)
+ .unwrap()
+}
+
+fn text(source: &str, span: Span) -> &str {
+ &source[span.start..span.end]
+}
+
+#[test]
+fn outline_roles_preserve_unicode_crlf_and_exact_escaped_uuid_literal() {
+ let source = "// 検査 🚒\r\noa_actor responder \"救助\" id \"\\u00300000000-0000-0000-0000-000000000001\" {}\r\nsource @ // 使用\r\n responder;";
+ let before = source.as_bytes().to_vec();
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.source.as_bytes(), before);
+ assert_eq!(result.source.as_ptr(), source.as_ptr());
+ let d = declaration(&result, "responder");
+ assert_eq!(text(source, d.kind_span), "oa_actor");
+ assert_eq!(text(source, d.alias_span), "responder");
+ assert_eq!(
+ text(source, d.id_literal_span.unwrap()),
+ "\"\\u00300000000-0000-0000-0000-000000000001\""
+ );
+ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
+ assert_eq!(d.declaration.span.start, source.find("oa_actor").unwrap());
+ assert_eq!(d.declaration.span.end, source.find("{}\r\n").unwrap() + 2);
+ let mut end = 0;
+ for token in &result.tokens {
+ assert_eq!(token.span.start, end);
+ assert!(token.span.start < token.span.end);
+ assert!(source.is_char_boundary(token.span.start));
+ assert!(source.is_char_boundary(token.span.end));
+ end = token.span.end;
+ }
+ assert_eq!(end, source.len());
+ assert!(!result.has_lexical_errors);
+ assert!(!result.has_unmatched_braces);
+}
+
+#[test]
+fn closed_id_at_eof_is_inside_the_declaration_extent() {
+ for source in [
+ "oa_actor a \"Actor\" id \"00000000-0000-0000-0000-000000000001\"",
+ "node placed id \"00000000-0000-0000-0000-000000000001\"",
+ ] {
+ let result = outline(source, limits()).unwrap();
+ let d = &result.declarations[0];
+ let literal = d.id_literal_span.unwrap();
+ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
+ assert_eq!(d.declaration.span.end, source.len());
+ assert!(d.declaration.span.start <= literal.start);
+ assert!(literal.end <= d.declaration.span.end);
+ }
+ for source in ["oa_actor a \"Actor\"", "oa_actor a \"Actor\" id"] {
+ let result = outline(source, limits()).unwrap();
+ let d = &result.declarations[0];
+ assert_eq!(d.declaration.span.end, source.len());
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(d.id_literal_span, None);
+ }
+}
+
+#[test]
+fn missing_bad_and_historical_ids_never_allocate_authored_identity() {
+ let source = "oa_actor missing \"No ID\" {}\noa_actor bad \"Bad ID\" id \"not-a-uuid\" {}\noa_actor historical \"00000000-0000-0000-0000-000000000001\" { history uuid \"00000000-0000-0000-0000-000000000001\"; }";
+ let first = outline(source, limits()).unwrap();
+ let second = outline(source, limits()).unwrap();
+ assert_eq!(first, second);
+ assert!(
+ first
+ .declarations
+ .iter()
+ .all(|d| d.declaration.id.is_none())
+ );
+ assert_eq!(declaration(&first, "missing").id_literal_span, None);
+ assert_eq!(declaration(&first, "historical").id_literal_span, None);
+ assert_eq!(
+ text(source, declaration(&first, "bad").id_literal_span.unwrap()),
+ "\"not-a-uuid\""
+ );
+ // Balanced tokens are not a successful syntax/semantic validation claim.
+ assert!(!first.has_lexical_errors);
+ assert!(!first.has_unmatched_braces);
+}
+
+#[test]
+fn invalid_full_scan_keeps_closed_children_and_reports_lexical_body_facts() {
+ let source = "oa_entity outer \"Outer\" {\n oa_actor ready \"Ready\" { description \"} stays text\"; }\n oa_actor pending \"Pending\" { description \"unfinished 🌊";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.source, source);
+ assert_eq!(result.tokens.last().unwrap().span.end, source.len());
+ assert!(result.has_lexical_errors);
+ assert!(result.has_unmatched_braces);
+ let ready = declaration(&result, "ready");
+ assert_eq!(
+ text(source, ready.declaration.span),
+ "oa_actor ready \"Ready\" { description \"} stays text\"; }"
+ );
+ assert_eq!(
+ declaration(&result, "outer").declaration.span.end,
+ source.len()
+ );
+ assert_eq!(
+ declaration(&result, "pending").declaration.span.end,
+ source.len()
+ );
+ let unmatched_close = outline("}\n// brace in comment {\n", limits()).unwrap();
+ assert!(unmatched_close.has_unmatched_braces);
+ assert!(!unmatched_close.has_lexical_errors);
+}
+
+#[test]
+fn missing_body_syntax_is_unchecked_even_when_known_error_flags_are_clear() {
+ let source = "oa_actor a \"A\" id \"00000000-0000-0000-0000-000000000001\"";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 1);
+ assert!(!result.has_lexical_errors);
+ assert!(!result.has_unmatched_braces);
+ // There is deliberately no `valid` flag: the known-complete fact is scanning
+ // these bytes, not the missing body becoming legal or the draft being applied.
+ assert_eq!(result.source, source);
+}
+
+#[test]
+fn keyword_alias_and_collisions_keep_every_source_ordered_header() {
+ let source = "project root_model \"Model\" {\n oa_actor oa_activity \"First\" id \"00000000-0000-0000-0000-000000000001\" {}\n oa_actor oa_activity \"Second\" id \"00000000-0000-0000-0000-000000000001\" {}\n diagram view_one \"View\" {}\n node placement { diagram @view_one; }\n}";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 5);
+ let duplicates: Vec<_> = result
+ .declarations
+ .iter()
+ .filter(|d| d.declaration.alias == "oa_activity")
+ .collect();
+ assert_eq!(duplicates.len(), 2);
+ assert!(duplicates[0].alias_span.start < duplicates[1].alias_span.start);
+ assert_eq!(duplicates[0].declaration.id, duplicates[1].declaration.id);
+ assert!(
+ result
+ .tokens
+ .iter()
+ .any(|t| t.span == duplicates[0].alias_span && t.class == TokenClass::Keyword)
+ );
+ assert_eq!(
+ text(source, declaration(&result, "placement").kind_span),
+ "node"
+ );
+ assert_eq!(declaration(&result, "placement").declaration.id, None);
+}
+
+#[test]
+fn bounds_refuse_before_returning_a_partial_outline() {
+ let source = "oa_actor a \"A\" {}\noa_actor b \"B\" {}";
+ let admitted = outline(source, limits()).unwrap();
+ let exact = OutlineLimits {
+ max_source_bytes: source.len(),
+ max_tokens: admitted.tokens.len(),
+ max_declarations: 2,
+ };
+ assert_eq!(outline(source, exact).unwrap(), admitted);
+ for resource in [
+ OutlineResource::SourceBytes,
+ OutlineResource::Tokens,
+ OutlineResource::Declarations,
+ ] {
+ let mut lower = exact;
+ match resource {
+ OutlineResource::SourceBytes => lower.max_source_bytes -= 1,
+ OutlineResource::Tokens => lower.max_tokens -= 1,
+ OutlineResource::Declarations => lower.max_declarations -= 1,
+ }
+ let error = outline(source, lower).unwrap_err();
+ assert_eq!(error.resource, resource);
+ assert!(error.observed_at_least > error.limit);
+ assert!(source.is_char_boundary(error.offset));
+ if resource == OutlineResource::Declarations {
+ assert_eq!(error.offset, source.find("oa_actor b").unwrap());
+ }
+ }
+ let empty = outline(
+ "",
+ OutlineLimits {
+ max_source_bytes: 0,
+ max_tokens: 0,
+ max_declarations: 0,
+ },
+ )
+ .unwrap();
+ assert!(empty.tokens.is_empty() && empty.declarations.is_empty());
+ let unicode = "// 🌊\r\noa_actor a \"A\" {}";
+ let error = outline(
+ unicode,
+ OutlineLimits {
+ max_source_bytes: 4,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::SourceBytes);
+ assert_eq!(error.offset, 0);
+ let error = outline(
+ unicode,
+ OutlineLimits {
+ max_tokens: 1,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::Tokens);
+ assert_eq!(error.offset, unicode.find('\n').unwrap());
+}
+
+#[test]
+fn outline_ranges_follow_current_bytes_without_identity_stabilization() {
+ let old = "oa_actor a \"A\" {}\nsource @a;";
+ let current = format!("// 航海 🚒\r\n{old}");
+ let old_outline = outline(old, limits()).unwrap();
+ let current_outline = outline(¤t, limits()).unwrap();
+ let shift = current.len() - old.len();
+ let old_role = &old_outline.declarations[0];
+ let current_role = ¤t_outline.declarations[0];
+ assert_eq!(
+ current_role.alias_span.start,
+ old_role.alias_span.start + shift
+ );
+ assert_eq!(
+ current_role.kind_span.start,
+ old_role.kind_span.start + shift
+ );
+ assert_eq!(
+ current_role.declaration.span.end,
+ old_role.declaration.span.end + shift
+ );
+ assert_eq!(current_outline.source, current);
+ assert_eq!(old_outline.source, old);
+ assert_eq!(current_role.declaration.id, None);
+ assert!(!current_outline.source.contains(" id "));
+ // Workspace revision/stale-handle rejection belongs to the query/adapter,
+ // not a claim that an old owned span can be applied to any other string.
+}
+
+#[test]
+fn deeply_nested_bounds_use_the_same_iterative_walk_and_declaration_budget() {
+ let mut source = String::new();
+ for index in 0..600 {
+ source.push_str(&format!("oa_activity work_{index} \"Work\" {{\n"));
+ }
+ source.push_str(&"}\n".repeat(600));
+ let result = outline(&source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 600);
+ assert!(!result.has_unmatched_braces);
+ assert_eq!(
+ result.declarations[0].declaration.span.end,
+ source.len() - 1
+ );
+ let error = outline(
+ &source,
+ OutlineLimits {
+ max_declarations: 599,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::Declarations);
+ assert_eq!(error.observed_at_least, 600);
+ assert_eq!(error.offset, source.find("oa_activity work_599").unwrap());
+}
+
+#[test]
+fn noncanonical_uuid_spellings_have_no_outline_or_analysis_identity() {
+ for raw in [
+ "00000000000000000000000000000001",
+ "{00000000-0000-0000-0000-000000000001}",
+ "urn:uuid:00000000-0000-0000-0000-000000000001",
+ ] {
+ assert!(
+ Id::parse_str(raw).is_ok(),
+ "The UUID crate alone is too permissive"
+ );
+ assert!(!has_uuid_header_shape(raw));
+ let literal = format!("\"{raw}\"");
+ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let before = modern.clone();
+ let result = outline(&modern, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
+ assert_eq!(modern, before);
+ let errors = parse(&modern).unwrap_err();
+ assert!(
+ errors
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12")
+ && error.span.start == modern.find(&literal).unwrap())
+ );
+ // Intentional compatibility correction: old analyze yielded Some(Id(1))
+ // here despite lint rejecting the header. It now shares outline's None.
+ let analysis = analyze(&modern);
+ assert_eq!(
+ analysis
+ .declarations
+ .iter()
+ .find(|d| d.alias == "model")
+ .unwrap()
+ .id,
+ None
+ );
+ assert!(
+ analysis
+ .diagnostics
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12"))
+ );
+ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
+ let legacy_errors = legacy::parse(&legacy_source).unwrap_err();
+ assert!(
+ legacy_errors
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12"))
+ );
+ }
+}
+
+#[test]
+fn uppercase_and_json_escaped_header_ids_survive_both_parsers_and_outline() {
+ let expected = Id::from_u128(0xabcdef);
+ let canonical = expected.to_string();
+ let uppercase = canonical.to_uppercase();
+ assert_ne!(uppercase, canonical);
+ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
+ let literal = format!("\"{raw}\"");
+ let decoded: String = serde_json::from_str(&literal).unwrap();
+ assert!(has_uuid_header_shape(&decoded));
+ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let result = outline(&modern, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, Some(expected));
+ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
+ assert_eq!(result.source.as_bytes(), modern.as_bytes());
+ assert_eq!(parse(&modern).unwrap().id, expected);
+ let analysis = analyze(&modern);
+ assert_eq!(analysis.declarations[0].id, Some(expected));
+ assert!(
+ !analysis
+ .diagnostics
+ .iter()
+ .any(|error| error.severity == crate::validation::Severity::Error)
+ );
+ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
+ assert_eq!(legacy::parse(&legacy_source).unwrap().id, expected);
+ }
+}
+
+#[test]
+fn header_shape_still_requires_valid_hexadecimal_uuid_data() {
+ let raw = "00000000-0000-0000-0000-00000000000Z";
+ assert!(has_uuid_header_shape(raw));
+ assert!(Id::parse_str(raw).is_err());
+ let literal = format!("\"{raw}\"");
+ let source = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let result = outline(&source, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(text(&source, d.id_literal_span.unwrap()), literal);
+ assert!(
+ parse(&source)
+ .unwrap_err()
+ .iter()
+ .any(|error| error.message == "Invalid UUID identity.")
+ );
+ assert_eq!(analyze(&source).declarations[0].id, None);
+}
diff --git a/harness/experiments/text-navigation-components-20260911/component-inputs/outline-seam-v2.patch b/harness/experiments/text-navigation-components-20260911/component-inputs/outline-seam-v2.patch
new file mode 100644
index 00000000..6e009842
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/component-inputs/outline-seam-v2.patch
@@ -0,0 +1,569 @@
+--- a/crates/oa-core/src/language.rs
++++ b/crates/oa-core/src/language.rs
+@@ -141,14 +141,89 @@
+ pub diagnostics: Vec,
+ pub declarations: Vec,
+ }
+
++/// Check the decoded spelling required by declaration-header UUID clauses.
++/// This is the shared 36-byte/hyphen shape rule, not a hexadecimal parser:
++/// callers must additionally parse the UUID. Uppercase hex and JSON escapes
++/// that decode to this spelling remain accepted. URN/braced/compact forms do not.
++pub fn has_uuid_header_shape(decoded: &str) -> bool {
++ decoded.len() == 36
++ && [8, 13, 18, 23]
++ .into_iter()
++ .all(|index| decoded.as_bytes()[index] == b'-')
++}
++
++/// Budgets for one original source buffer. Policy is caller-owned; these
++/// limits do not change the grammar or perform model admission.
++#[derive(Debug, Clone, Copy, PartialEq, Eq)]
++pub struct OutlineLimits {
++ pub max_source_bytes: usize,
++ pub max_tokens: usize,
++ pub max_declarations: usize,
++}
++
++#[derive(Debug, Clone, Copy, PartialEq, Eq)]
++pub enum OutlineResource {
++ SourceBytes,
++ Tokens,
++ Declarations,
++}
++
++#[derive(Debug, Clone, Copy, PartialEq, Eq)]
++pub struct OutlineLimit {
++ pub resource: OutlineResource,
++ pub limit: usize,
++ pub observed_at_least: usize,
++ /// Original byte offset where admission stopped; never a rewritten offset.
++ pub offset: usize,
++}
++
++/// Roles discovered by the existing tolerant declaration walk. Keeping the
++/// wrapper preserves DeclarationSpan and Analysis as existing public APIs.
++#[derive(Debug, Clone, PartialEq, Eq)]
++pub struct OutlineDeclaration {
++ pub declaration: DeclarationSpan,
++ pub kind_span: Span,
++ pub alias_span: Span,
++ /// Whole quoted header ID token, even if it is not a valid UUID. In that
++ /// case declaration.id is None. Missing/non-string ID values have no span.
++ pub id_literal_span: Option,
++}
++
++/// A full-file tolerant scan, not a successful parse or validated model. Invalid
++/// text remains available. Lexical errors and unmatched braces are facts only:
++/// their absence does not establish syntactic or semantic validity.
++///
++/// A budget failure returns Err and no partial Outline. Include reachability,
++/// project completeness and editor revision are intentionally not inferred here.
++#[derive(Debug, Clone, PartialEq, Eq)]
++pub struct Outline<'a> {
++ pub source: &'a str,
++ pub tokens: Vec,
++ pub declarations: Vec,
++ pub has_lexical_errors: bool,
++ pub has_unmatched_braces: bool,
++}
++
+ /// Tolerant highlighting tokens cover the whole input, including incomplete
+ /// strings and unexpected characters. No model is created or modified.
+ pub fn tokenize(source: &str) -> Vec {
++ tokenize_with_limit(source, usize::MAX).expect("unlimited token scan")
++}
++
++fn tokenize_with_limit(source: &str, max_tokens: usize) -> Result, OutlineLimit> {
+ let mut tokens = Vec::new();
+ let mut cursor = 0;
+ while cursor < source.len() {
++ if tokens.len() == max_tokens {
++ return Err(OutlineLimit {
++ resource: OutlineResource::Tokens,
++ limit: max_tokens,
++ observed_at_least: tokens.len().saturating_add(1),
++ offset: cursor,
++ });
++ }
+ let start = cursor;
+ let ch = source[cursor..].chars().next().expect("in bounds");
+ let class = if matches!(ch, ' ' | '\t' | '\r' | '\n') {
+ cursor += 1;
+@@ -223,9 +298,9 @@
+ class,
+ span: Span { start, end: cursor },
+ });
+ }
+- tokens
++ Ok(tokens)
+ }
+
+ pub fn lint(source: &str) -> Vec {
+ match parse_document(source, ParseLimits::UNLIMITED) {
+@@ -1229,13 +1304,9 @@
+ }
+ self.cursor += 1;
+ let offset = self.current().span.start;
+ let value = self.quoted()?;
+- if value.len() != 36
+- || ![8, 13, 18, 23]
+- .into_iter()
+- .all(|i| value.as_bytes()[i] == b'-')
+- {
++ if !has_uuid_header_shape(&value) {
+ return Err(Error {
+ code: "invalid_source",
+ offset,
+ message: "Expected UUID in 8-4-4-4-12 hexadecimal form.".into(),
+@@ -1961,29 +2032,74 @@
+ }
+
+ /// Analyze an incomplete source buffer without adopting a candidate. Declaration
+ /// spans remain available even when a later declaration contains errors.
++/// Invalid compact/braced/URN header IDs retain no authored identity, matching
++/// the declaration parser rather than the UUID crate's broader accepted forms.
+ pub fn analyze(source: &str) -> Analysis {
+ let tokens = tokenize(source);
++ let (declarations, _) =
++ declaration_outline(source, &tokens, usize::MAX).expect("unlimited declaration walk");
++ Analysis {
++ tokens,
++ diagnostics: lint(source),
++ declarations: declarations.into_iter().map(|d| d.declaration).collect(),
++ }
++}
++
++/// Read exact current text without lint, parsing a model, stabilization or I/O.
++/// Source bytes are admitted before token allocation; token/declaration limits
++/// are checked before lexing the next token or materializing the next record.
++pub fn outline(source: &str, limits: OutlineLimits) -> Result, OutlineLimit> {
++ if source.len() > limits.max_source_bytes {
++ return Err(OutlineLimit {
++ resource: OutlineResource::SourceBytes,
++ limit: limits.max_source_bytes,
++ observed_at_least: source.len(),
++ offset: 0,
++ });
++ }
++ let tokens = tokenize_with_limit(source, limits.max_tokens)?;
++ let has_lexical_errors = tokens.iter().any(|token| token.class == TokenClass::Error);
++ let (declarations, has_unmatched_braces) =
++ declaration_outline(source, &tokens, limits.max_declarations)?;
++ Ok(Outline {
++ source,
++ tokens,
++ declarations,
++ has_lexical_errors,
++ has_unmatched_braces,
++ })
++}
++
++fn declaration_outline(
++ source: &str,
++ tokens: &[TokenSpan],
++ max_declarations: usize,
++) -> Result<(Vec, bool), OutlineLimit> {
+ let significant = tokens
+ .iter()
+ .filter(|t| !matches!(t.class, TokenClass::Whitespace | TokenClass::Comment))
+ .collect::>();
+ // Recover matched body bounds once. Scanning each aggregate's descendants
+ // again would make tolerant navigation quadratic on deeply nested drafts.
+ let mut body_ends = vec![None; significant.len()];
+ let mut bodies = Vec::new();
++ let mut unmatched_close = false;
+ for (index, token) in significant.iter().enumerate() {
+ match &source[token.span.start..token.span.end] {
+ "{" => bodies.push(index),
+ "}" => {
+ if let Some(open) = bodies.pop() {
+ body_ends[open] = Some(token.span.end);
++ } else {
++ unmatched_close = true;
+ }
+ }
+ _ => {}
+ }
+ }
++ let has_unmatched_braces = unmatched_close || !bodies.is_empty();
+ let mut declarations = Vec::new();
+ for (i, t) in significant.iter().enumerate() {
+ let word = &source[t.span.start..t.span.end];
+ if declaration_kind(word).is_none() && word != "project" {
+@@ -2006,48 +2122,69 @@
+ continue;
+ }
+ j += 1;
+ }
++ if declarations.len() == max_declarations {
++ return Err(OutlineLimit {
++ resource: OutlineResource::Declarations,
++ limit: max_declarations,
++ observed_at_least: declarations.len().saturating_add(1),
++ offset: t.span.start,
++ });
++ }
+ let mut id = None;
++ let mut id_literal_span = None;
+ if significant
+ .get(j)
+ .is_some_and(|t| &source[t.span.start..t.span.end] == "id")
+ {
+ if let Some(value) = significant.get(j + 1)
+ && value.class == TokenClass::String
+ {
++ id_literal_span = Some(value.span);
+ let text =
+ serde_json::from_str::(&source[value.span.start..value.span.end])
+ .unwrap();
+- id = Id::parse_str(&text).ok();
++ if has_uuid_header_shape(&text) {
++ id = Id::parse_str(&text).ok();
++ }
+ }
+ j += 2;
+ }
+- let mut end = significant
+- .get(j)
+- .map_or(alias_token.span.end, |t| t.span.end);
++ // A recognized header ID at EOF must remain inside the whole span.
++ // Keep the old next-token/body behavior; only the EOF fallback changes.
++ let header_end = significant
++ .get(j.saturating_sub(1))
++ .or_else(|| significant.last())
++ .map_or(alias_token.span.end, |token| token.span.end);
++ let mut end = significant.get(j).map_or(header_end, |t| t.span.end);
+ if significant
+ .get(j)
+ .is_some_and(|t| &source[t.span.start..t.span.end] == "{")
+ {
+ end = body_ends[j].unwrap_or(source.len());
+ }
+- declarations.push(DeclarationSpan {
+- alias: alias.into(),
+- id,
+- kind: word.into(),
+- span: Span {
+- start: t.span.start,
+- end,
++ declarations.push(OutlineDeclaration {
++ declaration: DeclarationSpan {
++ alias: alias.into(),
++ id,
++ kind: word.into(),
++ span: Span {
++ start: t.span.start,
++ end,
++ },
+ },
++ kind_span: t.span,
++ alias_span: alias_token.span,
++ id_literal_span,
+ });
+ }
+- Analysis {
+- tokens,
+- diagnostics: lint(source),
+- declarations,
+- }
+-}
++ Ok((declarations, has_unmatched_braces))
++}
++
++#[cfg(test)]
++#[path = "language_outline_tests.rs"]
++mod outline_tests;
+
+ #[cfg(test)]
+ #[path = "language_nesting_tests.rs"]
+ mod nesting_tests;
+@@ -2441,13 +2578,9 @@
+ let offset = self.current().offset;
+ let value = self.quoted()?;
+ // Require the portable 8-4-4-4-12 spelling, not the UUID crate's URN,
+ // braces, or compact alternatives. Uppercase hexadecimal is accepted.
+- if value.len() != 36
+- || ![8, 13, 18, 23]
+- .into_iter()
+- .all(|index| value.as_bytes()[index] == b'-')
+- {
++ if !super::has_uuid_header_shape(&value) {
+ return Err(Error {
+ code: "invalid_source",
+ offset,
+ message: "Expected a UUID string in 8-4-4-4-12 hexadecimal form.".into(),
+--- /dev/null
++++ b/crates/oa-core/src/language_outline_tests.rs
+@@ -0,0 +1,271 @@
++//! Prepared outline tests. Explicit compatibility tests additionally exercise
++//! the existing parsers/analyze; the outline entry itself never calls them.
++use super::*;
++
++fn limits() -> OutlineLimits {
++ OutlineLimits {
++ max_source_bytes: 1024 * 1024,
++ max_tokens: 100_000,
++ max_declarations: 1_000,
++ }
++}
++
++fn declaration<'a>(result: &'a Outline<'_>, alias: &str) -> &'a OutlineDeclaration {
++ result.declarations.iter().find(|d| d.declaration.alias == alias).unwrap()
++}
++
++fn text(source: &str, span: Span) -> &str {
++ &source[span.start..span.end]
++}
++
++#[test]
++fn outline_roles_preserve_unicode_crlf_and_exact_escaped_uuid_literal() {
++ let source = "// 検査 🚒\r\noa_actor responder \"救助\" id \"\\u00300000000-0000-0000-0000-000000000001\" {}\r\nsource @ // 使用\r\n responder;";
++ let before = source.as_bytes().to_vec();
++ let result = outline(source, limits()).unwrap();
++ assert_eq!(result.source.as_bytes(), before);
++ assert_eq!(result.source.as_ptr(), source.as_ptr());
++ let d = declaration(&result, "responder");
++ assert_eq!(text(source, d.kind_span), "oa_actor");
++ assert_eq!(text(source, d.alias_span), "responder");
++ assert_eq!(text(source, d.id_literal_span.unwrap()), "\"\\u00300000000-0000-0000-0000-000000000001\"");
++ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
++ assert_eq!(d.declaration.span.start, source.find("oa_actor").unwrap());
++ assert_eq!(d.declaration.span.end, source.find("{}\r\n").unwrap() + 2);
++ let mut end = 0;
++ for token in &result.tokens {
++ assert_eq!(token.span.start, end);
++ assert!(token.span.start < token.span.end);
++ assert!(source.is_char_boundary(token.span.start));
++ assert!(source.is_char_boundary(token.span.end));
++ end = token.span.end;
++ }
++ assert_eq!(end, source.len());
++ assert!(!result.has_lexical_errors);
++ assert!(!result.has_unmatched_braces);
++}
++
++#[test]
++fn closed_id_at_eof_is_inside_the_declaration_extent() {
++ for source in [
++ "oa_actor a \"Actor\" id \"00000000-0000-0000-0000-000000000001\"",
++ "node placed id \"00000000-0000-0000-0000-000000000001\"",
++ ] {
++ let result = outline(source, limits()).unwrap();
++ let d = &result.declarations[0];
++ let literal = d.id_literal_span.unwrap();
++ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
++ assert_eq!(d.declaration.span.end, source.len());
++ assert!(d.declaration.span.start <= literal.start);
++ assert!(literal.end <= d.declaration.span.end);
++ }
++ for source in ["oa_actor a \"Actor\"", "oa_actor a \"Actor\" id"] {
++ let result = outline(source, limits()).unwrap();
++ let d = &result.declarations[0];
++ assert_eq!(d.declaration.span.end, source.len());
++ assert_eq!(d.declaration.id, None);
++ assert_eq!(d.id_literal_span, None);
++ }
++}
++
++#[test]
++fn missing_bad_and_historical_ids_never_allocate_authored_identity() {
++ let source = "oa_actor missing \"No ID\" {}\noa_actor bad \"Bad ID\" id \"not-a-uuid\" {}\noa_actor historical \"00000000-0000-0000-0000-000000000001\" { history uuid \"00000000-0000-0000-0000-000000000001\"; }";
++ let first = outline(source, limits()).unwrap();
++ let second = outline(source, limits()).unwrap();
++ assert_eq!(first, second);
++ assert!(first.declarations.iter().all(|d| d.declaration.id.is_none()));
++ assert_eq!(declaration(&first, "missing").id_literal_span, None);
++ assert_eq!(declaration(&first, "historical").id_literal_span, None);
++ assert_eq!(text(source, declaration(&first, "bad").id_literal_span.unwrap()), "\"not-a-uuid\"");
++ // Balanced tokens are not a successful syntax/semantic validation claim.
++ assert!(!first.has_lexical_errors);
++ assert!(!first.has_unmatched_braces);
++}
++
++#[test]
++fn invalid_full_scan_keeps_closed_children_and_reports_lexical_body_facts() {
++ let source = "oa_entity outer \"Outer\" {\n oa_actor ready \"Ready\" { description \"} stays text\"; }\n oa_actor pending \"Pending\" { description \"unfinished 🌊";
++ let result = outline(source, limits()).unwrap();
++ assert_eq!(result.source, source);
++ assert_eq!(result.tokens.last().unwrap().span.end, source.len());
++ assert!(result.has_lexical_errors);
++ assert!(result.has_unmatched_braces);
++ let ready = declaration(&result, "ready");
++ assert_eq!(text(source, ready.declaration.span), "oa_actor ready \"Ready\" { description \"} stays text\"; }");
++ assert_eq!(declaration(&result, "outer").declaration.span.end, source.len());
++ assert_eq!(declaration(&result, "pending").declaration.span.end, source.len());
++ let unmatched_close = outline("}\n// brace in comment {\n", limits()).unwrap();
++ assert!(unmatched_close.has_unmatched_braces);
++ assert!(!unmatched_close.has_lexical_errors);
++}
++
++#[test]
++fn missing_body_syntax_is_unchecked_even_when_known_error_flags_are_clear() {
++ let source = "oa_actor a \"A\" id \"00000000-0000-0000-0000-000000000001\"";
++ let result = outline(source, limits()).unwrap();
++ assert_eq!(result.declarations.len(), 1);
++ assert!(!result.has_lexical_errors);
++ assert!(!result.has_unmatched_braces);
++ // There is deliberately no `valid` flag: the known-complete fact is scanning
++ // these bytes, not the missing body becoming legal or the draft being applied.
++ assert_eq!(result.source, source);
++}
++
++#[test]
++fn keyword_alias_and_collisions_keep_every_source_ordered_header() {
++ let source = "project root_model \"Model\" {\n oa_actor oa_activity \"First\" id \"00000000-0000-0000-0000-000000000001\" {}\n oa_actor oa_activity \"Second\" id \"00000000-0000-0000-0000-000000000001\" {}\n diagram view_one \"View\" {}\n node placement { diagram @view_one; }\n}";
++ let result = outline(source, limits()).unwrap();
++ assert_eq!(result.declarations.len(), 5);
++ let duplicates: Vec<_> = result.declarations.iter().filter(|d| d.declaration.alias == "oa_activity").collect();
++ assert_eq!(duplicates.len(), 2);
++ assert!(duplicates[0].alias_span.start < duplicates[1].alias_span.start);
++ assert_eq!(duplicates[0].declaration.id, duplicates[1].declaration.id);
++ assert!(result.tokens.iter().any(|t| t.span == duplicates[0].alias_span && t.class == TokenClass::Keyword));
++ assert_eq!(text(source, declaration(&result, "placement").kind_span), "node");
++ assert_eq!(declaration(&result, "placement").declaration.id, None);
++}
++
++#[test]
++fn bounds_refuse_before_returning_a_partial_outline() {
++ let source = "oa_actor a \"A\" {}\noa_actor b \"B\" {}";
++ let admitted = outline(source, limits()).unwrap();
++ let exact = OutlineLimits {
++ max_source_bytes: source.len(),
++ max_tokens: admitted.tokens.len(),
++ max_declarations: 2,
++ };
++ assert_eq!(outline(source, exact).unwrap(), admitted);
++ for resource in [OutlineResource::SourceBytes, OutlineResource::Tokens, OutlineResource::Declarations] {
++ let mut lower = exact;
++ match resource {
++ OutlineResource::SourceBytes => lower.max_source_bytes -= 1,
++ OutlineResource::Tokens => lower.max_tokens -= 1,
++ OutlineResource::Declarations => lower.max_declarations -= 1,
++ }
++ let error = outline(source, lower).unwrap_err();
++ assert_eq!(error.resource, resource);
++ assert!(error.observed_at_least > error.limit);
++ assert!(source.is_char_boundary(error.offset));
++ if resource == OutlineResource::Declarations {
++ assert_eq!(error.offset, source.find("oa_actor b").unwrap());
++ }
++ }
++ let empty = outline("", OutlineLimits { max_source_bytes: 0, max_tokens: 0, max_declarations: 0 }).unwrap();
++ assert!(empty.tokens.is_empty() && empty.declarations.is_empty());
++ let unicode = "// 🌊\r\noa_actor a \"A\" {}";
++ let error = outline(unicode, OutlineLimits { max_source_bytes: 4, ..limits() }).unwrap_err();
++ assert_eq!(error.resource, OutlineResource::SourceBytes);
++ assert_eq!(error.offset, 0);
++ let error = outline(unicode, OutlineLimits { max_tokens: 1, ..limits() }).unwrap_err();
++ assert_eq!(error.resource, OutlineResource::Tokens);
++ assert_eq!(error.offset, unicode.find('\n').unwrap());
++}
++
++#[test]
++fn outline_ranges_follow_current_bytes_without_identity_stabilization() {
++ let old = "oa_actor a \"A\" {}\nsource @a;";
++ let current = format!("// 航海 🚒\r\n{old}");
++ let old_outline = outline(old, limits()).unwrap();
++ let current_outline = outline(¤t, limits()).unwrap();
++ let shift = current.len() - old.len();
++ let old_role = &old_outline.declarations[0];
++ let current_role = ¤t_outline.declarations[0];
++ assert_eq!(current_role.alias_span.start, old_role.alias_span.start + shift);
++ assert_eq!(current_role.kind_span.start, old_role.kind_span.start + shift);
++ assert_eq!(current_role.declaration.span.end, old_role.declaration.span.end + shift);
++ assert_eq!(current_outline.source, current);
++ assert_eq!(old_outline.source, old);
++ assert_eq!(current_role.declaration.id, None);
++ assert!(!current_outline.source.contains(" id "));
++ // Workspace revision/stale-handle rejection belongs to the query/adapter,
++ // not a claim that an old owned span can be applied to any other string.
++}
++
++#[test]
++fn deeply_nested_bounds_use_the_same_iterative_walk_and_declaration_budget() {
++ let mut source = String::new();
++ for index in 0..600 {
++ source.push_str(&format!("oa_activity work_{index} \"Work\" {{\n"));
++ }
++ source.push_str(&"}\n".repeat(600));
++ let result = outline(&source, limits()).unwrap();
++ assert_eq!(result.declarations.len(), 600);
++ assert!(!result.has_unmatched_braces);
++ assert_eq!(result.declarations[0].declaration.span.end, source.len() - 1);
++ let error = outline(&source, OutlineLimits { max_declarations: 599, ..limits() }).unwrap_err();
++ assert_eq!(error.resource, OutlineResource::Declarations);
++ assert_eq!(error.observed_at_least, 600);
++ assert_eq!(error.offset, source.find("oa_activity work_599").unwrap());
++}
++
++#[test]
++fn noncanonical_uuid_spellings_have_no_outline_or_analysis_identity() {
++ for raw in [
++ "00000000000000000000000000000001",
++ "{00000000-0000-0000-0000-000000000001}",
++ "urn:uuid:00000000-0000-0000-0000-000000000001",
++ ] {
++ assert!(Id::parse_str(raw).is_ok(), "The UUID crate alone is too permissive");
++ assert!(!has_uuid_header_shape(raw));
++ let literal = format!("\"{raw}\"");
++ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
++ let before = modern.clone();
++ let result = outline(&modern, limits()).unwrap();
++ let d = declaration(&result, "model");
++ assert_eq!(d.declaration.id, None);
++ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
++ assert_eq!(modern, before);
++ let errors = parse(&modern).unwrap_err();
++ assert!(errors.iter().any(|error| error.message.contains("8-4-4-4-12")
++ && error.span.start == modern.find(&literal).unwrap()));
++ // Intentional compatibility correction: old analyze yielded Some(Id(1))
++ // here despite lint rejecting the header. It now shares outline's None.
++ let analysis = analyze(&modern);
++ assert_eq!(analysis.declarations.iter().find(|d| d.alias == "model").unwrap().id, None);
++ assert!(analysis.diagnostics.iter().any(|error| error.message.contains("8-4-4-4-12")));
++ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
++ let legacy_errors = legacy::parse(&legacy_source).unwrap_err();
++ assert!(legacy_errors.iter().any(|error| error.message.contains("8-4-4-4-12")));
++ }
++}
++
++#[test]
++fn uppercase_and_json_escaped_header_ids_survive_both_parsers_and_outline() {
++ let expected = Id::from_u128(0xabcdef);
++ let canonical = expected.to_string();
++ let uppercase = canonical.to_uppercase();
++ assert_ne!(uppercase, canonical);
++ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
++ let literal = format!("\"{raw}\"");
++ let decoded: String = serde_json::from_str(&literal).unwrap();
++ assert!(has_uuid_header_shape(&decoded));
++ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
++ let result = outline(&modern, limits()).unwrap();
++ let d = declaration(&result, "model");
++ assert_eq!(d.declaration.id, Some(expected));
++ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
++ assert_eq!(result.source.as_bytes(), modern.as_bytes());
++ assert_eq!(parse(&modern).unwrap().id, expected);
++ let analysis = analyze(&modern);
++ assert_eq!(analysis.declarations[0].id, Some(expected));
++ assert!(!analysis.diagnostics.iter().any(|error| error.severity == crate::validation::Severity::Error));
++ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
++ assert_eq!(legacy::parse(&legacy_source).unwrap().id, expected);
++ }
++}
++
++#[test]
++fn header_shape_still_requires_valid_hexadecimal_uuid_data() {
++ let raw = "00000000-0000-0000-0000-00000000000Z";
++ assert!(has_uuid_header_shape(raw));
++ assert!(Id::parse_str(raw).is_err());
++ let literal = format!("\"{raw}\"");
++ let source = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
++ let result = outline(&source, limits()).unwrap();
++ let d = declaration(&result, "model");
++ assert_eq!(d.declaration.id, None);
++ assert_eq!(text(&source, d.id_literal_span.unwrap()), literal);
++ assert!(parse(&source).unwrap_err().iter().any(|error| error.message == "Invalid UUID identity."));
++ assert_eq!(analyze(&source).declarations[0].id, None);
++}
diff --git a/harness/experiments/text-navigation-components-20260911/component-inputs/query-admission-v2.patch b/harness/experiments/text-navigation-components-20260911/component-inputs/query-admission-v2.patch
new file mode 100644
index 00000000..f4ed97db
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/component-inputs/query-admission-v2.patch
@@ -0,0 +1,129 @@
+--- a/.local/text-navigation-probe-20260911/source_symbols.rs
++++ b/.local/text-navigation-probe-20260911/source_symbols.rs
+@@ -7,9 +7,9 @@
+ //! Its matches are authored lexical uses, not typed semantic dependency edges.
+
+ use oa_core::language::{Span, TokenClass, TokenSpan};
+ use oa_core::Id;
+-use std::collections::BTreeMap;
++use std::collections::{BTreeMap, BTreeSet};
+ use std::ops::Range;
+
+ /// Fresh editor/workspace session identity plus a revision changed for EVERY
+ /// indexed input change, including membership, outline and validation evidence.
+@@ -357,16 +357,19 @@
+ || files.iter().any(|file| file.membership == Membership::Unverified))
+ {
+ return Err(input_error(0, None, InputProblem::InconsistentCompleteScope));
+ }
++ // Scratch storage is allocated only after aggregate file/path admission.
++ // Each admitted path is folded once, retaining core's Unicode policy.
++ let mut folded_paths = BTreeSet::new();
+ for (file_index, file) in files.iter().copied().enumerate() {
+ if file.syntax != SyntaxCoverage::FullFile {
+ return Err(input_error(file_index, None, InputProblem::IncompleteSyntax));
+ }
+ if oa_core::source_project::resolve_include("", file.path).as_deref() != Ok(file.path) {
+ return Err(input_error(file_index, None, InputProblem::NonCanonicalPath));
+ }
+- if files[..file_index].iter().any(|other| other.path.eq_ignore_ascii_case(file.path)) {
++ if !folded_paths.insert(file.path.to_lowercase()) {
+ return Err(input_error(file_index, None, InputProblem::DuplicateOrCaseCollidingPath));
+ }
+ let mut end = 0;
+ for (token_index, token) in file.tokens.iter().enumerate() {
+@@ -417,8 +420,9 @@
+ }
+ let literal = &file.text[identity.literal_span.start..identity.literal_span.end];
+ let decoded = serde_json::from_str::(literal)
+ .ok()
++ .filter(|value| oa_core::language::has_uuid_header_shape(value))
+ .and_then(|value| Id::parse_str(&value).ok());
+ if decoded != Some(identity.id) {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+@@ -1005,8 +1009,84 @@
+ }
+ }
+
+ #[test]
++ fn unicode_path_collisions_match_core_lowercase_policy() {
++ for (first_path, second_path) in [("Ä.arcadia", "ä.arcadia"), ("ΟΣ/model.arcadia", "ος/model.arcadia")] {
++ let first = Fixture::new(first_path, "source @a;".into(), &[]);
++ let second = Fixture::new(second_path, "source @b;".into(), &[]);
++ assert_eq!(first_path.to_lowercase(), second_path.to_lowercase());
++ assert!(!first_path.eq_ignore_ascii_case(second_path));
++ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
++ }
++ }
++
++ #[test]
++ fn noncanonical_header_identity_cannot_enter_through_query_metadata() {
++ for raw in [
++ "00000000000000000000000000000001",
++ "{00000000-0000-0000-0000-000000000001}",
++ "urn:uuid:00000000-0000-0000-0000-000000000001",
++ ] {
++ let literal = format!("\"{raw}\"");
++ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
++ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
++ let start = source.find(&literal).unwrap();
++ // This fixture has one explicit empty body. A brace inside the
++ // quoted UUID must not become the test's declaration end.
++ let body_start = source.find("{}\n").unwrap();
++ fixture.declarations[0].span.end = body_start + 2;
++ assert!(start + literal.len() <= body_start);
++ // An adapter passes None from the corrected outline; lexical
++ // definition/reference navigation survives without an ID anchor.
++ {
++ let admitted = index(&[fixture.included()]);
++ let d = definition(&admitted, "a");
++ assert_eq!(admitted.identity(d), Ok(Identity::Missing));
++ assert_eq!(admitted.references_to(d, Domain::Included).unwrap().count(), 1);
++ assert_eq!(admitted.source(0).unwrap().text, source);
++ }
++ // A forged/stale caller cannot put the UUID crate's permissive
++ // interpretation back into an authored anchor via the raw API.
++ fixture.declarations[0].identity = Some(AuthoredIdentity {
++ id: A,
++ literal_span: Span { start, end: start + literal.len() },
++ });
++ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
++ }
++ }
++
++ #[test]
++ fn query_header_admission_preserves_uppercase_and_json_escape_bytes() {
++ let expected = Id::from_u128(0xabcdef);
++ let uppercase = expected.to_string().to_uppercase();
++ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
++ let literal = format!("\"{raw}\"");
++ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
++ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
++ let start = source.find(&literal).unwrap();
++ fixture.declarations[0].identity = Some(AuthoredIdentity {
++ id: expected,
++ literal_span: Span { start, end: start + literal.len() },
++ });
++ let admitted = index(&[fixture.included()]);
++ assert_eq!(anchor(&admitted, definition(&admitted, "a")).id(), expected);
++ assert_eq!(admitted.source(0).unwrap().text.as_bytes(), source.as_bytes());
++ }
++ }
++
++ #[test]
++ fn admitted_unicode_path_budget_keeps_original_policy_after_single_fold() {
++ let fixture = Fixture::new("İ.arcadia", String::new(), &[]);
++ assert!(fixture.path.to_lowercase().len() > fixture.path.len());
++ let exact = Limits { files: 1, source_bytes: 0, path_bytes: fixture.path.len(), tokens: 0, records: 0 };
++ let admitted = Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).unwrap();
++ assert_eq!(admitted.source(0).unwrap().path, "İ.arcadia");
++ // PathBytes continues to count authored input. One lowercase key's
++ // Unicode expansion is bounded scratch, not a new path rejection rule.
++ }
++
++ #[test]
+ fn every_budget_admits_boundary_and_returns_no_index_one_below() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a; target @a;".into(), &[("oa_actor", "a", None)]);
+ let exact = Limits { files: 1, source_bytes: fixture.text.len(), path_bytes: fixture.path.len(), tokens: fixture.tokens.len(), records: 3 };
+ assert!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).is_ok());
diff --git a/harness/experiments/text-navigation-components-20260911/component-inputs/source_symbols-baseline.rs b/harness/experiments/text-navigation-components-20260911/component-inputs/source_symbols-baseline.rs
new file mode 100644
index 00000000..1d1387bd
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/component-inputs/source_symbols-baseline.rs
@@ -0,0 +1,1043 @@
+//! Uncompiled experiment: current-text symbol queries, with no model or editor mutation.
+//!
+//! The caller supplies one immutable snapshot of current text, full lexer tokens,
+//! and syntax-only declaration role spans. This module does not lex, parse
+//! Arcadia declarations, lint, compile, allocate model IDs, discover includes,
+//! or read an accepted Project.
+//! Its matches are authored lexical uses, not typed semantic dependency edges.
+
+use oa_core::language::{Span, TokenClass, TokenSpan};
+use oa_core::Id;
+use std::collections::BTreeMap;
+use std::ops::Range;
+
+/// Fresh editor/workspace session identity plus a revision changed for EVERY
+/// indexed input change, including membership, outline and validation evidence.
+/// Reusing this key for different input violates the caller contract.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SnapshotKey {
+ pub workspace: Id,
+ pub revision: u64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Membership {
+ Included,
+ Detached,
+ Unverified,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Evidence {
+ /// The caller verified these exact bytes in the current source scope.
+ Validated,
+ /// Current editor bytes; no successful semantic validation is claimed.
+ Draft,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SyntaxCoverage {
+ /// The tolerant walk considered the entire file, even if its text is invalid.
+ FullFile,
+ /// A stopped/budget-limited outline cannot masquerade as a complete result.
+ Incomplete,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PartialReason {
+ IncludesUnverified,
+ MissingFiles,
+ UnappliedWorkspace,
+ UnsupportedBindings,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Scope {
+ CompleteProject,
+ Partial(PartialReason),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Domain {
+ Included,
+ Loaded,
+}
+
+impl Domain {
+ fn contains(self, membership: Membership) -> bool {
+ self == Self::Loaded || membership == Membership::Included
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredIdentity {
+ pub id: Id,
+ /// Whole quoted JSON string token in the declaration header, not a UUID
+ /// mentioned in a body, history record, comment or display name.
+ pub literal_span: Span,
+}
+
+/// These header roles must come from the coordinated core outline seam. The
+/// existing public DeclarationSpan alone lacks alias/kind/ID literal spans.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DeclarationInput {
+ pub span: Span,
+ pub kind_span: Span,
+ pub alias_span: Span,
+ pub identity: Option,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct FileInput<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ /// Exact lexer output for text, in order and covering the entire file.
+ pub tokens: &'a [TokenSpan],
+ /// Tolerant outline in alias-source order. Whole spans may nest.
+ pub declarations: &'a [DeclarationInput],
+ pub syntax: SyntaxCoverage,
+ pub membership: Membership,
+ pub evidence: Evidence,
+}
+
+/// Explicit experiment limits; production must select its own shared budgets.
+/// Caller-owned text/tokens/outline must already have bounded extraction.
+#[derive(Debug, Clone, Copy)]
+pub struct Limits {
+ pub files: usize,
+ pub source_bytes: usize,
+ pub path_bytes: usize,
+ pub tokens: usize,
+ pub records: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Resource {
+ Files,
+ SourceBytes,
+ PathBytes,
+ Tokens,
+ Records,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum InputProblem {
+ NonCanonicalPath,
+ DuplicateOrCaseCollidingPath,
+ InconsistentCompleteScope,
+ IncompleteSyntax,
+ TokenCoverage,
+ TokenBounds,
+ DeclarationBounds,
+ DeclarationOrder,
+ HeaderRoleToken,
+ InvalidAlias,
+ IdentityLiteral,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum BuildError {
+ /// No partial Index is returned. This is not a zero-result query.
+ Limit {
+ resource: Resource,
+ limit: usize,
+ observed_at_least: usize,
+ },
+ Input {
+ file: usize,
+ record: Option,
+ problem: InputProblem,
+ },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Location {
+ pub file: usize,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DefinitionHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ReferenceHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+/// Carries only an authored identity, never an identity generated by compilation.
+/// It may be resolved across revisions of the same workspace. Resolution still
+/// checks global loaded-file uniqueness every time.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredAnchor {
+ workspace: Id,
+ id: Id,
+}
+
+impl AuthoredAnchor {
+ pub fn id(self) -> Id {
+ self.id
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Identity {
+ Missing,
+ Unique(AuthoredAnchor),
+ Collision { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Lookup {
+ Missing,
+ Unique(DefinitionHandle),
+ Ambiguous { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Hit {
+ Definition(DefinitionHandle),
+ Reference(ReferenceHandle),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum QueryError {
+ StaleSnapshot,
+ WrongWorkspace,
+ InvalidHandle,
+ InvalidFile,
+ InvalidBytePosition,
+ OutsideDomain,
+ AmbiguousAlias { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct QueryScope {
+ pub declared: Scope,
+ pub domain: Domain,
+ /// True only for the complete included project, not loaded detached files.
+ pub project_complete: bool,
+ pub all_selected_files_validated: bool,
+ pub selected_files: usize,
+ pub detached_files: usize,
+ pub unverified_files: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Position {
+ pub line: usize,
+ /// Zero-based UTF-8 byte column. The editor adapter must convert explicitly
+ /// if it needs character/UTF-16 columns; CRLF bytes are never normalized.
+ pub byte_column: usize,
+}
+
+#[derive(Debug)]
+pub struct Definition<'a> {
+ pub alias: &'a str,
+ pub kind: &'a str,
+ pub declaration: Location,
+ pub alias_location: Location,
+ pub authored_id: Option,
+}
+
+#[derive(Debug)]
+pub struct Reference<'a> {
+ pub alias: &'a str,
+ pub at: Location,
+ pub alias_location: Location,
+}
+
+#[derive(Debug)]
+pub struct SourceFile<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ pub membership: Membership,
+ pub evidence: Evidence,
+ definitions: Range,
+ references: Range,
+}
+
+#[derive(Debug)]
+pub struct Index<'a> {
+ snapshot: SnapshotKey,
+ scope: Scope,
+ files: Vec>,
+ definitions: Vec>,
+ references: Vec>,
+ aliases: BTreeMap<&'a str, Vec>,
+ uses: BTreeMap<&'a str, Vec>,
+ identities: BTreeMap>,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Counts {
+ definitions: usize,
+ references: usize,
+}
+
+fn charge(total: &mut usize, value: usize, limit: usize, resource: Resource) -> Result<(), BuildError> {
+ let next = total.checked_add(value).ok_or(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: usize::MAX,
+ })?;
+ if next > limit {
+ return Err(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: next,
+ });
+ }
+ *total = next;
+ Ok(())
+}
+
+fn input_error(file: usize, record: Option, problem: InputProblem) -> BuildError {
+ BuildError::Input { file, record, problem }
+}
+
+fn valid_span(text: &str, span: Span) -> bool {
+ span.start < span.end
+ && span.end <= text.len()
+ && text.is_char_boundary(span.start)
+ && text.is_char_boundary(span.end)
+}
+
+fn contains(outer: Span, inner: Span) -> bool {
+ outer.start <= inner.start && inner.end <= outer.end
+}
+
+fn token_at(tokens: &[TokenSpan], span: Span) -> Option {
+ let index = tokens.binary_search_by_key(&span.start, |token| token.span.start).ok()?;
+ let token = tokens[index];
+ (token.span == span).then_some(token)
+}
+
+fn word(class: TokenClass) -> bool {
+ matches!(class, TokenClass::Identifier | TokenClass::Keyword)
+}
+
+/// Only a live punctuation @ followed by a legal alias outside trivia qualifies.
+/// Trivia can separate @ and alias, but its bytes never become a clickable hit.
+fn reference_spans<'a>(file: FileInput<'a>) -> impl Iterator- + 'a {
+ file.tokens.iter().enumerate().filter_map(move |(index, token)| {
+ if token.class != TokenClass::Punctuation || &file.text[token.span.start..token.span.end] != "@" {
+ return None;
+ }
+ let next = file.tokens[index + 1..].iter().find(|next| {
+ !matches!(next.class, TokenClass::Whitespace | TokenClass::Comment)
+ })?;
+ let alias = &file.text[next.span.start..next.span.end];
+ (word(next.class) && oa_core::valid_alias(alias)).then_some((token.span, next.span))
+ })
+}
+
+/// Runs all aggregate limits and structural checks before allocating Index maps
+/// or vectors. Path normalization/quoted-ID decoding may allocate bounded scratch
+/// strings; they cannot scale beyond the admitted input bytes.
+fn preflight(files: &[FileInput<'_>], scope: Scope, limits: Limits) -> Result
{
+ let mut file_count = 0;
+ charge(&mut file_count, files.len(), limits.files, Resource::Files)?;
+ let mut bytes = 0;
+ let mut path_bytes = 0;
+ let mut tokens = 0;
+ let mut records = 0;
+ for file in files {
+ charge(&mut bytes, file.text.len(), limits.source_bytes, Resource::SourceBytes)?;
+ charge(&mut path_bytes, file.path.len(), limits.path_bytes, Resource::PathBytes)?;
+ charge(&mut tokens, file.tokens.len(), limits.tokens, Resource::Tokens)?;
+ charge(&mut records, file.declarations.len(), limits.records, Resource::Records)?;
+ }
+ let definitions = records;
+ if scope == Scope::CompleteProject
+ && (!files.iter().any(|file| file.membership == Membership::Included)
+ || files.iter().any(|file| file.membership == Membership::Unverified))
+ {
+ return Err(input_error(0, None, InputProblem::InconsistentCompleteScope));
+ }
+ for (file_index, file) in files.iter().copied().enumerate() {
+ if file.syntax != SyntaxCoverage::FullFile {
+ return Err(input_error(file_index, None, InputProblem::IncompleteSyntax));
+ }
+ if oa_core::source_project::resolve_include("", file.path).as_deref() != Ok(file.path) {
+ return Err(input_error(file_index, None, InputProblem::NonCanonicalPath));
+ }
+ if files[..file_index].iter().any(|other| other.path.eq_ignore_ascii_case(file.path)) {
+ return Err(input_error(file_index, None, InputProblem::DuplicateOrCaseCollidingPath));
+ }
+ let mut end = 0;
+ for (token_index, token) in file.tokens.iter().enumerate() {
+ if !valid_span(file.text, token.span) {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenBounds));
+ }
+ if token.span.start != end {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenCoverage));
+ }
+ end = token.span.end;
+ }
+ if end != file.text.len() {
+ return Err(input_error(file_index, None, InputProblem::TokenCoverage));
+ }
+ let mut previous_alias_end = 0;
+ for (declaration_index, declaration) in file.declarations.iter().enumerate() {
+ let problem = |problem| input_error(file_index, Some(declaration_index), problem);
+ if !valid_span(file.text, declaration.span)
+ || !valid_span(file.text, declaration.kind_span)
+ || !valid_span(file.text, declaration.alias_span)
+ || declaration.kind_span.start != declaration.span.start
+ || !contains(declaration.span, declaration.kind_span)
+ || !contains(declaration.span, declaration.alias_span)
+ || declaration.alias_span.start < declaration.kind_span.end
+ {
+ return Err(problem(InputProblem::DeclarationBounds));
+ }
+ if declaration.alias_span.start < previous_alias_end {
+ return Err(problem(InputProblem::DeclarationOrder));
+ }
+ previous_alias_end = declaration.alias_span.end;
+ if !token_at(file.tokens, declaration.kind_span).is_some_and(|token| word(token.class))
+ || !token_at(file.tokens, declaration.alias_span).is_some_and(|token| word(token.class))
+ {
+ return Err(problem(InputProblem::HeaderRoleToken));
+ }
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ if !oa_core::valid_alias(alias) {
+ return Err(problem(InputProblem::InvalidAlias));
+ }
+ if let Some(identity) = declaration.identity {
+ if !valid_span(file.text, identity.literal_span)
+ || !contains(declaration.span, identity.literal_span)
+ || identity.literal_span.start < declaration.alias_span.end
+ || token_at(file.tokens, identity.literal_span).map(|token| token.class) != Some(TokenClass::String)
+ {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ let literal = &file.text[identity.literal_span.start..identity.literal_span.end];
+ let decoded = serde_json::from_str::(literal)
+ .ok()
+ .and_then(|value| Id::parse_str(&value).ok());
+ if decoded != Some(identity.id) {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ }
+ }
+ // Token bounds and coverage are proven before reference_spans slices.
+ for _ in reference_spans(file) {
+ charge(&mut records, 1, limits.records, Resource::Records)?;
+ }
+ }
+ Ok(Counts { definitions, references: records - definitions })
+}
+
+impl<'a> Index<'a> {
+ pub fn build(
+ snapshot: SnapshotKey,
+ files: &[FileInput<'a>],
+ scope: Scope,
+ limits: Limits,
+ ) -> Result {
+ let counts = preflight(files, scope, limits)?;
+ let mut index = Self {
+ snapshot,
+ scope,
+ files: Vec::with_capacity(files.len()),
+ definitions: Vec::with_capacity(counts.definitions),
+ references: Vec::with_capacity(counts.references),
+ aliases: BTreeMap::new(),
+ uses: BTreeMap::new(),
+ identities: BTreeMap::new(),
+ };
+ for (file_index, file) in files.iter().copied().enumerate() {
+ let definition_start = index.definitions.len();
+ let reference_start = index.references.len();
+ for declaration in file.declarations {
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ let number = index.definitions.len();
+ index.aliases.entry(alias).or_default().push(number);
+ if let Some(identity) = declaration.identity {
+ index.identities.entry(identity.id).or_default().push(number);
+ }
+ index.definitions.push(Definition {
+ alias,
+ kind: &file.text[declaration.kind_span.start..declaration.kind_span.end],
+ declaration: Location { file: file_index, span: declaration.span },
+ alias_location: Location { file: file_index, span: declaration.alias_span },
+ authored_id: declaration.identity.map(|identity| identity.id),
+ });
+ }
+ for (at, alias_span) in reference_spans(file) {
+ let alias = &file.text[alias_span.start..alias_span.end];
+ index.uses.entry(alias).or_default().push(index.references.len());
+ index.references.push(Reference {
+ alias,
+ at: Location { file: file_index, span: at },
+ alias_location: Location { file: file_index, span: alias_span },
+ });
+ }
+ index.files.push(SourceFile {
+ path: file.path,
+ text: file.text,
+ membership: file.membership,
+ evidence: file.evidence,
+ definitions: definition_start..index.definitions.len(),
+ references: reference_start..index.references.len(),
+ });
+ }
+ Ok(index)
+ }
+
+ pub fn snapshot(&self) -> SnapshotKey {
+ self.snapshot
+ }
+
+ pub fn source(&self, file: usize) -> Result<&SourceFile<'a>, QueryError> {
+ self.files.get(file).ok_or(QueryError::InvalidFile)
+ }
+
+ pub fn scope(&self, domain: Domain) -> QueryScope {
+ let selected_files = self.files.iter().filter(|file| domain.contains(file.membership)).count();
+ QueryScope {
+ declared: self.scope,
+ domain,
+ project_complete: self.scope == Scope::CompleteProject && domain == Domain::Included,
+ all_selected_files_validated: selected_files != 0
+ && self.files.iter().filter(|file| domain.contains(file.membership))
+ .all(|file| file.evidence == Evidence::Validated),
+ selected_files,
+ detached_files: self.files.iter().filter(|file| file.membership == Membership::Detached).count(),
+ unverified_files: self.files.iter().filter(|file| file.membership == Membership::Unverified).count(),
+ }
+ }
+
+ fn definition_handle(&self, index: usize) -> DefinitionHandle {
+ DefinitionHandle { snapshot: self.snapshot, index }
+ }
+
+ fn reference_handle(&self, index: usize) -> ReferenceHandle {
+ ReferenceHandle { snapshot: self.snapshot, index }
+ }
+
+ pub fn definition(&self, handle: DefinitionHandle) -> Result<&Definition<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.definitions.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn reference(&self, handle: ReferenceHandle) -> Result<&Reference<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.references.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn definitions_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> Definitions<'i, 'a> {
+ Definitions { index: self, indices: self.aliases.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn definitions_for_id(&self, id: Id, domain: Domain) -> Definitions<'_, 'a> {
+ Definitions { index: self, indices: self.identities.get(&id).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn references_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> References<'i, 'a> {
+ References { index: self, indices: self.uses.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn resolve_reference(&self, handle: ReferenceHandle, domain: Domain) -> Result {
+ let reference = self.reference(handle)?;
+ if !domain.contains(self.files[reference.at.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let mut definitions = self.definitions_for_alias(reference.alias, domain);
+ let Some(first) = definitions.next() else { return Ok(Lookup::Missing); };
+ let count = 1 + definitions.count();
+ Ok(if count == 1 { Lookup::Unique(first) } else { Lookup::Ambiguous { count } })
+ }
+
+ pub fn identity(&self, handle: DefinitionHandle) -> Result {
+ let Some(id) = self.definition(handle)?.authored_id else { return Ok(Identity::Missing); };
+ let count = self.identities[&id].len();
+ Ok(if count == 1 {
+ Identity::Unique(AuthoredAnchor { workspace: self.snapshot.workspace, id })
+ } else {
+ Identity::Collision { count }
+ })
+ }
+
+ pub fn resolve_anchor(&self, anchor: AuthoredAnchor, domain: Domain) -> Result {
+ if anchor.workspace != self.snapshot.workspace {
+ return Err(QueryError::WrongWorkspace);
+ }
+ let Some(candidates) = self.identities.get(&anchor.id) else { return Ok(Lookup::Missing); };
+ // A detached duplicate must not silently confer identity uniqueness.
+ if candidates.len() != 1 {
+ return Ok(Lookup::Ambiguous { count: candidates.len() });
+ }
+ let candidate = candidates[0];
+ Ok(if domain.contains(self.files[self.definitions[candidate].declaration.file].membership) {
+ Lookup::Unique(self.definition_handle(candidate))
+ } else {
+ Lookup::Missing
+ })
+ }
+
+ /// Exact authored alias-use projection for a unique current declaration in
+ /// the requested domain. Duplicate aliases require candidate inspection;
+ /// this API never attributes all matching uses to the first declaration.
+ pub fn references_to(&self, handle: DefinitionHandle, domain: Domain) -> Result, QueryError> {
+ let definition = self.definition(handle)?;
+ if !domain.contains(self.files[definition.declaration.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let count = self.definitions_for_alias(definition.alias, domain).count();
+ if count != 1 {
+ return Err(QueryError::AmbiguousAlias { count });
+ }
+ Ok(self.references_for_alias(definition.alias, domain))
+ }
+
+ pub fn position(&self, file: usize, offset: usize) -> Result {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let prefix = &source.text[..offset];
+ Ok(Position {
+ line: prefix.bytes().filter(|byte| *byte == b'\n').count(),
+ byte_column: prefix.rsplit_once('\n').map_or(prefix.len(), |(_, tail)| tail.len()),
+ })
+ }
+
+ pub fn hit(&self, file: usize, offset: usize) -> Result, QueryError> {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let definitions = &self.definitions[source.definitions.clone()];
+ let before = definitions.partition_point(|definition| definition.alias_location.span.start <= offset);
+ if before != 0 && offset < definitions[before - 1].alias_location.span.end {
+ return Ok(Some(Hit::Definition(self.definition_handle(source.definitions.start + before - 1))));
+ }
+ let references = &self.references[source.references.clone()];
+ let before = references.partition_point(|reference| reference.at.span.start <= offset);
+ if before != 0 {
+ let reference = &references[before - 1];
+ if offset < reference.at.span.end
+ || (reference.alias_location.span.start <= offset && offset < reference.alias_location.span.end)
+ {
+ return Ok(Some(Hit::Reference(self.reference_handle(source.references.start + before - 1))));
+ }
+ }
+ Ok(None)
+ }
+}
+
+pub struct Definitions<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl Iterator for Definitions<'_, '_> {
+ type Item = DefinitionHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let definition = &self.index.definitions[number];
+ if self.domain.contains(self.index.files[definition.declaration.file].membership) {
+ return Some(self.index.definition_handle(number));
+ }
+ }
+ None
+ }
+}
+
+pub struct References<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl References<'_, '_> {
+ pub fn scope(&self) -> QueryScope {
+ self.index.scope(self.domain)
+ }
+}
+
+impl Iterator for References<'_, '_> {
+ type Item = ReferenceHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let reference = &self.index.references[number];
+ if self.domain.contains(self.index.files[reference.at.file].membership) {
+ return Some(self.index.reference_handle(number));
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const A: Id = Id::from_u128(1);
+ const B: Id = Id::from_u128(2);
+ const C: Id = Id::from_u128(3);
+
+ struct Fixture {
+ path: String,
+ text: String,
+ tokens: Vec,
+ declarations: Vec,
+ }
+
+ impl Fixture {
+ /// Test-only literal role proof. Production never searches declaration
+ /// headers: its caller supplies the coordinated syntax outline roles.
+ fn new(path: &str, text: String, roles: &[(&str, &str, Option)]) -> Self {
+ let tokens = oa_core::language::tokenize(&text);
+ let declarations = roles.iter().map(|&(kind, alias, id)| {
+ let header = format!("{kind} {alias} ");
+ let start = text.find(&header).expect("authored fixture header");
+ let end = text[start..].find('}').map_or(text.len(), |end| start + end + 1);
+ let alias_start = start + kind.len() + 1;
+ DeclarationInput {
+ span: Span { start, end },
+ kind_span: Span { start, end: start + kind.len() },
+ alias_span: Span { start: alias_start, end: alias_start + alias.len() },
+ identity: id.map(|id| {
+ let literal = format!("\"{id}\"");
+ let literal_start = start + text[start..end].find(&literal).expect("authored fixture UUID");
+ AuthoredIdentity { id, literal_span: Span { start: literal_start, end: literal_start + literal.len() } }
+ }),
+ }
+ }).collect();
+ Self { path: path.into(), text, tokens, declarations }
+ }
+
+ fn input(&self, membership: Membership, evidence: Evidence) -> FileInput<'_> {
+ FileInput { path: &self.path, text: &self.text, tokens: &self.tokens, declarations: &self.declarations, syntax: SyntaxCoverage::FullFile, membership, evidence }
+ }
+
+ fn included(&self) -> FileInput<'_> {
+ self.input(Membership::Included, Evidence::Draft)
+ }
+ }
+
+ fn snapshot(revision: u64) -> SnapshotKey {
+ SnapshotKey { workspace: Id::from_u128(1000), revision }
+ }
+
+ fn limits() -> Limits {
+ Limits { files: 16, source_bytes: 1024 * 1024, path_bytes: 8192, tokens: 200_000, records: 100_000 }
+ }
+
+ fn index<'a>(files: &[FileInput<'a>]) -> Index<'a> {
+ Index::build(snapshot(1), files, Scope::CompleteProject, limits()).unwrap()
+ }
+
+ fn definition(index: &Index<'_>, alias: &str) -> DefinitionHandle {
+ let mut found = index.definitions_for_alias(alias, Domain::Loaded);
+ let result = found.next().expect("fixture definition");
+ assert!(found.next().is_none(), "fixture expects unique alias");
+ result
+ }
+
+ fn anchor(index: &Index<'_>, handle: DefinitionHandle) -> AuthoredAnchor {
+ match index.identity(handle).unwrap() {
+ Identity::Unique(anchor) => anchor,
+ identity => panic!("fixture expects unique authored identity: {identity:?}"),
+ }
+ }
+
+ #[test]
+ fn repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor tank \"Same display\" id \"{A}\" {{}}\n\
+ oa_actor tank_extra \"Same display\" id \"{B}\" {{}}\n\
+ oa_interaction flow \"Flow\" id \"{C}\" {{ source @tank; target @tank; other @tank_extra; \
+ description \"@tank @tank_extra\"; historical uuid \"{A}\"; }}\n\
+ // @tank @tank_extra and UUID {A}\n"
+ ), &[("oa_actor", "tank", Some(A)), ("oa_actor", "tank_extra", Some(B)), ("oa_interaction", "flow", Some(C))]);
+ let original = (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone());
+ let index = index(&[fixture.included()]);
+ let tank = definition(&index, "tank");
+ let uses: Vec<_> = index.references_to(tank, Domain::Included).unwrap().collect();
+ assert_eq!(uses.len(), 2);
+ assert_ne!(uses[0], uses[1]);
+ assert_eq!(index.references_for_alias("tank_extra", Domain::Loaded).count(), 1);
+ for usage in uses {
+ let record = index.reference(usage).unwrap();
+ assert_eq!(&fixture.text[record.at.span.start..record.at.span.end], "@");
+ assert_eq!(&fixture.text[record.alias_location.span.start..record.alias_location.span.end], "tank");
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(tank)));
+ assert_eq!(index.hit(0, record.alias_location.span.start), Ok(Some(Hit::Reference(usage))));
+ }
+ for ignored in [fixture.text.find("Same display").unwrap(), fixture.text.find("// @tank").unwrap() + 3,
+ fixture.text.find("\"@tank @tank_extra\"").unwrap() + 1,
+ fixture.text.find("historical uuid").unwrap() + "historical uuid \"".len()] {
+ assert_eq!(index.hit(0, ignored), Ok(None));
+ }
+ assert_eq!(index.hit(0, fixture.text.len()), Ok(None));
+ assert_eq!(original, (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone()));
+ }
+
+ #[test]
+ fn comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans() {
+ let fixture = Fixture::new("parts/actors.arcadia", format!(
+ "oa_actor a \"救助 🚒\" id \"{A}\" {{}}\r\nsource @ // 注释 @a\r\n a; target @a;\r\n"
+ ), &[("oa_actor", "a", Some(A))]);
+ let index = index(&[fixture.included()]);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert_eq!(uses.len(), 2);
+ let first = index.reference(uses[0]).unwrap();
+ assert_eq!(index.position(0, first.at.span.start), Ok(Position { line: 1, byte_column: 7 }));
+ assert_eq!(index.position(0, first.alias_location.span.start), Ok(Position { line: 2, byte_column: 2 }));
+ assert_eq!(index.hit(0, first.at.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ assert_eq!(index.hit(0, first.alias_location.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ for byte in [first.at.span.end, fixture.text.find("注释").unwrap(), fixture.text.find("// 注释 @a").unwrap() + "// 注释 ".len(), first.alias_location.span.end] {
+ assert_eq!(index.hit(0, byte), Ok(None));
+ }
+ let unicode_inside = fixture.text.find('救').unwrap() + 1;
+ assert_eq!(index.hit(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.position(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(0, fixture.text.len() + 1), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(1, 0), Err(QueryError::InvalidFile));
+ assert!(index.source(0).unwrap().text.contains("\r\n"));
+ }
+
+ #[test]
+ fn legal_keyword_classified_alias_is_not_discarded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor oa_activity \"Participant\" id \"{A}\" {{}}\nsource @oa_activity; source @parent;"
+ ), &[("oa_actor", "oa_activity", Some(A))]);
+ assert!(oa_core::valid_alias("oa_activity"));
+ assert!(!oa_core::valid_alias("parent"));
+ assert_eq!(token_at(&fixture.tokens, fixture.declarations[0].alias_span).unwrap().class, TokenClass::Keyword);
+ let index = index(&[fixture.included()]);
+ let found = definition(&index, "oa_activity");
+ let usage = index.references_for_alias("oa_activity", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(found)));
+ assert_eq!(index.references_for_alias("parent", Domain::Loaded).count(), 0);
+ assert_eq!(index.hit(0, fixture.declarations[0].alias_span.start), Ok(Some(Hit::Definition(found))));
+ }
+
+ #[test]
+ fn duplicate_aliases_are_candidates_without_false_reference_attribution() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"Same display\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let second = Fixture::new("parts/other.arcadia", format!("oa_actor a \"Same display\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let index = index(&[first.included(), second.included()]);
+ let candidates: Vec<_> = index.definitions_for_alias("a", Domain::Included).collect();
+ assert_eq!(candidates.len(), 2);
+ assert_eq!(index.definition(candidates[0]).unwrap().declaration.file, 0);
+ assert_eq!(index.definition(candidates[1]).unwrap().declaration.file, 1);
+ assert_ne!(anchor(&index, candidates[0]), anchor(&index, candidates[1]));
+ assert!(matches!(index.references_to(candidates[0], Domain::Included), Err(QueryError::AmbiguousAlias { count: 2 })));
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Included).collect();
+ assert_eq!(uses.len(), 2);
+ for usage in uses {
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ }
+ }
+
+ #[test]
+ fn detached_uuid_clone_disables_anchor_even_for_included_query() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_anchor = anchor(&original, definition(&original, "a"));
+ let detached = Fixture::new("retained.arcadia", format!("oa_actor old \"Retained\" id \"{A}\" {{}}"), &[("oa_actor", "old", Some(A))]);
+ let current = Index::build(snapshot(2), &[first.included(), detached.input(Membership::Detached, Evidence::Draft)], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.identity(definition(¤t, "a")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.identity(definition(¤t, "old")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.definitions_for_alias("old", Domain::Included).count(), 0);
+ assert_eq!(current.definitions_for_id(A, Domain::Included).count(), 1);
+ assert_eq!(current.definitions_for_id(A, Domain::Loaded).count(), 2);
+ }
+
+ #[test]
+ fn reused_alias_cannot_steal_old_identity_and_old_locations_are_stale() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_definition = definition(&original, "a");
+ let old_reference = original.references_for_alias("a", Domain::Included).next().unwrap();
+ let old_anchor = anchor(&original, old_definition);
+ let edited = Fixture::new("project.arcadia", format!(
+ "oa_actor b \"First renamed\" id \"{A}\" {{}}\noa_actor a \"New actor\" id \"{B}\" {{}}\nsource @a; target @b;"
+ ), &[("oa_actor", "b", Some(A)), ("oa_actor", "a", Some(B))]);
+ let current = Index::build(snapshot(2), &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.definition(old_definition).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.reference(old_reference).map(|reference| reference.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Unique(definition(¤t, "b"))));
+ let new_use = current.references_for_alias("a", Domain::Included).next().unwrap();
+ assert_eq!(current.resolve_reference(new_use, Domain::Included), Ok(Lookup::Unique(definition(¤t, "a"))));
+ assert_eq!(anchor(¤t, definition(¤t, "a")).id(), B);
+ let other_workspace = Index::build(SnapshotKey { workspace: Id::from_u128(2000), revision: 2 }, &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(other_workspace.resolve_anchor(old_anchor, Domain::Loaded), Err(QueryError::WrongWorkspace));
+ assert_eq!(other_workspace.definition(definition(¤t, "b")).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ let deleted = Fixture::new("project.arcadia", format!("oa_actor a \"New actor\" id \"{B}\" {{}}"), &[("oa_actor", "a", Some(B))]);
+ let after_delete = Index::build(snapshot(3), &[deleted.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(after_delete.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Missing));
+ }
+
+ #[test]
+ fn missing_ids_produce_only_current_snapshot_locations_without_stabilization() {
+ let fixture = Fixture::new("project.arcadia", format!("oa_actor a \"No UUID\" {{ historical uuid \"{A}\"; }}\r\nsource @a;"), &[("oa_actor", "a", None)]);
+ let original = fixture.text.clone();
+ let index = index(&[fixture.included()]);
+ let declaration = definition(&index, "a");
+ assert_eq!(index.identity(declaration), Ok(Identity::Missing));
+ assert_eq!(index.definition(declaration).unwrap().authored_id, None);
+ let usage = index.references_to(declaration, Domain::Included).unwrap().next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(declaration)));
+ assert_eq!(fixture.text, original);
+ assert_eq!(index.source(0).unwrap().text.as_bytes(), original.as_bytes());
+ assert!(!index.source(0).unwrap().text.contains(" id "));
+ assert_eq!(index.hit(0, fixture.text.find(&A.to_string()).unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn scope_provenance_and_detached_alias_collision_are_explicit() {
+ let included = Fixture::new("project.arcadia", format!("oa_actor a \"Included\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let detached = Fixture::new("old.arcadia", format!("oa_actor a \"Detached\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let unknown = Fixture::new("new.arcadia", "source @a;".into(), &[]);
+ let index = Index::build(snapshot(1), &[
+ included.input(Membership::Included, Evidence::Validated),
+ detached.input(Membership::Detached, Evidence::Draft),
+ unknown.input(Membership::Unverified, Evidence::Draft),
+ ], Scope::Partial(PartialReason::IncludesUnverified), limits()).unwrap();
+ assert_eq!(index.references_for_alias("a", Domain::Included).count(), 1);
+ assert_eq!(index.references_for_alias("a", Domain::Loaded).count(), 3);
+ let scope = index.references_for_alias("a", Domain::Included).scope();
+ assert_eq!(scope.declared, Scope::Partial(PartialReason::IncludesUnverified));
+ assert!(!scope.project_complete);
+ assert!(scope.all_selected_files_validated);
+ assert_eq!((scope.selected_files, scope.detached_files, scope.unverified_files), (1, 1, 1));
+ assert!(!index.scope(Domain::Loaded).all_selected_files_validated);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert!(matches!(index.resolve_reference(uses[0], Domain::Included), Ok(Lookup::Unique(_))));
+ assert_eq!(index.resolve_reference(uses[0], Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(index.resolve_reference(uses[1], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.resolve_reference(uses[2], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.source(2).unwrap().membership, Membership::Unverified);
+ assert!(matches!(Index::build(snapshot(1), &[included.included(), unknown.input(Membership::Unverified, Evidence::Draft)], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::InconsistentCompleteScope, .. })));
+ }
+
+ #[test]
+ fn incomplete_draft_is_lexical_and_error_token_does_not_leak_references() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Draft\" {}\nsource @a; ? \"unfinished @a".into(), &[("oa_actor", "a", None)]);
+ assert!(fixture.tokens.iter().any(|token| token.class == TokenClass::Error));
+ let index = Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::UnappliedWorkspace), limits()).unwrap();
+ let matches = index.references_to(definition(&index, "a"), Domain::Included).unwrap();
+ assert!(!matches.scope().project_complete);
+ assert!(!matches.scope().all_selected_files_validated);
+ assert_eq!(matches.count(), 1);
+ assert_eq!(index.hit(0, fixture.text.rfind("@a").unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn header_metadata_must_match_current_token_boundaries_and_authored_id() {
+ let mut fixture = Fixture::new("project.arcadia", format!("oa_actor alpha \"救助\" id \"{A}\" {{}}"), &[("oa_actor", "alpha", Some(A))]);
+ let original = fixture.declarations[0];
+ fixture.declarations[0].alias_span.start += 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::HeaderRoleToken, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].identity.as_mut().unwrap().id = B;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].alias_span = Span { start: fixture.text.find('救').unwrap() + 1, end: fixture.text.find('救').unwrap() + 3 };
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].span.end = fixture.text.len() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ }
+
+ #[test]
+ fn truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let final_token = fixture.tokens.pop().unwrap();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ fixture.tokens.push(final_token);
+ fixture.tokens.remove(1);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ let mut unicode = Fixture::new("project.arcadia", "// 救助\n".into(), &[]);
+ unicode.tokens[0].span.end = unicode.text.find('救').unwrap() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[unicode.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenBounds, .. })));
+ }
+
+ #[test]
+ fn stopped_outline_is_not_a_successful_empty_query() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let mut stopped = fixture.included();
+ stopped.declarations = &[];
+ stopped.syntax = SyntaxCoverage::Incomplete;
+ assert!(matches!(Index::build(snapshot(1), &[stopped], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::IncompleteSyntax, .. })));
+ }
+
+ #[test]
+ fn nested_declaration_extents_do_not_turn_body_text_into_definition_hits() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_entity outer \"Outer\" {\n oa_actor inner \"Inner\" {}\n}\nsource @inner;".into(), &[("oa_entity", "outer", None), ("oa_actor", "inner", None)]);
+ // Supply the actual outer whole span; nesting does not change alias order.
+ fixture.declarations[0].span.end = fixture.text.rfind('}').unwrap() + 1;
+ let index = index(&[fixture.included()]);
+ let inner = definition(&index, "inner");
+ assert_eq!(index.hit(0, fixture.declarations[1].alias_span.start), Ok(Some(Hit::Definition(inner))));
+ assert_eq!(index.hit(0, fixture.text.find("\"Inner\"").unwrap() + 1), Ok(None));
+ let usage = index.references_for_alias("inner", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(inner)));
+ }
+
+ #[test]
+ fn path_identity_rejects_normalization_and_case_collisions() {
+ let mut first = Fixture::new("project.arcadia", "source @a;".into(), &[]);
+ let second = Fixture::new("PROJECT.arcadia", "source @b;".into(), &[]);
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ for invalid in ["./project.arcadia", "parts/../project.arcadia", "../project.arcadia", "C:/project.arcadia", "parts\\project.arcadia"] {
+ first.path = invalid.into();
+ assert!(matches!(Index::build(snapshot(1), &[first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::NonCanonicalPath, .. })), "{invalid}");
+ }
+ }
+
+ #[test]
+ fn every_budget_admits_boundary_and_returns_no_index_one_below() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a; target @a;".into(), &[("oa_actor", "a", None)]);
+ let exact = Limits { files: 1, source_bytes: fixture.text.len(), path_bytes: fixture.path.len(), tokens: fixture.tokens.len(), records: 3 };
+ assert!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).is_ok());
+ for resource in [Resource::Files, Resource::SourceBytes, Resource::PathBytes, Resource::Tokens, Resource::Records] {
+ let mut reduced = exact;
+ match resource {
+ Resource::Files => reduced.files -= 1,
+ Resource::SourceBytes => reduced.source_bytes -= 1,
+ Resource::PathBytes => reduced.path_bytes -= 1,
+ Resource::Tokens => reduced.tokens -= 1,
+ Resource::Records => reduced.records -= 1,
+ }
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, reduced), Err(BuildError::Limit { resource: actual, .. }) if actual == resource));
+ }
+ // Aggregate bytes/tokens/records, not a per-file budget reset.
+ let second = Fixture::new("other.arcadia", "source @a;".into(), &[]);
+ let mut aggregate = limits();
+ aggregate.records = 3;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included(), second.included()], Scope::CompleteProject, aggregate), Err(BuildError::Limit { resource: Resource::Records, observed_at_least: 4, .. })));
+ let mut maximum = usize::MAX;
+ assert!(matches!(charge(&mut maximum, 1, usize::MAX, Resource::SourceBytes), Err(BuildError::Limit { resource: Resource::SourceBytes, .. })));
+ assert_eq!(maximum, usize::MAX);
+ }
+
+ #[test]
+ fn declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"First\" {}\noa_actor b \"Second\" {}".into(), &[("oa_actor", "a", None), ("oa_actor", "b", None)]);
+ fixture.declarations.reverse();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ fixture.declarations.reverse();
+ fixture.declarations.insert(1, fixture.declarations[0]);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/artifacts1-status.json b/harness/experiments/text-navigation-components-20260911/components/artifacts1-status.json
new file mode 100644
index 00000000..25c8fe75
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/artifacts1-status.json
@@ -0,0 +1,16 @@
+{
+ "started": "2026-09-11T16:56:18.6477215Z",
+ "finished": "2026-09-11T16:56:23.5765392Z",
+ "exitCode": 0,
+ "executable": "cargo",
+ "args": [
+ "build",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-core",
+ "--lib",
+ "--message-format=json"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/artifacts1-stderr.log b/harness/experiments/text-navigation-components-20260911/components/artifacts1-stderr.log
new file mode 100644
index 00000000..155b1db7
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/artifacts1-stderr.log
@@ -0,0 +1 @@
+ Finished `dev` profile [unoptimized] target(s) in 3.41s
diff --git a/harness/experiments/text-navigation-components-20260911/components/artifacts1-stdout.log b/harness/experiments/text-navigation-components-20260911/components/artifacts1-stdout.log
new file mode 100644
index 00000000..e65cde2e
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/artifacts1-stdout.log
@@ -0,0 +1,44 @@
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\proc-macro2-1.0.107\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\proc-macro2-1.0.107\\build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\proc-macro2-8bd275bc14efcc6d\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107","linked_libs":[],"linked_paths":[],"cfgs":["wrap_proc_macro","proc_macro_span_location","proc_macro_span_file"],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\proc-macro2-678fa29727a3d951\\out"}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\quote-1.0.47\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\quote-1.0.47\\build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\quote-78b7b00c15767e2b\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#unicode-ident@1.0.24","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\unicode-ident-1.0.24\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"unicode_ident","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\unicode-ident-1.0.24\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libunicode_ident-37cacd323a49429c.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libunicode_ident-37cacd323a49429c.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.107","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\proc-macro2-1.0.107\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"proc_macro2","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\proc-macro2-1.0.107\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libproc_macro2-80e9e45547beebf5.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libproc_macro2-80e9e45547beebf5.rmeta"],"executable":null,"fresh":true}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\quote-db749d68bbb2dbfe\\out"}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#quote@1.0.47","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\quote-1.0.47\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"quote","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\quote-1.0.47\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","proc-macro"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libquote-b79aec363b458000.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libquote-b79aec363b458000.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.229","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_core-1.0.229\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_core-1.0.229\\build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["result","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\serde_core-161b3ae2683262f1\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.229","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\serde_core-a1da37301c02ee2a\\out"}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#syn@3.0.5","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\syn-3.0.5\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"syn","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\syn-3.0.5\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["clone-impls","default","derive","parsing","printing","proc-macro"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libsyn-2232b8c89352da95.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libsyn-2232b8c89352da95.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#windows-link@0.2.1","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\windows-link-0.2.1\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"windows_link","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\windows-link-0.2.1\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libwindows_link-23f115c823619f71.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libwindows_link-23f115c823619f71.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.3","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\getrandom-0.4.3\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\getrandom-0.4.3\\build.rs","edition":"2024","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\getrandom-168cb7f91c8645ca\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.3","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\getrandom-4967b63cf7b288b8\\out"}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#windows-sys@0.61.2","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\windows-sys-0.61.2\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"windows_sys","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\windows-sys-0.61.2\\src\\lib.rs","edition":"2021","doc":true,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["Win32","Win32_Foundation","Win32_Storage","Win32_Storage_FileSystem","Win32_System","Win32_System_Console","Win32_System_SystemInformation","default"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libwindows_sys-43b3d9a205b229b7.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libwindows_sys-43b3d9a205b229b7.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_core@1.0.229","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_core-1.0.229\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde_core","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_core-1.0.229\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["result","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_core-f52ac0796dd4fc39.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_core-f52ac0796dd4fc39.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#cfg-if@1.0.4","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\cfg-if-1.0.4\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"cfg_if","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\cfg-if-1.0.4\\src\\lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libcfg_if-805b2ebc84d544f1.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libcfg_if-805b2ebc84d544f1.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.23","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\zmij-1.0.23\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\zmij-1.0.23\\build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\zmij-e8f67a20243f3063\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.23","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\zmij-c8f8f3187a72be14\\out"}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#getrandom@0.4.3","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\getrandom-0.4.3\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"getrandom","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\getrandom-0.4.3\\src\\lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libgetrandom-6e8efb5a01ff0cbb.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libgetrandom-6e8efb5a01ff0cbb.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.20","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\thiserror-2.0.20\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\thiserror-2.0.20\\build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\thiserror-d9d1cecb0b9dfa9d\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.229","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde-1.0.229\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde-1.0.229\\build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","derive","serde_derive","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\serde-c9911adbe87e85d2\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.151","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_json-1.0.151\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_json-1.0.151\\build.rs","edition":"2021","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\serde_json-28521d5277e5c15b\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.151","linked_libs":[],"linked_paths":[],"cfgs":["fast_arithmetic=\"64\""],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\serde_json-908788f6700df5fb\\out"}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.229","linked_libs":[],"linked_paths":[],"cfgs":["if_docsrs_then_no_serde_core"],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\serde-963672309bf9e4ba\\out"}
+{"reason":"build-script-executed","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.20","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\thiserror-2e28320913ae6f8a\\out"}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#zmij@1.0.23","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\zmij-1.0.23\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"zmij","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\zmij-1.0.23\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libzmij-9f8770986f00ef15.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libzmij-9f8770986f00ef15.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#winapi-util@0.1.11","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\winapi-util-0.1.11\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"winapi_util","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\winapi-util-0.1.11\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libwinapi_util-be9ab0847edf5b05.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libwinapi_util-be9ab0847edf5b05.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror-impl@2.0.20","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\thiserror-impl-2.0.20\\Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"thiserror_impl","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\thiserror-impl-2.0.20\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\thiserror_impl-774eb7636a800724.dll","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libthiserror_impl-774eb7636a800724.dll.a"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.229","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_derive-1.0.229\\Cargo.toml","target":{"kind":["proc-macro"],"crate_types":["proc-macro"],"name":"serde_derive","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_derive-1.0.229\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\serde_derive-c07a6353fdd1d8a1.dll","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_derive-c07a6353fdd1d8a1.dll.a"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#once_cell@1.21.4","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\once_cell-1.21.4\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"once_cell","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\once_cell-1.21.4\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","race","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libonce_cell-f7e62c44737ecbb9.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libonce_cell-f7e62c44737ecbb9.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#sha1_smol@1.0.1","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\sha1_smol-1.0.1\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"sha1_smol","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\sha1_smol-1.0.1\\src\\lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libsha1_smol-c4b7e5ec8b41792e.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libsha1_smol-c4b7e5ec8b41792e.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#itoa@1.0.18","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\itoa-1.0.18\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"itoa","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\itoa-1.0.18\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libitoa-971c3e13d32ffcf2.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libitoa-971c3e13d32ffcf2.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#fastrand@2.5.0","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\fastrand-2.5.0\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"fastrand","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\fastrand-2.5.0\\src\\lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","default","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libfastrand-3df248e815d242e1.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libfastrand-3df248e815d242e1.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#memchr@2.8.3","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\memchr-2.8.3\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"memchr","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\memchr-2.8.3\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["alloc","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libmemchr-088af8780d4df76d.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libmemchr-088af8780d4df76d.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"path+file:///C:/Users/User/Documents/Git/ARCADIA-Studio/crates/oa-core#0.2.0","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\Cargo.toml","target":{"kind":["custom-build"],"crate_types":["bin"],"name":"build-script-build","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\build.rs","edition":"2024","doc":false,"doctest":false,"test":false},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\oa-core-6f46b396061b0e99\\build-script-build.exe"],"executable":null,"fresh":true}
+{"reason":"build-script-executed","package_id":"path+file:///C:/Users/User/Documents/Git/ARCADIA-Studio/crates/oa-core#0.2.0","linked_libs":[],"linked_paths":[],"cfgs":[],"env":[],"out_dir":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\build\\oa-core-08552b12a2cacda2\\out"}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.151","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_json-1.0.151\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde_json","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_json-1.0.151\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#tempfile@3.27.0","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\tempfile-3.27.0\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"tempfile","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\tempfile-3.27.0\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","getrandom"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libtempfile-20c336358d7a22b5.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libtempfile-20c336358d7a22b5.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#uuid@1.26.0","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\uuid-1.26.0\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"uuid","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\uuid-1.26.0\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","rng","serde","sha1","std","v4","v5"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libuuid-f7851e8936dae115.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libuuid-f7851e8936dae115.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#serde@1.0.229","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde-1.0.229\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"serde","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde-1.0.229\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","derive","serde_derive","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde-968c91fb77bb5d38.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde-968c91fb77bb5d38.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#thiserror@2.0.20","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\thiserror-2.0.20\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"thiserror","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\thiserror-2.0.20\\src\\lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":["default","std"],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libthiserror-add096f46a35d6d2.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libthiserror-add096f46a35d6d2.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#same-file@1.0.6","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\same-file-1.0.6\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"same_file","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\same-file-1.0.6\\src\\lib.rs","edition":"2018","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libsame_file-a21d6395ce590b18.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libsame_file-a21d6395ce590b18.rmeta"],"executable":null,"fresh":true}
+{"reason":"compiler-artifact","package_id":"path+file:///C:/Users/User/Documents/Git/ARCADIA-Studio/crates/oa-core#0.2.0","manifest_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"oa_core","src_path":"C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\src\\lib.rs","edition":"2024","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":["C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\liboa_core.rlib","C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-781c6973417b169c.rmeta"],"executable":null,"fresh":true}
+{"reason":"build-finished","success":true}
diff --git a/harness/experiments/text-navigation-components-20260911/components/clippy-tests2-status.json b/harness/experiments/text-navigation-components-20260911/components/clippy-tests2-status.json
new file mode 100644
index 00000000..bfe2cfd7
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/clippy-tests2-status.json
@@ -0,0 +1,18 @@
+{
+ "started": "2026-09-11T17:18:18.6539471Z",
+ "finished": "2026-09-11T17:18:37.5366321Z",
+ "exitCode": 0,
+ "args": [
+ "clippy",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-core",
+ "--lib",
+ "--tests",
+ "--",
+ "-D",
+ "warnings"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/clippy-tests2.log b/harness/experiments/text-navigation-components-20260911/components/clippy-tests2.log
new file mode 100644
index 00000000..c3917ade
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/clippy-tests2.log
@@ -0,0 +1,2 @@
+ Checking oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-core)
+ Finished `dev` profile [unoptimized] target(s) in 18.22s
diff --git a/harness/experiments/text-navigation-components-20260911/components/clippy1-status.json b/harness/experiments/text-navigation-components-20260911/components/clippy1-status.json
new file mode 100644
index 00000000..04e723ff
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/clippy1-status.json
@@ -0,0 +1,17 @@
+{
+ "started": "2026-09-11T16:48:34.5596000Z",
+ "finished": "2026-09-11T16:55:38.1131532Z",
+ "exitCode": 0,
+ "args": [
+ "clippy",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-core",
+ "--all-targets",
+ "--",
+ "-D",
+ "warnings"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/clippy1.log b/harness/experiments/text-navigation-components-20260911/components/clippy1.log
new file mode 100644
index 00000000..9e3c87e4
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/clippy1.log
@@ -0,0 +1,2 @@
+ Checking oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-core)
+ Finished `dev` profile [unoptimized] target(s) in 7m 02s
diff --git a/harness/experiments/text-navigation-components-20260911/components/compiler1-status.json b/harness/experiments/text-navigation-components-20260911/components/compiler1-status.json
new file mode 100644
index 00000000..7f47dd1d
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/compiler1-status.json
@@ -0,0 +1,10 @@
+{
+ "started": "2026-09-11T16:56:16.8520811Z",
+ "finished": "2026-09-11T16:56:18.0426484Z",
+ "exitCode": 0,
+ "executable": "rustc",
+ "args": [
+ "-vV"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/compiler1-stderr.log b/harness/experiments/text-navigation-components-20260911/components/compiler1-stderr.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/components/compiler1-stdout.log b/harness/experiments/text-navigation-components-20260911/components/compiler1-stdout.log
new file mode 100644
index 00000000..04c0a1f5
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/compiler1-stdout.log
@@ -0,0 +1,7 @@
+rustc 1.98.1 (48a229cea 2026-09-01)
+binary: rustc
+commit-hash: 48a229ceaefd4985c50990b14116b6d856af0985
+commit-date: 2026-09-01
+host: x86_64-pc-windows-gnullvm
+release: 1.98.1
+LLVM version: 22.1.8
diff --git a/harness/experiments/text-navigation-components-20260911/components/core1-status.json b/harness/experiments/text-navigation-components-20260911/components/core1-status.json
new file mode 100644
index 00000000..1928084c
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/core1-status.json
@@ -0,0 +1,15 @@
+{
+ "started": "2026-09-11T16:23:10.2275390Z",
+ "finished": "2026-09-11T16:48:34.5241796Z",
+ "exitCode": 0,
+ "args": [
+ "test",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-core",
+ "--",
+ "--test-threads=1"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/core1.log b/harness/experiments/text-navigation-components-20260911/components/core1.log
new file mode 100644
index 00000000..8f13a38d
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/core1.log
@@ -0,0 +1,694 @@
+ Compiling oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-core)
+ Finished `test` profile [unoptimized] target(s) in 21m 33s
+ Running unittests src\lib.rs (.local\target-gnullvm\debug\deps\oa_core-e594d1d7e7cdca87.exe)
+
+running 54 tests
+test language::nesting_tests::checked_in_example_supports_cross_file_reparent_without_changing_allocations ... ok
+test language::nesting_tests::checked_in_nested_example_has_stable_ids_typed_exchanges_and_three_clear_views ... ok
+test language::nesting_tests::containment_validation_rejects_allocation_like_or_cross_layer_nesting ... ok
+test language::nesting_tests::declarations_are_preorder_occurrences_with_real_own_field_and_body_spans ... ok
+test language::nesting_tests::deep_declaration_parsing_and_positions_use_a_bounded_rust_stack ... ok
+test language::nesting_tests::matching_explicit_parent_is_accepted_but_none_or_another_owner_is_located ... ok
+test language::nesting_tests::nested_aliases_remain_global_and_forward_references_remain_legal ... ok
+test language::nesting_tests::nested_elements_infer_only_parent_and_keep_typed_fields_and_allocations_explicit ... ok
+test language::nesting_tests::nested_fragment_id_stabilization_preserves_comments_and_indexes_real_bytes ... ok
+test language::nesting_tests::nested_record_admission_precedes_resolution_and_typed_depth_stays_bounded ... ok
+test language::nesting_tests::non_elements_includes_and_declarations_inside_typed_values_are_rejected ... ok
+test language::nesting_tests::normative_nested_ownership_example_compiles_as_a_source_fragment ... ok
+test language::nesting_tests::syntax_edit_positions_retain_duplicate_identity_occurrences_during_a_command ... ok
+test language::nesting_tests::tolerant_nested_analysis_retains_closed_children_before_an_unfinished_sibling ... ok
+test language::outline_tests::bounds_refuse_before_returning_a_partial_outline ... ok
+test language::outline_tests::closed_id_at_eof_is_inside_the_declaration_extent ... ok
+test language::outline_tests::deeply_nested_bounds_use_the_same_iterative_walk_and_declaration_budget ... ok
+test language::outline_tests::header_shape_still_requires_valid_hexadecimal_uuid_data ... ok
+test language::outline_tests::invalid_full_scan_keeps_closed_children_and_reports_lexical_body_facts ... ok
+test language::outline_tests::keyword_alias_and_collisions_keep_every_source_ordered_header ... ok
+test language::outline_tests::missing_bad_and_historical_ids_never_allocate_authored_identity ... ok
+test language::outline_tests::missing_body_syntax_is_unchecked_even_when_known_error_flags_are_clear ... ok
+test language::outline_tests::noncanonical_uuid_spellings_have_no_outline_or_analysis_identity ... ok
+test language::outline_tests::outline_ranges_follow_current_bytes_without_identity_stabilization ... ok
+test language::outline_tests::outline_roles_preserve_unicode_crlf_and_exact_escaped_uuid_literal ... ok
+test language::outline_tests::uppercase_and_json_escaped_header_ids_survive_both_parsers_and_outline ... ok
+test semantic::catalog::coverage::every_current_payload_field_has_a_closed_typed_capture_and_write_scope ... ok
+test source_files::transaction_tests::interrupted_publication_without_write_marker_never_rolls_back_external_matching_bytes ... ok
+test source_files::transaction_tests::interrupted_save_and_interrupted_recovery_can_both_be_resumed ... ok
+test source_files::transaction_tests::journal_publication_does_not_replace_empty_target_created_after_precheck ... ok
+test source_files::transaction_tests::journal_publication_exhaustion_and_permanent_errors_keep_stage_and_report_original_path ... ok
+test source_files::transaction_tests::journal_publication_never_replaces_another_journal_that_appears_during_retry ... ok
+test source_files::transaction_tests::journal_publication_retries_only_transient_windows_errors_before_transferring_ownership ... ok
+test source_files::transaction_tests::malformed_v2_write_marker_refuses_recovery_without_changing_source_bytes ... ok
+test source_files::transaction_tests::partial_save_failures_restore_every_original_and_write_entry_last ... ok
+test source_files::transaction_tests::recovery_before_any_write_accepts_v2_marker_and_keeps_v1_rollback_compatibility ... ok
+test source_files::transaction_tests::recovery_rechecks_external_changes_between_restorations ... ok
+test source_files::transaction_tests::recovery_rejects_untrusted_escape_paths_before_touching_files ... ok
+test source_files::transaction_tests::rollback_refuses_external_edits_even_in_an_unchanged_dependency ... ok
+test source_files::transaction_tests::rollback_removes_only_our_new_file_and_retains_detached_files ... ok
+test source_files::transaction_tests::unwritten_journal_never_rolls_back_a_completed_writer_with_identical_after_bytes ... ok
+test topology_context::analysis::tests::edge_work_budget_stops_repeated_expansion_not_just_output_storage ... ok
+test topology_context::analysis::tests::exact_edge_budget_includes_reference_boundaries_and_node_abort_has_no_bypass ... ok
+test topology_context::analysis::tests::unresolved_authored_edges_consume_work_budget_without_stored_output ... ok
+test validation::coverage::tests::malformed_self_reuse_is_bounded_even_without_structural_gate ... ok
+test view_options::tests::global_behavior_coverage_and_chosen_context_are_independent_intersecting_predicates ... ok
+test view_options::tests::hidden_identity_survives_label_and_category_changes_without_mutating_the_model ... ok
+test view_options::tests::no_lines_and_explicit_trace_selection_include_actual_trace_identities ... ok
+test view_options::tests::omission_reasons_distinguish_missing_placements_folded_edges_and_explicit_hides ... ok
+test view_options::tests::persisted_empty_selectors_and_lifecycle_never_widen_to_all ... ok
+test view_options::tests::reusable_sequences_and_nested_scenarios_use_listed_contributions_in_both_layers ... ok
+test view_options::tests::separate_category_families_obey_all_any_empty_and_union_truth_table ... ok
+test view_options::tests::traceability_defaults_preserve_both_layers_and_explicit_selectors_still_apply ... ok
+test view_options::tests::wrong_family_layer_and_unknown_references_reject_and_draft_filters_fail_closed ... ok
+
+test result: ok. 54 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 44.50s
+
+ Running tests\communication_boundary_scenarios.rs (.local\target-gnullvm\debug\deps\communication_boundary_scenarios-9e00f9e11def63f8.exe)
+
+running 3 tests
+test mapped_unoriented_boundary_resolves_reverse_call_and_reply_from_actual_binding ... ok
+test reverse_boundary_scenario_transition_keeps_call_orientation_and_reply_identity ... ok
+test unoriented_flow_boundary_honors_ordinary_direction_but_preserves_correlated_reply_exception ... ok
+
+test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s
+
+ Running tests\communication_means.rs (.local\target-gnullvm\debug\deps\communication_means-671949388cdc4026.exe)
+
+running 9 tests
+test communication_allocation_checks_whole_partial_pair_and_does_not_guess_roles ... ok
+test communication_allocation_items_use_specialization_and_empty_is_not_wildcard ... ok
+test communication_means_are_distinct_oa_participant_relations_and_strict_source6_records ... ok
+test communication_reverse_transition_projects_owners_without_oa_ports ... ok
+test communication_scenario_ambiguous_and_incomplete_bindings_never_guess ... ok
+test communication_scenario_reverse_call_and_reply_follow_actual_direction ... ok
+test communication_schema5_upgrade_preserves_legacy_data_and_exact_source_trivia ... ok
+test communication_semantic_endpoint_take_needs_scoped_decision_and_exact_history ... ok
+test communication_transition_requires_explicit_ports_and_preserves_participant_mapping ... ok
+
+test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.01s
+
+ Running tests\coverage_diagnostics.rs (.local\target-gnullvm\debug\deps\coverage_diagnostics-b265a0dc28672d93.exe)
+
+running 6 tests
+test grouping_and_scenario_only_capabilities_do_not_duplicate_support_warnings ... ok
+test included_process_and_nested_scenario_evidence_is_identity_based_in_both_layers ... ok
+test incomplete_oa_repairs_exact_records_and_ordered_membership_without_inference ... ok
+test multifile_lint_preserves_core_codes_ids_and_actual_owning_source_paths ... ok
+test non_leaf_exchange_locations_use_work_endpoints_including_typed_sa_ports ... ok
+test structural_errors_suppress_completeness_cascades_and_rejection_keeps_the_model ... ok
+
+test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.56s
+
+ Running tests\language.rs (.local\target-gnullvm\debug\deps\language-ae9c78e7ce7d98ed.exe)
+
+running 16 tests
+test all_typed_fields_round_trip_with_stable_aliases_and_occurrences ... ok
+test authoring_defaults_and_forward_references_remain_strict ... ok
+test branching_reused_processes_and_chains_have_lossless_readable_source ... ok
+test complete_normative_examples_are_accepted_by_the_real_parser ... ok
+test coordinate_bits_and_permitted_strings_survive_text_and_storage ... ok
+test deeply_nested_invalid_draft_returns_a_diagnostic_without_exhausting_the_stack ... ok
+test global_identity_and_alias_collisions_include_diagram_nodes_and_project ... ok
+test malformed_json_never_ignores_duplicate_maps_or_nested_unknown_fields ... ok
+test original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensions ... ok
+test text_and_model_transactions_preserve_history_with_stable_aliases ... ok
+test tolerant_analysis_covers_unicode_and_returns_declarations_before_failure ... ok
+test typed_events_vertices_and_transition_kinds_round_trip_in_both_layers ... ok
+test typed_scenario_messages_values_and_reuse_round_trip_in_both_work_perspectives ... ok
+test typed_unknown_fields_enums_duplicates_and_reference_spelling_are_rejected ... ok
+test v1_repeated_exchange_occurrences_and_authored_aliases_survive ... ok
+test v1_text_and_json_migrate_deterministically_without_losing_legacy_semantics ... ok
+
+test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.96s
+
+ Running tests\port_anchors.rs (.local\target-gnullvm\debug\deps\port_anchors-d5a981eee97b6682.exe)
+
+running 13 tests
+test every_side_uses_exact_final_border_fraction_and_outward_routing_normal ... ok
+test invalid_anchors_and_payloads_fail_atomically_including_duplicate_owner_placements ... ok
+test malformed_source_anchor_records_have_located_errors ... ok
+test many_neutral_ports_balance_capacity_without_a_giant_bottom_footer ... ok
+test narrow_peer_gap_uses_clear_side_instead_of_a_blocked_facing_stub ... ok
+test nested_owner_bounds_group_movement_and_authored_routes_share_the_same_geometry ... ok
+test neutral_connected_ports_face_peers_and_unconnected_ports_use_four_sides ... ok
+test port_upsert_reset_and_undo_preserve_unrelated_node_fields_and_list_order ... ok
+test reidentification_deletion_reuse_and_owner_repair_preserve_correct_identity ... ok
+test resetting_last_source_anchors_preserves_comments_and_can_add_a_new_manual_record ... ok
+test separate_views_resize_layout_and_hidden_owners_keep_manual_metadata ... ok
+test source_json_roundtrip_defaults_and_admission_include_anchor_records ... ok
+test source_updater_keeps_comment_attachment_through_identity_swap_and_anchor_reordering ... ok
+
+test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.26s
+
+ Running tests\port_migration.rs (.local\target-gnullvm\debug\deps\port_migration-57b7ebd0823fc2a7.exe)
+
+running 8 tests
+test frozen_original_v2_reader_preserves_every_original_record ... ok
+test generated_alias_collision_is_deterministically_suffixed ... ok
+test generated_endpoint_collision_in_any_object_category_is_located_and_rejected ... ok
+test legacy_oa_inout_ports_attachments_and_traces_remain_unclassified ... ok
+test loading_v2_does_not_rewrite_original_bytes ... ok
+test migration_admission_counts_generated_objects_records_and_references ... ok
+test missing_v2_endpoints_are_deterministic_reported_and_independently_typed ... ok
+test old_contract_is_validated_before_new_shapes_and_v1_flows_through_v2 ... ok
+
+test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.11s
+
+ Running tests\port_source.rs (.local\target-gnullvm\debug\deps\port_source-f086cdb039d4e546.exe)
+
+running 3 tests
+test source4_covers_typed_contracts_roles_allocations_and_nested_legacy_references ... ok
+test source4_rejects_mixed_component_kinds_and_nested_reference_typos ... ok
+test source4_requires_explicit_port_contract_and_fe_endpoints_without_generation ... ok
+
+test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s
+
+ Running tests\prepared_commands.rs (.local\target-gnullvm\debug\deps\prepared_commands-886b16f33cc6f1e4.exe)
+
+running 7 tests
+test all_edit_commands_record_their_implicit_identity_change ... ok
+test deletion_and_cascade_discard_origins_before_uuid_reuse ... ok
+test embedded_diagram_replacement_cannot_revive_deleted_node_origin ... ok
+test explicit_chains_and_nested_swaps_compose_original_identity ... ok
+test failed_preparation_leaves_input_unchanged_after_intermediate_identity_work ... ok
+test fresh_creation_has_no_origin_even_when_subsequently_reidentified ... ok
+test replacement_resets_correspondence_against_the_original_input ... ok
+
+test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.78s
+
+ Running tests\process_diagnostics.rs (.local\target-gnullvm\debug\deps\process_diagnostics-61c82df507b624ee.exe)
+
+running 1 test
+test sequence_gaps_name_occurrences_exchanges_and_endpoints ... ok
+
+test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s
+
+ Running tests\r3_constructor_ack.rs (.local\target-gnullvm\debug\deps\r3_constructor_ack-0a72505cb81ae402.exe)
+
+running 8 tests
+test equal_constructor_candidate_requires_independent_selection_of_changed_descendants ... ok
+test first_constructor_acknowledgement_agrees_only_on_its_canonical_discriminator_slot ... ok
+test first_equal_constructor_acknowledges_only_discriminator_in_every_family_and_direction ... ok
+test historical_switches_retain_groups_and_completed_requests_still_reject_in_both_directions ... ok
+test independent_constructor_and_leaf_selections_complete_missing_or_empty_baselines ... ok
+test migrated_equal_constructor_can_complete_semantic_review_without_model_changes ... ok
+test one_sided_or_disappeared_message_constructor_is_not_an_equal_scalar_acknowledgement ... ok
+test reset_equal_constructor_does_not_refresh_previously_reviewed_descendants ... ok
+
+test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.06s
+
+ Running tests\r3_migration.rs (.local\target-gnullvm\debug\deps\r3_migration-8b4f10639d05f128.exe)
+
+running 5 tests
+test authored_v4_modules_upgrade_only_the_version_and_preserve_nested_comments ... ok
+test frozen_v4_json_and_source_preserve_the_old_review_without_fabricating_semantics ... ok
+test frozen_v4_trace_envelope_keeps_duplicate_and_unknown_field_rejection ... ok
+test older_headers_cannot_admit_new_semantic_baselines ... ok
+test original_v1_v2_v3_inputs_complete_the_explicit_upgrade_to_five ... ok
+
+test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.30s
+
+ Running tests\r3_semantic.rs (.local\target-gnullvm\debug\deps\r3_semantic-e75f0d913fd711fc.exe)
+
+running 35 tests
+test artifact_creation_requires_reviewed_metadata_and_deletion_retires_exact_facts ... ok
+test artifact_metadata_is_attached_and_cannot_last_write_through_two_owner_traces ... ok
+test artifact_owner_candidate_moves_link_and_conflicts_with_kept_attachment ... ok
+test atomic_annotation_full_path_updates_only_selected_row ... ok
+test atomic_binding_updates_local_and_rejects_pair_collision_and_remapping_without_reset ... ok
+test changed_state_kind_remains_reviewable_in_both_directions ... ok
+test child_membership_and_parent_resolutions_agree_by_actual_child_slot ... ok
+test combined_ordered_membership_commutes_or_requires_agreed_anchors ... ok
+test constructor_closure_does_not_restore_previously_corresponding_removed_members ... ok
+test constructor_closure_pairs_signal_members_and_requires_history_reset_in_both_directions ... ok
+test constructor_closure_preserves_target_only_literals_in_both_directions ... ok
+test constructor_retirement_cannot_override_other_sources_kept_bound_in_either_order ... ok
+test constructor_union_conflicts_with_another_sources_new_member_in_either_order ... ok
+test constructors_expand_into_independent_facts_without_outer_prose ... ok
+test enum_members_preserve_target_only_and_no_whole_list_overwrite ... ok
+test explicit_trace_selection_keeps_independent_trace_baselines ... ok
+test historical_reference_remap_preserves_text_and_deleted_reference_observations ... ok
+test independent_bounds_keep_take_and_stale_prepare ... ok
+test keeping_keyed_rows_with_an_unrelated_header_candidate_preserves_exact_authored_order ... ok
+test ordered_member_requires_anchor_or_explicit_order_and_preserves_target_only ... ok
+test prepared_inherited_annotation_cleanup_must_be_explicitly_accepted ... ok
+test repeated_keep_of_unequal_constructor_pair_cannot_swallow_bound_drift ... ok
+test retired_source_branch_retains_historical_member_pair_in_both_directions ... ok
+test reverse_existing_port_review_rejects_typed_oa_proposals_without_mutation ... ok
+test reverse_existing_port_review_retains_legacy_and_roundtrips_typed_history ... ok
+test reverse_new_port_counterpart_remains_an_explicit_sa_only_omission ... ok
+test same_target_constructor_preserves_local_members_and_reset_precedes_expansion ... ok
+test same_target_member_keys_cannot_last_write_win ... ok
+test semantic_batch_is_one_undo_and_replanning_acknowledged_values_is_noop ... ok
+test semantic_rollup_does_not_claim_unchanged_when_only_unit_is_reviewed ... ok
+test shared_definition_drift_is_linked_without_copying_definition_into_field_baseline ... ok
+test source_and_target_semantic_headers_can_be_reviewed_in_reverse ... ok
+test state_guard_and_deleted_trigger_membership_preserve_target_only_trigger ... ok
+test target_only_artifact_move_records_owner_and_metadata_in_either_order ... ok
+test wrong_family_both_na_and_duplicate_resolutions_rejected_atomically ... ok
+
+test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.08s
+
+ Running tests\r3_source.rs (.local\target-gnullvm\debug\deps\r3_source-bb0c42d15bbf39bc.exe)
+
+running 7 tests
+test facet_reordering_and_reidentification_keep_comments_with_typed_row_identity ... ok
+test historical_reference_deletion_does_not_remove_the_review_or_create_a_live_alias ... ok
+test historical_uuid_and_enum_tokens_are_contextual_and_text_remains_quoted ... ok
+test included_historical_review_edits_save_and_reopen_without_rewriting_other_modules ... ok
+test prepared_semantic_review_writes_back_and_preserves_the_accepted_target_difference ... ok
+test semantic_values_remain_closed_typed_records_in_both_formats ... ok
+test typed_historical_facets_roundtrip_deleted_references_and_all_leaf_categories ... ok
+
+test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.22s
+
+ Running tests\r6_data.rs (.local\target-gnullvm\debug\deps\r6_data-abc398e60ae89711.exe)
+
+running 16 tests
+test a_self_association_is_visible_with_neutral_connector_and_real_role_contracts ... ok
+test ambiguous_legacy_annotations_warn_and_reference_fields_stop_payload_descent ... ok
+test an_empty_enumeration_is_a_located_incomplete_draft ... ok
+test empty_record_collections_are_supported_but_record_literals_and_reference_defaults_are_not ... ok
+test enum_literal_edits_validate_type_and_field_defaults_in_one_candidate ... ok
+test exponential_payload_expansion_is_reported_and_recursive_finite_paths_remain_valid ... ok
+test identified_allocations_keep_protocols_order_artifacts_and_inherited_identity ... ok
+test inheritance_chain_validation_scaling_probe ... ignored, explicit inheritance scaling probe; run with --ignored --nocapture
+test inherited_interfaces_and_item_specializations_satisfy_typed_port_contracts ... ok
+test initial_transition_copies_qualified_paths_inheritance_allocations_and_associations ... ok
+test recursive_value_types_warn_while_optional_reference_cycles_and_associations_are_distinct ... ok
+test removing_inheritance_prunes_legacy_annotations_only_when_every_reachable_path_disappears ... ok
+test repeated_record_paths_enforce_call_reply_phase_and_survive_identity_changes ... ok
+test required_dictionary_closure_does_not_expand_through_incident_associations ... ok
+test scalar_bounds_defaults_unicode_units_and_failed_batches_are_exact ... ok
+test specialization_preserves_fields_rejects_cycles_and_prunes_removed_inherited_paths ... ok
+
+test result: ok. 15 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 5.14s
+
+ Running tests\r6_migration.rs (.local\target-gnullvm\debug\deps\r6_migration-c8aa91bbc2f4cb02.exe)
+
+running 7 tests
+test allocation_alias_collisions_are_stable_and_duplicate_legacy_payload_fields_reject ... ok
+test deterministic_allocation_id_collision_is_located_without_overwriting_any_object ... ok
+test genuine_v3_preserves_typed_ports_and_allocates_deterministic_relationships ... ok
+test recursive_legacy_record_paths_remain_ambiguous_instead_of_becoming_one_argument ... ok
+test unique_legacy_nested_value_is_qualified_and_physical_nature_is_not_guessed ... ok
+test v1_and_frozen_v2_inputs_reach_four_through_the_shared_data_upgrade ... ok
+test v3_changed_payloads_reject_schema4_fields_and_new_constructor_names ... ok
+
+test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.32s
+
+ Running tests\r6_source.rs (.local\target-gnullvm\debug\deps\r6_source-fc413074bb8b6a9c.exe)
+
+running 5 tests
+test authored_v3_bundle_upgrade_preserves_includes_nested_ownership_comments_and_identity ... ok
+test inferring_a_legacy_path_keeps_rationale_inside_its_argument ... ok
+test omitted_ids_are_stabilized_before_allocation_identity_generation ... ok
+test qualified_value_reordering_and_path_reidentification_keep_each_arguments_comments ... ok
+test schema4_typed_values_supertype_paths_and_protocols_roundtrip ... ok
+
+test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.77s
+
+ Running tests\release1_teaching.rs (.local\target-gnullvm\debug\deps\release1_teaching-817593550c6aba50.exe)
+
+running 4 tests
+test release1_teaching_preserves_explicit_transport_direction_reuse_and_view_identity ... ok
+test release1_teaching_regeneration_and_shipped_source_are_exact_with_one_intentional_warning ... ok
+test release1_teaching_rejects_guessed_ports_and_wrong_performer_without_changing_source ... ok
+test release1_teaching_review_changes_only_the_selected_counterpart_and_preserves_modules ... ok
+
+test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.40s
+
+ Running tests\release_capabilities.rs (.local\target-gnullvm\debug\deps\release_capabilities-2cfe40068aacbf30.exe)
+
+running 7 tests
+test capability_and_mission_links_enforce_direction_family_and_layer ... ok
+test capability_semantics_remain_edges_independent_of_visual_containment ... ok
+test conditional_extension_remains_editable_and_condition_is_validated ... ok
+test inclusion_and_specialization_reject_transitive_cycles_atomically ... ok
+test mission_map_transition_preserves_all_relationship_endpoints_and_conditions_both_ways ... ok
+test mission_reidentification_and_deletion_preserve_reference_integrity_and_undo ... ok
+test operational_missions_have_typed_bidirectional_counterparts_and_legacy_choices ... ok
+
+test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.56s
+
+ Running tests\release_diagrams.rs (.local\target-gnullvm\debug\deps\release_diagrams-410080fa9b665574.exe)
+
+running 4 tests
+test architecture_is_nested_exchange_context_and_ports_attach_without_placements ... ok
+test port_identity_collapse_and_group_movement_do_not_invent_semantic_edges ... ok
+test unplaced_work_parent_cannot_detach_a_leaf_from_its_visible_performer ... ok
+test view_filtering_keeps_placements_and_source_and_separates_capability_content ... ok
+
+test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.24s
+
+ Running tests\release_language_limits.rs (.local\target-gnullvm\debug\deps\release_language_limits-aa543b58fa1e4ac8.exe)
+
+running 4 tests
+test declaration_limits_precede_reference_resolution_and_structural_validation ... ok
+test json_record_and_step_limits_precede_model_errors_and_preserve_the_candidate ... ok
+test legacy_budgets_include_generated_occurrences_and_placements_before_migration ... ok
+test lint_reports_located_engineering_warnings_without_rejecting_valid_drafts ... ok
+
+test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.44s
+
+ Running tests\release_ports.rs (.local\target-gnullvm\debug\deps\release_ports-5189ef11700a47ae.exe)
+
+running 13 tests
+test ambiguous_reuse_creates_a_fresh_port_and_collisions_never_overwrite ... ok
+test component_generation_requires_real_allocations_and_reuses_existing_facts ... ok
+test deferred_deletion_preserves_preview_explicit_deletion_and_reidentification ... ok
+test deletion_preserves_independent_ports_but_removes_required_dependents ... ok
+test directed_legacy_resolution_retains_unclassified_interface_and_role_identity ... ok
+test functional_creation_reuse_and_item_sync_are_atomic_and_identity_safe ... ok
+test generated_ports_respect_authored_exchange_aliases ... ok
+test invalid_replacement_rolls_back_without_repairing_explicit_replacement_projects ... ok
+test legacy_split_preserves_original_identity_prose_interface_artifact_constraint_and_trace ... ok
+test projection_anchors_are_contract_aware_and_unoriented_connections_keep_their_identity ... ok
+test replacement_port_allocation_retains_exchange_mapping_and_evidence_in_either_order ... ok
+test standard_interface_roles_do_not_follow_message_direction_and_kind_mixing_is_rejected ... ok
+test transitions_create_sa_ports_and_omit_sa_implementation_from_portless_oa ... ok
+
+test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.78s
+
+ Running tests\release_routing.rs (.local\target-gnullvm\debug\deps\release_routing-bea7071d5be1912a.exe)
+
+running 13 tests
+test component_ports_use_actual_owners_in_movement_and_folded_lookup ... ok
+test finite_boundary_values_and_temporarily_hidden_edges_remain_editable ... ok
+test group_movement_translates_internal_routes_once_and_keeps_external_pins ... ok
+test group_translation_beyond_coordinate_limit_rolls_back_nodes_and_routes ... ok
+test hiding_or_relocating_a_node_does_not_erase_route_metadata ... ok
+test invalid_routes_reject_atomically_with_a_located_diagnostic ... ok
+test layout_preserves_authored_route_count_order_labels_and_undo ... ok
+test old_models_get_empty_routes_and_route_json_is_strict ... ok
+test reidentify_and_delete_preserve_or_clean_routes_with_the_semantic_edge ... ok
+test route_edits_are_one_history_step_and_identical_edits_preserve_redo ... ok
+test route_upsert_preserves_other_entries_and_accepts_an_explicit_empty_default ... ok
+test single_endpoint_raw_edits_and_differing_group_deltas_keep_world_pins ... ok
+test trace_routes_follow_trace_identity_and_cleanup_without_extra_objects ... ok
+
+test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.47s
+
+ Running tests\release_scenarios.rs (.local\target-gnullvm\debug\deps\release_scenarios-a97ddc158e8fd821.exe)
+
+running 14 tests
+test calls_and_replies_have_real_direction_correlation_and_order ... ok
+test conditional_and_parallel_replies_cannot_answer_one_call_twice_but_alternatives_can ... ok
+test conditional_lifetime_changes_conflict_with_later_or_parallel_use_not_exclusive_use ... ok
+test create_delete_and_local_timer_messages_enforce_lifetime_rules ... ok
+test deleting_reused_scenarios_or_bound_lifelines_preserves_callers_and_undo ... ok
+test effective_message_endpoints_are_shared_for_forward_and_reverse_reply_choices ... ok
+test loop_fragment_repetition_is_distinct_from_recursive_scenario_use ... ok
+test optional_cleanup_does_not_claim_a_repeated_lifetime_is_balanced ... ok
+test repeated_lifecycle_requires_per_iteration_balance_without_prohibiting_balanced_loops ... ok
+test replies_repeated_without_their_call_warn_in_each_extra_loop_scope ... ok
+test reuse_rejects_wrong_bindings_recursive_calls_and_wrong_viewpoints ... ok
+test reused_scenarios_bind_existing_roles_and_transition_the_reference_closure ... ok
+test role_lifelines_require_explicit_work_assignment_not_just_a_shared_realizing_entity ... ok
+test value_annotations_are_bound_to_carried_fields_and_all_references_follow_identity_changes ... ok
+
+test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.28s
+
+ Running tests\release_states.rs (.local\target-gnullvm\debug\deps\release_states-0d0025b63e4b31b9.exe)
+
+running 11 tests
+test boundary_points_enforce_inside_outside_local_routing_and_one_route_per_region ... ok
+test choices_and_junctions_report_unfinished_guards_but_reject_conflicting_defaults_or_triggers ... ok
+test event_reference_identity_cleanup_and_undo_preserve_triggering_intent ... ok
+test every_vertex_is_an_editable_draft_but_only_stable_states_own_behavior_or_regions ... ok
+test fork_join_detect_concurrency_at_the_common_composite_not_deep_immediate_regions ... ok
+test histories_remain_per_region_with_shallow_direct_and_deep_nested_fallbacks ... ok
+test initial_final_and_terminate_reject_forbidden_routes_and_cardinality_atomically ... ok
+test machine_subtrees_and_selected_edges_transition_events_work_and_payloads_in_both_directions ... ok
+test reachability_follows_nested_default_entry_and_reports_disconnected_vertices ... ok
+test transition_kinds_distinguish_internal_external_self_and_local_composite_routing ... ok
+test typed_events_trigger_alternatives_and_effects_validate_without_owner_restrictions ... ok
+
+test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.45s
+
+ Running tests\release_topology.rs (.local\target-gnullvm\debug\deps\release_topology-5e64e3a9873d768b.exe)
+
+running 6 tests
+test branching_reusable_processes_transition_as_contextual_topology_in_both_directions ... ok
+test contextual_links_match_work_exchange_endpoints_and_reject_foreign_owners ... ok
+test legacy_linear_exchange_only_sequences_keep_their_existing_meaning ... ok
+test loops_are_allowed_while_missing_guards_and_disconnected_paths_are_diagnosed ... ok
+test reuse_rejects_recursive_or_cross_layer_targets_and_deleted_targets_become_drafts ... ok
+test topology_identity_edits_and_deletions_preserve_remaining_paths_and_undo ... ok
+
+test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.55s
+
+ Running tests\review_regressions.rs (.local\target-gnullvm\debug\deps\review_regressions-66d7a72d208de855.exe)
+
+running 15 tests
+test an_explicit_merged_destination_stays_reviewed_on_repeated_preview ... ok
+test capability_mission_transition_remains_an_explicit_choice_in_both_directions ... ok
+test checked_save_rejects_external_changes_and_preserves_their_exact_bytes ... ok
+test checked_save_rejects_external_deletion_without_recreating_the_file ... ok
+test checked_save_returns_exact_bytes_and_accepts_unchanged_external_content ... ok
+test destination_only_changes_remain_available_for_reverse_propagation ... ok
+test divergent_changes_on_both_sides_still_require_review ... ok
+test factory_and_add_commands_share_fragment_branch_defaults ... ok
+test invalid_placement_relocations_preserve_model_artifacts_and_redo ... ok
+test keeping_divergent_values_is_idempotent_in_both_directions ... ok
+test later_source_changes_propagate_without_overwriting_other_reviewed_fields ... ok
+test relocating_a_placement_preserves_identity_artifacts_and_history ... ok
+test same_parent_reparent_preserves_fragment_branch_order_and_history ... ok
+test same_parent_reparent_preserves_sequence_order_revision_and_redo ... ok
+test unowned_state_machine_is_valid_incomplete_work_in_both_layers ... ok
+
+test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.39s
+
+ Running tests\route_source.rs (.local\target-gnullvm\debug\deps\route_source-04d120b08d6d38c4.exe)
+
+running 5 tests
+test invalid_route_source_has_located_errors_and_no_silent_nested_defaults ... ok
+test omitted_schema_three_routes_default_to_empty_and_v2_reader_stays_frozen ... ok
+test route_commands_json_and_one_step_undo_recover_exact_model_and_canonical_source ... ok
+test route_records_share_model_admission_for_source_json_and_bundles ... ok
+test route_source_json_and_bundle_roundtrips_preserve_order_defaults_and_edge_aliases ... ok
+
+test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.73s
+
+ Running tests\routing_geometry.rs (.local\target-gnullvm\debug\deps\routing_geometry-640bafaf2e3d1fb1.exe)
+
+running 18 tests
+test authored_pins_keep_traversal_order_even_when_reversing_and_repeated ... ok
+test automatic_label_fraction_survives_its_first_authored_drag_without_a_jump ... ok
+test blocked_pin_is_retained_and_warned_without_nan_or_diagonal_fallback ... ok
+test bottom_standard_ports_are_approached_from_below ... ok
+test collapse_suspends_only_promoted_endpoints_and_never_changes_stored_route ... ok
+test compressed_grid_search_finds_multi_turn_exit_from_a_narrow_corridor ... ok
+test deterministic_under_input_and_route_list_permutations ... ok
+test excessive_search_grid_falls_back_with_an_explicit_warning_and_preserved_endpoints ... ok
+test exhausted_search_budget_survives_label_and_pin_previews ... ok
+test functional_endpoints_preserve_real_ports_and_normals_while_detouring ... ok
+test interpolation_projection_empty_and_repeated_points_are_well_defined ... ok
+test label_geometry_and_bounds_are_shared_exact_and_unicode_safe ... ok
+test nested_endpoint_enclosure_is_traversable_but_its_header_and_siblings_are_not ... ok
+test nonport_relations_try_clear_alternate_sides_and_label_only_supported_roles ... ok
+test routing_100_nodes_150_edges_has_bounded_work_and_reports_actual_time ... ok
+test single_edge_preview_matches_settled_geometry_and_preserves_other_routes ... ok
+test single_pin_above_an_oa_interaction_makes_a_detour_without_a_retraced_spur ... ok
+test workshop_automatic_labels_are_readable_without_mutating_authored_routes ... ok
+
+test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 32.40s
+
+ Running tests\routing_labels.rs (.local\target-gnullvm\debug\deps\routing_labels-9815c14b9074e8c6.exe)
+
+running 2 tests
+test authored_overlap_is_exact_and_reset_only_repositions_automatic_text ... ok
+test demo_architecture_labels_do_not_cover_earlier_or_later_connection_strokes ... ok
+
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
+
+ Running tests\semantic_artifact_absence_oracle.rs (.local\target-gnullvm\debug\deps\semantic_artifact_absence_oracle-1a3d73f91af62a41.exe)
+
+running 2 tests
+test moved_away_source_artifact_counts_as_absent_from_reviewed_owner ... ok
+test target_only_artifact_take_preserves_without_fabricated_source_identity ... ok
+
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s
+
+ Running tests\semantic_attachments_gui_oracle.rs (.local\target-gnullvm\debug\deps\semantic_attachments_gui_oracle-aa43e037c3033729.exe)
+
+running 4 tests
+test attachment_candidate_and_direct_record_keep_conflict_by_actual_slot_in_either_order ... ok
+test attachment_candidate_changes_existing_parent_or_endpoint_without_attached_record_trace ... ok
+test convergent_owner_attachment_candidates_agree_and_keep_cannot_override_them ... ok
+test incident_absence_take_refuses_but_explicit_candidate_endpoint_is_reviewable ... ok
+
+test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.53s
+
+ Running tests\source6_view_contract.rs (.local\target-gnullvm\debug\deps\source6_view_contract-f91bdc53472d7a21.exe)
+
+running 8 tests
+test source6_category_and_saved_selection_reidentify_delete_preserve_empty_filters ... ok
+test source6_category_transition_requires_mapped_members_and_reviews_exact_set ... ok
+test source6_edge_availability_uses_actual_shared_projection_fold_and_placement_rules ... ok
+test source6_new_historical_facts_are_rejected_under_source5_even_without_live_categories ... ok
+test source6_projection_filters_keep_placements_and_label_modes_are_presentation_only ... ok
+test source6_traceability_default_keeps_both_layers_and_indexed_projection_matches ... ok
+test source6_view_defaults_cannot_be_smuggled_under_old_json_headers ... ok
+test source6_views_round_trip_strict_fields_and_reference_aliases ... ok
+
+test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.48s
+
+ Running tests\source7_contract.rs (.local\target-gnullvm\debug\deps\source7_contract-29c604533629d07b.exe)
+
+running 16 tests
+test source7_allocation_removal_requires_independent_channel_repair_and_keeps_values ... ok
+test source7_assembly_purposes_do_not_invent_precedence_or_global_coverage ... ok
+test source7_context_lifecycle_source_history_and_closed_admission ... ok
+test source7_context_resource_limits_are_per_request_and_count_authored_paths ... ok
+test source7_operation_allocation_delete_repairs_both_call_and_separate_reply_channels ... ok
+test source7_operation_inherited_exact_allocation_is_not_a_same_item_substitute ... ok
+test source7_operation_reverse_unoriented_call_and_reply_use_actual_binding ... ok
+test source7_operation_transmission_matrix_rejects_mismatch_and_warns_unspecified ... ok
+test source7_ordinary_old_calls_keep_bytes_and_explicit_null_is_not_old_admission ... ok
+test source7_parameterless_operations_have_exact_protocol_identity_and_deletion_intent ... ok
+test source7_repeated_nested_qualified_precedence_and_cache_are_distinct ... ok
+test source7_reply_derives_call_signature_and_void_return_channel_still_carries_operation ... ok
+test source7_selected_operation_requires_channel_lifelines_and_proven_direction ... ok
+test source7_semantic_facets_retain_typed_context_and_operation_references ... ok
+test source7_source_bundle_migration_is_token_only_and_new_link_stays_with_caller ... ok
+test source7_transition_preserves_qualified_paths_and_requires_reviewed_operation_omission ... ok
+
+test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.44s
+
+ Running tests\source_files.rs (.local\target-gnullvm\debug\deps\source_files-c259adb8020b7e75.exe)
+
+running 8 tests
+test aggregate_budgets_and_portable_paths_are_checked_before_loading_excess_data ... ok
+test discovery_never_refreshes_drafts_and_preserves_detached_buffers ... ok
+test forged_compiled_model_or_index_cannot_be_saved ... ok
+test invalid_and_missing_includes_keep_editable_text_and_located_diagnostics ... ok
+test loading_keeps_original_no_uuid_bytes_and_saving_stabilizes_exactly_once ... ok
+test new_destinations_require_explicit_missing_expectations_and_reject_collisions ... ok
+test physical_hard_link_aliases_are_rejected ... ok
+test unchanged_dependency_edits_and_deletions_block_save_before_any_write ... ok
+
+test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.42s
+
+ Running tests\source_identity_updates.rs (.local\target-gnullvm\debug\deps\source_identity_updates-ed289dca0ae67049.exe)
+
+running 3 tests
+test identity_and_alias_edit_keep_authored_file_and_reference_rationale ... ok
+test identity_swap_and_reorder_keep_both_declarations_and_item_comments_with_origin ... ok
+test provenance_cannot_merge_records_or_cross_record_classes ... ok
+
+test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.58s
+
+ Running tests\source_nested_updates.rs (.local\target-gnullvm\debug\deps\source_nested_updates-af2536c4b0fc948c.exe)
+
+running 12 tests
+test additions_nest_in_existing_and_new_owners_while_authored_flat_records_stay_flat ... ok
+test compact_same_file_reparent_and_unnest_preserve_explicit_parent_comments ... ok
+test crlf_moved_and_new_children_preserve_authored_and_destination_line_endings ... ok
+test cross_file_move_carries_attached_comments_and_subtree_but_leaves_gap_notes ... ok
+test deep_structural_writeback_runs_on_a_small_thread_stack ... ok
+test deleted_owner_identity_reused_by_new_owner_does_not_keep_old_lexical_ownership ... ok
+test deleting_owner_retains_annotations_and_extracts_independently_moved_descendant ... ok
+test independent_descendant_move_can_reverse_old_containment_without_overlapping_edits ... ok
+test moving_a_child_keeps_the_owners_opening_line_comment_with_its_owner ... ok
+test nested_scalar_edits_touch_only_authored_tokens ... ok
+test nested_typed_reference_list_updates_keep_item_rationale_while_port_moves ... ok
+test nested_uuid_alias_swap_and_cross_file_move_follow_command_provenance ... ok
+
+test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 18.71s
+
+ Running tests\source_project.rs (.local\target-gnullvm\debug\deps\source_project-70a23d8ae64722e2.exe)
+
+running 33 tests
+test added_list_items_empty_lists_and_literal_changes_preserve_existing_annotations ... ok
+test advisory_lint_locations_refer_to_raw_drafts_before_identity_stabilization ... ok
+test alias_updates_touch_only_header_and_reference_tokens_even_with_comments_after_at ... ok
+test canonical_export_groups_semantic_roots_ports_and_placements_and_is_deterministic ... ok
+test complete_demo_preserves_behavior_artifacts_traces_and_every_placement ... ok
+test constructor_tag_changes_retain_shared_nested_fields_and_removed_field_comments ... ok
+test cross_file_typed_ports_global_forward_references_and_stable_ids ... ok
+test duplicate_alias_and_identity_report_both_files_including_project_identity ... ok
+test duplicate_include_edges_count_toward_admission_and_ordered_references_are_aggregate ... ok
+test exported_case_colliding_root_aliases_are_portable_and_large_root_sets_are_bounded ... ok
+test failed_update_preserves_candidate_and_rejects_stale_index ... ok
+test generated_id_insertion_handles_adjacent_header_tokens_and_remains_exactly_indexed ... ok
+test graphical_move_updates_only_its_owned_view_document ... ok
+test include_inspection_returns_located_failure_without_partial_discovery_and_is_bounded ... ok
+test include_inspection_reuses_strict_entry_fragment_syntax_and_real_spans ... ok
+test includes_are_once_only_and_cycles_missing_and_unreachable_files_are_located ... ok
+test invalid_cross_file_route_references_and_geometry_locate_the_authored_view ... ok
+test json_bundle_rejects_duplicate_file_keys_and_unknown_fields ... ok
+test kind_change_removes_field_syntax_but_keeps_its_comments_and_other_fields ... ok
+test model_updates_rewrite_cross_file_alias_uses_preserve_ownership_and_untouched_comments ... ok
+test moving_declarations_between_files_changes_no_semantics_or_ids ... ok
+test nested_message_value_edits_preserve_field_comments_and_reordering_uses_field_identity ... ok
+test new_children_inherit_owner_file_new_roots_add_include_and_deletion_keeps_files ... ok
+test omitted_defaults_stay_omitted_and_new_fields_do_not_reformat_surviving_fields ... ok
+test portable_paths_reject_escapes_devices_urls_and_case_collisions ... ok
+test project_admission_is_aggregate_and_precedes_global_resolution ... ok
+test reference_list_reordering_carries_item_comments_and_alias_spelling_with_identity ... ok
+test removed_list_items_and_declarations_keep_comments_as_enclosing_annotations ... ok
+test route_edits_keep_diagram_file_ownership_and_comments_with_edge_identity ... ok
+test scalar_header_guard_and_project_edits_preserve_exact_crlf_trivia_and_escape_spelling ... ok
+test scalar_record_constructor_changes_keep_comments_inside_removed_values ... ok
+test single_string_and_legacy_apis_remain_explicit_and_filesystem_free ... ok
+test source_errors_keep_exact_fragment_unicode_positions ... ok
+
+test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.13s
+
+ Running tests\state_diagrams.rs (.local\target-gnullvm\debug\deps\state_diagrams-c385a34d3448c847.exe)
+
+running 13 tests
+test contexts_restrict_membership_without_changing_legacy_hidden_placements ... ok
+test cross_machine_reparent_requires_atomic_placement_and_route_repair ... ok
+test empty_and_partial_views_are_read_only_and_omit_unrelated_edges ... ok
+test folding_retains_authored_routes_without_false_self_transitions ... ok
+test grouped_moves_translate_hidden_descendants_and_pins_only_in_one_view ... ok
+test initial_transition_attaches_to_the_compact_symbol_and_preview_keeps_settled_geometry ... ok
+test layout_reserves_internal_rows_and_state_routes_retain_hard_pins ... ok
+test moving_a_region_does_not_translate_pins_attached_to_a_stationary_state_boundary ... ok
+test real_state_symbols_boundaries_internal_rows_and_self_loops_share_geometry ... ok
+test reconnect_keeps_identity_and_validates_replacement_without_double_counting ... ok
+test remapping_and_machine_deletion_include_dedicated_views_and_dependents ... ok
+test source_updates_preserve_context_identity_and_unrelated_authored_bytes ... ok
+test strict_context_wire_source_and_admission_contract ... ok
+
+test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.61s
+
+ Running tests\workflows.rs (.local\target-gnullvm\debug\deps\workflows-cf933e047345cb8a.exe)
+
+running 28 tests
+test artifacts_preserve_arbitrary_uri_and_revision_without_fetch ... ok
+test automatic_transition_does_not_accept_arbitrary_target_kinds ... ok
+test batch_is_one_undo_and_noop_does_not_change_revision ... ok
+test bidirectional_transition_creates_reuses_and_updates_one_history_transaction ... ok
+test both_side_transition_conflicts_require_explicit_field_resolution ... ok
+test create_occurrence_attaches_once_and_repeats_exchange_by_distinct_ids ... ok
+test deleting_source_cascades_owned_records_but_keeps_other_layer_counterpart ... ok
+test demo_has_both_layers_complete_contracts_and_no_checks ... ok
+test demo_is_valid_and_preserves_uuid_and_alias_through_json ... ok
+test every_demo_identity_can_be_remapped_and_deleted_without_dangling_references ... ok
+test explicit_dependencies_map_chain_order_and_repeated_occurrences ... ok
+test failed_batch_preserves_model_history_revision_and_redo ... ok
+test identity_aliases_and_map_keys_share_global_namespace ... ok
+test invalid_geometry_and_new_payload_fields_are_rejected ... ok
+test items_ports_interfaces_and_exchange_allocation_are_typed ... ok
+test many_traces_need_explicit_counterpart_and_layout_does_not_cause_drift ... ok
+test nested_projection_encloses_children_and_moves_only_selected_diagram ... ok
+test nested_work_is_independent_of_leaf_allocation ... ok
+test parent_and_endpoint_queries_match_creation_rules ... ok
+test parent_cycles_cross_layer_ownership_and_multiple_performers_reject ... ok
+test participant_transition_requires_boundary_decision_and_stale_plans_reject ... ok
+test reidentify_updates_containment_sequences_traces_artifacts_and_views_atomically ... ok
+test reidentifying_data_types_and_function_ports_updates_every_typed_reference ... ok
+test relation_deletion_prunes_owned_occurrences_and_undo_restores_order ... ok
+test rename_and_collision_insertion_preserve_existing_aliases ... ok
+test scenario_lifelines_and_occurrences_require_same_context ... ok
+test shared_roles_remain_valid_without_an_invented_unique_performer ... ok
+test state_regions_initial_final_and_typed_actions_validate ... ok
+
+test result: ok. 28 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.16s
+
+ Doc-tests oa_core
+
+running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
+
diff --git a/harness/experiments/text-navigation-components-20260911/components/evidence-manifest.json b/harness/experiments/text-navigation-components-20260911/components/evidence-manifest.json
new file mode 100644
index 00000000..c1be64ec
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/evidence-manifest.json
@@ -0,0 +1,270 @@
+{
+ "scope": "Small exact private outline/query integration evidence; excludes executable binaries and duplicate full input copies",
+ "records": [
+ {
+ "path": "artifacts1-status.json",
+ "bytes": 299,
+ "sha256": "6c162b92780232b5b3bfd24159cfef52d3bb4fb786485572f091ec2383735053"
+ },
+ {
+ "path": "artifacts1-stderr.log",
+ "bytes": 61,
+ "sha256": "d8b07b9fdfbce4832604bee2b69736dbb7054a3433d972cb15ad8c8fdc7a65c6"
+ },
+ {
+ "path": "artifacts1-stdout.log",
+ "bytes": 35777,
+ "sha256": "5b4441c8f4862d0bad438bbc8b8dddd3d4628586283c0add445248b4a12d85be"
+ },
+ {
+ "path": "clippy-tests2-status.json",
+ "bytes": 299,
+ "sha256": "04910578164f5dce73f8348caccd0590ac9f24150e2b331c554b9c287f2dc191"
+ },
+ {
+ "path": "clippy-tests2.log",
+ "bytes": 151,
+ "sha256": "78c52948b2dd53aca55947ccd1039fec7e054f0d8e61de56b9bd54960e883f61"
+ },
+ {
+ "path": "clippy1-status.json",
+ "bytes": 291,
+ "sha256": "60818516ec45371a8f4766b4936e11d7b53552f1d1e423846d5c02b55a9acea6"
+ },
+ {
+ "path": "clippy1.log",
+ "bytes": 151,
+ "sha256": "5d3c946198a0870c7245b8ebd8eecef6bff21765f6d98a72221dd1dded3e233e"
+ },
+ {
+ "path": "compiler1-status.json",
+ "bytes": 191,
+ "sha256": "865fbfc81fa2e58b5b8d6a9d666c18dfb6515f129a026f9cc5df2db6a94b01d9"
+ },
+ {
+ "path": "compiler1-stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "compiler1-stdout.log",
+ "bytes": 197,
+ "sha256": "cdaaa400be47332d69e678836251d4635b5ff2ecd34aa962b69d71f8961d274b"
+ },
+ {
+ "path": "core1-status.json",
+ "bytes": 264,
+ "sha256": "fcd9fae539fa0f82ce0906576971e0b16aec709155363dc1f0a4a201b986edb9"
+ },
+ {
+ "path": "core1.log",
+ "bytes": 48639,
+ "sha256": "4556049fbfc4ccecc118cad85afb2b1f92af10bfd787ed416ec2aa4389b7bf24"
+ },
+ {
+ "path": "final-readback.json",
+ "bytes": 1775,
+ "sha256": "29a07e2be5b608d4950b6728398d8f64c0c5c7cb38a014c8dde821ee4b18a390"
+ },
+ {
+ "path": "fingerprints/oa-core-781c6973417b169c/lib-oa_core",
+ "bytes": 16,
+ "sha256": "32d7fadb58b89d0877ab923522a0bbb715ac42c34526de01cd3cec0be61df760"
+ },
+ {
+ "path": "fingerprints/oa-core-781c6973417b169c/lib-oa_core.json",
+ "bytes": 776,
+ "sha256": "7664e5d9695d989d99873c6937409b188ee00489f6a808051d8fbafdaf826baa"
+ },
+ {
+ "path": "fingerprints/serde_json-b48005beba7e4267/lib-serde_json",
+ "bytes": 16,
+ "sha256": "9202101a31af6f8d24edceacbb8300c10dddd94fb496cbf6451c8860133ce784"
+ },
+ {
+ "path": "fingerprints/serde_json-b48005beba7e4267/lib-serde_json.json",
+ "bytes": 826,
+ "sha256": "310bedb04f5f35eef3a04e8019f0a204cc0f30b94ca808cb4d702cd7c6403737"
+ },
+ {
+ "path": "inputs-supplement.json",
+ "bytes": 388,
+ "sha256": "480e04d38ec1fda35727cc0b6f2f2837964484c90cffc0199bfe5bcef4236745"
+ },
+ {
+ "path": "inputs.json",
+ "bytes": 61752,
+ "sha256": "e0dd3e617c1c365e1d315b498ce032f375b734341d2eb87d6dc89867b636196e"
+ },
+ {
+ "path": "language_outline_tests-pre-encoding-fix.rs",
+ "bytes": 14922,
+ "sha256": "f64bd89959554cd5842609c031d442f3d9e27b73a216dd936715284c9fe9fb97"
+ },
+ {
+ "path": "literal-byte-readback.json",
+ "bytes": 1291,
+ "sha256": "0030e88a1a74e2bbf25c1bb3b7dad6dc25ce0477762f8cb71a5034344573d577"
+ },
+ {
+ "path": "outline1-status.json",
+ "bytes": 312,
+ "sha256": "3efbfd96f6b5921fa2079b8732d3c32705528989051629b357fb389b4a20933c"
+ },
+ {
+ "path": "outline1.log",
+ "bytes": 1593,
+ "sha256": "2582a9e5894d4caee7d092e6b0aa15bd02f49261a6beebaaca91d94affae9230"
+ },
+ {
+ "path": "outline2-inputs.json",
+ "bytes": 399,
+ "sha256": "5eeddbcef8643a75b1582575089405f717bae429c9bf3bf7614a846c712d45ec"
+ },
+ {
+ "path": "outline2-status.json",
+ "bytes": 312,
+ "sha256": "434a9410460a7c4bf1dedf948a76015b0b0cc63ae19843b834fd02df44ed6d1c"
+ },
+ {
+ "path": "outline2.log",
+ "bytes": 1593,
+ "sha256": "0e045f4287675661fde873950bce2387584b377ee747b663fa41ec7ffacd1bc7"
+ },
+ {
+ "path": "query-artifacts3.json",
+ "bytes": 206,
+ "sha256": "25298a1f6b40fd0e60e4bcbda610c81dc851ec37e4d0d58c00099ca1b4128018"
+ },
+ {
+ "path": "query-binding1-failure.json",
+ "bytes": 905,
+ "sha256": "b41e3478707a7b3aeb91ee9fd62ca3c2ee45401aff615fd202ffed7fc13c43b1"
+ },
+ {
+ "path": "query-bindings.json",
+ "bytes": 6727,
+ "sha256": "158f1c1797abb634f74b2cbc8f09656871d999bfb6aaa9220feacb443bc48889"
+ },
+ {
+ "path": "query-compile1-status.json",
+ "bytes": 987,
+ "sha256": "a3f9acff4713d9491cbaf7d66180d36e19fa44f8617d6449f9d7f8b4c5a8a383"
+ },
+ {
+ "path": "query-compile1-stderr.log",
+ "bytes": 1857,
+ "sha256": "82b4baefdbb06c4b744cb750268c02dd0d540c0ae77dbd36359403b03dfbf5d0"
+ },
+ {
+ "path": "query-compile1-stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "query-compile2-status.json",
+ "bytes": 997,
+ "sha256": "a64df6b29650a5b9a4d990e1c6ef65a22c8b0068b0e2fa151f912116521b7884"
+ },
+ {
+ "path": "query-compile2-stderr.log",
+ "bytes": 2819,
+ "sha256": "b0206bb2a0be17e6e6c27934566d145425bcf99cb3cc22f7f15143a5733d44fc"
+ },
+ {
+ "path": "query-compile2-stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "query-compile3-status.json",
+ "bytes": 1009,
+ "sha256": "8bff40eed5dbf499ea41119cc2696fe998a7071ac5ce15fecf447d6a86eb127b"
+ },
+ {
+ "path": "query-compile3-stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "query-compile3-stdout.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "query-source-reconstruction.json",
+ "bytes": 673,
+ "sha256": "7692eaf4ed52e0de5f95d1ea26b0b3c3d91b8b89cda7a0c0198394a0403ea03e"
+ },
+ {
+ "path": "query-tests3-status.json",
+ "bytes": 332,
+ "sha256": "e2edbb9cdb986ba5215a53830393e2c11f5d8e428a81ebbaede0e06bb395db9d"
+ },
+ {
+ "path": "query-tests3-stderr.log",
+ "bytes": 0,
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ {
+ "path": "query-tests3-stdout.log",
+ "bytes": 1785,
+ "sha256": "faec6eb5a3be074a755dfb0ce0bf7b34d2358ff543e0999ee51a27d7a8bcabc9"
+ },
+ {
+ "path": "query-type-correction.json",
+ "bytes": 414,
+ "sha256": "eb3675d734354dd8dcb2682b77276d4f2d08790fa98c20c045b0286959ecfcf7"
+ },
+ {
+ "path": "run-gates.ps1",
+ "bytes": 1231,
+ "sha256": "070c986b1ca9de6c01750597d68478201736093b70bba76812752cafb522e867"
+ },
+ {
+ "path": "run-query-resume.ps1",
+ "bytes": 5572,
+ "sha256": "07c707cfe4c143af8a64a5ba88de24c866d9a9929f23206f39e21df589c0977b"
+ },
+ {
+ "path": "run-query-typed.ps1",
+ "bytes": 3119,
+ "sha256": "92cde88b43382b6b7557bf341d7168f98e3838d3d2f3bd25e802780d400cb05c"
+ },
+ {
+ "path": "run-query-utf8.ps1",
+ "bytes": 3107,
+ "sha256": "ebb7b16ed431df7df6885a5aff6dbbe55db5b01cc899821e1a4af665ed2d3c18"
+ },
+ {
+ "path": "run-query.ps1",
+ "bytes": 4240,
+ "sha256": "c112d1a496ed9d39e202f7704548863af45e532dfe89c675bb84b2569201b1ec"
+ },
+ {
+ "path": "run-utf8-outline-gates.ps1",
+ "bytes": 1505,
+ "sha256": "742695aa1b1aadebfb475306fdcbfbb31af5a7f5dd4920e7c5c49df757d89962"
+ },
+ {
+ "path": "source_symbols-v2-utf8-typed.rs",
+ "bytes": 54554,
+ "sha256": "fe37cd55b95c892c05aec6c37a113154cab7646dcfc333646fa8218345919751"
+ },
+ {
+ "path": "source_symbols-v2-utf8.rs",
+ "bytes": 54542,
+ "sha256": "86666c8390f0c63ec75b045366494a80d0ecde82924f2bac312afd01675aee56"
+ },
+ {
+ "path": "source_symbols-v2.rs",
+ "bytes": 54637,
+ "sha256": "8f3f20a7e72c870cfd1ad46f0aa8b8998ffc1ebf3357911957ed4995758e7520"
+ },
+ {
+ "path": "utf8-reconstruction-correction.json",
+ "bytes": 776,
+ "sha256": "3140aab739ad4351f8538b8169b3fe972b975138cc95c6e12be82a0d5f76c7a3"
+ }
+ ]
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/final-readback.json b/harness/experiments/text-navigation-components-20260911/components/final-readback.json
new file mode 100644
index 00000000..4d047a8a
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/final-readback.json
@@ -0,0 +1,51 @@
+{
+ "status": "PASS",
+ "unchangedOriginalInputs": 321,
+ "expectedFixtureCorrection": [
+ {
+ "path": "crates/oa-core/src/language_outline_tests.rs",
+ "beforeSha256": "f64bd89959554cd5842609c031d442f3d9e27b73a216dd936715284c9fe9fb97",
+ "finalSha256": "4c79a01e8aea243dd9ef66ce71b9fae263d0e79c319a46c51223679897e8b9df"
+ }
+ ],
+ "documentationSupplementUnchanged": true,
+ "languageSha256": "34cbb144a111e6f0ddadfca7d40636a8e3323fe65ea8920fd2402f0352b41f57",
+ "outputs": {
+ "outline2.log": {
+ "bytes": 1593,
+ "sha256": "0e045f4287675661fde873950bce2387584b377ee747b663fa41ec7ffacd1bc7"
+ },
+ "outline2-status.json": {
+ "bytes": 312,
+ "sha256": "434a9410460a7c4bf1dedf948a76015b0b0cc63ae19843b834fd02df44ed6d1c"
+ },
+ "clippy-tests2.log": {
+ "bytes": 151,
+ "sha256": "78c52948b2dd53aca55947ccd1039fec7e054f0d8e61de56b9bd54960e883f61"
+ },
+ "clippy-tests2-status.json": {
+ "bytes": 299,
+ "sha256": "04910578164f5dce73f8348caccd0590ac9f24150e2b331c554b9c287f2dc191"
+ },
+ "query-compile3-status.json": {
+ "bytes": 1009,
+ "sha256": "8bff40eed5dbf499ea41119cc2696fe998a7071ac5ce15fecf447d6a86eb127b"
+ },
+ "query-tests3-status.json": {
+ "bytes": 332,
+ "sha256": "e2edbb9cdb986ba5215a53830393e2c11f5d8e428a81ebbaede0e06bb395db9d"
+ },
+ "query-tests3-stdout.log": {
+ "bytes": 1785,
+ "sha256": "faec6eb5a3be074a755dfb0ce0bf7b34d2358ff543e0999ee51a27d7a8bcabc9"
+ },
+ "query-bindings.json": {
+ "bytes": 6727,
+ "sha256": "158f1c1797abb634f74b2cbc8f09656871d999bfb6aaa9220feacb443bc48889"
+ },
+ "literal-byte-readback.json": {
+ "bytes": 1291,
+ "sha256": "0030e88a1a74e2bbf25c1bb3b7dad6dc25ce0477762f8cb71a5034344573d577"
+ }
+ }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core b/harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core
new file mode 100644
index 00000000..67882b93
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core
@@ -0,0 +1 @@
+bb7457aac905dc51
\ No newline at end of file
diff --git a/harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core.json b/harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core.json
new file mode 100644
index 00000000..6a3cd552
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/fingerprints/oa-core-781c6973417b169c/lib-oa_core.json
@@ -0,0 +1 @@
+{"rustc":4368751908343638535,"features":"[]","declared_features":"[]","target":9792512432450288461,"profile":12077771321704829354,"path":10192304750368139731,"deps":[[4012234191921133045,"thiserror",false,6221096245441968322],[5330460842384404171,"serde_json",false,10762123116135201565],[6557439603276904804,"serde",false,8207651326488808113],[9723370144619655183,"tempfile",false,6264596951560906265],[11781824977070132858,"same_file",false,3903024514762273217],[14063412941180217548,"uuid",false,16344938119731331185],[14493392674156086091,"build_script_build",false,13821171186339835210]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\oa-core-781c6973417b169c\\dep-lib-oa_core","checksum":false}}],"rustflags":[],"config":1025291897514690248,"compile_kind":0}
\ No newline at end of file
diff --git a/harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json b/harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json
new file mode 100644
index 00000000..455c422f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json
@@ -0,0 +1 @@
+1d3b4236f4bd5a95
\ No newline at end of file
diff --git a/harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json.json b/harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json.json
new file mode 100644
index 00000000..37af1d6f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/fingerprints/serde_json-b48005beba7e4267/lib-serde_json.json
@@ -0,0 +1 @@
+{"rustc":4368751908343638535,"features":"[\"default\", \"std\"]","declared_features":"[\"alloc\", \"arbitrary_precision\", \"default\", \"float_roundtrip\", \"indexmap\", \"preserve_order\", \"raw_value\", \"std\", \"unbounded_depth\"]","target":9592559880233824070,"profile":8378823585564974145,"path":9516600172453656966,"deps":[[5330460842384404171,"build_script_build",false,10302394721460756435],[5532778797167691009,"itoa",false,14930288972773478100],[11029742160753049355,"serde_core",false,12627368256302415275],[12613788554453945248,"memchr",false,17882262603682400281],[16226529040278277557,"zmij",false,18174765404489670566]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\serde_json-b48005beba7e4267\\dep-lib-serde_json","checksum":false}}],"rustflags":[],"config":1025291897514690248,"compile_kind":0}
\ No newline at end of file
diff --git a/harness/experiments/text-navigation-components-20260911/components/inputs-supplement.json b/harness/experiments/text-navigation-components-20260911/components/inputs-supplement.json
new file mode 100644
index 00000000..65b61f68
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/inputs-supplement.json
@@ -0,0 +1,8 @@
+{
+ "scope": "Supplement discovered by include_str audit during full-core compilation, after focused outline gate. Not retroactively part of the original pre-gate manifest.",
+ "path": "docs/language.rst",
+ "bytes": 130753,
+ "sha256": "ac6b1d982c55f246de2f3c844b24b874599a3c20cfbd7ed22efe4561ac622b83",
+ "matchesBaseGitBlobExactly": true,
+ "matchesBaseAfterCRLFNormalization": true
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/inputs.json b/harness/experiments/text-navigation-components-20260911/components/inputs.json
new file mode 100644
index 00000000..dab4e5a6
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/inputs.json
@@ -0,0 +1,1616 @@
+{
+ "base": "b2c3e54001fc19a05423bcd5808ceea78d13ff59",
+ "scope": "Raw current core sources, workspace manifests/lock and fixture/example inputs preserved before main-working-tree Windows gates; root holds overlapping core edits",
+ "files": [
+ {
+ "path": ".local/toolchain/env.ps1",
+ "bytes": 509,
+ "sha256": "916001e8486b7dba0d00f612aa788bef0c197ac71bab345603eba1dbdb25b0bd"
+ },
+ {
+ "path": "Cargo.lock",
+ "bytes": 119453,
+ "sha256": "2b960242c33eb839ff008d01731f6d5cdb4e047ca8cd6e13151cbf9c09913510"
+ },
+ {
+ "path": "Cargo.toml",
+ "bytes": 400,
+ "sha256": "369f5b4b2b60705a94623c8c0b68f8e0b048f263280ce07ae0629ad353797d62"
+ },
+ {
+ "path": "crates/oa-cli/Cargo.toml",
+ "bytes": 279,
+ "sha256": "49c567f1af74a826683b4d96845feb3806fd9bcb7dc2767e16b5509d4e5e1cde"
+ },
+ {
+ "path": "crates/oa-core/build.rs",
+ "bytes": 45,
+ "sha256": "ab1be6ae1fd8bf3ebaeef6348347a9d5e666bde693126f199e041659e0b094a9"
+ },
+ {
+ "path": "crates/oa-core/Cargo.toml",
+ "bytes": 342,
+ "sha256": "64824ec447be777841a972bf06c894ef2624a37e4725583bcfe0389deb9f00a3"
+ },
+ {
+ "path": "crates/oa-core/examples/generate_demo.rs",
+ "bytes": 795,
+ "sha256": "e26f432c95331aeca50985dbb3ad2c558affd43023b585811f0f7bfaa59f6661"
+ },
+ {
+ "path": "crates/oa-core/examples/generate_release1.rs",
+ "bytes": 1798,
+ "sha256": "1d5f11165d74893e4bc18dbf3a2f2111f4026bd89fd06ee7f0bae69e45c4d4b5"
+ },
+ {
+ "path": "crates/oa-core/examples/release_dense_views.rs",
+ "bytes": 14664,
+ "sha256": "a83158488ef9b7394f136c52b1496719be935c4e5bdfe457cab5c9894f5f9412"
+ },
+ {
+ "path": "crates/oa-core/examples/routing_workshop.rs",
+ "bytes": 17248,
+ "sha256": "571307b18b6d4fa04700c5a4d4b0c2154c419e93a21292b2028e7b6f46a2faa7"
+ },
+ {
+ "path": "crates/oa-core/src/data.rs",
+ "bytes": 42696,
+ "sha256": "7f9c7c7dd8353cb5a38597b92cde6d3e890203d392473187eb387d41ff37414a"
+ },
+ {
+ "path": "crates/oa-core/src/demo.rs",
+ "bytes": 20657,
+ "sha256": "ad62123f96c20ff023cafc1a9849b0fa06bc62edd1fb9701799e4599b458600c"
+ },
+ {
+ "path": "crates/oa-core/src/language/semantic_source.rs",
+ "bytes": 7326,
+ "sha256": "584b5bb59d87a7b8d44d5dd2d124c3a1323b21492b138a17bc9526c0087c9e29"
+ },
+ {
+ "path": "crates/oa-core/src/language.rs",
+ "bytes": 105312,
+ "sha256": "34cbb144a111e6f0ddadfca7d40636a8e3323fe65ea8920fd2402f0352b41f57"
+ },
+ {
+ "path": "crates/oa-core/src/language_nesting_tests.rs",
+ "bytes": 20639,
+ "sha256": "bd061611e8cb6f28ab203c73e544ac98bf275eb464cd314ad5fbd25f773df33b"
+ },
+ {
+ "path": "crates/oa-core/src/language_outline_tests.rs",
+ "bytes": 14922,
+ "sha256": "f64bd89959554cd5842609c031d442f3d9e27b73a216dd936715284c9fe9fb97"
+ },
+ {
+ "path": "crates/oa-core/src/lib.rs",
+ "bytes": 523,
+ "sha256": "52cb6ee7a4ffc00711ecf121c1a1b14ca154863f7ca27b0d2a8e239edce8aca7"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/adapter.rs",
+ "bytes": 9490,
+ "sha256": "66741b4c8354a23b8a932ecc5bf1bc3bb6e74279558757b8c35bc2157a7e51b5"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/mod.rs",
+ "bytes": 1158,
+ "sha256": "857d8bb13b6814daba6780ca770c16352943c01cb130245d055f11b3b957b3e9"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/model.rs",
+ "bytes": 52059,
+ "sha256": "8ac63eb59b2e57bdecff2477ee0634096090924d3dd1851e1c454880a889dacb"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/README.md",
+ "bytes": 1515,
+ "sha256": "fb61d3faa33d7bdfba73415415a0882c0fbd3e0b45df42f972a19aec6da927b9"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/references.rs",
+ "bytes": 13369,
+ "sha256": "33a6819abc83139fdaebeebd1b553238a328637106cce7dd5a535f1791d43e20"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/source.rs",
+ "bytes": 21946,
+ "sha256": "74fee9b24e7c912182c998ab9d9aeec125e2009c3214959229e844c67afe2bf6"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/state_validation.rs",
+ "bytes": 15769,
+ "sha256": "e29ef071bab0a68460ddf55c46131c6f66c022d52c3c602c37c013c3649da1b0"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/transitions.rs",
+ "bytes": 704,
+ "sha256": "f74174c532ba56b57c80b719a11fb4adea0a78ccc2828fff72fe2b9eda5a10b1"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/validation.rs",
+ "bytes": 98736,
+ "sha256": "6d5e4a4e2805ae5045bf789a0485e14a61c63346587bace2b4a91173a688f780"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v3/mod.rs",
+ "bytes": 23023,
+ "sha256": "f4edb6a0b50ecfe8670973776d703047b064420cb1f05913129c00fc656b3722"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v3/README.md",
+ "bytes": 2128,
+ "sha256": "3755a63f840d7f0eadf68afa7a9d587877a9468ff982d3610539e8a0bb067a82"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v4.rs",
+ "bytes": 4020,
+ "sha256": "50c652964f0f309e7f3f54b6ef12c40d42fcecc0490f005995c713df1179af70"
+ },
+ {
+ "path": "crates/oa-core/src/model.rs",
+ "bytes": 66352,
+ "sha256": "4585c895c64c4a183941c92cf047a9e52ff0cbe41971e16e78d229ea9dc101fb"
+ },
+ {
+ "path": "crates/oa-core/src/operations.rs",
+ "bytes": 11405,
+ "sha256": "a5ffbbf54cd440316a106d1188bfb699b57dae59f466edcb33618c333cd19f41"
+ },
+ {
+ "path": "crates/oa-core/src/persistence.rs",
+ "bytes": 31774,
+ "sha256": "1c01474f906a63e552c0d590b272c95821c00a100b3e79e58e67dbbeac341a14"
+ },
+ {
+ "path": "crates/oa-core/src/ports.rs",
+ "bytes": 45413,
+ "sha256": "5cac12eea70a54ebebbd6be77b63ce92e3195325c4bc7d27705c518686cbac80"
+ },
+ {
+ "path": "crates/oa-core/src/projection/port_layout.rs",
+ "bytes": 13474,
+ "sha256": "a08d17d10a58bb8b206dd001b19b91d5aa3a9f743b298a4da57d503223c20079"
+ },
+ {
+ "path": "crates/oa-core/src/projection/state.rs",
+ "bytes": 5233,
+ "sha256": "73a607bac713ec7ff49a509fefb1a53c7686dca93ee241ffc835564d743afb28"
+ },
+ {
+ "path": "crates/oa-core/src/projection.rs",
+ "bytes": 21374,
+ "sha256": "69ed12bace2cc5024e151752a174ab14034ac2d05eb741fcf13e9a27521c059b"
+ },
+ {
+ "path": "crates/oa-core/src/references.rs",
+ "bytes": 21957,
+ "sha256": "93553c234f32da35f1f41a7b83ad81c471c9803efa78ca222cf7de3c2f86c0bb"
+ },
+ {
+ "path": "crates/oa-core/src/routing.rs",
+ "bytes": 50309,
+ "sha256": "c5ebb350ba76f731f5c48d968bfbba4b6ef68ec046eb5893d116c4637ef15080"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/catalog.rs",
+ "bytes": 49407,
+ "sha256": "1ff74fcca18084d178681d7ff38ee1ccac632c27c4de0595ba1ceab8067ab3a4"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/historical.rs",
+ "bytes": 15271,
+ "sha256": "eaf3284bf234312698514cdb636bc8348e1b5d746cb16b18df777e77d312abfb"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/linked.rs",
+ "bytes": 6035,
+ "sha256": "6f335b14e809aecfc35defcaf77cb821b080d1506dbc46297912b25af98c07a2"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/mutate.rs",
+ "bytes": 22683,
+ "sha256": "dc2b4c4a6eb156fe468eaa8c85fd37a505c0cd8ea1f226cb69cd01689afd3d9d"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/review.rs",
+ "bytes": 51116,
+ "sha256": "0c157d2c327e72e89cca3b423e71ee73798105c5bb7271439c1a101642c91e3e"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/types.rs",
+ "bytes": 10405,
+ "sha256": "6922fe35089f12fad43ab0ce1c6886d01a3855f90b8e2543e99802ac5e6f1bbb"
+ },
+ {
+ "path": "crates/oa-core/src/semantic.rs",
+ "bytes": 540,
+ "sha256": "f9895dd5b8fb27d10d4cfbb41c3542ef2018365a589b8d5778e5674ae5a1f239"
+ },
+ {
+ "path": "crates/oa-core/src/session.rs",
+ "bytes": 35237,
+ "sha256": "1eb77b329b2986fc6f5dc0171e77fd860375cdfd8c769a1367b445d6c49ebe00"
+ },
+ {
+ "path": "crates/oa-core/src/source_files.rs",
+ "bytes": 74803,
+ "sha256": "669491dbddb8e8e6556161426dffa1a49ac1c826d31020bff0956d81a4d84683"
+ },
+ {
+ "path": "crates/oa-core/src/source_project/source_migration.rs",
+ "bytes": 4700,
+ "sha256": "add463ae85c420f853d324b00a221f0f874164cc3f3ed6bbb07557a9ed7a8c95"
+ },
+ {
+ "path": "crates/oa-core/src/source_project/source_structure.rs",
+ "bytes": 20225,
+ "sha256": "9f1f83315856a7025fe662c0fbb13452f31613224a03663150d6225d1ddc162f"
+ },
+ {
+ "path": "crates/oa-core/src/source_project.rs",
+ "bytes": 61843,
+ "sha256": "1394370bcc5908873b830c2c11312e6e1611af41f46c4f43ca0a6bcb713bb466"
+ },
+ {
+ "path": "crates/oa-core/src/state_validation.rs",
+ "bytes": 15665,
+ "sha256": "e28d7f74385e2a78763716b34491bd73a00bfeccc085970ceb130627e3faee18"
+ },
+ {
+ "path": "crates/oa-core/src/topology_context/analysis.rs",
+ "bytes": 20036,
+ "sha256": "dead6194f0aa60cc46045c0e5ee10dbdbb689158c38634dfbbbc45d2ba88ac65"
+ },
+ {
+ "path": "crates/oa-core/src/topology_context/analysis_tests.rs",
+ "bytes": 5258,
+ "sha256": "17839263410815e96fe027d4eba3aae0a9ab3b82b16947e2b29ebe0d09782b56"
+ },
+ {
+ "path": "crates/oa-core/src/topology_context.rs",
+ "bytes": 13734,
+ "sha256": "16de69b297de95d93c73808621607833b713af811dd082f0169186ae1e5c3e2d"
+ },
+ {
+ "path": "crates/oa-core/src/transitions/communication.rs",
+ "bytes": 8634,
+ "sha256": "2b476630a88edd9021b9156ca12a5402e01604095f0d4002afb452671903a028"
+ },
+ {
+ "path": "crates/oa-core/src/transitions.rs",
+ "bytes": 51998,
+ "sha256": "fadd1b07b89cae4117b91abb4f1ac9ea81ff818ef20f2175e76ee8cf21fb708d"
+ },
+ {
+ "path": "crates/oa-core/src/validation/coverage.rs",
+ "bytes": 6478,
+ "sha256": "b08e4b344ecad055714168c83beff7fe29ba4edc53edbffa2eae04e5580a4d5f"
+ },
+ {
+ "path": "crates/oa-core/src/validation.rs",
+ "bytes": 112792,
+ "sha256": "7c1b4ae5e4ebba13e99af9158ccde138dfc5e9a7afb580e76fba72d0b64ba74e"
+ },
+ {
+ "path": "crates/oa-core/src/view_options.rs",
+ "bytes": 24481,
+ "sha256": "421ede0a04c593ca1df0e6e0d06fabcfcc8113cdbcfb0adc7f5c8ef4d1724208"
+ },
+ {
+ "path": "crates/oa-core/src/view_options_tests.rs",
+ "bytes": 21433,
+ "sha256": "64aac44de2df42a1012239a3dd75c473daf0c8990b3170b7bf7e78a556080208"
+ },
+ {
+ "path": "crates/oa-core/tests/communication_boundary_scenarios.rs",
+ "bytes": 8816,
+ "sha256": "e1100b8f363910a487d90c73acc7df543dc6d532158c3c63ad11cd3d40d8e12b"
+ },
+ {
+ "path": "crates/oa-core/tests/communication_means.rs",
+ "bytes": 18102,
+ "sha256": "a25f2749180bc6f4beb1445bfad3351eedf0669131a7439b633ab804439e95a4"
+ },
+ {
+ "path": "crates/oa-core/tests/coverage_diagnostics.rs",
+ "bytes": 20527,
+ "sha256": "e2e9c3996daab47f2bd23f5d8c48b4e372af33331152f5fc85b76f06d04fcdf4"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v2-coastal-rescue.arcadia",
+ "bytes": 29220,
+ "sha256": "f0df129c1914bfc01d0b047b0cd4b520d8cf0c249ea9ced085de400c0be87ff3"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v2-coastal-rescue.json",
+ "bytes": 43990,
+ "sha256": "3ed53508eb544bbd788832f7bebea2ec31b3d66191f24f45098cc9750ec4c005"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v3-data-contract.arcadia",
+ "bytes": 2450,
+ "sha256": "305f19d1b79f99f8d4cf627ba2e9ca6836bb7959cc0070227815937a699eafea"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v3-data-contract.json",
+ "bytes": 8240,
+ "sha256": "1b41299c53204e6232397d25a58d5bccdbbc16e770c75e8edb55ce36ccfe58a2"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v4-reviewed-prose.arcadia",
+ "bytes": 1222,
+ "sha256": "5fa53a26a5ad010c8e59eb56d9b2b52003569e66c0377ffb58840590168ad0c1"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v4-reviewed-prose.json",
+ "bytes": 1734,
+ "sha256": "2808088afcc165f4bd6da5129151143411c997041c3de61d600ab76e05d24a47"
+ },
+ {
+ "path": "crates/oa-core/tests/language.rs",
+ "bytes": 56482,
+ "sha256": "77f6cb2a69976bfb5f60b243b71c07fb0edc7505002c1e9e73fcd6ddf6d9496e"
+ },
+ {
+ "path": "crates/oa-core/tests/port_anchors.rs",
+ "bytes": 26893,
+ "sha256": "efbc0fa18733b1aaac501f9785a4c392fc49c05c45a5778d26e9b5dd951ca6fa"
+ },
+ {
+ "path": "crates/oa-core/tests/port_migration.rs",
+ "bytes": 15620,
+ "sha256": "1801c376a5435ac7efb2a8342348b2c7ffef930bba65deca4acb66b06e0483d7"
+ },
+ {
+ "path": "crates/oa-core/tests/port_source.rs",
+ "bytes": 4858,
+ "sha256": "8d2ddaca4045d330f6eda2591561497b858aad53769a823249d0bea4f5916e12"
+ },
+ {
+ "path": "crates/oa-core/tests/prepared_commands.rs",
+ "bytes": 9555,
+ "sha256": "52cab1c668064777412df0a6a3c20d74d579b33794b4b00525b9f151c06eac9b"
+ },
+ {
+ "path": "crates/oa-core/tests/process_diagnostics.rs",
+ "bytes": 1104,
+ "sha256": "1dbbc590239ef7542be4a2bdc9543aaa64d108417b03dd312a0894e364745906"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_constructor_ack.rs",
+ "bytes": 24163,
+ "sha256": "e5c44676ea0aab97f2e091ddac2f4bc4a4a90f5877f7bbf08b61229600944dd0"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_migration.rs",
+ "bytes": 6590,
+ "sha256": "ebd04120f5215b3956fdc79ee78f6e38734c7d13392f1fc28aaba957912265bd"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_semantic.rs",
+ "bytes": 77473,
+ "sha256": "49c3763eaf2131070a7c46cc23a84f917d292d41610b6404510e5607f8890c32"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_source.rs",
+ "bytes": 15311,
+ "sha256": "f31f60659df3c7ef601650d1cff140589d8729f01a3f65a4a6a6c2272f15c408"
+ },
+ {
+ "path": "crates/oa-core/tests/r6_data.rs",
+ "bytes": 41437,
+ "sha256": "5f7ffb3bc73c3c85423623fe56f8680646dc97040327bdbead9d47a37e80f155"
+ },
+ {
+ "path": "crates/oa-core/tests/r6_migration.rs",
+ "bytes": 9174,
+ "sha256": "81519bc1512f5e069432e9a1cdad04c10216d492df01b4b75b02102480ebd6a1"
+ },
+ {
+ "path": "crates/oa-core/tests/r6_source.rs",
+ "bytes": 9519,
+ "sha256": "88a0aa087a7678e2898ff6057538072ca92072190712f05fff08372cc56ebed6"
+ },
+ {
+ "path": "crates/oa-core/tests/release1_teaching.rs",
+ "bytes": 10010,
+ "sha256": "be179290d871125dc97cfd2a3067c7ac89c8a538fdb0afafc5b825bcdfb2bef2"
+ },
+ {
+ "path": "crates/oa-core/tests/release_capabilities.rs",
+ "bytes": 19422,
+ "sha256": "b92cba2a562e78287ddd7dc3e94557848d17faa0c8a102601b50286b9af48a30"
+ },
+ {
+ "path": "crates/oa-core/tests/release_diagrams.rs",
+ "bytes": 7689,
+ "sha256": "cefcc0582d91266d5de6ee5daefcfe41b606ab971ab235b59034a8e1dbc24b7d"
+ },
+ {
+ "path": "crates/oa-core/tests/release_language_limits.rs",
+ "bytes": 5204,
+ "sha256": "8578da4f76bbd2d0e1bfe295ab1259e2162a3fe3d0d2ba8901915d7a40942806"
+ },
+ {
+ "path": "crates/oa-core/tests/release_ports.rs",
+ "bytes": 32740,
+ "sha256": "506e62beab0c7ec0cece83e9e0c1a810fa1f99285c9e1cab0c77f06de55a40e1"
+ },
+ {
+ "path": "crates/oa-core/tests/release_routing.rs",
+ "bytes": 23165,
+ "sha256": "ae168faaba23769b7ca1fd65f217b9db671726e67276eb5a04e5e72b1447a462"
+ },
+ {
+ "path": "crates/oa-core/tests/release_scenarios.rs",
+ "bytes": 41067,
+ "sha256": "0f5d876badeec653fc7b12610a77d867e9a53582e605253deff8d379da21572f"
+ },
+ {
+ "path": "crates/oa-core/tests/release_states.rs",
+ "bytes": 26873,
+ "sha256": "b1a26acee95eaba7f9920f9da738a1e33606552eb078e6d298895daff7e3d811"
+ },
+ {
+ "path": "crates/oa-core/tests/release_topology.rs",
+ "bytes": 17643,
+ "sha256": "c51dbe5aa5a038f9ed830daa02aa67714472179eb7fd6da5ce36dc6931c8b3a9"
+ },
+ {
+ "path": "crates/oa-core/tests/review_regressions.rs",
+ "bytes": 21823,
+ "sha256": "f154e5c88d35e31512e2b07733a1327ea3e2fcfd1ef8eb23066a4e9573f387c1"
+ },
+ {
+ "path": "crates/oa-core/tests/route_source.rs",
+ "bytes": 7270,
+ "sha256": "1a73fdbac0d0ae0e84d8d67b6ad398e11223958f5dca007f73757c91827decfb"
+ },
+ {
+ "path": "crates/oa-core/tests/routing_geometry.rs",
+ "bytes": 31890,
+ "sha256": "2d8eea4a2bba535ed0ea6daabccdaf87b375828c404baa10518c7bd7fd6253d3"
+ },
+ {
+ "path": "crates/oa-core/tests/routing_labels.rs",
+ "bytes": 5530,
+ "sha256": "dff884d799c3c5c9257210248c55767e294e3cb317c605cbd0ddc9600c216eb6"
+ },
+ {
+ "path": "crates/oa-core/tests/semantic_artifact_absence_oracle.rs",
+ "bytes": 4640,
+ "sha256": "d93eaf9667f4221232168bef0fe9f069e5be1b1f23410bda5dd67010b96e5c8a"
+ },
+ {
+ "path": "crates/oa-core/tests/semantic_attachments_gui_oracle.rs",
+ "bytes": 14957,
+ "sha256": "7f526d363ba05e7ac89faf0fead0796e419c34ee204edf44a1b46c78308b0970"
+ },
+ {
+ "path": "crates/oa-core/tests/source6_view_contract.rs",
+ "bytes": 14307,
+ "sha256": "fa566de53946d40bbbd9033edfeed177180c4dc44afa2f2f4667ffdc622eb3bb"
+ },
+ {
+ "path": "crates/oa-core/tests/source7_contract.rs",
+ "bytes": 43262,
+ "sha256": "cad4f1a135ddbd947bdf3c71d1b44f1e438725095ea8c4d893464bc721c2363f"
+ },
+ {
+ "path": "crates/oa-core/tests/source_files.rs",
+ "bytes": 13168,
+ "sha256": "f0429a0b89a443458ea9af7083c1e4e7411417a7e6b47e618bfbb9266dc164f9"
+ },
+ {
+ "path": "crates/oa-core/tests/source_identity_updates.rs",
+ "bytes": 5139,
+ "sha256": "f52aa38b4d317e532c641ad7463472e4b40e802e60052cf0017556ffc63da06c"
+ },
+ {
+ "path": "crates/oa-core/tests/source_nested_updates.rs",
+ "bytes": 16892,
+ "sha256": "9ac3390936c0d250651ea94a1b8faf0adfb680f061ececc56f2a52909b5ba855"
+ },
+ {
+ "path": "crates/oa-core/tests/source_project.rs",
+ "bytes": 45910,
+ "sha256": "cef58adb16aa4d2527ddc0628aec3e26f0b42b4be70014a7e360fafdac13a942"
+ },
+ {
+ "path": "crates/oa-core/tests/state_diagrams.rs",
+ "bytes": 23973,
+ "sha256": "6b02b387a3ae559cb3ad72de24292a5abcb76aaac0adbab5d2aa5211b22cd2e5"
+ },
+ {
+ "path": "crates/oa-core/tests/workflows.rs",
+ "bytes": 33426,
+ "sha256": "bd843dfddbd5724ec08f657571f887496203056fe2fa5864ef50a168a613b89b"
+ },
+ {
+ "path": "crates/oa-desktop/Cargo.toml",
+ "bytes": 842,
+ "sha256": "f3547b2b924d5db6706c0a8e069014b440ff65315d82fc8aced0360af4b3cdc9"
+ },
+ {
+ "path": "crates/oa-server/Cargo.toml",
+ "bytes": 778,
+ "sha256": "527ff5e15010a8b560a6659aa1ae4bf0c8df0d9e019f03c47529ec6a750c0982"
+ },
+ {
+ "path": "examples/coastal-rescue.arcadia",
+ "bytes": 33107,
+ "sha256": "7e183ef9fd8154d3074303f1956cd0d9871c19e3ffc630d333b0bffbd6e30e17"
+ },
+ {
+ "path": "examples/coastal-rescue.json",
+ "bytes": 49715,
+ "sha256": "242f0b031229a87af5c4ebe5d651defe59c191a6b7a69dc0a77f7e203411f195"
+ },
+ {
+ "path": "examples/legacy-v1-coastal-rescue.arcadia",
+ "bytes": 3449,
+ "sha256": "4dbbbdf8bc7e6cb8e0a81dd678069dae585a9e5187c59bc3665e1fdab3f1a86a"
+ },
+ {
+ "path": "examples/legacy-v1-coastal-rescue.oa.json",
+ "bytes": 5976,
+ "sha256": "fd0e9dedfb7adb922dc0575cc2b820d40344d639f1d8052bd70363aa59de9c24"
+ },
+ {
+ "path": "examples/release1/evidence/repair-check.md",
+ "bytes": 1419,
+ "sha256": "f4db17bdca4ecbb1bf0caef01b2da5f1f7f798ab3152b648ce6dd8696a6a68e5"
+ },
+ {
+ "path": "examples/release1/expected-diagnostics.json",
+ "bytes": 288,
+ "sha256": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e"
+ },
+ {
+ "path": "examples/release1/model.json",
+ "bytes": 134520,
+ "sha256": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727"
+ },
+ {
+ "path": "examples/release1/model.rs",
+ "bytes": 28762,
+ "sha256": "15f30f6bde655808fe754665c26d560e36f2a9bad340f346b09137d0d05045f5"
+ },
+ {
+ "path": "examples/release1/oa/activities/activity_oa_restore_work.arcadia",
+ "bytes": 1307,
+ "sha256": "83e73d0a013d5c3e1a4af0d9139ab0cfa362d25a846671fc2dac59181061a9d4"
+ },
+ {
+ "path": "examples/release1/oa/behavior/process_oa_assessment_procedure.arcadia",
+ "bytes": 697,
+ "sha256": "172c32c044ef4b5bc58a1f4635cc81f48dabb4014479b47dce7b25fedf2dbd0d"
+ },
+ {
+ "path": "examples/release1/oa/behavior/process_oa_field_procedure.arcadia",
+ "bytes": 1781,
+ "sha256": "d2552dc5383e2e91cb302a790364e197f663c2edc013a2ab1978789825c9c324"
+ },
+ {
+ "path": "examples/release1/oa/behavior/process_oa_response.arcadia",
+ "bytes": 3225,
+ "sha256": "cf86f46fa5c5d70592ef43f1bc1cb4d968efd8add384ba88036bef6b88aa4abc"
+ },
+ {
+ "path": "examples/release1/oa/behavior/scenario_oa_conversation.arcadia",
+ "bytes": 1795,
+ "sha256": "b25be524e98a35ebac5e4481b7609c027b32baf9605e8097f807560f94a77ca6"
+ },
+ {
+ "path": "examples/release1/oa/behavior/scenario_oa_full_scenario.arcadia",
+ "bytes": 1261,
+ "sha256": "00a1edf2c9707a0eb62ac070c2d02467874f2c9c1f273cc6ddb53d6d65999a1c"
+ },
+ {
+ "path": "examples/release1/oa/behavior/state_machine_oa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "447c9ad1359f4a31ecf975ad07c0b08d8e7e03191fb0858e2fc56f377b223cd5"
+ },
+ {
+ "path": "examples/release1/oa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "fc8012e5554350b12e1cb80080f8feab80d933c9b33c0f2a7bb500cb7d609936"
+ },
+ {
+ "path": "examples/release1/oa/model.arcadia",
+ "bytes": 661,
+ "sha256": "f7cf914766ea73ec53b85e32d117ea9a9dba52d3e038246863541ae7069b1a15"
+ },
+ {
+ "path": "examples/release1/oa/participants.arcadia",
+ "bytes": 1834,
+ "sha256": "951764c14179f27c10e315acf2ea37d1fff54c77b8441e23addd239e8c715ce3"
+ },
+ {
+ "path": "examples/release1/oa/types.arcadia",
+ "bytes": 788,
+ "sha256": "9d0396fb82dd5733edd8d1cf52f968ca84906927c704f9cf0944a7fae215cf49"
+ },
+ {
+ "path": "examples/release1/project.arcadia",
+ "bytes": 1958,
+ "sha256": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda"
+ },
+ {
+ "path": "examples/release1/README.md",
+ "bytes": 11137,
+ "sha256": "113cfb1cf38a98b51bff60442d86a35d636a3d80ef206bece7d53d0aae67f2d5"
+ },
+ {
+ "path": "examples/release1/sa/behavior/process_sa_assessment_procedure.arcadia",
+ "bytes": 706,
+ "sha256": "73928ced61c06cbc08728d7956676a50fd4c05aa5b7f1ff4f29ce27a0b0f2088"
+ },
+ {
+ "path": "examples/release1/sa/behavior/process_sa_field_procedure.arcadia",
+ "bytes": 1790,
+ "sha256": "886d462af83110cbfa55dd232a667ea1446761234471357cbb878794e98e8768"
+ },
+ {
+ "path": "examples/release1/sa/behavior/process_sa_response.arcadia",
+ "bytes": 3234,
+ "sha256": "041785fc22842423822431bd5e6e8d16094ebd0716965e7c765fb8c1d7549e8a"
+ },
+ {
+ "path": "examples/release1/sa/behavior/scenario_sa_conversation.arcadia",
+ "bytes": 2127,
+ "sha256": "97276f5757ee9aa12a0dbd9d3ccd58174b3091162bb8bcce357cec20a45f5d85"
+ },
+ {
+ "path": "examples/release1/sa/behavior/scenario_sa_full_scenario.arcadia",
+ "bytes": 1256,
+ "sha256": "e12fc406b99d60a2f7768aaa0b25ba560e8605f0b8e37f3a7cdd3b8443562b74"
+ },
+ {
+ "path": "examples/release1/sa/behavior/state_machine_sa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "05ce29af99424c491c47fe2450dc6f5f8ce995511c01ec7e17bb5855be3c9736"
+ },
+ {
+ "path": "examples/release1/sa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "b6912fb3ece439912345fa71724acd462e716a1e5a88203a1058a5c3b2c20a4f"
+ },
+ {
+ "path": "examples/release1/sa/functions/function_sa_audit_thresholds.arcadia",
+ "bytes": 297,
+ "sha256": "e5a04325d52183956dca814dd90f4e56c96c6b7db0f4f202f203ecb49c3e0f4f"
+ },
+ {
+ "path": "examples/release1/sa/functions/function_sa_restore_work.arcadia",
+ "bytes": 2576,
+ "sha256": "fddeb67308051325041fee641fe758ddf43345b7c47b66d433616ad07fdccdd6"
+ },
+ {
+ "path": "examples/release1/sa/model.arcadia",
+ "bytes": 661,
+ "sha256": "d431509251acb859fbb98733b6d18add952495e10a78703fbe8f31e40bc083c4"
+ },
+ {
+ "path": "examples/release1/sa/participants.arcadia",
+ "bytes": 4012,
+ "sha256": "780d2eb08b217b67b55febb3d34846f727b8ee9018df6c5741748647e36b2ff8"
+ },
+ {
+ "path": "examples/release1/sa/types.arcadia",
+ "bytes": 1227,
+ "sha256": "101fc0fbd99f1101514aa7b6da4f8e535aae0bb94c9e2b9e7e6c151135b28660"
+ },
+ {
+ "path": "examples/release1/traceability.arcadia",
+ "bytes": 29506,
+ "sha256": "48a96f543456c9b5a5798fc795594e4119147fe45f11a6f109f141b7f514560f"
+ },
+ {
+ "path": "examples/release1/transition-before.json",
+ "bytes": 35801,
+ "sha256": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d"
+ },
+ {
+ "path": "examples/release1/transition-request.json",
+ "bytes": 4276,
+ "sha256": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a"
+ },
+ {
+ "path": "examples/release1/views/diagram_oa_allocation_view.arcadia",
+ "bytes": 2176,
+ "sha256": "dfc0eb61e0e2aa5bda45524571fbfa3494f44ce19696f3e15e07438074a07bc7"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_allocation_view.arcadia",
+ "bytes": 2173,
+ "sha256": "b78b43a26a82efb8fbd5374b09c329fa341f57b29f635d397b9ef8aede71d389"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_capability_view.arcadia",
+ "bytes": 1620,
+ "sha256": "75e8dc9fb30f25d6f8fd3c6b595593ab4f98958a25798f961e336e0fac557649"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_service_view.arcadia",
+ "bytes": 2209,
+ "sha256": "b08c84f3ea633e7c78dfdad3272f75415f6256cf3614b6d99bfd4f0269076c9b"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_state_view.arcadia",
+ "bytes": 1931,
+ "sha256": "5f42f4c605e57b817d8400ed21f2b14bfc4e1592aebe941ff113767504c2b869"
+ },
+ {
+ "path": "examples/routing-workshop.arcadia",
+ "bytes": 66431,
+ "sha256": "dc6b75da72884d399132ed9e300fa5a26b73d802bb526c9af986ae9eeb0a646a"
+ },
+ {
+ "path": "examples/routing-workshop.json",
+ "bytes": 100281,
+ "sha256": "8592714f75b271133f21ce017d87896b6cf91ea0e4837f1c2ed453e21be26b14"
+ },
+ {
+ "path": "examples/source-project/oa.arcadia",
+ "bytes": 2142,
+ "sha256": "5f60642f211b2d352a951092faeeb7503d4fb7f6f7fca99e810347231101d31c"
+ },
+ {
+ "path": "examples/source-project/project.arcadia",
+ "bytes": 606,
+ "sha256": "787f6b67b179ffd221f7e5faf1a4edd74cfd116aa1e3b5e9ed9f22fbff612d8d"
+ },
+ {
+ "path": "examples/source-project/sa/assurance.arcadia",
+ "bytes": 682,
+ "sha256": "ab76044836e4dcd42a82d316846ec87da2108939aa2037640c5bca1d2ecdee0f"
+ },
+ {
+ "path": "examples/source-project/sa/participants.arcadia",
+ "bytes": 1231,
+ "sha256": "1abad579812e77877b45a3d8e3c3e9da734be2d04a0254cc730a5257ebd2dc88"
+ },
+ {
+ "path": "examples/source-project/sa/reporting.arcadia",
+ "bytes": 2839,
+ "sha256": "f0b7443f728912ca163de39b6055876b533a77feaa3219b2d40a2dd65712e058"
+ },
+ {
+ "path": "examples/source-project/views.arcadia",
+ "bytes": 3508,
+ "sha256": "e29ad3f406a6a50d2f0ae2ca2a53ee3b0c18222cd4098704a46d4f48b64ea17f"
+ },
+ {
+ "path": "examples/text-first/project.arcadia",
+ "bytes": 480,
+ "sha256": "f441535c623e0cf20e1875eb2cd33b587ed00e078c37af39cb36d89bb5edc19a"
+ },
+ {
+ "path": "examples/text-first/README.md",
+ "bytes": 1367,
+ "sha256": "4ec8250d5ed556f1d496e8c85137e973e802ecb11df1a36488411e8cdc5a9f9e"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/base/expected-baseline.json",
+ "bytes": 4524,
+ "sha256": "f239e728d650b0af32bb8f3dcaf6b27c99b9c5041132ac14e51756ea6563d978"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/base/project.arcadia",
+ "bytes": 3077,
+ "sha256": "045cb047d7bfbca72c3fb30db4c81a30ad463d3af7fc51604906ad7fa75a1ff8"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/base/rationale.arcadia",
+ "bytes": 57,
+ "sha256": "c01867c9dc0d40726853119fac3c6ad24a40d8d362a93a8b9d20df04ec2e273f"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/manifest.json",
+ "bytes": 447,
+ "sha256": "d23c21b02064ddb85e051e10e16ff089daba8d9910bb1a695dfabe2db3757734"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/README.md",
+ "bytes": 12012,
+ "sha256": "3545d9695e7c62e0caa818efd7f4dc426f04876a3626e0f3b1bad606b258cb26"
+ },
+ {
+ "path": "harness/fixtures/behavior-creation/README.md",
+ "bytes": 5954,
+ "sha256": "9e5fc751424d12cfe76a84abb1da45b2d38947409179451bb90ffe7e7ddc8723"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/hashes.json",
+ "bytes": 977,
+ "sha256": "f6d3e8aa42f40f64ce2ecba7b9cf112eb9091f3cccde3a592b0434e834cf2a6a"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/after-reset-deletion.arcadia",
+ "bytes": 4218,
+ "sha256": "c0fe9570684c0061efc4cad65e339fede6961b04ca90c6f324c787bd65e61226"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/after-reset-deletion.json",
+ "bytes": 7835,
+ "sha256": "93187f7a1b476a44185725c6b15f2e1767482b6911ab1280144348d033eb51f0"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/boundary-only.arcadia",
+ "bytes": 4771,
+ "sha256": "02f85e39957ebe6d56336fa729a5c8b10f51865face9d3b2c9679200571fcfc7"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/boundary-only.json",
+ "bytes": 8840,
+ "sha256": "756f9710336e6734ce3592ca774c6f104bba5a37c9e165540eb39326682393d1"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/explicit-functional-refinement.arcadia",
+ "bytes": 8635,
+ "sha256": "ac5b629ba5596464f9f8a975b8bc2764e3007ceb7342de3f828d4b18e4f069b7"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/explicit-functional-refinement.json",
+ "bytes": 16160,
+ "sha256": "78a47c978dc667aa1c45af711f06aa2a0d3c581003013e30c3e02fb0faae2e64"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/probe.rs",
+ "bytes": 11274,
+ "sha256": "72b1b443e25eb20420dec4477b0fb5ef5d1c705e3882c81df88dd578ab96a426"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/README.md",
+ "bytes": 2604,
+ "sha256": "088f0bdb3375deb8f2c7e3b749ca8d210ea828ca4dece69b3871b674e3110175"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/generate-semantic-artifacts.rs",
+ "bytes": 9275,
+ "sha256": "444f360b78df52c5e42046f77b148781e0c9bddc2a09c0b7bd65c7a4f376cfe6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/README.md",
+ "bytes": 4155,
+ "sha256": "24200c84ccc2ef3e17c029744074e5cea785188f5549ac54e44cf89682643f81"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.after.arcadia",
+ "bytes": 7768,
+ "sha256": "84b22e95c213018612e6e8a84cad68b35a8b495ff3515b0f978c5e7a3d196d79"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.after.json",
+ "bytes": 7330,
+ "sha256": "13a6f99ec2df1dd35ee8b2613f183918cd9537eda4a5da085ad52e65c033db59"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.arcadia",
+ "bytes": 1446,
+ "sha256": "1e0d662ba33071b122ad9206d2d559a8e748c9431eb3ac1b759bde3760ca877c"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.json",
+ "bytes": 2216,
+ "sha256": "fa6a2e3643c0f909b92407abec3edc897033162eeaa501f8d4616f79d41fa044"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.manifest.json",
+ "bytes": 567,
+ "sha256": "65cb70b0d697a055363ba01bf30bed7e412d43c9978a033549c378d117719ae7"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.request.json",
+ "bytes": 3991,
+ "sha256": "ed905267263e7f8a0cd2dbe833196b30118b6a246815216932fae661ad650699"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.after.arcadia",
+ "bytes": 6837,
+ "sha256": "e50fc462ec22dbf98011929931dd2489430001c3e6433608685b9d88bceb9acb"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.after.json",
+ "bytes": 6433,
+ "sha256": "f27a7b38d59c7cefe58cb36783d3d4dd8d8ae1168d9aed1eb61d343c18758908"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.arcadia",
+ "bytes": 1807,
+ "sha256": "eba2aec39e6efb405eace4bc8ab0c5563907c26d449e71a4aa750df9f73233b4"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.json",
+ "bytes": 2673,
+ "sha256": "2ef777f19b94f08b5e5ca2ef727777897e1018f1426eee291408b3c69101f596"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.manifest.json",
+ "bytes": 519,
+ "sha256": "3af7232ed3056ca84dcaf0f266ead3faaa512a0dc0051c7ba68791c20dbae0cf"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.request.json",
+ "bytes": 1246,
+ "sha256": "8af21b00b62e620256fbf50e47c6c5836cdb6922d9549cc4b3d86bc03b008073"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.after.arcadia",
+ "bytes": 4200,
+ "sha256": "20bb30473830e44228958e78fb7480269cdcc0580c6c90c718da75f34ca5a43f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.after.json",
+ "bytes": 4552,
+ "sha256": "44f4b05db49758dcf344788f57cecc0250fd029099b1c54e4e1e3e972f6cf039"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.arcadia",
+ "bytes": 1808,
+ "sha256": "b0fd031671c9f755756922ec5c40a0ce3f40164d22c3c389aec9fd0705344da7"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.json",
+ "bytes": 2673,
+ "sha256": "1acf81e08c51c85aceb14a3efbc39ec680552a19c7824aab4611ac3cf71b9c6d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.manifest.json",
+ "bytes": 610,
+ "sha256": "a07978f4b0a3ca83e5ae5f35e7b66b26473b24431fc90e5889d755f1816d52dd"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.request.json",
+ "bytes": 2352,
+ "sha256": "5ff844a038b0030adb5a83d13d81e60f30627543388f6199b6a0e026fc42ea58"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-native.md",
+ "bytes": 4075,
+ "sha256": "a51ffbc45b6c172cbfed3b16c249296e6a28d457fea57393dd958cdcfbafbca1"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts.hashes.json",
+ "bytes": 2367,
+ "sha256": "3ad8b7428b3ed7585a91e7f5d9d5f4880caccfd0b08dca25d05af523e62e0a10"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.after-template/entry.arcadia",
+ "bytes": 6098,
+ "sha256": "a6976e7c21031e7cc3cb731e20c3075cf89cf85144dc8d3f52930f8521ea196f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.after-template/expected.json",
+ "bytes": 10480,
+ "sha256": "60c4419415c546f06c9972ac7bb0e530ada7b30a200b59e75ddda231a24f9a3d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.after-template/traceability.arcadia",
+ "bytes": 407,
+ "sha256": "3c41459f2b0706a767988738bf8126f76540415bb5940d9c5db19cbe8c18182d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.request.json",
+ "bytes": 1331,
+ "sha256": "b16e673b29c96b5aed681389ff7669110abe552939e86dd0ec6011c9a45ed860"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-draw.arcadia",
+ "bytes": 5239,
+ "sha256": "a511a4d817d9c5c80233bce83e84a7056b7dce7afd9fcc25696e333f8f4df055"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-draw.json",
+ "bytes": 8424,
+ "sha256": "7140114926228b1c378c450535f7a70992c71a879a8d2270c49e8d5f04a2f484"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.after-template/entry.arcadia",
+ "bytes": 5710,
+ "sha256": "910e6dfe609467047ce629ec9c626f4e927cada74bfbfa164414a8fe6783ea87"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.after-template/expected.json",
+ "bytes": 9868,
+ "sha256": "567259e20c9f6278776e8fa99b8b2fdbbc9a883ad875c5d39dae1b53de3a2924"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.after-template/traceability.arcadia",
+ "bytes": 407,
+ "sha256": "997f72fe9258012df66e88344cf39c1a2cebf2327f604056b67f7f2a8ebcfbfa"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.request.json",
+ "bytes": 1127,
+ "sha256": "cb3fe05859cfffec6f8846c19983dc462413bc0ea4f6e19204f09c259919c4b1"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/cm-reverse-scenario.arcadia",
+ "bytes": 8209,
+ "sha256": "ec826b9b84616507490ac30e4d57100dd6338dba2f7071e879a8fb58820a85c5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/expected.json",
+ "bytes": 19735,
+ "sha256": "ef07e4d2d3fc36004ff01739838281581d9a703c747611eacc01bb9cc672489f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/oa/behavior/scenario_oa_cm_200.arcadia",
+ "bytes": 1488,
+ "sha256": "0ec8d7e38f5a5cd56ccf3979757b3a9f412ad0ae36ee6c2690b7872129046d70"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/request.json",
+ "bytes": 1090,
+ "sha256": "c0ac99ee186d126fe3ec57f2c0a4072984d39d36ad21ead357ab66a08ac0065c"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/traceability.arcadia",
+ "bytes": 2068,
+ "sha256": "dd5d356390089e4ec474374956f6219502733987e60ef8ff32920d8b33dce858"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/entry.arcadia",
+ "bytes": 9827,
+ "sha256": "86dd8a355f6c9dadfea56d06e5247c9b0f820819d7df17ef5690671c7c81324b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/expected.json",
+ "bytes": 21877,
+ "sha256": "980489ec2c483d4f9efada61358c1f29bafb2572fd5b804c656b91b8e9bb4f9a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/oa/behavior/scenario_oa_cm_200.arcadia",
+ "bytes": 1488,
+ "sha256": "0ec8d7e38f5a5cd56ccf3979757b3a9f412ad0ae36ee6c2690b7872129046d70"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/traceability.arcadia",
+ "bytes": 2068,
+ "sha256": "dd5d356390089e4ec474374956f6219502733987e60ef8ff32920d8b33dce858"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.arcadia",
+ "bytes": 7698,
+ "sha256": "1abc4c4cac37cfb06ed3b12a115f9e7e6a2d3affd34b25c355309e5ee465fa40"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.json",
+ "bytes": 13130,
+ "sha256": "a5cf4cb2ef390113dd752f4054d43fb789da2ca77799efb5782805ae8aa2dfd5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.request.json",
+ "bytes": 1090,
+ "sha256": "c0ac99ee186d126fe3ec57f2c0a4072984d39d36ad21ead357ab66a08ac0065c"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-transition-ready.arcadia",
+ "bytes": 5454,
+ "sha256": "b761c4c2af5f7064aaf1f887ffd6094eb6ae834b2c8057ecdbc4aa97c042066b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-transition-ready.json",
+ "bytes": 8852,
+ "sha256": "2536b000de361fb131787e32b466b9123d4e98abf00c099ad32f6cb8e7fb530b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/generate-communication-means.rs",
+ "bytes": 28035,
+ "sha256": "003521404b702332082e01f2be7676fda1dd7b5bcf220bb81d25120c8984c75d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/hashes.json",
+ "bytes": 3133,
+ "sha256": "39b4dc02fc8b074015c59354fd1c1061c25832fde25065c7151f36eb0455e6f6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/manifest.json",
+ "bytes": 1237,
+ "sha256": "fb921f42f2c4a4f8a67d1266f17f8c7750b6a2619554b713aa92b17918f0c43e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/README.md",
+ "bytes": 15091,
+ "sha256": "015c16010c212af283f9def118c6d263ba65a3bc6e81a750f180624ca69bb311"
+ },
+ {
+ "path": "harness/fixtures/release1-native/dense-40/dense-40-oracle.json",
+ "bytes": 26264,
+ "sha256": "74edca148583f42c269e51f135662f65ef1c9ea1c94516d63e2a5091967dc99b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/dense-40/dense-40.arcadia",
+ "bytes": 28947,
+ "sha256": "00b87736b82c5afd33b311df7aebe9a6c8c51b6de66221181b5ea8bc8ca221a3"
+ },
+ {
+ "path": "harness/fixtures/release1-native/dense-40/dense-40.json",
+ "bytes": 54938,
+ "sha256": "5dd4f22453330cab3abc059881660190e689a22619853f77cf6d0b52735d97ad"
+ },
+ {
+ "path": "harness/fixtures/release1-native/README.md",
+ "bytes": 2564,
+ "sha256": "5f5ba040fd2df9261a29bcab3f9a7ae27c3683265411ad3dda0b467d5df8590e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments-native.md",
+ "bytes": 2624,
+ "sha256": "28906924b05f75bbf432389517aef65f1df474d2f2bff79a3586564f7f7328ef"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.after.arcadia",
+ "bytes": 6795,
+ "sha256": "9d084dd75a0251d349cc31f9ee98df58d0bc76859dc3cb1503f242e2f759888a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.after.json",
+ "bytes": 9618,
+ "sha256": "f09be41afd85c18fd52fdf997e9b81c4b7cb2c41e78d167abcd14caea8110333"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.arcadia",
+ "bytes": 4990,
+ "sha256": "8740f27be99624013d9be876639c0411e1b8c14f0f9a843f43dc71d769efd79a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.hashes.json",
+ "bytes": 963,
+ "sha256": "00fc9e2c709bdcd9ee98012176cb0edb635c112153d963cd169bf4bf69a9e010"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.json",
+ "bytes": 8146,
+ "sha256": "9c188f2890630c7feec2be719a7104af9690da7ea836df18fa78da3ff2acfc1b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.manifest.json",
+ "bytes": 1045,
+ "sha256": "46b40f221ffad614733b615208814ace63947e96b80bbfdec63853ccd4f4e8c8"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.request.json",
+ "bytes": 3075,
+ "sha256": "b57f656f0b9cf2c595e83150c683301909e20f15684d35d108d3c57ee0dc5909"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch-native.md",
+ "bytes": 2637,
+ "sha256": "dddca2b85f892b05881c75ef3cd62437396cc086a09909b621e02bad0cbe56ff"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.after.arcadia",
+ "bytes": 3134,
+ "sha256": "d3315bba762395205ae761e6e64029e8b4070c15a1299c6114075581db7b0974"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.after.json",
+ "bytes": 3927,
+ "sha256": "8f36119c7e893a4af374b13b85b1c2cb3df0196df8d06a551386628fc8b8646d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.arcadia",
+ "bytes": 1787,
+ "sha256": "5e1f433e04621424edac43e53411624059e873f88dd531bfdb39093953485ed7"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.hashes.json",
+ "bytes": 933,
+ "sha256": "1bd87700b04e4f169ab5f8aceaa3b0c3ec3a1cc43e1bce135a1cee506fc75d38"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.json",
+ "bytes": 2816,
+ "sha256": "1972242d2a8677d004dc3af1081feb292de65fe26128d877dded8661f9906619"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.manifest.json",
+ "bytes": 647,
+ "sha256": "1e440efa02e2ccc27585d9a49c7864d53e3e3e7645ccb219f040e00d95331b8e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.request.json",
+ "bytes": 2197,
+ "sha256": "71f23474614aab90398ce24f00f2cf61d6cb9a3a34faa3e12e0c8104e0a71073"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-repair.arcadia",
+ "bytes": 15635,
+ "sha256": "e3ce0347746584f503b825a8b6bd23a7eebf0827940112ef8f36d500c1d0a043"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-repair.json",
+ "bytes": 25736,
+ "sha256": "29736f2a619f7f2d0e795229f6e8c3645a1b4eca6292043cd604aa01b1ec9170"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-take.arcadia",
+ "bytes": 15631,
+ "sha256": "9ab573c92be66e3bae659dd826923ba6066979b1651edda7bd469056632cd77e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-take.json",
+ "bytes": 25736,
+ "sha256": "828c990c6129692cd31c7f6ed0dd6c952f9cfffdd90bc80171df82d4d929afba"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.arcadia",
+ "bytes": 15617,
+ "sha256": "49214be2ca9c8bb6195c3b39737c053847021839f70f0bb2921d35a1fbd2ac83"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.invalid-request.json",
+ "bytes": 2225,
+ "sha256": "8b02d4739b1be2c8a64da76423b4a6cae7d89c60f4d30723b94f1da3779cbcca"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.invalid.txt",
+ "bytes": 78,
+ "sha256": "aa536e18bc65325163e9b645a4ad031ab599fcb05948dcf741b725d0f0c8b602"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.json",
+ "bytes": 25722,
+ "sha256": "995dd72f5c1f07f998233ae9847e337cb82de0c2a9053d3905be021d5f8124b9"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.manifest.json",
+ "bytes": 1059,
+ "sha256": "ad315359bfd68ce0d265976dc9e4ea6a502ebbb87d7339dd1af5ef27dd01e433"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.repair-plan.json",
+ "bytes": 15016,
+ "sha256": "27bf316ad66ffa91f6323e75b0c280baf5caec298645793dea72c5d42aa3b782"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.repair-request.json",
+ "bytes": 2225,
+ "sha256": "f4f6b5480ec8927033ea469ea82c1e8411cb6dba95d2da680f15bc193914c9b4"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.steps.md",
+ "bytes": 3897,
+ "sha256": "7d7e38d93641a5f22a2738baa4dbacc392eebb477a855cdce0641961980a19ad"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.take-plan.json",
+ "bytes": 18557,
+ "sha256": "74dd68ba686b8928054480f0bfed55aad91f6f1208803399dc9878e894f57ecb"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.take-request.json",
+ "bytes": 1508,
+ "sha256": "d19a53002a6bf95af3123a3ef9f78baaf7ce1085d4f06f0f4a9d5e5d5accbc0e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.arcadia",
+ "bytes": 6935,
+ "sha256": "90277f1780d43a6f0b2184bf00b87e7a4f3518d67c6f10d4793daafe1aabf493"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.json",
+ "bytes": 11286,
+ "sha256": "5a3c8494f6976c23b990c14b40760fb2f32ca9973caf6f31dab5632310663591"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "6a86fde194faa55f2d1bdb982571e0e711e5401b53a960e6acb8cf3e17f506c8"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.arcadia",
+ "bytes": 6195,
+ "sha256": "e7c30c5a5c3ba7209c8e223f02da55821d7af10a2f09844a7065ae6cd8f22aa5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.final-plan.json",
+ "bytes": 19156,
+ "sha256": "d1e5c2ed08e0aa6274a0421a39876d1ba99f4e441c7a0a68660a83b9e017da14"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.final-request.json",
+ "bytes": 2418,
+ "sha256": "433f723252598994dac7184da61f95344b528156cc5a0fba1a7864b2ec9f002b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.json",
+ "bytes": 10441,
+ "sha256": "9e4e2ce1c7879935ad0860a3ae720506c929a8422b51491b719d88af3fd3ff2d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.manifest.json",
+ "bytes": 1042,
+ "sha256": "87e8899c7d4e54faedeef32afb7592fb65278d1bd7390850ab198dcf62068329"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.after-order.arcadia",
+ "bytes": 6280,
+ "sha256": "6c77ed42bbf9f4ebe08bfa233197a4020e5587ce33747f7914430aa0c662d9a3"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.after-order.json",
+ "bytes": 10009,
+ "sha256": "1dd7ca2bcfc84ac23369c506302b39569d2ebc41e5a4edc99378a1fe01923b7e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous-request.json",
+ "bytes": 1390,
+ "sha256": "e0ea7aeb25687ae63c65128f1aab78273f14030d179d3fbea63fe91ab5430136"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.arcadia",
+ "bytes": 5529,
+ "sha256": "563c36162050c8564ffa25a85c726462ecdaebdb87659b3f3610506bfd5581e6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.final-plan.json",
+ "bytes": 18821,
+ "sha256": "f96c43fc58dbe48a044b628b31bb3f0b59171dad5b6982ec6c34a5597e474130"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.final-request.json",
+ "bytes": 2438,
+ "sha256": "d38fdb13612b07254edad80848f79c1e097d5f868c020feae9ba072aa697a50d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.json",
+ "bytes": 9153,
+ "sha256": "2ed0daed118f2df13dcad738dab174ad34d44a15921cf8097415e707978b0968"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.manifest.json",
+ "bytes": 1058,
+ "sha256": "c79bd12a85d8d4f25cc3ee43581b3e5e74ae9ad7e4de0c72764975ba2b89c279"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.after-order.arcadia",
+ "bytes": 5026,
+ "sha256": "98020e048ac01f285c6ad8c10ce22bef05e193b5ba0f5558be1fb26a7bf82620"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.after-order.json",
+ "bytes": 7851,
+ "sha256": "ea3783992488bc6ee944eb91a26ccf3dbc05a3909528fc32ec8023a76765cee6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "ea3cfd25f7a50481805956fb88ab851516dcd602dd6564502a0cc55f7de6a54f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.arcadia",
+ "bytes": 4295,
+ "sha256": "2b7c3694623bbea3e36dcc6f674d4adcd91e23c8e1005ded21e5a69648a048e5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.final-plan.json",
+ "bytes": 17912,
+ "sha256": "95a23bd334cffcaef0c3b1012b7555efd2a077ef6f9140d58020910ef8a76723"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.final-request.json",
+ "bytes": 2081,
+ "sha256": "1e2433ed5821bf65443f56cb27381ee935db81b1955d3a539b7952898312a410"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.json",
+ "bytes": 7012,
+ "sha256": "0033f8780c2ba69779b811bca792340c25a092f887e5811c6839f0a691d744f2"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.manifest.json",
+ "bytes": 1046,
+ "sha256": "e3b4a60a56abee779ca8356605c968aa2d99256a3bd3c5601b14b5c5502a1a05"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.after-order.arcadia",
+ "bytes": 5467,
+ "sha256": "e265cab35097acc46f5123107c13bc35b9f71931ff650557fc1c85779141b327"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.after-order.json",
+ "bytes": 8482,
+ "sha256": "9b777828d3681138de6bbd08de8bf922bed92c27be60b103d8ff73ce7ddd0a48"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "7d124256730bf2feee772c7d0118b87eb288e8aaa9179d69ceb9259dbceb2c71"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.arcadia",
+ "bytes": 4727,
+ "sha256": "87a5a3665dab2dd6e595867dcebb1586e93dd067c85d6e24b1a4d3ff0ad6d4ba"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.final-plan.json",
+ "bytes": 18803,
+ "sha256": "1888ca51e0923866a9afea99cad069fe5d3f19f0a1b24328e20ac1b9f52d8bad"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.final-request.json",
+ "bytes": 2294,
+ "sha256": "5070a5b85feb1bc7b1fc648cd5d35ef26663492c8666dcd471950c06ec3a6052"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.json",
+ "bytes": 7641,
+ "sha256": "725b35e36640c81c70c7efea5c3304bf61b49ef9664194ca0d491782cc62191a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.manifest.json",
+ "bytes": 1038,
+ "sha256": "1b43abf8fed3061c65a7b7cb103d31ea7ece333a2da83c645f8d228448c4dbb2"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.after-order.arcadia",
+ "bytes": 5019,
+ "sha256": "65a6217f474b5085f37b511151185c2c08ff7a1998e924f1fe01f29c410f6ced"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.after-order.json",
+ "bytes": 7835,
+ "sha256": "8a53bb9b43eab5b6dca5e08deef20c14c96e134b9ee816a99b7960bb5255e008"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "c704f99245f00d5c3be0562d91931b4e7f1fb3fb36f41991eb76656ffbd6129f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.arcadia",
+ "bytes": 4279,
+ "sha256": "c59ec1233d5a9fbb8e386f6d9b13f398be21baf813ba215efb51bfc1bb98db36"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.final-plan.json",
+ "bytes": 17938,
+ "sha256": "30c5c3f614a74708763df612016483623792f4c65e2ed2fa662ac417cfb852eb"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.final-request.json",
+ "bytes": 2098,
+ "sha256": "3b6c35ef78133f0980cb6fb53fbc29c5d3c4deb5dc4be01c2c3384e221987b9b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.json",
+ "bytes": 6996,
+ "sha256": "c159c60403abcf8fefab7f19cbad9989488e8ccc20b82c57597b22a0ae254802"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.manifest.json",
+ "bytes": 1038,
+ "sha256": "af8a314a9b1d85a8f98affa5a6649f43d252851158798e1ad687bb1b6966b14f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.steps.md",
+ "bytes": 4549,
+ "sha256": "6004894e9d9f478b7fe1c3c3e0706dcc546d5df8557cff91b4cf26f8a4badf24"
+ },
+ {
+ "path": "harness/fixtures/release1-native/source5-recovery/15000000-0000-4000-8000-000000000010.json",
+ "bytes": 1933,
+ "sha256": "71cca2a59f36b2a07d11dedbe9101226aa1a4a00d8ac244b5565202e56709f51"
+ },
+ {
+ "path": "harness/fixtures/release1-native/source5-recovery/generate.py",
+ "bytes": 1496,
+ "sha256": "e44986c1d563499f25917441220b23883fc9cb0f89faf409873191f0212fbd2f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/state-actions.arcadia",
+ "bytes": 6224,
+ "sha256": "9cde82608110e56aa9e14c9e33eb38976f53aed74ab013145d411d5743a0ec26"
+ },
+ {
+ "path": "harness/fixtures/rest-coverage/coverage-probe.json",
+ "bytes": 8008,
+ "sha256": "699b868dd6c955f7ef02f2f061935471f3d21a406005fcdecba8cc719cc69a1f"
+ },
+ {
+ "path": "harness/fixtures/rest-coverage/README.md",
+ "bytes": 1870,
+ "sha256": "df4a391312e0c8b20ccdcc850199443b08547be1b10b1f2447a067bf4bf544e6"
+ },
+ {
+ "path": "harness/fixtures/rest-coverage/run-coverage-probe.ps1",
+ "bytes": 9381,
+ "sha256": "f24f19fca418b76a7249894a08c116e16b465a02f8f899d12b808ccfdd90a3bd"
+ },
+ {
+ "path": "harness/fixtures/run-timed-bundle-oracle.ps1",
+ "bytes": 7466,
+ "sha256": "fab696486a574f9867c6e69a8a8b960e7e9722206441b9cc5f36d41f285f1a25"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/base/expected-baseline.json",
+ "bytes": 38589,
+ "sha256": "abd8246c939ebb8a2162ef82e23d0fb322ee1a8b4d0d7c28a34037e42e86e841"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/base/project.arcadia",
+ "bytes": 19470,
+ "sha256": "9b1afbe96b0bf8fa613451b00929492f05a59b9eb2ca74c2c374a8b7bb7cba9e"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/base/rationale.arcadia",
+ "bytes": 86,
+ "sha256": "82df210f22f6aa336a3e7e00240977d671c7c3923db7bc58fb29e6d98b67ef49"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/README.md",
+ "bytes": 14700,
+ "sha256": "6b651b180b76d10b00bc49d77a8f5e0b3ba0317446978f41b541f5bb23fedad8"
+ },
+ {
+ "path": "harness/fixtures/source7-native/base/expected-baseline.json",
+ "bytes": 26428,
+ "sha256": "e2882a58f841f460f45d9d67db1606440e0d7997b9b29a784569ae48e4b23bfb"
+ },
+ {
+ "path": "harness/fixtures/source7-native/base/project.arcadia",
+ "bytes": 13616,
+ "sha256": "5474d8ca05fd1375220dc0ad40d913631fde51159e9632bb9f290d1ecdab8dd7"
+ },
+ {
+ "path": "harness/fixtures/source7-native/base/rationale.arcadia",
+ "bytes": 59,
+ "sha256": "e6a600e13036d4ad594a65380ed1b3d82d1772d583041bb0a624c9588a73047a"
+ },
+ {
+ "path": "harness/fixtures/source7-native/manifest.json",
+ "bytes": 410,
+ "sha256": "6c57164a796234d83818d462d0a25f10e77062d405769e602ae2a7bec251e3b6"
+ },
+ {
+ "path": "harness/fixtures/source7-native/README.md",
+ "bytes": 7235,
+ "sha256": "7abccbc3da0980d1d099761d1a66d12c403a0f543954134537ecd4930d182b4b"
+ },
+ {
+ "path": "harness/fixtures/staged-source-authoring/base/notes.arcadia",
+ "bytes": 63,
+ "sha256": "ad9de02963c20d1f43856455a797deb05ca674659e45f2c4b96b52783024f8bd"
+ },
+ {
+ "path": "harness/fixtures/staged-source-authoring/base/project.arcadia",
+ "bytes": 593,
+ "sha256": "aa844950ec4fb86f262a257e1fc370a26557ea7978d8a4cab78265037660234c"
+ },
+ {
+ "path": "harness/fixtures/staged-source-authoring/README.md",
+ "bytes": 8380,
+ "sha256": "711910a12d5f3960f32da9aa7bc2e647f31d770c84b7fc26e232dcb3a76f8821"
+ }
+ ]
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/language_outline_tests-pre-encoding-fix.rs b/harness/experiments/text-navigation-components-20260911/components/language_outline_tests-pre-encoding-fix.rs
new file mode 100644
index 00000000..3ab83661
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/language_outline_tests-pre-encoding-fix.rs
@@ -0,0 +1,386 @@
+//! Prepared outline tests. Explicit compatibility tests additionally exercise
+//! the existing parsers/analyze; the outline entry itself never calls them.
+use super::*;
+
+fn limits() -> OutlineLimits {
+ OutlineLimits {
+ max_source_bytes: 1024 * 1024,
+ max_tokens: 100_000,
+ max_declarations: 1_000,
+ }
+}
+
+fn declaration<'a>(result: &'a Outline<'_>, alias: &str) -> &'a OutlineDeclaration {
+ result
+ .declarations
+ .iter()
+ .find(|d| d.declaration.alias == alias)
+ .unwrap()
+}
+
+fn text(source: &str, span: Span) -> &str {
+ &source[span.start..span.end]
+}
+
+#[test]
+fn outline_roles_preserve_unicode_crlf_and_exact_escaped_uuid_literal() {
+ let source = "// 検査 🚒\r\noa_actor responder \"救助\" id \"\\u00300000000-0000-0000-0000-000000000001\" {}\r\nsource @ // 使用\r\n responder;";
+ let before = source.as_bytes().to_vec();
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.source.as_bytes(), before);
+ assert_eq!(result.source.as_ptr(), source.as_ptr());
+ let d = declaration(&result, "responder");
+ assert_eq!(text(source, d.kind_span), "oa_actor");
+ assert_eq!(text(source, d.alias_span), "responder");
+ assert_eq!(
+ text(source, d.id_literal_span.unwrap()),
+ "\"\\u00300000000-0000-0000-0000-000000000001\""
+ );
+ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
+ assert_eq!(d.declaration.span.start, source.find("oa_actor").unwrap());
+ assert_eq!(d.declaration.span.end, source.find("{}\r\n").unwrap() + 2);
+ let mut end = 0;
+ for token in &result.tokens {
+ assert_eq!(token.span.start, end);
+ assert!(token.span.start < token.span.end);
+ assert!(source.is_char_boundary(token.span.start));
+ assert!(source.is_char_boundary(token.span.end));
+ end = token.span.end;
+ }
+ assert_eq!(end, source.len());
+ assert!(!result.has_lexical_errors);
+ assert!(!result.has_unmatched_braces);
+}
+
+#[test]
+fn closed_id_at_eof_is_inside_the_declaration_extent() {
+ for source in [
+ "oa_actor a \"Actor\" id \"00000000-0000-0000-0000-000000000001\"",
+ "node placed id \"00000000-0000-0000-0000-000000000001\"",
+ ] {
+ let result = outline(source, limits()).unwrap();
+ let d = &result.declarations[0];
+ let literal = d.id_literal_span.unwrap();
+ assert_eq!(d.declaration.id, Some(Id::from_u128(1)));
+ assert_eq!(d.declaration.span.end, source.len());
+ assert!(d.declaration.span.start <= literal.start);
+ assert!(literal.end <= d.declaration.span.end);
+ }
+ for source in ["oa_actor a \"Actor\"", "oa_actor a \"Actor\" id"] {
+ let result = outline(source, limits()).unwrap();
+ let d = &result.declarations[0];
+ assert_eq!(d.declaration.span.end, source.len());
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(d.id_literal_span, None);
+ }
+}
+
+#[test]
+fn missing_bad_and_historical_ids_never_allocate_authored_identity() {
+ let source = "oa_actor missing \"No ID\" {}\noa_actor bad \"Bad ID\" id \"not-a-uuid\" {}\noa_actor historical \"00000000-0000-0000-0000-000000000001\" { history uuid \"00000000-0000-0000-0000-000000000001\"; }";
+ let first = outline(source, limits()).unwrap();
+ let second = outline(source, limits()).unwrap();
+ assert_eq!(first, second);
+ assert!(
+ first
+ .declarations
+ .iter()
+ .all(|d| d.declaration.id.is_none())
+ );
+ assert_eq!(declaration(&first, "missing").id_literal_span, None);
+ assert_eq!(declaration(&first, "historical").id_literal_span, None);
+ assert_eq!(
+ text(source, declaration(&first, "bad").id_literal_span.unwrap()),
+ "\"not-a-uuid\""
+ );
+ // Balanced tokens are not a successful syntax/semantic validation claim.
+ assert!(!first.has_lexical_errors);
+ assert!(!first.has_unmatched_braces);
+}
+
+#[test]
+fn invalid_full_scan_keeps_closed_children_and_reports_lexical_body_facts() {
+ let source = "oa_entity outer \"Outer\" {\n oa_actor ready \"Ready\" { description \"} stays text\"; }\n oa_actor pending \"Pending\" { description \"unfinished 🌊";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.source, source);
+ assert_eq!(result.tokens.last().unwrap().span.end, source.len());
+ assert!(result.has_lexical_errors);
+ assert!(result.has_unmatched_braces);
+ let ready = declaration(&result, "ready");
+ assert_eq!(
+ text(source, ready.declaration.span),
+ "oa_actor ready \"Ready\" { description \"} stays text\"; }"
+ );
+ assert_eq!(
+ declaration(&result, "outer").declaration.span.end,
+ source.len()
+ );
+ assert_eq!(
+ declaration(&result, "pending").declaration.span.end,
+ source.len()
+ );
+ let unmatched_close = outline("}\n// brace in comment {\n", limits()).unwrap();
+ assert!(unmatched_close.has_unmatched_braces);
+ assert!(!unmatched_close.has_lexical_errors);
+}
+
+#[test]
+fn missing_body_syntax_is_unchecked_even_when_known_error_flags_are_clear() {
+ let source = "oa_actor a \"A\" id \"00000000-0000-0000-0000-000000000001\"";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 1);
+ assert!(!result.has_lexical_errors);
+ assert!(!result.has_unmatched_braces);
+ // There is deliberately no `valid` flag: the known-complete fact is scanning
+ // these bytes, not the missing body becoming legal or the draft being applied.
+ assert_eq!(result.source, source);
+}
+
+#[test]
+fn keyword_alias_and_collisions_keep_every_source_ordered_header() {
+ let source = "project root_model \"Model\" {\n oa_actor oa_activity \"First\" id \"00000000-0000-0000-0000-000000000001\" {}\n oa_actor oa_activity \"Second\" id \"00000000-0000-0000-0000-000000000001\" {}\n diagram view_one \"View\" {}\n node placement { diagram @view_one; }\n}";
+ let result = outline(source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 5);
+ let duplicates: Vec<_> = result
+ .declarations
+ .iter()
+ .filter(|d| d.declaration.alias == "oa_activity")
+ .collect();
+ assert_eq!(duplicates.len(), 2);
+ assert!(duplicates[0].alias_span.start < duplicates[1].alias_span.start);
+ assert_eq!(duplicates[0].declaration.id, duplicates[1].declaration.id);
+ assert!(
+ result
+ .tokens
+ .iter()
+ .any(|t| t.span == duplicates[0].alias_span && t.class == TokenClass::Keyword)
+ );
+ assert_eq!(
+ text(source, declaration(&result, "placement").kind_span),
+ "node"
+ );
+ assert_eq!(declaration(&result, "placement").declaration.id, None);
+}
+
+#[test]
+fn bounds_refuse_before_returning_a_partial_outline() {
+ let source = "oa_actor a \"A\" {}\noa_actor b \"B\" {}";
+ let admitted = outline(source, limits()).unwrap();
+ let exact = OutlineLimits {
+ max_source_bytes: source.len(),
+ max_tokens: admitted.tokens.len(),
+ max_declarations: 2,
+ };
+ assert_eq!(outline(source, exact).unwrap(), admitted);
+ for resource in [
+ OutlineResource::SourceBytes,
+ OutlineResource::Tokens,
+ OutlineResource::Declarations,
+ ] {
+ let mut lower = exact;
+ match resource {
+ OutlineResource::SourceBytes => lower.max_source_bytes -= 1,
+ OutlineResource::Tokens => lower.max_tokens -= 1,
+ OutlineResource::Declarations => lower.max_declarations -= 1,
+ }
+ let error = outline(source, lower).unwrap_err();
+ assert_eq!(error.resource, resource);
+ assert!(error.observed_at_least > error.limit);
+ assert!(source.is_char_boundary(error.offset));
+ if resource == OutlineResource::Declarations {
+ assert_eq!(error.offset, source.find("oa_actor b").unwrap());
+ }
+ }
+ let empty = outline(
+ "",
+ OutlineLimits {
+ max_source_bytes: 0,
+ max_tokens: 0,
+ max_declarations: 0,
+ },
+ )
+ .unwrap();
+ assert!(empty.tokens.is_empty() && empty.declarations.is_empty());
+ let unicode = "// 🌊\r\noa_actor a \"A\" {}";
+ let error = outline(
+ unicode,
+ OutlineLimits {
+ max_source_bytes: 4,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::SourceBytes);
+ assert_eq!(error.offset, 0);
+ let error = outline(
+ unicode,
+ OutlineLimits {
+ max_tokens: 1,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::Tokens);
+ assert_eq!(error.offset, unicode.find('\n').unwrap());
+}
+
+#[test]
+fn outline_ranges_follow_current_bytes_without_identity_stabilization() {
+ let old = "oa_actor a \"A\" {}\nsource @a;";
+ let current = format!("// 航海 🚒\r\n{old}");
+ let old_outline = outline(old, limits()).unwrap();
+ let current_outline = outline(¤t, limits()).unwrap();
+ let shift = current.len() - old.len();
+ let old_role = &old_outline.declarations[0];
+ let current_role = ¤t_outline.declarations[0];
+ assert_eq!(
+ current_role.alias_span.start,
+ old_role.alias_span.start + shift
+ );
+ assert_eq!(
+ current_role.kind_span.start,
+ old_role.kind_span.start + shift
+ );
+ assert_eq!(
+ current_role.declaration.span.end,
+ old_role.declaration.span.end + shift
+ );
+ assert_eq!(current_outline.source, current);
+ assert_eq!(old_outline.source, old);
+ assert_eq!(current_role.declaration.id, None);
+ assert!(!current_outline.source.contains(" id "));
+ // Workspace revision/stale-handle rejection belongs to the query/adapter,
+ // not a claim that an old owned span can be applied to any other string.
+}
+
+#[test]
+fn deeply_nested_bounds_use_the_same_iterative_walk_and_declaration_budget() {
+ let mut source = String::new();
+ for index in 0..600 {
+ source.push_str(&format!("oa_activity work_{index} \"Work\" {{\n"));
+ }
+ source.push_str(&"}\n".repeat(600));
+ let result = outline(&source, limits()).unwrap();
+ assert_eq!(result.declarations.len(), 600);
+ assert!(!result.has_unmatched_braces);
+ assert_eq!(
+ result.declarations[0].declaration.span.end,
+ source.len() - 1
+ );
+ let error = outline(
+ &source,
+ OutlineLimits {
+ max_declarations: 599,
+ ..limits()
+ },
+ )
+ .unwrap_err();
+ assert_eq!(error.resource, OutlineResource::Declarations);
+ assert_eq!(error.observed_at_least, 600);
+ assert_eq!(error.offset, source.find("oa_activity work_599").unwrap());
+}
+
+#[test]
+fn noncanonical_uuid_spellings_have_no_outline_or_analysis_identity() {
+ for raw in [
+ "00000000000000000000000000000001",
+ "{00000000-0000-0000-0000-000000000001}",
+ "urn:uuid:00000000-0000-0000-0000-000000000001",
+ ] {
+ assert!(
+ Id::parse_str(raw).is_ok(),
+ "The UUID crate alone is too permissive"
+ );
+ assert!(!has_uuid_header_shape(raw));
+ let literal = format!("\"{raw}\"");
+ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let before = modern.clone();
+ let result = outline(&modern, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
+ assert_eq!(modern, before);
+ let errors = parse(&modern).unwrap_err();
+ assert!(
+ errors
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12")
+ && error.span.start == modern.find(&literal).unwrap())
+ );
+ // Intentional compatibility correction: old analyze yielded Some(Id(1))
+ // here despite lint rejecting the header. It now shares outline's None.
+ let analysis = analyze(&modern);
+ assert_eq!(
+ analysis
+ .declarations
+ .iter()
+ .find(|d| d.alias == "model")
+ .unwrap()
+ .id,
+ None
+ );
+ assert!(
+ analysis
+ .diagnostics
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12"))
+ );
+ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
+ let legacy_errors = legacy::parse(&legacy_source).unwrap_err();
+ assert!(
+ legacy_errors
+ .iter()
+ .any(|error| error.message.contains("8-4-4-4-12"))
+ );
+ }
+}
+
+#[test]
+fn uppercase_and_json_escaped_header_ids_survive_both_parsers_and_outline() {
+ let expected = Id::from_u128(0xabcdef);
+ let canonical = expected.to_string();
+ let uppercase = canonical.to_uppercase();
+ assert_ne!(uppercase, canonical);
+ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
+ let literal = format!("\"{raw}\"");
+ let decoded: String = serde_json::from_str(&literal).unwrap();
+ assert!(has_uuid_header_shape(&decoded));
+ let modern = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let result = outline(&modern, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, Some(expected));
+ assert_eq!(text(&modern, d.id_literal_span.unwrap()), literal);
+ assert_eq!(result.source.as_bytes(), modern.as_bytes());
+ assert_eq!(parse(&modern).unwrap().id, expected);
+ let analysis = analyze(&modern);
+ assert_eq!(analysis.declarations[0].id, Some(expected));
+ assert!(
+ !analysis
+ .diagnostics
+ .iter()
+ .any(|error| error.severity == crate::validation::Severity::Error)
+ );
+ let legacy_source = format!("arcadia 1; project \"Legacy\" id {literal} {{}}");
+ assert_eq!(legacy::parse(&legacy_source).unwrap().id, expected);
+ }
+}
+
+#[test]
+fn header_shape_still_requires_valid_hexadecimal_uuid_data() {
+ let raw = "00000000-0000-0000-0000-00000000000Z";
+ assert!(has_uuid_header_shape(raw));
+ assert!(Id::parse_str(raw).is_err());
+ let literal = format!("\"{raw}\"");
+ let source = format!("arcadia 7; project model \"Model\" id {literal} {{}}");
+ let result = outline(&source, limits()).unwrap();
+ let d = declaration(&result, "model");
+ assert_eq!(d.declaration.id, None);
+ assert_eq!(text(&source, d.id_literal_span.unwrap()), literal);
+ assert!(
+ parse(&source)
+ .unwrap_err()
+ .iter()
+ .any(|error| error.message == "Invalid UUID identity.")
+ );
+ assert_eq!(analyze(&source).declarations[0].id, None);
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/literal-byte-readback.json b/harness/experiments/text-navigation-components-20260911/components/literal-byte-readback.json
new file mode 100644
index 00000000..51a44872
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/literal-byte-readback.json
@@ -0,0 +1,44 @@
+{
+ "status": "PASS",
+ "method": "Explicit original UTF-8 hex patterns and raw read_bytes, independent of locale text decoding",
+ "patterns": [
+ {
+ "name": "rescue",
+ "utf8Hex": "e69591e58aa9",
+ "verified": true
+ },
+ {
+ "name": "fire_engine",
+ "utf8Hex": "f09f9a92",
+ "verified": true
+ },
+ {
+ "name": "comment",
+ "utf8Hex": "e6b3a8e9878a",
+ "verified": true
+ },
+ {
+ "name": "inspection",
+ "utf8Hex": "e6a49ce69fbb",
+ "verified": true
+ },
+ {
+ "name": "ocean",
+ "utf8Hex": "f09f8c8a",
+ "verified": true
+ },
+ {
+ "name": "voyage",
+ "utf8Hex": "e888aae6b5b7",
+ "verified": true
+ }
+ ],
+ "originalRescueCharacterLiteralCount": 4,
+ "candidateRescueCharacterLiteralCount": 4,
+ "querySha256": "86666c8390f0c63ec75b045366494a80d0ecde82924f2bac312afd01675aee56",
+ "outlineTestsSha256": "4c79a01e8aea243dd9ef66ce71b9fae263d0e79c319a46c51223679897e8b9df",
+ "priorVerifierFailures": [
+ "Initial expectation used e98788 instead of original e9878a in comment fixture",
+ "Next expectation assumed three char-literal uses; direct original raw-byte readback establishes four. Both original and corrected candidate contain four. Only verifier expectations changed."
+ ]
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/outline1-status.json b/harness/experiments/text-navigation-components-20260911/components/outline1-status.json
new file mode 100644
index 00000000..f41143c3
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/outline1-status.json
@@ -0,0 +1,17 @@
+{
+ "started": "2026-09-11T16:17:39.9144342Z",
+ "finished": "2026-09-11T16:23:09.9455930Z",
+ "exitCode": 0,
+ "args": [
+ "test",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-core",
+ "--lib",
+ "language::outline_tests::",
+ "--",
+ "--test-threads=1"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/outline1.log b/harness/experiments/text-navigation-components-20260911/components/outline1.log
new file mode 100644
index 00000000..5d25263b
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/outline1.log
@@ -0,0 +1,20 @@
+ Compiling oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-core)
+ Finished `test` profile [unoptimized] target(s) in 5m 25s
+ Running unittests src\lib.rs (.local\target-gnullvm\debug\deps\oa_core-e594d1d7e7cdca87.exe)
+
+running 12 tests
+test language::outline_tests::bounds_refuse_before_returning_a_partial_outline ... ok
+test language::outline_tests::closed_id_at_eof_is_inside_the_declaration_extent ... ok
+test language::outline_tests::deeply_nested_bounds_use_the_same_iterative_walk_and_declaration_budget ... ok
+test language::outline_tests::header_shape_still_requires_valid_hexadecimal_uuid_data ... ok
+test language::outline_tests::invalid_full_scan_keeps_closed_children_and_reports_lexical_body_facts ... ok
+test language::outline_tests::keyword_alias_and_collisions_keep_every_source_ordered_header ... ok
+test language::outline_tests::missing_bad_and_historical_ids_never_allocate_authored_identity ... ok
+test language::outline_tests::missing_body_syntax_is_unchecked_even_when_known_error_flags_are_clear ... ok
+test language::outline_tests::noncanonical_uuid_spellings_have_no_outline_or_analysis_identity ... ok
+test language::outline_tests::outline_ranges_follow_current_bytes_without_identity_stabilization ... ok
+test language::outline_tests::outline_roles_preserve_unicode_crlf_and_exact_escaped_uuid_literal ... ok
+test language::outline_tests::uppercase_and_json_escaped_header_ids_survive_both_parsers_and_outline ... ok
+
+test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 42 filtered out; finished in 1.44s
+
diff --git a/harness/experiments/text-navigation-components-20260911/components/outline2-inputs.json b/harness/experiments/text-navigation-components-20260911/components/outline2-inputs.json
new file mode 100644
index 00000000..10a334c7
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/outline2-inputs.json
@@ -0,0 +1,10 @@
+[
+ {
+ "Path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\src\\language.rs",
+ "Hash": "34CBB144A111E6F0DDADFCA7D40636A8E3323FE65EA8920FD2402F0352B41F57"
+ },
+ {
+ "Path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\src\\language_outline_tests.rs",
+ "Hash": "4C79A01E8AEA243DD9EF66CE71B9FAE263D0E79C319A46C51223679897E8B9DF"
+ }
+]
diff --git a/harness/experiments/text-navigation-components-20260911/components/outline2-status.json b/harness/experiments/text-navigation-components-20260911/components/outline2-status.json
new file mode 100644
index 00000000..4ab8f85a
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/outline2-status.json
@@ -0,0 +1,17 @@
+{
+ "started": "2026-09-11T17:17:43.3562368Z",
+ "finished": "2026-09-11T17:18:18.6301449Z",
+ "exitCode": 0,
+ "args": [
+ "test",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-core",
+ "--lib",
+ "language::outline_tests::",
+ "--",
+ "--test-threads=1"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/outline2.log b/harness/experiments/text-navigation-components-20260911/components/outline2.log
new file mode 100644
index 00000000..31de8d5a
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/outline2.log
@@ -0,0 +1,20 @@
+ Compiling oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-core)
+ Finished `test` profile [unoptimized] target(s) in 34.77s
+ Running unittests src\lib.rs (.local\target-gnullvm\debug\deps\oa_core-e594d1d7e7cdca87.exe)
+
+running 12 tests
+test language::outline_tests::bounds_refuse_before_returning_a_partial_outline ... ok
+test language::outline_tests::closed_id_at_eof_is_inside_the_declaration_extent ... ok
+test language::outline_tests::deeply_nested_bounds_use_the_same_iterative_walk_and_declaration_budget ... ok
+test language::outline_tests::header_shape_still_requires_valid_hexadecimal_uuid_data ... ok
+test language::outline_tests::invalid_full_scan_keeps_closed_children_and_reports_lexical_body_facts ... ok
+test language::outline_tests::keyword_alias_and_collisions_keep_every_source_ordered_header ... ok
+test language::outline_tests::missing_bad_and_historical_ids_never_allocate_authored_identity ... ok
+test language::outline_tests::missing_body_syntax_is_unchecked_even_when_known_error_flags_are_clear ... ok
+test language::outline_tests::noncanonical_uuid_spellings_have_no_outline_or_analysis_identity ... ok
+test language::outline_tests::outline_ranges_follow_current_bytes_without_identity_stabilization ... ok
+test language::outline_tests::outline_roles_preserve_unicode_crlf_and_exact_escaped_uuid_literal ... ok
+test language::outline_tests::uppercase_and_json_escaped_header_ids_survive_both_parsers_and_outline ... ok
+
+test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 42 filtered out; finished in 0.11s
+
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-artifacts3.json b/harness/experiments/text-navigation-components-20260911/components/query-artifacts3.json
new file mode 100644
index 00000000..baa6fe64
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-artifacts3.json
@@ -0,0 +1,5 @@
+{
+ "sourceSha256": "FE37CD55B95C892C05AEC6C37A113154CAB7646DCFC333646FA8218345919751",
+ "binarySha256": "A20FD5147CC1953A13491050B289FAFC4049FBCD88D41B1BAC5D379FED2DAD63",
+ "binaryBytes": 8076800
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-binding1-failure.json b/harness/experiments/text-navigation-components-20260911/components/query-binding1-failure.json
new file mode 100644
index 00000000..af5b0f83
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-binding1-failure.json
@@ -0,0 +1,12 @@
+{
+ "scope": "Private artifact-binding helper failure before query rustc/test; all core gates and original artifacts1 Cargo build already passed",
+ "helper": "run-query.ps1",
+ "helperSha256": "c112d1a496ed9d39e202f7704548863af45e532dfe89c675bb84b2569201b1ec",
+ "observedExitCode": 1,
+ "observedLine": 34,
+ "exception": "Exception calling Substring with 1 argument(s): startIndex cannot be larger than length of string. (Parameter startIndex)",
+ "cause": "Cargo reports top-level liboa_core.rlib without the hash suffix assumed by the helper",
+ "recovery": "Read existing successful Cargo JSON; bind top-level rlib to one byte-identical hashed dependency rlib using SHA256 plus exact byte readback; preserve matching fingerprint. No repeated Cargo/core gates.",
+ "resumedHelper": "run-query-resume.ps1",
+ "resumedHelperSha256": "07c707cfe4c143af8a64a5ba88de24c866d9a9929f23206f39e21df589c0977b"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-bindings.json b/harness/experiments/text-navigation-components-20260911/components/query-bindings.json
new file mode 100644
index 00000000..5fed40bf
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-bindings.json
@@ -0,0 +1,137 @@
+[
+ {
+ "crate": "oa_core",
+ "rlib": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\liboa_core.rlib",
+ "bytes": 100082308,
+ "sha256": "43C8673DDBAE26D32D0557C7DB5E9F2AABA0B41F19AD1488A58461FE86BD5BDD",
+ "cargoArtifact": {
+ "reason": "compiler-artifact",
+ "package_id": "path+file:///C:/Users/User/Documents/Git/ARCADIA-Studio/crates/oa-core#0.2.0",
+ "manifest_path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\Cargo.toml",
+ "target": {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "oa_core",
+ "src_path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\crates\\oa-core\\src\\lib.rs",
+ "edition": "2024",
+ "doc": true,
+ "doctest": true,
+ "test": true
+ },
+ "profile": {
+ "opt_level": "0",
+ "debuginfo": 0,
+ "debug_assertions": true,
+ "overflow_checks": true,
+ "test": false
+ },
+ "features": [],
+ "filenames": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\liboa_core.rlib",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-781c6973417b169c.rmeta"
+ ],
+ "executable": null,
+ "fresh": true
+ },
+ "matchedDependencyRlib": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-781c6973417b169c.rlib",
+ "candidateHashes": [
+ {
+ "path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-095667392272c35c.rlib",
+ "sha256": "48872B220BC4450C172978F891D028F37B06822C05321C290A5AD2274A049E55"
+ },
+ {
+ "path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-3e81c616fea1d901.rlib",
+ "sha256": "5018A1FD7286ED9DFEDCBBF5B68B399AD5D143F2A9AAAED3078CFC934C9589C4"
+ },
+ {
+ "path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-6c8da57367eec642.rlib",
+ "sha256": "E49B23D858AC5EC1F5A4B9E2EFA78A4549CE6BC2C3936F8D76DB3B42E4A8D315"
+ },
+ {
+ "path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-781c6973417b169c.rlib",
+ "sha256": "43C8673DDBAE26D32D0557C7DB5E9F2AABA0B41F19AD1488A58461FE86BD5BDD"
+ },
+ {
+ "path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\liboa_core-fa08f4e46e1897c2.rlib",
+ "sha256": "A6084C3388E39C06B94EB9C13B4489A8AC974FB06907E86CC657CEEBE295401B"
+ }
+ ],
+ "fingerprints": [
+ {
+ "original": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\.fingerprint\\oa-core-781c6973417b169c\\lib-oa_core",
+ "copy": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\fingerprints\\oa-core-781c6973417b169c\\lib-oa_core",
+ "sha256": "32D7FADB58B89D0877AB923522A0BBB715AC42C34526DE01CD3CEC0BE61DF760"
+ },
+ {
+ "original": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\.fingerprint\\oa-core-781c6973417b169c\\lib-oa_core.json",
+ "copy": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\fingerprints\\oa-core-781c6973417b169c\\lib-oa_core.json",
+ "sha256": "7664E5D9695D989D99873C6937409B188EE00489F6A808051D8FBAFDAF826BAA"
+ }
+ ]
+ },
+ {
+ "crate": "serde_json",
+ "rlib": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib",
+ "bytes": 2698846,
+ "sha256": "441E8532A8AB31BB414A7FA1357354C27A692B3818CD9A219CF2D6DE32608589",
+ "cargoArtifact": {
+ "reason": "compiler-artifact",
+ "package_id": "registry+https://github.com/rust-lang/crates.io-index#serde_json@1.0.151",
+ "manifest_path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_json-1.0.151\\Cargo.toml",
+ "target": {
+ "kind": [
+ "lib"
+ ],
+ "crate_types": [
+ "lib"
+ ],
+ "name": "serde_json",
+ "src_path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\serde_json-1.0.151\\src\\lib.rs",
+ "edition": "2021",
+ "doc": true,
+ "doctest": true,
+ "test": true
+ },
+ "profile": {
+ "opt_level": "0",
+ "debuginfo": 0,
+ "debug_assertions": true,
+ "overflow_checks": true,
+ "test": false
+ },
+ "features": [
+ "default",
+ "std"
+ ],
+ "filenames": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rmeta"
+ ],
+ "executable": null,
+ "fresh": true
+ },
+ "matchedDependencyRlib": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib",
+ "candidateHashes": [
+ {
+ "path": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib",
+ "sha256": "441E8532A8AB31BB414A7FA1357354C27A692B3818CD9A219CF2D6DE32608589"
+ }
+ ],
+ "fingerprints": [
+ {
+ "original": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\.fingerprint\\serde_json-b48005beba7e4267\\lib-serde_json",
+ "copy": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\fingerprints\\serde_json-b48005beba7e4267\\lib-serde_json",
+ "sha256": "9202101A31AF6F8D24EDCEACBB8300C10DDDD94FB496CBF6451C8860133CE784"
+ },
+ {
+ "original": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\.fingerprint\\serde_json-b48005beba7e4267\\lib-serde_json.json",
+ "copy": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\fingerprints\\serde_json-b48005beba7e4267\\lib-serde_json.json",
+ "sha256": "310BEDB04F5F35EEF3A04E8019F0A204CC0F30B94CA808CB4D702CD7C6403737"
+ }
+ ]
+ }
+]
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile1-status.json b/harness/experiments/text-navigation-components-20260911/components/query-compile1-status.json
new file mode 100644
index 00000000..641edc3d
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-compile1-status.json
@@ -0,0 +1,25 @@
+{
+ "started": "2026-09-11T17:11:13.8444785Z",
+ "finished": "2026-09-11T17:11:14.3049366Z",
+ "exitCode": 1,
+ "executable": "rustc",
+ "args": [
+ "--edition=2024",
+ "--crate-name",
+ "source_symbols_v2",
+ "--test",
+ "-Dwarnings",
+ "-C",
+ "linker=clang.exe",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\source_symbols-v2.rs",
+ "--extern",
+ "oa_core=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\liboa_core.rlib",
+ "--extern",
+ "serde_json=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib",
+ "-L",
+ "dependency=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps",
+ "-o",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\source-symbols-v2-tests.exe"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile1-stderr.log b/harness/experiments/text-navigation-components-20260911/components/query-compile1-stderr.log
new file mode 100644
index 00000000..eb53af52
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-compile1-stderr.log
@@ -0,0 +1,39 @@
+error: unknown start of token: \u{2022}
+ --> C:\Users\User\Documents\Git\ARCADIA-Studio\.local\text-navigation-integration-20260911\source_symbols-v2.rs:811:50
+ |
+811 | let unicode_inside = fixture.text.find('æ•‘').unwrap() + 1;
+ | ^
+ |
+help: Unicode character '•' (Bullet) looks like '.' (Period), but it is not
+ |
+811 - let unicode_inside = fixture.text.find('æ•‘').unwrap() + 1;
+811 + let unicode_inside = fixture.text.find('æ.‘').unwrap() + 1;
+ |
+
+error: unknown start of token: \u{2018}
+ --> C:\Users\User\Documents\Git\ARCADIA-Studio\.local\text-navigation-integration-20260911\source_symbols-v2.rs:811:51
+ |
+811 | let unicode_inside = fixture.text.find('æ•‘').unwrap() + 1;
+ | ^
+ |
+help: Unicode character '‘' (Left Single Quotation Mark) looks like ''' (Single Quote), but it is not
+ |
+811 - let unicode_inside = fixture.text.find('æ•‘').unwrap() + 1;
+811 + let unicode_inside = fixture.text.find('æ•'').unwrap() + 1;
+ |
+
+error[E0762]: unterminated character literal
+ --> C:\Users\User\Documents\Git\ARCADIA-Studio\.local\text-navigation-integration-20260911\source_symbols-v2.rs:811:52
+ |
+811 | let unicode_inside = fixture.text.find('æ•‘').unwrap() + 1;
+ | ^^^^^^^^^^^^^^^^
+ |
+help: if you meant to write a string literal, use double quotes
+ |
+811 - let unicode_inside = fixture.text.find('æ•‘').unwrap() + 1;
+811 + let unicode_inside = fixture.text.find("æ•‘").unwrap() + 1;
+ |
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0762`.
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile1-stdout.log b/harness/experiments/text-navigation-components-20260911/components/query-compile1-stdout.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile2-status.json b/harness/experiments/text-navigation-components-20260911/components/query-compile2-status.json
new file mode 100644
index 00000000..21a2b19f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-compile2-status.json
@@ -0,0 +1,25 @@
+{
+ "started": "2026-09-11T17:14:29.0879039Z",
+ "finished": "2026-09-11T17:14:31.5558783Z",
+ "exitCode": 1,
+ "executable": "rustc",
+ "args": [
+ "--edition=2024",
+ "--crate-name",
+ "source_symbols_v2",
+ "--test",
+ "-Dwarnings",
+ "-C",
+ "linker=clang.exe",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\source_symbols-v2-utf8.rs",
+ "--extern",
+ "oa_core=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\liboa_core.rlib",
+ "--extern",
+ "serde_json=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib",
+ "-L",
+ "dependency=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps",
+ "-o",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\source-symbols-v2-utf8-tests.exe"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile2-stderr.log b/harness/experiments/text-navigation-components-20260911/components/query-compile2-stderr.log
new file mode 100644
index 00000000..43997fe5
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-compile2-stderr.log
@@ -0,0 +1,42 @@
+error[E0271]: expected `as_slice` to return `&[_; 0]`, but it returns `&[usize]`
+ --> C:\Users\User\Documents\Git\ARCADIA-Studio\.local\text-navigation-integration-20260911\source_symbols-v2-utf8.rs:543:81
+ |
+543 | Definitions { index: self, indices: self.aliases.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ | ------ ^^^^^^^^^^^^^ expected `&[_; 0]`, found `&[usize]`
+ | |
+ | required by a bound introduced by this call
+ |
+ = note: expected reference `&[_; 0]`
+ found reference `&[usize]`
+note: required by a bound in `Option::::map_or`
+ --> /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library\core\src\option.rs:1222:4
+
+error[E0271]: expected `as_slice` to return `&[_; 0]`, but it returns `&[usize]`
+ --> C:\Users\User\Documents\Git\ARCADIA-Studio\.local\text-navigation-integration-20260911\source_symbols-v2-utf8.rs:547:82
+ |
+547 | Definitions { index: self, indices: self.identities.get(&id).map_or(&[], Vec::as_slice).iter(), domain }
+ | ------ ^^^^^^^^^^^^^ expected `&[_; 0]`, found `&[usize]`
+ | |
+ | required by a bound introduced by this call
+ |
+ = note: expected reference `&[_; 0]`
+ found reference `&[usize]`
+note: required by a bound in `Option::::map_or`
+ --> /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library\core\src\option.rs:1222:4
+
+error[E0271]: expected `as_slice` to return `&[_; 0]`, but it returns `&[usize]`
+ --> C:\Users\User\Documents\Git\ARCADIA-Studio\.local\text-navigation-integration-20260911\source_symbols-v2-utf8.rs:551:77
+ |
+551 | References { index: self, indices: self.uses.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ | ------ ^^^^^^^^^^^^^ expected `&[_; 0]`, found `&[usize]`
+ | |
+ | required by a bound introduced by this call
+ |
+ = note: expected reference `&[_; 0]`
+ found reference `&[usize]`
+note: required by a bound in `Option::::map_or`
+ --> /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library\core\src\option.rs:1222:4
+
+error: aborting due to 3 previous errors
+
+For more information about this error, try `rustc --explain E0271`.
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile2-stdout.log b/harness/experiments/text-navigation-components-20260911/components/query-compile2-stdout.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile3-status.json b/harness/experiments/text-navigation-components-20260911/components/query-compile3-status.json
new file mode 100644
index 00000000..b6550d34
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-compile3-status.json
@@ -0,0 +1,25 @@
+{
+ "started": "2026-09-11T17:16:42.8222070Z",
+ "finished": "2026-09-11T17:16:47.1533244Z",
+ "exitCode": 0,
+ "executable": "rustc",
+ "args": [
+ "--edition=2024",
+ "--crate-name",
+ "source_symbols_v2",
+ "--test",
+ "-Dwarnings",
+ "-C",
+ "linker=clang.exe",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\source_symbols-v2-utf8-typed.rs",
+ "--extern",
+ "oa_core=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\liboa_core.rlib",
+ "--extern",
+ "serde_json=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps\\libserde_json-b48005beba7e4267.rlib",
+ "-L",
+ "dependency=C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\target-gnullvm\\debug\\deps",
+ "-o",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\source-symbols-v2-utf8-typed-tests.exe"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile3-stderr.log b/harness/experiments/text-navigation-components-20260911/components/query-compile3-stderr.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-compile3-stdout.log b/harness/experiments/text-navigation-components-20260911/components/query-compile3-stdout.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-source-reconstruction.json b/harness/experiments/text-navigation-components-20260911/components/query-source-reconstruction.json
new file mode 100644
index 00000000..fdddf3e8
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-source-reconstruction.json
@@ -0,0 +1,12 @@
+{
+ "baselineSha256": "cf82e34544ce508e446787b6335aefe97e146ea5e7cca0a9788deb9097f8f7bb",
+ "patchSha256": "f38b2d5bab3ca6ab66b8bd865f3b01562299fb961c48c8172d39e1d35d90e329",
+ "exactPatchedSha256": "3f313e44fad033b042699a47d2432d0a3a725c37ab9a6213b6dfcafd4b04d7b2",
+ "candidateSha256": "8f3f20a7e72c870cfd1ad46f0aa8b8998ffc1ebf3357911957ed4995758e7520",
+ "onlyPostPatchChange": {
+ "old": "/// strings; they cannot scale beyond the admitted input bytes.",
+ "new": "/// strings. Scratch storage is proportional to admitted input bytes and file\n/// count, including Unicode expansion and collection overhead."
+ },
+ "testAttributes": 20,
+ "execution": "pending"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-tests3-status.json b/harness/experiments/text-navigation-components-20260911/components/query-tests3-status.json
new file mode 100644
index 00000000..1d8f8740
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-tests3-status.json
@@ -0,0 +1,10 @@
+{
+ "started": "2026-09-11T17:16:47.2718656Z",
+ "finished": "2026-09-11T17:16:47.3866620Z",
+ "exitCode": 0,
+ "executable": "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-integration-20260911\\source-symbols-v2-utf8-typed-tests.exe",
+ "args": [
+ "--test-threads=1"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-tests3-stderr.log b/harness/experiments/text-navigation-components-20260911/components/query-tests3-stderr.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-tests3-stdout.log b/harness/experiments/text-navigation-components-20260911/components/query-tests3-stdout.log
new file mode 100644
index 00000000..de2fea26
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-tests3-stdout.log
@@ -0,0 +1,25 @@
+
+running 20 tests
+test tests::admitted_unicode_path_budget_keeps_original_policy_after_single_fold ... ok
+test tests::comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans ... ok
+test tests::declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search ... ok
+test tests::detached_uuid_clone_disables_anchor_even_for_included_query ... ok
+test tests::duplicate_aliases_are_candidates_without_false_reference_attribution ... ok
+test tests::every_budget_admits_boundary_and_returns_no_index_one_below ... ok
+test tests::header_metadata_must_match_current_token_boundaries_and_authored_id ... ok
+test tests::incomplete_draft_is_lexical_and_error_token_does_not_leak_references ... ok
+test tests::legal_keyword_classified_alias_is_not_discarded ... ok
+test tests::missing_ids_produce_only_current_snapshot_locations_without_stabilization ... ok
+test tests::nested_declaration_extents_do_not_turn_body_text_into_definition_hits ... ok
+test tests::noncanonical_header_identity_cannot_enter_through_query_metadata ... ok
+test tests::path_identity_rejects_normalization_and_case_collisions ... ok
+test tests::query_header_admission_preserves_uppercase_and_json_escape_bytes ... ok
+test tests::repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded ... ok
+test tests::reused_alias_cannot_steal_old_identity_and_old_locations_are_stale ... ok
+test tests::scope_provenance_and_detached_alias_collision_are_explicit ... ok
+test tests::stopped_outline_is_not_a_successful_empty_query ... ok
+test tests::truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial ... ok
+test tests::unicode_path_collisions_match_core_lowercase_policy ... ok
+
+test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
+
diff --git a/harness/experiments/text-navigation-components-20260911/components/query-type-correction.json b/harness/experiments/text-navigation-components-20260911/components/query-type-correction.json
new file mode 100644
index 00000000..a781baf2
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/query-type-correction.json
@@ -0,0 +1,9 @@
+{
+ "baselineCandidateSha256": "86666c8390f0c63ec75b045366494a80d0ecde82924f2bac312afd01675aee56",
+ "candidateSha256": "fe37cd55b95c892c05aec6c37a113154cab7646dcfc333646fa8218345919751",
+ "correction": "Exactly three empty-array references coerced to empty slices in map_or defaults",
+ "occurrences": 3,
+ "apiChange": false,
+ "cause": "Actual query-compile2 E0271 at lines543/547/551",
+ "tests": "pending"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/run-gates.ps1 b/harness/experiments/text-navigation-components-20260911/components/run-gates.ps1
new file mode 100644
index 00000000..bc903749
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/run-gates.ps1
@@ -0,0 +1,20 @@
+$ErrorActionPreference = 'Stop'
+. ./.local/toolchain/env.ps1
+$env:CARGO_BUILD_JOBS = '1'
+$evidenceRoot = Join-Path (Get-Location) '.local/text-navigation-integration-20260911'
+$gates = @(
+ @{name='outline1'; args=@('test','--locked','--offline','-p','oa-core','--lib','language::outline_tests::','--','--test-threads=1')},
+ @{name='core1'; args=@('test','--locked','--offline','-p','oa-core','--','--test-threads=1')},
+ @{name='clippy1'; args=@('clippy','--locked','--offline','-p','oa-core','--all-targets','--','-D','warnings')}
+)
+foreach ($gate in $gates) {
+ $logPath = Join-Path $evidenceRoot ($gate.name + '.log')
+ $statusPath = Join-Path $evidenceRoot ($gate.name + '-status.json')
+ if ((Test-Path -LiteralPath $logPath) -or (Test-Path -LiteralPath $statusPath)) { throw "Refusing to overwrite $($gate.name)" }
+ $started = [DateTime]::UtcNow.ToString('o')
+ $gateArgs = $gate.args
+ & cargo @gateArgs 2>&1 | Tee-Object -FilePath $logPath
+ $gateExit = $LASTEXITCODE
+ [ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$gateExit;args=$gateArgs;buildJobs=1} | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $statusPath
+ if ($gateExit -ne 0) { exit $gateExit }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/run-query-resume.ps1 b/harness/experiments/text-navigation-components-20260911/components/run-query-resume.ps1
new file mode 100644
index 00000000..fbae6708
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/run-query-resume.ps1
@@ -0,0 +1,74 @@
+$ErrorActionPreference = 'Stop'
+. ./.local/toolchain/env.ps1
+$env:CARGO_BUILD_JOBS = '1'
+$evidenceRoot = Join-Path (Get-Location) '.local/text-navigation-integration-20260911'
+foreach ($gateName in @('outline1','core1','clippy1')) {
+ $prior = Get-Content -Raw -LiteralPath (Join-Path $evidenceRoot "$gateName-status.json") | ConvertFrom-Json
+ if ($prior.exitCode -ne 0) { throw "Required prior gate failed: $gateName" }
+}
+function Invoke-RecordedNative {
+ param([string]$Name,[string]$Executable,[string[]]$Arguments)
+ $stdout = Join-Path $evidenceRoot "$Name-stdout.log"
+ $stderr = Join-Path $evidenceRoot "$Name-stderr.log"
+ $status = Join-Path $evidenceRoot "$Name-status.json"
+ if ((Test-Path $stdout) -or (Test-Path $stderr) -or (Test-Path $status)) { throw "Refusing overwrite $Name" }
+ $started = [DateTime]::UtcNow.ToString('o')
+ & $Executable @Arguments 1> $stdout 2> $stderr
+ $code = $LASTEXITCODE
+ [ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$code;executable=$Executable;args=$Arguments;buildJobs=1} | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $status
+ Get-Content -LiteralPath $stderr
+ if ($Name -ne 'artifacts1') { Get-Content -LiteralPath $stdout }
+ if ($code -ne 0) { exit $code }
+}
+$artifactStatus = Get-Content -Raw -LiteralPath (Join-Path $evidenceRoot 'artifacts1-status.json') | ConvertFrom-Json
+if ($artifactStatus.exitCode -ne 0) { throw 'Original artifact build did not pass' }
+$dependencyDirectories = @()
+$messages = Get-Content -LiteralPath (Join-Path $evidenceRoot 'artifacts1-stdout.log') | ForEach-Object { $_ | ConvertFrom-Json }
+$bindings = @{}
+$bindingRecords = @()
+foreach ($crate in @('oa_core','serde_json')) {
+ $artifacts = @($messages | Where-Object { $_.reason -eq 'compiler-artifact' -and $_.target.name -eq $crate -and $_.target.kind -contains 'lib' })
+ if ($artifacts.Count -ne 1) { throw "Expected exactly one Cargo lib artifact for $crate; got $($artifacts.Count)" }
+ $rlibs = @($artifacts[0].filenames | Where-Object { $_.EndsWith('.rlib') })
+ if ($rlibs.Count -ne 1) { throw "Expected one rlib for $crate" }
+ $rlib = $rlibs[0]
+ $artifactHash = (Get-FileHash -LiteralPath $rlib).Hash
+ $artifactBase = [IO.Path]::GetFileNameWithoutExtension($rlib)
+ if ($artifactBase.StartsWith('lib' + $crate + '-')) {
+ $dependencyRlib = $rlib
+ $matchEvidence = @([ordered]@{path=$rlib;sha256=$artifactHash})
+ } else {
+ $dependencyRoot = Join-Path (Split-Path $rlib) 'deps'
+ $matchEvidence = @(Get-ChildItem -LiteralPath $dependencyRoot -Filter "lib$crate-*.rlib" | ForEach-Object {
+ [pscustomobject]@{path=$_.FullName;sha256=(Get-FileHash -LiteralPath $_.FullName).Hash}
+ })
+ $matchesByHash = @($matchEvidence | Where-Object { $_.sha256 -eq $artifactHash })
+ if ($matchesByHash.Count -ne 1) { throw "Expected exactly one byte-hash-matching dependency rlib for $crate; got $($matchesByHash.Count)" }
+ $dependencyRlib = $matchesByHash[0].path
+ if (-not [Linq.Enumerable]::SequenceEqual([byte[]][IO.File]::ReadAllBytes($rlib),[byte[]][IO.File]::ReadAllBytes($dependencyRlib))) { throw 'Hash-matched rlib byte readback differs' }
+ }
+ $dependencyDirectories += Split-Path $dependencyRlib
+ $suffix = [IO.Path]::GetFileNameWithoutExtension($dependencyRlib).Substring(('lib' + $crate + '-').Length)
+ $packageName = if ($crate -eq 'oa_core') { 'oa-core' } else { $crate }
+ $fingerprintRoot = Join-Path $env:CARGO_TARGET_DIR "debug/.fingerprint/$packageName-$suffix"
+ $fingerprints = @()
+ foreach ($name in @("lib-$crate", "lib-$crate.json")) {
+ $fp = Join-Path $fingerprintRoot $name
+ if (-not (Test-Path -LiteralPath $fp -PathType Leaf)) { throw "Missing exact fingerprint $fp" }
+ $destination = Join-Path $evidenceRoot "fingerprints/$packageName-$suffix/$name"
+ New-Item -ItemType Directory -Path (Split-Path $destination) -Force | Out-Null
+ Copy-Item -LiteralPath $fp -Destination $destination
+ $fingerprints += [ordered]@{original=$fp;copy=$destination;sha256=(Get-FileHash -LiteralPath $fp).Hash}
+ }
+ $bindings[$crate] = $rlib
+ $bindingRecords += [ordered]@{crate=$crate;rlib=$rlib;bytes=(Get-Item -LiteralPath $rlib).Length;sha256=(Get-FileHash -LiteralPath $rlib).Hash;cargoArtifact=$artifacts[0];matchedDependencyRlib=$dependencyRlib;candidateHashes=$matchEvidence;fingerprints=$fingerprints}
+}
+$bindingRecords | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $evidenceRoot 'query-bindings.json')
+$source = Join-Path $evidenceRoot 'source_symbols-v2.rs'
+$binary = Join-Path $evidenceRoot 'source-symbols-v2-tests.exe'
+$uniqueDependencyDirectories = @($dependencyDirectories | Sort-Object -Unique)
+if ($uniqueDependencyDirectories.Count -ne 1) { throw 'Artifact dependency directories differ' }
+$dependencies = $uniqueDependencyDirectories[0]
+Invoke-RecordedNative 'query-compile1' 'rustc' @('--edition=2024','--crate-name','source_symbols_v2','--test','-Dwarnings','-C','linker=clang.exe',$source,'--extern',"oa_core=$($bindings['oa_core'])",'--extern',"serde_json=$($bindings['serde_json'])",'-L',"dependency=$dependencies",'-o',$binary)
+Invoke-RecordedNative 'query-tests1' $binary @('--test-threads=1')
+[ordered]@{sourceSha256=(Get-FileHash -LiteralPath $source).Hash;binarySha256=(Get-FileHash -LiteralPath $binary).Hash;binaryBytes=(Get-Item -LiteralPath $binary).Length} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $evidenceRoot 'query-artifacts.json')
diff --git a/harness/experiments/text-navigation-components-20260911/components/run-query-typed.ps1 b/harness/experiments/text-navigation-components-20260911/components/run-query-typed.ps1
new file mode 100644
index 00000000..9d23f767
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/run-query-typed.ps1
@@ -0,0 +1,43 @@
+$ErrorActionPreference = 'Stop'
+. ./.local/toolchain/env.ps1
+$env:CARGO_BUILD_JOBS = '1'
+$evidenceRoot = Join-Path (Get-Location) '.local/text-navigation-integration-20260911'
+foreach ($gateName in @('outline1','core1','clippy1')) {
+ $prior = Get-Content -Raw -LiteralPath (Join-Path $evidenceRoot "$gateName-status.json") | ConvertFrom-Json
+ if ($prior.exitCode -ne 0) { throw "Required prior gate failed: $gateName" }
+}
+function Invoke-RecordedNative {
+ param([string]$Name,[string]$Executable,[string[]]$Arguments)
+ $stdout = Join-Path $evidenceRoot "$Name-stdout.log"
+ $stderr = Join-Path $evidenceRoot "$Name-stderr.log"
+ $status = Join-Path $evidenceRoot "$Name-status.json"
+ if ((Test-Path $stdout) -or (Test-Path $stderr) -or (Test-Path $status)) { throw "Refusing overwrite $Name" }
+ $started = [DateTime]::UtcNow.ToString('o')
+ & $Executable @Arguments 1> $stdout 2> $stderr
+ $code = $LASTEXITCODE
+ [ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$code;executable=$Executable;args=$Arguments;buildJobs=1} | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $status
+ Get-Content -LiteralPath $stderr
+ if ($Name -ne 'artifacts1') { Get-Content -LiteralPath $stdout }
+ if ($code -ne 0) { exit $code }
+}
+$records = Get-Content -Raw -LiteralPath (Join-Path $evidenceRoot 'query-bindings.json') | ConvertFrom-Json
+$bindings = @{}
+$dependencyDirectories = @()
+foreach ($r in $records) {
+ if ((Get-FileHash -LiteralPath $r.rlib).Hash -ne $r.sha256) { throw 'Bound artifact changed' }
+ if ((Get-FileHash -LiteralPath $r.matchedDependencyRlib).Hash -ne $r.sha256) { throw 'Matched dependency artifact changed' }
+ foreach ($fp in $r.fingerprints) {
+ if ((Get-FileHash -LiteralPath $fp.original).Hash -ne $fp.sha256 -or (Get-FileHash -LiteralPath $fp.copy).Hash -ne $fp.sha256) { throw 'Bound fingerprint changed' }
+ }
+ $bindings[$r.crate] = $r.rlib
+ $dependencyDirectories += Split-Path $r.matchedDependencyRlib
+}
+$uniqueDirectories = @($dependencyDirectories | Sort-Object -Unique)
+if ($uniqueDirectories.Count -ne 1) { throw 'Dependency directory mismatch' }
+$dependencies = $uniqueDirectories[0]
+$source = Join-Path $evidenceRoot 'source_symbols-v2-utf8-typed.rs'
+if ((Get-FileHash -LiteralPath $source).Hash -ne 'FE37CD55B95C892C05AEC6C37A113154CAB7646DCFC333646FA8218345919751') { throw 'Query source changed' }
+$binary = Join-Path $evidenceRoot 'source-symbols-v2-utf8-typed-tests.exe'
+Invoke-RecordedNative 'query-compile3' 'rustc' @('--edition=2024','--crate-name','source_symbols_v2','--test','-Dwarnings','-C','linker=clang.exe',$source,'--extern',"oa_core=$($bindings['oa_core'])",'--extern',"serde_json=$($bindings['serde_json'])",'-L',"dependency=$dependencies",'-o',$binary)
+Invoke-RecordedNative 'query-tests3' $binary @('--test-threads=1')
+[ordered]@{sourceSha256=(Get-FileHash -LiteralPath $source).Hash;binarySha256=(Get-FileHash -LiteralPath $binary).Hash;binaryBytes=(Get-Item -LiteralPath $binary).Length} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $evidenceRoot 'query-artifacts3.json')
diff --git a/harness/experiments/text-navigation-components-20260911/components/run-query-utf8.ps1 b/harness/experiments/text-navigation-components-20260911/components/run-query-utf8.ps1
new file mode 100644
index 00000000..a7a80481
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/run-query-utf8.ps1
@@ -0,0 +1,43 @@
+$ErrorActionPreference = 'Stop'
+. ./.local/toolchain/env.ps1
+$env:CARGO_BUILD_JOBS = '1'
+$evidenceRoot = Join-Path (Get-Location) '.local/text-navigation-integration-20260911'
+foreach ($gateName in @('outline1','core1','clippy1')) {
+ $prior = Get-Content -Raw -LiteralPath (Join-Path $evidenceRoot "$gateName-status.json") | ConvertFrom-Json
+ if ($prior.exitCode -ne 0) { throw "Required prior gate failed: $gateName" }
+}
+function Invoke-RecordedNative {
+ param([string]$Name,[string]$Executable,[string[]]$Arguments)
+ $stdout = Join-Path $evidenceRoot "$Name-stdout.log"
+ $stderr = Join-Path $evidenceRoot "$Name-stderr.log"
+ $status = Join-Path $evidenceRoot "$Name-status.json"
+ if ((Test-Path $stdout) -or (Test-Path $stderr) -or (Test-Path $status)) { throw "Refusing overwrite $Name" }
+ $started = [DateTime]::UtcNow.ToString('o')
+ & $Executable @Arguments 1> $stdout 2> $stderr
+ $code = $LASTEXITCODE
+ [ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$code;executable=$Executable;args=$Arguments;buildJobs=1} | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $status
+ Get-Content -LiteralPath $stderr
+ if ($Name -ne 'artifacts1') { Get-Content -LiteralPath $stdout }
+ if ($code -ne 0) { exit $code }
+}
+$records = Get-Content -Raw -LiteralPath (Join-Path $evidenceRoot 'query-bindings.json') | ConvertFrom-Json
+$bindings = @{}
+$dependencyDirectories = @()
+foreach ($r in $records) {
+ if ((Get-FileHash -LiteralPath $r.rlib).Hash -ne $r.sha256) { throw 'Bound artifact changed' }
+ if ((Get-FileHash -LiteralPath $r.matchedDependencyRlib).Hash -ne $r.sha256) { throw 'Matched dependency artifact changed' }
+ foreach ($fp in $r.fingerprints) {
+ if ((Get-FileHash -LiteralPath $fp.original).Hash -ne $fp.sha256 -or (Get-FileHash -LiteralPath $fp.copy).Hash -ne $fp.sha256) { throw 'Bound fingerprint changed' }
+ }
+ $bindings[$r.crate] = $r.rlib
+ $dependencyDirectories += Split-Path $r.matchedDependencyRlib
+}
+$uniqueDirectories = @($dependencyDirectories | Sort-Object -Unique)
+if ($uniqueDirectories.Count -ne 1) { throw 'Dependency directory mismatch' }
+$dependencies = $uniqueDirectories[0]
+$source = Join-Path $evidenceRoot 'source_symbols-v2-utf8.rs'
+if ((Get-FileHash -LiteralPath $source).Hash -ne '86666C8390F0C63EC75B045366494A80D0ECDE82924F2BAC312AFD01675AEE56') { throw 'Query source changed' }
+$binary = Join-Path $evidenceRoot 'source-symbols-v2-utf8-tests.exe'
+Invoke-RecordedNative 'query-compile2' 'rustc' @('--edition=2024','--crate-name','source_symbols_v2','--test','-Dwarnings','-C','linker=clang.exe',$source,'--extern',"oa_core=$($bindings['oa_core'])",'--extern',"serde_json=$($bindings['serde_json'])",'-L',"dependency=$dependencies",'-o',$binary)
+Invoke-RecordedNative 'query-tests2' $binary @('--test-threads=1')
+[ordered]@{sourceSha256=(Get-FileHash -LiteralPath $source).Hash;binarySha256=(Get-FileHash -LiteralPath $binary).Hash;binaryBytes=(Get-Item -LiteralPath $binary).Length} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $evidenceRoot 'query-artifacts2.json')
diff --git a/harness/experiments/text-navigation-components-20260911/components/run-query.ps1 b/harness/experiments/text-navigation-components-20260911/components/run-query.ps1
new file mode 100644
index 00000000..4f6c7824
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/run-query.ps1
@@ -0,0 +1,56 @@
+$ErrorActionPreference = 'Stop'
+. ./.local/toolchain/env.ps1
+$env:CARGO_BUILD_JOBS = '1'
+$evidenceRoot = Join-Path (Get-Location) '.local/text-navigation-integration-20260911'
+foreach ($gateName in @('outline1','core1','clippy1')) {
+ $prior = Get-Content -Raw -LiteralPath (Join-Path $evidenceRoot "$gateName-status.json") | ConvertFrom-Json
+ if ($prior.exitCode -ne 0) { throw "Required prior gate failed: $gateName" }
+}
+function Invoke-RecordedNative {
+ param([string]$Name,[string]$Executable,[string[]]$Arguments)
+ $stdout = Join-Path $evidenceRoot "$Name-stdout.log"
+ $stderr = Join-Path $evidenceRoot "$Name-stderr.log"
+ $status = Join-Path $evidenceRoot "$Name-status.json"
+ if ((Test-Path $stdout) -or (Test-Path $stderr) -or (Test-Path $status)) { throw "Refusing overwrite $Name" }
+ $started = [DateTime]::UtcNow.ToString('o')
+ & $Executable @Arguments 1> $stdout 2> $stderr
+ $code = $LASTEXITCODE
+ [ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$code;executable=$Executable;args=$Arguments;buildJobs=1} | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $status
+ Get-Content -LiteralPath $stderr
+ if ($Name -ne 'artifacts1') { Get-Content -LiteralPath $stdout }
+ if ($code -ne 0) { exit $code }
+}
+Invoke-RecordedNative 'compiler1' 'rustc' @('-vV')
+Invoke-RecordedNative 'artifacts1' 'cargo' @('build','--locked','--offline','-p','oa-core','--lib','--message-format=json')
+$messages = Get-Content -LiteralPath (Join-Path $evidenceRoot 'artifacts1-stdout.log') | ForEach-Object { $_ | ConvertFrom-Json }
+$bindings = @{}
+$bindingRecords = @()
+foreach ($crate in @('oa_core','serde_json')) {
+ $artifacts = @($messages | Where-Object { $_.reason -eq 'compiler-artifact' -and $_.target.name -eq $crate -and $_.target.kind -contains 'lib' })
+ if ($artifacts.Count -ne 1) { throw "Expected exactly one Cargo lib artifact for $crate; got $($artifacts.Count)" }
+ $rlibs = @($artifacts[0].filenames | Where-Object { $_.EndsWith('.rlib') })
+ if ($rlibs.Count -ne 1) { throw "Expected one rlib for $crate" }
+ $rlib = $rlibs[0]
+ $suffix = [IO.Path]::GetFileNameWithoutExtension($rlib).Substring(('lib' + $crate + '-').Length)
+ $packageName = if ($crate -eq 'oa_core') { 'oa-core' } else { $crate }
+ $fingerprintRoot = Join-Path $env:CARGO_TARGET_DIR "debug/.fingerprint/$packageName-$suffix"
+ $fingerprints = @()
+ foreach ($name in @("lib-$crate", "lib-$crate.json")) {
+ $fp = Join-Path $fingerprintRoot $name
+ if (-not (Test-Path -LiteralPath $fp -PathType Leaf)) { throw "Missing exact fingerprint $fp" }
+ $destination = Join-Path $evidenceRoot "fingerprints/$packageName-$suffix/$name"
+ New-Item -ItemType Directory -Path (Split-Path $destination) -Force | Out-Null
+ Copy-Item -LiteralPath $fp -Destination $destination
+ $fingerprints += [ordered]@{original=$fp;copy=$destination;sha256=(Get-FileHash -LiteralPath $fp).Hash}
+ }
+ $bindings[$crate] = $rlib
+ $bindingRecords += [ordered]@{crate=$crate;rlib=$rlib;bytes=(Get-Item -LiteralPath $rlib).Length;sha256=(Get-FileHash -LiteralPath $rlib).Hash;cargoArtifact=$artifacts[0];fingerprints=$fingerprints}
+}
+$bindingRecords | ConvertTo-Json -Depth 12 | Set-Content -LiteralPath (Join-Path $evidenceRoot 'query-bindings.json')
+$source = Join-Path $evidenceRoot 'source_symbols-v2.rs'
+$binary = Join-Path $evidenceRoot 'source-symbols-v2-tests.exe'
+$dependencies = Split-Path $bindings['oa_core']
+if ((Split-Path $bindings['serde_json']) -ne $dependencies) { throw 'Artifact dependency directories differ' }
+Invoke-RecordedNative 'query-compile1' 'rustc' @('--edition=2024','--crate-name','source_symbols_v2','--test','-Dwarnings','-C','linker=clang.exe',$source,'--extern',"oa_core=$($bindings['oa_core'])",'--extern',"serde_json=$($bindings['serde_json'])",'-L',"dependency=$dependencies",'-o',$binary)
+Invoke-RecordedNative 'query-tests1' $binary @('--test-threads=1')
+[ordered]@{sourceSha256=(Get-FileHash -LiteralPath $source).Hash;binarySha256=(Get-FileHash -LiteralPath $binary).Hash;binaryBytes=(Get-Item -LiteralPath $binary).Length} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $evidenceRoot 'query-artifacts.json')
diff --git a/harness/experiments/text-navigation-components-20260911/components/run-utf8-outline-gates.ps1 b/harness/experiments/text-navigation-components-20260911/components/run-utf8-outline-gates.ps1
new file mode 100644
index 00000000..a67e53bf
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/run-utf8-outline-gates.ps1
@@ -0,0 +1,22 @@
+$ErrorActionPreference = 'Stop'
+. ./.local/toolchain/env.ps1
+$env:CARGO_BUILD_JOBS = '1'
+$evidenceRoot = Join-Path (Get-Location) '.local/text-navigation-integration-20260911'
+$sourcePaths = @('crates/oa-core/src/language.rs','crates/oa-core/src/language_outline_tests.rs')
+if (Test-Path (Join-Path $evidenceRoot 'outline2-inputs.json')) { throw 'Refusing input receipt overwrite' }
+Get-FileHash -LiteralPath $sourcePaths | Select-Object Path,Hash | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $evidenceRoot 'outline2-inputs.json')
+$gates = @(
+ @{name='outline2'; args=@('test','--locked','--offline','-p','oa-core','--lib','language::outline_tests::','--','--test-threads=1')},
+ @{name='clippy-tests2'; args=@('clippy','--locked','--offline','-p','oa-core','--lib','--tests','--','-D','warnings')}
+)
+foreach ($gate in $gates) {
+ $logPath = Join-Path $evidenceRoot ($gate.name + '.log')
+ $statusPath = Join-Path $evidenceRoot ($gate.name + '-status.json')
+ if ((Test-Path -LiteralPath $logPath) -or (Test-Path -LiteralPath $statusPath)) { throw "Refusing to overwrite $($gate.name)" }
+ $started = [DateTime]::UtcNow.ToString('o')
+ $gateArgs = $gate.args
+ & cargo @gateArgs 2>&1 | Tee-Object -FilePath $logPath
+ $gateExit = $LASTEXITCODE
+ [ordered]@{started=$started;finished=[DateTime]::UtcNow.ToString('o');exitCode=$gateExit;args=$gateArgs;buildJobs=1} | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $statusPath
+ if ($gateExit -ne 0) { exit $gateExit }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8-typed.rs b/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8-typed.rs
new file mode 100644
index 00000000..14aa4d2c
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8-typed.rs
@@ -0,0 +1,1124 @@
+//! Uncompiled experiment: current-text symbol queries, with no model or editor mutation.
+//!
+//! The caller supplies one immutable snapshot of current text, full lexer tokens,
+//! and syntax-only declaration role spans. This module does not lex, parse
+//! Arcadia declarations, lint, compile, allocate model IDs, discover includes,
+//! or read an accepted Project.
+//! Its matches are authored lexical uses, not typed semantic dependency edges.
+
+use oa_core::language::{Span, TokenClass, TokenSpan};
+use oa_core::Id;
+use std::collections::{BTreeMap, BTreeSet};
+use std::ops::Range;
+
+/// Fresh editor/workspace session identity plus a revision changed for EVERY
+/// indexed input change, including membership, outline and validation evidence.
+/// Reusing this key for different input violates the caller contract.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SnapshotKey {
+ pub workspace: Id,
+ pub revision: u64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Membership {
+ Included,
+ Detached,
+ Unverified,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Evidence {
+ /// The caller verified these exact bytes in the current source scope.
+ Validated,
+ /// Current editor bytes; no successful semantic validation is claimed.
+ Draft,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SyntaxCoverage {
+ /// The tolerant walk considered the entire file, even if its text is invalid.
+ FullFile,
+ /// A stopped/budget-limited outline cannot masquerade as a complete result.
+ Incomplete,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PartialReason {
+ IncludesUnverified,
+ MissingFiles,
+ UnappliedWorkspace,
+ UnsupportedBindings,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Scope {
+ CompleteProject,
+ Partial(PartialReason),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Domain {
+ Included,
+ Loaded,
+}
+
+impl Domain {
+ fn contains(self, membership: Membership) -> bool {
+ self == Self::Loaded || membership == Membership::Included
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredIdentity {
+ pub id: Id,
+ /// Whole quoted JSON string token in the declaration header, not a UUID
+ /// mentioned in a body, history record, comment or display name.
+ pub literal_span: Span,
+}
+
+/// These header roles must come from the coordinated core outline seam. The
+/// existing public DeclarationSpan alone lacks alias/kind/ID literal spans.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DeclarationInput {
+ pub span: Span,
+ pub kind_span: Span,
+ pub alias_span: Span,
+ pub identity: Option,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct FileInput<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ /// Exact lexer output for text, in order and covering the entire file.
+ pub tokens: &'a [TokenSpan],
+ /// Tolerant outline in alias-source order. Whole spans may nest.
+ pub declarations: &'a [DeclarationInput],
+ pub syntax: SyntaxCoverage,
+ pub membership: Membership,
+ pub evidence: Evidence,
+}
+
+/// Explicit experiment limits; production must select its own shared budgets.
+/// Caller-owned text/tokens/outline must already have bounded extraction.
+#[derive(Debug, Clone, Copy)]
+pub struct Limits {
+ pub files: usize,
+ pub source_bytes: usize,
+ pub path_bytes: usize,
+ pub tokens: usize,
+ pub records: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Resource {
+ Files,
+ SourceBytes,
+ PathBytes,
+ Tokens,
+ Records,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum InputProblem {
+ NonCanonicalPath,
+ DuplicateOrCaseCollidingPath,
+ InconsistentCompleteScope,
+ IncompleteSyntax,
+ TokenCoverage,
+ TokenBounds,
+ DeclarationBounds,
+ DeclarationOrder,
+ HeaderRoleToken,
+ InvalidAlias,
+ IdentityLiteral,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum BuildError {
+ /// No partial Index is returned. This is not a zero-result query.
+ Limit {
+ resource: Resource,
+ limit: usize,
+ observed_at_least: usize,
+ },
+ Input {
+ file: usize,
+ record: Option,
+ problem: InputProblem,
+ },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Location {
+ pub file: usize,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DefinitionHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ReferenceHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+/// Carries only an authored identity, never an identity generated by compilation.
+/// It may be resolved across revisions of the same workspace. Resolution still
+/// checks global loaded-file uniqueness every time.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredAnchor {
+ workspace: Id,
+ id: Id,
+}
+
+impl AuthoredAnchor {
+ pub fn id(self) -> Id {
+ self.id
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Identity {
+ Missing,
+ Unique(AuthoredAnchor),
+ Collision { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Lookup {
+ Missing,
+ Unique(DefinitionHandle),
+ Ambiguous { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Hit {
+ Definition(DefinitionHandle),
+ Reference(ReferenceHandle),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum QueryError {
+ StaleSnapshot,
+ WrongWorkspace,
+ InvalidHandle,
+ InvalidFile,
+ InvalidBytePosition,
+ OutsideDomain,
+ AmbiguousAlias { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct QueryScope {
+ pub declared: Scope,
+ pub domain: Domain,
+ /// True only for the complete included project, not loaded detached files.
+ pub project_complete: bool,
+ pub all_selected_files_validated: bool,
+ pub selected_files: usize,
+ pub detached_files: usize,
+ pub unverified_files: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Position {
+ pub line: usize,
+ /// Zero-based UTF-8 byte column. The editor adapter must convert explicitly
+ /// if it needs character/UTF-16 columns; CRLF bytes are never normalized.
+ pub byte_column: usize,
+}
+
+#[derive(Debug)]
+pub struct Definition<'a> {
+ pub alias: &'a str,
+ pub kind: &'a str,
+ pub declaration: Location,
+ pub alias_location: Location,
+ pub authored_id: Option,
+}
+
+#[derive(Debug)]
+pub struct Reference<'a> {
+ pub alias: &'a str,
+ pub at: Location,
+ pub alias_location: Location,
+}
+
+#[derive(Debug)]
+pub struct SourceFile<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ pub membership: Membership,
+ pub evidence: Evidence,
+ definitions: Range,
+ references: Range,
+}
+
+#[derive(Debug)]
+pub struct Index<'a> {
+ snapshot: SnapshotKey,
+ scope: Scope,
+ files: Vec>,
+ definitions: Vec>,
+ references: Vec>,
+ aliases: BTreeMap<&'a str, Vec>,
+ uses: BTreeMap<&'a str, Vec>,
+ identities: BTreeMap>,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Counts {
+ definitions: usize,
+ references: usize,
+}
+
+fn charge(total: &mut usize, value: usize, limit: usize, resource: Resource) -> Result<(), BuildError> {
+ let next = total.checked_add(value).ok_or(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: usize::MAX,
+ })?;
+ if next > limit {
+ return Err(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: next,
+ });
+ }
+ *total = next;
+ Ok(())
+}
+
+fn input_error(file: usize, record: Option, problem: InputProblem) -> BuildError {
+ BuildError::Input { file, record, problem }
+}
+
+fn valid_span(text: &str, span: Span) -> bool {
+ span.start < span.end
+ && span.end <= text.len()
+ && text.is_char_boundary(span.start)
+ && text.is_char_boundary(span.end)
+}
+
+fn contains(outer: Span, inner: Span) -> bool {
+ outer.start <= inner.start && inner.end <= outer.end
+}
+
+fn token_at(tokens: &[TokenSpan], span: Span) -> Option {
+ let index = tokens.binary_search_by_key(&span.start, |token| token.span.start).ok()?;
+ let token = tokens[index];
+ (token.span == span).then_some(token)
+}
+
+fn word(class: TokenClass) -> bool {
+ matches!(class, TokenClass::Identifier | TokenClass::Keyword)
+}
+
+/// Only a live punctuation @ followed by a legal alias outside trivia qualifies.
+/// Trivia can separate @ and alias, but its bytes never become a clickable hit.
+fn reference_spans<'a>(file: FileInput<'a>) -> impl Iterator- + 'a {
+ file.tokens.iter().enumerate().filter_map(move |(index, token)| {
+ if token.class != TokenClass::Punctuation || &file.text[token.span.start..token.span.end] != "@" {
+ return None;
+ }
+ let next = file.tokens[index + 1..].iter().find(|next| {
+ !matches!(next.class, TokenClass::Whitespace | TokenClass::Comment)
+ })?;
+ let alias = &file.text[next.span.start..next.span.end];
+ (word(next.class) && oa_core::valid_alias(alias)).then_some((token.span, next.span))
+ })
+}
+
+/// Runs all aggregate limits and structural checks before allocating Index maps
+/// or vectors. Path normalization/quoted-ID decoding may allocate bounded scratch
+/// strings. Scratch storage is proportional to admitted input bytes and file
+/// count, including Unicode expansion and collection overhead.
+fn preflight(files: &[FileInput<'_>], scope: Scope, limits: Limits) -> Result
{
+ let mut file_count = 0;
+ charge(&mut file_count, files.len(), limits.files, Resource::Files)?;
+ let mut bytes = 0;
+ let mut path_bytes = 0;
+ let mut tokens = 0;
+ let mut records = 0;
+ for file in files {
+ charge(&mut bytes, file.text.len(), limits.source_bytes, Resource::SourceBytes)?;
+ charge(&mut path_bytes, file.path.len(), limits.path_bytes, Resource::PathBytes)?;
+ charge(&mut tokens, file.tokens.len(), limits.tokens, Resource::Tokens)?;
+ charge(&mut records, file.declarations.len(), limits.records, Resource::Records)?;
+ }
+ let definitions = records;
+ if scope == Scope::CompleteProject
+ && (!files.iter().any(|file| file.membership == Membership::Included)
+ || files.iter().any(|file| file.membership == Membership::Unverified))
+ {
+ return Err(input_error(0, None, InputProblem::InconsistentCompleteScope));
+ }
+ // Scratch storage is allocated only after aggregate file/path admission.
+ // Each admitted path is folded once, retaining core's Unicode policy.
+ let mut folded_paths = BTreeSet::new();
+ for (file_index, file) in files.iter().copied().enumerate() {
+ if file.syntax != SyntaxCoverage::FullFile {
+ return Err(input_error(file_index, None, InputProblem::IncompleteSyntax));
+ }
+ if oa_core::source_project::resolve_include("", file.path).as_deref() != Ok(file.path) {
+ return Err(input_error(file_index, None, InputProblem::NonCanonicalPath));
+ }
+ if !folded_paths.insert(file.path.to_lowercase()) {
+ return Err(input_error(file_index, None, InputProblem::DuplicateOrCaseCollidingPath));
+ }
+ let mut end = 0;
+ for (token_index, token) in file.tokens.iter().enumerate() {
+ if !valid_span(file.text, token.span) {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenBounds));
+ }
+ if token.span.start != end {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenCoverage));
+ }
+ end = token.span.end;
+ }
+ if end != file.text.len() {
+ return Err(input_error(file_index, None, InputProblem::TokenCoverage));
+ }
+ let mut previous_alias_end = 0;
+ for (declaration_index, declaration) in file.declarations.iter().enumerate() {
+ let problem = |problem| input_error(file_index, Some(declaration_index), problem);
+ if !valid_span(file.text, declaration.span)
+ || !valid_span(file.text, declaration.kind_span)
+ || !valid_span(file.text, declaration.alias_span)
+ || declaration.kind_span.start != declaration.span.start
+ || !contains(declaration.span, declaration.kind_span)
+ || !contains(declaration.span, declaration.alias_span)
+ || declaration.alias_span.start < declaration.kind_span.end
+ {
+ return Err(problem(InputProblem::DeclarationBounds));
+ }
+ if declaration.alias_span.start < previous_alias_end {
+ return Err(problem(InputProblem::DeclarationOrder));
+ }
+ previous_alias_end = declaration.alias_span.end;
+ if !token_at(file.tokens, declaration.kind_span).is_some_and(|token| word(token.class))
+ || !token_at(file.tokens, declaration.alias_span).is_some_and(|token| word(token.class))
+ {
+ return Err(problem(InputProblem::HeaderRoleToken));
+ }
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ if !oa_core::valid_alias(alias) {
+ return Err(problem(InputProblem::InvalidAlias));
+ }
+ if let Some(identity) = declaration.identity {
+ if !valid_span(file.text, identity.literal_span)
+ || !contains(declaration.span, identity.literal_span)
+ || identity.literal_span.start < declaration.alias_span.end
+ || token_at(file.tokens, identity.literal_span).map(|token| token.class) != Some(TokenClass::String)
+ {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ let literal = &file.text[identity.literal_span.start..identity.literal_span.end];
+ let decoded = serde_json::from_str::(literal)
+ .ok()
+ .filter(|value| oa_core::language::has_uuid_header_shape(value))
+ .and_then(|value| Id::parse_str(&value).ok());
+ if decoded != Some(identity.id) {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ }
+ }
+ // Token bounds and coverage are proven before reference_spans slices.
+ for _ in reference_spans(file) {
+ charge(&mut records, 1, limits.records, Resource::Records)?;
+ }
+ }
+ Ok(Counts { definitions, references: records - definitions })
+}
+
+impl<'a> Index<'a> {
+ pub fn build(
+ snapshot: SnapshotKey,
+ files: &[FileInput<'a>],
+ scope: Scope,
+ limits: Limits,
+ ) -> Result {
+ let counts = preflight(files, scope, limits)?;
+ let mut index = Self {
+ snapshot,
+ scope,
+ files: Vec::with_capacity(files.len()),
+ definitions: Vec::with_capacity(counts.definitions),
+ references: Vec::with_capacity(counts.references),
+ aliases: BTreeMap::new(),
+ uses: BTreeMap::new(),
+ identities: BTreeMap::new(),
+ };
+ for (file_index, file) in files.iter().copied().enumerate() {
+ let definition_start = index.definitions.len();
+ let reference_start = index.references.len();
+ for declaration in file.declarations {
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ let number = index.definitions.len();
+ index.aliases.entry(alias).or_default().push(number);
+ if let Some(identity) = declaration.identity {
+ index.identities.entry(identity.id).or_default().push(number);
+ }
+ index.definitions.push(Definition {
+ alias,
+ kind: &file.text[declaration.kind_span.start..declaration.kind_span.end],
+ declaration: Location { file: file_index, span: declaration.span },
+ alias_location: Location { file: file_index, span: declaration.alias_span },
+ authored_id: declaration.identity.map(|identity| identity.id),
+ });
+ }
+ for (at, alias_span) in reference_spans(file) {
+ let alias = &file.text[alias_span.start..alias_span.end];
+ index.uses.entry(alias).or_default().push(index.references.len());
+ index.references.push(Reference {
+ alias,
+ at: Location { file: file_index, span: at },
+ alias_location: Location { file: file_index, span: alias_span },
+ });
+ }
+ index.files.push(SourceFile {
+ path: file.path,
+ text: file.text,
+ membership: file.membership,
+ evidence: file.evidence,
+ definitions: definition_start..index.definitions.len(),
+ references: reference_start..index.references.len(),
+ });
+ }
+ Ok(index)
+ }
+
+ pub fn snapshot(&self) -> SnapshotKey {
+ self.snapshot
+ }
+
+ pub fn source(&self, file: usize) -> Result<&SourceFile<'a>, QueryError> {
+ self.files.get(file).ok_or(QueryError::InvalidFile)
+ }
+
+ pub fn scope(&self, domain: Domain) -> QueryScope {
+ let selected_files = self.files.iter().filter(|file| domain.contains(file.membership)).count();
+ QueryScope {
+ declared: self.scope,
+ domain,
+ project_complete: self.scope == Scope::CompleteProject && domain == Domain::Included,
+ all_selected_files_validated: selected_files != 0
+ && self.files.iter().filter(|file| domain.contains(file.membership))
+ .all(|file| file.evidence == Evidence::Validated),
+ selected_files,
+ detached_files: self.files.iter().filter(|file| file.membership == Membership::Detached).count(),
+ unverified_files: self.files.iter().filter(|file| file.membership == Membership::Unverified).count(),
+ }
+ }
+
+ fn definition_handle(&self, index: usize) -> DefinitionHandle {
+ DefinitionHandle { snapshot: self.snapshot, index }
+ }
+
+ fn reference_handle(&self, index: usize) -> ReferenceHandle {
+ ReferenceHandle { snapshot: self.snapshot, index }
+ }
+
+ pub fn definition(&self, handle: DefinitionHandle) -> Result<&Definition<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.definitions.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn reference(&self, handle: ReferenceHandle) -> Result<&Reference<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.references.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn definitions_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> Definitions<'i, 'a> {
+ Definitions { index: self, indices: self.aliases.get(alias).map_or(&[][..], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn definitions_for_id(&self, id: Id, domain: Domain) -> Definitions<'_, 'a> {
+ Definitions { index: self, indices: self.identities.get(&id).map_or(&[][..], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn references_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> References<'i, 'a> {
+ References { index: self, indices: self.uses.get(alias).map_or(&[][..], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn resolve_reference(&self, handle: ReferenceHandle, domain: Domain) -> Result {
+ let reference = self.reference(handle)?;
+ if !domain.contains(self.files[reference.at.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let mut definitions = self.definitions_for_alias(reference.alias, domain);
+ let Some(first) = definitions.next() else { return Ok(Lookup::Missing); };
+ let count = 1 + definitions.count();
+ Ok(if count == 1 { Lookup::Unique(first) } else { Lookup::Ambiguous { count } })
+ }
+
+ pub fn identity(&self, handle: DefinitionHandle) -> Result {
+ let Some(id) = self.definition(handle)?.authored_id else { return Ok(Identity::Missing); };
+ let count = self.identities[&id].len();
+ Ok(if count == 1 {
+ Identity::Unique(AuthoredAnchor { workspace: self.snapshot.workspace, id })
+ } else {
+ Identity::Collision { count }
+ })
+ }
+
+ pub fn resolve_anchor(&self, anchor: AuthoredAnchor, domain: Domain) -> Result {
+ if anchor.workspace != self.snapshot.workspace {
+ return Err(QueryError::WrongWorkspace);
+ }
+ let Some(candidates) = self.identities.get(&anchor.id) else { return Ok(Lookup::Missing); };
+ // A detached duplicate must not silently confer identity uniqueness.
+ if candidates.len() != 1 {
+ return Ok(Lookup::Ambiguous { count: candidates.len() });
+ }
+ let candidate = candidates[0];
+ Ok(if domain.contains(self.files[self.definitions[candidate].declaration.file].membership) {
+ Lookup::Unique(self.definition_handle(candidate))
+ } else {
+ Lookup::Missing
+ })
+ }
+
+ /// Exact authored alias-use projection for a unique current declaration in
+ /// the requested domain. Duplicate aliases require candidate inspection;
+ /// this API never attributes all matching uses to the first declaration.
+ pub fn references_to(&self, handle: DefinitionHandle, domain: Domain) -> Result, QueryError> {
+ let definition = self.definition(handle)?;
+ if !domain.contains(self.files[definition.declaration.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let count = self.definitions_for_alias(definition.alias, domain).count();
+ if count != 1 {
+ return Err(QueryError::AmbiguousAlias { count });
+ }
+ Ok(self.references_for_alias(definition.alias, domain))
+ }
+
+ pub fn position(&self, file: usize, offset: usize) -> Result {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let prefix = &source.text[..offset];
+ Ok(Position {
+ line: prefix.bytes().filter(|byte| *byte == b'\n').count(),
+ byte_column: prefix.rsplit_once('\n').map_or(prefix.len(), |(_, tail)| tail.len()),
+ })
+ }
+
+ pub fn hit(&self, file: usize, offset: usize) -> Result, QueryError> {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let definitions = &self.definitions[source.definitions.clone()];
+ let before = definitions.partition_point(|definition| definition.alias_location.span.start <= offset);
+ if before != 0 && offset < definitions[before - 1].alias_location.span.end {
+ return Ok(Some(Hit::Definition(self.definition_handle(source.definitions.start + before - 1))));
+ }
+ let references = &self.references[source.references.clone()];
+ let before = references.partition_point(|reference| reference.at.span.start <= offset);
+ if before != 0 {
+ let reference = &references[before - 1];
+ if offset < reference.at.span.end
+ || (reference.alias_location.span.start <= offset && offset < reference.alias_location.span.end)
+ {
+ return Ok(Some(Hit::Reference(self.reference_handle(source.references.start + before - 1))));
+ }
+ }
+ Ok(None)
+ }
+}
+
+pub struct Definitions<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl Iterator for Definitions<'_, '_> {
+ type Item = DefinitionHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let definition = &self.index.definitions[number];
+ if self.domain.contains(self.index.files[definition.declaration.file].membership) {
+ return Some(self.index.definition_handle(number));
+ }
+ }
+ None
+ }
+}
+
+pub struct References<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl References<'_, '_> {
+ pub fn scope(&self) -> QueryScope {
+ self.index.scope(self.domain)
+ }
+}
+
+impl Iterator for References<'_, '_> {
+ type Item = ReferenceHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let reference = &self.index.references[number];
+ if self.domain.contains(self.index.files[reference.at.file].membership) {
+ return Some(self.index.reference_handle(number));
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const A: Id = Id::from_u128(1);
+ const B: Id = Id::from_u128(2);
+ const C: Id = Id::from_u128(3);
+
+ struct Fixture {
+ path: String,
+ text: String,
+ tokens: Vec,
+ declarations: Vec,
+ }
+
+ impl Fixture {
+ /// Test-only literal role proof. Production never searches declaration
+ /// headers: its caller supplies the coordinated syntax outline roles.
+ fn new(path: &str, text: String, roles: &[(&str, &str, Option)]) -> Self {
+ let tokens = oa_core::language::tokenize(&text);
+ let declarations = roles.iter().map(|&(kind, alias, id)| {
+ let header = format!("{kind} {alias} ");
+ let start = text.find(&header).expect("authored fixture header");
+ let end = text[start..].find('}').map_or(text.len(), |end| start + end + 1);
+ let alias_start = start + kind.len() + 1;
+ DeclarationInput {
+ span: Span { start, end },
+ kind_span: Span { start, end: start + kind.len() },
+ alias_span: Span { start: alias_start, end: alias_start + alias.len() },
+ identity: id.map(|id| {
+ let literal = format!("\"{id}\"");
+ let literal_start = start + text[start..end].find(&literal).expect("authored fixture UUID");
+ AuthoredIdentity { id, literal_span: Span { start: literal_start, end: literal_start + literal.len() } }
+ }),
+ }
+ }).collect();
+ Self { path: path.into(), text, tokens, declarations }
+ }
+
+ fn input(&self, membership: Membership, evidence: Evidence) -> FileInput<'_> {
+ FileInput { path: &self.path, text: &self.text, tokens: &self.tokens, declarations: &self.declarations, syntax: SyntaxCoverage::FullFile, membership, evidence }
+ }
+
+ fn included(&self) -> FileInput<'_> {
+ self.input(Membership::Included, Evidence::Draft)
+ }
+ }
+
+ fn snapshot(revision: u64) -> SnapshotKey {
+ SnapshotKey { workspace: Id::from_u128(1000), revision }
+ }
+
+ fn limits() -> Limits {
+ Limits { files: 16, source_bytes: 1024 * 1024, path_bytes: 8192, tokens: 200_000, records: 100_000 }
+ }
+
+ fn index<'a>(files: &[FileInput<'a>]) -> Index<'a> {
+ Index::build(snapshot(1), files, Scope::CompleteProject, limits()).unwrap()
+ }
+
+ fn definition(index: &Index<'_>, alias: &str) -> DefinitionHandle {
+ let mut found = index.definitions_for_alias(alias, Domain::Loaded);
+ let result = found.next().expect("fixture definition");
+ assert!(found.next().is_none(), "fixture expects unique alias");
+ result
+ }
+
+ fn anchor(index: &Index<'_>, handle: DefinitionHandle) -> AuthoredAnchor {
+ match index.identity(handle).unwrap() {
+ Identity::Unique(anchor) => anchor,
+ identity => panic!("fixture expects unique authored identity: {identity:?}"),
+ }
+ }
+
+ #[test]
+ fn repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor tank \"Same display\" id \"{A}\" {{}}\n\
+ oa_actor tank_extra \"Same display\" id \"{B}\" {{}}\n\
+ oa_interaction flow \"Flow\" id \"{C}\" {{ source @tank; target @tank; other @tank_extra; \
+ description \"@tank @tank_extra\"; historical uuid \"{A}\"; }}\n\
+ // @tank @tank_extra and UUID {A}\n"
+ ), &[("oa_actor", "tank", Some(A)), ("oa_actor", "tank_extra", Some(B)), ("oa_interaction", "flow", Some(C))]);
+ let original = (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone());
+ let index = index(&[fixture.included()]);
+ let tank = definition(&index, "tank");
+ let uses: Vec<_> = index.references_to(tank, Domain::Included).unwrap().collect();
+ assert_eq!(uses.len(), 2);
+ assert_ne!(uses[0], uses[1]);
+ assert_eq!(index.references_for_alias("tank_extra", Domain::Loaded).count(), 1);
+ for usage in uses {
+ let record = index.reference(usage).unwrap();
+ assert_eq!(&fixture.text[record.at.span.start..record.at.span.end], "@");
+ assert_eq!(&fixture.text[record.alias_location.span.start..record.alias_location.span.end], "tank");
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(tank)));
+ assert_eq!(index.hit(0, record.alias_location.span.start), Ok(Some(Hit::Reference(usage))));
+ }
+ for ignored in [fixture.text.find("Same display").unwrap(), fixture.text.find("// @tank").unwrap() + 3,
+ fixture.text.find("\"@tank @tank_extra\"").unwrap() + 1,
+ fixture.text.find("historical uuid").unwrap() + "historical uuid \"".len()] {
+ assert_eq!(index.hit(0, ignored), Ok(None));
+ }
+ assert_eq!(index.hit(0, fixture.text.len()), Ok(None));
+ assert_eq!(original, (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone()));
+ }
+
+ #[test]
+ fn comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans() {
+ let fixture = Fixture::new("parts/actors.arcadia", format!(
+ "oa_actor a \"救助 🚒\" id \"{A}\" {{}}\r\nsource @ // 注释 @a\r\n a; target @a;\r\n"
+ ), &[("oa_actor", "a", Some(A))]);
+ let index = index(&[fixture.included()]);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert_eq!(uses.len(), 2);
+ let first = index.reference(uses[0]).unwrap();
+ assert_eq!(index.position(0, first.at.span.start), Ok(Position { line: 1, byte_column: 7 }));
+ assert_eq!(index.position(0, first.alias_location.span.start), Ok(Position { line: 2, byte_column: 2 }));
+ assert_eq!(index.hit(0, first.at.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ assert_eq!(index.hit(0, first.alias_location.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ for byte in [first.at.span.end, fixture.text.find("注释").unwrap(), fixture.text.find("// 注释 @a").unwrap() + "// 注释 ".len(), first.alias_location.span.end] {
+ assert_eq!(index.hit(0, byte), Ok(None));
+ }
+ let unicode_inside = fixture.text.find('救').unwrap() + 1;
+ assert_eq!(index.hit(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.position(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(0, fixture.text.len() + 1), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(1, 0), Err(QueryError::InvalidFile));
+ assert!(index.source(0).unwrap().text.contains("\r\n"));
+ }
+
+ #[test]
+ fn legal_keyword_classified_alias_is_not_discarded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor oa_activity \"Participant\" id \"{A}\" {{}}\nsource @oa_activity; source @parent;"
+ ), &[("oa_actor", "oa_activity", Some(A))]);
+ assert!(oa_core::valid_alias("oa_activity"));
+ assert!(!oa_core::valid_alias("parent"));
+ assert_eq!(token_at(&fixture.tokens, fixture.declarations[0].alias_span).unwrap().class, TokenClass::Keyword);
+ let index = index(&[fixture.included()]);
+ let found = definition(&index, "oa_activity");
+ let usage = index.references_for_alias("oa_activity", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(found)));
+ assert_eq!(index.references_for_alias("parent", Domain::Loaded).count(), 0);
+ assert_eq!(index.hit(0, fixture.declarations[0].alias_span.start), Ok(Some(Hit::Definition(found))));
+ }
+
+ #[test]
+ fn duplicate_aliases_are_candidates_without_false_reference_attribution() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"Same display\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let second = Fixture::new("parts/other.arcadia", format!("oa_actor a \"Same display\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let index = index(&[first.included(), second.included()]);
+ let candidates: Vec<_> = index.definitions_for_alias("a", Domain::Included).collect();
+ assert_eq!(candidates.len(), 2);
+ assert_eq!(index.definition(candidates[0]).unwrap().declaration.file, 0);
+ assert_eq!(index.definition(candidates[1]).unwrap().declaration.file, 1);
+ assert_ne!(anchor(&index, candidates[0]), anchor(&index, candidates[1]));
+ assert!(matches!(index.references_to(candidates[0], Domain::Included), Err(QueryError::AmbiguousAlias { count: 2 })));
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Included).collect();
+ assert_eq!(uses.len(), 2);
+ for usage in uses {
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ }
+ }
+
+ #[test]
+ fn detached_uuid_clone_disables_anchor_even_for_included_query() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_anchor = anchor(&original, definition(&original, "a"));
+ let detached = Fixture::new("retained.arcadia", format!("oa_actor old \"Retained\" id \"{A}\" {{}}"), &[("oa_actor", "old", Some(A))]);
+ let current = Index::build(snapshot(2), &[first.included(), detached.input(Membership::Detached, Evidence::Draft)], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.identity(definition(¤t, "a")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.identity(definition(¤t, "old")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.definitions_for_alias("old", Domain::Included).count(), 0);
+ assert_eq!(current.definitions_for_id(A, Domain::Included).count(), 1);
+ assert_eq!(current.definitions_for_id(A, Domain::Loaded).count(), 2);
+ }
+
+ #[test]
+ fn reused_alias_cannot_steal_old_identity_and_old_locations_are_stale() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_definition = definition(&original, "a");
+ let old_reference = original.references_for_alias("a", Domain::Included).next().unwrap();
+ let old_anchor = anchor(&original, old_definition);
+ let edited = Fixture::new("project.arcadia", format!(
+ "oa_actor b \"First renamed\" id \"{A}\" {{}}\noa_actor a \"New actor\" id \"{B}\" {{}}\nsource @a; target @b;"
+ ), &[("oa_actor", "b", Some(A)), ("oa_actor", "a", Some(B))]);
+ let current = Index::build(snapshot(2), &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.definition(old_definition).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.reference(old_reference).map(|reference| reference.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Unique(definition(¤t, "b"))));
+ let new_use = current.references_for_alias("a", Domain::Included).next().unwrap();
+ assert_eq!(current.resolve_reference(new_use, Domain::Included), Ok(Lookup::Unique(definition(¤t, "a"))));
+ assert_eq!(anchor(¤t, definition(¤t, "a")).id(), B);
+ let other_workspace = Index::build(SnapshotKey { workspace: Id::from_u128(2000), revision: 2 }, &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(other_workspace.resolve_anchor(old_anchor, Domain::Loaded), Err(QueryError::WrongWorkspace));
+ assert_eq!(other_workspace.definition(definition(¤t, "b")).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ let deleted = Fixture::new("project.arcadia", format!("oa_actor a \"New actor\" id \"{B}\" {{}}"), &[("oa_actor", "a", Some(B))]);
+ let after_delete = Index::build(snapshot(3), &[deleted.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(after_delete.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Missing));
+ }
+
+ #[test]
+ fn missing_ids_produce_only_current_snapshot_locations_without_stabilization() {
+ let fixture = Fixture::new("project.arcadia", format!("oa_actor a \"No UUID\" {{ historical uuid \"{A}\"; }}\r\nsource @a;"), &[("oa_actor", "a", None)]);
+ let original = fixture.text.clone();
+ let index = index(&[fixture.included()]);
+ let declaration = definition(&index, "a");
+ assert_eq!(index.identity(declaration), Ok(Identity::Missing));
+ assert_eq!(index.definition(declaration).unwrap().authored_id, None);
+ let usage = index.references_to(declaration, Domain::Included).unwrap().next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(declaration)));
+ assert_eq!(fixture.text, original);
+ assert_eq!(index.source(0).unwrap().text.as_bytes(), original.as_bytes());
+ assert!(!index.source(0).unwrap().text.contains(" id "));
+ assert_eq!(index.hit(0, fixture.text.find(&A.to_string()).unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn scope_provenance_and_detached_alias_collision_are_explicit() {
+ let included = Fixture::new("project.arcadia", format!("oa_actor a \"Included\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let detached = Fixture::new("old.arcadia", format!("oa_actor a \"Detached\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let unknown = Fixture::new("new.arcadia", "source @a;".into(), &[]);
+ let index = Index::build(snapshot(1), &[
+ included.input(Membership::Included, Evidence::Validated),
+ detached.input(Membership::Detached, Evidence::Draft),
+ unknown.input(Membership::Unverified, Evidence::Draft),
+ ], Scope::Partial(PartialReason::IncludesUnverified), limits()).unwrap();
+ assert_eq!(index.references_for_alias("a", Domain::Included).count(), 1);
+ assert_eq!(index.references_for_alias("a", Domain::Loaded).count(), 3);
+ let scope = index.references_for_alias("a", Domain::Included).scope();
+ assert_eq!(scope.declared, Scope::Partial(PartialReason::IncludesUnverified));
+ assert!(!scope.project_complete);
+ assert!(scope.all_selected_files_validated);
+ assert_eq!((scope.selected_files, scope.detached_files, scope.unverified_files), (1, 1, 1));
+ assert!(!index.scope(Domain::Loaded).all_selected_files_validated);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert!(matches!(index.resolve_reference(uses[0], Domain::Included), Ok(Lookup::Unique(_))));
+ assert_eq!(index.resolve_reference(uses[0], Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(index.resolve_reference(uses[1], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.resolve_reference(uses[2], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.source(2).unwrap().membership, Membership::Unverified);
+ assert!(matches!(Index::build(snapshot(1), &[included.included(), unknown.input(Membership::Unverified, Evidence::Draft)], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::InconsistentCompleteScope, .. })));
+ }
+
+ #[test]
+ fn incomplete_draft_is_lexical_and_error_token_does_not_leak_references() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Draft\" {}\nsource @a; ? \"unfinished @a".into(), &[("oa_actor", "a", None)]);
+ assert!(fixture.tokens.iter().any(|token| token.class == TokenClass::Error));
+ let index = Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::UnappliedWorkspace), limits()).unwrap();
+ let matches = index.references_to(definition(&index, "a"), Domain::Included).unwrap();
+ assert!(!matches.scope().project_complete);
+ assert!(!matches.scope().all_selected_files_validated);
+ assert_eq!(matches.count(), 1);
+ assert_eq!(index.hit(0, fixture.text.rfind("@a").unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn header_metadata_must_match_current_token_boundaries_and_authored_id() {
+ let mut fixture = Fixture::new("project.arcadia", format!("oa_actor alpha \"救助\" id \"{A}\" {{}}"), &[("oa_actor", "alpha", Some(A))]);
+ let original = fixture.declarations[0];
+ fixture.declarations[0].alias_span.start += 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::HeaderRoleToken, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].identity.as_mut().unwrap().id = B;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].alias_span = Span { start: fixture.text.find('救').unwrap() + 1, end: fixture.text.find('救').unwrap() + 3 };
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].span.end = fixture.text.len() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ }
+
+ #[test]
+ fn truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let final_token = fixture.tokens.pop().unwrap();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ fixture.tokens.push(final_token);
+ fixture.tokens.remove(1);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ let mut unicode = Fixture::new("project.arcadia", "// 救助\n".into(), &[]);
+ unicode.tokens[0].span.end = unicode.text.find('救').unwrap() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[unicode.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenBounds, .. })));
+ }
+
+ #[test]
+ fn stopped_outline_is_not_a_successful_empty_query() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let mut stopped = fixture.included();
+ stopped.declarations = &[];
+ stopped.syntax = SyntaxCoverage::Incomplete;
+ assert!(matches!(Index::build(snapshot(1), &[stopped], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::IncompleteSyntax, .. })));
+ }
+
+ #[test]
+ fn nested_declaration_extents_do_not_turn_body_text_into_definition_hits() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_entity outer \"Outer\" {\n oa_actor inner \"Inner\" {}\n}\nsource @inner;".into(), &[("oa_entity", "outer", None), ("oa_actor", "inner", None)]);
+ // Supply the actual outer whole span; nesting does not change alias order.
+ fixture.declarations[0].span.end = fixture.text.rfind('}').unwrap() + 1;
+ let index = index(&[fixture.included()]);
+ let inner = definition(&index, "inner");
+ assert_eq!(index.hit(0, fixture.declarations[1].alias_span.start), Ok(Some(Hit::Definition(inner))));
+ assert_eq!(index.hit(0, fixture.text.find("\"Inner\"").unwrap() + 1), Ok(None));
+ let usage = index.references_for_alias("inner", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(inner)));
+ }
+
+ #[test]
+ fn path_identity_rejects_normalization_and_case_collisions() {
+ let mut first = Fixture::new("project.arcadia", "source @a;".into(), &[]);
+ let second = Fixture::new("PROJECT.arcadia", "source @b;".into(), &[]);
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ for invalid in ["./project.arcadia", "parts/../project.arcadia", "../project.arcadia", "C:/project.arcadia", "parts\\project.arcadia"] {
+ first.path = invalid.into();
+ assert!(matches!(Index::build(snapshot(1), &[first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::NonCanonicalPath, .. })), "{invalid}");
+ }
+ }
+
+ #[test]
+ fn unicode_path_collisions_match_core_lowercase_policy() {
+ for (first_path, second_path) in [("Ä.arcadia", "ä.arcadia"), ("ΟΣ/model.arcadia", "ος/model.arcadia")] {
+ let first = Fixture::new(first_path, "source @a;".into(), &[]);
+ let second = Fixture::new(second_path, "source @b;".into(), &[]);
+ assert_eq!(first_path.to_lowercase(), second_path.to_lowercase());
+ assert!(!first_path.eq_ignore_ascii_case(second_path));
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ }
+ }
+
+ #[test]
+ fn noncanonical_header_identity_cannot_enter_through_query_metadata() {
+ for raw in [
+ "00000000000000000000000000000001",
+ "{00000000-0000-0000-0000-000000000001}",
+ "urn:uuid:00000000-0000-0000-0000-000000000001",
+ ] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
+ let start = source.find(&literal).unwrap();
+ // This fixture has one explicit empty body. A brace inside the
+ // quoted UUID must not become the test's declaration end.
+ let body_start = source.find("{}\n").unwrap();
+ fixture.declarations[0].span.end = body_start + 2;
+ assert!(start + literal.len() <= body_start);
+ // An adapter passes None from the corrected outline; lexical
+ // definition/reference navigation survives without an ID anchor.
+ {
+ let admitted = index(&[fixture.included()]);
+ let d = definition(&admitted, "a");
+ assert_eq!(admitted.identity(d), Ok(Identity::Missing));
+ assert_eq!(admitted.references_to(d, Domain::Included).unwrap().count(), 1);
+ assert_eq!(admitted.source(0).unwrap().text, source);
+ }
+ // A forged/stale caller cannot put the UUID crate's permissive
+ // interpretation back into an authored anchor via the raw API.
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: A,
+ literal_span: Span { start, end: start + literal.len() },
+ });
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
+ }
+ }
+
+ #[test]
+ fn query_header_admission_preserves_uppercase_and_json_escape_bytes() {
+ let expected = Id::from_u128(0xabcdef);
+ let uppercase = expected.to_string().to_uppercase();
+ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
+ let start = source.find(&literal).unwrap();
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: expected,
+ literal_span: Span { start, end: start + literal.len() },
+ });
+ let admitted = index(&[fixture.included()]);
+ assert_eq!(anchor(&admitted, definition(&admitted, "a")).id(), expected);
+ assert_eq!(admitted.source(0).unwrap().text.as_bytes(), source.as_bytes());
+ }
+ }
+
+ #[test]
+ fn admitted_unicode_path_budget_keeps_original_policy_after_single_fold() {
+ let fixture = Fixture::new("İ.arcadia", String::new(), &[]);
+ assert!(fixture.path.to_lowercase().len() > fixture.path.len());
+ let exact = Limits { files: 1, source_bytes: 0, path_bytes: fixture.path.len(), tokens: 0, records: 0 };
+ let admitted = Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).unwrap();
+ assert_eq!(admitted.source(0).unwrap().path, "İ.arcadia");
+ // PathBytes continues to count authored input. One lowercase key's
+ // Unicode expansion is bounded scratch, not a new path rejection rule.
+ }
+
+ #[test]
+ fn every_budget_admits_boundary_and_returns_no_index_one_below() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a; target @a;".into(), &[("oa_actor", "a", None)]);
+ let exact = Limits { files: 1, source_bytes: fixture.text.len(), path_bytes: fixture.path.len(), tokens: fixture.tokens.len(), records: 3 };
+ assert!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).is_ok());
+ for resource in [Resource::Files, Resource::SourceBytes, Resource::PathBytes, Resource::Tokens, Resource::Records] {
+ let mut reduced = exact;
+ match resource {
+ Resource::Files => reduced.files -= 1,
+ Resource::SourceBytes => reduced.source_bytes -= 1,
+ Resource::PathBytes => reduced.path_bytes -= 1,
+ Resource::Tokens => reduced.tokens -= 1,
+ Resource::Records => reduced.records -= 1,
+ }
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, reduced), Err(BuildError::Limit { resource: actual, .. }) if actual == resource));
+ }
+ // Aggregate bytes/tokens/records, not a per-file budget reset.
+ let second = Fixture::new("other.arcadia", "source @a;".into(), &[]);
+ let mut aggregate = limits();
+ aggregate.records = 3;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included(), second.included()], Scope::CompleteProject, aggregate), Err(BuildError::Limit { resource: Resource::Records, observed_at_least: 4, .. })));
+ let mut maximum = usize::MAX;
+ assert!(matches!(charge(&mut maximum, 1, usize::MAX, Resource::SourceBytes), Err(BuildError::Limit { resource: Resource::SourceBytes, .. })));
+ assert_eq!(maximum, usize::MAX);
+ }
+
+ #[test]
+ fn declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"First\" {}\noa_actor b \"Second\" {}".into(), &[("oa_actor", "a", None), ("oa_actor", "b", None)]);
+ fixture.declarations.reverse();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ fixture.declarations.reverse();
+ fixture.declarations.insert(1, fixture.declarations[0]);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8.rs b/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8.rs
new file mode 100644
index 00000000..aeb2c31d
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2-utf8.rs
@@ -0,0 +1,1124 @@
+//! Uncompiled experiment: current-text symbol queries, with no model or editor mutation.
+//!
+//! The caller supplies one immutable snapshot of current text, full lexer tokens,
+//! and syntax-only declaration role spans. This module does not lex, parse
+//! Arcadia declarations, lint, compile, allocate model IDs, discover includes,
+//! or read an accepted Project.
+//! Its matches are authored lexical uses, not typed semantic dependency edges.
+
+use oa_core::language::{Span, TokenClass, TokenSpan};
+use oa_core::Id;
+use std::collections::{BTreeMap, BTreeSet};
+use std::ops::Range;
+
+/// Fresh editor/workspace session identity plus a revision changed for EVERY
+/// indexed input change, including membership, outline and validation evidence.
+/// Reusing this key for different input violates the caller contract.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SnapshotKey {
+ pub workspace: Id,
+ pub revision: u64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Membership {
+ Included,
+ Detached,
+ Unverified,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Evidence {
+ /// The caller verified these exact bytes in the current source scope.
+ Validated,
+ /// Current editor bytes; no successful semantic validation is claimed.
+ Draft,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SyntaxCoverage {
+ /// The tolerant walk considered the entire file, even if its text is invalid.
+ FullFile,
+ /// A stopped/budget-limited outline cannot masquerade as a complete result.
+ Incomplete,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PartialReason {
+ IncludesUnverified,
+ MissingFiles,
+ UnappliedWorkspace,
+ UnsupportedBindings,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Scope {
+ CompleteProject,
+ Partial(PartialReason),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Domain {
+ Included,
+ Loaded,
+}
+
+impl Domain {
+ fn contains(self, membership: Membership) -> bool {
+ self == Self::Loaded || membership == Membership::Included
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredIdentity {
+ pub id: Id,
+ /// Whole quoted JSON string token in the declaration header, not a UUID
+ /// mentioned in a body, history record, comment or display name.
+ pub literal_span: Span,
+}
+
+/// These header roles must come from the coordinated core outline seam. The
+/// existing public DeclarationSpan alone lacks alias/kind/ID literal spans.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DeclarationInput {
+ pub span: Span,
+ pub kind_span: Span,
+ pub alias_span: Span,
+ pub identity: Option,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct FileInput<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ /// Exact lexer output for text, in order and covering the entire file.
+ pub tokens: &'a [TokenSpan],
+ /// Tolerant outline in alias-source order. Whole spans may nest.
+ pub declarations: &'a [DeclarationInput],
+ pub syntax: SyntaxCoverage,
+ pub membership: Membership,
+ pub evidence: Evidence,
+}
+
+/// Explicit experiment limits; production must select its own shared budgets.
+/// Caller-owned text/tokens/outline must already have bounded extraction.
+#[derive(Debug, Clone, Copy)]
+pub struct Limits {
+ pub files: usize,
+ pub source_bytes: usize,
+ pub path_bytes: usize,
+ pub tokens: usize,
+ pub records: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Resource {
+ Files,
+ SourceBytes,
+ PathBytes,
+ Tokens,
+ Records,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum InputProblem {
+ NonCanonicalPath,
+ DuplicateOrCaseCollidingPath,
+ InconsistentCompleteScope,
+ IncompleteSyntax,
+ TokenCoverage,
+ TokenBounds,
+ DeclarationBounds,
+ DeclarationOrder,
+ HeaderRoleToken,
+ InvalidAlias,
+ IdentityLiteral,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum BuildError {
+ /// No partial Index is returned. This is not a zero-result query.
+ Limit {
+ resource: Resource,
+ limit: usize,
+ observed_at_least: usize,
+ },
+ Input {
+ file: usize,
+ record: Option,
+ problem: InputProblem,
+ },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Location {
+ pub file: usize,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DefinitionHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ReferenceHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+/// Carries only an authored identity, never an identity generated by compilation.
+/// It may be resolved across revisions of the same workspace. Resolution still
+/// checks global loaded-file uniqueness every time.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredAnchor {
+ workspace: Id,
+ id: Id,
+}
+
+impl AuthoredAnchor {
+ pub fn id(self) -> Id {
+ self.id
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Identity {
+ Missing,
+ Unique(AuthoredAnchor),
+ Collision { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Lookup {
+ Missing,
+ Unique(DefinitionHandle),
+ Ambiguous { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Hit {
+ Definition(DefinitionHandle),
+ Reference(ReferenceHandle),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum QueryError {
+ StaleSnapshot,
+ WrongWorkspace,
+ InvalidHandle,
+ InvalidFile,
+ InvalidBytePosition,
+ OutsideDomain,
+ AmbiguousAlias { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct QueryScope {
+ pub declared: Scope,
+ pub domain: Domain,
+ /// True only for the complete included project, not loaded detached files.
+ pub project_complete: bool,
+ pub all_selected_files_validated: bool,
+ pub selected_files: usize,
+ pub detached_files: usize,
+ pub unverified_files: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Position {
+ pub line: usize,
+ /// Zero-based UTF-8 byte column. The editor adapter must convert explicitly
+ /// if it needs character/UTF-16 columns; CRLF bytes are never normalized.
+ pub byte_column: usize,
+}
+
+#[derive(Debug)]
+pub struct Definition<'a> {
+ pub alias: &'a str,
+ pub kind: &'a str,
+ pub declaration: Location,
+ pub alias_location: Location,
+ pub authored_id: Option,
+}
+
+#[derive(Debug)]
+pub struct Reference<'a> {
+ pub alias: &'a str,
+ pub at: Location,
+ pub alias_location: Location,
+}
+
+#[derive(Debug)]
+pub struct SourceFile<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ pub membership: Membership,
+ pub evidence: Evidence,
+ definitions: Range,
+ references: Range,
+}
+
+#[derive(Debug)]
+pub struct Index<'a> {
+ snapshot: SnapshotKey,
+ scope: Scope,
+ files: Vec>,
+ definitions: Vec>,
+ references: Vec>,
+ aliases: BTreeMap<&'a str, Vec>,
+ uses: BTreeMap<&'a str, Vec>,
+ identities: BTreeMap>,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Counts {
+ definitions: usize,
+ references: usize,
+}
+
+fn charge(total: &mut usize, value: usize, limit: usize, resource: Resource) -> Result<(), BuildError> {
+ let next = total.checked_add(value).ok_or(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: usize::MAX,
+ })?;
+ if next > limit {
+ return Err(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: next,
+ });
+ }
+ *total = next;
+ Ok(())
+}
+
+fn input_error(file: usize, record: Option, problem: InputProblem) -> BuildError {
+ BuildError::Input { file, record, problem }
+}
+
+fn valid_span(text: &str, span: Span) -> bool {
+ span.start < span.end
+ && span.end <= text.len()
+ && text.is_char_boundary(span.start)
+ && text.is_char_boundary(span.end)
+}
+
+fn contains(outer: Span, inner: Span) -> bool {
+ outer.start <= inner.start && inner.end <= outer.end
+}
+
+fn token_at(tokens: &[TokenSpan], span: Span) -> Option {
+ let index = tokens.binary_search_by_key(&span.start, |token| token.span.start).ok()?;
+ let token = tokens[index];
+ (token.span == span).then_some(token)
+}
+
+fn word(class: TokenClass) -> bool {
+ matches!(class, TokenClass::Identifier | TokenClass::Keyword)
+}
+
+/// Only a live punctuation @ followed by a legal alias outside trivia qualifies.
+/// Trivia can separate @ and alias, but its bytes never become a clickable hit.
+fn reference_spans<'a>(file: FileInput<'a>) -> impl Iterator- + 'a {
+ file.tokens.iter().enumerate().filter_map(move |(index, token)| {
+ if token.class != TokenClass::Punctuation || &file.text[token.span.start..token.span.end] != "@" {
+ return None;
+ }
+ let next = file.tokens[index + 1..].iter().find(|next| {
+ !matches!(next.class, TokenClass::Whitespace | TokenClass::Comment)
+ })?;
+ let alias = &file.text[next.span.start..next.span.end];
+ (word(next.class) && oa_core::valid_alias(alias)).then_some((token.span, next.span))
+ })
+}
+
+/// Runs all aggregate limits and structural checks before allocating Index maps
+/// or vectors. Path normalization/quoted-ID decoding may allocate bounded scratch
+/// strings. Scratch storage is proportional to admitted input bytes and file
+/// count, including Unicode expansion and collection overhead.
+fn preflight(files: &[FileInput<'_>], scope: Scope, limits: Limits) -> Result
{
+ let mut file_count = 0;
+ charge(&mut file_count, files.len(), limits.files, Resource::Files)?;
+ let mut bytes = 0;
+ let mut path_bytes = 0;
+ let mut tokens = 0;
+ let mut records = 0;
+ for file in files {
+ charge(&mut bytes, file.text.len(), limits.source_bytes, Resource::SourceBytes)?;
+ charge(&mut path_bytes, file.path.len(), limits.path_bytes, Resource::PathBytes)?;
+ charge(&mut tokens, file.tokens.len(), limits.tokens, Resource::Tokens)?;
+ charge(&mut records, file.declarations.len(), limits.records, Resource::Records)?;
+ }
+ let definitions = records;
+ if scope == Scope::CompleteProject
+ && (!files.iter().any(|file| file.membership == Membership::Included)
+ || files.iter().any(|file| file.membership == Membership::Unverified))
+ {
+ return Err(input_error(0, None, InputProblem::InconsistentCompleteScope));
+ }
+ // Scratch storage is allocated only after aggregate file/path admission.
+ // Each admitted path is folded once, retaining core's Unicode policy.
+ let mut folded_paths = BTreeSet::new();
+ for (file_index, file) in files.iter().copied().enumerate() {
+ if file.syntax != SyntaxCoverage::FullFile {
+ return Err(input_error(file_index, None, InputProblem::IncompleteSyntax));
+ }
+ if oa_core::source_project::resolve_include("", file.path).as_deref() != Ok(file.path) {
+ return Err(input_error(file_index, None, InputProblem::NonCanonicalPath));
+ }
+ if !folded_paths.insert(file.path.to_lowercase()) {
+ return Err(input_error(file_index, None, InputProblem::DuplicateOrCaseCollidingPath));
+ }
+ let mut end = 0;
+ for (token_index, token) in file.tokens.iter().enumerate() {
+ if !valid_span(file.text, token.span) {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenBounds));
+ }
+ if token.span.start != end {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenCoverage));
+ }
+ end = token.span.end;
+ }
+ if end != file.text.len() {
+ return Err(input_error(file_index, None, InputProblem::TokenCoverage));
+ }
+ let mut previous_alias_end = 0;
+ for (declaration_index, declaration) in file.declarations.iter().enumerate() {
+ let problem = |problem| input_error(file_index, Some(declaration_index), problem);
+ if !valid_span(file.text, declaration.span)
+ || !valid_span(file.text, declaration.kind_span)
+ || !valid_span(file.text, declaration.alias_span)
+ || declaration.kind_span.start != declaration.span.start
+ || !contains(declaration.span, declaration.kind_span)
+ || !contains(declaration.span, declaration.alias_span)
+ || declaration.alias_span.start < declaration.kind_span.end
+ {
+ return Err(problem(InputProblem::DeclarationBounds));
+ }
+ if declaration.alias_span.start < previous_alias_end {
+ return Err(problem(InputProblem::DeclarationOrder));
+ }
+ previous_alias_end = declaration.alias_span.end;
+ if !token_at(file.tokens, declaration.kind_span).is_some_and(|token| word(token.class))
+ || !token_at(file.tokens, declaration.alias_span).is_some_and(|token| word(token.class))
+ {
+ return Err(problem(InputProblem::HeaderRoleToken));
+ }
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ if !oa_core::valid_alias(alias) {
+ return Err(problem(InputProblem::InvalidAlias));
+ }
+ if let Some(identity) = declaration.identity {
+ if !valid_span(file.text, identity.literal_span)
+ || !contains(declaration.span, identity.literal_span)
+ || identity.literal_span.start < declaration.alias_span.end
+ || token_at(file.tokens, identity.literal_span).map(|token| token.class) != Some(TokenClass::String)
+ {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ let literal = &file.text[identity.literal_span.start..identity.literal_span.end];
+ let decoded = serde_json::from_str::(literal)
+ .ok()
+ .filter(|value| oa_core::language::has_uuid_header_shape(value))
+ .and_then(|value| Id::parse_str(&value).ok());
+ if decoded != Some(identity.id) {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ }
+ }
+ // Token bounds and coverage are proven before reference_spans slices.
+ for _ in reference_spans(file) {
+ charge(&mut records, 1, limits.records, Resource::Records)?;
+ }
+ }
+ Ok(Counts { definitions, references: records - definitions })
+}
+
+impl<'a> Index<'a> {
+ pub fn build(
+ snapshot: SnapshotKey,
+ files: &[FileInput<'a>],
+ scope: Scope,
+ limits: Limits,
+ ) -> Result {
+ let counts = preflight(files, scope, limits)?;
+ let mut index = Self {
+ snapshot,
+ scope,
+ files: Vec::with_capacity(files.len()),
+ definitions: Vec::with_capacity(counts.definitions),
+ references: Vec::with_capacity(counts.references),
+ aliases: BTreeMap::new(),
+ uses: BTreeMap::new(),
+ identities: BTreeMap::new(),
+ };
+ for (file_index, file) in files.iter().copied().enumerate() {
+ let definition_start = index.definitions.len();
+ let reference_start = index.references.len();
+ for declaration in file.declarations {
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ let number = index.definitions.len();
+ index.aliases.entry(alias).or_default().push(number);
+ if let Some(identity) = declaration.identity {
+ index.identities.entry(identity.id).or_default().push(number);
+ }
+ index.definitions.push(Definition {
+ alias,
+ kind: &file.text[declaration.kind_span.start..declaration.kind_span.end],
+ declaration: Location { file: file_index, span: declaration.span },
+ alias_location: Location { file: file_index, span: declaration.alias_span },
+ authored_id: declaration.identity.map(|identity| identity.id),
+ });
+ }
+ for (at, alias_span) in reference_spans(file) {
+ let alias = &file.text[alias_span.start..alias_span.end];
+ index.uses.entry(alias).or_default().push(index.references.len());
+ index.references.push(Reference {
+ alias,
+ at: Location { file: file_index, span: at },
+ alias_location: Location { file: file_index, span: alias_span },
+ });
+ }
+ index.files.push(SourceFile {
+ path: file.path,
+ text: file.text,
+ membership: file.membership,
+ evidence: file.evidence,
+ definitions: definition_start..index.definitions.len(),
+ references: reference_start..index.references.len(),
+ });
+ }
+ Ok(index)
+ }
+
+ pub fn snapshot(&self) -> SnapshotKey {
+ self.snapshot
+ }
+
+ pub fn source(&self, file: usize) -> Result<&SourceFile<'a>, QueryError> {
+ self.files.get(file).ok_or(QueryError::InvalidFile)
+ }
+
+ pub fn scope(&self, domain: Domain) -> QueryScope {
+ let selected_files = self.files.iter().filter(|file| domain.contains(file.membership)).count();
+ QueryScope {
+ declared: self.scope,
+ domain,
+ project_complete: self.scope == Scope::CompleteProject && domain == Domain::Included,
+ all_selected_files_validated: selected_files != 0
+ && self.files.iter().filter(|file| domain.contains(file.membership))
+ .all(|file| file.evidence == Evidence::Validated),
+ selected_files,
+ detached_files: self.files.iter().filter(|file| file.membership == Membership::Detached).count(),
+ unverified_files: self.files.iter().filter(|file| file.membership == Membership::Unverified).count(),
+ }
+ }
+
+ fn definition_handle(&self, index: usize) -> DefinitionHandle {
+ DefinitionHandle { snapshot: self.snapshot, index }
+ }
+
+ fn reference_handle(&self, index: usize) -> ReferenceHandle {
+ ReferenceHandle { snapshot: self.snapshot, index }
+ }
+
+ pub fn definition(&self, handle: DefinitionHandle) -> Result<&Definition<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.definitions.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn reference(&self, handle: ReferenceHandle) -> Result<&Reference<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.references.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn definitions_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> Definitions<'i, 'a> {
+ Definitions { index: self, indices: self.aliases.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn definitions_for_id(&self, id: Id, domain: Domain) -> Definitions<'_, 'a> {
+ Definitions { index: self, indices: self.identities.get(&id).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn references_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> References<'i, 'a> {
+ References { index: self, indices: self.uses.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn resolve_reference(&self, handle: ReferenceHandle, domain: Domain) -> Result {
+ let reference = self.reference(handle)?;
+ if !domain.contains(self.files[reference.at.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let mut definitions = self.definitions_for_alias(reference.alias, domain);
+ let Some(first) = definitions.next() else { return Ok(Lookup::Missing); };
+ let count = 1 + definitions.count();
+ Ok(if count == 1 { Lookup::Unique(first) } else { Lookup::Ambiguous { count } })
+ }
+
+ pub fn identity(&self, handle: DefinitionHandle) -> Result {
+ let Some(id) = self.definition(handle)?.authored_id else { return Ok(Identity::Missing); };
+ let count = self.identities[&id].len();
+ Ok(if count == 1 {
+ Identity::Unique(AuthoredAnchor { workspace: self.snapshot.workspace, id })
+ } else {
+ Identity::Collision { count }
+ })
+ }
+
+ pub fn resolve_anchor(&self, anchor: AuthoredAnchor, domain: Domain) -> Result {
+ if anchor.workspace != self.snapshot.workspace {
+ return Err(QueryError::WrongWorkspace);
+ }
+ let Some(candidates) = self.identities.get(&anchor.id) else { return Ok(Lookup::Missing); };
+ // A detached duplicate must not silently confer identity uniqueness.
+ if candidates.len() != 1 {
+ return Ok(Lookup::Ambiguous { count: candidates.len() });
+ }
+ let candidate = candidates[0];
+ Ok(if domain.contains(self.files[self.definitions[candidate].declaration.file].membership) {
+ Lookup::Unique(self.definition_handle(candidate))
+ } else {
+ Lookup::Missing
+ })
+ }
+
+ /// Exact authored alias-use projection for a unique current declaration in
+ /// the requested domain. Duplicate aliases require candidate inspection;
+ /// this API never attributes all matching uses to the first declaration.
+ pub fn references_to(&self, handle: DefinitionHandle, domain: Domain) -> Result, QueryError> {
+ let definition = self.definition(handle)?;
+ if !domain.contains(self.files[definition.declaration.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let count = self.definitions_for_alias(definition.alias, domain).count();
+ if count != 1 {
+ return Err(QueryError::AmbiguousAlias { count });
+ }
+ Ok(self.references_for_alias(definition.alias, domain))
+ }
+
+ pub fn position(&self, file: usize, offset: usize) -> Result {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let prefix = &source.text[..offset];
+ Ok(Position {
+ line: prefix.bytes().filter(|byte| *byte == b'\n').count(),
+ byte_column: prefix.rsplit_once('\n').map_or(prefix.len(), |(_, tail)| tail.len()),
+ })
+ }
+
+ pub fn hit(&self, file: usize, offset: usize) -> Result, QueryError> {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let definitions = &self.definitions[source.definitions.clone()];
+ let before = definitions.partition_point(|definition| definition.alias_location.span.start <= offset);
+ if before != 0 && offset < definitions[before - 1].alias_location.span.end {
+ return Ok(Some(Hit::Definition(self.definition_handle(source.definitions.start + before - 1))));
+ }
+ let references = &self.references[source.references.clone()];
+ let before = references.partition_point(|reference| reference.at.span.start <= offset);
+ if before != 0 {
+ let reference = &references[before - 1];
+ if offset < reference.at.span.end
+ || (reference.alias_location.span.start <= offset && offset < reference.alias_location.span.end)
+ {
+ return Ok(Some(Hit::Reference(self.reference_handle(source.references.start + before - 1))));
+ }
+ }
+ Ok(None)
+ }
+}
+
+pub struct Definitions<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl Iterator for Definitions<'_, '_> {
+ type Item = DefinitionHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let definition = &self.index.definitions[number];
+ if self.domain.contains(self.index.files[definition.declaration.file].membership) {
+ return Some(self.index.definition_handle(number));
+ }
+ }
+ None
+ }
+}
+
+pub struct References<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl References<'_, '_> {
+ pub fn scope(&self) -> QueryScope {
+ self.index.scope(self.domain)
+ }
+}
+
+impl Iterator for References<'_, '_> {
+ type Item = ReferenceHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let reference = &self.index.references[number];
+ if self.domain.contains(self.index.files[reference.at.file].membership) {
+ return Some(self.index.reference_handle(number));
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const A: Id = Id::from_u128(1);
+ const B: Id = Id::from_u128(2);
+ const C: Id = Id::from_u128(3);
+
+ struct Fixture {
+ path: String,
+ text: String,
+ tokens: Vec,
+ declarations: Vec,
+ }
+
+ impl Fixture {
+ /// Test-only literal role proof. Production never searches declaration
+ /// headers: its caller supplies the coordinated syntax outline roles.
+ fn new(path: &str, text: String, roles: &[(&str, &str, Option)]) -> Self {
+ let tokens = oa_core::language::tokenize(&text);
+ let declarations = roles.iter().map(|&(kind, alias, id)| {
+ let header = format!("{kind} {alias} ");
+ let start = text.find(&header).expect("authored fixture header");
+ let end = text[start..].find('}').map_or(text.len(), |end| start + end + 1);
+ let alias_start = start + kind.len() + 1;
+ DeclarationInput {
+ span: Span { start, end },
+ kind_span: Span { start, end: start + kind.len() },
+ alias_span: Span { start: alias_start, end: alias_start + alias.len() },
+ identity: id.map(|id| {
+ let literal = format!("\"{id}\"");
+ let literal_start = start + text[start..end].find(&literal).expect("authored fixture UUID");
+ AuthoredIdentity { id, literal_span: Span { start: literal_start, end: literal_start + literal.len() } }
+ }),
+ }
+ }).collect();
+ Self { path: path.into(), text, tokens, declarations }
+ }
+
+ fn input(&self, membership: Membership, evidence: Evidence) -> FileInput<'_> {
+ FileInput { path: &self.path, text: &self.text, tokens: &self.tokens, declarations: &self.declarations, syntax: SyntaxCoverage::FullFile, membership, evidence }
+ }
+
+ fn included(&self) -> FileInput<'_> {
+ self.input(Membership::Included, Evidence::Draft)
+ }
+ }
+
+ fn snapshot(revision: u64) -> SnapshotKey {
+ SnapshotKey { workspace: Id::from_u128(1000), revision }
+ }
+
+ fn limits() -> Limits {
+ Limits { files: 16, source_bytes: 1024 * 1024, path_bytes: 8192, tokens: 200_000, records: 100_000 }
+ }
+
+ fn index<'a>(files: &[FileInput<'a>]) -> Index<'a> {
+ Index::build(snapshot(1), files, Scope::CompleteProject, limits()).unwrap()
+ }
+
+ fn definition(index: &Index<'_>, alias: &str) -> DefinitionHandle {
+ let mut found = index.definitions_for_alias(alias, Domain::Loaded);
+ let result = found.next().expect("fixture definition");
+ assert!(found.next().is_none(), "fixture expects unique alias");
+ result
+ }
+
+ fn anchor(index: &Index<'_>, handle: DefinitionHandle) -> AuthoredAnchor {
+ match index.identity(handle).unwrap() {
+ Identity::Unique(anchor) => anchor,
+ identity => panic!("fixture expects unique authored identity: {identity:?}"),
+ }
+ }
+
+ #[test]
+ fn repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor tank \"Same display\" id \"{A}\" {{}}\n\
+ oa_actor tank_extra \"Same display\" id \"{B}\" {{}}\n\
+ oa_interaction flow \"Flow\" id \"{C}\" {{ source @tank; target @tank; other @tank_extra; \
+ description \"@tank @tank_extra\"; historical uuid \"{A}\"; }}\n\
+ // @tank @tank_extra and UUID {A}\n"
+ ), &[("oa_actor", "tank", Some(A)), ("oa_actor", "tank_extra", Some(B)), ("oa_interaction", "flow", Some(C))]);
+ let original = (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone());
+ let index = index(&[fixture.included()]);
+ let tank = definition(&index, "tank");
+ let uses: Vec<_> = index.references_to(tank, Domain::Included).unwrap().collect();
+ assert_eq!(uses.len(), 2);
+ assert_ne!(uses[0], uses[1]);
+ assert_eq!(index.references_for_alias("tank_extra", Domain::Loaded).count(), 1);
+ for usage in uses {
+ let record = index.reference(usage).unwrap();
+ assert_eq!(&fixture.text[record.at.span.start..record.at.span.end], "@");
+ assert_eq!(&fixture.text[record.alias_location.span.start..record.alias_location.span.end], "tank");
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(tank)));
+ assert_eq!(index.hit(0, record.alias_location.span.start), Ok(Some(Hit::Reference(usage))));
+ }
+ for ignored in [fixture.text.find("Same display").unwrap(), fixture.text.find("// @tank").unwrap() + 3,
+ fixture.text.find("\"@tank @tank_extra\"").unwrap() + 1,
+ fixture.text.find("historical uuid").unwrap() + "historical uuid \"".len()] {
+ assert_eq!(index.hit(0, ignored), Ok(None));
+ }
+ assert_eq!(index.hit(0, fixture.text.len()), Ok(None));
+ assert_eq!(original, (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone()));
+ }
+
+ #[test]
+ fn comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans() {
+ let fixture = Fixture::new("parts/actors.arcadia", format!(
+ "oa_actor a \"救助 🚒\" id \"{A}\" {{}}\r\nsource @ // 注释 @a\r\n a; target @a;\r\n"
+ ), &[("oa_actor", "a", Some(A))]);
+ let index = index(&[fixture.included()]);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert_eq!(uses.len(), 2);
+ let first = index.reference(uses[0]).unwrap();
+ assert_eq!(index.position(0, first.at.span.start), Ok(Position { line: 1, byte_column: 7 }));
+ assert_eq!(index.position(0, first.alias_location.span.start), Ok(Position { line: 2, byte_column: 2 }));
+ assert_eq!(index.hit(0, first.at.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ assert_eq!(index.hit(0, first.alias_location.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ for byte in [first.at.span.end, fixture.text.find("注释").unwrap(), fixture.text.find("// 注释 @a").unwrap() + "// 注释 ".len(), first.alias_location.span.end] {
+ assert_eq!(index.hit(0, byte), Ok(None));
+ }
+ let unicode_inside = fixture.text.find('救').unwrap() + 1;
+ assert_eq!(index.hit(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.position(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(0, fixture.text.len() + 1), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(1, 0), Err(QueryError::InvalidFile));
+ assert!(index.source(0).unwrap().text.contains("\r\n"));
+ }
+
+ #[test]
+ fn legal_keyword_classified_alias_is_not_discarded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor oa_activity \"Participant\" id \"{A}\" {{}}\nsource @oa_activity; source @parent;"
+ ), &[("oa_actor", "oa_activity", Some(A))]);
+ assert!(oa_core::valid_alias("oa_activity"));
+ assert!(!oa_core::valid_alias("parent"));
+ assert_eq!(token_at(&fixture.tokens, fixture.declarations[0].alias_span).unwrap().class, TokenClass::Keyword);
+ let index = index(&[fixture.included()]);
+ let found = definition(&index, "oa_activity");
+ let usage = index.references_for_alias("oa_activity", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(found)));
+ assert_eq!(index.references_for_alias("parent", Domain::Loaded).count(), 0);
+ assert_eq!(index.hit(0, fixture.declarations[0].alias_span.start), Ok(Some(Hit::Definition(found))));
+ }
+
+ #[test]
+ fn duplicate_aliases_are_candidates_without_false_reference_attribution() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"Same display\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let second = Fixture::new("parts/other.arcadia", format!("oa_actor a \"Same display\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let index = index(&[first.included(), second.included()]);
+ let candidates: Vec<_> = index.definitions_for_alias("a", Domain::Included).collect();
+ assert_eq!(candidates.len(), 2);
+ assert_eq!(index.definition(candidates[0]).unwrap().declaration.file, 0);
+ assert_eq!(index.definition(candidates[1]).unwrap().declaration.file, 1);
+ assert_ne!(anchor(&index, candidates[0]), anchor(&index, candidates[1]));
+ assert!(matches!(index.references_to(candidates[0], Domain::Included), Err(QueryError::AmbiguousAlias { count: 2 })));
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Included).collect();
+ assert_eq!(uses.len(), 2);
+ for usage in uses {
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ }
+ }
+
+ #[test]
+ fn detached_uuid_clone_disables_anchor_even_for_included_query() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_anchor = anchor(&original, definition(&original, "a"));
+ let detached = Fixture::new("retained.arcadia", format!("oa_actor old \"Retained\" id \"{A}\" {{}}"), &[("oa_actor", "old", Some(A))]);
+ let current = Index::build(snapshot(2), &[first.included(), detached.input(Membership::Detached, Evidence::Draft)], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.identity(definition(¤t, "a")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.identity(definition(¤t, "old")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.definitions_for_alias("old", Domain::Included).count(), 0);
+ assert_eq!(current.definitions_for_id(A, Domain::Included).count(), 1);
+ assert_eq!(current.definitions_for_id(A, Domain::Loaded).count(), 2);
+ }
+
+ #[test]
+ fn reused_alias_cannot_steal_old_identity_and_old_locations_are_stale() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_definition = definition(&original, "a");
+ let old_reference = original.references_for_alias("a", Domain::Included).next().unwrap();
+ let old_anchor = anchor(&original, old_definition);
+ let edited = Fixture::new("project.arcadia", format!(
+ "oa_actor b \"First renamed\" id \"{A}\" {{}}\noa_actor a \"New actor\" id \"{B}\" {{}}\nsource @a; target @b;"
+ ), &[("oa_actor", "b", Some(A)), ("oa_actor", "a", Some(B))]);
+ let current = Index::build(snapshot(2), &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.definition(old_definition).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.reference(old_reference).map(|reference| reference.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Unique(definition(¤t, "b"))));
+ let new_use = current.references_for_alias("a", Domain::Included).next().unwrap();
+ assert_eq!(current.resolve_reference(new_use, Domain::Included), Ok(Lookup::Unique(definition(¤t, "a"))));
+ assert_eq!(anchor(¤t, definition(¤t, "a")).id(), B);
+ let other_workspace = Index::build(SnapshotKey { workspace: Id::from_u128(2000), revision: 2 }, &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(other_workspace.resolve_anchor(old_anchor, Domain::Loaded), Err(QueryError::WrongWorkspace));
+ assert_eq!(other_workspace.definition(definition(¤t, "b")).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ let deleted = Fixture::new("project.arcadia", format!("oa_actor a \"New actor\" id \"{B}\" {{}}"), &[("oa_actor", "a", Some(B))]);
+ let after_delete = Index::build(snapshot(3), &[deleted.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(after_delete.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Missing));
+ }
+
+ #[test]
+ fn missing_ids_produce_only_current_snapshot_locations_without_stabilization() {
+ let fixture = Fixture::new("project.arcadia", format!("oa_actor a \"No UUID\" {{ historical uuid \"{A}\"; }}\r\nsource @a;"), &[("oa_actor", "a", None)]);
+ let original = fixture.text.clone();
+ let index = index(&[fixture.included()]);
+ let declaration = definition(&index, "a");
+ assert_eq!(index.identity(declaration), Ok(Identity::Missing));
+ assert_eq!(index.definition(declaration).unwrap().authored_id, None);
+ let usage = index.references_to(declaration, Domain::Included).unwrap().next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(declaration)));
+ assert_eq!(fixture.text, original);
+ assert_eq!(index.source(0).unwrap().text.as_bytes(), original.as_bytes());
+ assert!(!index.source(0).unwrap().text.contains(" id "));
+ assert_eq!(index.hit(0, fixture.text.find(&A.to_string()).unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn scope_provenance_and_detached_alias_collision_are_explicit() {
+ let included = Fixture::new("project.arcadia", format!("oa_actor a \"Included\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let detached = Fixture::new("old.arcadia", format!("oa_actor a \"Detached\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let unknown = Fixture::new("new.arcadia", "source @a;".into(), &[]);
+ let index = Index::build(snapshot(1), &[
+ included.input(Membership::Included, Evidence::Validated),
+ detached.input(Membership::Detached, Evidence::Draft),
+ unknown.input(Membership::Unverified, Evidence::Draft),
+ ], Scope::Partial(PartialReason::IncludesUnverified), limits()).unwrap();
+ assert_eq!(index.references_for_alias("a", Domain::Included).count(), 1);
+ assert_eq!(index.references_for_alias("a", Domain::Loaded).count(), 3);
+ let scope = index.references_for_alias("a", Domain::Included).scope();
+ assert_eq!(scope.declared, Scope::Partial(PartialReason::IncludesUnverified));
+ assert!(!scope.project_complete);
+ assert!(scope.all_selected_files_validated);
+ assert_eq!((scope.selected_files, scope.detached_files, scope.unverified_files), (1, 1, 1));
+ assert!(!index.scope(Domain::Loaded).all_selected_files_validated);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert!(matches!(index.resolve_reference(uses[0], Domain::Included), Ok(Lookup::Unique(_))));
+ assert_eq!(index.resolve_reference(uses[0], Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(index.resolve_reference(uses[1], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.resolve_reference(uses[2], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.source(2).unwrap().membership, Membership::Unverified);
+ assert!(matches!(Index::build(snapshot(1), &[included.included(), unknown.input(Membership::Unverified, Evidence::Draft)], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::InconsistentCompleteScope, .. })));
+ }
+
+ #[test]
+ fn incomplete_draft_is_lexical_and_error_token_does_not_leak_references() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Draft\" {}\nsource @a; ? \"unfinished @a".into(), &[("oa_actor", "a", None)]);
+ assert!(fixture.tokens.iter().any(|token| token.class == TokenClass::Error));
+ let index = Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::UnappliedWorkspace), limits()).unwrap();
+ let matches = index.references_to(definition(&index, "a"), Domain::Included).unwrap();
+ assert!(!matches.scope().project_complete);
+ assert!(!matches.scope().all_selected_files_validated);
+ assert_eq!(matches.count(), 1);
+ assert_eq!(index.hit(0, fixture.text.rfind("@a").unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn header_metadata_must_match_current_token_boundaries_and_authored_id() {
+ let mut fixture = Fixture::new("project.arcadia", format!("oa_actor alpha \"救助\" id \"{A}\" {{}}"), &[("oa_actor", "alpha", Some(A))]);
+ let original = fixture.declarations[0];
+ fixture.declarations[0].alias_span.start += 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::HeaderRoleToken, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].identity.as_mut().unwrap().id = B;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].alias_span = Span { start: fixture.text.find('救').unwrap() + 1, end: fixture.text.find('救').unwrap() + 3 };
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].span.end = fixture.text.len() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ }
+
+ #[test]
+ fn truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let final_token = fixture.tokens.pop().unwrap();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ fixture.tokens.push(final_token);
+ fixture.tokens.remove(1);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ let mut unicode = Fixture::new("project.arcadia", "// 救助\n".into(), &[]);
+ unicode.tokens[0].span.end = unicode.text.find('救').unwrap() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[unicode.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenBounds, .. })));
+ }
+
+ #[test]
+ fn stopped_outline_is_not_a_successful_empty_query() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let mut stopped = fixture.included();
+ stopped.declarations = &[];
+ stopped.syntax = SyntaxCoverage::Incomplete;
+ assert!(matches!(Index::build(snapshot(1), &[stopped], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::IncompleteSyntax, .. })));
+ }
+
+ #[test]
+ fn nested_declaration_extents_do_not_turn_body_text_into_definition_hits() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_entity outer \"Outer\" {\n oa_actor inner \"Inner\" {}\n}\nsource @inner;".into(), &[("oa_entity", "outer", None), ("oa_actor", "inner", None)]);
+ // Supply the actual outer whole span; nesting does not change alias order.
+ fixture.declarations[0].span.end = fixture.text.rfind('}').unwrap() + 1;
+ let index = index(&[fixture.included()]);
+ let inner = definition(&index, "inner");
+ assert_eq!(index.hit(0, fixture.declarations[1].alias_span.start), Ok(Some(Hit::Definition(inner))));
+ assert_eq!(index.hit(0, fixture.text.find("\"Inner\"").unwrap() + 1), Ok(None));
+ let usage = index.references_for_alias("inner", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(inner)));
+ }
+
+ #[test]
+ fn path_identity_rejects_normalization_and_case_collisions() {
+ let mut first = Fixture::new("project.arcadia", "source @a;".into(), &[]);
+ let second = Fixture::new("PROJECT.arcadia", "source @b;".into(), &[]);
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ for invalid in ["./project.arcadia", "parts/../project.arcadia", "../project.arcadia", "C:/project.arcadia", "parts\\project.arcadia"] {
+ first.path = invalid.into();
+ assert!(matches!(Index::build(snapshot(1), &[first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::NonCanonicalPath, .. })), "{invalid}");
+ }
+ }
+
+ #[test]
+ fn unicode_path_collisions_match_core_lowercase_policy() {
+ for (first_path, second_path) in [("Ä.arcadia", "ä.arcadia"), ("ΟΣ/model.arcadia", "ος/model.arcadia")] {
+ let first = Fixture::new(first_path, "source @a;".into(), &[]);
+ let second = Fixture::new(second_path, "source @b;".into(), &[]);
+ assert_eq!(first_path.to_lowercase(), second_path.to_lowercase());
+ assert!(!first_path.eq_ignore_ascii_case(second_path));
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ }
+ }
+
+ #[test]
+ fn noncanonical_header_identity_cannot_enter_through_query_metadata() {
+ for raw in [
+ "00000000000000000000000000000001",
+ "{00000000-0000-0000-0000-000000000001}",
+ "urn:uuid:00000000-0000-0000-0000-000000000001",
+ ] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
+ let start = source.find(&literal).unwrap();
+ // This fixture has one explicit empty body. A brace inside the
+ // quoted UUID must not become the test's declaration end.
+ let body_start = source.find("{}\n").unwrap();
+ fixture.declarations[0].span.end = body_start + 2;
+ assert!(start + literal.len() <= body_start);
+ // An adapter passes None from the corrected outline; lexical
+ // definition/reference navigation survives without an ID anchor.
+ {
+ let admitted = index(&[fixture.included()]);
+ let d = definition(&admitted, "a");
+ assert_eq!(admitted.identity(d), Ok(Identity::Missing));
+ assert_eq!(admitted.references_to(d, Domain::Included).unwrap().count(), 1);
+ assert_eq!(admitted.source(0).unwrap().text, source);
+ }
+ // A forged/stale caller cannot put the UUID crate's permissive
+ // interpretation back into an authored anchor via the raw API.
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: A,
+ literal_span: Span { start, end: start + literal.len() },
+ });
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
+ }
+ }
+
+ #[test]
+ fn query_header_admission_preserves_uppercase_and_json_escape_bytes() {
+ let expected = Id::from_u128(0xabcdef);
+ let uppercase = expected.to_string().to_uppercase();
+ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
+ let start = source.find(&literal).unwrap();
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: expected,
+ literal_span: Span { start, end: start + literal.len() },
+ });
+ let admitted = index(&[fixture.included()]);
+ assert_eq!(anchor(&admitted, definition(&admitted, "a")).id(), expected);
+ assert_eq!(admitted.source(0).unwrap().text.as_bytes(), source.as_bytes());
+ }
+ }
+
+ #[test]
+ fn admitted_unicode_path_budget_keeps_original_policy_after_single_fold() {
+ let fixture = Fixture::new("İ.arcadia", String::new(), &[]);
+ assert!(fixture.path.to_lowercase().len() > fixture.path.len());
+ let exact = Limits { files: 1, source_bytes: 0, path_bytes: fixture.path.len(), tokens: 0, records: 0 };
+ let admitted = Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).unwrap();
+ assert_eq!(admitted.source(0).unwrap().path, "İ.arcadia");
+ // PathBytes continues to count authored input. One lowercase key's
+ // Unicode expansion is bounded scratch, not a new path rejection rule.
+ }
+
+ #[test]
+ fn every_budget_admits_boundary_and_returns_no_index_one_below() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a; target @a;".into(), &[("oa_actor", "a", None)]);
+ let exact = Limits { files: 1, source_bytes: fixture.text.len(), path_bytes: fixture.path.len(), tokens: fixture.tokens.len(), records: 3 };
+ assert!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).is_ok());
+ for resource in [Resource::Files, Resource::SourceBytes, Resource::PathBytes, Resource::Tokens, Resource::Records] {
+ let mut reduced = exact;
+ match resource {
+ Resource::Files => reduced.files -= 1,
+ Resource::SourceBytes => reduced.source_bytes -= 1,
+ Resource::PathBytes => reduced.path_bytes -= 1,
+ Resource::Tokens => reduced.tokens -= 1,
+ Resource::Records => reduced.records -= 1,
+ }
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, reduced), Err(BuildError::Limit { resource: actual, .. }) if actual == resource));
+ }
+ // Aggregate bytes/tokens/records, not a per-file budget reset.
+ let second = Fixture::new("other.arcadia", "source @a;".into(), &[]);
+ let mut aggregate = limits();
+ aggregate.records = 3;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included(), second.included()], Scope::CompleteProject, aggregate), Err(BuildError::Limit { resource: Resource::Records, observed_at_least: 4, .. })));
+ let mut maximum = usize::MAX;
+ assert!(matches!(charge(&mut maximum, 1, usize::MAX, Resource::SourceBytes), Err(BuildError::Limit { resource: Resource::SourceBytes, .. })));
+ assert_eq!(maximum, usize::MAX);
+ }
+
+ #[test]
+ fn declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"First\" {}\noa_actor b \"Second\" {}".into(), &[("oa_actor", "a", None), ("oa_actor", "b", None)]);
+ fixture.declarations.reverse();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ fixture.declarations.reverse();
+ fixture.declarations.insert(1, fixture.declarations[0]);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2.rs b/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2.rs
new file mode 100644
index 00000000..3956f249
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/source_symbols-v2.rs
@@ -0,0 +1,1124 @@
+//! Uncompiled experiment: current-text symbol queries, with no model or editor mutation.
+//!
+//! The caller supplies one immutable snapshot of current text, full lexer tokens,
+//! and syntax-only declaration role spans. This module does not lex, parse
+//! Arcadia declarations, lint, compile, allocate model IDs, discover includes,
+//! or read an accepted Project.
+//! Its matches are authored lexical uses, not typed semantic dependency edges.
+
+use oa_core::language::{Span, TokenClass, TokenSpan};
+use oa_core::Id;
+use std::collections::{BTreeMap, BTreeSet};
+use std::ops::Range;
+
+/// Fresh editor/workspace session identity plus a revision changed for EVERY
+/// indexed input change, including membership, outline and validation evidence.
+/// Reusing this key for different input violates the caller contract.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SnapshotKey {
+ pub workspace: Id,
+ pub revision: u64,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Membership {
+ Included,
+ Detached,
+ Unverified,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Evidence {
+ /// The caller verified these exact bytes in the current source scope.
+ Validated,
+ /// Current editor bytes; no successful semantic validation is claimed.
+ Draft,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum SyntaxCoverage {
+ /// The tolerant walk considered the entire file, even if its text is invalid.
+ FullFile,
+ /// A stopped/budget-limited outline cannot masquerade as a complete result.
+ Incomplete,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PartialReason {
+ IncludesUnverified,
+ MissingFiles,
+ UnappliedWorkspace,
+ UnsupportedBindings,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Scope {
+ CompleteProject,
+ Partial(PartialReason),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Domain {
+ Included,
+ Loaded,
+}
+
+impl Domain {
+ fn contains(self, membership: Membership) -> bool {
+ self == Self::Loaded || membership == Membership::Included
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredIdentity {
+ pub id: Id,
+ /// Whole quoted JSON string token in the declaration header, not a UUID
+ /// mentioned in a body, history record, comment or display name.
+ pub literal_span: Span,
+}
+
+/// These header roles must come from the coordinated core outline seam. The
+/// existing public DeclarationSpan alone lacks alias/kind/ID literal spans.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DeclarationInput {
+ pub span: Span,
+ pub kind_span: Span,
+ pub alias_span: Span,
+ pub identity: Option,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct FileInput<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ /// Exact lexer output for text, in order and covering the entire file.
+ pub tokens: &'a [TokenSpan],
+ /// Tolerant outline in alias-source order. Whole spans may nest.
+ pub declarations: &'a [DeclarationInput],
+ pub syntax: SyntaxCoverage,
+ pub membership: Membership,
+ pub evidence: Evidence,
+}
+
+/// Explicit experiment limits; production must select its own shared budgets.
+/// Caller-owned text/tokens/outline must already have bounded extraction.
+#[derive(Debug, Clone, Copy)]
+pub struct Limits {
+ pub files: usize,
+ pub source_bytes: usize,
+ pub path_bytes: usize,
+ pub tokens: usize,
+ pub records: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Resource {
+ Files,
+ SourceBytes,
+ PathBytes,
+ Tokens,
+ Records,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum InputProblem {
+ NonCanonicalPath,
+ DuplicateOrCaseCollidingPath,
+ InconsistentCompleteScope,
+ IncompleteSyntax,
+ TokenCoverage,
+ TokenBounds,
+ DeclarationBounds,
+ DeclarationOrder,
+ HeaderRoleToken,
+ InvalidAlias,
+ IdentityLiteral,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum BuildError {
+ /// No partial Index is returned. This is not a zero-result query.
+ Limit {
+ resource: Resource,
+ limit: usize,
+ observed_at_least: usize,
+ },
+ Input {
+ file: usize,
+ record: Option,
+ problem: InputProblem,
+ },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Location {
+ pub file: usize,
+ pub span: Span,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DefinitionHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ReferenceHandle {
+ snapshot: SnapshotKey,
+ index: usize,
+}
+
+/// Carries only an authored identity, never an identity generated by compilation.
+/// It may be resolved across revisions of the same workspace. Resolution still
+/// checks global loaded-file uniqueness every time.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct AuthoredAnchor {
+ workspace: Id,
+ id: Id,
+}
+
+impl AuthoredAnchor {
+ pub fn id(self) -> Id {
+ self.id
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Identity {
+ Missing,
+ Unique(AuthoredAnchor),
+ Collision { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Lookup {
+ Missing,
+ Unique(DefinitionHandle),
+ Ambiguous { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Hit {
+ Definition(DefinitionHandle),
+ Reference(ReferenceHandle),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum QueryError {
+ StaleSnapshot,
+ WrongWorkspace,
+ InvalidHandle,
+ InvalidFile,
+ InvalidBytePosition,
+ OutsideDomain,
+ AmbiguousAlias { count: usize },
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct QueryScope {
+ pub declared: Scope,
+ pub domain: Domain,
+ /// True only for the complete included project, not loaded detached files.
+ pub project_complete: bool,
+ pub all_selected_files_validated: bool,
+ pub selected_files: usize,
+ pub detached_files: usize,
+ pub unverified_files: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Position {
+ pub line: usize,
+ /// Zero-based UTF-8 byte column. The editor adapter must convert explicitly
+ /// if it needs character/UTF-16 columns; CRLF bytes are never normalized.
+ pub byte_column: usize,
+}
+
+#[derive(Debug)]
+pub struct Definition<'a> {
+ pub alias: &'a str,
+ pub kind: &'a str,
+ pub declaration: Location,
+ pub alias_location: Location,
+ pub authored_id: Option,
+}
+
+#[derive(Debug)]
+pub struct Reference<'a> {
+ pub alias: &'a str,
+ pub at: Location,
+ pub alias_location: Location,
+}
+
+#[derive(Debug)]
+pub struct SourceFile<'a> {
+ pub path: &'a str,
+ pub text: &'a str,
+ pub membership: Membership,
+ pub evidence: Evidence,
+ definitions: Range,
+ references: Range,
+}
+
+#[derive(Debug)]
+pub struct Index<'a> {
+ snapshot: SnapshotKey,
+ scope: Scope,
+ files: Vec>,
+ definitions: Vec>,
+ references: Vec>,
+ aliases: BTreeMap<&'a str, Vec>,
+ uses: BTreeMap<&'a str, Vec>,
+ identities: BTreeMap>,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Counts {
+ definitions: usize,
+ references: usize,
+}
+
+fn charge(total: &mut usize, value: usize, limit: usize, resource: Resource) -> Result<(), BuildError> {
+ let next = total.checked_add(value).ok_or(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: usize::MAX,
+ })?;
+ if next > limit {
+ return Err(BuildError::Limit {
+ resource,
+ limit,
+ observed_at_least: next,
+ });
+ }
+ *total = next;
+ Ok(())
+}
+
+fn input_error(file: usize, record: Option, problem: InputProblem) -> BuildError {
+ BuildError::Input { file, record, problem }
+}
+
+fn valid_span(text: &str, span: Span) -> bool {
+ span.start < span.end
+ && span.end <= text.len()
+ && text.is_char_boundary(span.start)
+ && text.is_char_boundary(span.end)
+}
+
+fn contains(outer: Span, inner: Span) -> bool {
+ outer.start <= inner.start && inner.end <= outer.end
+}
+
+fn token_at(tokens: &[TokenSpan], span: Span) -> Option {
+ let index = tokens.binary_search_by_key(&span.start, |token| token.span.start).ok()?;
+ let token = tokens[index];
+ (token.span == span).then_some(token)
+}
+
+fn word(class: TokenClass) -> bool {
+ matches!(class, TokenClass::Identifier | TokenClass::Keyword)
+}
+
+/// Only a live punctuation @ followed by a legal alias outside trivia qualifies.
+/// Trivia can separate @ and alias, but its bytes never become a clickable hit.
+fn reference_spans<'a>(file: FileInput<'a>) -> impl Iterator- + 'a {
+ file.tokens.iter().enumerate().filter_map(move |(index, token)| {
+ if token.class != TokenClass::Punctuation || &file.text[token.span.start..token.span.end] != "@" {
+ return None;
+ }
+ let next = file.tokens[index + 1..].iter().find(|next| {
+ !matches!(next.class, TokenClass::Whitespace | TokenClass::Comment)
+ })?;
+ let alias = &file.text[next.span.start..next.span.end];
+ (word(next.class) && oa_core::valid_alias(alias)).then_some((token.span, next.span))
+ })
+}
+
+/// Runs all aggregate limits and structural checks before allocating Index maps
+/// or vectors. Path normalization/quoted-ID decoding may allocate bounded scratch
+/// strings. Scratch storage is proportional to admitted input bytes and file
+/// count, including Unicode expansion and collection overhead.
+fn preflight(files: &[FileInput<'_>], scope: Scope, limits: Limits) -> Result
{
+ let mut file_count = 0;
+ charge(&mut file_count, files.len(), limits.files, Resource::Files)?;
+ let mut bytes = 0;
+ let mut path_bytes = 0;
+ let mut tokens = 0;
+ let mut records = 0;
+ for file in files {
+ charge(&mut bytes, file.text.len(), limits.source_bytes, Resource::SourceBytes)?;
+ charge(&mut path_bytes, file.path.len(), limits.path_bytes, Resource::PathBytes)?;
+ charge(&mut tokens, file.tokens.len(), limits.tokens, Resource::Tokens)?;
+ charge(&mut records, file.declarations.len(), limits.records, Resource::Records)?;
+ }
+ let definitions = records;
+ if scope == Scope::CompleteProject
+ && (!files.iter().any(|file| file.membership == Membership::Included)
+ || files.iter().any(|file| file.membership == Membership::Unverified))
+ {
+ return Err(input_error(0, None, InputProblem::InconsistentCompleteScope));
+ }
+ // Scratch storage is allocated only after aggregate file/path admission.
+ // Each admitted path is folded once, retaining core's Unicode policy.
+ let mut folded_paths = BTreeSet::new();
+ for (file_index, file) in files.iter().copied().enumerate() {
+ if file.syntax != SyntaxCoverage::FullFile {
+ return Err(input_error(file_index, None, InputProblem::IncompleteSyntax));
+ }
+ if oa_core::source_project::resolve_include("", file.path).as_deref() != Ok(file.path) {
+ return Err(input_error(file_index, None, InputProblem::NonCanonicalPath));
+ }
+ if !folded_paths.insert(file.path.to_lowercase()) {
+ return Err(input_error(file_index, None, InputProblem::DuplicateOrCaseCollidingPath));
+ }
+ let mut end = 0;
+ for (token_index, token) in file.tokens.iter().enumerate() {
+ if !valid_span(file.text, token.span) {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenBounds));
+ }
+ if token.span.start != end {
+ return Err(input_error(file_index, Some(token_index), InputProblem::TokenCoverage));
+ }
+ end = token.span.end;
+ }
+ if end != file.text.len() {
+ return Err(input_error(file_index, None, InputProblem::TokenCoverage));
+ }
+ let mut previous_alias_end = 0;
+ for (declaration_index, declaration) in file.declarations.iter().enumerate() {
+ let problem = |problem| input_error(file_index, Some(declaration_index), problem);
+ if !valid_span(file.text, declaration.span)
+ || !valid_span(file.text, declaration.kind_span)
+ || !valid_span(file.text, declaration.alias_span)
+ || declaration.kind_span.start != declaration.span.start
+ || !contains(declaration.span, declaration.kind_span)
+ || !contains(declaration.span, declaration.alias_span)
+ || declaration.alias_span.start < declaration.kind_span.end
+ {
+ return Err(problem(InputProblem::DeclarationBounds));
+ }
+ if declaration.alias_span.start < previous_alias_end {
+ return Err(problem(InputProblem::DeclarationOrder));
+ }
+ previous_alias_end = declaration.alias_span.end;
+ if !token_at(file.tokens, declaration.kind_span).is_some_and(|token| word(token.class))
+ || !token_at(file.tokens, declaration.alias_span).is_some_and(|token| word(token.class))
+ {
+ return Err(problem(InputProblem::HeaderRoleToken));
+ }
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ if !oa_core::valid_alias(alias) {
+ return Err(problem(InputProblem::InvalidAlias));
+ }
+ if let Some(identity) = declaration.identity {
+ if !valid_span(file.text, identity.literal_span)
+ || !contains(declaration.span, identity.literal_span)
+ || identity.literal_span.start < declaration.alias_span.end
+ || token_at(file.tokens, identity.literal_span).map(|token| token.class) != Some(TokenClass::String)
+ {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ let literal = &file.text[identity.literal_span.start..identity.literal_span.end];
+ let decoded = serde_json::from_str::(literal)
+ .ok()
+ .filter(|value| oa_core::language::has_uuid_header_shape(value))
+ .and_then(|value| Id::parse_str(&value).ok());
+ if decoded != Some(identity.id) {
+ return Err(problem(InputProblem::IdentityLiteral));
+ }
+ }
+ }
+ // Token bounds and coverage are proven before reference_spans slices.
+ for _ in reference_spans(file) {
+ charge(&mut records, 1, limits.records, Resource::Records)?;
+ }
+ }
+ Ok(Counts { definitions, references: records - definitions })
+}
+
+impl<'a> Index<'a> {
+ pub fn build(
+ snapshot: SnapshotKey,
+ files: &[FileInput<'a>],
+ scope: Scope,
+ limits: Limits,
+ ) -> Result {
+ let counts = preflight(files, scope, limits)?;
+ let mut index = Self {
+ snapshot,
+ scope,
+ files: Vec::with_capacity(files.len()),
+ definitions: Vec::with_capacity(counts.definitions),
+ references: Vec::with_capacity(counts.references),
+ aliases: BTreeMap::new(),
+ uses: BTreeMap::new(),
+ identities: BTreeMap::new(),
+ };
+ for (file_index, file) in files.iter().copied().enumerate() {
+ let definition_start = index.definitions.len();
+ let reference_start = index.references.len();
+ for declaration in file.declarations {
+ let alias = &file.text[declaration.alias_span.start..declaration.alias_span.end];
+ let number = index.definitions.len();
+ index.aliases.entry(alias).or_default().push(number);
+ if let Some(identity) = declaration.identity {
+ index.identities.entry(identity.id).or_default().push(number);
+ }
+ index.definitions.push(Definition {
+ alias,
+ kind: &file.text[declaration.kind_span.start..declaration.kind_span.end],
+ declaration: Location { file: file_index, span: declaration.span },
+ alias_location: Location { file: file_index, span: declaration.alias_span },
+ authored_id: declaration.identity.map(|identity| identity.id),
+ });
+ }
+ for (at, alias_span) in reference_spans(file) {
+ let alias = &file.text[alias_span.start..alias_span.end];
+ index.uses.entry(alias).or_default().push(index.references.len());
+ index.references.push(Reference {
+ alias,
+ at: Location { file: file_index, span: at },
+ alias_location: Location { file: file_index, span: alias_span },
+ });
+ }
+ index.files.push(SourceFile {
+ path: file.path,
+ text: file.text,
+ membership: file.membership,
+ evidence: file.evidence,
+ definitions: definition_start..index.definitions.len(),
+ references: reference_start..index.references.len(),
+ });
+ }
+ Ok(index)
+ }
+
+ pub fn snapshot(&self) -> SnapshotKey {
+ self.snapshot
+ }
+
+ pub fn source(&self, file: usize) -> Result<&SourceFile<'a>, QueryError> {
+ self.files.get(file).ok_or(QueryError::InvalidFile)
+ }
+
+ pub fn scope(&self, domain: Domain) -> QueryScope {
+ let selected_files = self.files.iter().filter(|file| domain.contains(file.membership)).count();
+ QueryScope {
+ declared: self.scope,
+ domain,
+ project_complete: self.scope == Scope::CompleteProject && domain == Domain::Included,
+ all_selected_files_validated: selected_files != 0
+ && self.files.iter().filter(|file| domain.contains(file.membership))
+ .all(|file| file.evidence == Evidence::Validated),
+ selected_files,
+ detached_files: self.files.iter().filter(|file| file.membership == Membership::Detached).count(),
+ unverified_files: self.files.iter().filter(|file| file.membership == Membership::Unverified).count(),
+ }
+ }
+
+ fn definition_handle(&self, index: usize) -> DefinitionHandle {
+ DefinitionHandle { snapshot: self.snapshot, index }
+ }
+
+ fn reference_handle(&self, index: usize) -> ReferenceHandle {
+ ReferenceHandle { snapshot: self.snapshot, index }
+ }
+
+ pub fn definition(&self, handle: DefinitionHandle) -> Result<&Definition<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.definitions.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn reference(&self, handle: ReferenceHandle) -> Result<&Reference<'a>, QueryError> {
+ if handle.snapshot != self.snapshot {
+ return Err(QueryError::StaleSnapshot);
+ }
+ self.references.get(handle.index).ok_or(QueryError::InvalidHandle)
+ }
+
+ pub fn definitions_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> Definitions<'i, 'a> {
+ Definitions { index: self, indices: self.aliases.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn definitions_for_id(&self, id: Id, domain: Domain) -> Definitions<'_, 'a> {
+ Definitions { index: self, indices: self.identities.get(&id).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn references_for_alias<'i>(&'i self, alias: &str, domain: Domain) -> References<'i, 'a> {
+ References { index: self, indices: self.uses.get(alias).map_or(&[], Vec::as_slice).iter(), domain }
+ }
+
+ pub fn resolve_reference(&self, handle: ReferenceHandle, domain: Domain) -> Result {
+ let reference = self.reference(handle)?;
+ if !domain.contains(self.files[reference.at.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let mut definitions = self.definitions_for_alias(reference.alias, domain);
+ let Some(first) = definitions.next() else { return Ok(Lookup::Missing); };
+ let count = 1 + definitions.count();
+ Ok(if count == 1 { Lookup::Unique(first) } else { Lookup::Ambiguous { count } })
+ }
+
+ pub fn identity(&self, handle: DefinitionHandle) -> Result {
+ let Some(id) = self.definition(handle)?.authored_id else { return Ok(Identity::Missing); };
+ let count = self.identities[&id].len();
+ Ok(if count == 1 {
+ Identity::Unique(AuthoredAnchor { workspace: self.snapshot.workspace, id })
+ } else {
+ Identity::Collision { count }
+ })
+ }
+
+ pub fn resolve_anchor(&self, anchor: AuthoredAnchor, domain: Domain) -> Result {
+ if anchor.workspace != self.snapshot.workspace {
+ return Err(QueryError::WrongWorkspace);
+ }
+ let Some(candidates) = self.identities.get(&anchor.id) else { return Ok(Lookup::Missing); };
+ // A detached duplicate must not silently confer identity uniqueness.
+ if candidates.len() != 1 {
+ return Ok(Lookup::Ambiguous { count: candidates.len() });
+ }
+ let candidate = candidates[0];
+ Ok(if domain.contains(self.files[self.definitions[candidate].declaration.file].membership) {
+ Lookup::Unique(self.definition_handle(candidate))
+ } else {
+ Lookup::Missing
+ })
+ }
+
+ /// Exact authored alias-use projection for a unique current declaration in
+ /// the requested domain. Duplicate aliases require candidate inspection;
+ /// this API never attributes all matching uses to the first declaration.
+ pub fn references_to(&self, handle: DefinitionHandle, domain: Domain) -> Result, QueryError> {
+ let definition = self.definition(handle)?;
+ if !domain.contains(self.files[definition.declaration.file].membership) {
+ return Err(QueryError::OutsideDomain);
+ }
+ let count = self.definitions_for_alias(definition.alias, domain).count();
+ if count != 1 {
+ return Err(QueryError::AmbiguousAlias { count });
+ }
+ Ok(self.references_for_alias(definition.alias, domain))
+ }
+
+ pub fn position(&self, file: usize, offset: usize) -> Result {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let prefix = &source.text[..offset];
+ Ok(Position {
+ line: prefix.bytes().filter(|byte| *byte == b'\n').count(),
+ byte_column: prefix.rsplit_once('\n').map_or(prefix.len(), |(_, tail)| tail.len()),
+ })
+ }
+
+ pub fn hit(&self, file: usize, offset: usize) -> Result, QueryError> {
+ let source = self.source(file)?;
+ if offset > source.text.len() || !source.text.is_char_boundary(offset) {
+ return Err(QueryError::InvalidBytePosition);
+ }
+ let definitions = &self.definitions[source.definitions.clone()];
+ let before = definitions.partition_point(|definition| definition.alias_location.span.start <= offset);
+ if before != 0 && offset < definitions[before - 1].alias_location.span.end {
+ return Ok(Some(Hit::Definition(self.definition_handle(source.definitions.start + before - 1))));
+ }
+ let references = &self.references[source.references.clone()];
+ let before = references.partition_point(|reference| reference.at.span.start <= offset);
+ if before != 0 {
+ let reference = &references[before - 1];
+ if offset < reference.at.span.end
+ || (reference.alias_location.span.start <= offset && offset < reference.alias_location.span.end)
+ {
+ return Ok(Some(Hit::Reference(self.reference_handle(source.references.start + before - 1))));
+ }
+ }
+ Ok(None)
+ }
+}
+
+pub struct Definitions<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl Iterator for Definitions<'_, '_> {
+ type Item = DefinitionHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let definition = &self.index.definitions[number];
+ if self.domain.contains(self.index.files[definition.declaration.file].membership) {
+ return Some(self.index.definition_handle(number));
+ }
+ }
+ None
+ }
+}
+
+pub struct References<'i, 'a> {
+ index: &'i Index<'a>,
+ indices: std::slice::Iter<'i, usize>,
+ domain: Domain,
+}
+
+impl References<'_, '_> {
+ pub fn scope(&self) -> QueryScope {
+ self.index.scope(self.domain)
+ }
+}
+
+impl Iterator for References<'_, '_> {
+ type Item = ReferenceHandle;
+
+ fn next(&mut self) -> Option {
+ for &number in self.indices.by_ref() {
+ let reference = &self.index.references[number];
+ if self.domain.contains(self.index.files[reference.at.file].membership) {
+ return Some(self.index.reference_handle(number));
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const A: Id = Id::from_u128(1);
+ const B: Id = Id::from_u128(2);
+ const C: Id = Id::from_u128(3);
+
+ struct Fixture {
+ path: String,
+ text: String,
+ tokens: Vec,
+ declarations: Vec,
+ }
+
+ impl Fixture {
+ /// Test-only literal role proof. Production never searches declaration
+ /// headers: its caller supplies the coordinated syntax outline roles.
+ fn new(path: &str, text: String, roles: &[(&str, &str, Option)]) -> Self {
+ let tokens = oa_core::language::tokenize(&text);
+ let declarations = roles.iter().map(|&(kind, alias, id)| {
+ let header = format!("{kind} {alias} ");
+ let start = text.find(&header).expect("authored fixture header");
+ let end = text[start..].find('}').map_or(text.len(), |end| start + end + 1);
+ let alias_start = start + kind.len() + 1;
+ DeclarationInput {
+ span: Span { start, end },
+ kind_span: Span { start, end: start + kind.len() },
+ alias_span: Span { start: alias_start, end: alias_start + alias.len() },
+ identity: id.map(|id| {
+ let literal = format!("\"{id}\"");
+ let literal_start = start + text[start..end].find(&literal).expect("authored fixture UUID");
+ AuthoredIdentity { id, literal_span: Span { start: literal_start, end: literal_start + literal.len() } }
+ }),
+ }
+ }).collect();
+ Self { path: path.into(), text, tokens, declarations }
+ }
+
+ fn input(&self, membership: Membership, evidence: Evidence) -> FileInput<'_> {
+ FileInput { path: &self.path, text: &self.text, tokens: &self.tokens, declarations: &self.declarations, syntax: SyntaxCoverage::FullFile, membership, evidence }
+ }
+
+ fn included(&self) -> FileInput<'_> {
+ self.input(Membership::Included, Evidence::Draft)
+ }
+ }
+
+ fn snapshot(revision: u64) -> SnapshotKey {
+ SnapshotKey { workspace: Id::from_u128(1000), revision }
+ }
+
+ fn limits() -> Limits {
+ Limits { files: 16, source_bytes: 1024 * 1024, path_bytes: 8192, tokens: 200_000, records: 100_000 }
+ }
+
+ fn index<'a>(files: &[FileInput<'a>]) -> Index<'a> {
+ Index::build(snapshot(1), files, Scope::CompleteProject, limits()).unwrap()
+ }
+
+ fn definition(index: &Index<'_>, alias: &str) -> DefinitionHandle {
+ let mut found = index.definitions_for_alias(alias, Domain::Loaded);
+ let result = found.next().expect("fixture definition");
+ assert!(found.next().is_none(), "fixture expects unique alias");
+ result
+ }
+
+ fn anchor(index: &Index<'_>, handle: DefinitionHandle) -> AuthoredAnchor {
+ match index.identity(handle).unwrap() {
+ Identity::Unique(anchor) => anchor,
+ identity => panic!("fixture expects unique authored identity: {identity:?}"),
+ }
+ }
+
+ #[test]
+ fn repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor tank \"Same display\" id \"{A}\" {{}}\n\
+ oa_actor tank_extra \"Same display\" id \"{B}\" {{}}\n\
+ oa_interaction flow \"Flow\" id \"{C}\" {{ source @tank; target @tank; other @tank_extra; \
+ description \"@tank @tank_extra\"; historical uuid \"{A}\"; }}\n\
+ // @tank @tank_extra and UUID {A}\n"
+ ), &[("oa_actor", "tank", Some(A)), ("oa_actor", "tank_extra", Some(B)), ("oa_interaction", "flow", Some(C))]);
+ let original = (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone());
+ let index = index(&[fixture.included()]);
+ let tank = definition(&index, "tank");
+ let uses: Vec<_> = index.references_to(tank, Domain::Included).unwrap().collect();
+ assert_eq!(uses.len(), 2);
+ assert_ne!(uses[0], uses[1]);
+ assert_eq!(index.references_for_alias("tank_extra", Domain::Loaded).count(), 1);
+ for usage in uses {
+ let record = index.reference(usage).unwrap();
+ assert_eq!(&fixture.text[record.at.span.start..record.at.span.end], "@");
+ assert_eq!(&fixture.text[record.alias_location.span.start..record.alias_location.span.end], "tank");
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(tank)));
+ assert_eq!(index.hit(0, record.alias_location.span.start), Ok(Some(Hit::Reference(usage))));
+ }
+ for ignored in [fixture.text.find("Same display").unwrap(), fixture.text.find("// @tank").unwrap() + 3,
+ fixture.text.find("\"@tank @tank_extra\"").unwrap() + 1,
+ fixture.text.find("historical uuid").unwrap() + "historical uuid \"".len()] {
+ assert_eq!(index.hit(0, ignored), Ok(None));
+ }
+ assert_eq!(index.hit(0, fixture.text.len()), Ok(None));
+ assert_eq!(original, (fixture.text.clone(), fixture.tokens.clone(), fixture.declarations.clone()));
+ }
+
+ #[test]
+ fn comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans() {
+ let fixture = Fixture::new("parts/actors.arcadia", format!(
+ "oa_actor a \"救助 🚒\" id \"{A}\" {{}}\r\nsource @ // 注释 @a\r\n a; target @a;\r\n"
+ ), &[("oa_actor", "a", Some(A))]);
+ let index = index(&[fixture.included()]);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert_eq!(uses.len(), 2);
+ let first = index.reference(uses[0]).unwrap();
+ assert_eq!(index.position(0, first.at.span.start), Ok(Position { line: 1, byte_column: 7 }));
+ assert_eq!(index.position(0, first.alias_location.span.start), Ok(Position { line: 2, byte_column: 2 }));
+ assert_eq!(index.hit(0, first.at.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ assert_eq!(index.hit(0, first.alias_location.span.start), Ok(Some(Hit::Reference(uses[0]))));
+ for byte in [first.at.span.end, fixture.text.find("注释").unwrap(), fixture.text.find("// 注释 @a").unwrap() + "// 注释 ".len(), first.alias_location.span.end] {
+ assert_eq!(index.hit(0, byte), Ok(None));
+ }
+ let unicode_inside = fixture.text.find('æ•‘').unwrap() + 1;
+ assert_eq!(index.hit(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.position(0, unicode_inside), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(0, fixture.text.len() + 1), Err(QueryError::InvalidBytePosition));
+ assert_eq!(index.hit(1, 0), Err(QueryError::InvalidFile));
+ assert!(index.source(0).unwrap().text.contains("\r\n"));
+ }
+
+ #[test]
+ fn legal_keyword_classified_alias_is_not_discarded() {
+ let fixture = Fixture::new("project.arcadia", format!(
+ "oa_actor oa_activity \"Participant\" id \"{A}\" {{}}\nsource @oa_activity; source @parent;"
+ ), &[("oa_actor", "oa_activity", Some(A))]);
+ assert!(oa_core::valid_alias("oa_activity"));
+ assert!(!oa_core::valid_alias("parent"));
+ assert_eq!(token_at(&fixture.tokens, fixture.declarations[0].alias_span).unwrap().class, TokenClass::Keyword);
+ let index = index(&[fixture.included()]);
+ let found = definition(&index, "oa_activity");
+ let usage = index.references_for_alias("oa_activity", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(found)));
+ assert_eq!(index.references_for_alias("parent", Domain::Loaded).count(), 0);
+ assert_eq!(index.hit(0, fixture.declarations[0].alias_span.start), Ok(Some(Hit::Definition(found))));
+ }
+
+ #[test]
+ fn duplicate_aliases_are_candidates_without_false_reference_attribution() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"Same display\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let second = Fixture::new("parts/other.arcadia", format!("oa_actor a \"Same display\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let index = index(&[first.included(), second.included()]);
+ let candidates: Vec<_> = index.definitions_for_alias("a", Domain::Included).collect();
+ assert_eq!(candidates.len(), 2);
+ assert_eq!(index.definition(candidates[0]).unwrap().declaration.file, 0);
+ assert_eq!(index.definition(candidates[1]).unwrap().declaration.file, 1);
+ assert_ne!(anchor(&index, candidates[0]), anchor(&index, candidates[1]));
+ assert!(matches!(index.references_to(candidates[0], Domain::Included), Err(QueryError::AmbiguousAlias { count: 2 })));
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Included).collect();
+ assert_eq!(uses.len(), 2);
+ for usage in uses {
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ }
+ }
+
+ #[test]
+ fn detached_uuid_clone_disables_anchor_even_for_included_query() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_anchor = anchor(&original, definition(&original, "a"));
+ let detached = Fixture::new("retained.arcadia", format!("oa_actor old \"Retained\" id \"{A}\" {{}}"), &[("oa_actor", "old", Some(A))]);
+ let current = Index::build(snapshot(2), &[first.included(), detached.input(Membership::Detached, Evidence::Draft)], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.identity(definition(¤t, "a")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.identity(definition(¤t, "old")), Ok(Identity::Collision { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(current.definitions_for_alias("old", Domain::Included).count(), 0);
+ assert_eq!(current.definitions_for_id(A, Domain::Included).count(), 1);
+ assert_eq!(current.definitions_for_id(A, Domain::Loaded).count(), 2);
+ }
+
+ #[test]
+ fn reused_alias_cannot_steal_old_identity_and_old_locations_are_stale() {
+ let first = Fixture::new("project.arcadia", format!("oa_actor a \"First\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let original = index(&[first.included()]);
+ let old_definition = definition(&original, "a");
+ let old_reference = original.references_for_alias("a", Domain::Included).next().unwrap();
+ let old_anchor = anchor(&original, old_definition);
+ let edited = Fixture::new("project.arcadia", format!(
+ "oa_actor b \"First renamed\" id \"{A}\" {{}}\noa_actor a \"New actor\" id \"{B}\" {{}}\nsource @a; target @b;"
+ ), &[("oa_actor", "b", Some(A)), ("oa_actor", "a", Some(B))]);
+ let current = Index::build(snapshot(2), &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(current.definition(old_definition).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.reference(old_reference).map(|reference| reference.alias), Err(QueryError::StaleSnapshot));
+ assert_eq!(current.resolve_anchor(old_anchor, Domain::Included), Ok(Lookup::Unique(definition(¤t, "b"))));
+ let new_use = current.references_for_alias("a", Domain::Included).next().unwrap();
+ assert_eq!(current.resolve_reference(new_use, Domain::Included), Ok(Lookup::Unique(definition(¤t, "a"))));
+ assert_eq!(anchor(¤t, definition(¤t, "a")).id(), B);
+ let other_workspace = Index::build(SnapshotKey { workspace: Id::from_u128(2000), revision: 2 }, &[edited.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(other_workspace.resolve_anchor(old_anchor, Domain::Loaded), Err(QueryError::WrongWorkspace));
+ assert_eq!(other_workspace.definition(definition(¤t, "b")).map(|definition| definition.alias), Err(QueryError::StaleSnapshot));
+ let deleted = Fixture::new("project.arcadia", format!("oa_actor a \"New actor\" id \"{B}\" {{}}"), &[("oa_actor", "a", Some(B))]);
+ let after_delete = Index::build(snapshot(3), &[deleted.included()], Scope::CompleteProject, limits()).unwrap();
+ assert_eq!(after_delete.resolve_anchor(old_anchor, Domain::Loaded), Ok(Lookup::Missing));
+ }
+
+ #[test]
+ fn missing_ids_produce_only_current_snapshot_locations_without_stabilization() {
+ let fixture = Fixture::new("project.arcadia", format!("oa_actor a \"No UUID\" {{ historical uuid \"{A}\"; }}\r\nsource @a;"), &[("oa_actor", "a", None)]);
+ let original = fixture.text.clone();
+ let index = index(&[fixture.included()]);
+ let declaration = definition(&index, "a");
+ assert_eq!(index.identity(declaration), Ok(Identity::Missing));
+ assert_eq!(index.definition(declaration).unwrap().authored_id, None);
+ let usage = index.references_to(declaration, Domain::Included).unwrap().next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(declaration)));
+ assert_eq!(fixture.text, original);
+ assert_eq!(index.source(0).unwrap().text.as_bytes(), original.as_bytes());
+ assert!(!index.source(0).unwrap().text.contains(" id "));
+ assert_eq!(index.hit(0, fixture.text.find(&A.to_string()).unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn scope_provenance_and_detached_alias_collision_are_explicit() {
+ let included = Fixture::new("project.arcadia", format!("oa_actor a \"Included\" id \"{A}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(A))]);
+ let detached = Fixture::new("old.arcadia", format!("oa_actor a \"Detached\" id \"{B}\" {{}}\nsource @a;"), &[("oa_actor", "a", Some(B))]);
+ let unknown = Fixture::new("new.arcadia", "source @a;".into(), &[]);
+ let index = Index::build(snapshot(1), &[
+ included.input(Membership::Included, Evidence::Validated),
+ detached.input(Membership::Detached, Evidence::Draft),
+ unknown.input(Membership::Unverified, Evidence::Draft),
+ ], Scope::Partial(PartialReason::IncludesUnverified), limits()).unwrap();
+ assert_eq!(index.references_for_alias("a", Domain::Included).count(), 1);
+ assert_eq!(index.references_for_alias("a", Domain::Loaded).count(), 3);
+ let scope = index.references_for_alias("a", Domain::Included).scope();
+ assert_eq!(scope.declared, Scope::Partial(PartialReason::IncludesUnverified));
+ assert!(!scope.project_complete);
+ assert!(scope.all_selected_files_validated);
+ assert_eq!((scope.selected_files, scope.detached_files, scope.unverified_files), (1, 1, 1));
+ assert!(!index.scope(Domain::Loaded).all_selected_files_validated);
+ let uses: Vec<_> = index.references_for_alias("a", Domain::Loaded).collect();
+ assert!(matches!(index.resolve_reference(uses[0], Domain::Included), Ok(Lookup::Unique(_))));
+ assert_eq!(index.resolve_reference(uses[0], Domain::Loaded), Ok(Lookup::Ambiguous { count: 2 }));
+ assert_eq!(index.resolve_reference(uses[1], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.resolve_reference(uses[2], Domain::Included), Err(QueryError::OutsideDomain));
+ assert_eq!(index.source(2).unwrap().membership, Membership::Unverified);
+ assert!(matches!(Index::build(snapshot(1), &[included.included(), unknown.input(Membership::Unverified, Evidence::Draft)], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::InconsistentCompleteScope, .. })));
+ }
+
+ #[test]
+ fn incomplete_draft_is_lexical_and_error_token_does_not_leak_references() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Draft\" {}\nsource @a; ? \"unfinished @a".into(), &[("oa_actor", "a", None)]);
+ assert!(fixture.tokens.iter().any(|token| token.class == TokenClass::Error));
+ let index = Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::UnappliedWorkspace), limits()).unwrap();
+ let matches = index.references_to(definition(&index, "a"), Domain::Included).unwrap();
+ assert!(!matches.scope().project_complete);
+ assert!(!matches.scope().all_selected_files_validated);
+ assert_eq!(matches.count(), 1);
+ assert_eq!(index.hit(0, fixture.text.rfind("@a").unwrap()), Ok(None));
+ }
+
+ #[test]
+ fn header_metadata_must_match_current_token_boundaries_and_authored_id() {
+ let mut fixture = Fixture::new("project.arcadia", format!("oa_actor alpha \"救助\" id \"{A}\" {{}}"), &[("oa_actor", "alpha", Some(A))]);
+ let original = fixture.declarations[0];
+ fixture.declarations[0].alias_span.start += 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::HeaderRoleToken, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].identity.as_mut().unwrap().id = B;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].alias_span = Span { start: fixture.text.find('æ•‘').unwrap() + 1, end: fixture.text.find('æ•‘').unwrap() + 3 };
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ fixture.declarations[0] = original;
+ fixture.declarations[0].span.end = fixture.text.len() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationBounds, .. })));
+ }
+
+ #[test]
+ fn truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let final_token = fixture.tokens.pop().unwrap();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ fixture.tokens.push(final_token);
+ fixture.tokens.remove(1);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenCoverage, .. })));
+ let mut unicode = Fixture::new("project.arcadia", "// 救助\n".into(), &[]);
+ unicode.tokens[0].span.end = unicode.text.find('æ•‘').unwrap() + 1;
+ assert!(matches!(Index::build(snapshot(1), &[unicode.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::TokenBounds, .. })));
+ }
+
+ #[test]
+ fn stopped_outline_is_not_a_successful_empty_query() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a;".into(), &[("oa_actor", "a", None)]);
+ let mut stopped = fixture.included();
+ stopped.declarations = &[];
+ stopped.syntax = SyntaxCoverage::Incomplete;
+ assert!(matches!(Index::build(snapshot(1), &[stopped], Scope::Partial(PartialReason::MissingFiles), limits()), Err(BuildError::Input { problem: InputProblem::IncompleteSyntax, .. })));
+ }
+
+ #[test]
+ fn nested_declaration_extents_do_not_turn_body_text_into_definition_hits() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_entity outer \"Outer\" {\n oa_actor inner \"Inner\" {}\n}\nsource @inner;".into(), &[("oa_entity", "outer", None), ("oa_actor", "inner", None)]);
+ // Supply the actual outer whole span; nesting does not change alias order.
+ fixture.declarations[0].span.end = fixture.text.rfind('}').unwrap() + 1;
+ let index = index(&[fixture.included()]);
+ let inner = definition(&index, "inner");
+ assert_eq!(index.hit(0, fixture.declarations[1].alias_span.start), Ok(Some(Hit::Definition(inner))));
+ assert_eq!(index.hit(0, fixture.text.find("\"Inner\"").unwrap() + 1), Ok(None));
+ let usage = index.references_for_alias("inner", Domain::Included).next().unwrap();
+ assert_eq!(index.resolve_reference(usage, Domain::Included), Ok(Lookup::Unique(inner)));
+ }
+
+ #[test]
+ fn path_identity_rejects_normalization_and_case_collisions() {
+ let mut first = Fixture::new("project.arcadia", "source @a;".into(), &[]);
+ let second = Fixture::new("PROJECT.arcadia", "source @b;".into(), &[]);
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ for invalid in ["./project.arcadia", "parts/../project.arcadia", "../project.arcadia", "C:/project.arcadia", "parts\\project.arcadia"] {
+ first.path = invalid.into();
+ assert!(matches!(Index::build(snapshot(1), &[first.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::NonCanonicalPath, .. })), "{invalid}");
+ }
+ }
+
+ #[test]
+ fn unicode_path_collisions_match_core_lowercase_policy() {
+ for (first_path, second_path) in [("Ä.arcadia", "ä.arcadia"), ("ΟΣ/model.arcadia", "ος/model.arcadia")] {
+ let first = Fixture::new(first_path, "source @a;".into(), &[]);
+ let second = Fixture::new(second_path, "source @b;".into(), &[]);
+ assert_eq!(first_path.to_lowercase(), second_path.to_lowercase());
+ assert!(!first_path.eq_ignore_ascii_case(second_path));
+ assert!(matches!(Index::build(snapshot(1), &[first.included(), second.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DuplicateOrCaseCollidingPath, .. })));
+ }
+ }
+
+ #[test]
+ fn noncanonical_header_identity_cannot_enter_through_query_metadata() {
+ for raw in [
+ "00000000000000000000000000000001",
+ "{00000000-0000-0000-0000-000000000001}",
+ "urn:uuid:00000000-0000-0000-0000-000000000001",
+ ] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
+ let start = source.find(&literal).unwrap();
+ // This fixture has one explicit empty body. A brace inside the
+ // quoted UUID must not become the test's declaration end.
+ let body_start = source.find("{}\n").unwrap();
+ fixture.declarations[0].span.end = body_start + 2;
+ assert!(start + literal.len() <= body_start);
+ // An adapter passes None from the corrected outline; lexical
+ // definition/reference navigation survives without an ID anchor.
+ {
+ let admitted = index(&[fixture.included()]);
+ let d = definition(&admitted, "a");
+ assert_eq!(admitted.identity(d), Ok(Identity::Missing));
+ assert_eq!(admitted.references_to(d, Domain::Included).unwrap().count(), 1);
+ assert_eq!(admitted.source(0).unwrap().text, source);
+ }
+ // A forged/stale caller cannot put the UUID crate's permissive
+ // interpretation back into an authored anchor via the raw API.
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: A,
+ literal_span: Span { start, end: start + literal.len() },
+ });
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::IdentityLiteral, .. })));
+ }
+ }
+
+ #[test]
+ fn query_header_admission_preserves_uppercase_and_json_escape_bytes() {
+ let expected = Id::from_u128(0xabcdef);
+ let uppercase = expected.to_string().to_uppercase();
+ for raw in [uppercase.clone(), format!("\\u0030{}", &uppercase[1..])] {
+ let literal = format!("\"{raw}\"");
+ let source = format!("oa_actor a \"A\" id {literal} {{}}\nsource @a;");
+ let mut fixture = Fixture::new("project.arcadia", source.clone(), &[("oa_actor", "a", None)]);
+ let start = source.find(&literal).unwrap();
+ fixture.declarations[0].identity = Some(AuthoredIdentity {
+ id: expected,
+ literal_span: Span { start, end: start + literal.len() },
+ });
+ let admitted = index(&[fixture.included()]);
+ assert_eq!(anchor(&admitted, definition(&admitted, "a")).id(), expected);
+ assert_eq!(admitted.source(0).unwrap().text.as_bytes(), source.as_bytes());
+ }
+ }
+
+ #[test]
+ fn admitted_unicode_path_budget_keeps_original_policy_after_single_fold() {
+ let fixture = Fixture::new("İ.arcadia", String::new(), &[]);
+ assert!(fixture.path.to_lowercase().len() > fixture.path.len());
+ let exact = Limits { files: 1, source_bytes: 0, path_bytes: fixture.path.len(), tokens: 0, records: 0 };
+ let admitted = Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).unwrap();
+ assert_eq!(admitted.source(0).unwrap().path, "İ.arcadia");
+ // PathBytes continues to count authored input. One lowercase key's
+ // Unicode expansion is bounded scratch, not a new path rejection rule.
+ }
+
+ #[test]
+ fn every_budget_admits_boundary_and_returns_no_index_one_below() {
+ let fixture = Fixture::new("project.arcadia", "oa_actor a \"Text\" {}\nsource @a; target @a;".into(), &[("oa_actor", "a", None)]);
+ let exact = Limits { files: 1, source_bytes: fixture.text.len(), path_bytes: fixture.path.len(), tokens: fixture.tokens.len(), records: 3 };
+ assert!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, exact).is_ok());
+ for resource in [Resource::Files, Resource::SourceBytes, Resource::PathBytes, Resource::Tokens, Resource::Records] {
+ let mut reduced = exact;
+ match resource {
+ Resource::Files => reduced.files -= 1,
+ Resource::SourceBytes => reduced.source_bytes -= 1,
+ Resource::PathBytes => reduced.path_bytes -= 1,
+ Resource::Tokens => reduced.tokens -= 1,
+ Resource::Records => reduced.records -= 1,
+ }
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, reduced), Err(BuildError::Limit { resource: actual, .. }) if actual == resource));
+ }
+ // Aggregate bytes/tokens/records, not a per-file budget reset.
+ let second = Fixture::new("other.arcadia", "source @a;".into(), &[]);
+ let mut aggregate = limits();
+ aggregate.records = 3;
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included(), second.included()], Scope::CompleteProject, aggregate), Err(BuildError::Limit { resource: Resource::Records, observed_at_least: 4, .. })));
+ let mut maximum = usize::MAX;
+ assert!(matches!(charge(&mut maximum, 1, usize::MAX, Resource::SourceBytes), Err(BuildError::Limit { resource: Resource::SourceBytes, .. })));
+ assert_eq!(maximum, usize::MAX);
+ }
+
+ #[test]
+ fn declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search() {
+ let mut fixture = Fixture::new("project.arcadia", "oa_actor a \"First\" {}\noa_actor b \"Second\" {}".into(), &[("oa_actor", "a", None), ("oa_actor", "b", None)]);
+ fixture.declarations.reverse();
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ fixture.declarations.reverse();
+ fixture.declarations.insert(1, fixture.declarations[0]);
+ assert!(matches!(Index::build(snapshot(1), &[fixture.included()], Scope::CompleteProject, limits()), Err(BuildError::Input { problem: InputProblem::DeclarationOrder, .. })));
+ }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/components/utf8-reconstruction-correction.json b/harness/experiments/text-navigation-components-20260911/components/utf8-reconstruction-correction.json
new file mode 100644
index 00000000..7a27c94f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/components/utf8-reconstruction-correction.json
@@ -0,0 +1,9 @@
+{
+ "cause": "Initial reconstruction used Windows cp1252 default instead of explicit UTF-8",
+ "productionLanguageByteEqualityToUtf8Patch": true,
+ "oldOutlineTestsSha256": "f64bd89959554cd5842609c031d442f3d9e27b73a216dd936715284c9fe9fb97",
+ "correctedOutlineTestsPreFormatSha256": "27f4a02f01c3e182da7e49556bcec602e01fb5060026e919d1a05116ca255805",
+ "failedQueryCandidateSha256": "8f3f20a7e72c870cfd1ad46f0aa8b8998ffc1ebf3357911957ed4995758e7520",
+ "correctedQueryCandidateSha256": "86666c8390f0c63ec75b045366494a80d0ecde82924f2bac312afd01675aee56",
+ "scope": "Four Unicode outline test fixture lines and private query literals; production language implementation unchanged. Preserve original successful broader evidence; repeat affected exact-Unicode tests and lint."
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/README.md b/harness/experiments/text-navigation-components-20260911/native-preparation/README.md
new file mode 100644
index 00000000..2ccc625d
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/README.md
@@ -0,0 +1,61 @@
+# Navigation native preparation
+
+Prepared 2026-09-11 by Codex-9e8r6t /root/linux_readiness for Gitea #6. These are input fixtures and read-only oracle results, **not native navigation acceptance**. No GUI, compiler, server or WSL was launched.
+
+## Inputs
+
+- `valid-baseline/project.arcadia`: immutable v7 entry with four files, UTF-8 without BOM and CRLF throughout. `valid-work` is an exact separate copy for GUI use. Two OA activities live in Unicode folder `救援`; `links.arcadia` contains one interaction with source `@assess` and target `@confirm`.
+- `ambiguous-open/project.arcadia`: duplicate `assess` declaration with its own UUID, preserving `confirm`. Copy this directory to a new trial directory before opening or repairing it. Expected draft lookup: two `assess` definitions and one lexical use, explicit row choice; no accepted-model success is assumed.
+- `missing-include-open/project.arcadia`: valid fixture plus `include "missing/not-loaded.arcadia";`; the target is intentionally absent. Copy before a repair trial. Expected loaded-source query scope is partial, with one loaded declaration/use for each alias; navigation must not fetch/create the missing file.
+- `release1-baseline` and `release1-work`: exact copies of all 37 files in `examples/release1`, including its 30 reachable Arcadia files. This is a realistic walkthrough, **not the separate 100-module performance fixture**.
+
+The valid fixture uses stable UUIDs `9e8f0000-0000-4000-8000-000000000001` through `...0004` for project, assess, confirm and handoff respectively. The duplicate has `...0005`. Expected aliases, excluded prose/comment occurrences, exact byte spans and one-based line/UTF-8 byte-column positions are in `expected-locations.json`; those expectations were calculated directly from authored bytes, independently of the application lexer.
+
+Useful valid locations:
+
+| Kind | Path | Line:byte-column | Alias bytes `[start,end)` |
+| --- | --- | --- | --- |
+| Declaration assess | 救援/assess.arcadia | 2:13 | 59..65 |
+| Declaration confirm | 救援/confirm.arcadia | 1:13 | 12..19 |
+| Live assess use | links.arcadia | 2:52 | 137..143 |
+| Live confirm use | links.arcadia | 2:68 | 153..160 |
+
+There are five excluded `@assess` occurrences across descriptions/comments. The quoted Unicode text before live references exercises byte columns that differ from character columns. A use's selection excludes its `@` marker. No scalar-column or normalized-newline position is substituted.
+
+## Native procedure for root
+
+1. Launch the separately identified new GUI using its established private environment. Open `valid-work/project.arcadia` through the real picker. Open `links.arcadia`, put the caret on the live assess use (not the description), press F12, inspect the exact declaration selection and viewport, then Alt+Left to return. Repeat with confirm. Buttons should also work; shortcut handling requires editor focus.
+2. From the assess declaration, Shift+F12 should show exactly one declaration and one lexical use. Prose/comment occurrences must not appear. Inspect Unicode path, locations, scope and any selected source bytes. Opening a unique validated included declaration may navigate directly.
+3. Edit a source draft without Apply, query again and observe explicit choice/draft status. Change an indexed source after a result is shown and verify stale results cannot jump; rerun. Check Back after changing its origin draft restores the current cursor rather than an obsolete selection. Preserve raw screenshots/logs and do not count planned behavior as a pass.
+4. Run separate Open repair trials on fresh copies of the two invalid fixtures. Observe real application admission and loaded-file state before asserting result counts. The negative oracle proves loader rejection, not that the GUI reaches a particular repair panel.
+5. For unchanged-model/source acceptance, use a fresh valid work copy or explicitly revert every intentional draft edit before Save. Compare the immutable baseline against the actual saved directory using the oracle below. Do not compare an intentionally edited draft checkpoint and call its expected mismatch a product defect.
+6. The release1 copy supports a realistic modular trial. A measured 100-module trial remains separate work; no performance result is claimed here.
+
+## Oracle evidence and reproduction
+
+Existing executable: `../staged-source-oracle-20260911/staged-source-authoring-contract.exe`, SHA256 `967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c`. It loads both projects, compares complete decoded Project/identities and exact reachable SourceBundle, and compares all physical `.arcadia` files. Its `same` command exposes no full-model JSON output; the full-model equality claim comes from those assertions, not an exported model artifact.
+
+From repository root, with the existing compatible runtime on a process-local PATH:
+
+```powershell
+$navRepo = (Get-Location).Path
+$navInputs = Join-Path $navRepo '.local/text-navigation-native-20260911-preparation'
+$navOracle = Join-Path $navRepo '.local/staged-source-oracle-20260911/staged-source-authoring-contract.exe'
+$env:PATH = "$navRepo\.local\toolchain\llvm-mingw-20260826-ucrt-x86_64\bin;$env:SystemRoot\System32;$env:SystemRoot"
+& $navOracle same "$navInputs\valid-baseline\project.arcadia" "$navInputs\valid-work\project.arcadia"
+# Preserve stdout, stderr and the actual $LASTEXITCODE for each new checkpoint.
+```
+
+`check.py` is the completed, one-shot preparation check and refuses to overwrite its result records. `prepare.py` likewise refuses existing authored paths; do not rerun either helper over these preserved inputs. Reuse the direct oracle command with fresh log/result names for native checkpoints.
+
+Actual preparation results:
+
+- `valid-same`: exit 0, complete model/identity and 4 reachable/4 disk files equal.
+- `release1-same`: exit 0, complete model/identity and 30 reachable/30 disk files equal.
+- `ambiguous-rejected`: exit 101, expected `duplicate_declaration` loader error.
+- `missing-include-rejected`: exit 101, expected `missing_include` loader error.
+- Initial helper failed before any oracle process because Python environment key `SystemRoot` was absent in that spelling. Corrected to a case-insensitive Windows key lookup; `helper-initial-failure.json` records the scope. No fixture/executable correction was necessary.
+- All 90 fixture files, 592459 bytes, match `fixture-manifest.json` after execution. `check-summary.json` records commands, exits, timing and runtime hash. Timing is oracle execution only, not GUI or navigation performance.
+
+Manifest SHA256: `edd4157fc31823f4494dee91862728b87f12ef51c49b8d1fd86a954bc9ecc1ed`.
+Expected-locations SHA256: `81574d2c9c0aec1657a293af52a469506ccddfa2914e820c61af619d5622837f`.
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/links.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/links.arcadia
new file mode 100644
index 00000000..8f99a9d8
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/links.arcadia
@@ -0,0 +1,4 @@
+interaction handoff "Assessment handoff" id "9e8f0000-0000-4000-8000-000000000004" {
+ description "救援 @assess is prose"; source @assess; target @confirm;
+ // Another comment @assess is excluded.
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/project.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/project.arcadia
new file mode 100644
index 00000000..2771b2bf
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/project.arcadia
@@ -0,0 +1,7 @@
+arcadia 7;
+project navigation_trial "Navigation — 救援" id "9e8f0000-0000-4000-8000-000000000001" {
+ description "Prose @assess must not be a lexical use.";
+ include "救援/assess.arcadia";
+ include "救援/confirm.arcadia";
+ include "links.arcadia";
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/assess.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/assess.arcadia
new file mode 100644
index 00000000..2595a7db
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/assess.arcadia
@@ -0,0 +1,4 @@
+// Comment @assess must not be a lexical use.
+oa_activity assess "Assess 救援 request" id "9e8f0000-0000-4000-8000-000000000002" {
+ description "Keep @assess as prose, not a use.";
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/confirm.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/confirm.arcadia
new file mode 100644
index 00000000..20e53049
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-open/救援/confirm.arcadia
@@ -0,0 +1,2 @@
+oa_activity confirm "Confirm 救援 response" id "9e8f0000-0000-4000-8000-000000000003" {}
+oa_activity assess "Duplicate draft assessment" id "9e8f0000-0000-4000-8000-000000000005" {}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected-result.json b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected-result.json
new file mode 100644
index 00000000..b1509872
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected-result.json
@@ -0,0 +1,14 @@
+{
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\ambiguous-open\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\ambiguous-open\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 101,
+ "expected_success": false,
+ "expectation_met": true,
+ "seconds": 0.031000000000858563,
+ "finished_utc": "2026-09-11T17:45:35.367737+00:00"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected.stderr.log b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected.stderr.log
new file mode 100644
index 00000000..699e1895
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected.stderr.log
@@ -0,0 +1,4 @@
+
+thread 'main' (7504) panicked at harness/staged-source-authoring-contract.rs:76:5:
+Loader errors: [SourceDiagnostic { path: "救援/confirm.arcadia", diagnostic: Diagnostic { severity: Error, code: "duplicate_declaration", object_id: None, field: None, line: 2, column: 1, message: "Duplicate alias 'assess'.", span: Span { start: 92, end: 184 } }, related: [SourceLocation { path: "救援/assess.arcadia", span: Span { start: 47, end: 190 } }] }]
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected.stdout.log b/harness/experiments/text-navigation-components-20260911/native-preparation/ambiguous-rejected.stdout.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/check-summary.json b/harness/experiments/text-navigation-components-20260911/native-preparation/check-summary.json
new file mode 100644
index 00000000..8d05b7bc
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/check-summary.json
@@ -0,0 +1,62 @@
+{
+ "results": [
+ {
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\valid-baseline\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\valid-work\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 0,
+ "expected_success": true,
+ "expectation_met": true,
+ "seconds": 0.07799999999951979,
+ "finished_utc": "2026-09-11T17:45:34.947690+00:00"
+ },
+ {
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\release1-baseline\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\release1-work\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 0,
+ "expected_success": true,
+ "expectation_met": true,
+ "seconds": 0.39099999999962165,
+ "finished_utc": "2026-09-11T17:45:35.339540+00:00"
+ },
+ {
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\ambiguous-open\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\ambiguous-open\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 101,
+ "expected_success": false,
+ "expectation_met": true,
+ "seconds": 0.031000000000858563,
+ "finished_utc": "2026-09-11T17:45:35.367737+00:00"
+ },
+ {
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\missing-include-open\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\missing-include-open\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 101,
+ "expected_success": false,
+ "expectation_met": true,
+ "seconds": 0.030999999999039574,
+ "finished_utc": "2026-09-11T17:45:35.401779+00:00"
+ }
+ ],
+ "all_fixture_bytes_unchanged": true,
+ "runtime_sha256": "f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/check.py b/harness/experiments/text-navigation-components-20260911/native-preparation/check.py
new file mode 100644
index 00000000..63a3b8f0
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/check.py
@@ -0,0 +1,43 @@
+from pathlib import Path
+import hashlib
+import json
+import os
+import subprocess
+import time
+from datetime import datetime, timezone
+
+root = Path(__file__).resolve().parent
+repo = root.parent.parent
+oracle = repo / '.local/staged-source-oracle-20260911/staged-source-authoring-contract.exe'
+expected_hash = '967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c'
+assert hashlib.sha256(oracle.read_bytes()).hexdigest() == expected_hash
+runtime = repo / '.local/toolchain/llvm-mingw-20260826-ucrt-x86_64/bin'
+assert (runtime / 'libunwind.dll').is_file()
+env = os.environ.copy()
+system_root = next(value for key, value in env.items() if key.lower() == 'systemroot')
+env['PATH'] = str(runtime) + ';' + system_root + '/System32;' + system_root
+env['RUST_BACKTRACE'] = '0'
+results = []
+for label, left, right, expected_success in [
+ ('valid-same', 'valid-baseline', 'valid-work', True),
+ ('release1-same', 'release1-baseline', 'release1-work', True),
+ ('ambiguous-rejected', 'ambiguous-open', 'ambiguous-open', False),
+ ('missing-include-rejected', 'missing-include-open', 'missing-include-open', False),
+]:
+ args = [str(oracle), 'same', str(root / left / 'project.arcadia'), str(root / right / 'project.arcadia')]
+ assert not (root / (label + '-result.json')).exists()
+ started = time.monotonic()
+ process = subprocess.run(args, cwd=root, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=45)
+ (root / (label + '.stdout.log')).write_bytes(process.stdout)
+ (root / (label + '.stderr.log')).write_bytes(process.stderr)
+ result = {'command': args, 'oracle_sha256': expected_hash, 'exit': process.returncode,
+ 'expected_success': expected_success, 'expectation_met': (process.returncode == 0) == expected_success,
+ 'seconds': time.monotonic() - started, 'finished_utc': datetime.now(timezone.utc).isoformat()}
+ (root / (label + '-result.json')).write_text(json.dumps(result, indent=2) + '\n', encoding='utf-8')
+ results.append(result)
+ print(label, process.returncode, process.stdout.decode('utf-8', errors='replace').strip())
+manifest = json.loads((root / 'fixture-manifest.json').read_text(encoding='utf-8'))
+assert all(hashlib.sha256((root / entry['path']).read_bytes()).hexdigest() == entry['sha256'] for entry in manifest)
+(root / 'check-summary.json').write_text(json.dumps({'results': results, 'all_fixture_bytes_unchanged': True,
+ 'runtime_sha256': hashlib.sha256((runtime / 'libunwind.dll').read_bytes()).hexdigest()}, indent=2) + '\n', encoding='utf-8')
+assert all(result['expectation_met'] for result in results)
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/expected-locations.json b/harness/experiments/text-navigation-components-20260911/native-preparation/expected-locations.json
new file mode 100644
index 00000000..d7a961dd
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/expected-locations.json
@@ -0,0 +1,385 @@
+{
+ "encoding": "UTF-8 without BOM; every authored fixture newline CRLF",
+ "valid_totals": {
+ "assess": {
+ "declarations": 1,
+ "lexical_uses": 1
+ },
+ "confirm": {
+ "declarations": 1,
+ "lexical_uses": 1
+ }
+ },
+ "ambiguous_totals": {
+ "assess": {
+ "declarations": 2,
+ "lexical_uses": 1
+ }
+ },
+ "locations": [
+ {
+ "fixture": "valid-baseline",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 111,
+ 117
+ ],
+ "byte_offset": 111,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 26
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "lexical-use",
+ "span": [
+ 137,
+ 143
+ ],
+ "byte_offset": 137,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 52
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 187,
+ 193
+ ],
+ "byte_offset": 187,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 25
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "links.arcadia",
+ "alias": "confirm",
+ "kind": "lexical-use",
+ "span": [
+ 153,
+ 160
+ ],
+ "byte_offset": 153,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 68
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "project.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 130,
+ 136
+ ],
+ "byte_offset": 130,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 25
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 12,
+ 18
+ ],
+ "byte_offset": 12,
+ "line_1based": 1,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "declaration",
+ "span": [
+ 59,
+ 65
+ ],
+ "byte_offset": 59,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 158,
+ 164
+ ],
+ "byte_offset": 158,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 24
+ },
+ {
+ "fixture": "valid-baseline",
+ "path": "救援/confirm.arcadia",
+ "alias": "confirm",
+ "kind": "declaration",
+ "span": [
+ 12,
+ 19
+ ],
+ "byte_offset": 12,
+ "line_1based": 1,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 111,
+ 117
+ ],
+ "byte_offset": 111,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 26
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "lexical-use",
+ "span": [
+ 137,
+ 143
+ ],
+ "byte_offset": 137,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 52
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 187,
+ 193
+ ],
+ "byte_offset": 187,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 25
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "links.arcadia",
+ "alias": "confirm",
+ "kind": "lexical-use",
+ "span": [
+ 153,
+ 160
+ ],
+ "byte_offset": 153,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 68
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "project.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 130,
+ 136
+ ],
+ "byte_offset": 130,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 25
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 12,
+ 18
+ ],
+ "byte_offset": 12,
+ "line_1based": 1,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "declaration",
+ "span": [
+ 59,
+ 65
+ ],
+ "byte_offset": 59,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 158,
+ 164
+ ],
+ "byte_offset": 158,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 24
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "救援/confirm.arcadia",
+ "alias": "assess",
+ "kind": "declaration",
+ "span": [
+ 104,
+ 110
+ ],
+ "byte_offset": 104,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "ambiguous-open",
+ "path": "救援/confirm.arcadia",
+ "alias": "confirm",
+ "kind": "declaration",
+ "span": [
+ 12,
+ 19
+ ],
+ "byte_offset": 12,
+ "line_1based": 1,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 111,
+ 117
+ ],
+ "byte_offset": 111,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 26
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "lexical-use",
+ "span": [
+ 137,
+ 143
+ ],
+ "byte_offset": 137,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 52
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "links.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 187,
+ 193
+ ],
+ "byte_offset": 187,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 25
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "links.arcadia",
+ "alias": "confirm",
+ "kind": "lexical-use",
+ "span": [
+ 153,
+ 160
+ ],
+ "byte_offset": 153,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 68
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "project.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 130,
+ 136
+ ],
+ "byte_offset": 130,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 25
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 12,
+ 18
+ ],
+ "byte_offset": 12,
+ "line_1based": 1,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "declaration",
+ "span": [
+ 59,
+ 65
+ ],
+ "byte_offset": 59,
+ "line_1based": 2,
+ "utf8_byte_column_1based": 13
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "救援/assess.arcadia",
+ "alias": "assess",
+ "kind": "excluded-comment-or-string",
+ "span": [
+ 158,
+ 164
+ ],
+ "byte_offset": 158,
+ "line_1based": 3,
+ "utf8_byte_column_1based": 24
+ },
+ {
+ "fixture": "missing-include-open",
+ "path": "救援/confirm.arcadia",
+ "alias": "confirm",
+ "kind": "declaration",
+ "span": [
+ 12,
+ 19
+ ],
+ "byte_offset": 12,
+ "line_1based": 1,
+ "utf8_byte_column_1based": 13
+ }
+ ]
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/fixture-manifest.json b/harness/experiments/text-navigation-components-20260911/native-preparation/fixture-manifest.json
new file mode 100644
index 00000000..8629b696
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/fixture-manifest.json
@@ -0,0 +1,452 @@
+[
+ {
+ "path": "valid-baseline/links.arcadia",
+ "bytes": 211,
+ "sha256": "d3229ecf6300804103eb7f85bd5891c022f6f6c194dbdbbdcb54e9550338dc3b"
+ },
+ {
+ "path": "valid-baseline/project.arcadia",
+ "bytes": 277,
+ "sha256": "79e26836c048a35aaf98b21ff006ae8ac931160abd9913b9e67b542303215414"
+ },
+ {
+ "path": "valid-baseline/救援/assess.arcadia",
+ "bytes": 192,
+ "sha256": "d99593537e67b2dfd293623f467ffbd46af4ea259dffae55d1fe9995c86e6992"
+ },
+ {
+ "path": "valid-baseline/救援/confirm.arcadia",
+ "bytes": 92,
+ "sha256": "591b32db3b1282825750d002d9293cfc450d55252506623302ce36c949fe9919"
+ },
+ {
+ "path": "valid-work/links.arcadia",
+ "bytes": 211,
+ "sha256": "d3229ecf6300804103eb7f85bd5891c022f6f6c194dbdbbdcb54e9550338dc3b"
+ },
+ {
+ "path": "valid-work/project.arcadia",
+ "bytes": 277,
+ "sha256": "79e26836c048a35aaf98b21ff006ae8ac931160abd9913b9e67b542303215414"
+ },
+ {
+ "path": "valid-work/救援/assess.arcadia",
+ "bytes": 192,
+ "sha256": "d99593537e67b2dfd293623f467ffbd46af4ea259dffae55d1fe9995c86e6992"
+ },
+ {
+ "path": "valid-work/救援/confirm.arcadia",
+ "bytes": 92,
+ "sha256": "591b32db3b1282825750d002d9293cfc450d55252506623302ce36c949fe9919"
+ },
+ {
+ "path": "ambiguous-open/links.arcadia",
+ "bytes": 211,
+ "sha256": "d3229ecf6300804103eb7f85bd5891c022f6f6c194dbdbbdcb54e9550338dc3b"
+ },
+ {
+ "path": "ambiguous-open/project.arcadia",
+ "bytes": 277,
+ "sha256": "79e26836c048a35aaf98b21ff006ae8ac931160abd9913b9e67b542303215414"
+ },
+ {
+ "path": "ambiguous-open/救援/assess.arcadia",
+ "bytes": 192,
+ "sha256": "d99593537e67b2dfd293623f467ffbd46af4ea259dffae55d1fe9995c86e6992"
+ },
+ {
+ "path": "ambiguous-open/救援/confirm.arcadia",
+ "bytes": 186,
+ "sha256": "ba3e222bdc7dfa94e4f1b16df091447835da6b5bea8a72701e12e077e77319a6"
+ },
+ {
+ "path": "missing-include-open/links.arcadia",
+ "bytes": 211,
+ "sha256": "d3229ecf6300804103eb7f85bd5891c022f6f6c194dbdbbdcb54e9550338dc3b"
+ },
+ {
+ "path": "missing-include-open/project.arcadia",
+ "bytes": 320,
+ "sha256": "573357995f0a68fef39aa3eef3125e2271242d1ce01607c129624399b77e682e"
+ },
+ {
+ "path": "missing-include-open/救援/assess.arcadia",
+ "bytes": 192,
+ "sha256": "d99593537e67b2dfd293623f467ffbd46af4ea259dffae55d1fe9995c86e6992"
+ },
+ {
+ "path": "missing-include-open/救援/confirm.arcadia",
+ "bytes": 92,
+ "sha256": "591b32db3b1282825750d002d9293cfc450d55252506623302ce36c949fe9919"
+ },
+ {
+ "path": "release1-baseline/evidence/repair-check.md",
+ "bytes": 1419,
+ "sha256": "f4db17bdca4ecbb1bf0caef01b2da5f1f7f798ab3152b648ce6dd8696a6a68e5"
+ },
+ {
+ "path": "release1-baseline/expected-diagnostics.json",
+ "bytes": 288,
+ "sha256": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e"
+ },
+ {
+ "path": "release1-baseline/model.json",
+ "bytes": 134520,
+ "sha256": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727"
+ },
+ {
+ "path": "release1-baseline/model.rs",
+ "bytes": 28762,
+ "sha256": "15f30f6bde655808fe754665c26d560e36f2a9bad340f346b09137d0d05045f5"
+ },
+ {
+ "path": "release1-baseline/oa/activities/activity_oa_restore_work.arcadia",
+ "bytes": 1307,
+ "sha256": "83e73d0a013d5c3e1a4af0d9139ab0cfa362d25a846671fc2dac59181061a9d4"
+ },
+ {
+ "path": "release1-baseline/oa/behavior/process_oa_assessment_procedure.arcadia",
+ "bytes": 697,
+ "sha256": "172c32c044ef4b5bc58a1f4635cc81f48dabb4014479b47dce7b25fedf2dbd0d"
+ },
+ {
+ "path": "release1-baseline/oa/behavior/process_oa_field_procedure.arcadia",
+ "bytes": 1781,
+ "sha256": "d2552dc5383e2e91cb302a790364e197f663c2edc013a2ab1978789825c9c324"
+ },
+ {
+ "path": "release1-baseline/oa/behavior/process_oa_response.arcadia",
+ "bytes": 3225,
+ "sha256": "cf86f46fa5c5d70592ef43f1bc1cb4d968efd8add384ba88036bef6b88aa4abc"
+ },
+ {
+ "path": "release1-baseline/oa/behavior/scenario_oa_conversation.arcadia",
+ "bytes": 1795,
+ "sha256": "b25be524e98a35ebac5e4481b7609c027b32baf9605e8097f807560f94a77ca6"
+ },
+ {
+ "path": "release1-baseline/oa/behavior/scenario_oa_full_scenario.arcadia",
+ "bytes": 1261,
+ "sha256": "00a1edf2c9707a0eb62ac070c2d02467874f2c9c1f273cc6ddb53d6d65999a1c"
+ },
+ {
+ "path": "release1-baseline/oa/behavior/state_machine_oa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "447c9ad1359f4a31ecf975ad07c0b08d8e7e03191fb0858e2fc56f377b223cd5"
+ },
+ {
+ "path": "release1-baseline/oa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "fc8012e5554350b12e1cb80080f8feab80d933c9b33c0f2a7bb500cb7d609936"
+ },
+ {
+ "path": "release1-baseline/oa/model.arcadia",
+ "bytes": 661,
+ "sha256": "f7cf914766ea73ec53b85e32d117ea9a9dba52d3e038246863541ae7069b1a15"
+ },
+ {
+ "path": "release1-baseline/oa/participants.arcadia",
+ "bytes": 1834,
+ "sha256": "951764c14179f27c10e315acf2ea37d1fff54c77b8441e23addd239e8c715ce3"
+ },
+ {
+ "path": "release1-baseline/oa/types.arcadia",
+ "bytes": 788,
+ "sha256": "9d0396fb82dd5733edd8d1cf52f968ca84906927c704f9cf0944a7fae215cf49"
+ },
+ {
+ "path": "release1-baseline/project.arcadia",
+ "bytes": 1958,
+ "sha256": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda"
+ },
+ {
+ "path": "release1-baseline/README.md",
+ "bytes": 11137,
+ "sha256": "113cfb1cf38a98b51bff60442d86a35d636a3d80ef206bece7d53d0aae67f2d5"
+ },
+ {
+ "path": "release1-baseline/sa/behavior/process_sa_assessment_procedure.arcadia",
+ "bytes": 706,
+ "sha256": "73928ced61c06cbc08728d7956676a50fd4c05aa5b7f1ff4f29ce27a0b0f2088"
+ },
+ {
+ "path": "release1-baseline/sa/behavior/process_sa_field_procedure.arcadia",
+ "bytes": 1790,
+ "sha256": "886d462af83110cbfa55dd232a667ea1446761234471357cbb878794e98e8768"
+ },
+ {
+ "path": "release1-baseline/sa/behavior/process_sa_response.arcadia",
+ "bytes": 3234,
+ "sha256": "041785fc22842423822431bd5e6e8d16094ebd0716965e7c765fb8c1d7549e8a"
+ },
+ {
+ "path": "release1-baseline/sa/behavior/scenario_sa_conversation.arcadia",
+ "bytes": 2127,
+ "sha256": "97276f5757ee9aa12a0dbd9d3ccd58174b3091162bb8bcce357cec20a45f5d85"
+ },
+ {
+ "path": "release1-baseline/sa/behavior/scenario_sa_full_scenario.arcadia",
+ "bytes": 1256,
+ "sha256": "e12fc406b99d60a2f7768aaa0b25ba560e8605f0b8e37f3a7cdd3b8443562b74"
+ },
+ {
+ "path": "release1-baseline/sa/behavior/state_machine_sa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "05ce29af99424c491c47fe2450dc6f5f8ce995511c01ec7e17bb5855be3c9736"
+ },
+ {
+ "path": "release1-baseline/sa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "b6912fb3ece439912345fa71724acd462e716a1e5a88203a1058a5c3b2c20a4f"
+ },
+ {
+ "path": "release1-baseline/sa/functions/function_sa_audit_thresholds.arcadia",
+ "bytes": 297,
+ "sha256": "e5a04325d52183956dca814dd90f4e56c96c6b7db0f4f202f203ecb49c3e0f4f"
+ },
+ {
+ "path": "release1-baseline/sa/functions/function_sa_restore_work.arcadia",
+ "bytes": 2576,
+ "sha256": "fddeb67308051325041fee641fe758ddf43345b7c47b66d433616ad07fdccdd6"
+ },
+ {
+ "path": "release1-baseline/sa/model.arcadia",
+ "bytes": 661,
+ "sha256": "d431509251acb859fbb98733b6d18add952495e10a78703fbe8f31e40bc083c4"
+ },
+ {
+ "path": "release1-baseline/sa/participants.arcadia",
+ "bytes": 4012,
+ "sha256": "780d2eb08b217b67b55febb3d34846f727b8ee9018df6c5741748647e36b2ff8"
+ },
+ {
+ "path": "release1-baseline/sa/types.arcadia",
+ "bytes": 1227,
+ "sha256": "101fc0fbd99f1101514aa7b6da4f8e535aae0bb94c9e2b9e7e6c151135b28660"
+ },
+ {
+ "path": "release1-baseline/traceability.arcadia",
+ "bytes": 29506,
+ "sha256": "48a96f543456c9b5a5798fc795594e4119147fe45f11a6f109f141b7f514560f"
+ },
+ {
+ "path": "release1-baseline/transition-before.json",
+ "bytes": 35801,
+ "sha256": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d"
+ },
+ {
+ "path": "release1-baseline/transition-request.json",
+ "bytes": 4276,
+ "sha256": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a"
+ },
+ {
+ "path": "release1-baseline/views/diagram_oa_allocation_view.arcadia",
+ "bytes": 2176,
+ "sha256": "dfc0eb61e0e2aa5bda45524571fbfa3494f44ce19696f3e15e07438074a07bc7"
+ },
+ {
+ "path": "release1-baseline/views/diagram_sa_allocation_view.arcadia",
+ "bytes": 2173,
+ "sha256": "b78b43a26a82efb8fbd5374b09c329fa341f57b29f635d397b9ef8aede71d389"
+ },
+ {
+ "path": "release1-baseline/views/diagram_sa_capability_view.arcadia",
+ "bytes": 1620,
+ "sha256": "75e8dc9fb30f25d6f8fd3c6b595593ab4f98958a25798f961e336e0fac557649"
+ },
+ {
+ "path": "release1-baseline/views/diagram_sa_service_view.arcadia",
+ "bytes": 2209,
+ "sha256": "b08c84f3ea633e7c78dfdad3272f75415f6256cf3614b6d99bfd4f0269076c9b"
+ },
+ {
+ "path": "release1-baseline/views/diagram_sa_state_view.arcadia",
+ "bytes": 1931,
+ "sha256": "5f42f4c605e57b817d8400ed21f2b14bfc4e1592aebe941ff113767504c2b869"
+ },
+ {
+ "path": "release1-work/evidence/repair-check.md",
+ "bytes": 1419,
+ "sha256": "f4db17bdca4ecbb1bf0caef01b2da5f1f7f798ab3152b648ce6dd8696a6a68e5"
+ },
+ {
+ "path": "release1-work/expected-diagnostics.json",
+ "bytes": 288,
+ "sha256": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e"
+ },
+ {
+ "path": "release1-work/model.json",
+ "bytes": 134520,
+ "sha256": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727"
+ },
+ {
+ "path": "release1-work/model.rs",
+ "bytes": 28762,
+ "sha256": "15f30f6bde655808fe754665c26d560e36f2a9bad340f346b09137d0d05045f5"
+ },
+ {
+ "path": "release1-work/oa/activities/activity_oa_restore_work.arcadia",
+ "bytes": 1307,
+ "sha256": "83e73d0a013d5c3e1a4af0d9139ab0cfa362d25a846671fc2dac59181061a9d4"
+ },
+ {
+ "path": "release1-work/oa/behavior/process_oa_assessment_procedure.arcadia",
+ "bytes": 697,
+ "sha256": "172c32c044ef4b5bc58a1f4635cc81f48dabb4014479b47dce7b25fedf2dbd0d"
+ },
+ {
+ "path": "release1-work/oa/behavior/process_oa_field_procedure.arcadia",
+ "bytes": 1781,
+ "sha256": "d2552dc5383e2e91cb302a790364e197f663c2edc013a2ab1978789825c9c324"
+ },
+ {
+ "path": "release1-work/oa/behavior/process_oa_response.arcadia",
+ "bytes": 3225,
+ "sha256": "cf86f46fa5c5d70592ef43f1bc1cb4d968efd8add384ba88036bef6b88aa4abc"
+ },
+ {
+ "path": "release1-work/oa/behavior/scenario_oa_conversation.arcadia",
+ "bytes": 1795,
+ "sha256": "b25be524e98a35ebac5e4481b7609c027b32baf9605e8097f807560f94a77ca6"
+ },
+ {
+ "path": "release1-work/oa/behavior/scenario_oa_full_scenario.arcadia",
+ "bytes": 1261,
+ "sha256": "00a1edf2c9707a0eb62ac070c2d02467874f2c9c1f273cc6ddb53d6d65999a1c"
+ },
+ {
+ "path": "release1-work/oa/behavior/state_machine_oa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "447c9ad1359f4a31ecf975ad07c0b08d8e7e03191fb0858e2fc56f377b223cd5"
+ },
+ {
+ "path": "release1-work/oa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "fc8012e5554350b12e1cb80080f8feab80d933c9b33c0f2a7bb500cb7d609936"
+ },
+ {
+ "path": "release1-work/oa/model.arcadia",
+ "bytes": 661,
+ "sha256": "f7cf914766ea73ec53b85e32d117ea9a9dba52d3e038246863541ae7069b1a15"
+ },
+ {
+ "path": "release1-work/oa/participants.arcadia",
+ "bytes": 1834,
+ "sha256": "951764c14179f27c10e315acf2ea37d1fff54c77b8441e23addd239e8c715ce3"
+ },
+ {
+ "path": "release1-work/oa/types.arcadia",
+ "bytes": 788,
+ "sha256": "9d0396fb82dd5733edd8d1cf52f968ca84906927c704f9cf0944a7fae215cf49"
+ },
+ {
+ "path": "release1-work/project.arcadia",
+ "bytes": 1958,
+ "sha256": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda"
+ },
+ {
+ "path": "release1-work/README.md",
+ "bytes": 11137,
+ "sha256": "113cfb1cf38a98b51bff60442d86a35d636a3d80ef206bece7d53d0aae67f2d5"
+ },
+ {
+ "path": "release1-work/sa/behavior/process_sa_assessment_procedure.arcadia",
+ "bytes": 706,
+ "sha256": "73928ced61c06cbc08728d7956676a50fd4c05aa5b7f1ff4f29ce27a0b0f2088"
+ },
+ {
+ "path": "release1-work/sa/behavior/process_sa_field_procedure.arcadia",
+ "bytes": 1790,
+ "sha256": "886d462af83110cbfa55dd232a667ea1446761234471357cbb878794e98e8768"
+ },
+ {
+ "path": "release1-work/sa/behavior/process_sa_response.arcadia",
+ "bytes": 3234,
+ "sha256": "041785fc22842423822431bd5e6e8d16094ebd0716965e7c765fb8c1d7549e8a"
+ },
+ {
+ "path": "release1-work/sa/behavior/scenario_sa_conversation.arcadia",
+ "bytes": 2127,
+ "sha256": "97276f5757ee9aa12a0dbd9d3ccd58174b3091162bb8bcce357cec20a45f5d85"
+ },
+ {
+ "path": "release1-work/sa/behavior/scenario_sa_full_scenario.arcadia",
+ "bytes": 1256,
+ "sha256": "e12fc406b99d60a2f7768aaa0b25ba560e8605f0b8e37f3a7cdd3b8443562b74"
+ },
+ {
+ "path": "release1-work/sa/behavior/state_machine_sa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "05ce29af99424c491c47fe2450dc6f5f8ce995511c01ec7e17bb5855be3c9736"
+ },
+ {
+ "path": "release1-work/sa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "b6912fb3ece439912345fa71724acd462e716a1e5a88203a1058a5c3b2c20a4f"
+ },
+ {
+ "path": "release1-work/sa/functions/function_sa_audit_thresholds.arcadia",
+ "bytes": 297,
+ "sha256": "e5a04325d52183956dca814dd90f4e56c96c6b7db0f4f202f203ecb49c3e0f4f"
+ },
+ {
+ "path": "release1-work/sa/functions/function_sa_restore_work.arcadia",
+ "bytes": 2576,
+ "sha256": "fddeb67308051325041fee641fe758ddf43345b7c47b66d433616ad07fdccdd6"
+ },
+ {
+ "path": "release1-work/sa/model.arcadia",
+ "bytes": 661,
+ "sha256": "d431509251acb859fbb98733b6d18add952495e10a78703fbe8f31e40bc083c4"
+ },
+ {
+ "path": "release1-work/sa/participants.arcadia",
+ "bytes": 4012,
+ "sha256": "780d2eb08b217b67b55febb3d34846f727b8ee9018df6c5741748647e36b2ff8"
+ },
+ {
+ "path": "release1-work/sa/types.arcadia",
+ "bytes": 1227,
+ "sha256": "101fc0fbd99f1101514aa7b6da4f8e535aae0bb94c9e2b9e7e6c151135b28660"
+ },
+ {
+ "path": "release1-work/traceability.arcadia",
+ "bytes": 29506,
+ "sha256": "48a96f543456c9b5a5798fc795594e4119147fe45f11a6f109f141b7f514560f"
+ },
+ {
+ "path": "release1-work/transition-before.json",
+ "bytes": 35801,
+ "sha256": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d"
+ },
+ {
+ "path": "release1-work/transition-request.json",
+ "bytes": 4276,
+ "sha256": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a"
+ },
+ {
+ "path": "release1-work/views/diagram_oa_allocation_view.arcadia",
+ "bytes": 2176,
+ "sha256": "dfc0eb61e0e2aa5bda45524571fbfa3494f44ce19696f3e15e07438074a07bc7"
+ },
+ {
+ "path": "release1-work/views/diagram_sa_allocation_view.arcadia",
+ "bytes": 2173,
+ "sha256": "b78b43a26a82efb8fbd5374b09c329fa341f57b29f635d397b9ef8aede71d389"
+ },
+ {
+ "path": "release1-work/views/diagram_sa_capability_view.arcadia",
+ "bytes": 1620,
+ "sha256": "75e8dc9fb30f25d6f8fd3c6b595593ab4f98958a25798f961e336e0fac557649"
+ },
+ {
+ "path": "release1-work/views/diagram_sa_service_view.arcadia",
+ "bytes": 2209,
+ "sha256": "b08c84f3ea633e7c78dfdad3272f75415f6256cf3614b6d99bfd4f0269076c9b"
+ },
+ {
+ "path": "release1-work/views/diagram_sa_state_view.arcadia",
+ "bytes": 1931,
+ "sha256": "5f42f4c605e57b817d8400ed21f2b14bfc4e1592aebe941ff113767504c2b869"
+ }
+]
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/helper-initial-failure.json b/harness/experiments/text-navigation-components-20260911/native-preparation/helper-initial-failure.json
new file mode 100644
index 00000000..b387594a
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/helper-initial-failure.json
@@ -0,0 +1,6 @@
+{
+ "scope": "check.py before any oracle process",
+ "exception": "KeyError: SystemRoot",
+ "exit": 1,
+ "correction": "Use case-insensitive Windows environment-key lookup; no fixture or oracle change."
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/links.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/links.arcadia
new file mode 100644
index 00000000..8f99a9d8
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/links.arcadia
@@ -0,0 +1,4 @@
+interaction handoff "Assessment handoff" id "9e8f0000-0000-4000-8000-000000000004" {
+ description "救援 @assess is prose"; source @assess; target @confirm;
+ // Another comment @assess is excluded.
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/project.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/project.arcadia
new file mode 100644
index 00000000..2d5c664f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/project.arcadia
@@ -0,0 +1,8 @@
+arcadia 7;
+project navigation_trial "Navigation — 救援" id "9e8f0000-0000-4000-8000-000000000001" {
+ description "Prose @assess must not be a lexical use.";
+ include "救援/assess.arcadia";
+ include "救援/confirm.arcadia";
+ include "links.arcadia";
+ include "missing/not-loaded.arcadia";
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/assess.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/assess.arcadia
new file mode 100644
index 00000000..2595a7db
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/assess.arcadia
@@ -0,0 +1,4 @@
+// Comment @assess must not be a lexical use.
+oa_activity assess "Assess 救援 request" id "9e8f0000-0000-4000-8000-000000000002" {
+ description "Keep @assess as prose, not a use.";
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/confirm.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/confirm.arcadia
new file mode 100644
index 00000000..2d3ec804
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-open/救援/confirm.arcadia
@@ -0,0 +1 @@
+oa_activity confirm "Confirm 救援 response" id "9e8f0000-0000-4000-8000-000000000003" {}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected-result.json b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected-result.json
new file mode 100644
index 00000000..51a5b9aa
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected-result.json
@@ -0,0 +1,14 @@
+{
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\missing-include-open\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\missing-include-open\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 101,
+ "expected_success": false,
+ "expectation_met": true,
+ "seconds": 0.030999999999039574,
+ "finished_utc": "2026-09-11T17:45:35.401779+00:00"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected.stderr.log b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected.stderr.log
new file mode 100644
index 00000000..32abcecd
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected.stderr.log
@@ -0,0 +1,4 @@
+
+thread 'main' (7868) panicked at harness/staged-source-authoring-contract.rs:76:5:
+Loader errors: [SourceDiagnostic { path: "project.arcadia", diagnostic: Diagnostic { severity: Error, code: "missing_include", object_id: None, field: None, line: 7, column: 5, message: "Cannot load 'missing/not-loaded.arcadia': Source file operation failed at \\\\?\\C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\missing-include-open\\missing\\not-loaded.arcadia: The system cannot find the path specified. (os error 3)", span: Span { start: 278, end: 315 } }, related: [] }]
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected.stdout.log b/harness/experiments/text-navigation-components-20260911/native-preparation/missing-include-rejected.stdout.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/prepare.py b/harness/experiments/text-navigation-components-20260911/native-preparation/prepare.py
new file mode 100644
index 00000000..39ac2c10
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/prepare.py
@@ -0,0 +1,98 @@
+from pathlib import Path
+import hashlib
+import json
+import shutil
+
+ROOT = Path(__file__).resolve().parent
+REPO = ROOT.parent.parent
+
+
+def write_source(relative, text):
+ path = ROOT / relative
+ assert not path.exists(), path
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_bytes(text.replace('\r\n', '\n').replace('\n', '\r\n').encode('utf-8'))
+
+
+files = {
+ 'project.arcadia': '''arcadia 7;
+project navigation_trial "Navigation — 救援" id "9e8f0000-0000-4000-8000-000000000001" {
+ description "Prose @assess must not be a lexical use.";
+ include "救援/assess.arcadia";
+ include "救援/confirm.arcadia";
+ include "links.arcadia";
+}
+''',
+ '救援/assess.arcadia': '''// Comment @assess must not be a lexical use.
+oa_activity assess "Assess 救援 request" id "9e8f0000-0000-4000-8000-000000000002" {
+ description "Keep @assess as prose, not a use.";
+}
+''',
+ '救援/confirm.arcadia': '''oa_activity confirm "Confirm 救援 response" id "9e8f0000-0000-4000-8000-000000000003" {}
+''',
+ 'links.arcadia': '''interaction handoff "Assessment handoff" id "9e8f0000-0000-4000-8000-000000000004" {
+ description "救援 @assess is prose"; source @assess; target @confirm;
+ // Another comment @assess is excluded.
+}
+''',
+}
+for name, text in files.items():
+ write_source('valid-baseline/' + name, text)
+shutil.copytree(ROOT / 'valid-baseline', ROOT / 'valid-work')
+for case in ['ambiguous-open', 'missing-include-open']:
+ for name, text in files.items():
+ if case == 'ambiguous-open' and name == '救援/confirm.arcadia':
+ text += 'oa_activity assess "Duplicate draft assessment" id "9e8f0000-0000-4000-8000-000000000005" {}\n'
+ if case == 'missing-include-open' and name == 'project.arcadia':
+ text = text.replace(' include "links.arcadia";', ' include "links.arcadia";\n include "missing/not-loaded.arcadia";')
+ write_source(case + '/' + name, text)
+
+# A realistic existing teaching model, copied without changing source bytes.
+shutil.copytree(REPO / 'examples/release1', ROOT / 'release1-baseline')
+shutil.copytree(ROOT / 'release1-baseline', ROOT / 'release1-work')
+
+
+def position(raw, offset):
+ line_start = raw.rfind(b'\n', 0, offset) + 1
+ return {'byte_offset': offset, 'line_1based': raw.count(b'\n', 0, offset) + 1,
+ 'utf8_byte_column_1based': offset - line_start + 1}
+
+
+locations = []
+for folder in ['valid-baseline', 'ambiguous-open', 'missing-include-open']:
+ for path in sorted((ROOT / folder).rglob('*.arcadia')):
+ raw = path.read_bytes()
+ text = raw.decode('utf-8')
+ assert b'\n' not in raw.replace(b'\r\n', b'')
+ for alias in ['assess', 'confirm']:
+ token = alias.encode('ascii')
+ start = 0
+ while (found := raw.find(token, start)) != -1:
+ start = found + len(token)
+ line_start = raw.rfind(b'\n', 0, found) + 1
+ prefix = raw[line_start:found]
+ # Explicit fixture expectations, independent of the application lexer.
+ if prefix == b'oa_activity ':
+ kind = 'declaration'
+ elif prefix.endswith(b'source @') or prefix.endswith(b'target @'):
+ kind = 'lexical-use'
+ elif found > 0 and raw[found-1:found] == b'@':
+ kind = 'excluded-comment-or-string'
+ else:
+ continue
+ locations.append({'fixture': folder, 'path': path.relative_to(ROOT / folder).as_posix(),
+ 'alias': alias, 'kind': kind, 'span': [found, start],
+ **position(raw, found)})
+expected = {'encoding': 'UTF-8 without BOM; every authored fixture newline CRLF',
+ 'valid_totals': {'assess': {'declarations': 1, 'lexical_uses': 1},
+ 'confirm': {'declarations': 1, 'lexical_uses': 1}},
+ 'ambiguous_totals': {'assess': {'declarations': 2, 'lexical_uses': 1}},
+ 'locations': locations}
+(ROOT / 'expected-locations.json').write_text(json.dumps(expected, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
+manifest = [{'path': p.relative_to(ROOT).as_posix(), 'bytes': p.stat().st_size,
+ 'sha256': hashlib.sha256(p.read_bytes()).hexdigest()}
+ for folder in ['valid-baseline', 'valid-work', 'ambiguous-open', 'missing-include-open', 'release1-baseline', 'release1-work']
+ for p in sorted((ROOT / folder).rglob('*')) if p.is_file()]
+(ROOT / 'fixture-manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
+print(json.dumps({'files': len(manifest), 'bytes': sum(p['bytes'] for p in manifest),
+ 'expected_locations': len(locations)}))
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/readback-print-initial-failure.json b/harness/experiments/text-navigation-components-20260911/native-preparation/readback-print-initial-failure.json
new file mode 100644
index 00000000..16e48c59
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/readback-print-initial-failure.json
@@ -0,0 +1,6 @@
+{
+ "scope": "read-only expectation summary printing; fixture bytes and JSON decoding unaffected",
+ "exception": "UnicodeEncodeError: cp1252 stdout cannot print Unicode subfolder",
+ "exit": 1,
+ "correction": "Explicit UTF-8 stdout on readback helper"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/README.md b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/README.md
new file mode 100644
index 00000000..f6f883d1
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/README.md
@@ -0,0 +1,86 @@
+# Restore safe water pressure
+
+This source-7 project follows one engineering question: after a pressure alarm, can the control room restore service remotely, or must a technician repair a valve? It connects the operational need to an explicitly chosen coordination-service boundary. Allow about 25 minutes for the walkthrough.
+
+Copy the whole `examples/release1` folder to your working location, then open the copied `project.arcadia` in Arcadia Studio. Keep the folder together: the entry includes 29 modules, and the evidence link is relative to it. If you instead use Save As to a new folder, it copies model source files only: also copy `evidence/` beside the new entry and keep this README available. Save As does not copy external artifact targets. Existing older examples remain useful migration exercises.
+
+The numbers, guards and states describe an illustrative design. The model does not simulate hydraulics, execute a control algorithm, or establish the safety of an actual water network.
+
+## 1. Start with responsibility, not equipment
+
+Open saved view **1 · OA responsibility and repair traffic**. The **Control room** assesses the alarm, resets the valve and confirms pressure. The **Field technician** performs field repair. The parent activity **Restore safe pressure** groups these leaf activities; responsibility is allocated to the leaves.
+
+Inspect **Technician service conversation** (`oa_contact`). This unoriented CommunicationMean connects operational participants. **Request field repair** and **Report repair result** are Interactions between their allocated activities. Each Interaction is explicitly allocated to the communication mean. No OA port or implementation component has been introduced.
+
+Inspect the **Valve repair** exchange item and its `pressure` parameter. The parameter has direction InOut, Value storage, multiplicity 1..1, and a real-valued type bounded from 0 to 600 with unit text `kPa`. The unit documents the contract; no conversion is inferred.
+
+## 2. Follow the alternative and its reusable procedure
+
+Open process **Handle a pressure alarm** (`oa_response`) in the behavior workspace. Its first step references **Assess an alarm procedure** (`oa_assessment_procedure`), whose Action uses the assessment activity and whose ChainInvolvement supports **Assess an alarm**. Follow that reference to the Choice in the main process. The guarded branch allows an authorized remote reset; the `else` branch references **Field repair procedure** (`oa_field_procedure`). Both paths merge before confirmation. The explicit SequenceLinks define flow; the list of steps alone does not imply those branches.
+
+Open the referenced procedure. It contains request exchange, repair action, then report exchange. The parent process reuses that procedure by identity. It does not copy its actions. The main process contributes to the restoration capability.
+
+Open scenario **Repair request and measured reply** (`oa_conversation`). The Control room calls the technician using the communication mean, supplying `420.0`. The technician's correlated Reply returns `320.0` on the reversed participant pair. The reply points to the actual Call. The enclosing scenario (`oa_full_scenario`) reuses this conversation with two explicit lifeline bindings.
+
+The mission **Maintain safe water supply** links to the restoration capability, which includes the assessment capability. The repair conversation has the restoration context: it begins after assessment and coordinates repair. The assessment procedure supplies the separate assessment behavior support.
+
+## 3. Examine the explicit SA engineering decisions
+
+Open **2 · SA service boundary and functions**. The chosen System is **Pressure coordination service**; the technician remains outside as a SystemActor. OA activities have traced SA functions. The communication mean has a traced SA BoundaryExchange (`sa_contact`), with two explicitly chosen Standard component ports. The two Interactions have traced FunctionalExchanges with directional function ports.
+
+Inspect **Valve repair service** (`sa_repair_interface`), its synchronous Operation acceptance, and the client Requires / provider Provides links. Four PortAllocations connect the function ports to their component ports; two ExchangeAllocations connect functional traffic to the boundary exchange. These are explicit contract refinements. An operational channel cannot choose the technical interface or ports on the engineer's behalf.
+
+The generator performs the real OA→SA transition. `transition-before.json` is the OA model plus the two intentionally named SA participants. `transition-request.json` contains their explicit mappings and both component-port Create decisions. It deliberately leaves the operational ExchangeAllocations out of this first transition: those allocations require the later reviewed SA interface and port refinements. Omitting these allocations is explicit, not silently guessed.
+
+For a fresh transition exercise, import `transition-before.json`, select the OA content, map Control room to the existing System and Field technician to the existing SystemActor, and review both communication endpoint ports. The supplied request is the exact programmatic counterpart. The generated final project then adds the interface/port allocations, views, evidence and one unfinished function. Fresh transition UUIDs are normalized by the generator for stable published files, so an interactive replay is semantically comparable rather than byte-identical to `model.json`.
+
+## 4. Compare views without changing the architecture
+
+Switch between **2 · SA service boundary and functions** and **3 · SA service conversation only**. They place the same semantic identities. The focused view uses the participant exchange category, an explicitly empty work-category selection, and NameAndItems labels. It shows the boundary conversation and hides functional traffic. The underlying exchanges and their port positions remain unchanged.
+
+Open View properties to inspect the selections. Clearing a filter does not delete a relation. The category is an engineering collection of existing exchanges; it is not a replacement exchange. **4 · SA mission and included capability** supplies a separate goal view.
+
+Open **5 · SA pressure supervision** in the state workspace. Follow the initial state to monitoring, the typed high-pressure Change event into waiting, and the guarded return. Waiting has an Entry action linked to the assessment function. This is an event/action relationship, not free prose embedded in a transition label.
+
+## 5. Investigate the deliberate warning
+
+Model checks should report exactly one warning: `work_without_behavior_context` on **Audit pressure thresholds** (`sa_audit_thresholds`). It has a responsible System but no process/chain or scenario use. `expected-diagnostics.json` records the expected located issue.
+
+Decide whether threshold auditing belongs in the response process, in a separate maintenance behavior, or outside the intended model. Add the appropriate behavior use or remove the explicitly out-of-scope function. Merely hiding it in a view would not resolve the coverage question. The supplied model intentionally leaves this decision open.
+
+## 6. Make one reviewed change and inspect its source
+
+1. In OA, rename **Confirm safe pressure** (`oa_confirm`) to **Confirm stable pressure** and Apply the edit.
+2. Open semantic OA→SA review for that activity and its existing function counterpart (`sa_confirm`). Review the Header Name facet and choose Take source. Preview, then Apply the reviewed change.
+3. Confirm that the SA function name changed while the deliberately different System name remains **Pressure coordination service**.
+4. In Source, inspect `sa/functions/function_sa_restore_work.arcadia` and `traceability.arcadia`. The counterpart name and review history change. Unrelated modules retain their text. Use Undo/Redo in this editing session to inspect the reviewed transaction; then Save and reopen to check persistence. Undo history is session-local; reopening restores the saved model, not the previous undo stack.
+
+The integration oracle executes this exact semantic review and source update. It separately proves that changing the request Interaction to target a Control-room-owned activity violates the communication allocation's performer pairing: command and source-update paths reject the invalid candidate together.
+
+## Source, service and reproducibility
+
+| File or folder | Purpose |
+|---|---|
+| `project.arcadia` | Openable source-7 entry with all module includes |
+| `oa/`, `sa/` | Participants, work, data, capabilities and reusable behavior |
+| `views/` | Five saved views; filters and placements are presentation state |
+| `traceability.arcadia` | OA↔SA correspondence and review history |
+| `model.json` | Current schema-7 interchange of the final accepted model |
+| `transition-before.json`, `transition-request.json` | Explicit first-transition replay inputs |
+| `evidence/repair-check.md` | Local teaching evidence linked from the SA conversation |
+| `model.rs` | Deterministic lesson builder shared by generator and tests |
+
+The REST routes are documented in [the API reference](../../docs/api.rst). Import `model.json` with `POST /api/v1/projects/import`; query the returned resource's `/model` and `/validation`. Expect schema 7 and the one warning above. JSON import provides canonical source; to retain the exact modular source layout, use `/api/v1/projects/import-source-project` with a SourceBundle containing entry `project.arcadia` and only the 30 reachable `.arcadia` file strings, keyed by slash-separated relative paths. Neither route reads the artifact target from your local filesystem.
+
+For transition replay, import `transition-before.json` instead. Submit `transition-request.json` to that baseline resource's `/transitions/preview` with its current ETag in `If-Match`. Review the returned plan, then follow the documented `/transitions/apply` protocol. Use the baseline resource for this exercise: the final model already contains the mapped records and subsequent refinements. This tutorial's automated evidence is core/source validation; it does not claim a separate native or HTTP acceptance run of this particular example.
+
+From the repository root, with the supported Rust toolchain available:
+
+```text
+cargo run --locked -p oa-core --example generate_release1 -- examples/release1
+cargo test --locked -p oa-core --test release1_teaching
+```
+
+Regeneration deliberately overwrites the generated source and JSON in the selected output folder. Copy the lesson before experimenting. No old examples are rewritten. The tests check deterministic regeneration, exact source/interchange parity, explicit transport and reply direction, behavior reuse, view identity/geometry, atomic rejection, and a reviewed name change preserving unrelated modules.
+
+The available SVG export is for architecture views. Faithful process, scenario and state-scene SVG export is outside this first-release export scope; the interactive behavior workspace is the authoritative rendering for this exercise.
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/evidence/repair-check.md b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/evidence/repair-check.md
new file mode 100644
index 00000000..b3e9ddf8
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/evidence/repair-check.md
@@ -0,0 +1,15 @@
+# Repair conversation acceptance notes
+
+Revision: lesson-1. Linked from `sa_conversation` by `repair_test_evidence`, relation Verifies, fragment `acceptance`.
+
+## Acceptance
+
+This is a reproducible teaching check, not field-test evidence for a real valve.
+
+- The Control room calls the Field technician across the operational channel; the correlated Reply reverses the actual Call pair.
+- The SA conversation uses the selected service boundary and explicit Standard client/provider ports. A synchronous operation interface and its Requires/Provides links describe the contract.
+- Both request and report functional exchanges are allocated through matching function/component ports and the boundary exchange.
+- The example carries input/output pressure values `420.0` and `320.0` against the declared 0..600 real type. Values and unit text are descriptive model data; no hydraulic simulation has run.
+- The remote reset and field-repair alternatives join before pressure confirmation. The field procedure and scenario conversation are reused through identities and explicit bindings.
+
+Run `cargo test --locked -p oa-core --test release1_teaching` from the repository root to reproduce the core/source oracles. The intentionally unfinished threshold-audit function remains a located coverage warning. Actual equipment safety assessment, executable control validation and field measurements are separate engineering work.
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/expected-diagnostics.json b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/expected-diagnostics.json
new file mode 100644
index 00000000..e93a7fa7
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/expected-diagnostics.json
@@ -0,0 +1,9 @@
+[
+ {
+ "code": "work_without_behavior_context",
+ "element": "4149a872-3440-59de-9879-8981e78d27ab",
+ "field": null,
+ "message": "Include this leaf activity/function in a process/chain or scenario through a work action or exchange occurrence.",
+ "severity": "Warning"
+ }
+]
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.json b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.json
new file mode 100644
index 00000000..dd61c402
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.json
@@ -0,0 +1,4401 @@
+{
+ "alias": "pressure_restoration",
+ "artifact_links": {
+ "f5922bf7-05d8-590f-86e9-1d09f37f4fa6": {
+ "alias": "repair_test_evidence",
+ "description": "A local teaching artifact, not proof of a real valve's safety.",
+ "fragment": "acceptance",
+ "id": "f5922bf7-05d8-590f-86e9-1d09f37f4fa6",
+ "name": "Repair conversation acceptance notes",
+ "owner": "730b27ca-b039-5a3b-8dc6-03418f9061b3",
+ "relation": "Verifies",
+ "revision": "lesson-1",
+ "uri": "evidence/repair-check.md"
+ }
+ },
+ "description": "A controller either resets a valve remotely or requests field repair. Compare the operational need with the explicitly chosen coordination-service boundary. Start with README.md beside project.arcadia. Units and guards are descriptive; this model does not simulate a real water network.",
+ "diagrams": {
+ "103ea62c-ac56-5827-94e7-cf09bca96ccc": {
+ "alias": "oa_allocation_view",
+ "description": "Teaching view: positions and filters do not change ownership or exchange identities.",
+ "id": "103ea62c-ac56-5827-94e7-cf09bca96ccc",
+ "layer": "Oa",
+ "name": "1 · OA responsibility and repair traffic",
+ "nodes": {
+ "202ebe37-ddb4-5099-8ce9-ec3e895ab9e9": {
+ "alias": "oa_allocation_view_oa_repair",
+ "collapsed": false,
+ "element": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "id": "202ebe37-ddb4-5099-8ce9-ec3e895ab9e9",
+ "position": {
+ "x": 890.0,
+ "y": 170.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "3191e05c-1c7f-5f0e-beb0-5f3bded54c48": {
+ "alias": "oa_allocation_view_oa_control",
+ "collapsed": false,
+ "element": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "id": "3191e05c-1c7f-5f0e-beb0-5f3bded54c48",
+ "position": {
+ "x": 60.0,
+ "y": 80.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "5ae1c46d-59a3-50fd-8cc3-b292df21f951": {
+ "alias": "oa_allocation_view_oa_assess",
+ "collapsed": false,
+ "element": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "id": "5ae1c46d-59a3-50fd-8cc3-b292df21f951",
+ "position": {
+ "x": 100.0,
+ "y": 170.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "7066f78c-9736-5349-b94a-0587331b980e": {
+ "alias": "oa_allocation_view_oa_reset",
+ "collapsed": false,
+ "element": "d0c8be87-d238-503a-a549-7558a210507f",
+ "id": "7066f78c-9736-5349-b94a-0587331b980e",
+ "position": {
+ "x": 100.0,
+ "y": 330.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "d27f28ef-f10a-5355-9a05-6f2fd39b7de6": {
+ "alias": "oa_allocation_view_oa_technician",
+ "collapsed": false,
+ "element": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "id": "d27f28ef-f10a-5355-9a05-6f2fd39b7de6",
+ "position": {
+ "x": 850.0,
+ "y": 80.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "edd74060-ac61-5450-a57c-a6577daa5d50": {
+ "alias": "oa_allocation_view_oa_confirm",
+ "collapsed": false,
+ "element": "727c0b5c-8db6-5eb7-83d6-735519c5949f",
+ "id": "edd74060-ac61-5450-a57c-a6577daa5d50",
+ "position": {
+ "x": 100.0,
+ "y": 490.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ }
+ },
+ "projection": "Allocation",
+ "routes": [],
+ "view_options": {
+ "coverage": "All",
+ "exchange_labels": "Name",
+ "hidden_edges": [],
+ "participant_categories": "All",
+ "relation_kinds": "All",
+ "work_categories": "All",
+ "work_contexts": "All"
+ }
+ },
+ "30d5baf9-6581-5cb6-846a-bcaa1e6cce63": {
+ "alias": "sa_state_view",
+ "description": "Teaching view: positions and filters do not change ownership or exchange identities.",
+ "id": "30d5baf9-6581-5cb6-846a-bcaa1e6cce63",
+ "layer": "Sa",
+ "name": "5 · SA pressure supervision",
+ "nodes": {
+ "55a74f91-2025-5001-8db8-9566b1dbe783": {
+ "alias": "sa_state_view_sa_monitor_region",
+ "collapsed": false,
+ "element": "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c",
+ "id": "55a74f91-2025-5001-8db8-9566b1dbe783",
+ "position": {
+ "x": 70.0,
+ "y": 95.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "57acbb71-ca86-57fc-ae90-42dad195f0e0": {
+ "alias": "sa_state_view_sa_waiting",
+ "collapsed": false,
+ "element": "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37",
+ "id": "57acbb71-ca86-57fc-ae90-42dad195f0e0",
+ "position": {
+ "x": 650.0,
+ "y": 200.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "5e2326a0-0f6c-5f8d-8a21-fdded18a9281": {
+ "alias": "sa_state_view_sa_monitor_initial",
+ "collapsed": false,
+ "element": "57eb5348-14d8-560e-996a-8dbf993243e2",
+ "id": "5e2326a0-0f6c-5f8d-8a21-fdded18a9281",
+ "position": {
+ "x": 100.0,
+ "y": 200.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "a676fbb9-0833-59a8-98cf-a10d8f4b7434": {
+ "alias": "sa_state_view_sa_monitor",
+ "collapsed": false,
+ "element": "132b3f28-3f88-5327-9868-8c8b645ed412",
+ "id": "a676fbb9-0833-59a8-98cf-a10d8f4b7434",
+ "position": {
+ "x": 30.0,
+ "y": 30.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "b3af7b04-5a45-5b82-ab69-7eada4b876f0": {
+ "alias": "sa_state_view_sa_monitoring",
+ "collapsed": false,
+ "element": "8af21f9b-c21e-5aae-af0d-0958840f6655",
+ "id": "b3af7b04-5a45-5b82-ab69-7eada4b876f0",
+ "position": {
+ "x": 220.0,
+ "y": 200.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ }
+ },
+ "projection": {
+ "StateMachine": {
+ "machine": "132b3f28-3f88-5327-9868-8c8b645ed412"
+ }
+ },
+ "routes": [],
+ "view_options": {
+ "coverage": "All",
+ "exchange_labels": "Name",
+ "hidden_edges": [],
+ "participant_categories": "All",
+ "relation_kinds": "All",
+ "work_categories": "All",
+ "work_contexts": "All"
+ }
+ },
+ "35d3c24b-289e-59e5-a610-8333e2c6a4bb": {
+ "alias": "sa_allocation_view",
+ "description": "Teaching view: positions and filters do not change ownership or exchange identities.",
+ "id": "35d3c24b-289e-59e5-a610-8333e2c6a4bb",
+ "layer": "Sa",
+ "name": "2 · SA service boundary and functions",
+ "nodes": {
+ "34630a18-41b0-5fdd-bff4-ee86e93e8651": {
+ "alias": "sa_allocation_view_sa_repair",
+ "collapsed": false,
+ "element": "9497e10f-04c4-5586-8738-2464a6f00bd4",
+ "id": "34630a18-41b0-5fdd-bff4-ee86e93e8651",
+ "position": {
+ "x": 890.0,
+ "y": 170.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "60f6ad74-b69e-5aef-b832-f1bd60b903ae": {
+ "alias": "sa_allocation_view_sa_assess",
+ "collapsed": false,
+ "element": "dddae020-a444-5d48-acc8-139d2696a672",
+ "id": "60f6ad74-b69e-5aef-b832-f1bd60b903ae",
+ "position": {
+ "x": 100.0,
+ "y": 170.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "73406244-ebd7-5995-86c7-e17c73c50b32": {
+ "alias": "sa_allocation_view_sa_reset",
+ "collapsed": false,
+ "element": "09342ccb-044b-5483-ac58-87dc3cf7831c",
+ "id": "73406244-ebd7-5995-86c7-e17c73c50b32",
+ "position": {
+ "x": 100.0,
+ "y": 330.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "7e3ba36f-bc68-5520-bdb9-77083bb7bba2": {
+ "alias": "sa_allocation_view_sa_confirm",
+ "collapsed": false,
+ "element": "d75eb3fa-a27d-52d5-8782-8b342b3865f6",
+ "id": "7e3ba36f-bc68-5520-bdb9-77083bb7bba2",
+ "position": {
+ "x": 100.0,
+ "y": 490.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "8345c76a-8d85-5c31-902e-ec1cd33ed4a3": {
+ "alias": "sa_allocation_view_sa_control",
+ "collapsed": false,
+ "element": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "id": "8345c76a-8d85-5c31-902e-ec1cd33ed4a3",
+ "position": {
+ "x": 60.0,
+ "y": 80.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "f7225d1c-e506-505d-bb20-916950269d4b": {
+ "alias": "sa_allocation_view_sa_technician",
+ "collapsed": false,
+ "element": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a",
+ "id": "f7225d1c-e506-505d-bb20-916950269d4b",
+ "position": {
+ "x": 850.0,
+ "y": 80.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ }
+ },
+ "projection": "Allocation",
+ "routes": [],
+ "view_options": {
+ "coverage": "All",
+ "exchange_labels": "Name",
+ "hidden_edges": [],
+ "participant_categories": "All",
+ "relation_kinds": "All",
+ "work_categories": "All",
+ "work_contexts": "All"
+ }
+ },
+ "3e6ad194-61b5-584c-b0f0-a78a20af5781": {
+ "alias": "sa_capability_view",
+ "description": "Teaching view: positions and filters do not change ownership or exchange identities.",
+ "id": "3e6ad194-61b5-584c-b0f0-a78a20af5781",
+ "layer": "Sa",
+ "name": "4 · SA mission and included capability",
+ "nodes": {
+ "069a3fb3-077c-59d8-91da-27b0665143c7": {
+ "alias": "sa_capability_view_sa_triage",
+ "collapsed": false,
+ "element": "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f",
+ "id": "069a3fb3-077c-59d8-91da-27b0665143c7",
+ "position": {
+ "x": 800.0,
+ "y": 260.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "41ddb68b-bd2b-5f91-82eb-efde01157828": {
+ "alias": "sa_capability_view_sa_control",
+ "collapsed": false,
+ "element": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "id": "41ddb68b-bd2b-5f91-82eb-efde01157828",
+ "position": {
+ "x": 60.0,
+ "y": 60.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "4b814335-bf98-5b68-85d5-ea549766517a": {
+ "alias": "sa_capability_view_sa_mission",
+ "collapsed": false,
+ "element": "5ad034b1-084d-5273-9e70-971671766132",
+ "id": "4b814335-bf98-5b68-85d5-ea549766517a",
+ "position": {
+ "x": 420.0,
+ "y": 60.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "d3bc13f9-7726-5280-83fe-63f391c45afb": {
+ "alias": "sa_capability_view_sa_restore",
+ "collapsed": false,
+ "element": "81259051-370a-512d-a3b5-6420a6f933b6",
+ "id": "d3bc13f9-7726-5280-83fe-63f391c45afb",
+ "position": {
+ "x": 420.0,
+ "y": 260.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ }
+ },
+ "projection": "Capabilities",
+ "routes": [],
+ "view_options": {
+ "coverage": "All",
+ "exchange_labels": "Name",
+ "hidden_edges": [],
+ "participant_categories": "All",
+ "relation_kinds": "All",
+ "work_categories": "All",
+ "work_contexts": "All"
+ }
+ },
+ "50af4c9b-93ce-5ff5-a1f8-7a81eb97e779": {
+ "alias": "sa_service_view",
+ "description": "Teaching view: positions and filters do not change ownership or exchange identities.",
+ "id": "50af4c9b-93ce-5ff5-a1f8-7a81eb97e779",
+ "layer": "Sa",
+ "name": "3 · SA service conversation only",
+ "nodes": {
+ "40210589-69f7-5209-b329-10bb83cf615c": {
+ "alias": "sa_service_view_sa_technician",
+ "collapsed": false,
+ "element": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a",
+ "id": "40210589-69f7-5209-b329-10bb83cf615c",
+ "position": {
+ "x": 850.0,
+ "y": 80.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "439f1e45-e5ed-567c-978b-4c93ef267db1": {
+ "alias": "sa_service_view_sa_assess",
+ "collapsed": false,
+ "element": "dddae020-a444-5d48-acc8-139d2696a672",
+ "id": "439f1e45-e5ed-567c-978b-4c93ef267db1",
+ "position": {
+ "x": 100.0,
+ "y": 170.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "6f138672-149b-5f88-ab07-7fc413a42262": {
+ "alias": "sa_service_view_sa_confirm",
+ "collapsed": false,
+ "element": "d75eb3fa-a27d-52d5-8782-8b342b3865f6",
+ "id": "6f138672-149b-5f88-ab07-7fc413a42262",
+ "position": {
+ "x": 100.0,
+ "y": 490.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "ae36489d-6ec8-5f1c-b603-8b497227efba": {
+ "alias": "sa_service_view_sa_repair",
+ "collapsed": false,
+ "element": "9497e10f-04c4-5586-8738-2464a6f00bd4",
+ "id": "ae36489d-6ec8-5f1c-b603-8b497227efba",
+ "position": {
+ "x": 890.0,
+ "y": 170.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "d52da4e6-40b7-5dbc-84fb-80c443711c69": {
+ "alias": "sa_service_view_sa_reset",
+ "collapsed": false,
+ "element": "09342ccb-044b-5483-ac58-87dc3cf7831c",
+ "id": "d52da4e6-40b7-5dbc-84fb-80c443711c69",
+ "position": {
+ "x": 100.0,
+ "y": 330.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ },
+ "f53d4352-b042-56a6-8b3e-c51cd27254f9": {
+ "alias": "sa_service_view_sa_control",
+ "collapsed": false,
+ "element": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "id": "f53d4352-b042-56a6-8b3e-c51cd27254f9",
+ "position": {
+ "x": 60.0,
+ "y": 80.0
+ },
+ "size": {
+ "height": 110.0,
+ "width": 245.0
+ }
+ }
+ },
+ "projection": "Allocation",
+ "routes": [],
+ "view_options": {
+ "coverage": "All",
+ "exchange_labels": "NameAndItems",
+ "hidden_edges": [],
+ "participant_categories": {
+ "Any": [
+ "781cbe75-410f-5cc5-aeb8-a43491f77b99"
+ ]
+ },
+ "relation_kinds": "All",
+ "work_categories": {
+ "Any": []
+ },
+ "work_contexts": "All"
+ }
+ }
+ },
+ "elements": {
+ "01490fa8-2f01-5ac8-9722-d55d45414379": {
+ "alias": "sa_high_pressure",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Event": {
+ "body": {
+ "Change": {
+ "expression": "measured_pressure > operating_limit"
+ }
+ }
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "01490fa8-2f01-5ac8-9722-d55d45414379",
+ "name": "Pressure alarm detected",
+ "parent": null
+ },
+ "022d3cfa-bbaf-5e13-924a-8e55099b8745": {
+ "alias": "oa_assessment_procedure",
+ "body": {
+ "Oa": {
+ "Process": {
+ "steps": [
+ "942975b8-0bef-5ae8-9fcb-c37497833fb0"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "022d3cfa-bbaf-5e13-924a-8e55099b8745",
+ "name": "Assess an alarm procedure",
+ "parent": null
+ },
+ "09342ccb-044b-5483-ac58-87dc3cf7831c": {
+ "alias": "sa_reset",
+ "body": {
+ "Sa": "Function"
+ },
+ "description": "",
+ "id": "09342ccb-044b-5483-ac58-87dc3cf7831c",
+ "name": "Reset valve remotely",
+ "parent": "58867e7d-916e-5ebc-9e4c-8dbed1314be3"
+ },
+ "0b07b9ed-492d-57f6-b0b1-ce8a709238a7": {
+ "alias": "oa_assess_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "SequenceReference": {
+ "sequence": "022d3cfa-bbaf-5e13-924a-8e55099b8745"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "name": "Assess the alarm",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "0f195fea-3493-5ebd-966d-da9eddeb05d7": {
+ "alias": "sa_repair_step",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "9497e10f-04c4-5586-8738-2464a6f00bd4",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "0f195fea-3493-5ebd-966d-da9eddeb05d7",
+ "name": "Repair the valve",
+ "parent": "93eb6479-54e9-59cb-ac7b-16912347bfc0"
+ },
+ "132b3f28-3f88-5327-9868-8c8b645ed412": {
+ "alias": "sa_monitor",
+ "body": {
+ "Sa": {
+ "Shared": "StateMachine"
+ }
+ },
+ "description": "",
+ "id": "132b3f28-3f88-5327-9868-8c8b645ed412",
+ "name": "Pressure supervision",
+ "parent": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0"
+ },
+ "155e4fe4-9e3b-5f4e-b886-28abcc3f086b": {
+ "alias": "sa_outer_technician",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "155e4fe4-9e3b-5f4e-b886-28abcc3f086b",
+ "name": "Technician",
+ "parent": "f746a1cd-ff9f-55c0-b325-01c03d2f4fae"
+ },
+ "1970608f-85d1-5234-ac86-5e2b0df27dc3": {
+ "alias": "oa_monitor",
+ "body": {
+ "Oa": {
+ "Shared": "StateMachine"
+ }
+ },
+ "description": "",
+ "id": "1970608f-85d1-5234-ac86-5e2b0df27dc3",
+ "name": "Pressure supervision",
+ "parent": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22"
+ },
+ "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771": {
+ "alias": "oa_contact_category",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "ExchangeCategory": {
+ "exchanges": [
+ "27b47887-baf0-518b-bf87-f5acbf7d0b93"
+ ],
+ "family": "Participant"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771",
+ "name": "Field contact",
+ "parent": null
+ },
+ "1b91f8a7-0bdb-514e-b1cd-955658e09927": {
+ "alias": "oa_restore_work",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "1b91f8a7-0bdb-514e-b1cd-955658e09927",
+ "name": "Restore safe pressure",
+ "parent": null
+ },
+ "1c9da843-e2d3-5591-8e3f-db083a42867e": {
+ "alias": "sa_use_conversation",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "ScenarioUse": {
+ "bindings": [
+ {
+ "local": "1edfcf9e-9bfa-51f1-80d5-62b8fd8bb023",
+ "referenced": "98c61788-2fee-51f4-8cf9-de48e8c70e9e"
+ },
+ {
+ "local": "155e4fe4-9e3b-5f4e-b886-28abcc3f086b",
+ "referenced": "6249b54a-e396-5338-82b9-89266c55670d"
+ }
+ ],
+ "scenario": "730b27ca-b039-5a3b-8dc6-03418f9061b3"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "1c9da843-e2d3-5591-8e3f-db083a42867e",
+ "name": "Use the reviewed repair conversation",
+ "parent": "f746a1cd-ff9f-55c0-b325-01c03d2f4fae"
+ },
+ "1edfcf9e-9bfa-51f1-80d5-62b8fd8bb023": {
+ "alias": "sa_outer_controller",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "1edfcf9e-9bfa-51f1-80d5-62b8fd8bb023",
+ "name": "Controller",
+ "parent": "f746a1cd-ff9f-55c0-b325-01c03d2f4fae"
+ },
+ "2011b3ac-614b-5a47-8891-7ff81965f425": {
+ "alias": "oa_outer_controller",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "2011b3ac-614b-5a47-8891-7ff81965f425",
+ "name": "Controller",
+ "parent": "d6f881a4-95da-54e9-ad97-1b8965532629"
+ },
+ "20472233-5ce6-5ffe-b4ad-0bdbccf3e423": {
+ "alias": "oa_conversation",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Scenario": {
+ "context": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "postcondition": "The controller has received the measured pressure after repair",
+ "precondition": "A pressure alarm has been assessed",
+ "steps": [
+ "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "cc2d497a-fd15-5455-9ee5-e70eb95811d8"
+ ],
+ "viewpoint": "ParticipantInteraction"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423",
+ "name": "Repair request and measured reply",
+ "parent": null
+ },
+ "24a6850e-b819-57e0-af88-ca4ab76581b7": {
+ "alias": "sa_repair_request_out",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Port": {
+ "contract": {
+ "FunctionOutput": {
+ "outgoing_items": [
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ ]
+ }
+ },
+ "legacy": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "24a6850e-b819-57e0-af88-ca4ab76581b7",
+ "name": "Request field repair output",
+ "parent": "dddae020-a444-5d48-acc8-139d2696a672"
+ },
+ "30c8fad9-a49b-5692-9542-3148e76afd7b": {
+ "alias": "sa_decision",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Control": {
+ "kind": "Choice"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "30c8fad9-a49b-5692-9542-3148e76afd7b",
+ "name": "Can a remote reset safely recover pressure?",
+ "parent": "5139ce73-56fd-5875-ba3a-e569fc890231"
+ },
+ "32aa9940-df6e-5a12-b9bd-6337e2d2ed22": {
+ "alias": "oa_control",
+ "body": {
+ "Oa": "Entity"
+ },
+ "description": "",
+ "id": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "name": "Control room",
+ "parent": null
+ },
+ "391af7c4-b07d-53a7-8ddd-5c81461fc4f8": {
+ "alias": "sa_repair_report_in",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Port": {
+ "contract": {
+ "FunctionInput": {
+ "incoming_items": [
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ ]
+ }
+ },
+ "legacy": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "391af7c4-b07d-53a7-8ddd-5c81461fc4f8",
+ "name": "Report repair result input",
+ "parent": "d75eb3fa-a27d-52d5-8782-8b342b3865f6"
+ },
+ "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c": {
+ "alias": "sa_monitor_region",
+ "body": {
+ "Sa": {
+ "Shared": "Region"
+ }
+ },
+ "description": "",
+ "id": "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c",
+ "name": "Supervision cycle",
+ "parent": "132b3f28-3f88-5327-9868-8c8b645ed412"
+ },
+ "3dfee23a-148b-5cec-ad2d-b774e84ea5bb": {
+ "alias": "oa_restore",
+ "body": {
+ "Oa": "Capability"
+ },
+ "description": "",
+ "id": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "name": "Restore a safe supply",
+ "parent": null
+ },
+ "40d94322-baa2-5c81-b261-ff47cdc33782": {
+ "alias": "sa_request_step",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "93474111-f23e-56c1-b61b-b7c2396b91bd",
+ "message_kind": "Asynchronous",
+ "receiver": null,
+ "reply_to": null,
+ "sender": null,
+ "values": []
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "40d94322-baa2-5c81-b261-ff47cdc33782",
+ "name": "Request the field repair",
+ "parent": "93eb6479-54e9-59cb-ac7b-16912347bfc0"
+ },
+ "4149a872-3440-59de-9879-8981e78d27ab": {
+ "alias": "sa_audit_thresholds",
+ "body": {
+ "Sa": "Function"
+ },
+ "description": "Deliberately unfinished: assigned to the service, but no behavior context yet. Model checks should expose this one work_without_behavior_context warning.",
+ "id": "4149a872-3440-59de-9879-8981e78d27ab",
+ "name": "Audit pressure thresholds",
+ "parent": null
+ },
+ "41859a65-ded2-547b-ab03-14e2fa740cf9": {
+ "alias": "oa_caller",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "name": "Controller",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "435bf87c-73fd-5e4d-978a-4c471bc9eaf0": {
+ "alias": "sa_control",
+ "body": {
+ "Sa": "System"
+ },
+ "description": "",
+ "id": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "name": "Pressure coordination service",
+ "parent": null
+ },
+ "47bec4f9-d6d0-5ed1-adca-fa0d960dc416": {
+ "alias": "sa_confirm_step",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "d75eb3fa-a27d-52d5-8782-8b342b3865f6",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "47bec4f9-d6d0-5ed1-adca-fa0d960dc416",
+ "name": "Confirm safe pressure",
+ "parent": "5139ce73-56fd-5875-ba3a-e569fc890231"
+ },
+ "4e1c0a56-ce80-5f13-a93a-6956de54880c": {
+ "alias": "sa_reply",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "63009b5f-539e-5914-b630-ec6b7fefb4d5",
+ "message_kind": "Reply",
+ "receiver": "98c61788-2fee-51f4-8cf9-de48e8c70e9e",
+ "reply_to": "8bab4783-a098-5984-9e36-493957caf26c",
+ "sender": "6249b54a-e396-5338-82b9-89266c55670d",
+ "values": [
+ {
+ "expression": "320.0",
+ "field": "bb12ba5d-aab5-5a1b-99de-c767f5365b57",
+ "path": []
+ }
+ ]
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "4e1c0a56-ce80-5f13-a93a-6956de54880c",
+ "name": "Return pressure after repair",
+ "parent": "730b27ca-b039-5a3b-8dc6-03418f9061b3"
+ },
+ "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07": {
+ "alias": "sa_merge",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Control": {
+ "kind": "Merge"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07",
+ "name": "Recovery path completed",
+ "parent": "5139ce73-56fd-5875-ba3a-e569fc890231"
+ },
+ "5139ce73-56fd-5875-ba3a-e569fc890231": {
+ "alias": "sa_response",
+ "body": {
+ "Sa": {
+ "FunctionalChain": {
+ "steps": [
+ "9024243c-ccc1-5edb-8477-2aa16e5e9a50",
+ "30c8fad9-a49b-5692-9542-3148e76afd7b",
+ "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6",
+ "d5e61c5d-510b-5091-a878-da46f3a9e152",
+ "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07",
+ "47bec4f9-d6d0-5ed1-adca-fa0d960dc416"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "5139ce73-56fd-5875-ba3a-e569fc890231",
+ "name": "Handle a pressure alarm",
+ "parent": null
+ },
+ "57eb5348-14d8-560e-996a-8dbf993243e2": {
+ "alias": "sa_monitor_initial",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "Initial"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "57eb5348-14d8-560e-996a-8dbf993243e2",
+ "name": "Start",
+ "parent": "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c"
+ },
+ "58867e7d-916e-5ebc-9e4c-8dbed1314be3": {
+ "alias": "sa_restore_work",
+ "body": {
+ "Sa": "Function"
+ },
+ "description": "",
+ "id": "58867e7d-916e-5ebc-9e4c-8dbed1314be3",
+ "name": "Restore safe pressure",
+ "parent": null
+ },
+ "58bae2b0-f560-50f2-818d-19091d22d18a": {
+ "alias": "oa_response",
+ "body": {
+ "Oa": {
+ "Process": {
+ "steps": [
+ "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "e00594a3-6122-5a6b-b3c2-ecb5506c741d"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "58bae2b0-f560-50f2-818d-19091d22d18a",
+ "name": "Handle a pressure alarm",
+ "parent": null
+ },
+ "58c15833-6e75-523a-b57f-a88f19f6d672": {
+ "alias": "oa_waiting",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "State"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "name": "Awaiting repair",
+ "parent": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7"
+ },
+ "5ad034b1-084d-5273-9e70-971671766132": {
+ "alias": "sa_mission",
+ "body": {
+ "Sa": "Mission"
+ },
+ "description": "",
+ "id": "5ad034b1-084d-5273-9e70-971671766132",
+ "name": "Maintain safe water supply",
+ "parent": null
+ },
+ "5ba8079c-2dbe-5e98-ae82-2b60c6020a02": {
+ "alias": "oa_request_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "message_kind": "Asynchronous",
+ "receiver": null,
+ "reply_to": null,
+ "sender": null,
+ "values": []
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "name": "Request the field repair",
+ "parent": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ },
+ "5d3af417-244c-54bb-9588-16daedda4e1f": {
+ "alias": "oa_field_procedure",
+ "body": {
+ "Oa": {
+ "Process": {
+ "steps": [
+ "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "df2fd3f8-6bc2-5380-a13f-36643ed35ac7"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "5d3af417-244c-54bb-9588-16daedda4e1f",
+ "name": "Field repair procedure",
+ "parent": null
+ },
+ "61f7bbfb-74a4-5540-9182-2583dfb27a49": {
+ "alias": "oa_assess",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "name": "Assess pressure alarm",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "6249b54a-e396-5338-82b9-89266c55670d": {
+ "alias": "sa_responder",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "6249b54a-e396-5338-82b9-89266c55670d",
+ "name": "Technician",
+ "parent": "730b27ca-b039-5a3b-8dc6-03418f9061b3"
+ },
+ "65d6b8d6-271a-5c84-8b42-abbe310c955e": {
+ "alias": "oa_high_pressure",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Event": {
+ "body": {
+ "Change": {
+ "expression": "measured_pressure > operating_limit"
+ }
+ }
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "65d6b8d6-271a-5c84-8b42-abbe310c955e",
+ "name": "Pressure alarm detected",
+ "parent": null
+ },
+ "6a30b763-cc51-546d-907f-5c63657cfd03": {
+ "alias": "oa_triage",
+ "body": {
+ "Oa": "Capability"
+ },
+ "description": "",
+ "id": "6a30b763-cc51-546d-907f-5c63657cfd03",
+ "name": "Assess an alarm",
+ "parent": null
+ },
+ "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d": {
+ "alias": "oa_merge",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Control": {
+ "kind": "Merge"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "name": "Recovery path completed",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae": {
+ "alias": "oa_monitoring",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "State"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "name": "Monitoring",
+ "parent": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7"
+ },
+ "727c0b5c-8db6-5eb7-83d6-735519c5949f": {
+ "alias": "oa_confirm",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "727c0b5c-8db6-5eb7-83d6-735519c5949f",
+ "name": "Confirm safe pressure",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "730b27ca-b039-5a3b-8dc6-03418f9061b3": {
+ "alias": "sa_conversation",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Scenario": {
+ "context": "81259051-370a-512d-a3b5-6420a6f933b6",
+ "postcondition": "The controller has received the measured pressure after repair",
+ "precondition": "A pressure alarm has been assessed",
+ "steps": [
+ "8bab4783-a098-5984-9e36-493957caf26c",
+ "4e1c0a56-ce80-5f13-a93a-6956de54880c"
+ ],
+ "viewpoint": "BoundaryInterface"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "730b27ca-b039-5a3b-8dc6-03418f9061b3",
+ "name": "Repair request and measured reply",
+ "parent": null
+ },
+ "781cbe75-410f-5cc5-aeb8-a43491f77b99": {
+ "alias": "sa_contact_category",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "ExchangeCategory": {
+ "exchanges": [
+ "63009b5f-539e-5914-b630-ec6b7fefb4d5"
+ ],
+ "family": "Participant"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "781cbe75-410f-5cc5-aeb8-a43491f77b99",
+ "name": "Field contact",
+ "parent": null
+ },
+ "81259051-370a-512d-a3b5-6420a6f933b6": {
+ "alias": "sa_restore",
+ "body": {
+ "Sa": "Capability"
+ },
+ "description": "",
+ "id": "81259051-370a-512d-a3b5-6420a6f933b6",
+ "name": "Restore a safe supply",
+ "parent": null
+ },
+ "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7": {
+ "alias": "oa_monitor_region",
+ "body": {
+ "Oa": {
+ "Shared": "Region"
+ }
+ },
+ "description": "",
+ "id": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7",
+ "name": "Supervision cycle",
+ "parent": "1970608f-85d1-5234-ac86-5e2b0df27dc3"
+ },
+ "87d0a95b-af1b-5352-b753-e4c6533671a5": {
+ "alias": "sa_pressure",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "DataType": {
+ "default": null,
+ "definition": {
+ "Real": {
+ "maximum": 600.0,
+ "minimum": 0.0
+ }
+ },
+ "unit": "kPa"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "87d0a95b-af1b-5352-b753-e4c6533671a5",
+ "name": "Measured pressure",
+ "parent": null
+ },
+ "8af21f9b-c21e-5aae-af0d-0958840f6655": {
+ "alias": "sa_monitoring",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "State"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "8af21f9b-c21e-5aae-af0d-0958840f6655",
+ "name": "Monitoring",
+ "parent": "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c"
+ },
+ "8bab4783-a098-5984-9e36-493957caf26c": {
+ "alias": "sa_call",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "63009b5f-539e-5914-b630-ec6b7fefb4d5",
+ "message_kind": "Call",
+ "receiver": "6249b54a-e396-5338-82b9-89266c55670d",
+ "reply_to": null,
+ "sender": "98c61788-2fee-51f4-8cf9-de48e8c70e9e",
+ "values": [
+ {
+ "expression": "420.0",
+ "field": "bb12ba5d-aab5-5a1b-99de-c767f5365b57",
+ "path": []
+ }
+ ]
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "8bab4783-a098-5984-9e36-493957caf26c",
+ "name": "Request valve repair",
+ "parent": "730b27ca-b039-5a3b-8dc6-03418f9061b3"
+ },
+ "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7": {
+ "alias": "oa_pressure_parameter",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "DataField": {
+ "default": null,
+ "direction": "InOut",
+ "maximum": 1,
+ "minimum": 1,
+ "storage": "Value",
+ "type_ref": {
+ "Named": "e7824764-5db9-5384-8198-2fbaea2b10b2"
+ },
+ "unit": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "name": "Pressure before / after repair",
+ "parent": "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ },
+ "9024243c-ccc1-5edb-8477-2aa16e5e9a50": {
+ "alias": "sa_assess_step",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "SequenceReference": {
+ "sequence": "acd5a018-f559-5298-a40d-1cf6e1353e74"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "9024243c-ccc1-5edb-8477-2aa16e5e9a50",
+ "name": "Assess the alarm",
+ "parent": "5139ce73-56fd-5875-ba3a-e569fc890231"
+ },
+ "9349b353-53f2-56f8-b571-a05b09335cdf": {
+ "alias": "oa_repair_traffic",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "ExchangeCategory": {
+ "exchanges": [
+ "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "b436d0b3-c824-5a96-af6b-2c269816d1cc"
+ ],
+ "family": "Work"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "9349b353-53f2-56f8-b571-a05b09335cdf",
+ "name": "Repair work exchanges",
+ "parent": null
+ },
+ "93eb6479-54e9-59cb-ac7b-16912347bfc0": {
+ "alias": "sa_field_procedure",
+ "body": {
+ "Sa": {
+ "FunctionalChain": {
+ "steps": [
+ "40d94322-baa2-5c81-b261-ff47cdc33782",
+ "0f195fea-3493-5ebd-966d-da9eddeb05d7",
+ "ea32e4ec-8bdc-57ea-8a90-7b09ba4f0eac"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "93eb6479-54e9-59cb-ac7b-16912347bfc0",
+ "name": "Field repair procedure",
+ "parent": null
+ },
+ "942975b8-0bef-5ae8-9fcb-c37497833fb0": {
+ "alias": "oa_assessment_action",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "942975b8-0bef-5ae8-9fcb-c37497833fb0",
+ "name": "Assess pressure alarm",
+ "parent": "022d3cfa-bbaf-5e13-924a-8e55099b8745"
+ },
+ "9497e10f-04c4-5586-8738-2464a6f00bd4": {
+ "alias": "sa_repair",
+ "body": {
+ "Sa": "Function"
+ },
+ "description": "",
+ "id": "9497e10f-04c4-5586-8738-2464a6f00bd4",
+ "name": "Repair valve in the field",
+ "parent": "58867e7d-916e-5ebc-9e4c-8dbed1314be3"
+ },
+ "98c61788-2fee-51f4-8cf9-de48e8c70e9e": {
+ "alias": "sa_caller",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "98c61788-2fee-51f4-8cf9-de48e8c70e9e",
+ "name": "Controller",
+ "parent": "730b27ca-b039-5a3b-8dc6-03418f9061b3"
+ },
+ "98d411e7-7c65-5c3f-b543-8bf0f2522ea2": {
+ "alias": "sa_control_port",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Port": {
+ "contract": {
+ "Component": {
+ "kind": "Standard",
+ "orientation": "Unset"
+ }
+ },
+ "legacy": null
+ }
+ }
+ }
+ },
+ "description": "Explicitly chosen Standard component port for the repair service.",
+ "id": "98d411e7-7c65-5c3f-b543-8bf0f2522ea2",
+ "name": "Repair service client",
+ "parent": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0"
+ },
+ "994faed7-dbc8-5b96-9d62-7604ac0fbc3a": {
+ "alias": "sa_repair_report_out",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Port": {
+ "contract": {
+ "FunctionOutput": {
+ "outgoing_items": [
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ ]
+ }
+ },
+ "legacy": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "994faed7-dbc8-5b96-9d62-7604ac0fbc3a",
+ "name": "Report repair result output",
+ "parent": "9497e10f-04c4-5586-8738-2464a6f00bd4"
+ },
+ "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5": {
+ "alias": "oa_use_conversation",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "ScenarioUse": {
+ "bindings": [
+ {
+ "local": "2011b3ac-614b-5a47-8891-7ff81965f425",
+ "referenced": "41859a65-ded2-547b-ab03-14e2fa740cf9"
+ },
+ {
+ "local": "fffff882-78fd-5415-928d-c1ef7fac6576",
+ "referenced": "fc81c5f0-0442-5b88-992b-e5b10f351c59"
+ }
+ ],
+ "scenario": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5",
+ "name": "Use the reviewed repair conversation",
+ "parent": "d6f881a4-95da-54e9-ad97-1b8965532629"
+ },
+ "a21465af-05f3-5fb5-8983-de26932422fb": {
+ "alias": "oa_repair",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "name": "Repair valve in the field",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "a4b8942e-a975-5f88-ad14-8f38c345baac": {
+ "alias": "sa_repair_interface",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Interface": {
+ "supertype": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "a4b8942e-a975-5f88-ad14-8f38c345baac",
+ "name": "Valve repair service",
+ "parent": null
+ },
+ "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37": {
+ "alias": "sa_waiting",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "State"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37",
+ "name": "Awaiting repair",
+ "parent": "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c"
+ },
+ "a7ffb869-9dde-5e76-b276-c719c5c143a9": {
+ "alias": "oa_monitor_initial",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "Initial"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "a7ffb869-9dde-5e76-b276-c719c5c143a9",
+ "name": "Start",
+ "parent": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7"
+ },
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218": {
+ "alias": "sa_repair_operation",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "ExchangeItem": {
+ "kind": "Information",
+ "mechanism": "Operation",
+ "supertype": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "a962fc5e-3ab0-5296-8b0d-fd84633f9218",
+ "name": "Valve repair",
+ "parent": null
+ },
+ "a9a47e48-100c-57a8-95f0-7fe24da94f94": {
+ "alias": "sa_repair_traffic",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "ExchangeCategory": {
+ "exchanges": [
+ "93474111-f23e-56c1-b61b-b7c2396b91bd",
+ "684eadc3-e670-5779-a60f-bbc9a5bfb062"
+ ],
+ "family": "Work"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "a9a47e48-100c-57a8-95f0-7fe24da94f94",
+ "name": "Repair work exchanges",
+ "parent": null
+ },
+ "acd5a018-f559-5298-a40d-1cf6e1353e74": {
+ "alias": "sa_assessment_procedure",
+ "body": {
+ "Sa": {
+ "FunctionalChain": {
+ "steps": [
+ "bae5b192-5b8e-51c6-82ae-2331af0d33bf"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "acd5a018-f559-5298-a40d-1cf6e1353e74",
+ "name": "Assess an alarm procedure",
+ "parent": null
+ },
+ "b280c6ca-e5f9-5424-a5c1-5676e01c2371": {
+ "alias": "oa_decision",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Control": {
+ "kind": "Choice"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "name": "Can a remote reset safely recover pressure?",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "b97437c6-cd7a-5a05-b537-b30a17dd936a": {
+ "alias": "oa_call",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "message_kind": "Call",
+ "receiver": "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "reply_to": null,
+ "sender": "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "values": [
+ {
+ "expression": "420.0",
+ "field": "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "path": []
+ }
+ ]
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "name": "Request valve repair",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f": {
+ "alias": "sa_triage",
+ "body": {
+ "Sa": "Capability"
+ },
+ "description": "",
+ "id": "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f",
+ "name": "Assess an alarm",
+ "parent": null
+ },
+ "bae5b192-5b8e-51c6-82ae-2331af0d33bf": {
+ "alias": "sa_assessment_action",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "dddae020-a444-5d48-acc8-139d2696a672",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "bae5b192-5b8e-51c6-82ae-2331af0d33bf",
+ "name": "Assess pressure alarm",
+ "parent": "acd5a018-f559-5298-a40d-1cf6e1353e74"
+ },
+ "bb12ba5d-aab5-5a1b-99de-c767f5365b57": {
+ "alias": "sa_pressure_parameter",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "DataField": {
+ "default": null,
+ "direction": "InOut",
+ "maximum": 1,
+ "minimum": 1,
+ "storage": "Value",
+ "type_ref": {
+ "Named": "87d0a95b-af1b-5352-b753-e4c6533671a5"
+ },
+ "unit": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "bb12ba5d-aab5-5a1b-99de-c767f5365b57",
+ "name": "Pressure before / after repair",
+ "parent": "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ },
+ "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6": {
+ "alias": "sa_reset_step",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "09342ccb-044b-5483-ac58-87dc3cf7831c",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6",
+ "name": "Try the authorized remote reset",
+ "parent": "5139ce73-56fd-5875-ba3a-e569fc890231"
+ },
+ "c05b9422-b9da-5de5-b21d-07ad23bd2b8a": {
+ "alias": "sa_technician",
+ "body": {
+ "Sa": "Actor"
+ },
+ "description": "",
+ "id": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a",
+ "name": "Field technician",
+ "parent": null
+ },
+ "c59432fe-6092-5ee0-908d-9dbf39476471": {
+ "alias": "oa_repair_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "name": "Repair the valve",
+ "parent": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ },
+ "c9c61f50-06e0-5d17-973d-dcc57cd5cc90": {
+ "alias": "oa_mission",
+ "body": {
+ "Oa": "Mission"
+ },
+ "description": "",
+ "id": "c9c61f50-06e0-5d17-973d-dcc57cd5cc90",
+ "name": "Maintain safe water supply",
+ "parent": null
+ },
+ "cc2d497a-fd15-5455-9ee5-e70eb95811d8": {
+ "alias": "oa_reply",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "message_kind": "Reply",
+ "receiver": "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "reply_to": "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "sender": "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "values": [
+ {
+ "expression": "320.0",
+ "field": "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "path": []
+ }
+ ]
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "cc2d497a-fd15-5455-9ee5-e70eb95811d8",
+ "name": "Return pressure after repair",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "d0c8be87-d238-503a-a549-7558a210507f": {
+ "alias": "oa_reset",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "d0c8be87-d238-503a-a549-7558a210507f",
+ "name": "Reset valve remotely",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "d2026fc2-74a8-5e22-9942-ff3eb68a3677": {
+ "alias": "oa_reset_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "d0c8be87-d238-503a-a549-7558a210507f",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "name": "Try the authorized remote reset",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "d2113fb0-a531-54ef-9484-13951b00ab8f": {
+ "alias": "sa_repair_request_in",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Port": {
+ "contract": {
+ "FunctionInput": {
+ "incoming_items": [
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ ]
+ }
+ },
+ "legacy": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d2113fb0-a531-54ef-9484-13951b00ab8f",
+ "name": "Request field repair input",
+ "parent": "9497e10f-04c4-5586-8738-2464a6f00bd4"
+ },
+ "d5e61c5d-510b-5091-a878-da46f3a9e152": {
+ "alias": "sa_use_field",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "SequenceReference": {
+ "sequence": "93eb6479-54e9-59cb-ac7b-16912347bfc0"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d5e61c5d-510b-5091-a878-da46f3a9e152",
+ "name": "Use the field repair procedure",
+ "parent": "5139ce73-56fd-5875-ba3a-e569fc890231"
+ },
+ "d6f881a4-95da-54e9-ad97-1b8965532629": {
+ "alias": "oa_full_scenario",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Scenario": {
+ "context": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "postcondition": "The controller has received the measured pressure after repair",
+ "precondition": "A pressure alarm has been assessed",
+ "steps": [
+ "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5"
+ ],
+ "viewpoint": "ParticipantInteraction"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d6f881a4-95da-54e9-ad97-1b8965532629",
+ "name": "Restore supply through field repair",
+ "parent": null
+ },
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3": {
+ "alias": "oa_repair_operation",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "ExchangeItem": {
+ "kind": "Information",
+ "mechanism": "Operation",
+ "supertype": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d736c7d6-d369-550a-8ba4-dae223e40bb3",
+ "name": "Valve repair",
+ "parent": null
+ },
+ "d75eb3fa-a27d-52d5-8782-8b342b3865f6": {
+ "alias": "sa_confirm",
+ "body": {
+ "Sa": "Function"
+ },
+ "description": "",
+ "id": "d75eb3fa-a27d-52d5-8782-8b342b3865f6",
+ "name": "Confirm safe pressure",
+ "parent": "58867e7d-916e-5ebc-9e4c-8dbed1314be3"
+ },
+ "dddae020-a444-5d48-acc8-139d2696a672": {
+ "alias": "sa_assess",
+ "body": {
+ "Sa": "Function"
+ },
+ "description": "",
+ "id": "dddae020-a444-5d48-acc8-139d2696a672",
+ "name": "Assess pressure alarm",
+ "parent": "58867e7d-916e-5ebc-9e4c-8dbed1314be3"
+ },
+ "df2fd3f8-6bc2-5380-a13f-36643ed35ac7": {
+ "alias": "oa_report_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "b436d0b3-c824-5a96-af6b-2c269816d1cc",
+ "message_kind": "Asynchronous",
+ "receiver": null,
+ "reply_to": null,
+ "sender": null,
+ "values": []
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "df2fd3f8-6bc2-5380-a13f-36643ed35ac7",
+ "name": "Report the measured result",
+ "parent": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ },
+ "e00594a3-6122-5a6b-b3c2-ecb5506c741d": {
+ "alias": "oa_confirm_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "727c0b5c-8db6-5eb7-83d6-735519c5949f",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "e00594a3-6122-5a6b-b3c2-ecb5506c741d",
+ "name": "Confirm safe pressure",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "e07c3952-4a1a-5cc7-b7d5-03a01101ce49": {
+ "alias": "oa_technician",
+ "body": {
+ "Oa": "Actor"
+ },
+ "description": "",
+ "id": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "name": "Field technician",
+ "parent": null
+ },
+ "e6d69cbd-99d7-596b-bbe3-dada6118a600": {
+ "alias": "sa_technician_port",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Port": {
+ "contract": {
+ "Component": {
+ "kind": "Standard",
+ "orientation": "Unset"
+ }
+ },
+ "legacy": null
+ }
+ }
+ }
+ },
+ "description": "Explicitly chosen Standard component port for the repair service.",
+ "id": "e6d69cbd-99d7-596b-bbe3-dada6118a600",
+ "name": "Repair service provider",
+ "parent": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a"
+ },
+ "e7824764-5db9-5384-8198-2fbaea2b10b2": {
+ "alias": "oa_pressure",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "DataType": {
+ "default": null,
+ "definition": {
+ "Real": {
+ "maximum": 600.0,
+ "minimum": 0.0
+ }
+ },
+ "unit": "kPa"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "e7824764-5db9-5384-8198-2fbaea2b10b2",
+ "name": "Measured pressure",
+ "parent": null
+ },
+ "ea32e4ec-8bdc-57ea-8a90-7b09ba4f0eac": {
+ "alias": "sa_report_step",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "684eadc3-e670-5779-a60f-bbc9a5bfb062",
+ "message_kind": "Asynchronous",
+ "receiver": null,
+ "reply_to": null,
+ "sender": null,
+ "values": []
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "ea32e4ec-8bdc-57ea-8a90-7b09ba4f0eac",
+ "name": "Report the measured result",
+ "parent": "93eb6479-54e9-59cb-ac7b-16912347bfc0"
+ },
+ "f746a1cd-ff9f-55c0-b325-01c03d2f4fae": {
+ "alias": "sa_full_scenario",
+ "body": {
+ "Sa": {
+ "Shared": {
+ "Scenario": {
+ "context": "81259051-370a-512d-a3b5-6420a6f933b6",
+ "postcondition": "The controller has received the measured pressure after repair",
+ "precondition": "A pressure alarm has been assessed",
+ "steps": [
+ "1c9da843-e2d3-5591-8e3f-db083a42867e"
+ ],
+ "viewpoint": "BoundaryInterface"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "f746a1cd-ff9f-55c0-b325-01c03d2f4fae",
+ "name": "Restore supply through field repair",
+ "parent": null
+ },
+ "fc6763bf-a534-54bf-afcd-57ab285ff07d": {
+ "alias": "oa_use_field",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "SequenceReference": {
+ "sequence": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "name": "Use the field repair procedure",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "fc81c5f0-0442-5b88-992b-e5b10f351c59": {
+ "alias": "oa_responder",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "name": "Technician",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "fffff882-78fd-5415-928d-c1ef7fac6576": {
+ "alias": "oa_outer_technician",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "fffff882-78fd-5415-928d-c1ef7fac6576",
+ "name": "Technician",
+ "parent": "d6f881a4-95da-54e9-ad97-1b8965532629"
+ }
+ },
+ "id": "2786d72f-5763-5f33-a57f-2773a4ca1a6c",
+ "name": "Restore safe water pressure — release 1 walkthrough",
+ "relations": {
+ "094b888c-450c-5c4f-971a-7d75db059574": {
+ "alias": "oa_request_carriage",
+ "body": {
+ "ExchangeAllocation": {
+ "source": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "target": "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d"
+ }
+ },
+ "description": "",
+ "id": "094b888c-450c-5c4f-971a-7d75db059574",
+ "name": "Exchange allocation: Request field repair"
+ },
+ "09eaa992-4cdf-5ea3-b748-f10fd74b8cba": {
+ "alias": "sa_port_request_in",
+ "body": {
+ "PortAllocation": {
+ "source": "e6d69cbd-99d7-596b-bbe3-dada6118a600",
+ "target": "d2113fb0-a531-54ef-9484-13951b00ab8f"
+ }
+ },
+ "description": "",
+ "id": "09eaa992-4cdf-5ea3-b748-f10fd74b8cba",
+ "name": "Port allocation: Request field repair input"
+ },
+ "0c4c9e58-ff32-5c00-b82a-d408a4be0183": {
+ "alias": "oa_mission_participant",
+ "body": {
+ "MissionInvolvement": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "c9c61f50-06e0-5d17-973d-dcc57cd5cc90"
+ }
+ },
+ "description": "",
+ "id": "0c4c9e58-ff32-5c00-b82a-d408a4be0183",
+ "name": "Mission involvement: Maintain safe water supply"
+ },
+ "0f6ac3fb-4ad6-581e-ad14-336472072d5e": {
+ "alias": "sa_field_merge",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "d5e61c5d-510b-5091-a878-da46f3a9e152",
+ "target": "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07"
+ }
+ },
+ "description": "",
+ "id": "0f6ac3fb-4ad6-581e-ad14-336472072d5e",
+ "name": "Continue"
+ },
+ "109e2d6b-7e86-5934-93a5-fab56eced84e": {
+ "alias": "oa_assess_then_decide",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "target": "b280c6ca-e5f9-5424-a5c1-5676e01c2371"
+ }
+ },
+ "description": "",
+ "id": "109e2d6b-7e86-5934-93a5-fab56eced84e",
+ "name": "Continue"
+ },
+ "13c3bfa4-9254-5ab2-b96f-f65f402adf81": {
+ "alias": "sa_alarm_transition",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "",
+ "source": "8af21f9b-c21e-5aae-af0d-0958840f6655",
+ "target": "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": [
+ "01490fa8-2f01-5ac8-9722-d55d45414379"
+ ]
+ }
+ },
+ "description": "",
+ "id": "13c3bfa4-9254-5ab2-b96f-f65f402adf81",
+ "name": "State transition: Awaiting repair"
+ },
+ "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a": {
+ "alias": "oa_reset_allocation",
+ "body": {
+ "Allocation": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "d0c8be87-d238-503a-a549-7558a210507f"
+ }
+ },
+ "description": "",
+ "id": "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a",
+ "name": "Allocation: Reset valve remotely"
+ },
+ "1b245065-7242-5d81-8472-aaeb932054c9": {
+ "alias": "sa_audit_allocation",
+ "body": {
+ "Allocation": {
+ "source": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "target": "4149a872-3440-59de-9879-8981e78d27ab"
+ }
+ },
+ "description": "",
+ "id": "1b245065-7242-5d81-8472-aaeb932054c9",
+ "name": "Allocation: Audit pressure thresholds"
+ },
+ "1bbd5957-1173-51d8-97e3-ba4f084227ae": {
+ "alias": "sa_field_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "else",
+ "source": "30c8fad9-a49b-5692-9542-3148e76afd7b",
+ "target": "d5e61c5d-510b-5091-a878-da46f3a9e152"
+ }
+ },
+ "description": "",
+ "id": "1bbd5957-1173-51d8-97e3-ba4f084227ae",
+ "name": "else"
+ },
+ "1c1e5634-ba8c-591f-b143-90a27cdc6cd2": {
+ "alias": "sa_mission_participant",
+ "body": {
+ "MissionInvolvement": {
+ "source": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "target": "5ad034b1-084d-5273-9e70-971671766132"
+ }
+ },
+ "description": "",
+ "id": "1c1e5634-ba8c-591f-b143-90a27cdc6cd2",
+ "name": "Mission involvement: Maintain safe water supply"
+ },
+ "1e738f27-a3a1-526d-b330-6384088b9689": {
+ "alias": "sa_confirm_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07",
+ "target": "47bec4f9-d6d0-5ed1-adca-fa0d960dc416"
+ }
+ },
+ "description": "",
+ "id": "1e738f27-a3a1-526d-b330-6384088b9689",
+ "name": "Continue"
+ },
+ "1f8b1898-65f6-5360-99cf-b78a71bd50ec": {
+ "alias": "oa_mission_delivery",
+ "body": {
+ "MissionCapability": {
+ "source": "c9c61f50-06e0-5d17-973d-dcc57cd5cc90",
+ "target": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb"
+ }
+ },
+ "description": "",
+ "id": "1f8b1898-65f6-5360-99cf-b78a71bd50ec",
+ "name": "Mission capability: Restore a safe supply"
+ },
+ "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97": {
+ "alias": "oa_normal_transition",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "repair confirmed and pressure is safe",
+ "source": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "target": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": []
+ }
+ },
+ "description": "",
+ "id": "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97",
+ "name": "State transition: Monitoring"
+ },
+ "229ee94f-d147-50f6-9769-b5bd0c03a084": {
+ "alias": "oa_confirm_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "target": "e00594a3-6122-5a6b-b3c2-ecb5506c741d"
+ }
+ },
+ "description": "",
+ "id": "229ee94f-d147-50f6-9769-b5bd0c03a084",
+ "name": "Continue"
+ },
+ "27b47887-baf0-518b-bf87-f5acbf7d0b93": {
+ "alias": "oa_contact",
+ "body": {
+ "CommunicationMean": {
+ "items": [
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ ],
+ "oriented": false,
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49"
+ }
+ },
+ "description": "",
+ "id": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "name": "Technician service conversation"
+ },
+ "2b00270c-79a5-52a5-af96-33346bea531b": {
+ "alias": "oa_start_monitoring",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "",
+ "source": "a7ffb869-9dde-5e76-b276-c719c5c143a9",
+ "target": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": []
+ }
+ },
+ "description": "",
+ "id": "2b00270c-79a5-52a5-af96-33346bea531b",
+ "name": "State transition: Monitoring"
+ },
+ "3cc9ab52-2b00-57fd-8709-aabc75e5d3e3": {
+ "alias": "sa_reset_allocation",
+ "body": {
+ "Allocation": {
+ "source": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "target": "09342ccb-044b-5483-ac58-87dc3cf7831c"
+ }
+ },
+ "description": "",
+ "id": "3cc9ab52-2b00-57fd-8709-aabc75e5d3e3",
+ "name": "Allocation: Reset valve remotely"
+ },
+ "3f54d890-4ca5-5e36-873b-abb5c92d578c": {
+ "alias": "oa_request_then_repair",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "target": "c59432fe-6092-5ee0-908d-9dbf39476471"
+ }
+ },
+ "description": "",
+ "id": "3f54d890-4ca5-5e36-873b-abb5c92d578c",
+ "name": "Sequence link: Repair the valve"
+ },
+ "4549d3fa-5ab9-52a9-b4f9-3a78fc9d625a": {
+ "alias": "oa_report_carriage",
+ "body": {
+ "ExchangeAllocation": {
+ "source": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "target": "b436d0b3-c824-5a96-af6b-2c269816d1cc"
+ }
+ },
+ "description": "",
+ "id": "4549d3fa-5ab9-52a9-b4f9-3a78fc9d625a",
+ "name": "Exchange allocation: Report repair result"
+ },
+ "47730532-0372-5cc5-9d6d-0b8953aebdd8": {
+ "alias": "sa_start_monitoring",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "",
+ "source": "57eb5348-14d8-560e-996a-8dbf993243e2",
+ "target": "8af21f9b-c21e-5aae-af0d-0958840f6655",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": []
+ }
+ },
+ "description": "",
+ "id": "47730532-0372-5cc5-9d6d-0b8953aebdd8",
+ "name": "State transition: Monitoring"
+ },
+ "4c4c4851-5a3d-5bb9-94df-b19f9cf39819": {
+ "alias": "sa_request_carriage",
+ "body": {
+ "ExchangeAllocation": {
+ "source": "63009b5f-539e-5914-b630-ec6b7fefb4d5",
+ "target": "93474111-f23e-56c1-b61b-b7c2396b91bd"
+ }
+ },
+ "description": "",
+ "id": "4c4c4851-5a3d-5bb9-94df-b19f9cf39819",
+ "name": "Exchange allocation: Request field repair"
+ },
+ "4f85337b-f6eb-533c-a45a-9a5d1d9567ca": {
+ "alias": "sa_client_requires",
+ "body": {
+ "InterfaceRequirement": {
+ "source": "98d411e7-7c65-5c3f-b543-8bf0f2522ea2",
+ "target": "a4b8942e-a975-5f88-ad14-8f38c345baac"
+ }
+ },
+ "description": "",
+ "id": "4f85337b-f6eb-533c-a45a-9a5d1d9567ca",
+ "name": "Interface requirement: Valve repair service"
+ },
+ "55ec4dbf-bd17-5b0c-b3a3-95b2194aa8b9": {
+ "alias": "sa_port_request_out",
+ "body": {
+ "PortAllocation": {
+ "source": "98d411e7-7c65-5c3f-b543-8bf0f2522ea2",
+ "target": "24a6850e-b819-57e0-af88-ca4ab76581b7"
+ }
+ },
+ "description": "",
+ "id": "55ec4dbf-bd17-5b0c-b3a3-95b2194aa8b9",
+ "name": "Port allocation: Request field repair output"
+ },
+ "55f89fbf-aa3c-5ae1-95ef-ac676f9c1ada": {
+ "alias": "sa_provider_provides",
+ "body": {
+ "InterfaceProvision": {
+ "source": "e6d69cbd-99d7-596b-bbe3-dada6118a600",
+ "target": "a4b8942e-a975-5f88-ad14-8f38c345baac"
+ }
+ },
+ "description": "",
+ "id": "55f89fbf-aa3c-5ae1-95ef-ac676f9c1ada",
+ "name": "Interface provision: Valve repair service"
+ },
+ "568c5c01-de21-52a2-83bd-afdddc158085": {
+ "alias": "sa_port_report_in",
+ "body": {
+ "PortAllocation": {
+ "source": "98d411e7-7c65-5c3f-b543-8bf0f2522ea2",
+ "target": "391af7c4-b07d-53a7-8ddd-5c81461fc4f8"
+ }
+ },
+ "description": "",
+ "id": "568c5c01-de21-52a2-83bd-afdddc158085",
+ "name": "Port allocation: Report repair result input"
+ },
+ "594c61cd-94e0-5c07-890d-edfd520d7df9": {
+ "alias": "oa_assess_allocation",
+ "body": {
+ "Allocation": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "61f7bbfb-74a4-5540-9182-2583dfb27a49"
+ }
+ },
+ "description": "",
+ "id": "594c61cd-94e0-5c07-890d-edfd520d7df9",
+ "name": "Allocation: Assess pressure alarm"
+ },
+ "628cb4fc-fe63-5f6b-b364-6268f26b5740": {
+ "alias": "oa_assessment_delivery",
+ "body": {
+ "ChainInvolvement": {
+ "source": "022d3cfa-bbaf-5e13-924a-8e55099b8745",
+ "target": "6a30b763-cc51-546d-907f-5c63657cfd03"
+ }
+ },
+ "description": "",
+ "id": "628cb4fc-fe63-5f6b-b364-6268f26b5740",
+ "name": "Chain involvement: Assess an alarm"
+ },
+ "63009b5f-539e-5914-b630-ec6b7fefb4d5": {
+ "alias": "sa_contact",
+ "body": {
+ "BoundaryExchange": {
+ "items": [
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ ],
+ "oriented": false,
+ "source": "98d411e7-7c65-5c3f-b543-8bf0f2522ea2",
+ "target": "e6d69cbd-99d7-596b-bbe3-dada6118a600"
+ }
+ },
+ "description": "",
+ "id": "63009b5f-539e-5914-b630-ec6b7fefb4d5",
+ "name": "Technician service conversation"
+ },
+ "66efc484-53c7-51a3-89e9-422429344b38": {
+ "alias": "sa_response_delivery",
+ "body": {
+ "ChainInvolvement": {
+ "source": "5139ce73-56fd-5875-ba3a-e569fc890231",
+ "target": "81259051-370a-512d-a3b5-6420a6f933b6"
+ }
+ },
+ "description": "",
+ "id": "66efc484-53c7-51a3-89e9-422429344b38",
+ "name": "Chain involvement: Restore a safe supply"
+ },
+ "684eadc3-e670-5779-a60f-bbc9a5bfb062": {
+ "alias": "sa_repair_report",
+ "body": {
+ "FunctionalExchange": {
+ "items": [
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ ],
+ "source": "9497e10f-04c4-5586-8738-2464a6f00bd4",
+ "source_port": "994faed7-dbc8-5b96-9d62-7604ac0fbc3a",
+ "target": "d75eb3fa-a27d-52d5-8782-8b342b3865f6",
+ "target_port": "391af7c4-b07d-53a7-8ddd-5c81461fc4f8"
+ }
+ },
+ "description": "",
+ "id": "684eadc3-e670-5779-a60f-bbc9a5bfb062",
+ "name": "Report repair result"
+ },
+ "6a6d968f-26d6-5dbb-b721-5a27d362779a": {
+ "alias": "sa_request_then_repair",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "40d94322-baa2-5c81-b261-ff47cdc33782",
+ "target": "0f195fea-3493-5ebd-966d-da9eddeb05d7"
+ }
+ },
+ "description": "",
+ "id": "6a6d968f-26d6-5dbb-b721-5a27d362779a",
+ "name": "Sequence link: Repair the valve"
+ },
+ "6e2c1b47-764c-577a-b325-ff66dd2a2768": {
+ "alias": "sa_normal_transition",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "repair confirmed and pressure is safe",
+ "source": "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37",
+ "target": "8af21f9b-c21e-5aae-af0d-0958840f6655",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": []
+ }
+ },
+ "description": "",
+ "id": "6e2c1b47-764c-577a-b325-ff66dd2a2768",
+ "name": "State transition: Monitoring"
+ },
+ "725f2e1b-aa2c-5615-9b1f-28da178168fa": {
+ "alias": "sa_repair_allocation",
+ "body": {
+ "Allocation": {
+ "source": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a",
+ "target": "9497e10f-04c4-5586-8738-2464a6f00bd4"
+ }
+ },
+ "description": "",
+ "id": "725f2e1b-aa2c-5615-9b1f-28da178168fa",
+ "name": "Allocation: Repair valve in the field"
+ },
+ "76218d89-68fd-531d-b35b-90c5b769a5ef": {
+ "alias": "oa_remote_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "remote reset is authorized",
+ "source": "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "target": "d2026fc2-74a8-5e22-9942-ff3eb68a3677"
+ }
+ },
+ "description": "",
+ "id": "76218d89-68fd-531d-b35b-90c5b769a5ef",
+ "name": "remote reset is authorized"
+ },
+ "7fa37e63-db65-5aa7-88f2-31ff4347a787": {
+ "alias": "oa_assess_on_entry",
+ "body": {
+ "StateAction": {
+ "phase": "Entry",
+ "source": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "target": "61f7bbfb-74a4-5540-9182-2583dfb27a49"
+ }
+ },
+ "description": "",
+ "id": "7fa37e63-db65-5aa7-88f2-31ff4347a787",
+ "name": "Assess when repair is needed"
+ },
+ "7fb30b1d-195c-5f0e-a19c-a536f519a899": {
+ "alias": "sa_repair_then_report",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "0f195fea-3493-5ebd-966d-da9eddeb05d7",
+ "target": "ea32e4ec-8bdc-57ea-8a90-7b09ba4f0eac"
+ }
+ },
+ "description": "",
+ "id": "7fb30b1d-195c-5f0e-a19c-a536f519a899",
+ "name": "Sequence link: Report the measured result"
+ },
+ "93474111-f23e-56c1-b61b-b7c2396b91bd": {
+ "alias": "sa_repair_request",
+ "body": {
+ "FunctionalExchange": {
+ "items": [
+ "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ ],
+ "source": "dddae020-a444-5d48-acc8-139d2696a672",
+ "source_port": "24a6850e-b819-57e0-af88-ca4ab76581b7",
+ "target": "9497e10f-04c4-5586-8738-2464a6f00bd4",
+ "target_port": "d2113fb0-a531-54ef-9484-13951b00ab8f"
+ }
+ },
+ "description": "",
+ "id": "93474111-f23e-56c1-b61b-b7c2396b91bd",
+ "name": "Request field repair"
+ },
+ "93eaad8d-83b7-5980-b9bb-86c9e3f31da4": {
+ "alias": "sa_assessment_support",
+ "body": {
+ "CapabilityInvolvement": {
+ "source": "dddae020-a444-5d48-acc8-139d2696a672",
+ "target": "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f"
+ }
+ },
+ "description": "",
+ "id": "93eaad8d-83b7-5980-b9bb-86c9e3f31da4",
+ "name": "Capability involvement: Assess an alarm"
+ },
+ "9b9356d7-2db2-5a94-a47e-ea892cf02afe": {
+ "alias": "sa_assess_allocation",
+ "body": {
+ "Allocation": {
+ "source": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "target": "dddae020-a444-5d48-acc8-139d2696a672"
+ }
+ },
+ "description": "",
+ "id": "9b9356d7-2db2-5a94-a47e-ea892cf02afe",
+ "name": "Allocation: Assess pressure alarm"
+ },
+ "9d4a131f-36ad-5125-b21f-c3dd033b8c82": {
+ "alias": "sa_assess_then_decide",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "9024243c-ccc1-5edb-8477-2aa16e5e9a50",
+ "target": "30c8fad9-a49b-5692-9542-3148e76afd7b"
+ }
+ },
+ "description": "",
+ "id": "9d4a131f-36ad-5125-b21f-c3dd033b8c82",
+ "name": "Continue"
+ },
+ "a0902e0f-3d18-576a-9aca-975a136dfdd7": {
+ "alias": "sa_mission_delivery",
+ "body": {
+ "MissionCapability": {
+ "source": "5ad034b1-084d-5273-9e70-971671766132",
+ "target": "81259051-370a-512d-a3b5-6420a6f933b6"
+ }
+ },
+ "description": "",
+ "id": "a0902e0f-3d18-576a-9aca-975a136dfdd7",
+ "name": "Mission capability: Restore a safe supply"
+ },
+ "a6d5a6be-fa6e-5fa0-a309-08e87ba40c57": {
+ "alias": "sa_assessment_delivery",
+ "body": {
+ "ChainInvolvement": {
+ "source": "acd5a018-f559-5298-a40d-1cf6e1353e74",
+ "target": "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f"
+ }
+ },
+ "description": "",
+ "id": "a6d5a6be-fa6e-5fa0-a309-08e87ba40c57",
+ "name": "Chain involvement: Assess an alarm"
+ },
+ "a887f96a-37cb-5891-8070-011fa276e5b5": {
+ "alias": "sa_assess_on_entry",
+ "body": {
+ "StateAction": {
+ "phase": "Entry",
+ "source": "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37",
+ "target": "dddae020-a444-5d48-acc8-139d2696a672"
+ }
+ },
+ "description": "",
+ "id": "a887f96a-37cb-5891-8070-011fa276e5b5",
+ "name": "Assess when repair is needed"
+ },
+ "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2": {
+ "alias": "oa_repair_allocation",
+ "body": {
+ "Allocation": {
+ "source": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "target": "a21465af-05f3-5fb5-8983-de26932422fb"
+ }
+ },
+ "description": "",
+ "id": "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2",
+ "name": "Allocation: Repair valve in the field"
+ },
+ "b436d0b3-c824-5a96-af6b-2c269816d1cc": {
+ "alias": "oa_repair_report",
+ "body": {
+ "Interaction": {
+ "items": [
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ ],
+ "source": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "target": "727c0b5c-8db6-5eb7-83d6-735519c5949f"
+ }
+ },
+ "description": "",
+ "id": "b436d0b3-c824-5a96-af6b-2c269816d1cc",
+ "name": "Report repair result"
+ },
+ "b76bf8de-2fa4-563f-a540-dd30f30e6c49": {
+ "alias": "sa_confirm_allocation",
+ "body": {
+ "Allocation": {
+ "source": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "target": "d75eb3fa-a27d-52d5-8782-8b342b3865f6"
+ }
+ },
+ "description": "",
+ "id": "b76bf8de-2fa4-563f-a540-dd30f30e6c49",
+ "name": "Allocation: Confirm safe pressure"
+ },
+ "bac43d3b-910b-5bdc-be9b-f78cf973e1ed": {
+ "alias": "oa_remote_merge",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "target": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d"
+ }
+ },
+ "description": "",
+ "id": "bac43d3b-910b-5bdc-be9b-f78cf973e1ed",
+ "name": "Continue"
+ },
+ "bfd4326e-ea65-5269-b5e6-e063a355d1e9": {
+ "alias": "oa_confirm_allocation",
+ "body": {
+ "Allocation": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "727c0b5c-8db6-5eb7-83d6-735519c5949f"
+ }
+ },
+ "description": "",
+ "id": "bfd4326e-ea65-5269-b5e6-e063a355d1e9",
+ "name": "Allocation: Confirm safe pressure"
+ },
+ "c1adbbd0-a762-5ee3-8b9a-5c18af377771": {
+ "alias": "oa_field_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "else",
+ "source": "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "target": "fc6763bf-a534-54bf-afcd-57ab285ff07d"
+ }
+ },
+ "description": "",
+ "id": "c1adbbd0-a762-5ee3-8b9a-5c18af377771",
+ "name": "else"
+ },
+ "c7c248f8-28b8-55be-9023-0991d44021e3": {
+ "alias": "oa_response_delivery",
+ "body": {
+ "ChainInvolvement": {
+ "source": "58bae2b0-f560-50f2-818d-19091d22d18a",
+ "target": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb"
+ }
+ },
+ "description": "",
+ "id": "c7c248f8-28b8-55be-9023-0991d44021e3",
+ "name": "Chain involvement: Restore a safe supply"
+ },
+ "c8196d35-2168-5fef-8b03-ba0d139bc00f": {
+ "alias": "oa_repair_then_report",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "target": "df2fd3f8-6bc2-5380-a13f-36643ed35ac7"
+ }
+ },
+ "description": "",
+ "id": "c8196d35-2168-5fef-8b03-ba0d139bc00f",
+ "name": "Sequence link: Report the measured result"
+ },
+ "c8593670-8906-530f-b6a2-3ada77a9c219": {
+ "alias": "oa_field_merge",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "target": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d"
+ }
+ },
+ "description": "",
+ "id": "c8593670-8906-530f-b6a2-3ada77a9c219",
+ "name": "Continue"
+ },
+ "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d": {
+ "alias": "oa_repair_request",
+ "body": {
+ "Interaction": {
+ "items": [
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ ],
+ "source": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "target": "a21465af-05f3-5fb5-8983-de26932422fb"
+ }
+ },
+ "description": "",
+ "id": "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "name": "Request field repair"
+ },
+ "d8b6710a-e713-5979-934f-e5ca7d155bb4": {
+ "alias": "sa_remote_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "remote reset is authorized",
+ "source": "30c8fad9-a49b-5692-9542-3148e76afd7b",
+ "target": "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6"
+ }
+ },
+ "description": "",
+ "id": "d8b6710a-e713-5979-934f-e5ca7d155bb4",
+ "name": "remote reset is authorized"
+ },
+ "e21d9a54-ae8e-5618-a4f8-67ee22b92638": {
+ "alias": "oa_include_triage",
+ "body": {
+ "CapabilityInclude": {
+ "source": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "target": "6a30b763-cc51-546d-907f-5c63657cfd03"
+ }
+ },
+ "description": "",
+ "id": "e21d9a54-ae8e-5618-a4f8-67ee22b92638",
+ "name": "Capability include: Assess an alarm"
+ },
+ "e5279265-8eac-52e8-98dc-571b20475e1f": {
+ "alias": "sa_remote_merge",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6",
+ "target": "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07"
+ }
+ },
+ "description": "",
+ "id": "e5279265-8eac-52e8-98dc-571b20475e1f",
+ "name": "Continue"
+ },
+ "e53b7a40-e8a3-5b3f-826d-c2b5c164a84a": {
+ "alias": "sa_port_report_out",
+ "body": {
+ "PortAllocation": {
+ "source": "e6d69cbd-99d7-596b-bbe3-dada6118a600",
+ "target": "994faed7-dbc8-5b96-9d62-7604ac0fbc3a"
+ }
+ },
+ "description": "",
+ "id": "e53b7a40-e8a3-5b3f-826d-c2b5c164a84a",
+ "name": "Port allocation: Report repair result output"
+ },
+ "f074e041-23cf-54ad-af9c-f171e30621e7": {
+ "alias": "sa_repair_interface_item",
+ "body": {
+ "InterfaceItemAllocation": {
+ "acquisition": "Accept",
+ "order": 0,
+ "source": "a4b8942e-a975-5f88-ad14-8f38c345baac",
+ "target": "a962fc5e-3ab0-5296-8b0d-fd84633f9218",
+ "transmission": "Synchronous"
+ }
+ },
+ "description": "",
+ "id": "f074e041-23cf-54ad-af9c-f171e30621e7",
+ "name": "Repair operation contract"
+ },
+ "f3fe7690-734a-5877-8b57-a6599a4033c4": {
+ "alias": "sa_include_triage",
+ "body": {
+ "CapabilityInclude": {
+ "source": "81259051-370a-512d-a3b5-6420a6f933b6",
+ "target": "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f"
+ }
+ },
+ "description": "",
+ "id": "f3fe7690-734a-5877-8b57-a6599a4033c4",
+ "name": "Capability include: Assess an alarm"
+ },
+ "fbde9d5e-c7e9-55db-a2be-497345c491b3": {
+ "alias": "oa_alarm_transition",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "",
+ "source": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "target": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": [
+ "65d6b8d6-271a-5c84-8b42-abbe310c955e"
+ ]
+ }
+ },
+ "description": "",
+ "id": "fbde9d5e-c7e9-55db-a2be-497345c491b3",
+ "name": "State transition: Awaiting repair"
+ },
+ "ff9897b3-a23a-5994-a5b3-99a5b4d34ceb": {
+ "alias": "sa_report_carriage",
+ "body": {
+ "ExchangeAllocation": {
+ "source": "63009b5f-539e-5914-b630-ec6b7fefb4d5",
+ "target": "684eadc3-e670-5779-a60f-bbc9a5bfb062"
+ }
+ },
+ "description": "",
+ "id": "ff9897b3-a23a-5994-a5b3-99a5b4d34ceb",
+ "name": "Exchange allocation: Report repair result"
+ },
+ "ffdbfcd0-d423-5707-a880-0dfbe1400f72": {
+ "alias": "oa_assessment_support",
+ "body": {
+ "CapabilityInvolvement": {
+ "source": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "target": "6a30b763-cc51-546d-907f-5c63657cfd03"
+ }
+ },
+ "description": "",
+ "id": "ffdbfcd0-d423-5707-a880-0dfbe1400f72",
+ "name": "Capability involvement: Assess an alarm"
+ }
+ },
+ "schema_version": 7,
+ "traces": {
+ "0754be23-e4b8-53bc-b03c-89c2dbd80361": {
+ "alias": "trace_oa_waiting",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Awaiting repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Awaiting repair"
+ }
+ },
+ "description": "",
+ "id": "0754be23-e4b8-53bc-b03c-89c2dbd80361",
+ "kind": "State",
+ "name": "Awaiting repair ↔ Awaiting repair",
+ "oa": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "sa": "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37"
+ },
+ "07a7b82a-b5a7-5737-abfa-09e422f7420e": {
+ "alias": "trace_oa_start_monitoring",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "State transition: Monitoring"
+ },
+ "sa": {
+ "description": "",
+ "name": "State transition: Monitoring"
+ }
+ },
+ "description": "",
+ "id": "07a7b82a-b5a7-5737-abfa-09e422f7420e",
+ "kind": "Manual",
+ "name": "State transition: Monitoring ↔ State transition: Monitoring",
+ "oa": "2b00270c-79a5-52a5-af96-33346bea531b",
+ "sa": "47730532-0372-5cc5-9d6d-0b8953aebdd8"
+ },
+ "0c7c8a06-163e-5b3e-8a98-e66e2ce8963b": {
+ "alias": "trace_oa_alarm_transition",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "State transition: Awaiting repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "State transition: Awaiting repair"
+ }
+ },
+ "description": "",
+ "id": "0c7c8a06-163e-5b3e-8a98-e66e2ce8963b",
+ "kind": "Manual",
+ "name": "State transition: Awaiting repair ↔ State transition: Awaiting repair",
+ "oa": "fbde9d5e-c7e9-55db-a2be-497345c491b3",
+ "sa": "13c3bfa4-9254-5ab2-b96f-f65f402adf81"
+ },
+ "0c9de959-d366-5765-98eb-450ea27bd316": {
+ "alias": "trace_oa_merge",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Recovery path completed"
+ },
+ "sa": {
+ "description": "",
+ "name": "Recovery path completed"
+ }
+ },
+ "description": "",
+ "id": "0c9de959-d366-5765-98eb-450ea27bd316",
+ "kind": "Sequence",
+ "name": "Recovery path completed ↔ Recovery path completed",
+ "oa": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "sa": "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07"
+ },
+ "0d905fbb-5b3c-56f0-9f11-a27091901eb0": {
+ "alias": "trace_oa_pressure_parameter",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Pressure before / after repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Pressure before / after repair"
+ }
+ },
+ "description": "",
+ "id": "0d905fbb-5b3c-56f0-9f11-a27091901eb0",
+ "kind": "Item",
+ "name": "Pressure before / after repair ↔ Pressure before / after repair",
+ "oa": "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "sa": "bb12ba5d-aab5-5a1b-99de-c767f5365b57"
+ },
+ "0fbef164-b5d3-58ba-ba41-54d0e63709ab": {
+ "alias": "trace_oa_restore",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Restore a safe supply"
+ },
+ "sa": {
+ "description": "",
+ "name": "Restore a safe supply"
+ }
+ },
+ "description": "",
+ "id": "0fbef164-b5d3-58ba-ba41-54d0e63709ab",
+ "kind": "Capability",
+ "name": "Restore a safe supply ↔ Restore a safe supply",
+ "oa": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "sa": "81259051-370a-512d-a3b5-6420a6f933b6"
+ },
+ "12ec320f-2e14-5a5a-8c6b-bd2b6471dae9": {
+ "alias": "trace_oa_monitoring",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Monitoring"
+ },
+ "sa": {
+ "description": "",
+ "name": "Monitoring"
+ }
+ },
+ "description": "",
+ "id": "12ec320f-2e14-5a5a-8c6b-bd2b6471dae9",
+ "kind": "State",
+ "name": "Monitoring ↔ Monitoring",
+ "oa": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "sa": "8af21f9b-c21e-5aae-af0d-0958840f6655"
+ },
+ "154abfce-c311-52d1-85d1-3f4c85203b26": {
+ "alias": "trace_oa_confirm_step",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Confirm safe pressure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Confirm safe pressure"
+ }
+ },
+ "description": "",
+ "id": "154abfce-c311-52d1-85d1-3f4c85203b26",
+ "kind": "Sequence",
+ "name": "Confirm safe pressure ↔ Confirm safe pressure",
+ "oa": "e00594a3-6122-5a6b-b3c2-ecb5506c741d",
+ "sa": "47bec4f9-d6d0-5ed1-adca-fa0d960dc416"
+ },
+ "1b788e74-36b2-5c17-a936-642dbe51ca3c": {
+ "alias": "trace_oa_remote_merge",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Continue"
+ },
+ "sa": {
+ "description": "",
+ "name": "Continue"
+ }
+ },
+ "description": "",
+ "id": "1b788e74-36b2-5c17-a936-642dbe51ca3c",
+ "kind": "Manual",
+ "name": "Continue ↔ Continue",
+ "oa": "bac43d3b-910b-5bdc-be9b-f78cf973e1ed",
+ "sa": "e5279265-8eac-52e8-98dc-571b20475e1f"
+ },
+ "208753e0-9151-50d8-86ca-29fc28eca80b": {
+ "alias": "trace_oa_monitor_region",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Supervision cycle"
+ },
+ "sa": {
+ "description": "",
+ "name": "Supervision cycle"
+ }
+ },
+ "description": "",
+ "id": "208753e0-9151-50d8-86ca-29fc28eca80b",
+ "kind": "State",
+ "name": "Supervision cycle ↔ Supervision cycle",
+ "oa": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7",
+ "sa": "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c"
+ },
+ "241dfdac-2450-5704-a4cb-9f1047db1b6d": {
+ "alias": "trace_oa_caller",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Controller"
+ },
+ "sa": {
+ "description": "",
+ "name": "Controller"
+ }
+ },
+ "description": "",
+ "id": "241dfdac-2450-5704-a4cb-9f1047db1b6d",
+ "kind": "Scenario",
+ "name": "Controller ↔ Controller",
+ "oa": "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "sa": "98c61788-2fee-51f4-8cf9-de48e8c70e9e"
+ },
+ "2e849e31-35aa-5aef-a989-26948038e52b": {
+ "alias": "trace_oa_field_path",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "else"
+ },
+ "sa": {
+ "description": "",
+ "name": "else"
+ }
+ },
+ "description": "",
+ "id": "2e849e31-35aa-5aef-a989-26948038e52b",
+ "kind": "Manual",
+ "name": "else ↔ else",
+ "oa": "c1adbbd0-a762-5ee3-8b9a-5c18af377771",
+ "sa": "1bbd5957-1173-51d8-97e3-ba4f084227ae"
+ },
+ "2fdcbc6f-526e-5a4e-bde5-314cf0cf3ef3": {
+ "alias": "trace_oa_confirm_allocation",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Allocation: Confirm safe pressure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Allocation: Confirm safe pressure"
+ }
+ },
+ "description": "",
+ "id": "2fdcbc6f-526e-5a4e-bde5-314cf0cf3ef3",
+ "kind": "Manual",
+ "name": "Allocation: Confirm safe pressure ↔ Allocation: Confirm safe pressure",
+ "oa": "bfd4326e-ea65-5269-b5e6-e063a355d1e9",
+ "sa": "b76bf8de-2fa4-563f-a540-dd30f30e6c49"
+ },
+ "3694c752-ba80-5702-a7d7-9cc66b6b01d8": {
+ "alias": "trace_oa_conversation",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Repair request and measured reply"
+ },
+ "sa": {
+ "description": "",
+ "name": "Repair request and measured reply"
+ }
+ },
+ "description": "",
+ "id": "3694c752-ba80-5702-a7d7-9cc66b6b01d8",
+ "kind": "Scenario",
+ "name": "Repair request and measured reply ↔ Repair request and measured reply",
+ "oa": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423",
+ "sa": "730b27ca-b039-5a3b-8dc6-03418f9061b3"
+ },
+ "3714d547-006f-5c3a-97f8-175ccc23d994": {
+ "alias": "trace_oa_repair_allocation",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Allocation: Repair valve in the field"
+ },
+ "sa": {
+ "description": "",
+ "name": "Allocation: Repair valve in the field"
+ }
+ },
+ "description": "",
+ "id": "3714d547-006f-5c3a-97f8-175ccc23d994",
+ "kind": "Manual",
+ "name": "Allocation: Repair valve in the field ↔ Allocation: Repair valve in the field",
+ "oa": "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2",
+ "sa": "725f2e1b-aa2c-5615-9b1f-28da178168fa"
+ },
+ "3a33de40-20a3-5252-899c-c12dc35e9783": {
+ "alias": "trace_oa_assessment_action",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Assess pressure alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Assess pressure alarm"
+ }
+ },
+ "description": "",
+ "id": "3a33de40-20a3-5252-899c-c12dc35e9783",
+ "kind": "Sequence",
+ "name": "Assess pressure alarm ↔ Assess pressure alarm",
+ "oa": "942975b8-0bef-5ae8-9fcb-c37497833fb0",
+ "sa": "bae5b192-5b8e-51c6-82ae-2331af0d33bf"
+ },
+ "3e7bde54-f9c1-549f-9e5b-2ce1ecb685a7": {
+ "alias": "trace_oa_repair_traffic",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Repair work exchanges"
+ },
+ "sa": {
+ "description": "",
+ "name": "Repair work exchanges"
+ }
+ },
+ "description": "",
+ "id": "3e7bde54-f9c1-549f-9e5b-2ce1ecb685a7",
+ "kind": "Manual",
+ "name": "Repair work exchanges ↔ Repair work exchanges",
+ "oa": "9349b353-53f2-56f8-b571-a05b09335cdf",
+ "sa": "a9a47e48-100c-57a8-95f0-7fe24da94f94"
+ },
+ "424eca39-1c29-5a2f-b9ee-33dd018926aa": {
+ "alias": "trace_oa_repair_report",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Report repair result"
+ },
+ "sa": {
+ "description": "",
+ "name": "Report repair result"
+ }
+ },
+ "description": "",
+ "id": "424eca39-1c29-5a2f-b9ee-33dd018926aa",
+ "kind": "Exchange",
+ "name": "Report repair result ↔ Report repair result",
+ "oa": "b436d0b3-c824-5a96-af6b-2c269816d1cc",
+ "sa": "684eadc3-e670-5779-a60f-bbc9a5bfb062"
+ },
+ "435bae4c-cf6d-59f3-9eb5-d660ebeec079": {
+ "alias": "trace_oa_full_scenario",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Restore supply through field repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Restore supply through field repair"
+ }
+ },
+ "description": "",
+ "id": "435bae4c-cf6d-59f3-9eb5-d660ebeec079",
+ "kind": "Scenario",
+ "name": "Restore supply through field repair ↔ Restore supply through field repair",
+ "oa": "d6f881a4-95da-54e9-ad97-1b8965532629",
+ "sa": "f746a1cd-ff9f-55c0-b325-01c03d2f4fae"
+ },
+ "48d243f0-553f-507e-acb0-9474ae156b63": {
+ "alias": "trace_oa_monitor_initial",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Start"
+ },
+ "sa": {
+ "description": "",
+ "name": "Start"
+ }
+ },
+ "description": "",
+ "id": "48d243f0-553f-507e-acb0-9474ae156b63",
+ "kind": "State",
+ "name": "Start ↔ Start",
+ "oa": "a7ffb869-9dde-5e76-b276-c719c5c143a9",
+ "sa": "57eb5348-14d8-560e-996a-8dbf993243e2"
+ },
+ "5309f955-53fd-5ff4-9c0a-91737e947341": {
+ "alias": "trace_oa_assess",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Assess pressure alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Assess pressure alarm"
+ }
+ },
+ "description": "",
+ "id": "5309f955-53fd-5ff4-9c0a-91737e947341",
+ "kind": "Refinement",
+ "name": "Assess pressure alarm ↔ Assess pressure alarm",
+ "oa": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "sa": "dddae020-a444-5d48-acc8-139d2696a672"
+ },
+ "595b5729-ccd9-5291-b5d9-30d8c8bd9dfa": {
+ "alias": "trace_oa_reply",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Return pressure after repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Return pressure after repair"
+ }
+ },
+ "description": "",
+ "id": "595b5729-ccd9-5291-b5d9-30d8c8bd9dfa",
+ "kind": "Scenario",
+ "name": "Return pressure after repair ↔ Return pressure after repair",
+ "oa": "cc2d497a-fd15-5455-9ee5-e70eb95811d8",
+ "sa": "4e1c0a56-ce80-5f13-a93a-6956de54880c"
+ },
+ "5b96bfed-63e2-5d7f-b882-91e8b81f8efe": {
+ "alias": "trace_oa_assessment_delivery",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Chain involvement: Assess an alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Chain involvement: Assess an alarm"
+ }
+ },
+ "description": "",
+ "id": "5b96bfed-63e2-5d7f-b882-91e8b81f8efe",
+ "kind": "Manual",
+ "name": "Chain involvement: Assess an alarm ↔ Chain involvement: Assess an alarm",
+ "oa": "628cb4fc-fe63-5f6b-b364-6268f26b5740",
+ "sa": "a6d5a6be-fa6e-5fa0-a309-08e87ba40c57"
+ },
+ "6acd072d-431e-5881-ab20-1813b4fda0c5": {
+ "alias": "trace_oa_triage",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Assess an alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Assess an alarm"
+ }
+ },
+ "description": "",
+ "id": "6acd072d-431e-5881-ab20-1813b4fda0c5",
+ "kind": "Capability",
+ "name": "Assess an alarm ↔ Assess an alarm",
+ "oa": "6a30b763-cc51-546d-907f-5c63657cfd03",
+ "sa": "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f"
+ },
+ "6ca4c78a-a5f2-5470-b401-732f5a8825d0": {
+ "alias": "trace_oa_assessment_procedure",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Assess an alarm procedure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Assess an alarm procedure"
+ }
+ },
+ "description": "",
+ "id": "6ca4c78a-a5f2-5470-b401-732f5a8825d0",
+ "kind": "Sequence",
+ "name": "Assess an alarm procedure ↔ Assess an alarm procedure",
+ "oa": "022d3cfa-bbaf-5e13-924a-8e55099b8745",
+ "sa": "acd5a018-f559-5298-a40d-1cf6e1353e74"
+ },
+ "6e13cf86-7242-5e33-8912-b037cf6e8c87": {
+ "alias": "trace_oa_assessment_support",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Capability involvement: Assess an alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Capability involvement: Assess an alarm"
+ }
+ },
+ "description": "",
+ "id": "6e13cf86-7242-5e33-8912-b037cf6e8c87",
+ "kind": "Manual",
+ "name": "Capability involvement: Assess an alarm ↔ Capability involvement: Assess an alarm",
+ "oa": "ffdbfcd0-d423-5707-a880-0dfbe1400f72",
+ "sa": "93eaad8d-83b7-5980-b9bb-86c9e3f31da4"
+ },
+ "70c90b0c-85ce-574a-a217-f0d19fad30e1": {
+ "alias": "trace_oa_response_delivery",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Chain involvement: Restore a safe supply"
+ },
+ "sa": {
+ "description": "",
+ "name": "Chain involvement: Restore a safe supply"
+ }
+ },
+ "description": "",
+ "id": "70c90b0c-85ce-574a-a217-f0d19fad30e1",
+ "kind": "Manual",
+ "name": "Chain involvement: Restore a safe supply ↔ Chain involvement: Restore a safe supply",
+ "oa": "c7c248f8-28b8-55be-9023-0991d44021e3",
+ "sa": "66efc484-53c7-51a3-89e9-422429344b38"
+ },
+ "77073fcb-53aa-5f06-987a-0317cbb6971d": {
+ "alias": "trace_oa_include_triage",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Capability include: Assess an alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Capability include: Assess an alarm"
+ }
+ },
+ "description": "",
+ "id": "77073fcb-53aa-5f06-987a-0317cbb6971d",
+ "kind": "Manual",
+ "name": "Capability include: Assess an alarm ↔ Capability include: Assess an alarm",
+ "oa": "e21d9a54-ae8e-5618-a4f8-67ee22b92638",
+ "sa": "f3fe7690-734a-5877-8b57-a6599a4033c4"
+ },
+ "7a6b4b9c-4220-546a-a752-1587c2881fe1": {
+ "alias": "trace_oa_use_field",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Use the field repair procedure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Use the field repair procedure"
+ }
+ },
+ "description": "",
+ "id": "7a6b4b9c-4220-546a-a752-1587c2881fe1",
+ "kind": "Sequence",
+ "name": "Use the field repair procedure ↔ Use the field repair procedure",
+ "oa": "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "sa": "d5e61c5d-510b-5091-a878-da46f3a9e152"
+ },
+ "82a5be1a-92a8-578d-9448-086062a20d8f": {
+ "alias": "trace_oa_reset",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Reset valve remotely"
+ },
+ "sa": {
+ "description": "",
+ "name": "Reset valve remotely"
+ }
+ },
+ "description": "",
+ "id": "82a5be1a-92a8-578d-9448-086062a20d8f",
+ "kind": "Refinement",
+ "name": "Reset valve remotely ↔ Reset valve remotely",
+ "oa": "d0c8be87-d238-503a-a549-7558a210507f",
+ "sa": "09342ccb-044b-5483-ac58-87dc3cf7831c"
+ },
+ "82cca563-9d5d-5479-aa1a-9db03e9674d1": {
+ "alias": "trace_oa_restore_work",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Restore safe pressure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Restore safe pressure"
+ }
+ },
+ "description": "",
+ "id": "82cca563-9d5d-5479-aa1a-9db03e9674d1",
+ "kind": "Refinement",
+ "name": "Restore safe pressure ↔ Restore safe pressure",
+ "oa": "1b91f8a7-0bdb-514e-b1cd-955658e09927",
+ "sa": "58867e7d-916e-5ebc-9e4c-8dbed1314be3"
+ },
+ "8d693d90-5017-56af-b056-d29d3cb46baa": {
+ "alias": "trace_oa_mission_participant",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Mission involvement: Maintain safe water supply"
+ },
+ "sa": {
+ "description": "",
+ "name": "Mission involvement: Maintain safe water supply"
+ }
+ },
+ "description": "",
+ "id": "8d693d90-5017-56af-b056-d29d3cb46baa",
+ "kind": "Manual",
+ "name": "Mission involvement: Maintain safe water supply ↔ Mission involvement: Maintain safe water supply",
+ "oa": "0c4c9e58-ff32-5c00-b82a-d408a4be0183",
+ "sa": "1c1e5634-ba8c-591f-b143-90a27cdc6cd2"
+ },
+ "98ef05b5-5381-5d35-a433-ebab55e9f382": {
+ "alias": "trace_oa_reset_allocation",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Allocation: Reset valve remotely"
+ },
+ "sa": {
+ "description": "",
+ "name": "Allocation: Reset valve remotely"
+ }
+ },
+ "description": "",
+ "id": "98ef05b5-5381-5d35-a433-ebab55e9f382",
+ "kind": "Manual",
+ "name": "Allocation: Reset valve remotely ↔ Allocation: Reset valve remotely",
+ "oa": "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a",
+ "sa": "3cc9ab52-2b00-57fd-8709-aabc75e5d3e3"
+ },
+ "a106d183-c239-5c9f-b1f1-141af19fc778": {
+ "alias": "trace_oa_confirm_path",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Continue"
+ },
+ "sa": {
+ "description": "",
+ "name": "Continue"
+ }
+ },
+ "description": "",
+ "id": "a106d183-c239-5c9f-b1f1-141af19fc778",
+ "kind": "Manual",
+ "name": "Continue ↔ Continue",
+ "oa": "229ee94f-d147-50f6-9769-b5bd0c03a084",
+ "sa": "1e738f27-a3a1-526d-b330-6384088b9689"
+ },
+ "a5c1eb01-a95f-5f7a-9742-7f201493d7aa": {
+ "alias": "trace_oa_assess_then_decide",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Continue"
+ },
+ "sa": {
+ "description": "",
+ "name": "Continue"
+ }
+ },
+ "description": "",
+ "id": "a5c1eb01-a95f-5f7a-9742-7f201493d7aa",
+ "kind": "Manual",
+ "name": "Continue ↔ Continue",
+ "oa": "109e2d6b-7e86-5934-93a5-fab56eced84e",
+ "sa": "9d4a131f-36ad-5125-b21f-c3dd033b8c82"
+ },
+ "aa164208-394d-5bc7-8462-b0043f682aad": {
+ "alias": "trace_oa_mission_delivery",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Mission capability: Restore a safe supply"
+ },
+ "sa": {
+ "description": "",
+ "name": "Mission capability: Restore a safe supply"
+ }
+ },
+ "description": "",
+ "id": "aa164208-394d-5bc7-8462-b0043f682aad",
+ "kind": "Manual",
+ "name": "Mission capability: Restore a safe supply ↔ Mission capability: Restore a safe supply",
+ "oa": "1f8b1898-65f6-5360-99cf-b78a71bd50ec",
+ "sa": "a0902e0f-3d18-576a-9aca-975a136dfdd7"
+ },
+ "aba528ea-e396-582a-bfba-f6e2212d391a": {
+ "alias": "trace_oa_control",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Control room"
+ },
+ "sa": {
+ "description": "",
+ "name": "Pressure coordination service"
+ }
+ },
+ "description": "",
+ "id": "aba528ea-e396-582a-bfba-f6e2212d391a",
+ "kind": "Participant",
+ "name": "Control room ↔ Pressure coordination service",
+ "oa": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "sa": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0"
+ },
+ "ac3aa6b0-568e-5a3f-963a-172aa3cfc7a4": {
+ "alias": "trace_oa_repair_step",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Repair the valve"
+ },
+ "sa": {
+ "description": "",
+ "name": "Repair the valve"
+ }
+ },
+ "description": "",
+ "id": "ac3aa6b0-568e-5a3f-963a-172aa3cfc7a4",
+ "kind": "Sequence",
+ "name": "Repair the valve ↔ Repair the valve",
+ "oa": "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "sa": "0f195fea-3493-5ebd-966d-da9eddeb05d7"
+ },
+ "acdbee9c-5017-5154-804e-50801f0376ec": {
+ "alias": "trace_oa_assess_allocation",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Allocation: Assess pressure alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Allocation: Assess pressure alarm"
+ }
+ },
+ "description": "",
+ "id": "acdbee9c-5017-5154-804e-50801f0376ec",
+ "kind": "Manual",
+ "name": "Allocation: Assess pressure alarm ↔ Allocation: Assess pressure alarm",
+ "oa": "594c61cd-94e0-5c07-890d-edfd520d7df9",
+ "sa": "9b9356d7-2db2-5a94-a47e-ea892cf02afe"
+ },
+ "ad697c00-07d9-5799-89b3-2c78da8dff4d": {
+ "alias": "trace_oa_request_step",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Request the field repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Request the field repair"
+ }
+ },
+ "description": "",
+ "id": "ad697c00-07d9-5799-89b3-2c78da8dff4d",
+ "kind": "Sequence",
+ "name": "Request the field repair ↔ Request the field repair",
+ "oa": "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "sa": "40d94322-baa2-5c81-b261-ff47cdc33782"
+ },
+ "b13452bb-1a55-5ad6-b6d9-a37fb34e8d13": {
+ "alias": "trace_oa_repair",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Repair valve in the field"
+ },
+ "sa": {
+ "description": "",
+ "name": "Repair valve in the field"
+ }
+ },
+ "description": "",
+ "id": "b13452bb-1a55-5ad6-b6d9-a37fb34e8d13",
+ "kind": "Refinement",
+ "name": "Repair valve in the field ↔ Repair valve in the field",
+ "oa": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "sa": "9497e10f-04c4-5586-8738-2464a6f00bd4"
+ },
+ "b53863a2-ed80-5f98-ad3c-50e1b821066d": {
+ "alias": "trace_oa_monitor",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Pressure supervision"
+ },
+ "sa": {
+ "description": "",
+ "name": "Pressure supervision"
+ }
+ },
+ "description": "",
+ "id": "b53863a2-ed80-5f98-ad3c-50e1b821066d",
+ "kind": "State",
+ "name": "Pressure supervision ↔ Pressure supervision",
+ "oa": "1970608f-85d1-5234-ac86-5e2b0df27dc3",
+ "sa": "132b3f28-3f88-5327-9868-8c8b645ed412"
+ },
+ "b97749d4-f9ad-5fb7-9d78-bc60a74e98eb": {
+ "alias": "trace_oa_high_pressure",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Pressure alarm detected"
+ },
+ "sa": {
+ "description": "",
+ "name": "Pressure alarm detected"
+ }
+ },
+ "description": "",
+ "id": "b97749d4-f9ad-5fb7-9d78-bc60a74e98eb",
+ "kind": "State",
+ "name": "Pressure alarm detected ↔ Pressure alarm detected",
+ "oa": "65d6b8d6-271a-5c84-8b42-abbe310c955e",
+ "sa": "01490fa8-2f01-5ac8-9722-d55d45414379"
+ },
+ "bb72b2c2-6e32-5f65-9d8e-b33c1a65da37": {
+ "alias": "trace_oa_field_procedure",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Field repair procedure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Field repair procedure"
+ }
+ },
+ "description": "",
+ "id": "bb72b2c2-6e32-5f65-9d8e-b33c1a65da37",
+ "kind": "Sequence",
+ "name": "Field repair procedure ↔ Field repair procedure",
+ "oa": "5d3af417-244c-54bb-9588-16daedda4e1f",
+ "sa": "93eb6479-54e9-59cb-ac7b-16912347bfc0"
+ },
+ "bbf6f1a1-cb2b-5632-8ec0-bf4b8ef1a941": {
+ "alias": "trace_oa_decision",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Can a remote reset safely recover pressure?"
+ },
+ "sa": {
+ "description": "",
+ "name": "Can a remote reset safely recover pressure?"
+ }
+ },
+ "description": "",
+ "id": "bbf6f1a1-cb2b-5632-8ec0-bf4b8ef1a941",
+ "kind": "Sequence",
+ "name": "Can a remote reset safely recover pressure? ↔ Can a remote reset safely recover pressure?",
+ "oa": "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "sa": "30c8fad9-a49b-5692-9542-3148e76afd7b"
+ },
+ "c132a0df-1270-55a8-ac4e-6e5075823dc1": {
+ "alias": "trace_oa_outer_technician",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Technician"
+ },
+ "sa": {
+ "description": "",
+ "name": "Technician"
+ }
+ },
+ "description": "",
+ "id": "c132a0df-1270-55a8-ac4e-6e5075823dc1",
+ "kind": "Scenario",
+ "name": "Technician ↔ Technician",
+ "oa": "fffff882-78fd-5415-928d-c1ef7fac6576",
+ "sa": "155e4fe4-9e3b-5f4e-b886-28abcc3f086b"
+ },
+ "c47cf0b4-fe15-5175-bd62-5292e43adc39": {
+ "alias": "trace_oa_normal_transition",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "State transition: Monitoring"
+ },
+ "sa": {
+ "description": "",
+ "name": "State transition: Monitoring"
+ }
+ },
+ "description": "",
+ "id": "c47cf0b4-fe15-5175-bd62-5292e43adc39",
+ "kind": "Manual",
+ "name": "State transition: Monitoring ↔ State transition: Monitoring",
+ "oa": "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97",
+ "sa": "6e2c1b47-764c-577a-b325-ff66dd2a2768"
+ },
+ "c5051628-b7af-5fb1-a014-bb60a0b46e03": {
+ "alias": "trace_oa_responder",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Technician"
+ },
+ "sa": {
+ "description": "",
+ "name": "Technician"
+ }
+ },
+ "description": "",
+ "id": "c5051628-b7af-5fb1-a014-bb60a0b46e03",
+ "kind": "Scenario",
+ "name": "Technician ↔ Technician",
+ "oa": "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "sa": "6249b54a-e396-5338-82b9-89266c55670d"
+ },
+ "c987bd0f-87f6-5550-945d-2a42450fe1c5": {
+ "alias": "trace_oa_mission",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Maintain safe water supply"
+ },
+ "sa": {
+ "description": "",
+ "name": "Maintain safe water supply"
+ }
+ },
+ "description": "",
+ "id": "c987bd0f-87f6-5550-945d-2a42450fe1c5",
+ "kind": "Mission",
+ "name": "Maintain safe water supply ↔ Maintain safe water supply",
+ "oa": "c9c61f50-06e0-5d17-973d-dcc57cd5cc90",
+ "sa": "5ad034b1-084d-5273-9e70-971671766132"
+ },
+ "c9d65349-3400-58eb-8a55-57fa02de4b67": {
+ "alias": "trace_oa_contact",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Technician service conversation"
+ },
+ "sa": {
+ "description": "",
+ "name": "Technician service conversation"
+ }
+ },
+ "description": "",
+ "id": "c9d65349-3400-58eb-8a55-57fa02de4b67",
+ "kind": "Exchange",
+ "name": "Technician service conversation ↔ Technician service conversation",
+ "oa": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "sa": "63009b5f-539e-5914-b630-ec6b7fefb4d5"
+ },
+ "cf92cedf-ea1a-510c-9bc5-89c16d60e8d2": {
+ "alias": "trace_oa_repair_operation",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Valve repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Valve repair"
+ }
+ },
+ "description": "",
+ "id": "cf92cedf-ea1a-510c-9bc5-89c16d60e8d2",
+ "kind": "Item",
+ "name": "Valve repair ↔ Valve repair",
+ "oa": "d736c7d6-d369-550a-8ba4-dae223e40bb3",
+ "sa": "a962fc5e-3ab0-5296-8b0d-fd84633f9218"
+ },
+ "d32dd433-9c9e-5b57-9355-2970cc44a5f2": {
+ "alias": "trace_oa_field_merge",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Continue"
+ },
+ "sa": {
+ "description": "",
+ "name": "Continue"
+ }
+ },
+ "description": "",
+ "id": "d32dd433-9c9e-5b57-9355-2970cc44a5f2",
+ "kind": "Manual",
+ "name": "Continue ↔ Continue",
+ "oa": "c8593670-8906-530f-b6a2-3ada77a9c219",
+ "sa": "0f6ac3fb-4ad6-581e-ad14-336472072d5e"
+ },
+ "d3626818-4846-5893-9c02-3407cdba7f3c": {
+ "alias": "trace_oa_response",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Handle a pressure alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Handle a pressure alarm"
+ }
+ },
+ "description": "",
+ "id": "d3626818-4846-5893-9c02-3407cdba7f3c",
+ "kind": "Sequence",
+ "name": "Handle a pressure alarm ↔ Handle a pressure alarm",
+ "oa": "58bae2b0-f560-50f2-818d-19091d22d18a",
+ "sa": "5139ce73-56fd-5875-ba3a-e569fc890231"
+ },
+ "d47d3c36-50fb-5189-b801-d6fd1694bd49": {
+ "alias": "trace_oa_technician",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Field technician"
+ },
+ "sa": {
+ "description": "",
+ "name": "Field technician"
+ }
+ },
+ "description": "",
+ "id": "d47d3c36-50fb-5189-b801-d6fd1694bd49",
+ "kind": "Participant",
+ "name": "Field technician ↔ Field technician",
+ "oa": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "sa": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a"
+ },
+ "d7773ff5-cef5-50c1-8f9d-c5659115e1f0": {
+ "alias": "trace_oa_confirm",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Confirm safe pressure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Confirm safe pressure"
+ }
+ },
+ "description": "",
+ "id": "d7773ff5-cef5-50c1-8f9d-c5659115e1f0",
+ "kind": "Refinement",
+ "name": "Confirm safe pressure ↔ Confirm safe pressure",
+ "oa": "727c0b5c-8db6-5eb7-83d6-735519c5949f",
+ "sa": "d75eb3fa-a27d-52d5-8782-8b342b3865f6"
+ },
+ "d9d47371-e98d-5d29-9df7-4de2d28a5c73": {
+ "alias": "trace_oa_pressure",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Measured pressure"
+ },
+ "sa": {
+ "description": "",
+ "name": "Measured pressure"
+ }
+ },
+ "description": "",
+ "id": "d9d47371-e98d-5d29-9df7-4de2d28a5c73",
+ "kind": "Item",
+ "name": "Measured pressure ↔ Measured pressure",
+ "oa": "e7824764-5db9-5384-8198-2fbaea2b10b2",
+ "sa": "87d0a95b-af1b-5352-b753-e4c6533671a5"
+ },
+ "da8efc3c-78df-5b10-b2e5-1e28d6d99e86": {
+ "alias": "trace_oa_repair_request",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Request field repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Request field repair"
+ }
+ },
+ "description": "",
+ "id": "da8efc3c-78df-5b10-b2e5-1e28d6d99e86",
+ "kind": "Exchange",
+ "name": "Request field repair ↔ Request field repair",
+ "oa": "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "sa": "93474111-f23e-56c1-b61b-b7c2396b91bd"
+ },
+ "e63b14d2-f408-56a2-aec4-be20cf4ace73": {
+ "alias": "trace_oa_report_step",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Report the measured result"
+ },
+ "sa": {
+ "description": "",
+ "name": "Report the measured result"
+ }
+ },
+ "description": "",
+ "id": "e63b14d2-f408-56a2-aec4-be20cf4ace73",
+ "kind": "Sequence",
+ "name": "Report the measured result ↔ Report the measured result",
+ "oa": "df2fd3f8-6bc2-5380-a13f-36643ed35ac7",
+ "sa": "ea32e4ec-8bdc-57ea-8a90-7b09ba4f0eac"
+ },
+ "e848da41-445e-5e3c-8b4e-ef53fdddf1e1": {
+ "alias": "trace_oa_assess_step",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Assess the alarm"
+ },
+ "sa": {
+ "description": "",
+ "name": "Assess the alarm"
+ }
+ },
+ "description": "",
+ "id": "e848da41-445e-5e3c-8b4e-ef53fdddf1e1",
+ "kind": "Sequence",
+ "name": "Assess the alarm ↔ Assess the alarm",
+ "oa": "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "sa": "9024243c-ccc1-5edb-8477-2aa16e5e9a50"
+ },
+ "e96ff119-bb4d-5fd9-9ab7-2ede0d3fd418": {
+ "alias": "trace_oa_contact_category",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Field contact"
+ },
+ "sa": {
+ "description": "",
+ "name": "Field contact"
+ }
+ },
+ "description": "",
+ "id": "e96ff119-bb4d-5fd9-9ab7-2ede0d3fd418",
+ "kind": "Manual",
+ "name": "Field contact ↔ Field contact",
+ "oa": "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771",
+ "sa": "781cbe75-410f-5cc5-aeb8-a43491f77b99"
+ },
+ "eee79ec1-41d7-5374-986e-995a56b9026f": {
+ "alias": "trace_oa_remote_path",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "remote reset is authorized"
+ },
+ "sa": {
+ "description": "",
+ "name": "remote reset is authorized"
+ }
+ },
+ "description": "",
+ "id": "eee79ec1-41d7-5374-986e-995a56b9026f",
+ "kind": "Manual",
+ "name": "remote reset is authorized ↔ remote reset is authorized",
+ "oa": "76218d89-68fd-531d-b35b-90c5b769a5ef",
+ "sa": "d8b6710a-e713-5979-934f-e5ca7d155bb4"
+ },
+ "f0cc86e5-f8fd-5091-a40d-dbfb6515072f": {
+ "alias": "trace_oa_call",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Request valve repair"
+ },
+ "sa": {
+ "description": "",
+ "name": "Request valve repair"
+ }
+ },
+ "description": "",
+ "id": "f0cc86e5-f8fd-5091-a40d-dbfb6515072f",
+ "kind": "Scenario",
+ "name": "Request valve repair ↔ Request valve repair",
+ "oa": "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "sa": "8bab4783-a098-5984-9e36-493957caf26c"
+ },
+ "f2b7050c-629d-533f-b873-bffc33e072f7": {
+ "alias": "trace_oa_request_then_repair",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Sequence link: Repair the valve"
+ },
+ "sa": {
+ "description": "",
+ "name": "Sequence link: Repair the valve"
+ }
+ },
+ "description": "",
+ "id": "f2b7050c-629d-533f-b873-bffc33e072f7",
+ "kind": "Manual",
+ "name": "Sequence link: Repair the valve ↔ Sequence link: Repair the valve",
+ "oa": "3f54d890-4ca5-5e36-873b-abb5c92d578c",
+ "sa": "6a6d968f-26d6-5dbb-b721-5a27d362779a"
+ },
+ "f5733d3f-eee3-5cf7-a422-5edd2054d6a6": {
+ "alias": "trace_oa_use_conversation",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Use the reviewed repair conversation"
+ },
+ "sa": {
+ "description": "",
+ "name": "Use the reviewed repair conversation"
+ }
+ },
+ "description": "",
+ "id": "f5733d3f-eee3-5cf7-a422-5edd2054d6a6",
+ "kind": "Scenario",
+ "name": "Use the reviewed repair conversation ↔ Use the reviewed repair conversation",
+ "oa": "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5",
+ "sa": "1c9da843-e2d3-5591-8e3f-db083a42867e"
+ },
+ "f8d34715-1dc8-504b-acdf-78b770aad1e8": {
+ "alias": "trace_oa_repair_then_report",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Sequence link: Report the measured result"
+ },
+ "sa": {
+ "description": "",
+ "name": "Sequence link: Report the measured result"
+ }
+ },
+ "description": "",
+ "id": "f8d34715-1dc8-504b-acdf-78b770aad1e8",
+ "kind": "Manual",
+ "name": "Sequence link: Report the measured result ↔ Sequence link: Report the measured result",
+ "oa": "c8196d35-2168-5fef-8b03-ba0d139bc00f",
+ "sa": "7fb30b1d-195c-5f0e-a19c-a536f519a899"
+ },
+ "fb02420d-b066-5773-87c1-198a056e0731": {
+ "alias": "trace_oa_assess_on_entry",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Assess when repair is needed"
+ },
+ "sa": {
+ "description": "",
+ "name": "Assess when repair is needed"
+ }
+ },
+ "description": "",
+ "id": "fb02420d-b066-5773-87c1-198a056e0731",
+ "kind": "Manual",
+ "name": "Assess when repair is needed ↔ Assess when repair is needed",
+ "oa": "7fa37e63-db65-5aa7-88f2-31ff4347a787",
+ "sa": "a887f96a-37cb-5891-8070-011fa276e5b5"
+ },
+ "fd195498-dbba-523b-94d0-49f3b9531a3e": {
+ "alias": "trace_oa_outer_controller",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Controller"
+ },
+ "sa": {
+ "description": "",
+ "name": "Controller"
+ }
+ },
+ "description": "",
+ "id": "fd195498-dbba-523b-94d0-49f3b9531a3e",
+ "kind": "Scenario",
+ "name": "Controller ↔ Controller",
+ "oa": "2011b3ac-614b-5a47-8891-7ff81965f425",
+ "sa": "1edfcf9e-9bfa-51f1-80d5-62b8fd8bb023"
+ },
+ "ff84fc06-6f99-5cf3-b73d-236e9eff98a3": {
+ "alias": "trace_oa_reset_step",
+ "baseline": {
+ "oa": {
+ "description": "",
+ "name": "Try the authorized remote reset"
+ },
+ "sa": {
+ "description": "",
+ "name": "Try the authorized remote reset"
+ }
+ },
+ "description": "",
+ "id": "ff84fc06-6f99-5cf3-b73d-236e9eff98a3",
+ "kind": "Sequence",
+ "name": "Try the authorized remote reset ↔ Try the authorized remote reset",
+ "oa": "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "sa": "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6"
+ }
+ }
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.rs b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.rs
new file mode 100644
index 00000000..e7a3e98b
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/model.rs
@@ -0,0 +1,1040 @@
+//! Reproducible teaching model. The SA counterpart is produced by the real planner.
+use oa_core::{source_project, transitions::*, validation, view_options::*, *};
+use std::collections::BTreeMap;
+
+pub fn id(alias: &str) -> Id {
+ Id::new_v5(
+ &Id::NAMESPACE_URL,
+ format!("https://arcadia-studio.example/lessons/pressure/{alias}").as_bytes(),
+ )
+}
+fn element(
+ p: &mut Project,
+ alias: &str,
+ name: &str,
+ kind: Kind,
+ layer: Layer,
+ parent: Option,
+) -> Id {
+ let id = id(alias);
+ p.elements.insert(
+ id,
+ Element {
+ id,
+ alias: alias.into(),
+ name: name.into(),
+ description: String::new(),
+ parent,
+ body: ElementBody::new(kind, layer).unwrap(),
+ },
+ );
+ id
+}
+fn shared(p: &mut Project, id: Id, body: SharedBody) {
+ *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = body;
+}
+fn relation(p: &mut Project, alias: &str, name: &str, body: RelationBody) -> Id {
+ let id = id(alias);
+ p.relations.insert(
+ id,
+ Relation {
+ id,
+ alias: alias.into(),
+ name: name.into(),
+ description: String::new(),
+ body,
+ },
+ );
+ id
+}
+fn link(p: &mut Project, alias: &str, kind: RelationKind, source: Id, target: Id) -> Id {
+ let name = format!("{kind}: {}", p.named(target));
+ relation(p, alias, &name, RelationBody::new(kind, source, target))
+}
+fn occurrence(p: &mut Project, owner: Id, alias: &str, name: &str, body: OccurrenceBody) -> Id {
+ let layer = p.elements[&owner].layer();
+ let id = element(p, alias, name, Kind::Occurrence, layer, Some(owner));
+ shared(
+ p,
+ id,
+ SharedBody::Occurrence {
+ body,
+ precondition: String::new(),
+ postcondition: String::new(),
+ },
+ );
+ p.elements
+ .get_mut(&owner)
+ .unwrap()
+ .steps_mut()
+ .unwrap()
+ .push(id);
+ id
+}
+fn line(p: &mut Project, owner: Id, alias: &str, name: &str, represented: Id) -> Id {
+ let id = element(p, alias, name, Kind::Lifeline, Layer::Oa, Some(owner));
+ shared(
+ p,
+ id,
+ SharedBody::Lifeline {
+ represented: Some(represented),
+ },
+ );
+ id
+}
+fn scenario(p: &mut Project, alias: &str, name: &str, capability: Id) -> Id {
+ let id = element(p, alias, name, Kind::Scenario, Layer::Oa, None);
+ shared(
+ p,
+ id,
+ SharedBody::Scenario {
+ viewpoint: ScenarioViewpoint::ParticipantInteraction,
+ context: Some(capability),
+ precondition: "A pressure alarm has been assessed".into(),
+ postcondition: "The controller has received the measured pressure after repair".into(),
+ steps: vec![],
+ },
+ );
+ id
+}
+fn message(
+ exchange: Id,
+ sender: Id,
+ receiver: Id,
+ reply_to: Option,
+ field: Id,
+ value: &str,
+) -> OccurrenceBody {
+ OccurrenceBody::Exchange {
+ operation: None,
+ exchange: Some(exchange),
+ sender: Some(sender),
+ receiver: Some(receiver),
+ message_kind: if reply_to.is_some() {
+ MessageKind::Reply
+ } else {
+ MessageKind::Call
+ },
+ reply_to,
+ values: vec![MessageValue {
+ path: vec![],
+ field,
+ expression: value.into(),
+ }],
+ }
+}
+fn diagram(
+ p: &mut Project,
+ alias: &str,
+ name: &str,
+ layer: Layer,
+ projection: ProjectionKind,
+ members: &[(&str, f32, f32)],
+) -> Id {
+ let diagram_id = id(alias);
+ let nodes = members
+ .iter()
+ .map(|(member, x, y)| {
+ let alias = format!("{alias}_{member}");
+ let node = NodePlacement {
+ id: id(&alias),
+ alias,
+ element: id(member),
+ position: Position { x: *x, y: *y },
+ size: Size {
+ width: 245.,
+ height: 110.,
+ },
+ collapsed: false,
+ port_anchors: vec![],
+ };
+ (node.id, node)
+ })
+ .collect();
+ p.diagrams.insert(diagram_id, Diagram { id: diagram_id, alias: alias.into(), name: name.into(), description: "Teaching view: positions and filters do not change ownership or exchange identities.".into(), layer, projection, view_options: ViewOptions::default(), nodes, routes: vec![] });
+ diagram_id
+}
+
+pub struct Lesson {
+ pub project: Project,
+ pub before_transition: Project,
+ pub request: TransitionRequest,
+}
+
+pub fn build() -> Lesson {
+ let mut p = Project::empty();
+ p.id = id("pressure_restoration");
+ p.alias = "pressure_restoration".into();
+ p.name = "Restore safe water pressure — release 1 walkthrough".into();
+ p.description = "A controller either resets a valve remotely or requests field repair. Compare the operational need with the explicitly chosen coordination-service boundary. Start with README.md beside project.arcadia. Units and guards are descriptive; this model does not simulate a real water network.".into();
+ let control = element(
+ &mut p,
+ "oa_control",
+ "Control room",
+ Kind::Entity,
+ Layer::Oa,
+ None,
+ );
+ let technician = element(
+ &mut p,
+ "oa_technician",
+ "Field technician",
+ Kind::Actor,
+ Layer::Oa,
+ None,
+ );
+ let parent = element(
+ &mut p,
+ "oa_restore_work",
+ "Restore safe pressure",
+ Kind::Activity,
+ Layer::Oa,
+ None,
+ );
+ let mut works = vec![];
+ for (alias, name, performer) in [
+ ("oa_assess", "Assess pressure alarm", control),
+ ("oa_reset", "Reset valve remotely", control),
+ ("oa_repair", "Repair valve in the field", technician),
+ ("oa_confirm", "Confirm safe pressure", control),
+ ] {
+ let work = element(&mut p, alias, name, Kind::Activity, Layer::Oa, Some(parent));
+ link(
+ &mut p,
+ &format!("{alias}_allocation"),
+ RelationKind::Allocation,
+ performer,
+ work,
+ );
+ works.push(work);
+ }
+ let [assess, reset, repair, confirm] = works[..] else {
+ unreachable!()
+ };
+ let mission = element(
+ &mut p,
+ "oa_mission",
+ "Maintain safe water supply",
+ Kind::OperationalMission,
+ Layer::Oa,
+ None,
+ );
+ let capability = element(
+ &mut p,
+ "oa_restore",
+ "Restore a safe supply",
+ Kind::Capability,
+ Layer::Oa,
+ None,
+ );
+ let triage = element(
+ &mut p,
+ "oa_triage",
+ "Assess an alarm",
+ Kind::Capability,
+ Layer::Oa,
+ None,
+ );
+ link(
+ &mut p,
+ "oa_mission_delivery",
+ RelationKind::MissionCapability,
+ mission,
+ capability,
+ );
+ link(
+ &mut p,
+ "oa_mission_participant",
+ RelationKind::MissionInvolvement,
+ control,
+ mission,
+ );
+ link(
+ &mut p,
+ "oa_include_triage",
+ RelationKind::CapabilityInclude,
+ capability,
+ triage,
+ );
+ link(
+ &mut p,
+ "oa_assessment_support",
+ RelationKind::CapabilityInvolvement,
+ assess,
+ triage,
+ );
+
+ let pressure = element(
+ &mut p,
+ "oa_pressure",
+ "Measured pressure",
+ Kind::DataType,
+ Layer::Oa,
+ None,
+ );
+ shared(
+ &mut p,
+ pressure,
+ SharedBody::DataType {
+ definition: DataTypeDefinition::Real {
+ minimum: Some(0.),
+ maximum: Some(600.),
+ },
+ default: None,
+ unit: "kPa".into(),
+ },
+ );
+ let ticket = element(
+ &mut p,
+ "oa_repair_operation",
+ "Valve repair",
+ Kind::ExchangeItem,
+ Layer::Oa,
+ None,
+ );
+ shared(
+ &mut p,
+ ticket,
+ SharedBody::ExchangeItem {
+ kind: ExchangeItemKind::Information,
+ mechanism: ExchangeMechanism::Operation,
+ supertype: None,
+ },
+ );
+ let measured = element(
+ &mut p,
+ "oa_pressure_parameter",
+ "Pressure before / after repair",
+ Kind::DataField,
+ Layer::Oa,
+ Some(ticket),
+ );
+ shared(
+ &mut p,
+ measured,
+ SharedBody::DataField {
+ type_ref: TypeRef::Named(pressure),
+ minimum: 1,
+ maximum: Some(1),
+ unit: String::new(),
+ direction: ParameterDirection::InOut,
+ storage: FieldStorage::Value,
+ default: None,
+ },
+ );
+ let request_exchange = relation(
+ &mut p,
+ "oa_repair_request",
+ "Request field repair",
+ RelationBody::Interaction {
+ source: assess,
+ target: repair,
+ items: vec![ticket],
+ },
+ );
+ let report_exchange = relation(
+ &mut p,
+ "oa_repair_report",
+ "Report repair result",
+ RelationBody::Interaction {
+ source: repair,
+ target: confirm,
+ items: vec![ticket],
+ },
+ );
+ let channel = relation(
+ &mut p,
+ "oa_contact",
+ "Technician service conversation",
+ RelationBody::CommunicationMean {
+ source: control,
+ target: technician,
+ oriented: false,
+ items: vec![ticket],
+ },
+ );
+ for (alias, exchange) in [
+ ("oa_request_carriage", request_exchange),
+ ("oa_report_carriage", report_exchange),
+ ] {
+ link(
+ &mut p,
+ alias,
+ RelationKind::ExchangeAllocation,
+ channel,
+ exchange,
+ );
+ }
+ for (alias, name, family, exchanges) in [
+ (
+ "oa_repair_traffic",
+ "Repair work exchanges",
+ ExchangeFamily::Work,
+ vec![request_exchange, report_exchange],
+ ),
+ (
+ "oa_contact_category",
+ "Field contact",
+ ExchangeFamily::Participant,
+ vec![channel],
+ ),
+ ] {
+ let category = element(&mut p, alias, name, Kind::ExchangeCategory, Layer::Oa, None);
+ shared(
+ &mut p,
+ category,
+ SharedBody::ExchangeCategory { family, exchanges },
+ );
+ }
+
+ let assessment = element(
+ &mut p,
+ "oa_assessment_procedure",
+ "Assess an alarm procedure",
+ Kind::Process,
+ Layer::Oa,
+ None,
+ );
+ occurrence(
+ &mut p,
+ assessment,
+ "oa_assessment_action",
+ "Assess pressure alarm",
+ OccurrenceBody::Action {
+ action: Some(assess),
+ lifeline: None,
+ },
+ );
+ link(
+ &mut p,
+ "oa_assessment_delivery",
+ RelationKind::ChainInvolvement,
+ assessment,
+ triage,
+ );
+ let field = element(
+ &mut p,
+ "oa_field_procedure",
+ "Field repair procedure",
+ Kind::Process,
+ Layer::Oa,
+ None,
+ );
+ let ask = occurrence(
+ &mut p,
+ field,
+ "oa_request_step",
+ "Request the field repair",
+ OccurrenceBody::Exchange {
+ operation: None,
+ exchange: Some(request_exchange),
+ sender: None,
+ receiver: None,
+ message_kind: MessageKind::Asynchronous,
+ reply_to: None,
+ values: vec![],
+ },
+ );
+ let fix = occurrence(
+ &mut p,
+ field,
+ "oa_repair_step",
+ "Repair the valve",
+ OccurrenceBody::Action {
+ action: Some(repair),
+ lifeline: None,
+ },
+ );
+ let report = occurrence(
+ &mut p,
+ field,
+ "oa_report_step",
+ "Report the measured result",
+ OccurrenceBody::Exchange {
+ operation: None,
+ exchange: Some(report_exchange),
+ sender: None,
+ receiver: None,
+ message_kind: MessageKind::Asynchronous,
+ reply_to: None,
+ values: vec![],
+ },
+ );
+ link(
+ &mut p,
+ "oa_request_then_repair",
+ RelationKind::SequenceLink,
+ ask,
+ fix,
+ );
+ link(
+ &mut p,
+ "oa_repair_then_report",
+ RelationKind::SequenceLink,
+ fix,
+ report,
+ );
+ let process = element(
+ &mut p,
+ "oa_response",
+ "Handle a pressure alarm",
+ Kind::Process,
+ Layer::Oa,
+ None,
+ );
+ link(
+ &mut p,
+ "oa_response_delivery",
+ RelationKind::ChainInvolvement,
+ process,
+ capability,
+ );
+ let start = occurrence(
+ &mut p,
+ process,
+ "oa_assess_step",
+ "Assess the alarm",
+ OccurrenceBody::SequenceReference {
+ sequence: Some(assessment),
+ },
+ );
+ let choice = occurrence(
+ &mut p,
+ process,
+ "oa_decision",
+ "Can a remote reset safely recover pressure?",
+ OccurrenceBody::Control {
+ kind: ControlKind::Choice,
+ },
+ );
+ let remote = occurrence(
+ &mut p,
+ process,
+ "oa_reset_step",
+ "Try the authorized remote reset",
+ OccurrenceBody::Action {
+ action: Some(reset),
+ lifeline: None,
+ },
+ );
+ let reuse = occurrence(
+ &mut p,
+ process,
+ "oa_use_field",
+ "Use the field repair procedure",
+ OccurrenceBody::SequenceReference {
+ sequence: Some(field),
+ },
+ );
+ let merge = occurrence(
+ &mut p,
+ process,
+ "oa_merge",
+ "Recovery path completed",
+ OccurrenceBody::Control {
+ kind: ControlKind::Merge,
+ },
+ );
+ let end = occurrence(
+ &mut p,
+ process,
+ "oa_confirm_step",
+ "Confirm safe pressure",
+ OccurrenceBody::Action {
+ action: Some(confirm),
+ lifeline: None,
+ },
+ );
+ for (alias, a, b, guard) in [
+ ("oa_assess_then_decide", start, choice, ""),
+ (
+ "oa_remote_path",
+ choice,
+ remote,
+ "remote reset is authorized",
+ ),
+ ("oa_field_path", choice, reuse, "else"),
+ ("oa_remote_merge", remote, merge, ""),
+ ("oa_field_merge", reuse, merge, ""),
+ ("oa_confirm_path", merge, end, ""),
+ ] {
+ relation(
+ &mut p,
+ alias,
+ if guard.is_empty() { "Continue" } else { guard },
+ RelationBody::SequenceLink {
+ source: a,
+ target: b,
+ guard: guard.into(),
+ },
+ );
+ }
+ let conversation = scenario(
+ &mut p,
+ "oa_conversation",
+ "Repair request and measured reply",
+ capability,
+ );
+ let caller = line(&mut p, conversation, "oa_caller", "Controller", control);
+ let responder = line(
+ &mut p,
+ conversation,
+ "oa_responder",
+ "Technician",
+ technician,
+ );
+ let call = occurrence(
+ &mut p,
+ conversation,
+ "oa_call",
+ "Request valve repair",
+ message(channel, caller, responder, None, measured, "420.0"),
+ );
+ occurrence(
+ &mut p,
+ conversation,
+ "oa_reply",
+ "Return pressure after repair",
+ message(channel, responder, caller, Some(call), measured, "320.0"),
+ );
+ let full = scenario(
+ &mut p,
+ "oa_full_scenario",
+ "Restore supply through field repair",
+ capability,
+ );
+ let outer_control = line(&mut p, full, "oa_outer_controller", "Controller", control);
+ let outer_tech = line(
+ &mut p,
+ full,
+ "oa_outer_technician",
+ "Technician",
+ technician,
+ );
+ occurrence(
+ &mut p,
+ full,
+ "oa_use_conversation",
+ "Use the reviewed repair conversation",
+ OccurrenceBody::ScenarioUse {
+ scenario: Some(conversation),
+ bindings: vec![
+ LifelineBinding {
+ referenced: caller,
+ local: outer_control,
+ },
+ LifelineBinding {
+ referenced: responder,
+ local: outer_tech,
+ },
+ ],
+ },
+ );
+
+ let machine = element(
+ &mut p,
+ "oa_monitor",
+ "Pressure supervision",
+ Kind::StateMachine,
+ Layer::Oa,
+ Some(control),
+ );
+ let region = element(
+ &mut p,
+ "oa_monitor_region",
+ "Supervision cycle",
+ Kind::Region,
+ Layer::Oa,
+ Some(machine),
+ );
+ let initial = element(
+ &mut p,
+ "oa_monitor_initial",
+ "Start",
+ Kind::Initial,
+ Layer::Oa,
+ Some(region),
+ );
+ let watch = element(
+ &mut p,
+ "oa_monitoring",
+ "Monitoring",
+ Kind::State,
+ Layer::Oa,
+ Some(region),
+ );
+ let waiting = element(
+ &mut p,
+ "oa_waiting",
+ "Awaiting repair",
+ Kind::State,
+ Layer::Oa,
+ Some(region),
+ );
+ let high = element(
+ &mut p,
+ "oa_high_pressure",
+ "Pressure alarm detected",
+ Kind::Event,
+ Layer::Oa,
+ None,
+ );
+ shared(
+ &mut p,
+ high,
+ SharedBody::Event {
+ body: EventBody::Change {
+ expression: "measured_pressure > operating_limit".into(),
+ },
+ },
+ );
+ link(
+ &mut p,
+ "oa_start_monitoring",
+ RelationKind::StateTransition,
+ initial,
+ watch,
+ );
+ let alarm = link(
+ &mut p,
+ "oa_alarm_transition",
+ RelationKind::StateTransition,
+ watch,
+ waiting,
+ );
+ if let RelationBody::StateTransition { triggers, .. } =
+ &mut p.relations.get_mut(&alarm).unwrap().body
+ {
+ triggers.push(high);
+ }
+ let normal = link(
+ &mut p,
+ "oa_normal_transition",
+ RelationKind::StateTransition,
+ waiting,
+ watch,
+ );
+ if let RelationBody::StateTransition { guard, .. } =
+ &mut p.relations.get_mut(&normal).unwrap().body
+ {
+ *guard = "repair confirmed and pressure is safe".into();
+ }
+ relation(
+ &mut p,
+ "oa_assess_on_entry",
+ "Assess when repair is needed",
+ RelationBody::StateAction {
+ source: waiting,
+ target: assess,
+ phase: StateActionKind::Entry,
+ },
+ );
+
+ // The engineer chooses this system boundary; the planner must never infer it.
+ let system = element(
+ &mut p,
+ "sa_control",
+ "Pressure coordination service",
+ Kind::System,
+ Layer::Sa,
+ None,
+ );
+ let external = element(
+ &mut p,
+ "sa_technician",
+ "Field technician",
+ Kind::SystemActor,
+ Layer::Sa,
+ None,
+ );
+ let mut request = TransitionRequest::new(
+ TransitionDirection::OaToSa,
+ p.elements
+ .values()
+ .filter(|e| e.layer() == Layer::Oa)
+ .map(|e| e.id)
+ .chain(
+ p.relations
+ .values()
+ .filter(|r| r.kind() != RelationKind::ExchangeAllocation)
+ .map(|r| r.id),
+ )
+ .collect(),
+ );
+ request.fields.clear(); // Existing selected participants retain their deliberately different names.
+ request.mappings = vec![
+ TransitionMapping {
+ source: control,
+ target: Some(system),
+ target_kind: None,
+ },
+ TransitionMapping {
+ source: technician,
+ target: Some(external),
+ target_kind: None,
+ },
+ ];
+ let port = |alias: &str, name: &str| CommunicationPortChoice::Create {
+ id: id(alias),
+ alias: alias.into(),
+ name: name.into(),
+ description: "Explicitly chosen Standard component port for the repair service.".into(),
+ kind: ComponentPortKind::Standard,
+ orientation: ComponentPortOrientation::Unset,
+ };
+ request
+ .communication_endpoints
+ .push(CommunicationEndpointDecision {
+ source: channel,
+ source_port: port("sa_control_port", "Repair service client"),
+ target_port: port("sa_technician_port", "Repair service provider"),
+ });
+ let before_transition = p.clone();
+ let plan = plan_transition(&p, &request).expect("well-formed teaching transition");
+ assert!(
+ plan.conflicts.is_empty(),
+ "teaching transition conflicts: {:?}",
+ plan.conflicts
+ );
+ p = apply_transition(&p, &plan).expect("teaching transition is structurally valid");
+
+ // Canonical lesson aliases/UUIDs replace only generator-created identities.
+ // The real planner still determines every counterpart and function-port contract.
+ for change in &plan.changes {
+ let alias = before_transition
+ .identities()
+ .into_iter()
+ .find(|(id, _)| *id == change.source)
+ .unwrap()
+ .1
+ .replacen("oa_", "sa_", 1);
+ if let Some(e) = p.elements.get_mut(&change.target) {
+ e.alias = alias.clone();
+ }
+ if let Some(r) = p.relations.get_mut(&change.target) {
+ r.alias = alias;
+ }
+ }
+ let functions: Vec<_> = p
+ .relations
+ .values()
+ .filter_map(|r| {
+ if let RelationBody::FunctionalExchange {
+ source_port,
+ target_port,
+ ..
+ } = r.body
+ {
+ Some((r.alias.clone(), source_port, target_port))
+ } else {
+ None
+ }
+ })
+ .collect();
+ for (alias, out, input) in functions {
+ p.elements.get_mut(&out).unwrap().alias = format!("{alias}_out");
+ p.elements.get_mut(&input).unwrap().alias = format!("{alias}_in");
+ }
+ let aliases: BTreeMap<_, _> = p
+ .identities()
+ .into_iter()
+ .map(|(id, alias)| (id, alias.to_owned()))
+ .collect();
+ for trace in p.traces.values_mut() {
+ trace.alias = format!("trace_{}", aliases[&trace.oa]);
+ }
+ let identities: Vec<_> = p
+ .identities()
+ .into_iter()
+ .map(|(old, alias)| (old, id(alias)))
+ .collect();
+ for (old, new) in identities {
+ session::reidentify(&mut p, old, new).unwrap();
+ }
+
+ let interface = element(
+ &mut p,
+ "sa_repair_interface",
+ "Valve repair service",
+ Kind::Interface,
+ Layer::Sa,
+ None,
+ );
+ relation(
+ &mut p,
+ "sa_repair_interface_item",
+ "Repair operation contract",
+ RelationBody::InterfaceItemAllocation {
+ source: interface,
+ target: id("sa_repair_operation"),
+ order: 0,
+ transmission: TransmissionProtocol::Synchronous,
+ acquisition: AcquisitionProtocol::Accept,
+ },
+ );
+ link(
+ &mut p,
+ "sa_client_requires",
+ RelationKind::InterfaceRequirement,
+ id("sa_control_port"),
+ interface,
+ );
+ link(
+ &mut p,
+ "sa_provider_provides",
+ RelationKind::InterfaceProvision,
+ id("sa_technician_port"),
+ interface,
+ );
+ for (name, component, port) in [
+ ("request_out", "sa_control_port", "sa_repair_request_out"),
+ ("request_in", "sa_technician_port", "sa_repair_request_in"),
+ ("report_out", "sa_technician_port", "sa_repair_report_out"),
+ ("report_in", "sa_control_port", "sa_repair_report_in"),
+ ] {
+ link(
+ &mut p,
+ &format!("sa_port_{name}"),
+ RelationKind::PortAllocation,
+ id(component),
+ id(port),
+ );
+ }
+ for (alias, exchange) in [
+ ("sa_request_carriage", "sa_repair_request"),
+ ("sa_report_carriage", "sa_repair_report"),
+ ] {
+ link(
+ &mut p,
+ alias,
+ RelationKind::ExchangeAllocation,
+ id("sa_contact"),
+ id(exchange),
+ );
+ }
+ let audit = element(
+ &mut p,
+ "sa_audit_thresholds",
+ "Audit pressure thresholds",
+ Kind::Function,
+ Layer::Sa,
+ None,
+ );
+ p.elements.get_mut(&audit).unwrap().description = "Deliberately unfinished: assigned to the service, but no behavior context yet. Model checks should expose this one work_without_behavior_context warning.".into();
+ link(
+ &mut p,
+ "sa_audit_allocation",
+ RelationKind::Allocation,
+ id("sa_control"),
+ audit,
+ );
+ let evidence = id("repair_test_evidence");
+ p.artifact_links.insert(
+ evidence,
+ ArtifactLink {
+ id: evidence,
+ alias: "repair_test_evidence".into(),
+ name: "Repair conversation acceptance notes".into(),
+ description: "A local teaching artifact, not proof of a real valve's safety.".into(),
+ owner: id("sa_conversation"),
+ relation: ArtifactRelation::Verifies,
+ uri: "evidence/repair-check.md".into(),
+ revision: Some("lesson-1".into()),
+ fragment: Some("acceptance".into()),
+ },
+ );
+
+ let layout = [
+ ("control", 60., 80.),
+ ("technician", 850., 80.),
+ ("assess", 100., 170.),
+ ("reset", 100., 330.),
+ ("confirm", 100., 490.),
+ ("repair", 890., 170.),
+ ];
+ for prefix in ["oa", "sa"] {
+ let names: Vec<_> = layout
+ .iter()
+ .map(|(n, x, y)| (format!("{prefix}_{n}"), *x, *y))
+ .collect();
+ let members: Vec<_> = names.iter().map(|(n, x, y)| (n.as_str(), *x, *y)).collect();
+ diagram(
+ &mut p,
+ &format!("{prefix}_allocation_view"),
+ if prefix == "oa" {
+ "1 · OA responsibility and repair traffic"
+ } else {
+ "2 · SA service boundary and functions"
+ },
+ if prefix == "oa" { Layer::Oa } else { Layer::Sa },
+ ProjectionKind::Allocation,
+ &members,
+ );
+ }
+ let mut focused = p.diagrams[&id("sa_allocation_view")].clone();
+ focused.id = id("sa_service_view");
+ focused.alias = "sa_service_view".into();
+ focused.name = "3 · SA service conversation only".into();
+ focused.nodes = focused
+ .nodes
+ .into_values()
+ .map(|mut n| {
+ n.alias = n.alias.replacen("sa_allocation_view", "sa_service_view", 1);
+ n.id = id(&n.alias);
+ (n.id, n)
+ })
+ .collect();
+ focused.view_options.work_categories = CategorySelection::Any(vec![]);
+ focused.view_options.participant_categories =
+ CategorySelection::Any(vec![id("sa_contact_category")]);
+ focused.view_options.exchange_labels = ExchangeLabels::NameAndItems;
+ p.diagrams.insert(focused.id, focused);
+ diagram(
+ &mut p,
+ "sa_capability_view",
+ "4 · SA mission and included capability",
+ Layer::Sa,
+ ProjectionKind::Capabilities,
+ &[
+ ("sa_control", 60., 60.),
+ ("sa_mission", 420., 60.),
+ ("sa_restore", 420., 260.),
+ ("sa_triage", 800., 260.),
+ ],
+ );
+ diagram(
+ &mut p,
+ "sa_state_view",
+ "5 · SA pressure supervision",
+ Layer::Sa,
+ ProjectionKind::StateMachine {
+ machine: id("sa_monitor"),
+ },
+ &[
+ ("sa_monitor", 30., 30.),
+ ("sa_monitor_region", 70., 95.),
+ ("sa_monitor_initial", 100., 200.),
+ ("sa_monitoring", 220., 200.),
+ ("sa_waiting", 650., 200.),
+ ],
+ );
+ Lesson {
+ project: p,
+ before_transition,
+ request,
+ }
+}
+
+pub fn checked_bundle(project: &Project) -> source_project::SourceBundle {
+ let issues = validation::validate(project);
+ let errors: Vec<_> = issues
+ .iter()
+ .filter(|i| i.severity == validation::Severity::Error)
+ .collect();
+ assert!(errors.is_empty(), "teaching model errors: {errors:?}");
+ let bundle = source_project::format_bundle(project);
+ let compiled =
+ source_project::compile_bundle(&bundle, source_project::ProjectLimits::default()).unwrap();
+ assert_eq!(&compiled.decoded.project, project);
+ bundle
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/activities/activity_oa_restore_work.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/activities/activity_oa_restore_work.arcadia
new file mode 100644
index 00000000..78781701
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/activities/activity_oa_restore_work.arcadia
@@ -0,0 +1,45 @@
+oa_activity oa_restore_work "Restore safe pressure" id "1b91f8a7-0bdb-514e-b1cd-955658e09927" {
+ description "";
+ parent none;
+}
+
+oa_activity oa_assess "Assess pressure alarm" id "61f7bbfb-74a4-5540-9182-2583dfb27a49" {
+ description "";
+ parent @oa_restore_work;
+}
+
+oa_activity oa_confirm "Confirm safe pressure" id "727c0b5c-8db6-5eb7-83d6-735519c5949f" {
+ description "";
+ parent @oa_restore_work;
+}
+
+oa_activity oa_repair "Repair valve in the field" id "a21465af-05f3-5fb5-8983-de26932422fb" {
+ description "";
+ parent @oa_restore_work;
+}
+
+oa_activity oa_reset "Reset valve remotely" id "d0c8be87-d238-503a-a549-7558a210507f" {
+ description "";
+ parent @oa_restore_work;
+}
+
+capability_involvement oa_assessment_support "Capability involvement: Assess an alarm" id "ffdbfcd0-d423-5707-a880-0dfbe1400f72" {
+ description "";
+ source @oa_assess;
+ target @oa_triage;
+}
+
+interaction oa_repair_request "Request field repair" id "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d" {
+ description "";
+ items [@oa_repair_operation];
+ source @oa_assess;
+ target @oa_repair;
+}
+
+interaction oa_repair_report "Report repair result" id "b436d0b3-c824-5a96-af6b-2c269816d1cc" {
+ description "";
+ items [@oa_repair_operation];
+ source @oa_repair;
+ target @oa_confirm;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_assessment_procedure.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_assessment_procedure.arcadia
new file mode 100644
index 00000000..4ab5bda8
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_assessment_procedure.arcadia
@@ -0,0 +1,25 @@
+oa_process oa_assessment_procedure "Assess an alarm procedure" id "022d3cfa-bbaf-5e13-924a-8e55099b8745" {
+ description "";
+ parent none;
+ steps [@oa_assessment_action];
+}
+
+oa_occurrence oa_assessment_action "Assess pressure alarm" id "942975b8-0bef-5ae8-9fcb-c37497833fb0" {
+ body {
+ Action {
+ action @oa_assess;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @oa_assessment_procedure;
+ postcondition "";
+ precondition "";
+}
+
+chain_involvement oa_assessment_delivery "Chain involvement: Assess an alarm" id "628cb4fc-fe63-5f6b-b364-6268f26b5740" {
+ description "";
+ source @oa_assessment_procedure;
+ target @oa_triage;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_field_procedure.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_field_procedure.arcadia
new file mode 100644
index 00000000..d639717f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_field_procedure.arcadia
@@ -0,0 +1,67 @@
+oa_process oa_field_procedure "Field repair procedure" id "5d3af417-244c-54bb-9588-16daedda4e1f" {
+ description "";
+ parent none;
+ steps [@oa_request_step, @oa_repair_step, @oa_report_step];
+}
+
+oa_occurrence oa_repair_step "Repair the valve" id "c59432fe-6092-5ee0-908d-9dbf39476471" {
+ body {
+ Action {
+ action @oa_repair;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @oa_field_procedure;
+ postcondition "";
+ precondition "";
+}
+
+oa_occurrence oa_report_step "Report the measured result" id "df2fd3f8-6bc2-5380-a13f-36643ed35ac7" {
+ body {
+ Exchange {
+ exchange @oa_repair_report;
+ message_kind Asynchronous;
+ receiver none;
+ reply_to none;
+ sender none;
+ values [];
+ };
+ };
+ description "";
+ parent @oa_field_procedure;
+ postcondition "";
+ precondition "";
+}
+
+oa_occurrence oa_request_step "Request the field repair" id "5ba8079c-2dbe-5e98-ae82-2b60c6020a02" {
+ body {
+ Exchange {
+ exchange @oa_repair_request;
+ message_kind Asynchronous;
+ receiver none;
+ reply_to none;
+ sender none;
+ values [];
+ };
+ };
+ description "";
+ parent @oa_field_procedure;
+ postcondition "";
+ precondition "";
+}
+
+sequence_link oa_repair_then_report "Sequence link: Report the measured result" id "c8196d35-2168-5fef-8b03-ba0d139bc00f" {
+ description "";
+ guard "";
+ source @oa_repair_step;
+ target @oa_report_step;
+}
+
+sequence_link oa_request_then_repair "Sequence link: Repair the valve" id "3f54d890-4ca5-5e36-873b-abb5c92d578c" {
+ description "";
+ guard "";
+ source @oa_request_step;
+ target @oa_repair_step;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_response.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_response.arcadia
new file mode 100644
index 00000000..b4cf4c29
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/process_oa_response.arcadia
@@ -0,0 +1,128 @@
+oa_process oa_response "Handle a pressure alarm" id "58bae2b0-f560-50f2-818d-19091d22d18a" {
+ description "";
+ parent none;
+ steps [@oa_assess_step, @oa_decision, @oa_reset_step, @oa_use_field, @oa_merge, @oa_confirm_step];
+}
+
+oa_occurrence oa_assess_step "Assess the alarm" id "0b07b9ed-492d-57f6-b0b1-ce8a709238a7" {
+ body {
+ SequenceReference {
+ sequence @oa_assessment_procedure;
+ };
+ };
+ description "";
+ parent @oa_response;
+ postcondition "";
+ precondition "";
+}
+
+oa_occurrence oa_confirm_step "Confirm safe pressure" id "e00594a3-6122-5a6b-b3c2-ecb5506c741d" {
+ body {
+ Action {
+ action @oa_confirm;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @oa_response;
+ postcondition "";
+ precondition "";
+}
+
+oa_occurrence oa_decision "Can a remote reset safely recover pressure?" id "b280c6ca-e5f9-5424-a5c1-5676e01c2371" {
+ body {
+ Control {
+ kind Choice;
+ };
+ };
+ description "";
+ parent @oa_response;
+ postcondition "";
+ precondition "";
+}
+
+oa_occurrence oa_merge "Recovery path completed" id "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d" {
+ body {
+ Control {
+ kind Merge;
+ };
+ };
+ description "";
+ parent @oa_response;
+ postcondition "";
+ precondition "";
+}
+
+oa_occurrence oa_reset_step "Try the authorized remote reset" id "d2026fc2-74a8-5e22-9942-ff3eb68a3677" {
+ body {
+ Action {
+ action @oa_reset;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @oa_response;
+ postcondition "";
+ precondition "";
+}
+
+oa_occurrence oa_use_field "Use the field repair procedure" id "fc6763bf-a534-54bf-afcd-57ab285ff07d" {
+ body {
+ SequenceReference {
+ sequence @oa_field_procedure;
+ };
+ };
+ description "";
+ parent @oa_response;
+ postcondition "";
+ precondition "";
+}
+
+sequence_link oa_assess_then_decide "Continue" id "109e2d6b-7e86-5934-93a5-fab56eced84e" {
+ description "";
+ guard "";
+ source @oa_assess_step;
+ target @oa_decision;
+}
+
+sequence_link oa_field_path "else" id "c1adbbd0-a762-5ee3-8b9a-5c18af377771" {
+ description "";
+ guard "else";
+ source @oa_decision;
+ target @oa_use_field;
+}
+
+sequence_link oa_remote_path "remote reset is authorized" id "76218d89-68fd-531d-b35b-90c5b769a5ef" {
+ description "";
+ guard "remote reset is authorized";
+ source @oa_decision;
+ target @oa_reset_step;
+}
+
+sequence_link oa_confirm_path "Continue" id "229ee94f-d147-50f6-9769-b5bd0c03a084" {
+ description "";
+ guard "";
+ source @oa_merge;
+ target @oa_confirm_step;
+}
+
+sequence_link oa_remote_merge "Continue" id "bac43d3b-910b-5bdc-be9b-f78cf973e1ed" {
+ description "";
+ guard "";
+ source @oa_reset_step;
+ target @oa_merge;
+}
+
+chain_involvement oa_response_delivery "Chain involvement: Restore a safe supply" id "c7c248f8-28b8-55be-9023-0991d44021e3" {
+ description "";
+ source @oa_response;
+ target @oa_restore;
+}
+
+sequence_link oa_field_merge "Continue" id "c8593670-8906-530f-b6a2-3ada77a9c219" {
+ description "";
+ guard "";
+ source @oa_use_field;
+ target @oa_merge;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_conversation.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_conversation.arcadia
new file mode 100644
index 00000000..89470e88
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_conversation.arcadia
@@ -0,0 +1,64 @@
+oa_scenario oa_conversation "Repair request and measured reply" id "20472233-5ce6-5ffe-b4ad-0bdbccf3e423" {
+ context @oa_restore;
+ description "";
+ parent none;
+ postcondition "The controller has received the measured pressure after repair";
+ precondition "A pressure alarm has been assessed";
+ steps [@oa_call, @oa_reply];
+ viewpoint ParticipantInteraction;
+}
+
+oa_occurrence oa_call "Request valve repair" id "b97437c6-cd7a-5a05-b537-b30a17dd936a" {
+ body {
+ Exchange {
+ exchange @oa_contact;
+ message_kind Call;
+ receiver @oa_responder;
+ reply_to none;
+ sender @oa_caller;
+ values [{
+ expression "420.0";
+ field @oa_pressure_parameter;
+ path [];
+ }];
+ };
+ };
+ description "";
+ parent @oa_conversation;
+ postcondition "";
+ precondition "";
+}
+
+oa_lifeline oa_caller "Controller" id "41859a65-ded2-547b-ab03-14e2fa740cf9" {
+ description "";
+ parent @oa_conversation;
+ represented @oa_control;
+}
+
+oa_occurrence oa_reply "Return pressure after repair" id "cc2d497a-fd15-5455-9ee5-e70eb95811d8" {
+ body {
+ Exchange {
+ exchange @oa_contact;
+ message_kind Reply;
+ receiver @oa_caller;
+ reply_to @oa_call;
+ sender @oa_responder;
+ values [{
+ expression "320.0";
+ field @oa_pressure_parameter;
+ path [];
+ }];
+ };
+ };
+ description "";
+ parent @oa_conversation;
+ postcondition "";
+ precondition "";
+}
+
+oa_lifeline oa_responder "Technician" id "fc81c5f0-0442-5b88-992b-e5b10f351c59" {
+ description "";
+ parent @oa_conversation;
+ represented @oa_technician;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_full_scenario.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_full_scenario.arcadia
new file mode 100644
index 00000000..33c9aad1
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/scenario_oa_full_scenario.arcadia
@@ -0,0 +1,41 @@
+oa_scenario oa_full_scenario "Restore supply through field repair" id "d6f881a4-95da-54e9-ad97-1b8965532629" {
+ context @oa_restore;
+ description "";
+ parent none;
+ postcondition "The controller has received the measured pressure after repair";
+ precondition "A pressure alarm has been assessed";
+ steps [@oa_use_conversation];
+ viewpoint ParticipantInteraction;
+}
+
+oa_lifeline oa_outer_controller "Controller" id "2011b3ac-614b-5a47-8891-7ff81965f425" {
+ description "";
+ parent @oa_full_scenario;
+ represented @oa_control;
+}
+
+oa_lifeline oa_outer_technician "Technician" id "fffff882-78fd-5415-928d-c1ef7fac6576" {
+ description "";
+ parent @oa_full_scenario;
+ represented @oa_technician;
+}
+
+oa_occurrence oa_use_conversation "Use the reviewed repair conversation" id "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5" {
+ body {
+ ScenarioUse {
+ bindings [{
+ local @oa_outer_controller;
+ referenced @oa_caller;
+ }, {
+ local @oa_outer_technician;
+ referenced @oa_responder;
+ }];
+ scenario @oa_conversation;
+ };
+ };
+ description "";
+ parent @oa_full_scenario;
+ postcondition "";
+ precondition "";
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/state_machine_oa_monitor.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/state_machine_oa_monitor.arcadia
new file mode 100644
index 00000000..aeaa90cf
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/behavior/state_machine_oa_monitor.arcadia
@@ -0,0 +1,80 @@
+oa_state_machine oa_monitor "Pressure supervision" id "1970608f-85d1-5234-ac86-5e2b0df27dc3" {
+ description "";
+ parent @oa_control;
+}
+
+oa_region oa_monitor_region "Supervision cycle" id "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7" {
+ description "";
+ parent @oa_monitor;
+}
+
+oa_initial oa_monitor_initial "Start" id "a7ffb869-9dde-5e76-b276-c719c5c143a9" {
+ description "";
+ do_activity "";
+ entry "";
+ exit "";
+ kind Initial;
+ parent @oa_monitor_region;
+}
+
+oa_state oa_monitoring "Monitoring" id "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae" {
+ description "";
+ do_activity "";
+ entry "";
+ exit "";
+ kind State;
+ parent @oa_monitor_region;
+}
+
+oa_state oa_waiting "Awaiting repair" id "58c15833-6e75-523a-b57f-a88f19f6d672" {
+ description "";
+ do_activity "";
+ entry "";
+ exit "";
+ kind State;
+ parent @oa_monitor_region;
+}
+
+state_transition oa_start_monitoring "State transition: Monitoring" id "2b00270c-79a5-52a5-af96-33346bea531b" {
+ description "";
+ effect "";
+ effects [];
+ guard "";
+ source @oa_monitor_initial;
+ target @oa_monitoring;
+ transition_kind External;
+ trigger "";
+ triggers [];
+}
+
+state_transition oa_alarm_transition "State transition: Awaiting repair" id "fbde9d5e-c7e9-55db-a2be-497345c491b3" {
+ description "";
+ effect "";
+ effects [];
+ guard "";
+ source @oa_monitoring;
+ target @oa_waiting;
+ transition_kind External;
+ trigger "";
+ triggers [@oa_high_pressure];
+}
+
+state_action oa_assess_on_entry "Assess when repair is needed" id "7fa37e63-db65-5aa7-88f2-31ff4347a787" {
+ description "";
+ phase Entry;
+ source @oa_waiting;
+ target @oa_assess;
+}
+
+state_transition oa_normal_transition "State transition: Monitoring" id "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97" {
+ description "";
+ effect "";
+ effects [];
+ guard "repair confirmed and pressure is safe";
+ source @oa_waiting;
+ target @oa_monitoring;
+ transition_kind External;
+ trigger "";
+ triggers [];
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/capabilities.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/capabilities.arcadia
new file mode 100644
index 00000000..77c2ab08
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/capabilities.arcadia
@@ -0,0 +1,27 @@
+oa_mission oa_mission "Maintain safe water supply" id "c9c61f50-06e0-5d17-973d-dcc57cd5cc90" {
+ description "";
+ parent none;
+}
+
+oa_capability oa_restore "Restore a safe supply" id "3dfee23a-148b-5cec-ad2d-b774e84ea5bb" {
+ description "";
+ parent none;
+}
+
+oa_capability oa_triage "Assess an alarm" id "6a30b763-cc51-546d-907f-5c63657cfd03" {
+ description "";
+ parent none;
+}
+
+mission_capability oa_mission_delivery "Mission capability: Restore a safe supply" id "1f8b1898-65f6-5360-99cf-b78a71bd50ec" {
+ description "";
+ source @oa_mission;
+ target @oa_restore;
+}
+
+capability_include oa_include_triage "Capability include: Assess an alarm" id "e21d9a54-ae8e-5618-a4f8-67ee22b92638" {
+ description "";
+ source @oa_restore;
+ target @oa_triage;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/model.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/model.arcadia
new file mode 100644
index 00000000..b3f4a60a
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/model.arcadia
@@ -0,0 +1,24 @@
+oa_exchange_category oa_contact_category "Field contact" id "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771" {
+ description "";
+ exchanges [@oa_contact];
+ family "Participant";
+ parent none;
+}
+
+oa_event oa_high_pressure "Pressure alarm detected" id "65d6b8d6-271a-5c84-8b42-abbe310c955e" {
+ body {
+ Change {
+ expression "measured_pressure > operating_limit";
+ };
+ };
+ description "";
+ parent none;
+}
+
+oa_exchange_category oa_repair_traffic "Repair work exchanges" id "9349b353-53f2-56f8-b571-a05b09335cdf" {
+ description "";
+ exchanges [@oa_repair_request, @oa_repair_report];
+ family "Work";
+ parent none;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/participants.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/participants.arcadia
new file mode 100644
index 00000000..ed901190
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/participants.arcadia
@@ -0,0 +1,60 @@
+oa_entity oa_control "Control room" id "32aa9940-df6e-5a12-b9bd-6337e2d2ed22" {
+ description "";
+ parent none;
+}
+
+oa_actor oa_technician "Field technician" id "e07c3952-4a1a-5cc7-b7d5-03a01101ce49" {
+ description "";
+ parent none;
+}
+
+allocation oa_assess_allocation "Allocation: Assess pressure alarm" id "594c61cd-94e0-5c07-890d-edfd520d7df9" {
+ description "";
+ source @oa_control;
+ target @oa_assess;
+}
+
+allocation oa_confirm_allocation "Allocation: Confirm safe pressure" id "bfd4326e-ea65-5269-b5e6-e063a355d1e9" {
+ description "";
+ source @oa_control;
+ target @oa_confirm;
+}
+
+communication_mean oa_contact "Technician service conversation" id "27b47887-baf0-518b-bf87-f5acbf7d0b93" {
+ description "";
+ items [@oa_repair_operation];
+ oriented false;
+ source @oa_control;
+ target @oa_technician;
+}
+
+exchange_allocation oa_report_carriage "Exchange allocation: Report repair result" id "4549d3fa-5ab9-52a9-b4f9-3a78fc9d625a" {
+ description "";
+ source @oa_contact;
+ target @oa_repair_report;
+}
+
+exchange_allocation oa_request_carriage "Exchange allocation: Request field repair" id "094b888c-450c-5c4f-971a-7d75db059574" {
+ description "";
+ source @oa_contact;
+ target @oa_repair_request;
+}
+
+mission_involvement oa_mission_participant "Mission involvement: Maintain safe water supply" id "0c4c9e58-ff32-5c00-b82a-d408a4be0183" {
+ description "";
+ source @oa_control;
+ target @oa_mission;
+}
+
+allocation oa_reset_allocation "Allocation: Reset valve remotely" id "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a" {
+ description "";
+ source @oa_control;
+ target @oa_reset;
+}
+
+allocation oa_repair_allocation "Allocation: Repair valve in the field" id "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2" {
+ description "";
+ source @oa_technician;
+ target @oa_repair;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/types.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/types.arcadia
new file mode 100644
index 00000000..c285895b
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/oa/types.arcadia
@@ -0,0 +1,35 @@
+oa_data_type oa_pressure "Measured pressure" id "e7824764-5db9-5384-8198-2fbaea2b10b2" {
+ default none;
+ definition {
+ Real {
+ maximum 600.0;
+ minimum 0.0;
+ };
+ };
+ description "";
+ parent none;
+ unit "kPa";
+}
+
+oa_exchange_item oa_repair_operation "Valve repair" id "d736c7d6-d369-550a-8ba4-dae223e40bb3" {
+ description "";
+ kind Information;
+ mechanism Operation;
+ parent none;
+ supertype none;
+}
+
+oa_data_field oa_pressure_parameter "Pressure before / after repair" id "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7" {
+ default none;
+ description "";
+ direction InOut;
+ maximum 1;
+ minimum 1;
+ parent @oa_repair_operation;
+ storage Value;
+ type_ref {
+ Named @oa_pressure;
+ };
+ unit "";
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/project.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/project.arcadia
new file mode 100644
index 00000000..702dd7a3
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/project.arcadia
@@ -0,0 +1,33 @@
+arcadia 7;
+project pressure_restoration "Restore safe water pressure — release 1 walkthrough" id "2786d72f-5763-5f33-a57f-2773a4ca1a6c" {
+ description "A controller either resets a valve remotely or requests field repair. Compare the operational need with the explicitly chosen coordination-service boundary. Start with README.md beside project.arcadia. Units and guards are descriptive; this model does not simulate a real water network.";
+ include "oa/activities/activity_oa_restore_work.arcadia";
+ include "oa/behavior/process_oa_assessment_procedure.arcadia";
+ include "oa/behavior/process_oa_field_procedure.arcadia";
+ include "oa/behavior/process_oa_response.arcadia";
+ include "oa/behavior/scenario_oa_conversation.arcadia";
+ include "oa/behavior/scenario_oa_full_scenario.arcadia";
+ include "oa/behavior/state_machine_oa_monitor.arcadia";
+ include "oa/capabilities.arcadia";
+ include "oa/model.arcadia";
+ include "oa/participants.arcadia";
+ include "oa/types.arcadia";
+ include "sa/behavior/process_sa_assessment_procedure.arcadia";
+ include "sa/behavior/process_sa_field_procedure.arcadia";
+ include "sa/behavior/process_sa_response.arcadia";
+ include "sa/behavior/scenario_sa_conversation.arcadia";
+ include "sa/behavior/scenario_sa_full_scenario.arcadia";
+ include "sa/behavior/state_machine_sa_monitor.arcadia";
+ include "sa/capabilities.arcadia";
+ include "sa/functions/function_sa_audit_thresholds.arcadia";
+ include "sa/functions/function_sa_restore_work.arcadia";
+ include "sa/model.arcadia";
+ include "sa/participants.arcadia";
+ include "sa/types.arcadia";
+ include "traceability.arcadia";
+ include "views/diagram_oa_allocation_view.arcadia";
+ include "views/diagram_sa_allocation_view.arcadia";
+ include "views/diagram_sa_capability_view.arcadia";
+ include "views/diagram_sa_service_view.arcadia";
+ include "views/diagram_sa_state_view.arcadia";
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_assessment_procedure.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_assessment_procedure.arcadia
new file mode 100644
index 00000000..ff30c34f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_assessment_procedure.arcadia
@@ -0,0 +1,25 @@
+sa_functional_chain sa_assessment_procedure "Assess an alarm procedure" id "acd5a018-f559-5298-a40d-1cf6e1353e74" {
+ description "";
+ parent none;
+ steps [@sa_assessment_action];
+}
+
+sa_occurrence sa_assessment_action "Assess pressure alarm" id "bae5b192-5b8e-51c6-82ae-2331af0d33bf" {
+ body {
+ Action {
+ action @sa_assess;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @sa_assessment_procedure;
+ postcondition "";
+ precondition "";
+}
+
+chain_involvement sa_assessment_delivery "Chain involvement: Assess an alarm" id "a6d5a6be-fa6e-5fa0-a309-08e87ba40c57" {
+ description "";
+ source @sa_assessment_procedure;
+ target @sa_triage;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_field_procedure.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_field_procedure.arcadia
new file mode 100644
index 00000000..b6de2a32
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_field_procedure.arcadia
@@ -0,0 +1,67 @@
+sa_functional_chain sa_field_procedure "Field repair procedure" id "93eb6479-54e9-59cb-ac7b-16912347bfc0" {
+ description "";
+ parent none;
+ steps [@sa_request_step, @sa_repair_step, @sa_report_step];
+}
+
+sa_occurrence sa_repair_step "Repair the valve" id "0f195fea-3493-5ebd-966d-da9eddeb05d7" {
+ body {
+ Action {
+ action @sa_repair;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @sa_field_procedure;
+ postcondition "";
+ precondition "";
+}
+
+sa_occurrence sa_report_step "Report the measured result" id "ea32e4ec-8bdc-57ea-8a90-7b09ba4f0eac" {
+ body {
+ Exchange {
+ exchange @sa_repair_report;
+ message_kind Asynchronous;
+ receiver none;
+ reply_to none;
+ sender none;
+ values [];
+ };
+ };
+ description "";
+ parent @sa_field_procedure;
+ postcondition "";
+ precondition "";
+}
+
+sa_occurrence sa_request_step "Request the field repair" id "40d94322-baa2-5c81-b261-ff47cdc33782" {
+ body {
+ Exchange {
+ exchange @sa_repair_request;
+ message_kind Asynchronous;
+ receiver none;
+ reply_to none;
+ sender none;
+ values [];
+ };
+ };
+ description "";
+ parent @sa_field_procedure;
+ postcondition "";
+ precondition "";
+}
+
+sequence_link sa_repair_then_report "Sequence link: Report the measured result" id "7fb30b1d-195c-5f0e-a19c-a536f519a899" {
+ description "";
+ guard "";
+ source @sa_repair_step;
+ target @sa_report_step;
+}
+
+sequence_link sa_request_then_repair "Sequence link: Repair the valve" id "6a6d968f-26d6-5dbb-b721-5a27d362779a" {
+ description "";
+ guard "";
+ source @sa_request_step;
+ target @sa_repair_step;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_response.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_response.arcadia
new file mode 100644
index 00000000..241fb132
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/process_sa_response.arcadia
@@ -0,0 +1,128 @@
+sa_functional_chain sa_response "Handle a pressure alarm" id "5139ce73-56fd-5875-ba3a-e569fc890231" {
+ description "";
+ parent none;
+ steps [@sa_assess_step, @sa_decision, @sa_reset_step, @sa_use_field, @sa_merge, @sa_confirm_step];
+}
+
+sa_occurrence sa_assess_step "Assess the alarm" id "9024243c-ccc1-5edb-8477-2aa16e5e9a50" {
+ body {
+ SequenceReference {
+ sequence @sa_assessment_procedure;
+ };
+ };
+ description "";
+ parent @sa_response;
+ postcondition "";
+ precondition "";
+}
+
+sa_occurrence sa_confirm_step "Confirm safe pressure" id "47bec4f9-d6d0-5ed1-adca-fa0d960dc416" {
+ body {
+ Action {
+ action @sa_confirm;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @sa_response;
+ postcondition "";
+ precondition "";
+}
+
+sa_occurrence sa_decision "Can a remote reset safely recover pressure?" id "30c8fad9-a49b-5692-9542-3148e76afd7b" {
+ body {
+ Control {
+ kind Choice;
+ };
+ };
+ description "";
+ parent @sa_response;
+ postcondition "";
+ precondition "";
+}
+
+sa_occurrence sa_merge "Recovery path completed" id "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07" {
+ body {
+ Control {
+ kind Merge;
+ };
+ };
+ description "";
+ parent @sa_response;
+ postcondition "";
+ precondition "";
+}
+
+sa_occurrence sa_reset_step "Try the authorized remote reset" id "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6" {
+ body {
+ Action {
+ action @sa_reset;
+ lifeline none;
+ };
+ };
+ description "";
+ parent @sa_response;
+ postcondition "";
+ precondition "";
+}
+
+sa_occurrence sa_use_field "Use the field repair procedure" id "d5e61c5d-510b-5091-a878-da46f3a9e152" {
+ body {
+ SequenceReference {
+ sequence @sa_field_procedure;
+ };
+ };
+ description "";
+ parent @sa_response;
+ postcondition "";
+ precondition "";
+}
+
+sequence_link sa_assess_then_decide "Continue" id "9d4a131f-36ad-5125-b21f-c3dd033b8c82" {
+ description "";
+ guard "";
+ source @sa_assess_step;
+ target @sa_decision;
+}
+
+sequence_link sa_field_path "else" id "1bbd5957-1173-51d8-97e3-ba4f084227ae" {
+ description "";
+ guard "else";
+ source @sa_decision;
+ target @sa_use_field;
+}
+
+sequence_link sa_remote_path "remote reset is authorized" id "d8b6710a-e713-5979-934f-e5ca7d155bb4" {
+ description "";
+ guard "remote reset is authorized";
+ source @sa_decision;
+ target @sa_reset_step;
+}
+
+sequence_link sa_confirm_path "Continue" id "1e738f27-a3a1-526d-b330-6384088b9689" {
+ description "";
+ guard "";
+ source @sa_merge;
+ target @sa_confirm_step;
+}
+
+sequence_link sa_remote_merge "Continue" id "e5279265-8eac-52e8-98dc-571b20475e1f" {
+ description "";
+ guard "";
+ source @sa_reset_step;
+ target @sa_merge;
+}
+
+chain_involvement sa_response_delivery "Chain involvement: Restore a safe supply" id "66efc484-53c7-51a3-89e9-422429344b38" {
+ description "";
+ source @sa_response;
+ target @sa_restore;
+}
+
+sequence_link sa_field_merge "Continue" id "0f6ac3fb-4ad6-581e-ad14-336472072d5e" {
+ description "";
+ guard "";
+ source @sa_use_field;
+ target @sa_merge;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_conversation.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_conversation.arcadia
new file mode 100644
index 00000000..4e324730
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_conversation.arcadia
@@ -0,0 +1,73 @@
+sa_scenario sa_conversation "Repair request and measured reply" id "730b27ca-b039-5a3b-8dc6-03418f9061b3" {
+ context @sa_restore;
+ description "";
+ parent none;
+ postcondition "The controller has received the measured pressure after repair";
+ precondition "A pressure alarm has been assessed";
+ steps [@sa_call, @sa_reply];
+ viewpoint BoundaryInterface;
+}
+
+sa_occurrence sa_call "Request valve repair" id "8bab4783-a098-5984-9e36-493957caf26c" {
+ body {
+ Exchange {
+ exchange @sa_contact;
+ message_kind Call;
+ receiver @sa_responder;
+ reply_to none;
+ sender @sa_caller;
+ values [{
+ expression "420.0";
+ field @sa_pressure_parameter;
+ path [];
+ }];
+ };
+ };
+ description "";
+ parent @sa_conversation;
+ postcondition "";
+ precondition "";
+}
+
+sa_lifeline sa_caller "Controller" id "98c61788-2fee-51f4-8cf9-de48e8c70e9e" {
+ description "";
+ parent @sa_conversation;
+ represented @sa_control;
+}
+
+sa_occurrence sa_reply "Return pressure after repair" id "4e1c0a56-ce80-5f13-a93a-6956de54880c" {
+ body {
+ Exchange {
+ exchange @sa_contact;
+ message_kind Reply;
+ receiver @sa_caller;
+ reply_to @sa_call;
+ sender @sa_responder;
+ values [{
+ expression "320.0";
+ field @sa_pressure_parameter;
+ path [];
+ }];
+ };
+ };
+ description "";
+ parent @sa_conversation;
+ postcondition "";
+ precondition "";
+}
+
+sa_lifeline sa_responder "Technician" id "6249b54a-e396-5338-82b9-89266c55670d" {
+ description "";
+ parent @sa_conversation;
+ represented @sa_technician;
+}
+
+artifact repair_test_evidence "Repair conversation acceptance notes" id "f5922bf7-05d8-590f-86e9-1d09f37f4fa6" {
+ description "A local teaching artifact, not proof of a real valve's safety.";
+ fragment "acceptance";
+ owner @sa_conversation;
+ relation Verifies;
+ revision "lesson-1";
+ uri "evidence/repair-check.md";
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_full_scenario.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_full_scenario.arcadia
new file mode 100644
index 00000000..b88e5d12
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/scenario_sa_full_scenario.arcadia
@@ -0,0 +1,41 @@
+sa_scenario sa_full_scenario "Restore supply through field repair" id "f746a1cd-ff9f-55c0-b325-01c03d2f4fae" {
+ context @sa_restore;
+ description "";
+ parent none;
+ postcondition "The controller has received the measured pressure after repair";
+ precondition "A pressure alarm has been assessed";
+ steps [@sa_use_conversation];
+ viewpoint BoundaryInterface;
+}
+
+sa_lifeline sa_outer_controller "Controller" id "1edfcf9e-9bfa-51f1-80d5-62b8fd8bb023" {
+ description "";
+ parent @sa_full_scenario;
+ represented @sa_control;
+}
+
+sa_lifeline sa_outer_technician "Technician" id "155e4fe4-9e3b-5f4e-b886-28abcc3f086b" {
+ description "";
+ parent @sa_full_scenario;
+ represented @sa_technician;
+}
+
+sa_occurrence sa_use_conversation "Use the reviewed repair conversation" id "1c9da843-e2d3-5591-8e3f-db083a42867e" {
+ body {
+ ScenarioUse {
+ bindings [{
+ local @sa_outer_controller;
+ referenced @sa_caller;
+ }, {
+ local @sa_outer_technician;
+ referenced @sa_responder;
+ }];
+ scenario @sa_conversation;
+ };
+ };
+ description "";
+ parent @sa_full_scenario;
+ postcondition "";
+ precondition "";
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/state_machine_sa_monitor.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/state_machine_sa_monitor.arcadia
new file mode 100644
index 00000000..46949f1b
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/behavior/state_machine_sa_monitor.arcadia
@@ -0,0 +1,80 @@
+sa_state_machine sa_monitor "Pressure supervision" id "132b3f28-3f88-5327-9868-8c8b645ed412" {
+ description "";
+ parent @sa_control;
+}
+
+sa_region sa_monitor_region "Supervision cycle" id "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c" {
+ description "";
+ parent @sa_monitor;
+}
+
+sa_initial sa_monitor_initial "Start" id "57eb5348-14d8-560e-996a-8dbf993243e2" {
+ description "";
+ do_activity "";
+ entry "";
+ exit "";
+ kind Initial;
+ parent @sa_monitor_region;
+}
+
+sa_state sa_monitoring "Monitoring" id "8af21f9b-c21e-5aae-af0d-0958840f6655" {
+ description "";
+ do_activity "";
+ entry "";
+ exit "";
+ kind State;
+ parent @sa_monitor_region;
+}
+
+sa_state sa_waiting "Awaiting repair" id "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37" {
+ description "";
+ do_activity "";
+ entry "";
+ exit "";
+ kind State;
+ parent @sa_monitor_region;
+}
+
+state_transition sa_start_monitoring "State transition: Monitoring" id "47730532-0372-5cc5-9d6d-0b8953aebdd8" {
+ description "";
+ effect "";
+ effects [];
+ guard "";
+ source @sa_monitor_initial;
+ target @sa_monitoring;
+ transition_kind External;
+ trigger "";
+ triggers [];
+}
+
+state_transition sa_alarm_transition "State transition: Awaiting repair" id "13c3bfa4-9254-5ab2-b96f-f65f402adf81" {
+ description "";
+ effect "";
+ effects [];
+ guard "";
+ source @sa_monitoring;
+ target @sa_waiting;
+ transition_kind External;
+ trigger "";
+ triggers [@sa_high_pressure];
+}
+
+state_action sa_assess_on_entry "Assess when repair is needed" id "a887f96a-37cb-5891-8070-011fa276e5b5" {
+ description "";
+ phase Entry;
+ source @sa_waiting;
+ target @sa_assess;
+}
+
+state_transition sa_normal_transition "State transition: Monitoring" id "6e2c1b47-764c-577a-b325-ff66dd2a2768" {
+ description "";
+ effect "";
+ effects [];
+ guard "repair confirmed and pressure is safe";
+ source @sa_waiting;
+ target @sa_monitoring;
+ transition_kind External;
+ trigger "";
+ triggers [];
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/capabilities.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/capabilities.arcadia
new file mode 100644
index 00000000..4b5c4210
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/capabilities.arcadia
@@ -0,0 +1,27 @@
+sa_mission sa_mission "Maintain safe water supply" id "5ad034b1-084d-5273-9e70-971671766132" {
+ description "";
+ parent none;
+}
+
+sa_capability sa_restore "Restore a safe supply" id "81259051-370a-512d-a3b5-6420a6f933b6" {
+ description "";
+ parent none;
+}
+
+sa_capability sa_triage "Assess an alarm" id "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f" {
+ description "";
+ parent none;
+}
+
+mission_capability sa_mission_delivery "Mission capability: Restore a safe supply" id "a0902e0f-3d18-576a-9aca-975a136dfdd7" {
+ description "";
+ source @sa_mission;
+ target @sa_restore;
+}
+
+capability_include sa_include_triage "Capability include: Assess an alarm" id "f3fe7690-734a-5877-8b57-a6599a4033c4" {
+ description "";
+ source @sa_restore;
+ target @sa_triage;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_audit_thresholds.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_audit_thresholds.arcadia
new file mode 100644
index 00000000..2ec039aa
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_audit_thresholds.arcadia
@@ -0,0 +1,5 @@
+sa_function sa_audit_thresholds "Audit pressure thresholds" id "4149a872-3440-59de-9879-8981e78d27ab" {
+ description "Deliberately unfinished: assigned to the service, but no behavior context yet. Model checks should expose this one work_without_behavior_context warning.";
+ parent none;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_restore_work.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_restore_work.arcadia
new file mode 100644
index 00000000..772ec0f1
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/functions/function_sa_restore_work.arcadia
@@ -0,0 +1,93 @@
+sa_function sa_restore_work "Restore safe pressure" id "58867e7d-916e-5ebc-9e4c-8dbed1314be3" {
+ description "";
+ parent none;
+}
+
+sa_function sa_assess "Assess pressure alarm" id "dddae020-a444-5d48-acc8-139d2696a672" {
+ description "";
+ parent @sa_restore_work;
+}
+
+sa_port sa_repair_request_out "Request field repair output" id "24a6850e-b819-57e0-af88-ca4ab76581b7" {
+ contract {
+ FunctionOutput {
+ outgoing_items [@sa_repair_operation];
+ };
+ };
+ description "";
+ legacy none;
+ parent @sa_assess;
+}
+
+sa_function sa_confirm "Confirm safe pressure" id "d75eb3fa-a27d-52d5-8782-8b342b3865f6" {
+ description "";
+ parent @sa_restore_work;
+}
+
+sa_port sa_repair_report_in "Report repair result input" id "391af7c4-b07d-53a7-8ddd-5c81461fc4f8" {
+ contract {
+ FunctionInput {
+ incoming_items [@sa_repair_operation];
+ };
+ };
+ description "";
+ legacy none;
+ parent @sa_confirm;
+}
+
+sa_function sa_repair "Repair valve in the field" id "9497e10f-04c4-5586-8738-2464a6f00bd4" {
+ description "";
+ parent @sa_restore_work;
+}
+
+sa_port sa_repair_report_out "Report repair result output" id "994faed7-dbc8-5b96-9d62-7604ac0fbc3a" {
+ contract {
+ FunctionOutput {
+ outgoing_items [@sa_repair_operation];
+ };
+ };
+ description "";
+ legacy none;
+ parent @sa_repair;
+}
+
+sa_port sa_repair_request_in "Request field repair input" id "d2113fb0-a531-54ef-9484-13951b00ab8f" {
+ contract {
+ FunctionInput {
+ incoming_items [@sa_repair_operation];
+ };
+ };
+ description "";
+ legacy none;
+ parent @sa_repair;
+}
+
+sa_function sa_reset "Reset valve remotely" id "09342ccb-044b-5483-ac58-87dc3cf7831c" {
+ description "";
+ parent @sa_restore_work;
+}
+
+capability_involvement sa_assessment_support "Capability involvement: Assess an alarm" id "93eaad8d-83b7-5980-b9bb-86c9e3f31da4" {
+ description "";
+ source @sa_assess;
+ target @sa_triage;
+}
+
+functional_exchange sa_repair_request "Request field repair" id "93474111-f23e-56c1-b61b-b7c2396b91bd" {
+ description "";
+ items [@sa_repair_operation];
+ source @sa_assess;
+ source_port @sa_repair_request_out;
+ target @sa_repair;
+ target_port @sa_repair_request_in;
+}
+
+functional_exchange sa_repair_report "Report repair result" id "684eadc3-e670-5779-a60f-bbc9a5bfb062" {
+ description "";
+ items [@sa_repair_operation];
+ source @sa_repair;
+ source_port @sa_repair_report_out;
+ target @sa_confirm;
+ target_port @sa_repair_report_in;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/model.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/model.arcadia
new file mode 100644
index 00000000..0856d908
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/model.arcadia
@@ -0,0 +1,24 @@
+sa_exchange_category sa_contact_category "Field contact" id "781cbe75-410f-5cc5-aeb8-a43491f77b99" {
+ description "";
+ exchanges [@sa_contact];
+ family "Participant";
+ parent none;
+}
+
+sa_event sa_high_pressure "Pressure alarm detected" id "01490fa8-2f01-5ac8-9722-d55d45414379" {
+ body {
+ Change {
+ expression "measured_pressure > operating_limit";
+ };
+ };
+ description "";
+ parent none;
+}
+
+sa_exchange_category sa_repair_traffic "Repair work exchanges" id "a9a47e48-100c-57a8-95f0-7fe24da94f94" {
+ description "";
+ exchanges [@sa_repair_request, @sa_repair_report];
+ family "Work";
+ parent none;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/participants.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/participants.arcadia
new file mode 100644
index 00000000..4b9c5558
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/participants.arcadia
@@ -0,0 +1,126 @@
+sa_system sa_control "Pressure coordination service" id "435bf87c-73fd-5e4d-978a-4c471bc9eaf0" {
+ description "";
+ parent none;
+}
+
+sa_port sa_control_port "Repair service client" id "98d411e7-7c65-5c3f-b543-8bf0f2522ea2" {
+ contract {
+ Component {
+ kind Standard;
+ orientation Unset;
+ };
+ };
+ description "Explicitly chosen Standard component port for the repair service.";
+ legacy none;
+ parent @sa_control;
+}
+
+sa_actor sa_technician "Field technician" id "c05b9422-b9da-5de5-b21d-07ad23bd2b8a" {
+ description "";
+ parent none;
+}
+
+sa_port sa_technician_port "Repair service provider" id "e6d69cbd-99d7-596b-bbe3-dada6118a600" {
+ contract {
+ Component {
+ kind Standard;
+ orientation Unset;
+ };
+ };
+ description "Explicitly chosen Standard component port for the repair service.";
+ legacy none;
+ parent @sa_technician;
+}
+
+allocation sa_assess_allocation "Allocation: Assess pressure alarm" id "9b9356d7-2db2-5a94-a47e-ea892cf02afe" {
+ description "";
+ source @sa_control;
+ target @sa_assess;
+}
+
+allocation sa_audit_allocation "Allocation: Audit pressure thresholds" id "1b245065-7242-5d81-8472-aaeb932054c9" {
+ description "";
+ source @sa_control;
+ target @sa_audit_thresholds;
+}
+
+allocation sa_confirm_allocation "Allocation: Confirm safe pressure" id "b76bf8de-2fa4-563f-a540-dd30f30e6c49" {
+ description "";
+ source @sa_control;
+ target @sa_confirm;
+}
+
+interface_requirement sa_client_requires "Interface requirement: Valve repair service" id "4f85337b-f6eb-533c-a45a-9a5d1d9567ca" {
+ description "";
+ source @sa_control_port;
+ target @sa_repair_interface;
+}
+
+boundary_exchange sa_contact "Technician service conversation" id "63009b5f-539e-5914-b630-ec6b7fefb4d5" {
+ description "";
+ items [@sa_repair_operation];
+ oriented false;
+ source @sa_control_port;
+ target @sa_technician_port;
+}
+
+exchange_allocation sa_report_carriage "Exchange allocation: Report repair result" id "ff9897b3-a23a-5994-a5b3-99a5b4d34ceb" {
+ description "";
+ source @sa_contact;
+ target @sa_repair_report;
+}
+
+exchange_allocation sa_request_carriage "Exchange allocation: Request field repair" id "4c4c4851-5a3d-5bb9-94df-b19f9cf39819" {
+ description "";
+ source @sa_contact;
+ target @sa_repair_request;
+}
+
+port_allocation sa_port_report_in "Port allocation: Report repair result input" id "568c5c01-de21-52a2-83bd-afdddc158085" {
+ description "";
+ source @sa_control_port;
+ target @sa_repair_report_in;
+}
+
+port_allocation sa_port_request_out "Port allocation: Request field repair output" id "55ec4dbf-bd17-5b0c-b3a3-95b2194aa8b9" {
+ description "";
+ source @sa_control_port;
+ target @sa_repair_request_out;
+}
+
+mission_involvement sa_mission_participant "Mission involvement: Maintain safe water supply" id "1c1e5634-ba8c-591f-b143-90a27cdc6cd2" {
+ description "";
+ source @sa_control;
+ target @sa_mission;
+}
+
+allocation sa_reset_allocation "Allocation: Reset valve remotely" id "3cc9ab52-2b00-57fd-8709-aabc75e5d3e3" {
+ description "";
+ source @sa_control;
+ target @sa_reset;
+}
+
+allocation sa_repair_allocation "Allocation: Repair valve in the field" id "725f2e1b-aa2c-5615-9b1f-28da178168fa" {
+ description "";
+ source @sa_technician;
+ target @sa_repair;
+}
+
+port_allocation sa_port_report_out "Port allocation: Report repair result output" id "e53b7a40-e8a3-5b3f-826d-c2b5c164a84a" {
+ description "";
+ source @sa_technician_port;
+ target @sa_repair_report_out;
+}
+
+port_allocation sa_port_request_in "Port allocation: Request field repair input" id "09eaa992-4cdf-5ea3-b748-f10fd74b8cba" {
+ description "";
+ source @sa_technician_port;
+ target @sa_repair_request_in;
+}
+
+interface_provision sa_provider_provides "Interface provision: Valve repair service" id "55f89fbf-aa3c-5ae1-95ef-ac676f9c1ada" {
+ description "";
+ source @sa_technician_port;
+ target @sa_repair_interface;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/types.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/types.arcadia
new file mode 100644
index 00000000..faa0117f
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/sa/types.arcadia
@@ -0,0 +1,50 @@
+sa_data_type sa_pressure "Measured pressure" id "87d0a95b-af1b-5352-b753-e4c6533671a5" {
+ default none;
+ definition {
+ Real {
+ maximum 600.0;
+ minimum 0.0;
+ };
+ };
+ description "";
+ parent none;
+ unit "kPa";
+}
+
+sa_interface sa_repair_interface "Valve repair service" id "a4b8942e-a975-5f88-ad14-8f38c345baac" {
+ description "";
+ parent none;
+ supertype none;
+}
+
+sa_exchange_item sa_repair_operation "Valve repair" id "a962fc5e-3ab0-5296-8b0d-fd84633f9218" {
+ description "";
+ kind Information;
+ mechanism Operation;
+ parent none;
+ supertype none;
+}
+
+sa_data_field sa_pressure_parameter "Pressure before / after repair" id "bb12ba5d-aab5-5a1b-99de-c767f5365b57" {
+ default none;
+ description "";
+ direction InOut;
+ maximum 1;
+ minimum 1;
+ parent @sa_repair_operation;
+ storage Value;
+ type_ref {
+ Named @sa_pressure;
+ };
+ unit "";
+}
+
+interface_item_allocation sa_repair_interface_item "Repair operation contract" id "f074e041-23cf-54ad-af9c-f171e30621e7" {
+ acquisition Accept;
+ description "";
+ order 0;
+ source @sa_repair_interface;
+ target @sa_repair_operation;
+ transmission Synchronous;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/traceability.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/traceability.arcadia
new file mode 100644
index 00000000..b185b027
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/traceability.arcadia
@@ -0,0 +1,1156 @@
+trace trace_oa_alarm_transition "State transition: Awaiting repair ↔ State transition: Awaiting repair" id "0c7c8a06-163e-5b3e-8a98-e66e2ce8963b" {
+ baseline {
+ oa {
+ description "";
+ name "State transition: Awaiting repair";
+ };
+ sa {
+ description "";
+ name "State transition: Awaiting repair";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_alarm_transition;
+ sa @sa_alarm_transition;
+}
+
+trace trace_oa_assess "Assess pressure alarm ↔ Assess pressure alarm" id "5309f955-53fd-5ff4-9c0a-91737e947341" {
+ baseline {
+ oa {
+ description "";
+ name "Assess pressure alarm";
+ };
+ sa {
+ description "";
+ name "Assess pressure alarm";
+ };
+ };
+ description "";
+ kind Refinement;
+ oa @oa_assess;
+ sa @sa_assess;
+}
+
+trace trace_oa_assess_allocation "Allocation: Assess pressure alarm ↔ Allocation: Assess pressure alarm" id "acdbee9c-5017-5154-804e-50801f0376ec" {
+ baseline {
+ oa {
+ description "";
+ name "Allocation: Assess pressure alarm";
+ };
+ sa {
+ description "";
+ name "Allocation: Assess pressure alarm";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_assess_allocation;
+ sa @sa_assess_allocation;
+}
+
+trace trace_oa_assess_on_entry "Assess when repair is needed ↔ Assess when repair is needed" id "fb02420d-b066-5773-87c1-198a056e0731" {
+ baseline {
+ oa {
+ description "";
+ name "Assess when repair is needed";
+ };
+ sa {
+ description "";
+ name "Assess when repair is needed";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_assess_on_entry;
+ sa @sa_assess_on_entry;
+}
+
+trace trace_oa_assess_step "Assess the alarm ↔ Assess the alarm" id "e848da41-445e-5e3c-8b4e-ef53fdddf1e1" {
+ baseline {
+ oa {
+ description "";
+ name "Assess the alarm";
+ };
+ sa {
+ description "";
+ name "Assess the alarm";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_assess_step;
+ sa @sa_assess_step;
+}
+
+trace trace_oa_assess_then_decide "Continue ↔ Continue" id "a5c1eb01-a95f-5f7a-9742-7f201493d7aa" {
+ baseline {
+ oa {
+ description "";
+ name "Continue";
+ };
+ sa {
+ description "";
+ name "Continue";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_assess_then_decide;
+ sa @sa_assess_then_decide;
+}
+
+trace trace_oa_assessment_action "Assess pressure alarm ↔ Assess pressure alarm" id "3a33de40-20a3-5252-899c-c12dc35e9783" {
+ baseline {
+ oa {
+ description "";
+ name "Assess pressure alarm";
+ };
+ sa {
+ description "";
+ name "Assess pressure alarm";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_assessment_action;
+ sa @sa_assessment_action;
+}
+
+trace trace_oa_assessment_delivery "Chain involvement: Assess an alarm ↔ Chain involvement: Assess an alarm" id "5b96bfed-63e2-5d7f-b882-91e8b81f8efe" {
+ baseline {
+ oa {
+ description "";
+ name "Chain involvement: Assess an alarm";
+ };
+ sa {
+ description "";
+ name "Chain involvement: Assess an alarm";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_assessment_delivery;
+ sa @sa_assessment_delivery;
+}
+
+trace trace_oa_assessment_procedure "Assess an alarm procedure ↔ Assess an alarm procedure" id "6ca4c78a-a5f2-5470-b401-732f5a8825d0" {
+ baseline {
+ oa {
+ description "";
+ name "Assess an alarm procedure";
+ };
+ sa {
+ description "";
+ name "Assess an alarm procedure";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_assessment_procedure;
+ sa @sa_assessment_procedure;
+}
+
+trace trace_oa_assessment_support "Capability involvement: Assess an alarm ↔ Capability involvement: Assess an alarm" id "6e13cf86-7242-5e33-8912-b037cf6e8c87" {
+ baseline {
+ oa {
+ description "";
+ name "Capability involvement: Assess an alarm";
+ };
+ sa {
+ description "";
+ name "Capability involvement: Assess an alarm";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_assessment_support;
+ sa @sa_assessment_support;
+}
+
+trace trace_oa_call "Request valve repair ↔ Request valve repair" id "f0cc86e5-f8fd-5091-a40d-dbfb6515072f" {
+ baseline {
+ oa {
+ description "";
+ name "Request valve repair";
+ };
+ sa {
+ description "";
+ name "Request valve repair";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_call;
+ sa @sa_call;
+}
+
+trace trace_oa_caller "Controller ↔ Controller" id "241dfdac-2450-5704-a4cb-9f1047db1b6d" {
+ baseline {
+ oa {
+ description "";
+ name "Controller";
+ };
+ sa {
+ description "";
+ name "Controller";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_caller;
+ sa @sa_caller;
+}
+
+trace trace_oa_confirm "Confirm safe pressure ↔ Confirm safe pressure" id "d7773ff5-cef5-50c1-8f9d-c5659115e1f0" {
+ baseline {
+ oa {
+ description "";
+ name "Confirm safe pressure";
+ };
+ sa {
+ description "";
+ name "Confirm safe pressure";
+ };
+ };
+ description "";
+ kind Refinement;
+ oa @oa_confirm;
+ sa @sa_confirm;
+}
+
+trace trace_oa_confirm_allocation "Allocation: Confirm safe pressure ↔ Allocation: Confirm safe pressure" id "2fdcbc6f-526e-5a4e-bde5-314cf0cf3ef3" {
+ baseline {
+ oa {
+ description "";
+ name "Allocation: Confirm safe pressure";
+ };
+ sa {
+ description "";
+ name "Allocation: Confirm safe pressure";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_confirm_allocation;
+ sa @sa_confirm_allocation;
+}
+
+trace trace_oa_confirm_path "Continue ↔ Continue" id "a106d183-c239-5c9f-b1f1-141af19fc778" {
+ baseline {
+ oa {
+ description "";
+ name "Continue";
+ };
+ sa {
+ description "";
+ name "Continue";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_confirm_path;
+ sa @sa_confirm_path;
+}
+
+trace trace_oa_confirm_step "Confirm safe pressure ↔ Confirm safe pressure" id "154abfce-c311-52d1-85d1-3f4c85203b26" {
+ baseline {
+ oa {
+ description "";
+ name "Confirm safe pressure";
+ };
+ sa {
+ description "";
+ name "Confirm safe pressure";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_confirm_step;
+ sa @sa_confirm_step;
+}
+
+trace trace_oa_contact "Technician service conversation ↔ Technician service conversation" id "c9d65349-3400-58eb-8a55-57fa02de4b67" {
+ baseline {
+ oa {
+ description "";
+ name "Technician service conversation";
+ };
+ sa {
+ description "";
+ name "Technician service conversation";
+ };
+ };
+ description "";
+ kind Exchange;
+ oa @oa_contact;
+ sa @sa_contact;
+}
+
+trace trace_oa_contact_category "Field contact ↔ Field contact" id "e96ff119-bb4d-5fd9-9ab7-2ede0d3fd418" {
+ baseline {
+ oa {
+ description "";
+ name "Field contact";
+ };
+ sa {
+ description "";
+ name "Field contact";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_contact_category;
+ sa @sa_contact_category;
+}
+
+trace trace_oa_control "Control room ↔ Pressure coordination service" id "aba528ea-e396-582a-bfba-f6e2212d391a" {
+ baseline {
+ oa {
+ description "";
+ name "Control room";
+ };
+ sa {
+ description "";
+ name "Pressure coordination service";
+ };
+ };
+ description "";
+ kind Participant;
+ oa @oa_control;
+ sa @sa_control;
+}
+
+trace trace_oa_conversation "Repair request and measured reply ↔ Repair request and measured reply" id "3694c752-ba80-5702-a7d7-9cc66b6b01d8" {
+ baseline {
+ oa {
+ description "";
+ name "Repair request and measured reply";
+ };
+ sa {
+ description "";
+ name "Repair request and measured reply";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_conversation;
+ sa @sa_conversation;
+}
+
+trace trace_oa_decision "Can a remote reset safely recover pressure? ↔ Can a remote reset safely recover pressure?" id "bbf6f1a1-cb2b-5632-8ec0-bf4b8ef1a941" {
+ baseline {
+ oa {
+ description "";
+ name "Can a remote reset safely recover pressure?";
+ };
+ sa {
+ description "";
+ name "Can a remote reset safely recover pressure?";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_decision;
+ sa @sa_decision;
+}
+
+trace trace_oa_field_merge "Continue ↔ Continue" id "d32dd433-9c9e-5b57-9355-2970cc44a5f2" {
+ baseline {
+ oa {
+ description "";
+ name "Continue";
+ };
+ sa {
+ description "";
+ name "Continue";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_field_merge;
+ sa @sa_field_merge;
+}
+
+trace trace_oa_field_path "else ↔ else" id "2e849e31-35aa-5aef-a989-26948038e52b" {
+ baseline {
+ oa {
+ description "";
+ name "else";
+ };
+ sa {
+ description "";
+ name "else";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_field_path;
+ sa @sa_field_path;
+}
+
+trace trace_oa_field_procedure "Field repair procedure ↔ Field repair procedure" id "bb72b2c2-6e32-5f65-9d8e-b33c1a65da37" {
+ baseline {
+ oa {
+ description "";
+ name "Field repair procedure";
+ };
+ sa {
+ description "";
+ name "Field repair procedure";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_field_procedure;
+ sa @sa_field_procedure;
+}
+
+trace trace_oa_full_scenario "Restore supply through field repair ↔ Restore supply through field repair" id "435bae4c-cf6d-59f3-9eb5-d660ebeec079" {
+ baseline {
+ oa {
+ description "";
+ name "Restore supply through field repair";
+ };
+ sa {
+ description "";
+ name "Restore supply through field repair";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_full_scenario;
+ sa @sa_full_scenario;
+}
+
+trace trace_oa_high_pressure "Pressure alarm detected ↔ Pressure alarm detected" id "b97749d4-f9ad-5fb7-9d78-bc60a74e98eb" {
+ baseline {
+ oa {
+ description "";
+ name "Pressure alarm detected";
+ };
+ sa {
+ description "";
+ name "Pressure alarm detected";
+ };
+ };
+ description "";
+ kind State;
+ oa @oa_high_pressure;
+ sa @sa_high_pressure;
+}
+
+trace trace_oa_include_triage "Capability include: Assess an alarm ↔ Capability include: Assess an alarm" id "77073fcb-53aa-5f06-987a-0317cbb6971d" {
+ baseline {
+ oa {
+ description "";
+ name "Capability include: Assess an alarm";
+ };
+ sa {
+ description "";
+ name "Capability include: Assess an alarm";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_include_triage;
+ sa @sa_include_triage;
+}
+
+trace trace_oa_merge "Recovery path completed ↔ Recovery path completed" id "0c9de959-d366-5765-98eb-450ea27bd316" {
+ baseline {
+ oa {
+ description "";
+ name "Recovery path completed";
+ };
+ sa {
+ description "";
+ name "Recovery path completed";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_merge;
+ sa @sa_merge;
+}
+
+trace trace_oa_mission "Maintain safe water supply ↔ Maintain safe water supply" id "c987bd0f-87f6-5550-945d-2a42450fe1c5" {
+ baseline {
+ oa {
+ description "";
+ name "Maintain safe water supply";
+ };
+ sa {
+ description "";
+ name "Maintain safe water supply";
+ };
+ };
+ description "";
+ kind Mission;
+ oa @oa_mission;
+ sa @sa_mission;
+}
+
+trace trace_oa_mission_delivery "Mission capability: Restore a safe supply ↔ Mission capability: Restore a safe supply" id "aa164208-394d-5bc7-8462-b0043f682aad" {
+ baseline {
+ oa {
+ description "";
+ name "Mission capability: Restore a safe supply";
+ };
+ sa {
+ description "";
+ name "Mission capability: Restore a safe supply";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_mission_delivery;
+ sa @sa_mission_delivery;
+}
+
+trace trace_oa_mission_participant "Mission involvement: Maintain safe water supply ↔ Mission involvement: Maintain safe water supply" id "8d693d90-5017-56af-b056-d29d3cb46baa" {
+ baseline {
+ oa {
+ description "";
+ name "Mission involvement: Maintain safe water supply";
+ };
+ sa {
+ description "";
+ name "Mission involvement: Maintain safe water supply";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_mission_participant;
+ sa @sa_mission_participant;
+}
+
+trace trace_oa_monitor "Pressure supervision ↔ Pressure supervision" id "b53863a2-ed80-5f98-ad3c-50e1b821066d" {
+ baseline {
+ oa {
+ description "";
+ name "Pressure supervision";
+ };
+ sa {
+ description "";
+ name "Pressure supervision";
+ };
+ };
+ description "";
+ kind State;
+ oa @oa_monitor;
+ sa @sa_monitor;
+}
+
+trace trace_oa_monitor_initial "Start ↔ Start" id "48d243f0-553f-507e-acb0-9474ae156b63" {
+ baseline {
+ oa {
+ description "";
+ name "Start";
+ };
+ sa {
+ description "";
+ name "Start";
+ };
+ };
+ description "";
+ kind State;
+ oa @oa_monitor_initial;
+ sa @sa_monitor_initial;
+}
+
+trace trace_oa_monitor_region "Supervision cycle ↔ Supervision cycle" id "208753e0-9151-50d8-86ca-29fc28eca80b" {
+ baseline {
+ oa {
+ description "";
+ name "Supervision cycle";
+ };
+ sa {
+ description "";
+ name "Supervision cycle";
+ };
+ };
+ description "";
+ kind State;
+ oa @oa_monitor_region;
+ sa @sa_monitor_region;
+}
+
+trace trace_oa_monitoring "Monitoring ↔ Monitoring" id "12ec320f-2e14-5a5a-8c6b-bd2b6471dae9" {
+ baseline {
+ oa {
+ description "";
+ name "Monitoring";
+ };
+ sa {
+ description "";
+ name "Monitoring";
+ };
+ };
+ description "";
+ kind State;
+ oa @oa_monitoring;
+ sa @sa_monitoring;
+}
+
+trace trace_oa_normal_transition "State transition: Monitoring ↔ State transition: Monitoring" id "c47cf0b4-fe15-5175-bd62-5292e43adc39" {
+ baseline {
+ oa {
+ description "";
+ name "State transition: Monitoring";
+ };
+ sa {
+ description "";
+ name "State transition: Monitoring";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_normal_transition;
+ sa @sa_normal_transition;
+}
+
+trace trace_oa_outer_controller "Controller ↔ Controller" id "fd195498-dbba-523b-94d0-49f3b9531a3e" {
+ baseline {
+ oa {
+ description "";
+ name "Controller";
+ };
+ sa {
+ description "";
+ name "Controller";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_outer_controller;
+ sa @sa_outer_controller;
+}
+
+trace trace_oa_outer_technician "Technician ↔ Technician" id "c132a0df-1270-55a8-ac4e-6e5075823dc1" {
+ baseline {
+ oa {
+ description "";
+ name "Technician";
+ };
+ sa {
+ description "";
+ name "Technician";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_outer_technician;
+ sa @sa_outer_technician;
+}
+
+trace trace_oa_pressure "Measured pressure ↔ Measured pressure" id "d9d47371-e98d-5d29-9df7-4de2d28a5c73" {
+ baseline {
+ oa {
+ description "";
+ name "Measured pressure";
+ };
+ sa {
+ description "";
+ name "Measured pressure";
+ };
+ };
+ description "";
+ kind Item;
+ oa @oa_pressure;
+ sa @sa_pressure;
+}
+
+trace trace_oa_pressure_parameter "Pressure before / after repair ↔ Pressure before / after repair" id "0d905fbb-5b3c-56f0-9f11-a27091901eb0" {
+ baseline {
+ oa {
+ description "";
+ name "Pressure before / after repair";
+ };
+ sa {
+ description "";
+ name "Pressure before / after repair";
+ };
+ };
+ description "";
+ kind Item;
+ oa @oa_pressure_parameter;
+ sa @sa_pressure_parameter;
+}
+
+trace trace_oa_remote_merge "Continue ↔ Continue" id "1b788e74-36b2-5c17-a936-642dbe51ca3c" {
+ baseline {
+ oa {
+ description "";
+ name "Continue";
+ };
+ sa {
+ description "";
+ name "Continue";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_remote_merge;
+ sa @sa_remote_merge;
+}
+
+trace trace_oa_remote_path "remote reset is authorized ↔ remote reset is authorized" id "eee79ec1-41d7-5374-986e-995a56b9026f" {
+ baseline {
+ oa {
+ description "";
+ name "remote reset is authorized";
+ };
+ sa {
+ description "";
+ name "remote reset is authorized";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_remote_path;
+ sa @sa_remote_path;
+}
+
+trace trace_oa_repair "Repair valve in the field ↔ Repair valve in the field" id "b13452bb-1a55-5ad6-b6d9-a37fb34e8d13" {
+ baseline {
+ oa {
+ description "";
+ name "Repair valve in the field";
+ };
+ sa {
+ description "";
+ name "Repair valve in the field";
+ };
+ };
+ description "";
+ kind Refinement;
+ oa @oa_repair;
+ sa @sa_repair;
+}
+
+trace trace_oa_repair_allocation "Allocation: Repair valve in the field ↔ Allocation: Repair valve in the field" id "3714d547-006f-5c3a-97f8-175ccc23d994" {
+ baseline {
+ oa {
+ description "";
+ name "Allocation: Repair valve in the field";
+ };
+ sa {
+ description "";
+ name "Allocation: Repair valve in the field";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_repair_allocation;
+ sa @sa_repair_allocation;
+}
+
+trace trace_oa_repair_operation "Valve repair ↔ Valve repair" id "cf92cedf-ea1a-510c-9bc5-89c16d60e8d2" {
+ baseline {
+ oa {
+ description "";
+ name "Valve repair";
+ };
+ sa {
+ description "";
+ name "Valve repair";
+ };
+ };
+ description "";
+ kind Item;
+ oa @oa_repair_operation;
+ sa @sa_repair_operation;
+}
+
+trace trace_oa_repair_report "Report repair result ↔ Report repair result" id "424eca39-1c29-5a2f-b9ee-33dd018926aa" {
+ baseline {
+ oa {
+ description "";
+ name "Report repair result";
+ };
+ sa {
+ description "";
+ name "Report repair result";
+ };
+ };
+ description "";
+ kind Exchange;
+ oa @oa_repair_report;
+ sa @sa_repair_report;
+}
+
+trace trace_oa_repair_request "Request field repair ↔ Request field repair" id "da8efc3c-78df-5b10-b2e5-1e28d6d99e86" {
+ baseline {
+ oa {
+ description "";
+ name "Request field repair";
+ };
+ sa {
+ description "";
+ name "Request field repair";
+ };
+ };
+ description "";
+ kind Exchange;
+ oa @oa_repair_request;
+ sa @sa_repair_request;
+}
+
+trace trace_oa_repair_step "Repair the valve ↔ Repair the valve" id "ac3aa6b0-568e-5a3f-963a-172aa3cfc7a4" {
+ baseline {
+ oa {
+ description "";
+ name "Repair the valve";
+ };
+ sa {
+ description "";
+ name "Repair the valve";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_repair_step;
+ sa @sa_repair_step;
+}
+
+trace trace_oa_repair_then_report "Sequence link: Report the measured result ↔ Sequence link: Report the measured result" id "f8d34715-1dc8-504b-acdf-78b770aad1e8" {
+ baseline {
+ oa {
+ description "";
+ name "Sequence link: Report the measured result";
+ };
+ sa {
+ description "";
+ name "Sequence link: Report the measured result";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_repair_then_report;
+ sa @sa_repair_then_report;
+}
+
+trace trace_oa_repair_traffic "Repair work exchanges ↔ Repair work exchanges" id "3e7bde54-f9c1-549f-9e5b-2ce1ecb685a7" {
+ baseline {
+ oa {
+ description "";
+ name "Repair work exchanges";
+ };
+ sa {
+ description "";
+ name "Repair work exchanges";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_repair_traffic;
+ sa @sa_repair_traffic;
+}
+
+trace trace_oa_reply "Return pressure after repair ↔ Return pressure after repair" id "595b5729-ccd9-5291-b5d9-30d8c8bd9dfa" {
+ baseline {
+ oa {
+ description "";
+ name "Return pressure after repair";
+ };
+ sa {
+ description "";
+ name "Return pressure after repair";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_reply;
+ sa @sa_reply;
+}
+
+trace trace_oa_report_step "Report the measured result ↔ Report the measured result" id "e63b14d2-f408-56a2-aec4-be20cf4ace73" {
+ baseline {
+ oa {
+ description "";
+ name "Report the measured result";
+ };
+ sa {
+ description "";
+ name "Report the measured result";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_report_step;
+ sa @sa_report_step;
+}
+
+trace trace_oa_request_step "Request the field repair ↔ Request the field repair" id "ad697c00-07d9-5799-89b3-2c78da8dff4d" {
+ baseline {
+ oa {
+ description "";
+ name "Request the field repair";
+ };
+ sa {
+ description "";
+ name "Request the field repair";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_request_step;
+ sa @sa_request_step;
+}
+
+trace trace_oa_request_then_repair "Sequence link: Repair the valve ↔ Sequence link: Repair the valve" id "f2b7050c-629d-533f-b873-bffc33e072f7" {
+ baseline {
+ oa {
+ description "";
+ name "Sequence link: Repair the valve";
+ };
+ sa {
+ description "";
+ name "Sequence link: Repair the valve";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_request_then_repair;
+ sa @sa_request_then_repair;
+}
+
+trace trace_oa_reset "Reset valve remotely ↔ Reset valve remotely" id "82a5be1a-92a8-578d-9448-086062a20d8f" {
+ baseline {
+ oa {
+ description "";
+ name "Reset valve remotely";
+ };
+ sa {
+ description "";
+ name "Reset valve remotely";
+ };
+ };
+ description "";
+ kind Refinement;
+ oa @oa_reset;
+ sa @sa_reset;
+}
+
+trace trace_oa_reset_allocation "Allocation: Reset valve remotely ↔ Allocation: Reset valve remotely" id "98ef05b5-5381-5d35-a433-ebab55e9f382" {
+ baseline {
+ oa {
+ description "";
+ name "Allocation: Reset valve remotely";
+ };
+ sa {
+ description "";
+ name "Allocation: Reset valve remotely";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_reset_allocation;
+ sa @sa_reset_allocation;
+}
+
+trace trace_oa_reset_step "Try the authorized remote reset ↔ Try the authorized remote reset" id "ff84fc06-6f99-5cf3-b73d-236e9eff98a3" {
+ baseline {
+ oa {
+ description "";
+ name "Try the authorized remote reset";
+ };
+ sa {
+ description "";
+ name "Try the authorized remote reset";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_reset_step;
+ sa @sa_reset_step;
+}
+
+trace trace_oa_responder "Technician ↔ Technician" id "c5051628-b7af-5fb1-a014-bb60a0b46e03" {
+ baseline {
+ oa {
+ description "";
+ name "Technician";
+ };
+ sa {
+ description "";
+ name "Technician";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_responder;
+ sa @sa_responder;
+}
+
+trace trace_oa_response "Handle a pressure alarm ↔ Handle a pressure alarm" id "d3626818-4846-5893-9c02-3407cdba7f3c" {
+ baseline {
+ oa {
+ description "";
+ name "Handle a pressure alarm";
+ };
+ sa {
+ description "";
+ name "Handle a pressure alarm";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_response;
+ sa @sa_response;
+}
+
+trace trace_oa_response_delivery "Chain involvement: Restore a safe supply ↔ Chain involvement: Restore a safe supply" id "70c90b0c-85ce-574a-a217-f0d19fad30e1" {
+ baseline {
+ oa {
+ description "";
+ name "Chain involvement: Restore a safe supply";
+ };
+ sa {
+ description "";
+ name "Chain involvement: Restore a safe supply";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_response_delivery;
+ sa @sa_response_delivery;
+}
+
+trace trace_oa_restore "Restore a safe supply ↔ Restore a safe supply" id "0fbef164-b5d3-58ba-ba41-54d0e63709ab" {
+ baseline {
+ oa {
+ description "";
+ name "Restore a safe supply";
+ };
+ sa {
+ description "";
+ name "Restore a safe supply";
+ };
+ };
+ description "";
+ kind Capability;
+ oa @oa_restore;
+ sa @sa_restore;
+}
+
+trace trace_oa_restore_work "Restore safe pressure ↔ Restore safe pressure" id "82cca563-9d5d-5479-aa1a-9db03e9674d1" {
+ baseline {
+ oa {
+ description "";
+ name "Restore safe pressure";
+ };
+ sa {
+ description "";
+ name "Restore safe pressure";
+ };
+ };
+ description "";
+ kind Refinement;
+ oa @oa_restore_work;
+ sa @sa_restore_work;
+}
+
+trace trace_oa_start_monitoring "State transition: Monitoring ↔ State transition: Monitoring" id "07a7b82a-b5a7-5737-abfa-09e422f7420e" {
+ baseline {
+ oa {
+ description "";
+ name "State transition: Monitoring";
+ };
+ sa {
+ description "";
+ name "State transition: Monitoring";
+ };
+ };
+ description "";
+ kind Manual;
+ oa @oa_start_monitoring;
+ sa @sa_start_monitoring;
+}
+
+trace trace_oa_technician "Field technician ↔ Field technician" id "d47d3c36-50fb-5189-b801-d6fd1694bd49" {
+ baseline {
+ oa {
+ description "";
+ name "Field technician";
+ };
+ sa {
+ description "";
+ name "Field technician";
+ };
+ };
+ description "";
+ kind Participant;
+ oa @oa_technician;
+ sa @sa_technician;
+}
+
+trace trace_oa_triage "Assess an alarm ↔ Assess an alarm" id "6acd072d-431e-5881-ab20-1813b4fda0c5" {
+ baseline {
+ oa {
+ description "";
+ name "Assess an alarm";
+ };
+ sa {
+ description "";
+ name "Assess an alarm";
+ };
+ };
+ description "";
+ kind Capability;
+ oa @oa_triage;
+ sa @sa_triage;
+}
+
+trace trace_oa_use_conversation "Use the reviewed repair conversation ↔ Use the reviewed repair conversation" id "f5733d3f-eee3-5cf7-a422-5edd2054d6a6" {
+ baseline {
+ oa {
+ description "";
+ name "Use the reviewed repair conversation";
+ };
+ sa {
+ description "";
+ name "Use the reviewed repair conversation";
+ };
+ };
+ description "";
+ kind Scenario;
+ oa @oa_use_conversation;
+ sa @sa_use_conversation;
+}
+
+trace trace_oa_use_field "Use the field repair procedure ↔ Use the field repair procedure" id "7a6b4b9c-4220-546a-a752-1587c2881fe1" {
+ baseline {
+ oa {
+ description "";
+ name "Use the field repair procedure";
+ };
+ sa {
+ description "";
+ name "Use the field repair procedure";
+ };
+ };
+ description "";
+ kind Sequence;
+ oa @oa_use_field;
+ sa @sa_use_field;
+}
+
+trace trace_oa_waiting "Awaiting repair ↔ Awaiting repair" id "0754be23-e4b8-53bc-b03c-89c2dbd80361" {
+ baseline {
+ oa {
+ description "";
+ name "Awaiting repair";
+ };
+ sa {
+ description "";
+ name "Awaiting repair";
+ };
+ };
+ description "";
+ kind State;
+ oa @oa_waiting;
+ sa @sa_waiting;
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-before.json b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-before.json
new file mode 100644
index 00000000..71f12774
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-before.json
@@ -0,0 +1,1198 @@
+{
+ "alias": "pressure_restoration",
+ "artifact_links": {},
+ "description": "A controller either resets a valve remotely or requests field repair. Compare the operational need with the explicitly chosen coordination-service boundary. Start with README.md beside project.arcadia. Units and guards are descriptive; this model does not simulate a real water network.",
+ "diagrams": {},
+ "elements": {
+ "022d3cfa-bbaf-5e13-924a-8e55099b8745": {
+ "alias": "oa_assessment_procedure",
+ "body": {
+ "Oa": {
+ "Process": {
+ "steps": [
+ "942975b8-0bef-5ae8-9fcb-c37497833fb0"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "022d3cfa-bbaf-5e13-924a-8e55099b8745",
+ "name": "Assess an alarm procedure",
+ "parent": null
+ },
+ "0b07b9ed-492d-57f6-b0b1-ce8a709238a7": {
+ "alias": "oa_assess_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "SequenceReference": {
+ "sequence": "022d3cfa-bbaf-5e13-924a-8e55099b8745"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "name": "Assess the alarm",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "1970608f-85d1-5234-ac86-5e2b0df27dc3": {
+ "alias": "oa_monitor",
+ "body": {
+ "Oa": {
+ "Shared": "StateMachine"
+ }
+ },
+ "description": "",
+ "id": "1970608f-85d1-5234-ac86-5e2b0df27dc3",
+ "name": "Pressure supervision",
+ "parent": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22"
+ },
+ "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771": {
+ "alias": "oa_contact_category",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "ExchangeCategory": {
+ "exchanges": [
+ "27b47887-baf0-518b-bf87-f5acbf7d0b93"
+ ],
+ "family": "Participant"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771",
+ "name": "Field contact",
+ "parent": null
+ },
+ "1b91f8a7-0bdb-514e-b1cd-955658e09927": {
+ "alias": "oa_restore_work",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "1b91f8a7-0bdb-514e-b1cd-955658e09927",
+ "name": "Restore safe pressure",
+ "parent": null
+ },
+ "2011b3ac-614b-5a47-8891-7ff81965f425": {
+ "alias": "oa_outer_controller",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "2011b3ac-614b-5a47-8891-7ff81965f425",
+ "name": "Controller",
+ "parent": "d6f881a4-95da-54e9-ad97-1b8965532629"
+ },
+ "20472233-5ce6-5ffe-b4ad-0bdbccf3e423": {
+ "alias": "oa_conversation",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Scenario": {
+ "context": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "postcondition": "The controller has received the measured pressure after repair",
+ "precondition": "A pressure alarm has been assessed",
+ "steps": [
+ "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "cc2d497a-fd15-5455-9ee5-e70eb95811d8"
+ ],
+ "viewpoint": "ParticipantInteraction"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423",
+ "name": "Repair request and measured reply",
+ "parent": null
+ },
+ "32aa9940-df6e-5a12-b9bd-6337e2d2ed22": {
+ "alias": "oa_control",
+ "body": {
+ "Oa": "Entity"
+ },
+ "description": "",
+ "id": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "name": "Control room",
+ "parent": null
+ },
+ "3dfee23a-148b-5cec-ad2d-b774e84ea5bb": {
+ "alias": "oa_restore",
+ "body": {
+ "Oa": "Capability"
+ },
+ "description": "",
+ "id": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "name": "Restore a safe supply",
+ "parent": null
+ },
+ "41859a65-ded2-547b-ab03-14e2fa740cf9": {
+ "alias": "oa_caller",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "name": "Controller",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "435bf87c-73fd-5e4d-978a-4c471bc9eaf0": {
+ "alias": "sa_control",
+ "body": {
+ "Sa": "System"
+ },
+ "description": "",
+ "id": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "name": "Pressure coordination service",
+ "parent": null
+ },
+ "58bae2b0-f560-50f2-818d-19091d22d18a": {
+ "alias": "oa_response",
+ "body": {
+ "Oa": {
+ "Process": {
+ "steps": [
+ "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "e00594a3-6122-5a6b-b3c2-ecb5506c741d"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "58bae2b0-f560-50f2-818d-19091d22d18a",
+ "name": "Handle a pressure alarm",
+ "parent": null
+ },
+ "58c15833-6e75-523a-b57f-a88f19f6d672": {
+ "alias": "oa_waiting",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "State"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "name": "Awaiting repair",
+ "parent": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7"
+ },
+ "5ba8079c-2dbe-5e98-ae82-2b60c6020a02": {
+ "alias": "oa_request_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "message_kind": "Asynchronous",
+ "receiver": null,
+ "reply_to": null,
+ "sender": null,
+ "values": []
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "name": "Request the field repair",
+ "parent": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ },
+ "5d3af417-244c-54bb-9588-16daedda4e1f": {
+ "alias": "oa_field_procedure",
+ "body": {
+ "Oa": {
+ "Process": {
+ "steps": [
+ "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "df2fd3f8-6bc2-5380-a13f-36643ed35ac7"
+ ]
+ }
+ }
+ },
+ "description": "",
+ "id": "5d3af417-244c-54bb-9588-16daedda4e1f",
+ "name": "Field repair procedure",
+ "parent": null
+ },
+ "61f7bbfb-74a4-5540-9182-2583dfb27a49": {
+ "alias": "oa_assess",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "name": "Assess pressure alarm",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "65d6b8d6-271a-5c84-8b42-abbe310c955e": {
+ "alias": "oa_high_pressure",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Event": {
+ "body": {
+ "Change": {
+ "expression": "measured_pressure > operating_limit"
+ }
+ }
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "65d6b8d6-271a-5c84-8b42-abbe310c955e",
+ "name": "Pressure alarm detected",
+ "parent": null
+ },
+ "6a30b763-cc51-546d-907f-5c63657cfd03": {
+ "alias": "oa_triage",
+ "body": {
+ "Oa": "Capability"
+ },
+ "description": "",
+ "id": "6a30b763-cc51-546d-907f-5c63657cfd03",
+ "name": "Assess an alarm",
+ "parent": null
+ },
+ "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d": {
+ "alias": "oa_merge",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Control": {
+ "kind": "Merge"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "name": "Recovery path completed",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae": {
+ "alias": "oa_monitoring",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "State"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "name": "Monitoring",
+ "parent": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7"
+ },
+ "727c0b5c-8db6-5eb7-83d6-735519c5949f": {
+ "alias": "oa_confirm",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "727c0b5c-8db6-5eb7-83d6-735519c5949f",
+ "name": "Confirm safe pressure",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7": {
+ "alias": "oa_monitor_region",
+ "body": {
+ "Oa": {
+ "Shared": "Region"
+ }
+ },
+ "description": "",
+ "id": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7",
+ "name": "Supervision cycle",
+ "parent": "1970608f-85d1-5234-ac86-5e2b0df27dc3"
+ },
+ "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7": {
+ "alias": "oa_pressure_parameter",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "DataField": {
+ "default": null,
+ "direction": "InOut",
+ "maximum": 1,
+ "minimum": 1,
+ "storage": "Value",
+ "type_ref": {
+ "Named": "e7824764-5db9-5384-8198-2fbaea2b10b2"
+ },
+ "unit": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "name": "Pressure before / after repair",
+ "parent": "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ },
+ "9349b353-53f2-56f8-b571-a05b09335cdf": {
+ "alias": "oa_repair_traffic",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "ExchangeCategory": {
+ "exchanges": [
+ "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "b436d0b3-c824-5a96-af6b-2c269816d1cc"
+ ],
+ "family": "Work"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "9349b353-53f2-56f8-b571-a05b09335cdf",
+ "name": "Repair work exchanges",
+ "parent": null
+ },
+ "942975b8-0bef-5ae8-9fcb-c37497833fb0": {
+ "alias": "oa_assessment_action",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "942975b8-0bef-5ae8-9fcb-c37497833fb0",
+ "name": "Assess pressure alarm",
+ "parent": "022d3cfa-bbaf-5e13-924a-8e55099b8745"
+ },
+ "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5": {
+ "alias": "oa_use_conversation",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "ScenarioUse": {
+ "bindings": [
+ {
+ "local": "2011b3ac-614b-5a47-8891-7ff81965f425",
+ "referenced": "41859a65-ded2-547b-ab03-14e2fa740cf9"
+ },
+ {
+ "local": "fffff882-78fd-5415-928d-c1ef7fac6576",
+ "referenced": "fc81c5f0-0442-5b88-992b-e5b10f351c59"
+ }
+ ],
+ "scenario": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5",
+ "name": "Use the reviewed repair conversation",
+ "parent": "d6f881a4-95da-54e9-ad97-1b8965532629"
+ },
+ "a21465af-05f3-5fb5-8983-de26932422fb": {
+ "alias": "oa_repair",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "name": "Repair valve in the field",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "a7ffb869-9dde-5e76-b276-c719c5c143a9": {
+ "alias": "oa_monitor_initial",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "State": {
+ "do_activity": "",
+ "entry": "",
+ "exit": "",
+ "kind": "Initial"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "a7ffb869-9dde-5e76-b276-c719c5c143a9",
+ "name": "Start",
+ "parent": "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7"
+ },
+ "b280c6ca-e5f9-5424-a5c1-5676e01c2371": {
+ "alias": "oa_decision",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Control": {
+ "kind": "Choice"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "name": "Can a remote reset safely recover pressure?",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "b97437c6-cd7a-5a05-b537-b30a17dd936a": {
+ "alias": "oa_call",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "message_kind": "Call",
+ "receiver": "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "reply_to": null,
+ "sender": "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "values": [
+ {
+ "expression": "420.0",
+ "field": "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "path": []
+ }
+ ]
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "name": "Request valve repair",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "c05b9422-b9da-5de5-b21d-07ad23bd2b8a": {
+ "alias": "sa_technician",
+ "body": {
+ "Sa": "Actor"
+ },
+ "description": "",
+ "id": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a",
+ "name": "Field technician",
+ "parent": null
+ },
+ "c59432fe-6092-5ee0-908d-9dbf39476471": {
+ "alias": "oa_repair_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "name": "Repair the valve",
+ "parent": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ },
+ "c9c61f50-06e0-5d17-973d-dcc57cd5cc90": {
+ "alias": "oa_mission",
+ "body": {
+ "Oa": "Mission"
+ },
+ "description": "",
+ "id": "c9c61f50-06e0-5d17-973d-dcc57cd5cc90",
+ "name": "Maintain safe water supply",
+ "parent": null
+ },
+ "cc2d497a-fd15-5455-9ee5-e70eb95811d8": {
+ "alias": "oa_reply",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "message_kind": "Reply",
+ "receiver": "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "reply_to": "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "sender": "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "values": [
+ {
+ "expression": "320.0",
+ "field": "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "path": []
+ }
+ ]
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "cc2d497a-fd15-5455-9ee5-e70eb95811d8",
+ "name": "Return pressure after repair",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "d0c8be87-d238-503a-a549-7558a210507f": {
+ "alias": "oa_reset",
+ "body": {
+ "Oa": "Activity"
+ },
+ "description": "",
+ "id": "d0c8be87-d238-503a-a549-7558a210507f",
+ "name": "Reset valve remotely",
+ "parent": "1b91f8a7-0bdb-514e-b1cd-955658e09927"
+ },
+ "d2026fc2-74a8-5e22-9942-ff3eb68a3677": {
+ "alias": "oa_reset_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "d0c8be87-d238-503a-a549-7558a210507f",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "name": "Try the authorized remote reset",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "d6f881a4-95da-54e9-ad97-1b8965532629": {
+ "alias": "oa_full_scenario",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Scenario": {
+ "context": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "postcondition": "The controller has received the measured pressure after repair",
+ "precondition": "A pressure alarm has been assessed",
+ "steps": [
+ "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5"
+ ],
+ "viewpoint": "ParticipantInteraction"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d6f881a4-95da-54e9-ad97-1b8965532629",
+ "name": "Restore supply through field repair",
+ "parent": null
+ },
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3": {
+ "alias": "oa_repair_operation",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "ExchangeItem": {
+ "kind": "Information",
+ "mechanism": "Operation",
+ "supertype": null
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "d736c7d6-d369-550a-8ba4-dae223e40bb3",
+ "name": "Valve repair",
+ "parent": null
+ },
+ "df2fd3f8-6bc2-5380-a13f-36643ed35ac7": {
+ "alias": "oa_report_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Exchange": {
+ "exchange": "b436d0b3-c824-5a96-af6b-2c269816d1cc",
+ "message_kind": "Asynchronous",
+ "receiver": null,
+ "reply_to": null,
+ "sender": null,
+ "values": []
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "df2fd3f8-6bc2-5380-a13f-36643ed35ac7",
+ "name": "Report the measured result",
+ "parent": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ },
+ "e00594a3-6122-5a6b-b3c2-ecb5506c741d": {
+ "alias": "oa_confirm_step",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "Action": {
+ "action": "727c0b5c-8db6-5eb7-83d6-735519c5949f",
+ "lifeline": null
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "e00594a3-6122-5a6b-b3c2-ecb5506c741d",
+ "name": "Confirm safe pressure",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "e07c3952-4a1a-5cc7-b7d5-03a01101ce49": {
+ "alias": "oa_technician",
+ "body": {
+ "Oa": "Actor"
+ },
+ "description": "",
+ "id": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "name": "Field technician",
+ "parent": null
+ },
+ "e7824764-5db9-5384-8198-2fbaea2b10b2": {
+ "alias": "oa_pressure",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "DataType": {
+ "default": null,
+ "definition": {
+ "Real": {
+ "maximum": 600.0,
+ "minimum": 0.0
+ }
+ },
+ "unit": "kPa"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "e7824764-5db9-5384-8198-2fbaea2b10b2",
+ "name": "Measured pressure",
+ "parent": null
+ },
+ "fc6763bf-a534-54bf-afcd-57ab285ff07d": {
+ "alias": "oa_use_field",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Occurrence": {
+ "body": {
+ "SequenceReference": {
+ "sequence": "5d3af417-244c-54bb-9588-16daedda4e1f"
+ }
+ },
+ "postcondition": "",
+ "precondition": ""
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "name": "Use the field repair procedure",
+ "parent": "58bae2b0-f560-50f2-818d-19091d22d18a"
+ },
+ "fc81c5f0-0442-5b88-992b-e5b10f351c59": {
+ "alias": "oa_responder",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "name": "Technician",
+ "parent": "20472233-5ce6-5ffe-b4ad-0bdbccf3e423"
+ },
+ "fffff882-78fd-5415-928d-c1ef7fac6576": {
+ "alias": "oa_outer_technician",
+ "body": {
+ "Oa": {
+ "Shared": {
+ "Lifeline": {
+ "represented": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49"
+ }
+ }
+ }
+ },
+ "description": "",
+ "id": "fffff882-78fd-5415-928d-c1ef7fac6576",
+ "name": "Technician",
+ "parent": "d6f881a4-95da-54e9-ad97-1b8965532629"
+ }
+ },
+ "id": "2786d72f-5763-5f33-a57f-2773a4ca1a6c",
+ "name": "Restore safe water pressure — release 1 walkthrough",
+ "relations": {
+ "094b888c-450c-5c4f-971a-7d75db059574": {
+ "alias": "oa_request_carriage",
+ "body": {
+ "ExchangeAllocation": {
+ "source": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "target": "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d"
+ }
+ },
+ "description": "",
+ "id": "094b888c-450c-5c4f-971a-7d75db059574",
+ "name": "Exchange allocation: Request field repair"
+ },
+ "0c4c9e58-ff32-5c00-b82a-d408a4be0183": {
+ "alias": "oa_mission_participant",
+ "body": {
+ "MissionInvolvement": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "c9c61f50-06e0-5d17-973d-dcc57cd5cc90"
+ }
+ },
+ "description": "",
+ "id": "0c4c9e58-ff32-5c00-b82a-d408a4be0183",
+ "name": "Mission involvement: Maintain safe water supply"
+ },
+ "109e2d6b-7e86-5934-93a5-fab56eced84e": {
+ "alias": "oa_assess_then_decide",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "target": "b280c6ca-e5f9-5424-a5c1-5676e01c2371"
+ }
+ },
+ "description": "",
+ "id": "109e2d6b-7e86-5934-93a5-fab56eced84e",
+ "name": "Continue"
+ },
+ "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a": {
+ "alias": "oa_reset_allocation",
+ "body": {
+ "Allocation": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "d0c8be87-d238-503a-a549-7558a210507f"
+ }
+ },
+ "description": "",
+ "id": "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a",
+ "name": "Allocation: Reset valve remotely"
+ },
+ "1f8b1898-65f6-5360-99cf-b78a71bd50ec": {
+ "alias": "oa_mission_delivery",
+ "body": {
+ "MissionCapability": {
+ "source": "c9c61f50-06e0-5d17-973d-dcc57cd5cc90",
+ "target": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb"
+ }
+ },
+ "description": "",
+ "id": "1f8b1898-65f6-5360-99cf-b78a71bd50ec",
+ "name": "Mission capability: Restore a safe supply"
+ },
+ "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97": {
+ "alias": "oa_normal_transition",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "repair confirmed and pressure is safe",
+ "source": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "target": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": []
+ }
+ },
+ "description": "",
+ "id": "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97",
+ "name": "State transition: Monitoring"
+ },
+ "229ee94f-d147-50f6-9769-b5bd0c03a084": {
+ "alias": "oa_confirm_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "target": "e00594a3-6122-5a6b-b3c2-ecb5506c741d"
+ }
+ },
+ "description": "",
+ "id": "229ee94f-d147-50f6-9769-b5bd0c03a084",
+ "name": "Continue"
+ },
+ "27b47887-baf0-518b-bf87-f5acbf7d0b93": {
+ "alias": "oa_contact",
+ "body": {
+ "CommunicationMean": {
+ "items": [
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ ],
+ "oriented": false,
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49"
+ }
+ },
+ "description": "",
+ "id": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "name": "Technician service conversation"
+ },
+ "2b00270c-79a5-52a5-af96-33346bea531b": {
+ "alias": "oa_start_monitoring",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "",
+ "source": "a7ffb869-9dde-5e76-b276-c719c5c143a9",
+ "target": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": []
+ }
+ },
+ "description": "",
+ "id": "2b00270c-79a5-52a5-af96-33346bea531b",
+ "name": "State transition: Monitoring"
+ },
+ "3f54d890-4ca5-5e36-873b-abb5c92d578c": {
+ "alias": "oa_request_then_repair",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "target": "c59432fe-6092-5ee0-908d-9dbf39476471"
+ }
+ },
+ "description": "",
+ "id": "3f54d890-4ca5-5e36-873b-abb5c92d578c",
+ "name": "Sequence link: Repair the valve"
+ },
+ "4549d3fa-5ab9-52a9-b4f9-3a78fc9d625a": {
+ "alias": "oa_report_carriage",
+ "body": {
+ "ExchangeAllocation": {
+ "source": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "target": "b436d0b3-c824-5a96-af6b-2c269816d1cc"
+ }
+ },
+ "description": "",
+ "id": "4549d3fa-5ab9-52a9-b4f9-3a78fc9d625a",
+ "name": "Exchange allocation: Report repair result"
+ },
+ "594c61cd-94e0-5c07-890d-edfd520d7df9": {
+ "alias": "oa_assess_allocation",
+ "body": {
+ "Allocation": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "61f7bbfb-74a4-5540-9182-2583dfb27a49"
+ }
+ },
+ "description": "",
+ "id": "594c61cd-94e0-5c07-890d-edfd520d7df9",
+ "name": "Allocation: Assess pressure alarm"
+ },
+ "628cb4fc-fe63-5f6b-b364-6268f26b5740": {
+ "alias": "oa_assessment_delivery",
+ "body": {
+ "ChainInvolvement": {
+ "source": "022d3cfa-bbaf-5e13-924a-8e55099b8745",
+ "target": "6a30b763-cc51-546d-907f-5c63657cfd03"
+ }
+ },
+ "description": "",
+ "id": "628cb4fc-fe63-5f6b-b364-6268f26b5740",
+ "name": "Chain involvement: Assess an alarm"
+ },
+ "76218d89-68fd-531d-b35b-90c5b769a5ef": {
+ "alias": "oa_remote_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "remote reset is authorized",
+ "source": "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "target": "d2026fc2-74a8-5e22-9942-ff3eb68a3677"
+ }
+ },
+ "description": "",
+ "id": "76218d89-68fd-531d-b35b-90c5b769a5ef",
+ "name": "remote reset is authorized"
+ },
+ "7fa37e63-db65-5aa7-88f2-31ff4347a787": {
+ "alias": "oa_assess_on_entry",
+ "body": {
+ "StateAction": {
+ "phase": "Entry",
+ "source": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "target": "61f7bbfb-74a4-5540-9182-2583dfb27a49"
+ }
+ },
+ "description": "",
+ "id": "7fa37e63-db65-5aa7-88f2-31ff4347a787",
+ "name": "Assess when repair is needed"
+ },
+ "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2": {
+ "alias": "oa_repair_allocation",
+ "body": {
+ "Allocation": {
+ "source": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "target": "a21465af-05f3-5fb5-8983-de26932422fb"
+ }
+ },
+ "description": "",
+ "id": "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2",
+ "name": "Allocation: Repair valve in the field"
+ },
+ "b436d0b3-c824-5a96-af6b-2c269816d1cc": {
+ "alias": "oa_repair_report",
+ "body": {
+ "Interaction": {
+ "items": [
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ ],
+ "source": "a21465af-05f3-5fb5-8983-de26932422fb",
+ "target": "727c0b5c-8db6-5eb7-83d6-735519c5949f"
+ }
+ },
+ "description": "",
+ "id": "b436d0b3-c824-5a96-af6b-2c269816d1cc",
+ "name": "Report repair result"
+ },
+ "bac43d3b-910b-5bdc-be9b-f78cf973e1ed": {
+ "alias": "oa_remote_merge",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "target": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d"
+ }
+ },
+ "description": "",
+ "id": "bac43d3b-910b-5bdc-be9b-f78cf973e1ed",
+ "name": "Continue"
+ },
+ "bfd4326e-ea65-5269-b5e6-e063a355d1e9": {
+ "alias": "oa_confirm_allocation",
+ "body": {
+ "Allocation": {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "727c0b5c-8db6-5eb7-83d6-735519c5949f"
+ }
+ },
+ "description": "",
+ "id": "bfd4326e-ea65-5269-b5e6-e063a355d1e9",
+ "name": "Allocation: Confirm safe pressure"
+ },
+ "c1adbbd0-a762-5ee3-8b9a-5c18af377771": {
+ "alias": "oa_field_path",
+ "body": {
+ "SequenceLink": {
+ "guard": "else",
+ "source": "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "target": "fc6763bf-a534-54bf-afcd-57ab285ff07d"
+ }
+ },
+ "description": "",
+ "id": "c1adbbd0-a762-5ee3-8b9a-5c18af377771",
+ "name": "else"
+ },
+ "c7c248f8-28b8-55be-9023-0991d44021e3": {
+ "alias": "oa_response_delivery",
+ "body": {
+ "ChainInvolvement": {
+ "source": "58bae2b0-f560-50f2-818d-19091d22d18a",
+ "target": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb"
+ }
+ },
+ "description": "",
+ "id": "c7c248f8-28b8-55be-9023-0991d44021e3",
+ "name": "Chain involvement: Restore a safe supply"
+ },
+ "c8196d35-2168-5fef-8b03-ba0d139bc00f": {
+ "alias": "oa_repair_then_report",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "target": "df2fd3f8-6bc2-5380-a13f-36643ed35ac7"
+ }
+ },
+ "description": "",
+ "id": "c8196d35-2168-5fef-8b03-ba0d139bc00f",
+ "name": "Sequence link: Report the measured result"
+ },
+ "c8593670-8906-530f-b6a2-3ada77a9c219": {
+ "alias": "oa_field_merge",
+ "body": {
+ "SequenceLink": {
+ "guard": "",
+ "source": "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "target": "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d"
+ }
+ },
+ "description": "",
+ "id": "c8593670-8906-530f-b6a2-3ada77a9c219",
+ "name": "Continue"
+ },
+ "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d": {
+ "alias": "oa_repair_request",
+ "body": {
+ "Interaction": {
+ "items": [
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3"
+ ],
+ "source": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "target": "a21465af-05f3-5fb5-8983-de26932422fb"
+ }
+ },
+ "description": "",
+ "id": "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "name": "Request field repair"
+ },
+ "e21d9a54-ae8e-5618-a4f8-67ee22b92638": {
+ "alias": "oa_include_triage",
+ "body": {
+ "CapabilityInclude": {
+ "source": "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "target": "6a30b763-cc51-546d-907f-5c63657cfd03"
+ }
+ },
+ "description": "",
+ "id": "e21d9a54-ae8e-5618-a4f8-67ee22b92638",
+ "name": "Capability include: Assess an alarm"
+ },
+ "fbde9d5e-c7e9-55db-a2be-497345c491b3": {
+ "alias": "oa_alarm_transition",
+ "body": {
+ "StateTransition": {
+ "effect": "",
+ "effects": [],
+ "guard": "",
+ "source": "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "target": "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "transition_kind": "External",
+ "trigger": "",
+ "triggers": [
+ "65d6b8d6-271a-5c84-8b42-abbe310c955e"
+ ]
+ }
+ },
+ "description": "",
+ "id": "fbde9d5e-c7e9-55db-a2be-497345c491b3",
+ "name": "State transition: Awaiting repair"
+ },
+ "ffdbfcd0-d423-5707-a880-0dfbe1400f72": {
+ "alias": "oa_assessment_support",
+ "body": {
+ "CapabilityInvolvement": {
+ "source": "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "target": "6a30b763-cc51-546d-907f-5c63657cfd03"
+ }
+ },
+ "description": "",
+ "id": "ffdbfcd0-d423-5707-a880-0dfbe1400f72",
+ "name": "Capability involvement: Assess an alarm"
+ }
+ },
+ "schema_version": 7,
+ "traces": {}
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-request.json b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-request.json
new file mode 100644
index 00000000..b2cc442b
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/transition-request.json
@@ -0,0 +1,114 @@
+{
+ "communication_endpoints": [
+ {
+ "source": "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "source_port": {
+ "Create": {
+ "alias": "sa_control_port",
+ "description": "Explicitly chosen Standard component port for the repair service.",
+ "id": "98d411e7-7c65-5c3f-b543-8bf0f2522ea2",
+ "kind": "Standard",
+ "name": "Repair service client",
+ "orientation": "Unset"
+ }
+ },
+ "target_port": {
+ "Create": {
+ "alias": "sa_technician_port",
+ "description": "Explicitly chosen Standard component port for the repair service.",
+ "id": "e6d69cbd-99d7-596b-bbe3-dada6118a600",
+ "kind": "Standard",
+ "name": "Repair service provider",
+ "orientation": "Unset"
+ }
+ }
+ }
+ ],
+ "destination_diagram": null,
+ "direction": "OaToSa",
+ "fields": [],
+ "mappings": [
+ {
+ "source": "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "target": "435bf87c-73fd-5e4d-978a-4c471bc9eaf0",
+ "target_kind": null
+ },
+ {
+ "source": "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "target": "c05b9422-b9da-5de5-b21d-07ad23bd2b8a",
+ "target_kind": null
+ }
+ ],
+ "resolutions": [],
+ "scope": "SelectedOnly",
+ "sources": [
+ "022d3cfa-bbaf-5e13-924a-8e55099b8745",
+ "0b07b9ed-492d-57f6-b0b1-ce8a709238a7",
+ "1970608f-85d1-5234-ac86-5e2b0df27dc3",
+ "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771",
+ "1b91f8a7-0bdb-514e-b1cd-955658e09927",
+ "2011b3ac-614b-5a47-8891-7ff81965f425",
+ "20472233-5ce6-5ffe-b4ad-0bdbccf3e423",
+ "32aa9940-df6e-5a12-b9bd-6337e2d2ed22",
+ "3dfee23a-148b-5cec-ad2d-b774e84ea5bb",
+ "41859a65-ded2-547b-ab03-14e2fa740cf9",
+ "58bae2b0-f560-50f2-818d-19091d22d18a",
+ "58c15833-6e75-523a-b57f-a88f19f6d672",
+ "5ba8079c-2dbe-5e98-ae82-2b60c6020a02",
+ "5d3af417-244c-54bb-9588-16daedda4e1f",
+ "61f7bbfb-74a4-5540-9182-2583dfb27a49",
+ "65d6b8d6-271a-5c84-8b42-abbe310c955e",
+ "6a30b763-cc51-546d-907f-5c63657cfd03",
+ "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d",
+ "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae",
+ "727c0b5c-8db6-5eb7-83d6-735519c5949f",
+ "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7",
+ "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7",
+ "9349b353-53f2-56f8-b571-a05b09335cdf",
+ "942975b8-0bef-5ae8-9fcb-c37497833fb0",
+ "9ff94014-afa1-50ab-a4d3-8e9dd0565cc5",
+ "a21465af-05f3-5fb5-8983-de26932422fb",
+ "a7ffb869-9dde-5e76-b276-c719c5c143a9",
+ "b280c6ca-e5f9-5424-a5c1-5676e01c2371",
+ "b97437c6-cd7a-5a05-b537-b30a17dd936a",
+ "c59432fe-6092-5ee0-908d-9dbf39476471",
+ "c9c61f50-06e0-5d17-973d-dcc57cd5cc90",
+ "cc2d497a-fd15-5455-9ee5-e70eb95811d8",
+ "d0c8be87-d238-503a-a549-7558a210507f",
+ "d2026fc2-74a8-5e22-9942-ff3eb68a3677",
+ "d6f881a4-95da-54e9-ad97-1b8965532629",
+ "d736c7d6-d369-550a-8ba4-dae223e40bb3",
+ "df2fd3f8-6bc2-5380-a13f-36643ed35ac7",
+ "e00594a3-6122-5a6b-b3c2-ecb5506c741d",
+ "e07c3952-4a1a-5cc7-b7d5-03a01101ce49",
+ "e7824764-5db9-5384-8198-2fbaea2b10b2",
+ "fc6763bf-a534-54bf-afcd-57ab285ff07d",
+ "fc81c5f0-0442-5b88-992b-e5b10f351c59",
+ "fffff882-78fd-5415-928d-c1ef7fac6576",
+ "0c4c9e58-ff32-5c00-b82a-d408a4be0183",
+ "109e2d6b-7e86-5934-93a5-fab56eced84e",
+ "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a",
+ "1f8b1898-65f6-5360-99cf-b78a71bd50ec",
+ "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97",
+ "229ee94f-d147-50f6-9769-b5bd0c03a084",
+ "27b47887-baf0-518b-bf87-f5acbf7d0b93",
+ "2b00270c-79a5-52a5-af96-33346bea531b",
+ "3f54d890-4ca5-5e36-873b-abb5c92d578c",
+ "594c61cd-94e0-5c07-890d-edfd520d7df9",
+ "628cb4fc-fe63-5f6b-b364-6268f26b5740",
+ "76218d89-68fd-531d-b35b-90c5b769a5ef",
+ "7fa37e63-db65-5aa7-88f2-31ff4347a787",
+ "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2",
+ "b436d0b3-c824-5a96-af6b-2c269816d1cc",
+ "bac43d3b-910b-5bdc-be9b-f78cf973e1ed",
+ "bfd4326e-ea65-5269-b5e6-e063a355d1e9",
+ "c1adbbd0-a762-5ee3-8b9a-5c18af377771",
+ "c7c248f8-28b8-55be-9023-0991d44021e3",
+ "c8196d35-2168-5fef-8b03-ba0d139bc00f",
+ "c8593670-8906-530f-b6a2-3ada77a9c219",
+ "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d",
+ "e21d9a54-ae8e-5618-a4f8-67ee22b92638",
+ "fbde9d5e-c7e9-55db-a2be-497345c491b3",
+ "ffdbfcd0-d423-5707-a880-0dfbe1400f72"
+ ]
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_oa_allocation_view.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_oa_allocation_view.arcadia
new file mode 100644
index 00000000..0c34ca06
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_oa_allocation_view.arcadia
@@ -0,0 +1,100 @@
+diagram oa_allocation_view "1 · OA responsibility and repair traffic" id "103ea62c-ac56-5827-94e7-cf09bca96ccc" {
+ description "Teaching view: positions and filters do not change ownership or exchange identities.";
+ layer Oa;
+ projection Allocation;
+ routes [];
+ view_options {
+ coverage "All";
+ exchange_labels "Name";
+ hidden_edges [];
+ participant_categories "All";
+ relation_kinds "All";
+ work_categories "All";
+ work_contexts "All";
+ };
+}
+
+node oa_allocation_view_oa_assess id "5ae1c46d-59a3-50fd-8cc3-b292df21f951" {
+ collapsed false;
+ diagram @oa_allocation_view;
+ element @oa_assess;
+ position {
+ x 100.0;
+ y 170.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node oa_allocation_view_oa_confirm id "edd74060-ac61-5450-a57c-a6577daa5d50" {
+ collapsed false;
+ diagram @oa_allocation_view;
+ element @oa_confirm;
+ position {
+ x 100.0;
+ y 490.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node oa_allocation_view_oa_control id "3191e05c-1c7f-5f0e-beb0-5f3bded54c48" {
+ collapsed false;
+ diagram @oa_allocation_view;
+ element @oa_control;
+ position {
+ x 60.0;
+ y 80.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node oa_allocation_view_oa_repair id "202ebe37-ddb4-5099-8ce9-ec3e895ab9e9" {
+ collapsed false;
+ diagram @oa_allocation_view;
+ element @oa_repair;
+ position {
+ x 890.0;
+ y 170.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node oa_allocation_view_oa_reset id "7066f78c-9736-5349-b94a-0587331b980e" {
+ collapsed false;
+ diagram @oa_allocation_view;
+ element @oa_reset;
+ position {
+ x 100.0;
+ y 330.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node oa_allocation_view_oa_technician id "d27f28ef-f10a-5355-9a05-6f2fd39b7de6" {
+ collapsed false;
+ diagram @oa_allocation_view;
+ element @oa_technician;
+ position {
+ x 850.0;
+ y 80.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_allocation_view.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_allocation_view.arcadia
new file mode 100644
index 00000000..b3f72d22
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_allocation_view.arcadia
@@ -0,0 +1,100 @@
+diagram sa_allocation_view "2 · SA service boundary and functions" id "35d3c24b-289e-59e5-a610-8333e2c6a4bb" {
+ description "Teaching view: positions and filters do not change ownership or exchange identities.";
+ layer Sa;
+ projection Allocation;
+ routes [];
+ view_options {
+ coverage "All";
+ exchange_labels "Name";
+ hidden_edges [];
+ participant_categories "All";
+ relation_kinds "All";
+ work_categories "All";
+ work_contexts "All";
+ };
+}
+
+node sa_allocation_view_sa_assess id "60f6ad74-b69e-5aef-b832-f1bd60b903ae" {
+ collapsed false;
+ diagram @sa_allocation_view;
+ element @sa_assess;
+ position {
+ x 100.0;
+ y 170.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_allocation_view_sa_confirm id "7e3ba36f-bc68-5520-bdb9-77083bb7bba2" {
+ collapsed false;
+ diagram @sa_allocation_view;
+ element @sa_confirm;
+ position {
+ x 100.0;
+ y 490.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_allocation_view_sa_control id "8345c76a-8d85-5c31-902e-ec1cd33ed4a3" {
+ collapsed false;
+ diagram @sa_allocation_view;
+ element @sa_control;
+ position {
+ x 60.0;
+ y 80.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_allocation_view_sa_repair id "34630a18-41b0-5fdd-bff4-ee86e93e8651" {
+ collapsed false;
+ diagram @sa_allocation_view;
+ element @sa_repair;
+ position {
+ x 890.0;
+ y 170.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_allocation_view_sa_reset id "73406244-ebd7-5995-86c7-e17c73c50b32" {
+ collapsed false;
+ diagram @sa_allocation_view;
+ element @sa_reset;
+ position {
+ x 100.0;
+ y 330.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_allocation_view_sa_technician id "f7225d1c-e506-505d-bb20-916950269d4b" {
+ collapsed false;
+ diagram @sa_allocation_view;
+ element @sa_technician;
+ position {
+ x 850.0;
+ y 80.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_capability_view.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_capability_view.arcadia
new file mode 100644
index 00000000..0b6b78ed
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_capability_view.arcadia
@@ -0,0 +1,72 @@
+diagram sa_capability_view "4 · SA mission and included capability" id "3e6ad194-61b5-584c-b0f0-a78a20af5781" {
+ description "Teaching view: positions and filters do not change ownership or exchange identities.";
+ layer Sa;
+ projection Capabilities;
+ routes [];
+ view_options {
+ coverage "All";
+ exchange_labels "Name";
+ hidden_edges [];
+ participant_categories "All";
+ relation_kinds "All";
+ work_categories "All";
+ work_contexts "All";
+ };
+}
+
+node sa_capability_view_sa_control id "41ddb68b-bd2b-5f91-82eb-efde01157828" {
+ collapsed false;
+ diagram @sa_capability_view;
+ element @sa_control;
+ position {
+ x 60.0;
+ y 60.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_capability_view_sa_mission id "4b814335-bf98-5b68-85d5-ea549766517a" {
+ collapsed false;
+ diagram @sa_capability_view;
+ element @sa_mission;
+ position {
+ x 420.0;
+ y 60.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_capability_view_sa_restore id "d3bc13f9-7726-5280-83fe-63f391c45afb" {
+ collapsed false;
+ diagram @sa_capability_view;
+ element @sa_restore;
+ position {
+ x 420.0;
+ y 260.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_capability_view_sa_triage id "069a3fb3-077c-59d8-91da-27b0665143c7" {
+ collapsed false;
+ diagram @sa_capability_view;
+ element @sa_triage;
+ position {
+ x 800.0;
+ y 260.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_service_view.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_service_view.arcadia
new file mode 100644
index 00000000..48576c7b
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_service_view.arcadia
@@ -0,0 +1,104 @@
+diagram sa_service_view "3 · SA service conversation only" id "50af4c9b-93ce-5ff5-a1f8-7a81eb97e779" {
+ description "Teaching view: positions and filters do not change ownership or exchange identities.";
+ layer Sa;
+ projection Allocation;
+ routes [];
+ view_options {
+ coverage "All";
+ exchange_labels "NameAndItems";
+ hidden_edges [];
+ participant_categories {
+ Any [@sa_contact_category];
+ };
+ relation_kinds "All";
+ work_categories {
+ Any [];
+ };
+ work_contexts "All";
+ };
+}
+
+node sa_service_view_sa_assess id "439f1e45-e5ed-567c-978b-4c93ef267db1" {
+ collapsed false;
+ diagram @sa_service_view;
+ element @sa_assess;
+ position {
+ x 100.0;
+ y 170.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_service_view_sa_confirm id "6f138672-149b-5f88-ab07-7fc413a42262" {
+ collapsed false;
+ diagram @sa_service_view;
+ element @sa_confirm;
+ position {
+ x 100.0;
+ y 490.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_service_view_sa_control id "f53d4352-b042-56a6-8b3e-c51cd27254f9" {
+ collapsed false;
+ diagram @sa_service_view;
+ element @sa_control;
+ position {
+ x 60.0;
+ y 80.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_service_view_sa_repair id "ae36489d-6ec8-5f1c-b603-8b497227efba" {
+ collapsed false;
+ diagram @sa_service_view;
+ element @sa_repair;
+ position {
+ x 890.0;
+ y 170.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_service_view_sa_reset id "d52da4e6-40b7-5dbc-84fb-80c443711c69" {
+ collapsed false;
+ diagram @sa_service_view;
+ element @sa_reset;
+ position {
+ x 100.0;
+ y 330.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_service_view_sa_technician id "40210589-69f7-5209-b329-10bb83cf615c" {
+ collapsed false;
+ diagram @sa_service_view;
+ element @sa_technician;
+ position {
+ x 850.0;
+ y 80.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_state_view.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_state_view.arcadia
new file mode 100644
index 00000000..6bbb6df1
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-baseline/views/diagram_sa_state_view.arcadia
@@ -0,0 +1,90 @@
+diagram sa_state_view "5 · SA pressure supervision" id "30d5baf9-6581-5cb6-846a-bcaa1e6cce63" {
+ description "Teaching view: positions and filters do not change ownership or exchange identities.";
+ layer Sa;
+ projection {
+ StateMachine {
+ machine @sa_monitor;
+ };
+ };
+ routes [];
+ view_options {
+ coverage "All";
+ exchange_labels "Name";
+ hidden_edges [];
+ participant_categories "All";
+ relation_kinds "All";
+ work_categories "All";
+ work_contexts "All";
+ };
+}
+
+node sa_state_view_sa_monitor id "a676fbb9-0833-59a8-98cf-a10d8f4b7434" {
+ collapsed false;
+ diagram @sa_state_view;
+ element @sa_monitor;
+ position {
+ x 30.0;
+ y 30.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_state_view_sa_monitor_initial id "5e2326a0-0f6c-5f8d-8a21-fdded18a9281" {
+ collapsed false;
+ diagram @sa_state_view;
+ element @sa_monitor_initial;
+ position {
+ x 100.0;
+ y 200.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_state_view_sa_monitor_region id "55a74f91-2025-5001-8db8-9566b1dbe783" {
+ collapsed false;
+ diagram @sa_state_view;
+ element @sa_monitor_region;
+ position {
+ x 70.0;
+ y 95.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_state_view_sa_monitoring id "b3af7b04-5a45-5b82-ab69-7eada4b876f0" {
+ collapsed false;
+ diagram @sa_state_view;
+ element @sa_monitoring;
+ position {
+ x 220.0;
+ y 200.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
+node sa_state_view_sa_waiting id "57acbb71-ca86-57fc-ae90-42dad195f0e0" {
+ collapsed false;
+ diagram @sa_state_view;
+ element @sa_waiting;
+ position {
+ x 650.0;
+ y 200.0;
+ };
+ size {
+ height 110.0;
+ width 245.0;
+ };
+}
+
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same-result.json b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same-result.json
new file mode 100644
index 00000000..4f60b5ac
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same-result.json
@@ -0,0 +1,14 @@
+{
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\release1-baseline\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\release1-work\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 0,
+ "expected_success": true,
+ "expectation_met": true,
+ "seconds": 0.39099999999962165,
+ "finished_utc": "2026-09-11T17:45:35.339540+00:00"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same.stderr.log b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same.stderr.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same.stdout.log b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same.stdout.log
new file mode 100644
index 00000000..92ba2034
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/release1-same.stdout.log
@@ -0,0 +1 @@
+PASS exact model/identity/30 reachable sources; 30 exact disk files (0 proven retained)
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/links.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/links.arcadia
new file mode 100644
index 00000000..8f99a9d8
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/links.arcadia
@@ -0,0 +1,4 @@
+interaction handoff "Assessment handoff" id "9e8f0000-0000-4000-8000-000000000004" {
+ description "救援 @assess is prose"; source @assess; target @confirm;
+ // Another comment @assess is excluded.
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/project.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/project.arcadia
new file mode 100644
index 00000000..2771b2bf
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/project.arcadia
@@ -0,0 +1,7 @@
+arcadia 7;
+project navigation_trial "Navigation — 救援" id "9e8f0000-0000-4000-8000-000000000001" {
+ description "Prose @assess must not be a lexical use.";
+ include "救援/assess.arcadia";
+ include "救援/confirm.arcadia";
+ include "links.arcadia";
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/assess.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/assess.arcadia
new file mode 100644
index 00000000..2595a7db
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/assess.arcadia
@@ -0,0 +1,4 @@
+// Comment @assess must not be a lexical use.
+oa_activity assess "Assess 救援 request" id "9e8f0000-0000-4000-8000-000000000002" {
+ description "Keep @assess as prose, not a use.";
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/confirm.arcadia b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/confirm.arcadia
new file mode 100644
index 00000000..2d3ec804
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-baseline/救援/confirm.arcadia
@@ -0,0 +1 @@
+oa_activity confirm "Confirm 救援 response" id "9e8f0000-0000-4000-8000-000000000003" {}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same-result.json b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same-result.json
new file mode 100644
index 00000000..642bac1d
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same-result.json
@@ -0,0 +1,14 @@
+{
+ "command": [
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\staged-source-oracle-20260911\\staged-source-authoring-contract.exe",
+ "same",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\valid-baseline\\project.arcadia",
+ "C:\\Users\\User\\Documents\\Git\\ARCADIA-Studio\\.local\\text-navigation-native-20260911-preparation\\valid-work\\project.arcadia"
+ ],
+ "oracle_sha256": "967d9ba3c6fb3a7efb410833903ec94556ca2d48a67d5072c8bb160f40a00f9c",
+ "exit": 0,
+ "expected_success": true,
+ "expectation_met": true,
+ "seconds": 0.07799999999951979,
+ "finished_utc": "2026-09-11T17:45:34.947690+00:00"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same.stderr.log b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same.stderr.log
new file mode 100644
index 00000000..e69de29b
diff --git a/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same.stdout.log b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same.stdout.log
new file mode 100644
index 00000000..dfcbc2b3
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/native-preparation/valid-same.stdout.log
@@ -0,0 +1 @@
+PASS exact model/identity/4 reachable sources; 4 exact disk files (0 proven retained)
diff --git a/harness/experiments/text-navigation-components-20260911/readback.json b/harness/experiments/text-navigation-components-20260911/readback.json
new file mode 100644
index 00000000..0334ac0e
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/readback.json
@@ -0,0 +1,12 @@
+{
+ "finished_utc": "2026-09-11T18:11:28.403046+00:00",
+ "payload_files": 131,
+ "payload_bytes": 934554,
+ "manifest_sha256": "709910823c9b53a2e26356bede5b0295e6908532e722bb5f03b0dcd8a5765522",
+ "exact_entry_set": true,
+ "payload_bytes_and_hashes": true,
+ "all_copied_raw_bytes_equal_sources": true,
+ "source_zip_manifest_verified": true,
+ "native_baselines_match_preparation_manifest": true,
+ "git_index_verification": "not performed; root owns staging"
+}
diff --git a/harness/experiments/text-navigation-components-20260911/verify_archive.py b/harness/experiments/text-navigation-components-20260911/verify_archive.py
new file mode 100644
index 00000000..3a9397ba
--- /dev/null
+++ b/harness/experiments/text-navigation-components-20260911/verify_archive.py
@@ -0,0 +1,16 @@
+"""Read-only verification of the archived payload; no original workspace required."""
+from pathlib import Path
+import hashlib
+import json
+
+root = Path(__file__).resolve().parent
+manifest = json.loads((root / 'archive-manifest.json').read_text(encoding='utf-8'))
+records = manifest['records']
+expected = {row['path'] for row in records}
+actual = {p.relative_to(root).as_posix() for p in root.rglob('*') if p.is_file()}
+assert actual == expected | {'archive-manifest.json', 'readback.json'}, (actual - expected, expected - actual)
+for row in records:
+ raw = (root / row['path']).read_bytes()
+ assert len(raw) == row['bytes'], row['path']
+ assert hashlib.sha256(raw).hexdigest() == row['sha256'], row['path']
+print(f"PASS {len(records)} payload files / {sum(r['bytes'] for r in records)} bytes; exact entry set and hashes")
diff --git a/harness/experiments/text-navigation-desktop-20260911/.gitattributes b/harness/experiments/text-navigation-desktop-20260911/.gitattributes
new file mode 100644
index 00000000..fa1385d9
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/.gitattributes
@@ -0,0 +1 @@
+* -text
diff --git a/harness/experiments/text-navigation-desktop-20260911/README.md b/harness/experiments/text-navigation-desktop-20260911/README.md
new file mode 100644
index 00000000..de2d8365
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/README.md
@@ -0,0 +1,33 @@
+# Desktop source-navigation gates — 2026-09-11
+
+Codex-9e8r6t `/root/behavior_audit`. This directory preserves completed Windows desktop gate records from `.local/text-navigation-desktop-20260911`. The original producer ran the gates; this archival task only copied bytes, inspected receipts/source, and independently verified hashes and ZIP contents. No compiler, application, server or Linux process was run for this archive.
+
+## What passed
+
+| Record | Actual terminal result | Input scope |
+| --- | --- | --- |
+| `check1` | Exit 0 at 17:36:52 UTC; 13 unused-code warnings | Historical initial check, before `inputs1` was captured; no exact `inputs1` binding claimed. |
+| `test1` | Exit 0 at 17:51:59 UTC; 737 passed, 0 failed, 1 ignored; test execution 179.86 s | `inputs1`; predates the queued-action replay correction. Two unused-code warnings remain in this historical log. |
+| `performance1` | Exit 0 at 17:53:16 UTC; the one ignored trial passed | Exact preserved `test1.exe`, independently hash-checked during archival. |
+| `focused2` | Exit 0 at 18:01:39 UTC; 177 passed, 0 failed, 1 ignored, 562 filtered out; test execution 19.40 s | `inputs2`, including the replay correction. |
+| `clippy2` | Exit 0 at 18:03:25 UTC; all-target desktop Clippy with `-D warnings` | `inputs2`. |
+
+The raw statuses and helpers retain commands, timestamps and one-job configuration. Gates used the existing Windows gnullvm environment sourced from `.local/toolchain/env.ps1`. The helper is referenced, not copied here; this directory is evidence, not a bundled toolchain. `capture.py` is preserved as the original receipt-generating helper and depends on its original private directory location; use `verify.py` for this archive's portable, read-only integrity check.
+
+Both captured snapshots name branch `codex/release1-9e8r6t` and base HEAD `fb24def2f55699942d6fe689c44424933dac0d9e`. Navigation changes were uncommitted above that HEAD. Each ZIP contains 499 exact captured files: `inputs1` is 9,418,355 source bytes; `inputs2` is 9,429,450. Seven paths differ; `manifest.json` lists them. These are the source scopes enumerated by `capture.py` (crates, Cargo files, `.cargo`, docs, examples and harness fixtures), not a hermetic compiler/dependency capture. Both original post-gate readbacks report no changed, added or removed input files. This archive separately verifies every ZIP member against its captured manifest, including original newline/UTF-8 bytes.
+
+The second snapshot tests emitting-session and active-path envelopes for Query, Back, Close and Choose. Actual App-path regressions replay old messages after reset at the same path and after switching files in the same session, both while busy and after completion. They preserve current request/reveal state, interactions, model, raw text and histories; fresh-context actions still work. Earlier tests cover invalid/unapplied drafts without Apply, ambiguous/detached choices, exact Unicode/CRLF Back selections, worker/reset ordering and the 64-entry Back cap. Async completions are delivered deterministically by tests; this does not exercise native event timing. The full 737-test suite was not repeated after the replay correction.
+
+## Performance scope
+
+The separate trial constructs 100 module files plus an entry, 1,000 process declarations and 10,000 lexical uses in 179,156 source bytes. It measured admission 364.9 µs, snapshot capture 137.7013 ms and reference query 28.7229 ms. The result contains one definition and 199 reference rows, with 9,801 uses explicitly omitted by the row budget. It asserts the source is unchanged.
+
+This is a current-draft lexical fixture with process-to-process placeholder steps, not a semantically validated engineering model. It is neither GUI latency nor native usability acceptance. Reported process resident memory was 10,186,752 → 16,093,184 bytes, with peak 16,842,752 bytes; this includes the process/fixture and is not an allocator-exact index measurement. Performance was not rerun on `inputs2`.
+
+## Provenance and exclusions
+
+The full-test executable is intentionally omitted (109,325,824 bytes, SHA256 `F6C6312FF0C726AEC6A30A90658808DAF4EA7ACE6DF4F1977DFFC54CA09AABD9`). Its original artifact receipt remains byte-exact, and `manifest.json` records the independent size/hash readback. No other binary, GUI build/native output, `review-final`, profile or work data is included.
+
+Strict parser UUID-header spelling was already constrained to canonical decoded 8-4-4-4-12 shape. This slice aligns tolerant outline/analyze identity recognition with that existing rule; it does not introduce a new strict grammar restriction. Core, primitive-query and adapter evidence is owned separately in [the component archive](../text-navigation-components-20260911/) and [the outline integration report](../../release-text-navigation-outline-integration-20260911.md). Those component payloads are not duplicated here.
+
+`manifest.json` hashes the 20 exact copied payloads and three archive control files. `readback.json` pins that manifest and records verification of the complete archive file set and both source ZIPs. The local `.gitattributes` disables text normalization only inside this directory. Run `python verify.py` here or by absolute path to check without modifying any file. Root owns subsequent GUI/native acceptance, integration and publication; these receipts do not establish a release or the complete OA/SA goal. Live coordination is in [issue 6](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/6).
diff --git a/harness/experiments/text-navigation-desktop-20260911/capture.py b/harness/experiments/text-navigation-desktop-20260911/capture.py
new file mode 100644
index 00000000..3db5958e
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/capture.py
@@ -0,0 +1,63 @@
+"""Preserve exact current source bytes before a gate and read them back after it."""
+import datetime
+import hashlib
+import json
+import pathlib
+import re
+import subprocess
+import sys
+import zipfile
+
+root = pathlib.Path(__file__).resolve().parents[2]
+evidence = pathlib.Path(__file__).resolve().parent
+mode, label = sys.argv[1:]
+assert re.fullmatch(r"inputs[0-9]+", label)
+
+def paths():
+ result = subprocess.run(["git", "ls-files", "-z", "--cached", "--others", "--exclude-standard", "--",
+ "crates", ".cargo", "Cargo.toml", "Cargo.lock", "docs", "examples", "harness/fixtures"],
+ cwd=root, check=True, capture_output=True)
+ return sorted(set(p.decode("utf-8") for p in result.stdout.split(b"\0") if p))
+
+def data(path):
+ target = (root / path).resolve()
+ assert target.is_relative_to(root) and target.is_file(), path
+ return target.read_bytes()
+
+def sha(raw):
+ return hashlib.sha256(raw).hexdigest()
+
+def write(path, value):
+ with path.open("x", encoding="utf-8", newline="\n") as out:
+ json.dump(value, out, indent=2, ensure_ascii=False)
+ out.write("\n")
+
+if mode == "capture":
+ entries = []
+ archive = evidence / (label + ".zip")
+ with zipfile.ZipFile(archive, "x", zipfile.ZIP_DEFLATED) as out:
+ for path in paths():
+ raw = data(path)
+ out.writestr(path, raw)
+ entries.append({"path": path, "bytes": len(raw), "sha256": sha(raw)})
+ result = {"atUtc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
+ "head": subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=root).decode().strip(),
+ "branch": subprocess.check_output(["git", "branch", "--show-current"], cwd=root).decode().strip(),
+ "scope": "Exact Windows gate source inputs; no compiler or native result implied.",
+ "archiveSha256": sha(archive.read_bytes()), "files": entries}
+ write(evidence / (label + ".json"), result)
+ print(json.dumps({"files": len(entries), "bytes": sum(e["bytes"] for e in entries), "archiveSha256": result["archiveSha256"]}))
+elif mode == "verify":
+ before = json.loads((evidence / (label + ".json")).read_text(encoding="utf-8"))
+ current = paths()
+ expected = [e["path"] for e in before["files"]]
+ changed = [e["path"] for e in before["files"] if sha(data(e["path"])) != e["sha256"]]
+ result = {"atUtc": datetime.datetime.now(datetime.timezone.utc).isoformat(),
+ "changed": changed, "added": sorted(set(current) - set(expected)),
+ "removed": sorted(set(expected) - set(current))}
+ result["passed"] = not any(result[k] for k in ("changed", "added", "removed"))
+ write(evidence / (label + "-readback.json"), result)
+ print(json.dumps(result))
+ sys.exit(0 if result["passed"] else 1)
+else:
+ raise ValueError(mode)
diff --git a/harness/experiments/text-navigation-desktop-20260911/check1-status.json b/harness/experiments/text-navigation-desktop-20260911/check1-status.json
new file mode 100644
index 00000000..dbdf4c83
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/check1-status.json
@@ -0,0 +1,7 @@
+{
+ "started": "2026-09-11T17:34:19.4023546Z",
+ "finished": "2026-09-11T17:36:52.7058311Z",
+ "exitCode": 0,
+ "command": "cargo check --locked --offline -p oa-desktop --bin arcadia-studio",
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-desktop-20260911/check1.log b/harness/experiments/text-navigation-desktop-20260911/check1.log
new file mode 100644
index 00000000..bd9dce53
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/check1.log
@@ -0,0 +1,174 @@
+ Checking tokio v1.53.1
+ Checking iced_futures v0.14.0
+ Checking iced_graphics v0.14.0
+ Checking iced_debug v0.14.0
+ Checking subtle v2.6.1
+ Checking hyper v1.11.1
+ Checking rustls v0.23.43
+ Checking serde v1.0.229
+ Checking iced_wgpu v0.14.0
+ Checking iced_runtime v0.14.0
+ Checking tower v0.5.3
+ Checking hyper-util v0.1.20
+ Checking iced_program v0.14.0
+ Checking serde_json v1.0.151
+ Checking iced_renderer v0.14.0
+ Checking tokio-rustls v0.26.5
+ Checking serde_urlencoded v0.7.1
+ Checking hyper-rustls v0.27.9
+ Checking iced_winit v0.14.0
+ Checking iced_widget v0.14.2
+ Checking tower-http v0.6.11
+ Checking reqwest v0.12.28
+ Checking iced v0.14.0
+ Checking oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-core)
+ Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
+warning: function `capture` is never used
+ --> crates\oa-desktop\src\source_navigation\query.rs:453:8
+ |
+453 | pub fn capture(input: Input<'_>, limits: Limits) -> Result {
+ | ^^^^^^^
+ |
+ = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
+
+warning: methods `scope` and `file` are never used
+ --> crates\oa-desktop\src\source_navigation\query.rs:742:12
+ |
+737 | impl Snapshot {
+ | ------------- methods in this implementation
+...
+742 | pub fn scope(&self) -> &Scope {
+ | ^^^^^
+...
+746 | pub fn file(&self, index: usize) -> Option> {
+ | ^^^^
+
+warning: variant `Incomplete` is never constructed
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:43:5
+ |
+39 | pub enum SyntaxCoverage {
+ | -------------- variant in this enum
+...
+43 | Incomplete,
+ | ^^^^^^^^^^
+ |
+ = note: `SyntaxCoverage` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
+
+warning: variant `UnsupportedBindings` is never constructed
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:51:5
+ |
+47 | pub enum PartialReason {
+ | ------------- variant in this enum
+...
+51 | UnsupportedBindings,
+ | ^^^^^^^^^^^^^^^^^^^
+ |
+ = note: `PartialReason` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
+
+warning: variant `Included` is never constructed
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:62:5
+ |
+61 | pub enum Domain {
+ | ------ variant in this enum
+62 | Included,
+ | ^^^^^^^^
+ |
+ = note: `Domain` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
+
+warning: method `id` is never used
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:181:12
+ |
+180 | impl AuthoredAnchor {
+ | ------------------- method in this implementation
+181 | pub fn id(self) -> Id {
+ | ^^
+
+warning: enum `Lookup` is never used
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:194:10
+ |
+194 | pub enum Lookup {
+ | ^^^^^^
+
+warning: variants `WrongWorkspace`, `OutsideDomain`, and `AmbiguousAlias` are never constructed
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:209:5
+ |
+207 | pub enum QueryError {
+ | ---------- variants in this enum
+208 | StaleSnapshot,
+209 | WrongWorkspace,
+ | ^^^^^^^^^^^^^^
+...
+213 | OutsideDomain,
+ | ^^^^^^^^^^^^^
+214 | AmbiguousAlias { count: usize },
+ | ^^^^^^^^^^^^^^
+ |
+ = note: `QueryError` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
+
+warning: struct `QueryScope` is never constructed
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:218:12
+ |
+218 | pub struct QueryScope {
+ | ^^^^^^^^^^
+
+warning: fields `path` and `evidence` are never read
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:255:9
+ |
+254 | pub struct SourceFile<'a> {
+ | ---------- fields in this struct
+255 | pub path: &'a str,
+ | ^^^^
+...
+258 | pub evidence: Evidence,
+ | ^^^^^^^^
+ |
+ = note: `SourceFile` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
+
+warning: field `scope` is never read
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:266:5
+ |
+264 | pub struct Index<'a> {
+ | ----- field in this struct
+265 | snapshot: SnapshotKey,
+266 | scope: Scope,
+ | ^^^^^
+ |
+ = note: `Index` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
+
+warning: multiple methods are never used
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:586:12
+ |
+509 | impl<'a> Index<'a> {
+ | ------------------ methods in this implementation
+...
+586 | pub fn snapshot(&self) -> SnapshotKey {
+ | ^^^^^^^^
+...
+594 | pub fn scope(&self, domain: Domain) -> QueryScope {
+ | ^^^^^
+...
+668 | pub fn definitions_for_id(&self, id: Id, domain: Domain) -> Definitions<'_, 'a> {
+ | ^^^^^^^^^^^^^^^^^^
+...
+688 | pub fn resolve_reference(
+ | ^^^^^^^^^^^^^^^^^
+...
+724 | pub fn resolve_anchor(
+ | ^^^^^^^^^^^^^^
+...
+755 | pub fn references_to(
+ | ^^^^^^^^^^^^^
+...
+771 | pub fn position(&self, file: usize, offset: usize) -> Result {
+ | ^^^^^^^^
+
+warning: method `scope` is never used
+ --> crates\oa-desktop\src\source_navigation\symbols.rs:845:12
+ |
+844 | impl References<'_, '_> {
+ | ----------------------- method in this implementation
+845 | pub fn scope(&self) -> QueryScope {
+ | ^^^^^
+
+warning: `oa-desktop` (bin "arcadia-studio") generated 13 warnings
+ Finished `dev` profile [unoptimized] target(s) in 2m 32s
diff --git a/harness/experiments/text-navigation-desktop-20260911/clippy2-status.json b/harness/experiments/text-navigation-desktop-20260911/clippy2-status.json
new file mode 100644
index 00000000..9527ccb5
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/clippy2-status.json
@@ -0,0 +1,17 @@
+{
+ "started": "2026-09-11T18:01:39.0906021Z",
+ "finished": "2026-09-11T18:03:25.6253014Z",
+ "exitCode": 0,
+ "args": [
+ "clippy",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-desktop",
+ "--all-targets",
+ "--",
+ "-D",
+ "warnings"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-desktop-20260911/clippy2.log b/harness/experiments/text-navigation-desktop-20260911/clippy2.log
new file mode 100644
index 00000000..976c2de9
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/clippy2.log
@@ -0,0 +1,4 @@
+ Checking oa-core v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-core)
+ Checking oa-server v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-server)
+ Checking oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
+ Finished `dev` profile [unoptimized] target(s) in 1m 46s
diff --git a/harness/experiments/text-navigation-desktop-20260911/focused2-status.json b/harness/experiments/text-navigation-desktop-20260911/focused2-status.json
new file mode 100644
index 00000000..faf74c6b
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/focused2-status.json
@@ -0,0 +1,18 @@
+{
+ "started": "2026-09-11T17:59:19.3952363Z",
+ "finished": "2026-09-11T18:01:39.0593069Z",
+ "exitCode": 0,
+ "args": [
+ "test",
+ "--locked",
+ "--offline",
+ "-p",
+ "oa-desktop",
+ "--bin",
+ "arcadia-studio",
+ "source_",
+ "--",
+ "--test-threads=1"
+ ],
+ "buildJobs": 1
+}
diff --git a/harness/experiments/text-navigation-desktop-20260911/focused2.log b/harness/experiments/text-navigation-desktop-20260911/focused2.log
new file mode 100644
index 00000000..b1c4fffa
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/focused2.log
@@ -0,0 +1,186 @@
+ Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
+ Finished `test` profile [unoptimized] target(s) in 1m 59s
+ Running unittests src\main.rs (.local\target-gnullvm\debug\deps\arcadia_studio-58e30cee1fd51a8b.exe)
+
+running 178 tests
+test app::behavior_lifecycle_tests::pending_behavior_blocks_source_creation_and_source_history_replay ... ok
+test app::behavior_lifecycle_tests::source_writeback_failure_keeps_typed_behavior_form_for_repair_and_one_undo ... ok
+test app::checkpoints::tests::home_resume_retains_invalid_source_and_property_drafts ... ok
+test app::checkpoints::tests::walkthrough_request_keeps_unfinished_source_behind_the_normal_pending_guard ... ok
+test app::direct_scenario_tests::direct_scenario_source_failure_and_navigation_keep_form_until_success ... ok
+test app::hosted::tests::hosted_invalid_and_detached_source_drafts_never_start_network_writes ... ok
+test app::hosted::tests::hosted_only_successful_source_save_as_changes_the_active_destination ... ok
+test app::process_canvas::direct_process_dirty_source_blocks_canvas_creation_without_losing_text ... ok
+test app::process_canvas::tests::direct_process_app_open_source_and_back_preserve_local_reference_identity ... ok
+test app::process_canvas::tests::direct_process_app_source_failure_retains_form_then_commits_once_and_selects_occurrence ... ok
+test app::process_canvas::tests::direct_process_app_stale_context_dirty_source_and_properties_never_mutate ... ok
+test app::process_canvas::tests::direct_process_append_empty_first_second_exchange_has_exact_source_history ... ok
+test app::review_navigation_tests::rejected_semantic_source_writeback_retains_review_camera_and_original_navigation ... ok
+test app::save_as_tests::save_as_late_receipt_preserves_newer_raw_source_draft_and_destination ... ok
+test app::source_project_tests::closed_detached_file_keeps_first_save_conflict_then_can_be_reloaded_fresh ... ok
+test app::source_project_tests::inactive_invalid_draft_blocks_graphical_changes_and_diagnostic_switches_files ... ok
+test app::source_project_tests::json_export_and_rejected_save_as_preserve_source_location_history_and_bytes ... ok
+test app::source_project_tests::missing_include_open_keeps_old_model_and_can_be_repaired_and_saved ... ok
+test app::source_project_tests::recovery_after_first_save_failure_retains_target_then_retries_without_reentering_path ... ok
+test app::source_project_tests::recovery_of_previous_save_as_target_does_not_redirect_current_project ... ok
+test app::source_project_tests::recovery_open_finds_first_save_journal_without_an_entry_file ... ok
+test app::source_project_tests::recovery_target_survives_edit_lint_apply_and_rejected_recovery_until_success ... ok
+test app::source_project_tests::source3_native_open_preserves_report_comments_nesting_and_save_reopen ... ok
+test app::source_project_tests::two_file_apply_graphical_edit_exact_undo_save_as_and_reopen ... ok
+test app::source_symbols::tests::back_after_origin_edit_uses_its_current_cursor_instead_of_the_old_range ... ok
+test app::source_symbols::tests::back_keeps_the_latest_64_actual_locations_without_reusing_dropped_history ... ok
+test app::source_symbols::tests::back_restores_exact_reversed_utf8_selection_and_crlf_source ... ok
+test app::source_symbols::tests::close_during_busy_hides_panel_and_completion_does_not_reopen_it ... ok
+test app::source_symbols::tests::completion_rejects_file_edit_session_cursor_interaction_and_raw_input_changes ... ok
+test app::source_symbols::tests::detached_duplicate_requires_an_explicit_choice_even_when_included_source_is_validated ... ok
+test app::source_symbols::tests::invalid_unapplied_drafts_navigate_without_apply_or_losing_either_history ... ok
+test app::source_symbols::tests::matching_reveal_ticket_is_required_and_raw_input_invalidates_pending_reveal ... ok
+test app::source_symbols::tests::old_symbol_widget_envelopes_after_file_switch_preserve_same_session_intent ... ok
+test app::source_symbols::tests::old_symbol_widget_envelopes_after_reset_preserve_new_same_path_intent ... ok
+test app::source_symbols::tests::reset_retains_worker_slot_until_old_completion_then_admits_new_session_query ... ok
+test app::source_symbols::tests::saturated_raw_clock_never_authorizes_completion_or_reveal ... ok
+test app::source_symbols::tests::source_symbols_100_modules_1000_definitions_10000_uses ... ignored, manual 100-module latency/process-memory trial; run alone with --ignored --nocapture --test-threads=1
+test app::source_symbols::tests::unknown_job_cannot_release_current_worker_and_old_duplicate_cannot_replace_new_results ... ok
+test app::source_symbols::tests::valid_definition_and_reference_choice_keep_whole_model_and_source_unchanged ... ok
+test app::state_diagram_tests::create_view_places_only_its_machine_and_restores_exact_source_in_one_undo ... ok
+test app::state_diagram_tests::rejected_creation_keeps_source_and_selection_then_valid_owner_can_retry ... ok
+test app::state_diagram_tests::state_diagram_routes_constructor_and_source_open_to_the_existing_workspace_without_edits ... ok
+test app::transition_context_tests::rejected_source_writeback_keeps_review_and_navigation_and_invalid_draft_still_blocks_start ... ok
+test app::view_copy_tests::copying_the_current_state_view_uses_its_own_identity_and_is_one_source_undo ... ok
+test app::view_copy_tests::duplicate_rejection_preserves_an_incomplete_source_buffer ... ok
+test behavior::contextual_source_tests::contextual_source_app_checkpoint_restores_inner_raw_draft_without_model_adoption ... ok
+test behavior::contextual_source_tests::contextual_source_app_empty_actions_and_child_open_refuse_other_raw_editors ... ok
+test behavior::contextual_source_tests::contextual_source_app_modal_escape_then_child_back_then_parent_cancel_preserve_accepted_source ... ok
+test behavior::contextual_source_tests::contextual_source_back_remains_available_after_owner_deletion_and_with_dirty_editors ... ok
+test behavior::contextual_source_tests::contextual_source_back_retains_parent_and_rejects_late_child_event ... ok
+test behavior::contextual_source_tests::contextual_source_default_consumer_name_has_stable_alias_and_correlated_selection ... ok
+test behavior::contextual_source_tests::contextual_source_deleted_owner_view_retains_draft_instead_of_panicking_or_using_fallback ... ok
+test behavior::contextual_source_tests::contextual_source_empty_history_reports_no_change_and_success_requires_a_real_history_step ... ok
+test behavior::contextual_source_tests::contextual_source_existing_choice_prunes_staged_dependency ... ok
+test behavior::contextual_source_tests::contextual_source_failed_source_budget_preserves_both_drafts_and_accepted_bytes ... ok
+test behavior::contextual_source_tests::contextual_source_incomplete_recovered_kind_reports_error_without_panicking ... ok
+test behavior::contextual_source_tests::contextual_source_inserts_work_on_the_exact_guarded_link_and_refuses_exchange_identity_conflict ... ok
+test behavior::contextual_source_tests::contextual_source_lifelines_stage_exact_separate_records_and_one_history_entry ... ok
+test behavior::contextual_source_tests::contextual_source_machine_before_any_owner_creates_optional_view_atomically ... ok
+test behavior::contextual_source_tests::contextual_source_mounted_small_editor_keeps_back_and_use_outside_field_scroll ... ok
+test behavior::contextual_source_tests::contextual_source_nested_semantic_parent_is_explicit_and_revalidated ... ok
+test behavior::contextual_source_tests::contextual_source_new_callee_lifeline_preserves_caller_bindings_as_an_explicit_warning ... ok
+test behavior::contextual_source_tests::contextual_source_old_empty_action_cannot_open_a_previous_owner_or_layer ... ok
+test behavior::contextual_source_tests::contextual_source_original_document_remains_bound_after_back_or_choosing_existing ... ok
+test behavior::contextual_source_tests::contextual_source_owner_first_can_return_to_an_unnamed_machine_without_losing_source ... ok
+test behavior::contextual_source_tests::contextual_source_recovery_keeps_inner_raw_fields_and_refuses_deleted_or_changed_owner ... ok
+test behavior::contextual_source_tests::contextual_source_second_system_and_role_machine_owner_refuse_without_fallback ... ok
+test behavior::contextual_source_tests::contextual_source_work_uses_reserved_contribution_and_link_ids_and_one_undo ... ok
+test behavior::creation::app_tests::app_new_never_auto_applies_valid_or_invalid_property_and_source_drafts ... ok
+test behavior::creation::tests::all_four_viewpoints_are_explicit_and_survive_source_history_reopen ... ok
+test behavior::creation::tests::rejected_source_transaction_keeps_draft_and_every_accepted_source_byte ... ok
+test behavior::scenario::direct_tests::direct_clicked_occurrences_use_root_and_operand_slots_with_exact_source_history ... ok
+test behavior::scenario::direct_tests::direct_middle_pair_insertion_is_one_exact_source_and_model_undo ... ok
+test behavior::scenario::tests::graphical_call_reply_is_atomic_correlated_and_source_equivalent ... ok
+test behavior::source_draft::layout_tests::contextual_source_layout_initial_essentials_fit_short_composed_host_with_real_paragraphs ... ok
+test behavior::source_draft::layout_tests::contextual_source_layout_long_parent_wraps_after_visible_name_without_losing_raw_data ... ok
+test behavior::source_draft::layout_tests::contextual_source_layout_visible_kind_click_keeps_choice_explicit_and_name_input_works ... ok
+test behavior::state_edit::tests::view_initializes_only_its_machine_in_one_source_and_model_history_entry ... ok
+test behavior::topology::contextual::tests::qualified_link_gui_repeated_sources_remain_distinct_and_apply_has_exact_source_history ... ok
+test behavior::topology::direct_tests::direct_process_nested_source_reopen_and_api_command_use_one_transaction ... ok
+test behavior::topology::tests::guarded_branch_is_one_transaction_with_distinct_source_identities ... ok
+test connection_tool::tests::direct_connections_share_exact_source_history_and_model_parity ... ok
+test diagram::lifecycle_tests::middle_and_arrow_panning_preserve_source_and_history ... ok
+test diagram::lifecycle_tests::motion_after_source_click_release_must_not_create_connection ... ok
+test diagram::pointer_tests::coalesced_fe_ports_commit_once_through_app_and_source_history ... ok
+test diagram::pointer_tests::coalesced_port_drag_commits_once_through_app_and_source_history ... ok
+test diagram::pointer_tests::drag_from_already_selected_stationary_port_keeps_source_between_batches ... ok
+test document::save_as_tests::save_as_preserves_explicit_source_entry_changes_in_history ... ok
+test document::tests::nested_cross_file_reparent_and_source_edit_share_exact_history ... ok
+test document::tests::no_op_command_or_source_apply_preserves_history_revision_and_redo ... ok
+test document::tests::omitted_id_stabilization_remains_dirty_until_exact_source_is_saved ... ok
+test document::tests::recreated_equal_record_does_not_inherit_deleted_source_ownership ... ok
+test forms::history_fields::tests::history_every_descriptor_fact_choice_matches_actual_closed_types_and_source_contract ... ok
+test hosted::client::tests::hosted_publication_retains_known_resource_after_failed_followup ... ok
+test model_menu::tests::add_bend_here_uses_context_point_and_one_source_aware_history_change ... ok
+test model_menu::tests::pending_source_allows_file_navigation_but_disables_model_actions ... ok
+test recovery::source7_tests::source7_legacy_profiles_preserve_dirty_source_property_and_behavior_raw_fields ... ok
+test recovery::tests::exact_source_and_unfinished_invalid_buffers_restore_without_model_overwrite ... ok
+test semantic_review::artifacts_tests::artifact_creation_and_identified_source_deletion_follow_core_metadata_and_history_boundary_both_directions ... ok
+test semantic_review::artifacts_tests::artifact_metadata_apply_and_exact_history_reset_are_one_source_preserving_undo_redo ... ok
+test semantic_review::artifacts_tests::artifact_moved_source_absence_deletes_but_destination_only_take_retains_both_directions ... ok
+test semantic_review::attachments::tests::attachment_apply_is_one_exact_source_and_history_undo_redo ... ok
+test semantic_review::attachments::tests::functional_incident_source_requires_explicit_matching_port_review_in_one_atomic_candidate ... ok
+test semantic_review::attachments::tests::source_only_absence_and_explicit_absent_endpoint_are_different ... ok
+test semantic_review::batch_tests::batch_apply_is_one_exact_source_and_history_undo_redo_roundtrip ... ok
+test semantic_review::constructor_tests::scenario_use_group_takes_typed_binding_references_and_preserves_source_text_both_directions ... ok
+test semantic_review::keyed_rows_tests::keyed_rows_distinguish_unpaired_source_from_explicit_absence_and_require_exact_reset ... ok
+test semantic_review::keyed_rows_tests::row_review_apply_preserves_exact_source_and_semantic_history_through_undo_redo ... ok
+test semantic_review::keyed_tests::deleted_member_source_observation_removes_only_mapped_membership_both_directions ... ok
+test semantic_review::keyed_tests::exact_reset_only_is_one_history_source_transaction_and_undo_redo_both_directions ... ok
+test semantic_review::keyed_tests::stale_or_source_failed_reset_and_membership_apply_retain_exact_choices ... ok
+test semantic_review::message_kind_tests::message_group_rejects_protected_edits_stale_plan_and_source_failure_without_losing_timer ... ok
+test semantic_review::message_kind_tests::message_group_take_full_timer_and_scalar_candidate_are_one_source_history_change_both_directions ... ok
+test semantic_review::ordered_tests::ordered_review_apply_is_exact_source_history_transaction_with_reopen_and_undo_redo ... ok
+test semantic_review::ordered_tests::several_incoming_members_follow_source_order_despite_independent_identity_sorting ... ok
+test semantic_review::tests::scalar_keep_take_candidate_commit_exact_source_and_history_in_both_directions ... ok
+test semantic_review::tests::stale_plan_and_source_writeback_failure_do_not_lose_decisions_or_history ... ok
+test source_navigation::interaction::tests::captured_find_click_invalidates_without_app_message_and_preserves_focus ... ok
+test source_navigation::interaction::tests::focus_rechecks_clock_between_inspection_and_application_and_missing_target_is_inert ... ok
+test source_navigation::interaction::tests::raw_navigation_inputs_advance_but_passive_geometry_does_not_and_exhaustion_refuses ... ok
+test source_navigation::query::tests::comments_strings_and_comment_gap_are_not_navigation_hits ... ok
+test source_navigation::query::tests::detached_alias_and_identity_collisions_never_auto_select ... ok
+test source_navigation::query::tests::dirty_but_parseable_text_has_current_names_and_no_validated_fallback ... ok
+test source_navigation::query::tests::exact_multifile_unicode_crlf_definition_and_repeated_lexical_references ... ok
+test source_navigation::query::tests::exact_snapshot_readback_rejects_same_key_byte_membership_and_evidence_changes ... ok
+test source_navigation::query::tests::include_cycle_depth_and_aggregate_edges_are_explicit_partial_outcomes ... ok
+test source_navigation::query::tests::invalid_include_syntax_never_borrows_the_old_reachable_file_set ... ok
+test source_navigation::query::tests::keyword_alias_and_loaded_path_case_collisions_use_reviewed_index_rules ... ok
+test source_navigation::query::tests::large_display_name_cannot_escape_the_owned_row_string_budget ... ok
+test source_navigation::query::tests::missing_and_noncanonical_authored_ids_remain_draft_spans_without_generated_anchors ... ok
+test source_navigation::query::tests::missing_include_partial_scope_does_not_resurrect_witness_text ... ok
+test source_navigation::query::tests::omitted_rows_are_counted_before_result_allocation_and_never_confer_uniqueness ... ok
+test source_navigation::query::tests::owned_request_checks_raw_limits_immediately_and_extracts_only_on_worker ... ok
+test source_navigation::query::tests::owned_request_moves_exact_text_into_worker_snapshot_and_matches_sync_results ... ok
+test source_navigation::query::tests::owned_validation_proof_preserves_full_witness_count_and_partial_scope ... ok
+test source_navigation::query::tests::raw_aggregate_limits_refuse_before_outline_or_copied_snapshot ... ok
+test source_navigation::query::tests::sole_detached_candidate_and_unapplied_equal_bytes_require_choice ... ok
+test source_navigation::query::tests::token_declaration_and_live_reference_limits_are_aggregate_not_per_file ... ok
+test source_navigation::query::tests::validation_witness_must_match_entry_and_complete_included_file_set ... ok
+test source_navigation::symbols::tests::admitted_unicode_path_budget_keeps_original_policy_after_single_fold ... ok
+test source_navigation::symbols::tests::comment_separated_reference_keeps_crlf_unicode_and_disjoint_hit_spans ... ok
+test source_navigation::symbols::tests::declaration_order_and_duplicate_roles_refuse_instead_of_corrupting_hit_search ... ok
+test source_navigation::symbols::tests::detached_uuid_clone_disables_anchor_even_for_included_query ... ok
+test source_navigation::symbols::tests::duplicate_aliases_are_candidates_without_false_reference_attribution ... ok
+test source_navigation::symbols::tests::every_budget_admits_boundary_and_returns_no_index_one_below ... ok
+test source_navigation::symbols::tests::header_metadata_must_match_current_token_boundaries_and_authored_id ... ok
+test source_navigation::symbols::tests::incomplete_draft_is_lexical_and_error_token_does_not_leak_references ... ok
+test source_navigation::symbols::tests::legal_keyword_classified_alias_is_not_discarded ... ok
+test source_navigation::symbols::tests::missing_ids_produce_only_current_snapshot_locations_without_stabilization ... ok
+test source_navigation::symbols::tests::nested_declaration_extents_do_not_turn_body_text_into_definition_hits ... ok
+test source_navigation::symbols::tests::noncanonical_header_identity_cannot_enter_through_query_metadata ... ok
+test source_navigation::symbols::tests::path_identity_rejects_normalization_and_case_collisions ... ok
+test source_navigation::symbols::tests::query_header_admission_preserves_uppercase_and_json_escape_bytes ... ok
+test source_navigation::symbols::tests::repeated_live_uses_are_distinct_and_strings_comments_history_are_excluded ... ok
+test source_navigation::symbols::tests::reused_alias_cannot_steal_old_identity_and_old_locations_are_stale ... ok
+test source_navigation::symbols::tests::scope_provenance_and_detached_alias_collision_are_explicit ... ok
+test source_navigation::symbols::tests::stopped_outline_is_not_a_successful_empty_query ... ok
+test source_navigation::symbols::tests::truncated_or_gapped_tokens_refuse_even_when_project_scope_is_partial ... ok
+test source_navigation::symbols::tests::unicode_path_collisions_match_core_lowercase_policy ... ok
+test source_navigation::tests::search_uses_utf8_columns_wraps_and_refreshes_after_edits ... ok
+test source_tree::tests::active_scroll_offset_uses_the_visible_order_and_retains_ancestor_context ... ok
+test source_tree::tests::collapsed_folders_retain_active_dirty_and_diagnostic_markers ... ok
+test source_tree::tests::deep_paths_build_expand_and_drop_on_a_small_stack ... ok
+test source_tree::tests::detached_buffers_keep_paths_and_only_clean_buffers_are_closable ... ok
+test source_tree::tests::exact_unicode_spaces_and_file_folder_name_collisions_are_not_normalized ... ok
+test source_tree::tests::folders_are_compact_and_sorted_before_files_without_losing_duplicate_basenames ... ok
+test source_workspace::tests::draft_history_budget_covers_inactive_files_without_discarding_drafts ... ok
+test source_workspace::tests::fresh_session_cannot_reuse_a_retained_buffer_generation ... ok
+test source_workspace::tests::navigation_revision_wrap_cannot_reuse_an_old_workspace_key ... ok
+test source_workspace::tests::navigation_scope_changes_invalidate_without_changing_unchanged_positions ... ok
+test source_workspace::tests::navigation_versions_distinguish_reopened_recovered_and_changed_drafts ... ok
+test source_workspace::tests::switching_retains_invalid_drafts_and_independent_history ... ok
+test tests::malformed_source_preserves_model_and_is_recoverable ... ok
+test tests::opening_legacy_source_surfaces_report_without_modifying_original_bytes ... ok
+test tests::pending_source_prevents_property_draft_collision_and_preserves_both_if_present ... ok
+test tests::source_find_navigates_an_invalid_draft_without_applying_or_losing_it ... ok
+test tests::source_navigation_waits_for_editor_layout_and_cancels_on_edit ... ok
+test workflow::transition_tests::basic_transition_explicit_destination_appends_below_existing_bounds_and_is_one_source_edit ... ok
+
+test result: ok. 177 passed; 0 failed; 1 ignored; 0 measured; 562 filtered out; finished in 19.40s
+
diff --git a/harness/experiments/text-navigation-desktop-20260911/inputs1-readback.json b/harness/experiments/text-navigation-desktop-20260911/inputs1-readback.json
new file mode 100644
index 00000000..e6fca955
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/inputs1-readback.json
@@ -0,0 +1,7 @@
+{
+ "atUtc": "2026-09-11T17:53:13.844090+00:00",
+ "changed": [],
+ "added": [],
+ "removed": [],
+ "passed": true
+}
diff --git a/harness/experiments/text-navigation-desktop-20260911/inputs1.json b/harness/experiments/text-navigation-desktop-20260911/inputs1.json
new file mode 100644
index 00000000..96d02d91
--- /dev/null
+++ b/harness/experiments/text-navigation-desktop-20260911/inputs1.json
@@ -0,0 +1,2504 @@
+{
+ "atUtc": "2026-09-11T17:44:54.994144+00:00",
+ "head": "fb24def2f55699942d6fe689c44424933dac0d9e",
+ "branch": "codex/release1-9e8r6t",
+ "scope": "Exact Windows gate source inputs; no compiler or native result implied.",
+ "archiveSha256": "af415e14c200e11ba42c7b95a383e7c8dafc95fa4ac8588725553bff7251a3b3",
+ "files": [
+ {
+ "path": "Cargo.lock",
+ "bytes": 119453,
+ "sha256": "2b960242c33eb839ff008d01731f6d5cdb4e047ca8cd6e13151cbf9c09913510"
+ },
+ {
+ "path": "Cargo.toml",
+ "bytes": 400,
+ "sha256": "369f5b4b2b60705a94623c8c0b68f8e0b048f263280ce07ae0629ad353797d62"
+ },
+ {
+ "path": "crates/oa-cli/Cargo.toml",
+ "bytes": 279,
+ "sha256": "49c567f1af74a826683b4d96845feb3806fd9bcb7dc2767e16b5509d4e5e1cde"
+ },
+ {
+ "path": "crates/oa-cli/src/lib.rs",
+ "bytes": 15498,
+ "sha256": "97bebab63b4a611a1d36ad0c20fe452cbe1985fe3a6fbd39c954603da11410fc"
+ },
+ {
+ "path": "crates/oa-cli/src/main.rs",
+ "bytes": 2984,
+ "sha256": "de964f9b6c59c0f31c080afbd3b31d442b07b1b44f6a187a248d3409c45d35fb"
+ },
+ {
+ "path": "crates/oa-cli/tests/source_cli.rs",
+ "bytes": 16600,
+ "sha256": "a3a1b856c7a9fd3d80bc8074150a24fbb0ef0e9ddf736c7cea036725fc3dc2b8"
+ },
+ {
+ "path": "crates/oa-core/Cargo.toml",
+ "bytes": 342,
+ "sha256": "64824ec447be777841a972bf06c894ef2624a37e4725583bcfe0389deb9f00a3"
+ },
+ {
+ "path": "crates/oa-core/build.rs",
+ "bytes": 45,
+ "sha256": "ab1be6ae1fd8bf3ebaeef6348347a9d5e666bde693126f199e041659e0b094a9"
+ },
+ {
+ "path": "crates/oa-core/examples/generate_demo.rs",
+ "bytes": 795,
+ "sha256": "e26f432c95331aeca50985dbb3ad2c558affd43023b585811f0f7bfaa59f6661"
+ },
+ {
+ "path": "crates/oa-core/examples/generate_release1.rs",
+ "bytes": 1798,
+ "sha256": "1d5f11165d74893e4bc18dbf3a2f2111f4026bd89fd06ee7f0bae69e45c4d4b5"
+ },
+ {
+ "path": "crates/oa-core/examples/release_dense_views.rs",
+ "bytes": 14664,
+ "sha256": "a83158488ef9b7394f136c52b1496719be935c4e5bdfe457cab5c9894f5f9412"
+ },
+ {
+ "path": "crates/oa-core/examples/routing_workshop.rs",
+ "bytes": 17248,
+ "sha256": "571307b18b6d4fa04700c5a4d4b0c2154c419e93a21292b2028e7b6f46a2faa7"
+ },
+ {
+ "path": "crates/oa-core/src/data.rs",
+ "bytes": 42696,
+ "sha256": "7f9c7c7dd8353cb5a38597b92cde6d3e890203d392473187eb387d41ff37414a"
+ },
+ {
+ "path": "crates/oa-core/src/demo.rs",
+ "bytes": 20657,
+ "sha256": "ad62123f96c20ff023cafc1a9849b0fa06bc62edd1fb9701799e4599b458600c"
+ },
+ {
+ "path": "crates/oa-core/src/language.rs",
+ "bytes": 105312,
+ "sha256": "34cbb144a111e6f0ddadfca7d40636a8e3323fe65ea8920fd2402f0352b41f57"
+ },
+ {
+ "path": "crates/oa-core/src/language/semantic_source.rs",
+ "bytes": 7326,
+ "sha256": "584b5bb59d87a7b8d44d5dd2d124c3a1323b21492b138a17bc9526c0087c9e29"
+ },
+ {
+ "path": "crates/oa-core/src/language_nesting_tests.rs",
+ "bytes": 20639,
+ "sha256": "bd061611e8cb6f28ab203c73e544ac98bf275eb464cd314ad5fbd25f773df33b"
+ },
+ {
+ "path": "crates/oa-core/src/language_outline_tests.rs",
+ "bytes": 14877,
+ "sha256": "4c79a01e8aea243dd9ef66ce71b9fae263d0e79c319a46c51223679897e8b9df"
+ },
+ {
+ "path": "crates/oa-core/src/lib.rs",
+ "bytes": 523,
+ "sha256": "52cb6ee7a4ffc00711ecf121c1a1b14ca154863f7ca27b0d2a8e239edce8aca7"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/README.md",
+ "bytes": 1515,
+ "sha256": "fb61d3faa33d7bdfba73415415a0882c0fbd3e0b45df42f972a19aec6da927b9"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/adapter.rs",
+ "bytes": 9490,
+ "sha256": "66741b4c8354a23b8a932ecc5bf1bc3bb6e74279558757b8c35bc2157a7e51b5"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/mod.rs",
+ "bytes": 1158,
+ "sha256": "857d8bb13b6814daba6780ca770c16352943c01cb130245d055f11b3b957b3e9"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/model.rs",
+ "bytes": 52059,
+ "sha256": "8ac63eb59b2e57bdecff2477ee0634096090924d3dd1851e1c454880a889dacb"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/references.rs",
+ "bytes": 13369,
+ "sha256": "33a6819abc83139fdaebeebd1b553238a328637106cce7dd5a535f1791d43e20"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/source.rs",
+ "bytes": 21946,
+ "sha256": "74fee9b24e7c912182c998ab9d9aeec125e2009c3214959229e844c67afe2bf6"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/state_validation.rs",
+ "bytes": 15769,
+ "sha256": "e29ef071bab0a68460ddf55c46131c6f66c022d52c3c602c37c013c3649da1b0"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/transitions.rs",
+ "bytes": 704,
+ "sha256": "f74174c532ba56b57c80b719a11fb4adea0a78ccc2828fff72fe2b9eda5a10b1"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v2/validation.rs",
+ "bytes": 98736,
+ "sha256": "6d5e4a4e2805ae5045bf789a0485e14a61c63346587bace2b4a91173a688f780"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v3/README.md",
+ "bytes": 2128,
+ "sha256": "3755a63f840d7f0eadf68afa7a9d587877a9468ff982d3610539e8a0bb067a82"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v3/mod.rs",
+ "bytes": 23023,
+ "sha256": "f4edb6a0b50ecfe8670973776d703047b064420cb1f05913129c00fc656b3722"
+ },
+ {
+ "path": "crates/oa-core/src/migration_v4.rs",
+ "bytes": 4020,
+ "sha256": "50c652964f0f309e7f3f54b6ef12c40d42fcecc0490f005995c713df1179af70"
+ },
+ {
+ "path": "crates/oa-core/src/model.rs",
+ "bytes": 66352,
+ "sha256": "4585c895c64c4a183941c92cf047a9e52ff0cbe41971e16e78d229ea9dc101fb"
+ },
+ {
+ "path": "crates/oa-core/src/operations.rs",
+ "bytes": 11405,
+ "sha256": "a5ffbbf54cd440316a106d1188bfb699b57dae59f466edcb33618c333cd19f41"
+ },
+ {
+ "path": "crates/oa-core/src/persistence.rs",
+ "bytes": 31774,
+ "sha256": "1c01474f906a63e552c0d590b272c95821c00a100b3e79e58e67dbbeac341a14"
+ },
+ {
+ "path": "crates/oa-core/src/ports.rs",
+ "bytes": 45413,
+ "sha256": "5cac12eea70a54ebebbd6be77b63ce92e3195325c4bc7d27705c518686cbac80"
+ },
+ {
+ "path": "crates/oa-core/src/projection.rs",
+ "bytes": 21374,
+ "sha256": "69ed12bace2cc5024e151752a174ab14034ac2d05eb741fcf13e9a27521c059b"
+ },
+ {
+ "path": "crates/oa-core/src/projection/port_layout.rs",
+ "bytes": 13474,
+ "sha256": "a08d17d10a58bb8b206dd001b19b91d5aa3a9f743b298a4da57d503223c20079"
+ },
+ {
+ "path": "crates/oa-core/src/projection/state.rs",
+ "bytes": 5233,
+ "sha256": "73a607bac713ec7ff49a509fefb1a53c7686dca93ee241ffc835564d743afb28"
+ },
+ {
+ "path": "crates/oa-core/src/references.rs",
+ "bytes": 21957,
+ "sha256": "93553c234f32da35f1f41a7b83ad81c471c9803efa78ca222cf7de3c2f86c0bb"
+ },
+ {
+ "path": "crates/oa-core/src/routing.rs",
+ "bytes": 50309,
+ "sha256": "c5ebb350ba76f731f5c48d968bfbba4b6ef68ec046eb5893d116c4637ef15080"
+ },
+ {
+ "path": "crates/oa-core/src/semantic.rs",
+ "bytes": 540,
+ "sha256": "f9895dd5b8fb27d10d4cfbb41c3542ef2018365a589b8d5778e5674ae5a1f239"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/catalog.rs",
+ "bytes": 49407,
+ "sha256": "1ff74fcca18084d178681d7ff38ee1ccac632c27c4de0595ba1ceab8067ab3a4"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/historical.rs",
+ "bytes": 15271,
+ "sha256": "eaf3284bf234312698514cdb636bc8348e1b5d746cb16b18df777e77d312abfb"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/linked.rs",
+ "bytes": 6035,
+ "sha256": "6f335b14e809aecfc35defcaf77cb821b080d1506dbc46297912b25af98c07a2"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/mutate.rs",
+ "bytes": 22683,
+ "sha256": "dc2b4c4a6eb156fe468eaa8c85fd37a505c0cd8ea1f226cb69cd01689afd3d9d"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/review.rs",
+ "bytes": 51116,
+ "sha256": "0c157d2c327e72e89cca3b423e71ee73798105c5bb7271439c1a101642c91e3e"
+ },
+ {
+ "path": "crates/oa-core/src/semantic/types.rs",
+ "bytes": 10405,
+ "sha256": "6922fe35089f12fad43ab0ce1c6886d01a3855f90b8e2543e99802ac5e6f1bbb"
+ },
+ {
+ "path": "crates/oa-core/src/session.rs",
+ "bytes": 35237,
+ "sha256": "1eb77b329b2986fc6f5dc0171e77fd860375cdfd8c769a1367b445d6c49ebe00"
+ },
+ {
+ "path": "crates/oa-core/src/source_files.rs",
+ "bytes": 74803,
+ "sha256": "669491dbddb8e8e6556161426dffa1a49ac1c826d31020bff0956d81a4d84683"
+ },
+ {
+ "path": "crates/oa-core/src/source_project.rs",
+ "bytes": 61843,
+ "sha256": "1394370bcc5908873b830c2c11312e6e1611af41f46c4f43ca0a6bcb713bb466"
+ },
+ {
+ "path": "crates/oa-core/src/source_project/source_migration.rs",
+ "bytes": 4700,
+ "sha256": "add463ae85c420f853d324b00a221f0f874164cc3f3ed6bbb07557a9ed7a8c95"
+ },
+ {
+ "path": "crates/oa-core/src/source_project/source_structure.rs",
+ "bytes": 20225,
+ "sha256": "9f1f83315856a7025fe662c0fbb13452f31613224a03663150d6225d1ddc162f"
+ },
+ {
+ "path": "crates/oa-core/src/state_validation.rs",
+ "bytes": 15665,
+ "sha256": "e28d7f74385e2a78763716b34491bd73a00bfeccc085970ceb130627e3faee18"
+ },
+ {
+ "path": "crates/oa-core/src/topology_context.rs",
+ "bytes": 13734,
+ "sha256": "16de69b297de95d93c73808621607833b713af811dd082f0169186ae1e5c3e2d"
+ },
+ {
+ "path": "crates/oa-core/src/topology_context/analysis.rs",
+ "bytes": 20036,
+ "sha256": "dead6194f0aa60cc46045c0e5ee10dbdbb689158c38634dfbbbc45d2ba88ac65"
+ },
+ {
+ "path": "crates/oa-core/src/topology_context/analysis_tests.rs",
+ "bytes": 5258,
+ "sha256": "17839263410815e96fe027d4eba3aae0a9ab3b82b16947e2b29ebe0d09782b56"
+ },
+ {
+ "path": "crates/oa-core/src/transitions.rs",
+ "bytes": 51998,
+ "sha256": "fadd1b07b89cae4117b91abb4f1ac9ea81ff818ef20f2175e76ee8cf21fb708d"
+ },
+ {
+ "path": "crates/oa-core/src/transitions/communication.rs",
+ "bytes": 8634,
+ "sha256": "2b476630a88edd9021b9156ca12a5402e01604095f0d4002afb452671903a028"
+ },
+ {
+ "path": "crates/oa-core/src/validation.rs",
+ "bytes": 112792,
+ "sha256": "7c1b4ae5e4ebba13e99af9158ccde138dfc5e9a7afb580e76fba72d0b64ba74e"
+ },
+ {
+ "path": "crates/oa-core/src/validation/coverage.rs",
+ "bytes": 6478,
+ "sha256": "b08e4b344ecad055714168c83beff7fe29ba4edc53edbffa2eae04e5580a4d5f"
+ },
+ {
+ "path": "crates/oa-core/src/view_options.rs",
+ "bytes": 24481,
+ "sha256": "421ede0a04c593ca1df0e6e0d06fabcfcc8113cdbcfb0adc7f5c8ef4d1724208"
+ },
+ {
+ "path": "crates/oa-core/src/view_options_tests.rs",
+ "bytes": 21433,
+ "sha256": "64aac44de2df42a1012239a3dd75c473daf0c8990b3170b7bf7e78a556080208"
+ },
+ {
+ "path": "crates/oa-core/tests/communication_boundary_scenarios.rs",
+ "bytes": 8816,
+ "sha256": "e1100b8f363910a487d90c73acc7df543dc6d532158c3c63ad11cd3d40d8e12b"
+ },
+ {
+ "path": "crates/oa-core/tests/communication_means.rs",
+ "bytes": 18102,
+ "sha256": "a25f2749180bc6f4beb1445bfad3351eedf0669131a7439b633ab804439e95a4"
+ },
+ {
+ "path": "crates/oa-core/tests/coverage_diagnostics.rs",
+ "bytes": 20527,
+ "sha256": "e2e9c3996daab47f2bd23f5d8c48b4e372af33331152f5fc85b76f06d04fcdf4"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v2-coastal-rescue.arcadia",
+ "bytes": 29220,
+ "sha256": "f0df129c1914bfc01d0b047b0cd4b520d8cf0c249ea9ced085de400c0be87ff3"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v2-coastal-rescue.json",
+ "bytes": 43990,
+ "sha256": "3ed53508eb544bbd788832f7bebea2ec31b3d66191f24f45098cc9750ec4c005"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v3-data-contract.arcadia",
+ "bytes": 2450,
+ "sha256": "305f19d1b79f99f8d4cf627ba2e9ca6836bb7959cc0070227815937a699eafea"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v3-data-contract.json",
+ "bytes": 8240,
+ "sha256": "1b41299c53204e6232397d25a58d5bccdbbc16e770c75e8edb55ce36ccfe58a2"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v4-reviewed-prose.arcadia",
+ "bytes": 1222,
+ "sha256": "5fa53a26a5ad010c8e59eb56d9b2b52003569e66c0377ffb58840590168ad0c1"
+ },
+ {
+ "path": "crates/oa-core/tests/fixtures/v4-reviewed-prose.json",
+ "bytes": 1734,
+ "sha256": "2808088afcc165f4bd6da5129151143411c997041c3de61d600ab76e05d24a47"
+ },
+ {
+ "path": "crates/oa-core/tests/language.rs",
+ "bytes": 56482,
+ "sha256": "77f6cb2a69976bfb5f60b243b71c07fb0edc7505002c1e9e73fcd6ddf6d9496e"
+ },
+ {
+ "path": "crates/oa-core/tests/port_anchors.rs",
+ "bytes": 26893,
+ "sha256": "efbc0fa18733b1aaac501f9785a4c392fc49c05c45a5778d26e9b5dd951ca6fa"
+ },
+ {
+ "path": "crates/oa-core/tests/port_migration.rs",
+ "bytes": 15620,
+ "sha256": "1801c376a5435ac7efb2a8342348b2c7ffef930bba65deca4acb66b06e0483d7"
+ },
+ {
+ "path": "crates/oa-core/tests/port_source.rs",
+ "bytes": 4858,
+ "sha256": "8d2ddaca4045d330f6eda2591561497b858aad53769a823249d0bea4f5916e12"
+ },
+ {
+ "path": "crates/oa-core/tests/prepared_commands.rs",
+ "bytes": 9555,
+ "sha256": "52cab1c668064777412df0a6a3c20d74d579b33794b4b00525b9f151c06eac9b"
+ },
+ {
+ "path": "crates/oa-core/tests/process_diagnostics.rs",
+ "bytes": 1104,
+ "sha256": "1dbbc590239ef7542be4a2bdc9543aaa64d108417b03dd312a0894e364745906"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_constructor_ack.rs",
+ "bytes": 24163,
+ "sha256": "e5c44676ea0aab97f2e091ddac2f4bc4a4a90f5877f7bbf08b61229600944dd0"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_migration.rs",
+ "bytes": 6590,
+ "sha256": "ebd04120f5215b3956fdc79ee78f6e38734c7d13392f1fc28aaba957912265bd"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_semantic.rs",
+ "bytes": 77473,
+ "sha256": "49c3763eaf2131070a7c46cc23a84f917d292d41610b6404510e5607f8890c32"
+ },
+ {
+ "path": "crates/oa-core/tests/r3_source.rs",
+ "bytes": 15311,
+ "sha256": "f31f60659df3c7ef601650d1cff140589d8729f01a3f65a4a6a6c2272f15c408"
+ },
+ {
+ "path": "crates/oa-core/tests/r6_data.rs",
+ "bytes": 41437,
+ "sha256": "5f7ffb3bc73c3c85423623fe56f8680646dc97040327bdbead9d47a37e80f155"
+ },
+ {
+ "path": "crates/oa-core/tests/r6_migration.rs",
+ "bytes": 9174,
+ "sha256": "81519bc1512f5e069432e9a1cdad04c10216d492df01b4b75b02102480ebd6a1"
+ },
+ {
+ "path": "crates/oa-core/tests/r6_source.rs",
+ "bytes": 9519,
+ "sha256": "88a0aa087a7678e2898ff6057538072ca92072190712f05fff08372cc56ebed6"
+ },
+ {
+ "path": "crates/oa-core/tests/release1_teaching.rs",
+ "bytes": 10010,
+ "sha256": "be179290d871125dc97cfd2a3067c7ac89c8a538fdb0afafc5b825bcdfb2bef2"
+ },
+ {
+ "path": "crates/oa-core/tests/release_capabilities.rs",
+ "bytes": 19422,
+ "sha256": "b92cba2a562e78287ddd7dc3e94557848d17faa0c8a102601b50286b9af48a30"
+ },
+ {
+ "path": "crates/oa-core/tests/release_diagrams.rs",
+ "bytes": 7689,
+ "sha256": "cefcc0582d91266d5de6ee5daefcfe41b606ab971ab235b59034a8e1dbc24b7d"
+ },
+ {
+ "path": "crates/oa-core/tests/release_language_limits.rs",
+ "bytes": 5204,
+ "sha256": "8578da4f76bbd2d0e1bfe295ab1259e2162a3fe3d0d2ba8901915d7a40942806"
+ },
+ {
+ "path": "crates/oa-core/tests/release_ports.rs",
+ "bytes": 32740,
+ "sha256": "506e62beab0c7ec0cece83e9e0c1a810fa1f99285c9e1cab0c77f06de55a40e1"
+ },
+ {
+ "path": "crates/oa-core/tests/release_routing.rs",
+ "bytes": 23165,
+ "sha256": "ae168faaba23769b7ca1fd65f217b9db671726e67276eb5a04e5e72b1447a462"
+ },
+ {
+ "path": "crates/oa-core/tests/release_scenarios.rs",
+ "bytes": 41067,
+ "sha256": "0f5d876badeec653fc7b12610a77d867e9a53582e605253deff8d379da21572f"
+ },
+ {
+ "path": "crates/oa-core/tests/release_states.rs",
+ "bytes": 26873,
+ "sha256": "b1a26acee95eaba7f9920f9da738a1e33606552eb078e6d298895daff7e3d811"
+ },
+ {
+ "path": "crates/oa-core/tests/release_topology.rs",
+ "bytes": 17643,
+ "sha256": "c51dbe5aa5a038f9ed830daa02aa67714472179eb7fd6da5ce36dc6931c8b3a9"
+ },
+ {
+ "path": "crates/oa-core/tests/review_regressions.rs",
+ "bytes": 21823,
+ "sha256": "f154e5c88d35e31512e2b07733a1327ea3e2fcfd1ef8eb23066a4e9573f387c1"
+ },
+ {
+ "path": "crates/oa-core/tests/route_source.rs",
+ "bytes": 7270,
+ "sha256": "1a73fdbac0d0ae0e84d8d67b6ad398e11223958f5dca007f73757c91827decfb"
+ },
+ {
+ "path": "crates/oa-core/tests/routing_geometry.rs",
+ "bytes": 31890,
+ "sha256": "2d8eea4a2bba535ed0ea6daabccdaf87b375828c404baa10518c7bd7fd6253d3"
+ },
+ {
+ "path": "crates/oa-core/tests/routing_labels.rs",
+ "bytes": 5530,
+ "sha256": "dff884d799c3c5c9257210248c55767e294e3cb317c605cbd0ddc9600c216eb6"
+ },
+ {
+ "path": "crates/oa-core/tests/semantic_artifact_absence_oracle.rs",
+ "bytes": 4640,
+ "sha256": "d93eaf9667f4221232168bef0fe9f069e5be1b1f23410bda5dd67010b96e5c8a"
+ },
+ {
+ "path": "crates/oa-core/tests/semantic_attachments_gui_oracle.rs",
+ "bytes": 14957,
+ "sha256": "7f526d363ba05e7ac89faf0fead0796e419c34ee204edf44a1b46c78308b0970"
+ },
+ {
+ "path": "crates/oa-core/tests/source6_view_contract.rs",
+ "bytes": 14307,
+ "sha256": "fa566de53946d40bbbd9033edfeed177180c4dc44afa2f2f4667ffdc622eb3bb"
+ },
+ {
+ "path": "crates/oa-core/tests/source7_contract.rs",
+ "bytes": 43262,
+ "sha256": "cad4f1a135ddbd947bdf3c71d1b44f1e438725095ea8c4d893464bc721c2363f"
+ },
+ {
+ "path": "crates/oa-core/tests/source_files.rs",
+ "bytes": 13168,
+ "sha256": "f0429a0b89a443458ea9af7083c1e4e7411417a7e6b47e618bfbb9266dc164f9"
+ },
+ {
+ "path": "crates/oa-core/tests/source_identity_updates.rs",
+ "bytes": 5139,
+ "sha256": "f52aa38b4d317e532c641ad7463472e4b40e802e60052cf0017556ffc63da06c"
+ },
+ {
+ "path": "crates/oa-core/tests/source_nested_updates.rs",
+ "bytes": 16892,
+ "sha256": "9ac3390936c0d250651ea94a1b8faf0adfb680f061ececc56f2a52909b5ba855"
+ },
+ {
+ "path": "crates/oa-core/tests/source_project.rs",
+ "bytes": 45910,
+ "sha256": "cef58adb16aa4d2527ddc0628aec3e26f0b42b4be70014a7e360fafdac13a942"
+ },
+ {
+ "path": "crates/oa-core/tests/state_diagrams.rs",
+ "bytes": 23973,
+ "sha256": "6b02b387a3ae559cb3ad72de24292a5abcb76aaac0adbab5d2aa5211b22cd2e5"
+ },
+ {
+ "path": "crates/oa-core/tests/workflows.rs",
+ "bytes": 33426,
+ "sha256": "bd843dfddbd5724ec08f657571f887496203056fe2fa5864ef50a168a613b89b"
+ },
+ {
+ "path": "crates/oa-desktop/Cargo.toml",
+ "bytes": 842,
+ "sha256": "f3547b2b924d5db6706c0a8e069014b440ff65315d82fc8aced0360af4b3cdc9"
+ },
+ {
+ "path": "crates/oa-desktop/build.rs",
+ "bytes": 45,
+ "sha256": "ab1be6ae1fd8bf3ebaeef6348347a9d5e666bde693126f199e041659e0b094a9"
+ },
+ {
+ "path": "crates/oa-desktop/src/app.rs",
+ "bytes": 212423,
+ "sha256": "516ffc8483461ba965e15373bf78bf9cd5e32577c8b837a58e4090b7888b0731"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/architecture_authoring.rs",
+ "bytes": 27835,
+ "sha256": "37c7db33e99dd4adcc5a51f09ee4878af94fd32413359890a541690080885f65"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/architecture_authoring/palette.rs",
+ "bytes": 8887,
+ "sha256": "8f9937cb30b48b83759fd084a8a5990a2daa3920280f1210abe048560241985a"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/architecture_authoring/tests.rs",
+ "bytes": 14732,
+ "sha256": "ed864839940a1910c97537b6ee151b57ab115ece6f60bae602552b84f9af4a5d"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/architecture_authoring_tests.rs",
+ "bytes": 18599,
+ "sha256": "77eb1a0038d038b989f7ac568760e16fe23c4b8d73cd9fbe32b70817ea23cfec"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/behavior_lifecycle_tests.rs",
+ "bytes": 13669,
+ "sha256": "1af2e290aa259a6af6b0585950cda93fe3a562c7b8be1e938fe1d80747b11808"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/checkpoints.rs",
+ "bytes": 29576,
+ "sha256": "55e5babdcabef767af3bc8be9cc3153b3f5cc0695701b890820ec0225643852e"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/diagram_selection_tests.rs",
+ "bytes": 5582,
+ "sha256": "4fa507adbc23db3a6eb202d809c695ec0ebd6181356930c4fa7528a9a0c7be57"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/hosted.rs",
+ "bytes": 25905,
+ "sha256": "14e2fcd79558ff56d000571f1f576e5601818d702783634aa4017000d382c62a"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/hosted/tests.rs",
+ "bytes": 30344,
+ "sha256": "fa118f64d04b400ccc01e4e67adaf3541975288882afa265a9e7f092cdbda232"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/process_canvas.rs",
+ "bytes": 28640,
+ "sha256": "11e991afbce187ae633e01214b2509a13623c940f1f5e6416aaee204158bc5a4"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/review_navigation_tests.rs",
+ "bytes": 7049,
+ "sha256": "5ebba36092cc89fcba394578e69c9507ad5a369e9a26526761716b5d4ee3ad75"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/save_as_tests.rs",
+ "bytes": 4113,
+ "sha256": "f8b915793f1a36fd152569c4781a4bdc17f4af404a8ceb373330053413087a1c"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/source_symbols.rs",
+ "bytes": 16090,
+ "sha256": "c273ee956e37a5bfc04048e07aeb9ad3d700d4feb06298a100f441bfa24a133d"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/source_symbols/tests.rs",
+ "bytes": 26704,
+ "sha256": "be638c8047914969fa154a4659d06e45a9acf09c8e6ccf907597226660c002e5"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/state_diagram.rs",
+ "bytes": 9979,
+ "sha256": "f7fd50cc9a86adb794713958348af63afc7c698f74965e13188b7b8a9e735c55"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/state_diagram_tests.rs",
+ "bytes": 39957,
+ "sha256": "612e414b1a2cab12cbfd8fce23745559ff27577700694d53af67ad1f2883ee2c"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/transition_context_tests.rs",
+ "bytes": 11508,
+ "sha256": "47002fb025f3a2f8c53871f9509bfe8611a61db6fb0e1b3125eb6e875425cb1d"
+ },
+ {
+ "path": "crates/oa-desktop/src/app/view_copy_tests.rs",
+ "bytes": 7846,
+ "sha256": "296821c51dcde568e7d717039aa26bfc1b56a9074aa16051007bb351b33a4d44"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior.rs",
+ "bytes": 131449,
+ "sha256": "c9e0aea6a24cc982d0d82ee2b9b66f250cdfac037b36bf6b1b9d7dd38246cd3c"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/contextual_source_tests.rs",
+ "bytes": 49386,
+ "sha256": "5f05318a6533503b9d6febf1ec4dd7256f192394bc1b952a7904c8175bd8ddae"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/creation.rs",
+ "bytes": 30053,
+ "sha256": "0fb19be3a5688c4bd3ca3643c47a97da7e7f85add8a860cd046a0d9cb3ce9dfc"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/creation/app_tests.rs",
+ "bytes": 13181,
+ "sha256": "e9c5bf2b07357b7a2f679ae0d8f6904c1c1461aaeb94fa0db478b54d4641ae3e"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/creation/tests.rs",
+ "bytes": 22793,
+ "sha256": "a0d1daf9fb3920f7e6181a9802d84fbe056cbf0043b9e41dba5fdda687d9818a"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/scenario.rs",
+ "bytes": 149092,
+ "sha256": "d12a4b1caa61c08f69c4d2a64bd15568c4b7158dea91aac5a52a58a35adb863c"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/scenario/workspace.rs",
+ "bytes": 32785,
+ "sha256": "69c7ad32e4bdc39f42d2e6c7764cfe1fa7867cd7dbcf9b904dec805e6c912666"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/scenario/workspace/tests.rs",
+ "bytes": 35390,
+ "sha256": "abfc7caefb0de5bd479eb4af1b159ca77b68e6a157f477939628c3d5c90b44b6"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/scenario_operation_tests.rs",
+ "bytes": 10087,
+ "sha256": "89fb71c690e07eac314f7a5e05862d5c1e2f9c21039673bf22c4ac894d21c977"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/source_draft.rs",
+ "bytes": 39485,
+ "sha256": "85d543cc288e48b4c13001e4e386b5b54d9b1e349a5ac31e4e307572f2d8fb3c"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/state_canvas.rs",
+ "bytes": 93561,
+ "sha256": "2e2ad57b86f4cc1cc0c4c4c230571ef19bcc2f052001a8bdcc44925e5103077a"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/state_edit.rs",
+ "bytes": 35736,
+ "sha256": "cb2289e727163626152fc2f11195c6ee824851cb9442c57496da97fe7aebfb7c"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/state_menu.rs",
+ "bytes": 7447,
+ "sha256": "cc87c5caa177e114e4d9a7f7367e37ec2b0d1e1c16b0b99709b05ea0e59cf894"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/state_view.rs",
+ "bytes": 6988,
+ "sha256": "1eb892a9139ecb0671784c879ee5114f27e598e5b3589f5cdad0abffd5909f24"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/states.rs",
+ "bytes": 26986,
+ "sha256": "8986ed9492960e20f1254e9dfe3ff91d79ce9fef66f3bda738f9933278043954"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/topology.rs",
+ "bytes": 83845,
+ "sha256": "d06b2f0fc39b4814e94200297e148f1d4d97e04cb167766030ac9df18689e3b0"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/topology/contextual.rs",
+ "bytes": 29107,
+ "sha256": "edc84fc69d8d4a8f4ce81a4452c4e2e35cfaf98addeed10235f392506c74e8b0"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/topology/contextual_routing.rs",
+ "bytes": 16731,
+ "sha256": "d914b0e82aa8d253fe64be2ef74443d1fd2e6cec26d1cf30620a57646e1d426c"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/topology/direct.rs",
+ "bytes": 32036,
+ "sha256": "413aa2009ee4cba0941836b43e41f16a04c8fc8568b6e92db5bb08516e95c287"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/topology/direct_tests.rs",
+ "bytes": 54304,
+ "sha256": "ac8c8023117e31723084adb06e3ca7fd4ea3f95ad229622ccc759fa19b50c27e"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/topology/layout_tests.rs",
+ "bytes": 14815,
+ "sha256": "4529ac8b05de769a2ada112473146fbfa27b0467d97d3541b86c4446b6050b83"
+ },
+ {
+ "path": "crates/oa-desktop/src/behavior/topology/view.rs",
+ "bytes": 29176,
+ "sha256": "a7d806cd621bdf857c4e4861685cd3eb556b320de3985b9cc3d9d131dad370dc"
+ },
+ {
+ "path": "crates/oa-desktop/src/connection_edit.rs",
+ "bytes": 5301,
+ "sha256": "020581e077f82b43c936b623cc51733b02b88428df4e5f25df74b9705819fa37"
+ },
+ {
+ "path": "crates/oa-desktop/src/connection_tool.rs",
+ "bytes": 27023,
+ "sha256": "649da93677d52dae495b0e19bc32f3122ab05a6d84de8402578ff5ce3f6ee1d3"
+ },
+ {
+ "path": "crates/oa-desktop/src/context_menu.rs",
+ "bytes": 52955,
+ "sha256": "629354b34dc232763700251f2695b4d4e53d6251c0b1432ecc0dfe424f7dfeac"
+ },
+ {
+ "path": "crates/oa-desktop/src/data_values.rs",
+ "bytes": 23877,
+ "sha256": "e1deb1cad551f0a9fdef9653aad257cfb36e6d9c0f93b6245061ec3fab8fd365"
+ },
+ {
+ "path": "crates/oa-desktop/src/data_view.rs",
+ "bytes": 38871,
+ "sha256": "158b852be5d3db450aa5cda8b68a35bd96bddce1d8d053a7a23582095c06362d"
+ },
+ {
+ "path": "crates/oa-desktop/src/diagram.rs",
+ "bytes": 105256,
+ "sha256": "4580b170e264d14e5cfa874709f6d2e076a05155bfbe1809336a9d05f75e98ef"
+ },
+ {
+ "path": "crates/oa-desktop/src/diagram/lifecycle_tests.rs",
+ "bytes": 9316,
+ "sha256": "d0e72cc0574657d5a31c3ce307b58401d0ed77eedccd1aa02377f55ab0bc03f0"
+ },
+ {
+ "path": "crates/oa-desktop/src/diagram/pointer_tests.rs",
+ "bytes": 11833,
+ "sha256": "10ca5049034159205ac8f22805354f308b0ba0596f331568c26cd1393bc8cedc"
+ },
+ {
+ "path": "crates/oa-desktop/src/diagram/port_hit_tests.rs",
+ "bytes": 5624,
+ "sha256": "d2d0647629bf725b4dddfc211f5a27d495ed093a79af87cdae6c196f09a97b93"
+ },
+ {
+ "path": "crates/oa-desktop/src/diagram/port_labels.rs",
+ "bytes": 13408,
+ "sha256": "28782983b82e4fca5dd4410ac8bd562359943b7010c7b24ecc42f0c7ad7fb63e"
+ },
+ {
+ "path": "crates/oa-desktop/src/diagram/view_options_tests.rs",
+ "bytes": 2815,
+ "sha256": "cf0c98a7715558baf17b59f67a80556f98ad30262b4434635e617c6601775433"
+ },
+ {
+ "path": "crates/oa-desktop/src/document.rs",
+ "bytes": 31080,
+ "sha256": "2ecc2cd496d4a83fd7a32b9dc68e49f0f1cb31a06263dfd6939b4764da5b73e2"
+ },
+ {
+ "path": "crates/oa-desktop/src/document_save_as_tests.rs",
+ "bytes": 5781,
+ "sha256": "83dc243a25f4e97674ff0d86c794a490cc923e98f1f9fb05bb038f622f11e20b"
+ },
+ {
+ "path": "crates/oa-desktop/src/explorer.rs",
+ "bytes": 14764,
+ "sha256": "fa01c59219564ba9ae5553abc2b6f94e9849f9148d5812ef99b40318fe5dcf9a"
+ },
+ {
+ "path": "crates/oa-desktop/src/explorer_view.rs",
+ "bytes": 13773,
+ "sha256": "a720d7484bcc3a24729332a00e005d1cc6f5f945a9cc3965134e0024dffb581f"
+ },
+ {
+ "path": "crates/oa-desktop/src/forms.rs",
+ "bytes": 106002,
+ "sha256": "e5c0805b93daf0e8244d4be32e5a0dd3a1856c281a9d9fb5f98497dcc43c4da2"
+ },
+ {
+ "path": "crates/oa-desktop/src/forms/context_fields.rs",
+ "bytes": 10831,
+ "sha256": "eaacd474f6500dd344d5a8b63ccb1d1dd86fe2aea78ab708afa8a80d62ce69bf"
+ },
+ {
+ "path": "crates/oa-desktop/src/forms/data_fields.rs",
+ "bytes": 49285,
+ "sha256": "f56397886d74086d69783f18fab961e13b0af05a8bb0d10d0dfa187cdad05f5c"
+ },
+ {
+ "path": "crates/oa-desktop/src/forms/history_fields.rs",
+ "bytes": 27414,
+ "sha256": "e7100474cde18bdfe95562bcb3c055f29b4b15aa64788a578194464aebcd046e"
+ },
+ {
+ "path": "crates/oa-desktop/src/forms/history_fields/tests.rs",
+ "bytes": 33950,
+ "sha256": "16d3c308138c063f45986d53c621ad83c7dc2ce853edd31b7204e2939a382df9"
+ },
+ {
+ "path": "crates/oa-desktop/src/forms/port_fields.rs",
+ "bytes": 30554,
+ "sha256": "3fe684d651d5be6203fd9db3979c772913e1af5ac1514e67be386ffd88dc8127"
+ },
+ {
+ "path": "crates/oa-desktop/src/forms/view_fields.rs",
+ "bytes": 24987,
+ "sha256": "5184d438f0dfd4e29f284ff048e19da73e71be1f24a6e9f123c7e922aecb85fa"
+ },
+ {
+ "path": "crates/oa-desktop/src/guide.rs",
+ "bytes": 115874,
+ "sha256": "64c782afebecad5489d3bf06b41f3273904222c18e3244990f2749c991c92b55"
+ },
+ {
+ "path": "crates/oa-desktop/src/highlight.rs",
+ "bytes": 2751,
+ "sha256": "da7c254fff7052e5fd9ec7469f473b91a819406028d7c304f6e22c4169561600"
+ },
+ {
+ "path": "crates/oa-desktop/src/home.rs",
+ "bytes": 17587,
+ "sha256": "f03c1071334894f586a5a7c86026602a694db3c2faf902822e1dbe5156a859c2"
+ },
+ {
+ "path": "crates/oa-desktop/src/home/tests.rs",
+ "bytes": 8167,
+ "sha256": "3b03ccfc97dfb75954e1e0ed568bb87a632295c07b96cedc05ed381f87b378f3"
+ },
+ {
+ "path": "crates/oa-desktop/src/hosted/client.rs",
+ "bytes": 21912,
+ "sha256": "54b24b4b50f8d87d7c92946ea5a2cff4c98760e70555b3ce3d90be27115e95d4"
+ },
+ {
+ "path": "crates/oa-desktop/src/hosted/client_tests.rs",
+ "bytes": 23009,
+ "sha256": "3ef70907ca75e098d631c41b90badd4cff1e1eb1b3c78e15a9f811c13ba8ffbe"
+ },
+ {
+ "path": "crates/oa-desktop/src/hosted/mod.rs",
+ "bytes": 3744,
+ "sha256": "1b75e11c80fcb38fd20bb3487e3f88729fdac73e471c5ae64da28c249f77c63b"
+ },
+ {
+ "path": "crates/oa-desktop/src/hosted/view.rs",
+ "bytes": 9281,
+ "sha256": "fc0c7b7ce3a5f05c49713df6661067751f81e810af11c614ca265ea9490e6463"
+ },
+ {
+ "path": "crates/oa-desktop/src/main.rs",
+ "bytes": 1511,
+ "sha256": "cb0a919fd0df0924591283da5b55d64d525d8461640441509350be840235e51a"
+ },
+ {
+ "path": "crates/oa-desktop/src/model_menu.rs",
+ "bytes": 34680,
+ "sha256": "12994a5433a91f8295a59d4bf40f35f1edb8d8dbb497a11eae8760299627f1d4"
+ },
+ {
+ "path": "crates/oa-desktop/src/navigation.rs",
+ "bytes": 8839,
+ "sha256": "71355a12e1b4791106426d83fe5ebf0b29bce8e1dc594f0655ab85695469669e"
+ },
+ {
+ "path": "crates/oa-desktop/src/port_edit.rs",
+ "bytes": 7809,
+ "sha256": "70b99f577c4fdeda8780c8dc1ee7b1639fd77aaca06658f06d725139fb3efb6d"
+ },
+ {
+ "path": "crates/oa-desktop/src/port_preview.rs",
+ "bytes": 35855,
+ "sha256": "069487c4ece737d52e8ae4a84dcfb132236aca6799e841c91c8e7244320cf871"
+ },
+ {
+ "path": "crates/oa-desktop/src/port_workflow.rs",
+ "bytes": 40582,
+ "sha256": "ccc15f97c3272145a67bb5857b4faeaf7437561bd61912fa27ab26e8824146a0"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery.rs",
+ "bytes": 21041,
+ "sha256": "57e22e5fbb0e994e64ae03e69450271fe9f04a951db1670c2a018abe99db4489"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/fixtures/README.md",
+ "bytes": 1587,
+ "sha256": "a90002df1e087d2dac7be26926864243568a0de7c4103e4927435b34178e8016"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/fixtures/profiles.json",
+ "bytes": 473,
+ "sha256": "2c12ae96b50e6da5d60ce2b7fa00204be71dfbe2fd51311fc337a0de6a4332ff"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/fixtures/source5-bundle.json",
+ "bytes": 80974,
+ "sha256": "12715995e6f7f35414aea53b3ad720b9b31873ffdeaf4444231db1a5ac3c463e"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/fixtures/source5-model.json",
+ "bytes": 86191,
+ "sha256": "5535a706df9ec21715e8b6a5aa6dbd79636ae461450aee61305559016167612b"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/fixtures/source6-bundle.json",
+ "bytes": 85286,
+ "sha256": "ea88890a33f47d67dfb06d6dbbcc179058cf28dad76ae7d615c00aaf30dbbbfe"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/fixtures/source6-model.json",
+ "bytes": 90629,
+ "sha256": "5bb8db0b6394276666323483f977ac3586f88554c81a148c6d508fca0ef4576f"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/source5.rs",
+ "bytes": 6956,
+ "sha256": "0f0a763f0c98c2c908d4c2b95d8a4c042b6a255ee1cc16260e5404669691f642"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/source5_tests.rs",
+ "bytes": 12146,
+ "sha256": "cfd17a742f1f27b523fce7cd9309f0a07a30413395f6c58659fb91d02834a7a3"
+ },
+ {
+ "path": "crates/oa-desktop/src/recovery/source7_tests.rs",
+ "bytes": 18213,
+ "sha256": "7ee61de66c75bf949489ba51ddc9677e2b4efc68801cf27909c9b894bfdc9288"
+ },
+ {
+ "path": "crates/oa-desktop/src/route_edit.rs",
+ "bytes": 2941,
+ "sha256": "457ae59c2761a43a8620b4d336a7cf0f54b965997110ecaf968756a88b6b0374"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review.rs",
+ "bytes": 3240,
+ "sha256": "916192003637b60c12f6b3b3663a5b9c770ae4d10d51a415f6b3e4035e30fe13"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/artifacts.rs",
+ "bytes": 24201,
+ "sha256": "a79236a8150597ff808597e59cf4f1e9e61c74c4901d9c8cf42d3427bb52a9a8"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/artifacts_recovery_falsifier_tests.rs",
+ "bytes": 11034,
+ "sha256": "3f255d04e4bffa5a467b75e3cbf6f7149aa3269a02f2a89fbaa1db0e5df9ae62"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/artifacts_tests.rs",
+ "bytes": 20798,
+ "sha256": "04d0dd2ac453705185663f705282aa4a3a31433466a5be305764248da1a03c32"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/artifacts_view.rs",
+ "bytes": 6594,
+ "sha256": "e8ce3d844d6230c20fe2fda4d0fb256af8043f9b586160a8d110665c45a79561"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/attachments.rs",
+ "bytes": 18160,
+ "sha256": "de0ec0c740895b160ff5b847332c087c3f1a48931583464069470cc61315ed59"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/attachments_tests.rs",
+ "bytes": 29389,
+ "sha256": "8b26be0324e0f33b1e7cf7af340350df8ed98963002d6da8589ee0e6f6900bea"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/attachments_view.rs",
+ "bytes": 3111,
+ "sha256": "cd8c4418b43d207fcc55a82a2364cd84053643e5c09c96de008c94b8372653fa"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/batch.rs",
+ "bytes": 31771,
+ "sha256": "44830e1a9fa572493d423545336f76e79fa24ef29900e4f7927cbc83f9071d17"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/batch_tests.rs",
+ "bytes": 27546,
+ "sha256": "2a8131b81322e6a49b1348c6efec4526a3df53c8eefc0a2b25c746e2b7c6ead8"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/compose.rs",
+ "bytes": 27155,
+ "sha256": "c6adcc9882dde65e9f04f6eda7f0c855c5cbb285cf421123029169112793ce9b"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/compose_diagnostic_tests.rs",
+ "bytes": 9809,
+ "sha256": "440daf8d43278ef981c620d1818d0daaed8fb83193b8e8ff67b2e42fbf882938"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/constructor_tests.rs",
+ "bytes": 54132,
+ "sha256": "85149521ad7df62918f6f2fab6da2b3346c342ab4bce7100de3f6206c1e81096"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/constructors.rs",
+ "bytes": 12258,
+ "sha256": "0982163439b06a42d9af1643530364109282e63f05b323a35a76d8241f3caa32"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/history.rs",
+ "bytes": 3211,
+ "sha256": "df038c2464eab8093a983f3104f0d04ca8f602010a50e1d3eeb8e53f2309e201"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/keyed.rs",
+ "bytes": 14525,
+ "sha256": "f8db8578ecff84526bda96c233b50e56f1cf1c5b8ab04cbbaa04f9b5e78300f0"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/keyed_rows.rs",
+ "bytes": 13567,
+ "sha256": "af9b137fe56ff3a71378e9998bfbb7b68f035bd542aa6fa90b83e04796cf4b38"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/keyed_rows_tests.rs",
+ "bytes": 30804,
+ "sha256": "1a3b09e7ed100411cdf8043f9a78defc664a873ee0201117709185c201a5c57f"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/keyed_tests.rs",
+ "bytes": 31955,
+ "sha256": "112a2d50130407a4a1069827e4e5d5a8681e672dd3e54b559c6d3dde4302a298"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/message_kind.rs",
+ "bytes": 789,
+ "sha256": "4ed19952ea511d1b3226564a138e3322f994371d6b49794c14bc3689d1393e7f"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/message_kind_tests.rs",
+ "bytes": 23424,
+ "sha256": "5fcf9930e949aebeb12eab0327264f32a2388d62dbf098bd2365711e22623c52"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/ordered.rs",
+ "bytes": 9959,
+ "sha256": "8b809e8b0958e553896829b655684084bf2f0ca7df9d40c6acbdad8749cd6342"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/ordered_tests.rs",
+ "bytes": 22383,
+ "sha256": "5c8ea3a69e3da3b94d13f50f12aa7dc53246ccba25ffcace9552a0eb76b0bf36"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/recovery.rs",
+ "bytes": 28769,
+ "sha256": "88e69044d90c481593bab7cc125f0401c05202fccdde07698cec9483947fe8be"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/row.rs",
+ "bytes": 36972,
+ "sha256": "ecde54540071fde82d8d580d0da2fccd16d6b8e0144bd5e129afe91455f73bb1"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/scalar.rs",
+ "bytes": 15153,
+ "sha256": "9ad202174934dd9a63fbf9cba43135aead571db60846c8b7b1d3019a11df26c2"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/source7_tests.rs",
+ "bytes": 11665,
+ "sha256": "5e8221d77b068fa30c1571988448cb8829711735881d810f3d333cbc95d34a9f"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/tests.rs",
+ "bytes": 32480,
+ "sha256": "8839e241cf81d1e66abe072728eee1870b42341fbbefae9f39bda1b1a020f4ba"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/view.rs",
+ "bytes": 57336,
+ "sha256": "c391814442cd5e95040c6dab9d511a254181de56c68d5c266d879870001561ad"
+ },
+ {
+ "path": "crates/oa-desktop/src/semantic_review/view_reserved_artifact_tests.rs",
+ "bytes": 4737,
+ "sha256": "6c766b29c8664d15cb113d2cc71bba9b60ae4ccf7e44c9c5d3c41e1dc4b7ed90"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_navigation.rs",
+ "bytes": 4030,
+ "sha256": "3f7007bc3b3ef6266e92ea4121cfbdb32df2e97713797b403ce6649070a90517"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_navigation/controller.rs",
+ "bytes": 3106,
+ "sha256": "ba64ed2970c3919fcad17c5e75cf5f780d7610245e399ea25cbe653f7935711f"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_navigation/interaction.rs",
+ "bytes": 8808,
+ "sha256": "c6efa6105b8ba46655e27a89b9fa59e6fc01a4c6f331f886e035af1e70cbf8a9"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_navigation/interaction/tests.rs",
+ "bytes": 6616,
+ "sha256": "6cf5c5944b9c46c275c9039f7166b627fa9176187b39348152f67040c92c95e1"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_navigation/query.rs",
+ "bytes": 35200,
+ "sha256": "aa8e8e9d446b57c30520c4b3ca1ae9fe524cecb2f4f4f4b229d930ea0377ecca"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_navigation/query_tests.rs",
+ "bytes": 29720,
+ "sha256": "a4ecc95a49d0a9325cbf1548cf95a8d367f758f8eb1f44346a2b71ef046f6668"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_navigation/symbols.rs",
+ "bytes": 69323,
+ "sha256": "ba7c4da1d22af8a745a43e20ca898bcda9785d800b9a3b13c5d30871e06241d1"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_tree.rs",
+ "bytes": 21816,
+ "sha256": "4d55e0110a1eb128a5d26c28f3aae5853d79b7245812b7c82bd275f38440a5aa"
+ },
+ {
+ "path": "crates/oa-desktop/src/source_workspace.rs",
+ "bytes": 24090,
+ "sha256": "388c71f63829c0c76526a1700ebf5f240c86c467a65aae4fca4cd6c6a88c468b"
+ },
+ {
+ "path": "crates/oa-desktop/src/style.rs",
+ "bytes": 3015,
+ "sha256": "05d61bec16141612ccafa0988e8c4827f41754509efdb3b961eab69127ded896"
+ },
+ {
+ "path": "crates/oa-desktop/src/tests.rs",
+ "bytes": 58663,
+ "sha256": "94e340b8ef7f9a3bb71fd2a2098dbb9c3d2bb3adba25b5949430b714e36fdfe1"
+ },
+ {
+ "path": "crates/oa-desktop/src/view_copy.rs",
+ "bytes": 9043,
+ "sha256": "5630f31c942e1226aafd67eabdace6a5c5494875bdc6f7f077019e8110ba41a9"
+ },
+ {
+ "path": "crates/oa-desktop/src/views.rs",
+ "bytes": 50906,
+ "sha256": "0e717aa46e28f946a6486346ffbc0cdd237840f0b058cdc984377feb28be317a"
+ },
+ {
+ "path": "crates/oa-desktop/src/views/source_symbols.rs",
+ "bytes": 8702,
+ "sha256": "2e8248773db7f8139631154479d0893521d961f5b67db2003be4549f5d30be8a"
+ },
+ {
+ "path": "crates/oa-desktop/src/views/traceability.rs",
+ "bytes": 7501,
+ "sha256": "4c89dcf35c25e737d8cce4d543a5989ca104a5678e6cf40df47e457773d2ef94"
+ },
+ {
+ "path": "crates/oa-desktop/src/workflow.rs",
+ "bytes": 82829,
+ "sha256": "a00ba19d6d23435d18c9e1f6a0ad2065f889bcfa97aa3840bcdfaa03cb2666aa"
+ },
+ {
+ "path": "crates/oa-desktop/src/workflow/communication.rs",
+ "bytes": 11084,
+ "sha256": "34d121ad97706012722e4bb6210072ecc3a5b4718b0ceab0c3d0f14d4cd780a1"
+ },
+ {
+ "path": "crates/oa-desktop/src/workflow/recovery.rs",
+ "bytes": 9859,
+ "sha256": "0849b0f34dba5e8761be5fa05784d134ac74e4e9db395d6b9b7d6cff71e3f3dc"
+ },
+ {
+ "path": "crates/oa-desktop/src/workflow/transition_tests.rs",
+ "bytes": 8553,
+ "sha256": "589b1b2dbadca74a52c2cfcfd4fe620bd4790abd113ac02182e91616c52d793c"
+ },
+ {
+ "path": "crates/oa-server/Cargo.toml",
+ "bytes": 778,
+ "sha256": "527ff5e15010a8b560a6659aa1ae4bf0c8df0d9e019f03c47529ec6a750c0982"
+ },
+ {
+ "path": "crates/oa-server/build.rs",
+ "bytes": 45,
+ "sha256": "ab1be6ae1fd8bf3ebaeef6348347a9d5e666bde693126f199e041659e0b094a9"
+ },
+ {
+ "path": "crates/oa-server/src/error.rs",
+ "bytes": 5610,
+ "sha256": "93817f1502386e4662c96ee1c7d1c4afc4e33bda6fd64e46c86c8e1afd5a9368"
+ },
+ {
+ "path": "crates/oa-server/src/lib.rs",
+ "bytes": 7743,
+ "sha256": "20bef350350ed6339588b23af684abf745f97916c7edf3853ee042abe687e664"
+ },
+ {
+ "path": "crates/oa-server/src/main.rs",
+ "bytes": 4163,
+ "sha256": "d59a6a2b5c7034bd2e00a516d2d49abb16391bf0316c1c357ddafb9c82d2d1e3"
+ },
+ {
+ "path": "crates/oa-server/src/preparation_http_tests.rs",
+ "bytes": 5523,
+ "sha256": "454b90d0055d35644c8d990cd7576ddf675a9b271ca07fe9b0efce25d8ddbc07"
+ },
+ {
+ "path": "crates/oa-server/src/routes.rs",
+ "bytes": 46053,
+ "sha256": "3f9a0c22043dbf28f7a8e587d2fc3abf869f76f4d23a46fe0aeb9c4392454a4b"
+ },
+ {
+ "path": "crates/oa-server/src/store.rs",
+ "bytes": 25524,
+ "sha256": "37dcfb845b7993d5bced2bcf25fedfcd2a9c20a748f9315afe6cca1f41f89ae5"
+ },
+ {
+ "path": "crates/oa-server/src/store_preparation_gate.rs",
+ "bytes": 4440,
+ "sha256": "427c390da3c0281ba1fe115a93ea90800e3b45fce6bcb9185e71982ddd885fec"
+ },
+ {
+ "path": "crates/oa-server/tests/http.rs",
+ "bytes": 71892,
+ "sha256": "50daa6a90e96ad6fe3c6a27df2eaebe486270599c203892d09769c547a194975"
+ },
+ {
+ "path": "crates/oa-server/tests/http/communication_limits.rs",
+ "bytes": 5790,
+ "sha256": "ba5474dd8e494a0176fffb4548c8e3deb2081b15de33bfce82394c764e0a2c16"
+ },
+ {
+ "path": "crates/oa-server/tests/http/data_contracts.rs",
+ "bytes": 25949,
+ "sha256": "8cd456db83f301baca380acf025a5a99db4edf7d8839537dfbd02c4307e10e6b"
+ },
+ {
+ "path": "crates/oa-server/tests/http/port_anchors.rs",
+ "bytes": 15086,
+ "sha256": "7199d0a208ddad9e1518b6e3fb390d64da111716d7505ba5c995deae19ad1258"
+ },
+ {
+ "path": "crates/oa-server/tests/http/semantic_transitions.rs",
+ "bytes": 22832,
+ "sha256": "856d1c9b633ffb581e3e631f4e5a607789a02181dbf060667f208a569bfa4920"
+ },
+ {
+ "path": "crates/oa-server/tests/http/source6_views.rs",
+ "bytes": 13023,
+ "sha256": "867bccda0e030eeeb9976b4ceeb47b21ab5044d700278022d5fb495bbb64ca07"
+ },
+ {
+ "path": "crates/oa-server/tests/http/source7_contracts.rs",
+ "bytes": 11000,
+ "sha256": "0e0a467186da7263c0754890aff69ebd0d92d36b5224e4865bb6854b18dab7e8"
+ },
+ {
+ "path": "crates/oa-server/tests/http/source_projects.rs",
+ "bytes": 30907,
+ "sha256": "8ff580f317ec267d1cd72ae14d8638f74a77cdf99ca4c78f66f4290c2d77460a"
+ },
+ {
+ "path": "crates/oa-server/tests/semantic_http_contract.py",
+ "bytes": 7251,
+ "sha256": "4737ff94f10761f94151a694414c5c62737e0391ec820bca132830652bc4cca2"
+ },
+ {
+ "path": "crates/oa-server/tests/semantic_schema.py",
+ "bytes": 25038,
+ "sha256": "70c9d267268bd390680f290a585bd4ae0c6d8ef999ee2f32ece6e172293574f7"
+ },
+ {
+ "path": "crates/oa-server/tests/state_representation.rs",
+ "bytes": 12241,
+ "sha256": "6686c826f28248ae4b63ec3f2112e193d4a347724b87974483217fcfee49cd61"
+ },
+ {
+ "path": "docs/acceptance.md",
+ "bytes": 3463,
+ "sha256": "32ed3e4fed63f5c464e0937221f401d71623b276dd70f083531ac6ccebbf9050"
+ },
+ {
+ "path": "docs/api.html",
+ "bytes": 73861,
+ "sha256": "6f18cfc2a35eba631dbc61d04cd2721e0e361efff1699bf56d2c450a30a17a0d"
+ },
+ {
+ "path": "docs/api.rst",
+ "bytes": 42711,
+ "sha256": "7eb258088824efa5ac94311b07b73eb46edcac3c300a2aac78d3802111e338c4"
+ },
+ {
+ "path": "docs/architecture.md",
+ "bytes": 10974,
+ "sha256": "a50bd1645222ef3e574fa07a6ab9aa213431ee8833c6c0158ad91ce0f799543c"
+ },
+ {
+ "path": "docs/language.html",
+ "bytes": 179377,
+ "sha256": "739a5bb100c7cb0ed70e18afd1e363029757f3cfd6580bc9c5cf77faccb46940"
+ },
+ {
+ "path": "docs/language.rst",
+ "bytes": 131984,
+ "sha256": "894a1a77f97d1aa94c11762fe4e84b4d95e181a77c630324c16c307637ed27e1"
+ },
+ {
+ "path": "docs/linux-packaging.md",
+ "bytes": 8671,
+ "sha256": "41a1bb1095801df37a1411341c5791d55ea8c365739314d599a60269ac4bc7a0"
+ },
+ {
+ "path": "docs/openapi.json",
+ "bytes": 359757,
+ "sha256": "c6ae512f791a939eaf6ec5e5e78e9468bdee0e028092d00098bd90ad9498d967"
+ },
+ {
+ "path": "docs/research-v2-architecture.md",
+ "bytes": 40445,
+ "sha256": "1b3809f2dabe6b1805dbf3d3f2625c471cd21356c7bd603916466a8c4ced8090"
+ },
+ {
+ "path": "docs/research-v2-semantics.md",
+ "bytes": 31535,
+ "sha256": "b7ec3677300248926a88b84da3e7ffe52f5b73524c7712c927a0e951569db04f"
+ },
+ {
+ "path": "docs/research-v2-service.md",
+ "bytes": 36209,
+ "sha256": "4828df5bc679e7c71f49b4b85c9e7dacbec52f46d416fed48af70e08886d4e4e"
+ },
+ {
+ "path": "docs/research-v2-ui.md",
+ "bytes": 3245,
+ "sha256": "894e721b59a8ca85f0d95b6706ed5f130a0372def460cf33aeefc9e5e38cde0a"
+ },
+ {
+ "path": "docs/scope.md",
+ "bytes": 16260,
+ "sha256": "58b88a7f0b1c8a79edf23c375a141d7b1b821bca4f3212a80e29db5ea3fba796"
+ },
+ {
+ "path": "docs/server.md",
+ "bytes": 23361,
+ "sha256": "4eb24937d424ba8b384e1734e02a6f8e963ae11d2715889c92b14b39ff2e4a48"
+ },
+ {
+ "path": "docs/source-cli.md",
+ "bytes": 3374,
+ "sha256": "34f12b72cc6d6ead4f47c7818c1897db592f8ad493c70776db2ab48c24d8f051"
+ },
+ {
+ "path": "docs/v2-interface.md",
+ "bytes": 11807,
+ "sha256": "c7fbaf0ebd3fc9f6c11f818db2b0b9d68a33f580cfced22bde83ce08ae790bc7"
+ },
+ {
+ "path": "docs/validation.md",
+ "bytes": 6505,
+ "sha256": "f46ea950ed1a54409aa07c4f9e90f39f8e2d00c1dd1b6cb805caa3993845967f"
+ },
+ {
+ "path": "docs/windows-packaging.md",
+ "bytes": 10406,
+ "sha256": "2c852ff7041713ffbff3c951ac98df647c51fb70b0feba9000f7bfd5bc2703a8"
+ },
+ {
+ "path": "examples/coastal-rescue.arcadia",
+ "bytes": 33107,
+ "sha256": "7e183ef9fd8154d3074303f1956cd0d9871c19e3ffc630d333b0bffbd6e30e17"
+ },
+ {
+ "path": "examples/coastal-rescue.json",
+ "bytes": 49715,
+ "sha256": "242f0b031229a87af5c4ebe5d651defe59c191a6b7a69dc0a77f7e203411f195"
+ },
+ {
+ "path": "examples/legacy-v1-coastal-rescue.arcadia",
+ "bytes": 3449,
+ "sha256": "4dbbbdf8bc7e6cb8e0a81dd678069dae585a9e5187c59bc3665e1fdab3f1a86a"
+ },
+ {
+ "path": "examples/legacy-v1-coastal-rescue.oa.json",
+ "bytes": 5976,
+ "sha256": "fd0e9dedfb7adb922dc0575cc2b820d40344d639f1d8052bd70363aa59de9c24"
+ },
+ {
+ "path": "examples/release1/README.md",
+ "bytes": 11137,
+ "sha256": "113cfb1cf38a98b51bff60442d86a35d636a3d80ef206bece7d53d0aae67f2d5"
+ },
+ {
+ "path": "examples/release1/evidence/repair-check.md",
+ "bytes": 1419,
+ "sha256": "f4db17bdca4ecbb1bf0caef01b2da5f1f7f798ab3152b648ce6dd8696a6a68e5"
+ },
+ {
+ "path": "examples/release1/expected-diagnostics.json",
+ "bytes": 288,
+ "sha256": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e"
+ },
+ {
+ "path": "examples/release1/model.json",
+ "bytes": 134520,
+ "sha256": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727"
+ },
+ {
+ "path": "examples/release1/model.rs",
+ "bytes": 28762,
+ "sha256": "15f30f6bde655808fe754665c26d560e36f2a9bad340f346b09137d0d05045f5"
+ },
+ {
+ "path": "examples/release1/oa/activities/activity_oa_restore_work.arcadia",
+ "bytes": 1307,
+ "sha256": "83e73d0a013d5c3e1a4af0d9139ab0cfa362d25a846671fc2dac59181061a9d4"
+ },
+ {
+ "path": "examples/release1/oa/behavior/process_oa_assessment_procedure.arcadia",
+ "bytes": 697,
+ "sha256": "172c32c044ef4b5bc58a1f4635cc81f48dabb4014479b47dce7b25fedf2dbd0d"
+ },
+ {
+ "path": "examples/release1/oa/behavior/process_oa_field_procedure.arcadia",
+ "bytes": 1781,
+ "sha256": "d2552dc5383e2e91cb302a790364e197f663c2edc013a2ab1978789825c9c324"
+ },
+ {
+ "path": "examples/release1/oa/behavior/process_oa_response.arcadia",
+ "bytes": 3225,
+ "sha256": "cf86f46fa5c5d70592ef43f1bc1cb4d968efd8add384ba88036bef6b88aa4abc"
+ },
+ {
+ "path": "examples/release1/oa/behavior/scenario_oa_conversation.arcadia",
+ "bytes": 1795,
+ "sha256": "b25be524e98a35ebac5e4481b7609c027b32baf9605e8097f807560f94a77ca6"
+ },
+ {
+ "path": "examples/release1/oa/behavior/scenario_oa_full_scenario.arcadia",
+ "bytes": 1261,
+ "sha256": "00a1edf2c9707a0eb62ac070c2d02467874f2c9c1f273cc6ddb53d6d65999a1c"
+ },
+ {
+ "path": "examples/release1/oa/behavior/state_machine_oa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "447c9ad1359f4a31ecf975ad07c0b08d8e7e03191fb0858e2fc56f377b223cd5"
+ },
+ {
+ "path": "examples/release1/oa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "fc8012e5554350b12e1cb80080f8feab80d933c9b33c0f2a7bb500cb7d609936"
+ },
+ {
+ "path": "examples/release1/oa/model.arcadia",
+ "bytes": 661,
+ "sha256": "f7cf914766ea73ec53b85e32d117ea9a9dba52d3e038246863541ae7069b1a15"
+ },
+ {
+ "path": "examples/release1/oa/participants.arcadia",
+ "bytes": 1834,
+ "sha256": "951764c14179f27c10e315acf2ea37d1fff54c77b8441e23addd239e8c715ce3"
+ },
+ {
+ "path": "examples/release1/oa/types.arcadia",
+ "bytes": 788,
+ "sha256": "9d0396fb82dd5733edd8d1cf52f968ca84906927c704f9cf0944a7fae215cf49"
+ },
+ {
+ "path": "examples/release1/project.arcadia",
+ "bytes": 1958,
+ "sha256": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda"
+ },
+ {
+ "path": "examples/release1/sa/behavior/process_sa_assessment_procedure.arcadia",
+ "bytes": 706,
+ "sha256": "73928ced61c06cbc08728d7956676a50fd4c05aa5b7f1ff4f29ce27a0b0f2088"
+ },
+ {
+ "path": "examples/release1/sa/behavior/process_sa_field_procedure.arcadia",
+ "bytes": 1790,
+ "sha256": "886d462af83110cbfa55dd232a667ea1446761234471357cbb878794e98e8768"
+ },
+ {
+ "path": "examples/release1/sa/behavior/process_sa_response.arcadia",
+ "bytes": 3234,
+ "sha256": "041785fc22842423822431bd5e6e8d16094ebd0716965e7c765fb8c1d7549e8a"
+ },
+ {
+ "path": "examples/release1/sa/behavior/scenario_sa_conversation.arcadia",
+ "bytes": 2127,
+ "sha256": "97276f5757ee9aa12a0dbd9d3ccd58174b3091162bb8bcce357cec20a45f5d85"
+ },
+ {
+ "path": "examples/release1/sa/behavior/scenario_sa_full_scenario.arcadia",
+ "bytes": 1256,
+ "sha256": "e12fc406b99d60a2f7768aaa0b25ba560e8605f0b8e37f3a7cdd3b8443562b74"
+ },
+ {
+ "path": "examples/release1/sa/behavior/state_machine_sa_monitor.arcadia",
+ "bytes": 2022,
+ "sha256": "05ce29af99424c491c47fe2450dc6f5f8ce995511c01ec7e17bb5855be3c9736"
+ },
+ {
+ "path": "examples/release1/sa/capabilities.arcadia",
+ "bytes": 781,
+ "sha256": "b6912fb3ece439912345fa71724acd462e716a1e5a88203a1058a5c3b2c20a4f"
+ },
+ {
+ "path": "examples/release1/sa/functions/function_sa_audit_thresholds.arcadia",
+ "bytes": 297,
+ "sha256": "e5a04325d52183956dca814dd90f4e56c96c6b7db0f4f202f203ecb49c3e0f4f"
+ },
+ {
+ "path": "examples/release1/sa/functions/function_sa_restore_work.arcadia",
+ "bytes": 2576,
+ "sha256": "fddeb67308051325041fee641fe758ddf43345b7c47b66d433616ad07fdccdd6"
+ },
+ {
+ "path": "examples/release1/sa/model.arcadia",
+ "bytes": 661,
+ "sha256": "d431509251acb859fbb98733b6d18add952495e10a78703fbe8f31e40bc083c4"
+ },
+ {
+ "path": "examples/release1/sa/participants.arcadia",
+ "bytes": 4012,
+ "sha256": "780d2eb08b217b67b55febb3d34846f727b8ee9018df6c5741748647e36b2ff8"
+ },
+ {
+ "path": "examples/release1/sa/types.arcadia",
+ "bytes": 1227,
+ "sha256": "101fc0fbd99f1101514aa7b6da4f8e535aae0bb94c9e2b9e7e6c151135b28660"
+ },
+ {
+ "path": "examples/release1/traceability.arcadia",
+ "bytes": 29506,
+ "sha256": "48a96f543456c9b5a5798fc795594e4119147fe45f11a6f109f141b7f514560f"
+ },
+ {
+ "path": "examples/release1/transition-before.json",
+ "bytes": 35801,
+ "sha256": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d"
+ },
+ {
+ "path": "examples/release1/transition-request.json",
+ "bytes": 4276,
+ "sha256": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a"
+ },
+ {
+ "path": "examples/release1/views/diagram_oa_allocation_view.arcadia",
+ "bytes": 2176,
+ "sha256": "dfc0eb61e0e2aa5bda45524571fbfa3494f44ce19696f3e15e07438074a07bc7"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_allocation_view.arcadia",
+ "bytes": 2173,
+ "sha256": "b78b43a26a82efb8fbd5374b09c329fa341f57b29f635d397b9ef8aede71d389"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_capability_view.arcadia",
+ "bytes": 1620,
+ "sha256": "75e8dc9fb30f25d6f8fd3c6b595593ab4f98958a25798f961e336e0fac557649"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_service_view.arcadia",
+ "bytes": 2209,
+ "sha256": "b08c84f3ea633e7c78dfdad3272f75415f6256cf3614b6d99bfd4f0269076c9b"
+ },
+ {
+ "path": "examples/release1/views/diagram_sa_state_view.arcadia",
+ "bytes": 1931,
+ "sha256": "5f42f4c605e57b817d8400ed21f2b14bfc4e1592aebe941ff113767504c2b869"
+ },
+ {
+ "path": "examples/routing-workshop.arcadia",
+ "bytes": 66431,
+ "sha256": "dc6b75da72884d399132ed9e300fa5a26b73d802bb526c9af986ae9eeb0a646a"
+ },
+ {
+ "path": "examples/routing-workshop.json",
+ "bytes": 100281,
+ "sha256": "8592714f75b271133f21ce017d87896b6cf91ea0e4837f1c2ed453e21be26b14"
+ },
+ {
+ "path": "examples/source-project/oa.arcadia",
+ "bytes": 2142,
+ "sha256": "5f60642f211b2d352a951092faeeb7503d4fb7f6f7fca99e810347231101d31c"
+ },
+ {
+ "path": "examples/source-project/project.arcadia",
+ "bytes": 606,
+ "sha256": "787f6b67b179ffd221f7e5faf1a4edd74cfd116aa1e3b5e9ed9f22fbff612d8d"
+ },
+ {
+ "path": "examples/source-project/sa/assurance.arcadia",
+ "bytes": 682,
+ "sha256": "ab76044836e4dcd42a82d316846ec87da2108939aa2037640c5bca1d2ecdee0f"
+ },
+ {
+ "path": "examples/source-project/sa/participants.arcadia",
+ "bytes": 1231,
+ "sha256": "1abad579812e77877b45a3d8e3c3e9da734be2d04a0254cc730a5257ebd2dc88"
+ },
+ {
+ "path": "examples/source-project/sa/reporting.arcadia",
+ "bytes": 2839,
+ "sha256": "f0b7443f728912ca163de39b6055876b533a77feaa3219b2d40a2dd65712e058"
+ },
+ {
+ "path": "examples/source-project/views.arcadia",
+ "bytes": 3508,
+ "sha256": "e29ad3f406a6a50d2f0ae2ca2a53ee3b0c18222cd4098704a46d4f48b64ea17f"
+ },
+ {
+ "path": "examples/text-first/README.md",
+ "bytes": 1367,
+ "sha256": "4ec8250d5ed556f1d496e8c85137e973e802ecb11df1a36488411e8cdc5a9f9e"
+ },
+ {
+ "path": "examples/text-first/project.arcadia",
+ "bytes": 480,
+ "sha256": "f441535c623e0cf20e1875eb2cd33b587ed00e078c37af39cb36d89bb5edc19a"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/README.md",
+ "bytes": 12012,
+ "sha256": "3545d9695e7c62e0caa818efd7f4dc426f04876a3626e0f3b1bad606b258cb26"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/base/expected-baseline.json",
+ "bytes": 4524,
+ "sha256": "f239e728d650b0af32bb8f3dcaf6b27c99b9c5041132ac14e51756ea6563d978"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/base/project.arcadia",
+ "bytes": 3077,
+ "sha256": "045cb047d7bfbca72c3fb30db4c81a30ad463d3af7fc51604906ad7fa75a1ff8"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/base/rationale.arcadia",
+ "bytes": 57,
+ "sha256": "c01867c9dc0d40726853119fac3c6ad24a40d8d362a93a8b9d20df04ec2e273f"
+ },
+ {
+ "path": "harness/fixtures/basic-editing/manifest.json",
+ "bytes": 447,
+ "sha256": "d23c21b02064ddb85e051e10e16ff089daba8d9910bb1a695dfabe2db3757734"
+ },
+ {
+ "path": "harness/fixtures/behavior-creation/README.md",
+ "bytes": 5954,
+ "sha256": "9e5fc751424d12cfe76a84abb1da45b2d38947409179451bb90ffe7e7ddc8723"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/README.md",
+ "bytes": 2604,
+ "sha256": "088f0bdb3375deb8f2c7e3b749ca8d210ea828ca4dece69b3871b674e3110175"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/hashes.json",
+ "bytes": 977,
+ "sha256": "f6d3e8aa42f40f64ce2ecba7b9cf112eb9091f3cccde3a592b0434e834cf2a6a"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/after-reset-deletion.arcadia",
+ "bytes": 4218,
+ "sha256": "c0fe9570684c0061efc4cad65e339fede6961b04ca90c6f324c787bd65e61226"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/after-reset-deletion.json",
+ "bytes": 7835,
+ "sha256": "93187f7a1b476a44185725c6b15f2e1767482b6911ab1280144348d033eb51f0"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/boundary-only.arcadia",
+ "bytes": 4771,
+ "sha256": "02f85e39957ebe6d56336fa729a5c8b10f51865face9d3b2c9679200571fcfc7"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/boundary-only.json",
+ "bytes": 8840,
+ "sha256": "756f9710336e6734ce3592ca774c6f104bba5a37c9e165540eb39326682393d1"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/explicit-functional-refinement.arcadia",
+ "bytes": 8635,
+ "sha256": "ac5b629ba5596464f9f8a975b8bc2764e3007ceb7342de3f828d4b18e4f069b7"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/output/explicit-functional-refinement.json",
+ "bytes": 16160,
+ "sha256": "78a47c978dc667aa1c45af711f06aa2a0d3c581003013e30c3e02fb0faae2e64"
+ },
+ {
+ "path": "harness/fixtures/interface-operation-probe/probe.rs",
+ "bytes": 11274,
+ "sha256": "72b1b443e25eb20420dec4477b0fb5ef5d1c705e3882c81df88dd578ab96a426"
+ },
+ {
+ "path": "harness/fixtures/release1-native/README.md",
+ "bytes": 2564,
+ "sha256": "5f5ba040fd2df9261a29bcab3f9a7ae27c3683265411ad3dda0b467d5df8590e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/README.md",
+ "bytes": 4155,
+ "sha256": "24200c84ccc2ef3e17c029744074e5cea785188f5549ac54e44cf89682643f81"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/generate-semantic-artifacts.rs",
+ "bytes": 9275,
+ "sha256": "444f360b78df52c5e42046f77b148781e0c9bddc2a09c0b7bd65c7a4f376cfe6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.after.arcadia",
+ "bytes": 7768,
+ "sha256": "84b22e95c213018612e6e8a84cad68b35a8b495ff3515b0f978c5e7a3d196d79"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.after.json",
+ "bytes": 7330,
+ "sha256": "13a6f99ec2df1dd35ee8b2613f183918cd9537eda4a5da085ad52e65c033db59"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.arcadia",
+ "bytes": 1446,
+ "sha256": "1e0d662ba33071b122ad9206d2d559a8e748c9431eb3ac1b759bde3760ca877c"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.json",
+ "bytes": 2216,
+ "sha256": "fa6a2e3643c0f909b92407abec3edc897033162eeaa501f8d4616f79d41fa044"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.manifest.json",
+ "bytes": 567,
+ "sha256": "65cb70b0d697a055363ba01bf30bed7e412d43c9978a033549c378d117719ae7"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-create.request.json",
+ "bytes": 3991,
+ "sha256": "ed905267263e7f8a0cd2dbe833196b30118b6a246815216932fae661ad650699"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.after.arcadia",
+ "bytes": 6837,
+ "sha256": "e50fc462ec22dbf98011929931dd2489430001c3e6433608685b9d88bceb9acb"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.after.json",
+ "bytes": 6433,
+ "sha256": "f27a7b38d59c7cefe58cb36783d3d4dd8d8ae1168d9aed1eb61d343c18758908"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.arcadia",
+ "bytes": 1807,
+ "sha256": "eba2aec39e6efb405eace4bc8ab0c5563907c26d449e71a4aa750df9f73233b4"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.json",
+ "bytes": 2673,
+ "sha256": "2ef777f19b94f08b5e5ca2ef727777897e1018f1426eee291408b3c69101f596"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.manifest.json",
+ "bytes": 519,
+ "sha256": "3af7232ed3056ca84dcaf0f266ead3faaa512a0dc0051c7ba68791c20dbae0cf"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-delete.request.json",
+ "bytes": 1246,
+ "sha256": "8af21b00b62e620256fbf50e47c6c5836cdb6922d9549cc4b3d86bc03b008073"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.after.arcadia",
+ "bytes": 4200,
+ "sha256": "20bb30473830e44228958e78fb7480269cdcc0580c6c90c718da75f34ca5a43f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.after.json",
+ "bytes": 4552,
+ "sha256": "44f4b05db49758dcf344788f57cecc0250fd029099b1c54e4e1e3e972f6cf039"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.arcadia",
+ "bytes": 1808,
+ "sha256": "b0fd031671c9f755756922ec5c40a0ce3f40164d22c3c389aec9fd0705344da7"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.json",
+ "bytes": 2673,
+ "sha256": "1acf81e08c51c85aceb14a3efbc39ec680552a19c7824aab4611ac3cf71b9c6d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.manifest.json",
+ "bytes": 610,
+ "sha256": "a07978f4b0a3ca83e5ae5f35e7b66b26473b24431fc90e5889d755f1816d52dd"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-move.request.json",
+ "bytes": 2352,
+ "sha256": "5ff844a038b0030adb5a83d13d81e60f30627543388f6199b6a0e026fc42ea58"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts-native.md",
+ "bytes": 4075,
+ "sha256": "a51ffbc45b6c172cbfed3b16c249296e6a28d457fea57393dd958cdcfbafbca1"
+ },
+ {
+ "path": "harness/fixtures/release1-native/artifacts/semantic-artifacts.hashes.json",
+ "bytes": 2367,
+ "sha256": "3ad8b7428b3ed7585a91e7f5d9d5f4880caccfd0b08dca25d05af523e62e0a10"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/README.md",
+ "bytes": 15091,
+ "sha256": "015c16010c212af283f9def118c6d263ba65a3bc6e81a750f180624ca69bb311"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.after-template/entry.arcadia",
+ "bytes": 6098,
+ "sha256": "a6976e7c21031e7cc3cb731e20c3075cf89cf85144dc8d3f52930f8521ea196f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.after-template/expected.json",
+ "bytes": 10480,
+ "sha256": "60c4419415c546f06c9972ac7bb0e530ada7b30a200b59e75ddda231a24f9a3d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.after-template/traceability.arcadia",
+ "bytes": 407,
+ "sha256": "3c41459f2b0706a767988738bf8126f76540415bb5940d9c5db19cbe8c18182d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-create.request.json",
+ "bytes": 1331,
+ "sha256": "b16e673b29c96b5aed681389ff7669110abe552939e86dd0ec6011c9a45ed860"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-draw.arcadia",
+ "bytes": 5239,
+ "sha256": "a511a4d817d9c5c80233bce83e84a7056b7dce7afd9fcc25696e333f8f4df055"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-draw.json",
+ "bytes": 8424,
+ "sha256": "7140114926228b1c378c450535f7a70992c71a879a8d2270c49e8d5f04a2f484"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.after-template/entry.arcadia",
+ "bytes": 5710,
+ "sha256": "910e6dfe609467047ce629ec9c626f4e927cada74bfbfa164414a8fe6783ea87"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.after-template/expected.json",
+ "bytes": 9868,
+ "sha256": "567259e20c9f6278776e8fa99b8b2fdbbc9a883ad875c5d39dae1b53de3a2924"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.after-template/traceability.arcadia",
+ "bytes": 407,
+ "sha256": "997f72fe9258012df66e88344cf39c1a2cebf2327f604056b67f7f2a8ebcfbfa"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reuse.request.json",
+ "bytes": 1127,
+ "sha256": "cb3fe05859cfffec6f8846c19983dc462413bc0ea4f6e19204f09c259919c4b1"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/cm-reverse-scenario.arcadia",
+ "bytes": 8209,
+ "sha256": "ec826b9b84616507490ac30e4d57100dd6338dba2f7071e879a8fb58820a85c5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/expected.json",
+ "bytes": 19735,
+ "sha256": "ef07e4d2d3fc36004ff01739838281581d9a703c747611eacc01bb9cc672489f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/oa/behavior/scenario_oa_cm_200.arcadia",
+ "bytes": 1488,
+ "sha256": "0ec8d7e38f5a5cd56ccf3979757b3a9f412ad0ae36ee6c2690b7872129046d70"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/request.json",
+ "bytes": 1090,
+ "sha256": "c0ac99ee186d126fe3ec57f2c0a4072984d39d36ad21ead357ab66a08ac0065c"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-behavior.after-template/traceability.arcadia",
+ "bytes": 2068,
+ "sha256": "dd5d356390089e4ec474374956f6219502733987e60ef8ff32920d8b33dce858"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/entry.arcadia",
+ "bytes": 9827,
+ "sha256": "86dd8a355f6c9dadfea56d06e5247c9b0f820819d7df17ef5690671c7c81324b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/expected.json",
+ "bytes": 21877,
+ "sha256": "980489ec2c483d4f9efada61358c1f29bafb2572fd5b804c656b91b8e9bb4f9a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/oa/behavior/scenario_oa_cm_200.arcadia",
+ "bytes": 1488,
+ "sha256": "0ec8d7e38f5a5cd56ccf3979757b3a9f412ad0ae36ee6c2690b7872129046d70"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.after-template/traceability.arcadia",
+ "bytes": 2068,
+ "sha256": "dd5d356390089e4ec474374956f6219502733987e60ef8ff32920d8b33dce858"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.arcadia",
+ "bytes": 7698,
+ "sha256": "1abc4c4cac37cfb06ed3b12a115f9e7e6a2d3affd34b25c355309e5ee465fa40"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.json",
+ "bytes": 13130,
+ "sha256": "a5cf4cb2ef390113dd752f4054d43fb789da2ca77799efb5782805ae8aa2dfd5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-reverse-scenario.request.json",
+ "bytes": 1090,
+ "sha256": "c0ac99ee186d126fe3ec57f2c0a4072984d39d36ad21ead357ab66a08ac0065c"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-transition-ready.arcadia",
+ "bytes": 5454,
+ "sha256": "b761c4c2af5f7064aaf1f887ffd6094eb6ae834b2c8057ecdbc4aa97c042066b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/cm-transition-ready.json",
+ "bytes": 8852,
+ "sha256": "2536b000de361fb131787e32b466b9123d4e98abf00c099ad32f6cb8e7fb530b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/generate-communication-means.rs",
+ "bytes": 28035,
+ "sha256": "003521404b702332082e01f2be7676fda1dd7b5bcf220bb81d25120c8984c75d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/hashes.json",
+ "bytes": 3133,
+ "sha256": "39b4dc02fc8b074015c59354fd1c1061c25832fde25065c7151f36eb0455e6f6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/communication-means/manifest.json",
+ "bytes": 1237,
+ "sha256": "fb921f42f2c4a4f8a67d1266f17f8c7750b6a2619554b713aa92b17918f0c43e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/dense-40/dense-40-oracle.json",
+ "bytes": 26264,
+ "sha256": "74edca148583f42c269e51f135662f65ef1c9ea1c94516d63e2a5091967dc99b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/dense-40/dense-40.arcadia",
+ "bytes": 28947,
+ "sha256": "00b87736b82c5afd33b311df7aebe9a6c8c51b6de66221181b5ea8bc8ca221a3"
+ },
+ {
+ "path": "harness/fixtures/release1-native/dense-40/dense-40.json",
+ "bytes": 54938,
+ "sha256": "5dd4f22453330cab3abc059881660190e689a22619853f77cf6d0b52735d97ad"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments-native.md",
+ "bytes": 2624,
+ "sha256": "28906924b05f75bbf432389517aef65f1df474d2f2bff79a3586564f7f7328ef"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.after.arcadia",
+ "bytes": 6795,
+ "sha256": "9d084dd75a0251d349cc31f9ee98df58d0bc76859dc3cb1503f242e2f759888a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.after.json",
+ "bytes": 9618,
+ "sha256": "f09be41afd85c18fd52fdf997e9b81c4b7cb2c41e78d167abcd14caea8110333"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.arcadia",
+ "bytes": 4990,
+ "sha256": "8740f27be99624013d9be876639c0411e1b8c14f0f9a843f43dc71d769efd79a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.hashes.json",
+ "bytes": 963,
+ "sha256": "00fc9e2c709bdcd9ee98012176cb0edb635c112153d963cd169bf4bf69a9e010"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.json",
+ "bytes": 8146,
+ "sha256": "9c188f2890630c7feec2be719a7104af9690da7ea836df18fa78da3ff2acfc1b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.manifest.json",
+ "bytes": 1045,
+ "sha256": "46b40f221ffad614733b615208814ace63947e96b80bbfdec63853ccd4f4e8c8"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-attachments.request.json",
+ "bytes": 3075,
+ "sha256": "b57f656f0b9cf2c595e83150c683301909e20f15684d35d108d3c57ee0dc5909"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch-native.md",
+ "bytes": 2637,
+ "sha256": "dddca2b85f892b05881c75ef3cd62437396cc086a09909b621e02bad0cbe56ff"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.after.arcadia",
+ "bytes": 3134,
+ "sha256": "d3315bba762395205ae761e6e64029e8b4070c15a1299c6114075581db7b0974"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.after.json",
+ "bytes": 3927,
+ "sha256": "8f36119c7e893a4af374b13b85b1c2cb3df0196df8d06a551386628fc8b8646d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.arcadia",
+ "bytes": 1787,
+ "sha256": "5e1f433e04621424edac43e53411624059e873f88dd531bfdb39093953485ed7"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.hashes.json",
+ "bytes": 933,
+ "sha256": "1bd87700b04e4f169ab5f8aceaa3b0c3ec3a1cc43e1bce135a1cee506fc75d38"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.json",
+ "bytes": 2816,
+ "sha256": "1972242d2a8677d004dc3af1081feb292de65fe26128d877dded8661f9906619"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.manifest.json",
+ "bytes": 647,
+ "sha256": "1e440efa02e2ccc27585d9a49c7864d53e3e3e7645ccb219f040e00d95331b8e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-batch.request.json",
+ "bytes": 2197,
+ "sha256": "71f23474614aab90398ce24f00f2cf61d6cb9a3a34faa3e12e0c8104e0a71073"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-repair.arcadia",
+ "bytes": 15635,
+ "sha256": "e3ce0347746584f503b825a8b6bd23a7eebf0827940112ef8f36d500c1d0a043"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-repair.json",
+ "bytes": 25736,
+ "sha256": "29736f2a619f7f2d0e795229f6e8c3645a1b4eca6292043cd604aa01b1ec9170"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-take.arcadia",
+ "bytes": 15631,
+ "sha256": "9ab573c92be66e3bae659dd826923ba6066979b1651edda7bd469056632cd77e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.after-take.json",
+ "bytes": 25736,
+ "sha256": "828c990c6129692cd31c7f6ed0dd6c952f9cfffdd90bc80171df82d4d929afba"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.arcadia",
+ "bytes": 15617,
+ "sha256": "49214be2ca9c8bb6195c3b39737c053847021839f70f0bb2921d35a1fbd2ac83"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.invalid-request.json",
+ "bytes": 2225,
+ "sha256": "8b02d4739b1be2c8a64da76423b4a6cae7d89c60f4d30723b94f1da3779cbcca"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.invalid.txt",
+ "bytes": 78,
+ "sha256": "aa536e18bc65325163e9b645a4ad031ab599fcb05948dcf741b725d0f0c8b602"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.json",
+ "bytes": 25722,
+ "sha256": "995dd72f5c1f07f998233ae9847e337cb82de0c2a9053d3905be021d5f8124b9"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.manifest.json",
+ "bytes": 1059,
+ "sha256": "ad315359bfd68ce0d265976dc9e4ea6a502ebbb87d7339dd1af5ef27dd01e433"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.repair-plan.json",
+ "bytes": 15016,
+ "sha256": "27bf316ad66ffa91f6323e75b0c280baf5caec298645793dea72c5d42aa3b782"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.repair-request.json",
+ "bytes": 2225,
+ "sha256": "f4f6b5480ec8927033ea469ea82c1e8411cb6dba95d2da680f15bc193914c9b4"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.steps.md",
+ "bytes": 3897,
+ "sha256": "7d7e38d93641a5f22a2738baa4dbacc392eebb477a855cdce0641961980a19ad"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.take-plan.json",
+ "bytes": 18557,
+ "sha256": "74dd68ba686b8928054480f0bfed55aad91f6f1208803399dc9878e894f57ecb"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-keyed.take-request.json",
+ "bytes": 1508,
+ "sha256": "d19a53002a6bf95af3123a3ef9f78baaf7ce1085d4f06f0f4a9d5e5d5accbc0e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.arcadia",
+ "bytes": 6935,
+ "sha256": "90277f1780d43a6f0b2184bf00b87e7a4f3518d67c6f10d4793daafe1aabf493"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.json",
+ "bytes": 11286,
+ "sha256": "5a3c8494f6976c23b990c14b40760fb2f32ca9973caf6f31dab5632310663591"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "6a86fde194faa55f2d1bdb982571e0e711e5401b53a960e6acb8cf3e17f506c8"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.arcadia",
+ "bytes": 6195,
+ "sha256": "e7c30c5a5c3ba7209c8e223f02da55821d7af10a2f09844a7065ae6cd8f22aa5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.final-plan.json",
+ "bytes": 19156,
+ "sha256": "d1e5c2ed08e0aa6274a0421a39876d1ba99f4e441c7a0a68660a83b9e017da14"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.final-request.json",
+ "bytes": 2418,
+ "sha256": "433f723252598994dac7184da61f95344b528156cc5a0fba1a7864b2ec9f002b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.json",
+ "bytes": 10441,
+ "sha256": "9e4e2ce1c7879935ad0860a3ae720506c929a8422b51491b719d88af3fd3ff2d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-branch-steps.manifest.json",
+ "bytes": 1042,
+ "sha256": "87e8899c7d4e54faedeef32afb7592fb65278d1bd7390850ab198dcf62068329"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.after-order.arcadia",
+ "bytes": 6280,
+ "sha256": "6c77ed42bbf9f4ebe08bfa233197a4020e5587ce33747f7914430aa0c662d9a3"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.after-order.json",
+ "bytes": 10009,
+ "sha256": "1dd7ca2bcfc84ac23369c506302b39569d2ebc41e5a4edc99378a1fe01923b7e"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous-request.json",
+ "bytes": 1390,
+ "sha256": "e0ea7aeb25687ae63c65128f1aab78273f14030d179d3fbea63fe91ab5430136"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.arcadia",
+ "bytes": 5529,
+ "sha256": "563c36162050c8564ffa25a85c726462ecdaebdb87659b3f3610506bfd5581e6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.final-plan.json",
+ "bytes": 18821,
+ "sha256": "f96c43fc58dbe48a044b628b31bb3f0b59171dad5b6982ec6c34a5597e474130"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.final-request.json",
+ "bytes": 2438,
+ "sha256": "d38fdb13612b07254edad80848f79c1e097d5f868c020feae9ba072aa697a50d"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.json",
+ "bytes": 9153,
+ "sha256": "2ed0daed118f2df13dcad738dab174ad34d44a15921cf8097415e707978b0968"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-fragment.manifest.json",
+ "bytes": 1058,
+ "sha256": "c79bd12a85d8d4f25cc3ee43581b3e5e74ae9ad7e4de0c72764975ba2b89c279"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.after-order.arcadia",
+ "bytes": 5026,
+ "sha256": "98020e048ac01f285c6ad8c10ce22bef05e193b5ba0f5558be1fb26a7bf82620"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.after-order.json",
+ "bytes": 7851,
+ "sha256": "ea3783992488bc6ee944eb91a26ccf3dbc05a3909528fc32ec8023a76765cee6"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "ea3cfd25f7a50481805956fb88ab851516dcd602dd6564502a0cc55f7de6a54f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.arcadia",
+ "bytes": 4295,
+ "sha256": "2b7c3694623bbea3e36dcc6f674d4adcd91e23c8e1005ded21e5a69648a048e5"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.final-plan.json",
+ "bytes": 17912,
+ "sha256": "95a23bd334cffcaef0c3b1012b7555efd2a077ef6f9140d58020910ef8a76723"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.final-request.json",
+ "bytes": 2081,
+ "sha256": "1e2433ed5821bf65443f56cb27381ee935db81b1955d3a539b7952898312a410"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.json",
+ "bytes": 7012,
+ "sha256": "0033f8780c2ba69779b811bca792340c25a092f887e5811c6839f0a691d744f2"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-reverse.manifest.json",
+ "bytes": 1046,
+ "sha256": "e3b4a60a56abee779ca8356605c968aa2d99256a3bd3c5601b14b5c5502a1a05"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.after-order.arcadia",
+ "bytes": 5467,
+ "sha256": "e265cab35097acc46f5123107c13bc35b9f71931ff650557fc1c85779141b327"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.after-order.json",
+ "bytes": 8482,
+ "sha256": "9b777828d3681138de6bbd08de8bf922bed92c27be60b103d8ff73ce7ddd0a48"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "7d124256730bf2feee772c7d0118b87eb288e8aaa9179d69ceb9259dbceb2c71"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.arcadia",
+ "bytes": 4727,
+ "sha256": "87a5a3665dab2dd6e595867dcebb1586e93dd067c85d6e24b1a4d3ff0ad6d4ba"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.final-plan.json",
+ "bytes": 18803,
+ "sha256": "1888ca51e0923866a9afea99cad069fe5d3f19f0a1b24328e20ac1b9f52d8bad"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.final-request.json",
+ "bytes": 2294,
+ "sha256": "5070a5b85feb1bc7b1fc648cd5d35ef26663492c8666dcd471950c06ec3a6052"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.json",
+ "bytes": 7641,
+ "sha256": "725b35e36640c81c70c7efea5c3304bf61b49ef9664194ca0d491782cc62191a"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered-scenario.manifest.json",
+ "bytes": 1038,
+ "sha256": "1b43abf8fed3061c65a7b7cb103d31ea7ece333a2da83c645f8d228448c4dbb2"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.after-order.arcadia",
+ "bytes": 5019,
+ "sha256": "65a6217f474b5085f37b511151185c2c08ff7a1998e924f1fe01f29c410f6ced"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.after-order.json",
+ "bytes": 7835,
+ "sha256": "8a53bb9b43eab5b6dca5e08deef20c14c96e134b9ee816a99b7960bb5255e008"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.ambiguous-request.json",
+ "bytes": 1379,
+ "sha256": "c704f99245f00d5c3be0562d91931b4e7f1fb3fb36f41991eb76656ffbd6129f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.ambiguous.txt",
+ "bytes": 94,
+ "sha256": "0f938b23fcfce776a61a57de5a2c82b4a0720b5e5af6780fdb96f5f92b3edd21"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.arcadia",
+ "bytes": 4279,
+ "sha256": "c59ec1233d5a9fbb8e386f6d9b13f398be21baf813ba215efb51bfc1bb98db36"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.final-plan.json",
+ "bytes": 17938,
+ "sha256": "30c5c3f614a74708763df612016483623792f4c65e2ed2fa662ac417cfb852eb"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.final-request.json",
+ "bytes": 2098,
+ "sha256": "3b6c35ef78133f0980cb6fb53fbc29c5d3c4deb5dc4be01c2c3384e221987b9b"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.json",
+ "bytes": 6996,
+ "sha256": "c159c60403abcf8fefab7f19cbad9989488e8ccc20b82c57597b22a0ae254802"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.manifest.json",
+ "bytes": 1038,
+ "sha256": "af8a314a9b1d85a8f98affa5a6649f43d252851158798e1ad687bb1b6966b14f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/semantic-ordered.steps.md",
+ "bytes": 4549,
+ "sha256": "6004894e9d9f478b7fe1c3c3e0706dcc546d5df8557cff91b4cf26f8a4badf24"
+ },
+ {
+ "path": "harness/fixtures/release1-native/source5-recovery/15000000-0000-4000-8000-000000000010.json",
+ "bytes": 1933,
+ "sha256": "71cca2a59f36b2a07d11dedbe9101226aa1a4a00d8ac244b5565202e56709f51"
+ },
+ {
+ "path": "harness/fixtures/release1-native/source5-recovery/generate.py",
+ "bytes": 1496,
+ "sha256": "e44986c1d563499f25917441220b23883fc9cb0f89faf409873191f0212fbd2f"
+ },
+ {
+ "path": "harness/fixtures/release1-native/state-actions.arcadia",
+ "bytes": 6224,
+ "sha256": "9cde82608110e56aa9e14c9e33eb38976f53aed74ab013145d411d5743a0ec26"
+ },
+ {
+ "path": "harness/fixtures/rest-coverage/README.md",
+ "bytes": 1870,
+ "sha256": "df4a391312e0c8b20ccdcc850199443b08547be1b10b1f2447a067bf4bf544e6"
+ },
+ {
+ "path": "harness/fixtures/rest-coverage/coverage-probe.json",
+ "bytes": 8008,
+ "sha256": "699b868dd6c955f7ef02f2f061935471f3d21a406005fcdecba8cc719cc69a1f"
+ },
+ {
+ "path": "harness/fixtures/rest-coverage/run-coverage-probe.ps1",
+ "bytes": 9381,
+ "sha256": "f24f19fca418b76a7249894a08c116e16b465a02f8f899d12b808ccfdd90a3bd"
+ },
+ {
+ "path": "harness/fixtures/run-timed-bundle-oracle.ps1",
+ "bytes": 7466,
+ "sha256": "fab696486a574f9867c6e69a8a8b960e7e9722206441b9cc5f36d41f285f1a25"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/README.md",
+ "bytes": 14700,
+ "sha256": "6b651b180b76d10b00bc49d77a8f5e0b3ba0317446978f41b541f5bb23fedad8"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/base/expected-baseline.json",
+ "bytes": 38589,
+ "sha256": "abd8246c939ebb8a2162ef82e23d0fb322ee1a8b4d0d7c28a34037e42e86e841"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/base/project.arcadia",
+ "bytes": 19470,
+ "sha256": "9b1afbe96b0bf8fa613451b00929492f05a59b9eb2ca74c2c374a8b7bb7cba9e"
+ },
+ {
+ "path": "harness/fixtures/scenario-authoring/base/rationale.arcadia",
+ "bytes": 86,
+ "sha256": "82df210f22f6aa336a3e7e00240977d671c7c3923db7bc58fb29e6d98b67ef49"
+ },
+ {
+ "path": "harness/fixtures/source7-native/README.md",
+ "bytes": 7235,
+ "sha256": "7abccbc3da0980d1d099761d1a66d12c403a0f543954134537ecd4930d182b4b"
+ },
+ {
+ "path": "harness/fixtures/source7-native/base/expected-baseline.json",
+ "bytes": 26428,
+ "sha256": "e2882a58f841f460f45d9d67db1606440e0d7997b9b29a784569ae48e4b23bfb"
+ },
+ {
+ "path": "harness/fixtures/source7-native/base/project.arcadia",
+ "bytes": 13616,
+ "sha256": "5474d8ca05fd1375220dc0ad40d913631fde51159e9632bb9f290d1ecdab8dd7"
+ },
+ {
+ "path": "harness/fixtures/source7-native/base/rationale.arcadia",
+ "bytes": 59,
+ "sha256": "e6a600e13036d4ad594a65380ed1b3d82d1772d583041bb0a624c9588a73047a"
+ },
+ {
+ "path": "harness/fixtures/source7-native/manifest.json",
+ "bytes": 410,
+ "sha256": "6c57164a796234d83818d462d0a25f10e77062d405769e602ae2a7bec251e3b6"
+ },
+ {
+ "path": "harness/fixtures/staged-source-authoring/README.md",
+ "bytes": 8380,
+ "sha256": "711910a12d5f3960f32da9aa7bc2e647f31d770c84b7fc26e232dcb3a76f8821"
+ },
+ {
+ "path": "harness/fixtures/staged-source-authoring/base/notes.arcadia",
+ "bytes": 63,
+ "sha256": "ad9de02963c20d1f43856455a797deb05ca674659e45f2c4b96b52783024f8bd"
+ },
+ {
+ "path": "harness/fixtures/staged-source-authoring/base/project.arcadia",
+ "bytes": 593,
+ "sha256": "aa844950ec4fb86f262a257e1fc370a26557ea7978d8a4cab78265037660234c"
+ }
+ ]
+}
diff --git a/harness/experiments/text-navigation-desktop-20260911/inputs1.zip b/harness/experiments/text-navigation-desktop-20260911/inputs1.zip
new file mode 100644
index 0000000000000000000000000000000000000000..33658afc52f15b469a7becb28bbe9111a33cbba2
GIT binary patch
literal 2073758
zcmV(*K;FMlO9KQH000080Gd@RUAA?CiV1T70G-kS015yA07GGNXKyZSZ)0o3y-jl?
zIg%~9?_ZHtvz%rQzCWJXHM?eS_4ao+HYRfb4kbG!QB6{nmHq4Q@gPM}!3auO73??D
z-6>L(6aXCV=K$`<|KrR5`L^DD>FWpnf`92{zj>tny3)hL&oBS`o}YM2dv0IK&oBQk
zZSOZ<|MnkW{=dC^^l5{Cp7#9I@a?|YetD#)7kc=@ZM|PV-GBYt@4VfuH&0*w*BAG<
zzy0@rKU4iq_xwNq^S38@WW4(umAm_gpU>ZZJNxKw>Z|!`&2N9(ZCC?Dne%db-InX!DM-FPL_SH1K~deXk@
zm}?8FI-gugl&a4mn@H5E3kDC{O{J)?=B!lIIXH^8P^9Q5SF-l|)w&OhchCDRJ+;l_
z7xmSCb%zCgs(e@Z;bC}n9O2#YRKq)1woTnU|Gcu$Pi?zdw-wd>`uFwz=V+e7SG}~s
zcSE`q