# Traceability Matrix Maps every acceptance criterion and every restriction in `_docs/00_problem/` to the test scenarios that verify it (this directory) and the expected-result row in `_docs/00_problem/input_data/expected_results/results_report.md` that provides the quantifiable observable. Quarantined tests are marked `[Q]` — they assert against a Phase B target feature or a Step 4 fix that has not landed; they activate the day the implementation ships. ## Acceptance Criteria Coverage | AC ID | Acceptance Criterion (short) | Tests | results_report rows | Coverage | |-------|------------------------------|-------|---------------------|----------| | AC-01 | `credentials:'include'` on every authenticated fetch | FT-P-01 [Q for bootstrap], FT-P-02, NFT-PERF-02, NFT-SEC-04, NFT-RES-01, NFT-RES-08 | 01, 02, 03 | Covered | | AC-02 | Bearer never written to client storage | NFT-SEC-01 | 04 | Covered | | AC-03 | Refresh cookie `Secure HttpOnly SameSite=Strict` | NFT-SEC-02, NFT-SEC-03 | 05, 06, 07 | Covered | | AC-04 | Numeric enums match suite spec | FT-P-04, FT-P-05, FT-P-06 | 14, 15, 16, 17, 18, 19 | Covered (`enum_spec_snapshot.json` committed — Phase 3 gate resolved) | | AC-05 | Annotation save endpoint + required body fields | FT-P-07, FT-P-08 | 22, 23 | Covered | | AC-06 | Selected-flight persistence path | FT-P-16, FT-P-17 | 32, 33 | Covered | | AC-07 | Bulk-validate works | FT-P-20, FT-P-21, NFT-PERF-07 | 36, 37 | Covered | | AC-08 | Live-GPS SSE per flight | FT-P-18, FT-P-19, NFT-PERF-04, NFT-PERF-05, NFT-RES-10, NFT-RES-LIM-06, NFT-RES-LIM-07 | 34, 35, 97 | Covered | | AC-09 | Annotation-status SSE during page lifetime | FT-P-09, FT-P-10, NFT-PERF-06, NFT-RES-LIM-05 | 24, 25, 97 | Covered | | AC-10 | Upload size cap 500 MB + UI error path | FT-N-06, NFT-RES-07, NFT-RES-LIM-02 | 38, 39 | Covered | | AC-11 | Initial JS bundle ≤ 2 MB | NFT-PERF-01, NFT-RES-LIM-01 | 40 | Covered (documentary — no CI gate today) | | AC-12 | i18n key parity en ↔ ua | FT-P-22, FT-P-23 | 45, 46 | Covered | | AC-13 | i18n detector + persistence | FT-P-24 [Q], FT-P-25 [Q] | 47, 48 | Covered (quarantined — Step 4 fix) | | AC-14 | Destructive actions require ConfirmDialog + alert() forbidden | FT-P-26, FT-P-27, FT-N-07, NFT-SEC-07, NFT-SEC-08 | 49, 50, 51 | Covered | | AC-15 | ConfirmDialog a11y | FT-P-28, FT-P-29, FT-N-08 | 52, 53, 54 | Covered | | AC-16 | Header flight dropdown a11y | FT-P-30, FT-P-31, FT-N-09 | 55, 56, 57 | Covered | | AC-17 | ProtectedRoute spinner a11y + timeout | FT-P-32, FT-P-33 [Q], NFT-RES-04 [Q] | 58, 59 | Covered (quarantined for timeout) | | AC-18 | Browser support — Chromium + Firefox latest 2 | FT-P-34, NFT-PERF-10 | 60, 98 | Covered (manual smoke, no automated gate today) | | AC-19 | Mobile / desktop breakpoint variants | FT-P-35, FT-P-36 | 61, 62 | Covered | | AC-20 | OpenWeatherMap key not in source | NFT-SEC-09 (all 3 steps active — source check un-quarantined on cycle 2 / 2026-05-12 by AZ-499) | 63 | Covered | | AC-21 | UserSettings panel-width persistence | FT-P-37 [Q], FT-P-38 [Q], NFT-PERF-08 [Q] | 64, 65 | Covered (quarantined) | | AC-22 | RBAC client-side route gates | FT-N-03 [Q], FT-N-04, FT-N-05 [Q], NFT-SEC-05 [Q], NFT-SEC-06 [Q], NFT-RES-08 | 08, 09, 10 | Covered (quarantined for `/admin` + `/settings` gates) | | AC-23 | Auth refresh transparency | FT-P-02, FT-P-03, NFT-PERF-02, NFT-RES-01 | 11, 12 | Covered | | AC-24 | SSE bearer-rotation reconnect | NFT-PERF-03 [Q], NFT-RES-02 [Q], NFT-RES-10 | 13, 97 | Covered (quarantined — Step 8 hardening) | | AC-25 | Detect endpoint correctness (sync + async) | FT-P-11, FT-P-12 [Q], FT-P-13 [Q] | 26, 27, 28 | Covered (async path quarantined — F7 target) | | AC-26 | Numeric input hygiene | FT-N-11 [Q], FT-N-12 [Q] | 66, 67 | Covered (quarantined — Step 4 fix) | | AC-27 | Save error surfacing in settings | FT-N-13 [Q], FT-N-14 [Q], NFT-PERF-09 [Q], NFT-RES-05 [Q], NFT-RES-06 [Q] | 68, 69 | Covered (quarantined — Step 4 fix) | | AC-28 | Annotation overlay time window `[-50, +150] ms` | FT-P-14, FT-P-15, FT-N-01, FT-N-02 | 29, 30, 31 | Covered | | AC-29 | `mediaType` is typed (no magic literals) | FT-N-15 | 20, 21 | Covered | | AC-30 | Class delete confirmation | FT-P-26, FT-N-07, NFT-SEC-08 | 49 | Covered (overlaps AC-14 row 49) | | AC-31 | `mission-planner/` not in production bundle | NFT-RES-LIM-04 | 41 | Covered | | AC-32 | CI image tag + OCI labels | NFT-RES-LIM-11, NFT-RES-LIM-12, NFT-RES-LIM-13 | 70, 71, 72 | Covered | | AC-33 | Production runtime `nginx:alpine` only | NFT-RES-LIM-03 | 42 | Covered | | AC-34 | nginx routes 9 services with prefix stripping | NFT-RES-LIM-09, NFT-RES-LIM-10 | 43, 44 | Covered | | AC-35 | Manual bbox draw on CanvasEditor | FT-P-39 | 73 | Covered | | AC-36 | 8-handle resize + Ctrl-multi-select + Ctrl-wheel zoom + Ctrl-drag pan | FT-P-40, FT-P-41, FT-P-42, FT-P-43 | 74, 75, 76, 77 | Covered | | AC-37 | Class picker — load + hotkey + click + fallback | FT-P-44, FT-P-45, FT-P-46, FT-P-47 | 78, 79, 80, 81 | Covered (pending backend ordering check — Phase 3 gate) | | AC-38 | PhotoMode switcher | FT-P-48, FT-P-49, FT-P-50 | 82, 83, 84 | Covered | | AC-39 | Tile-splitting endpoint + parser | FT-P-51 [Q], FT-P-52, FT-P-53, FT-N-10 | 85, 86, 87, 88 | Covered (split surface quarantined) | | AC-40 | Tile-zoom auto-zoom + indicator | FT-P-54 [Q], FT-P-55 [Q] | 89, 90 | Covered (quarantined — UX missing today) | | AC-N1 | No collaborative-edit semantics | NFT-SEC-14 | 91 | Covered | | AC-N2 | No in-browser ML | NFT-SEC-10 | 92 | Covered | | AC-N3 | No offline mode | NFT-RES-03, NFT-SEC-12 | 93 | Covered | | AC-N4 | No response-signature library | NFT-SEC-11 | 94 | Covered | | AC-N5 | Dropped legacy features (Sound Detections, Drone Maintenance) | NFT-SEC-13 | 95 | Covered | | AC-41 | Map tiles served by self-hosted `satellite-provider` via cookie auth; classic/satellite toggle removed (added cycle 2 / 2026-05-12, epic AZ-497, ticket AZ-498) | FT-P-56, FT-P-57, FT-P-58, FT-P-59, NFT-RES-11; STC-T1 (env-decl typecheck), STC-FP22 (i18n parity post-key removal), STC-ARCH-01 + STC-ARCH-02 (architecture gates stay green) | n/a — env-var plumbing + DOM observable + e2e contract; no `results_report.md` row required | Covered | | AC-42 | mission-planner OpenWeatherMap key + base URL externalized via Vite env vars; fail-soft on missing key; STC-SEC1C source-tree literal scan defends against re-introduction (added cycle 2 / 2026-05-12, epic AZ-497, ticket AZ-499) | FT-P-60, FT-N-16; NFT-SEC-09 step 3 (STC-SEC1C); STC-T1 (env-decl typecheck) | 63 (literal-key scan shares row 63 with AC-20) | Covered (manual deliverable AZ-499 AC-7 — old key revocation at OWM dashboard — tracked separately, not a test) | | AC-43 | mission-planner Google Geocode API key extracted to a new `services/GeocodeService.ts` module + externalized via Vite env var; fail-soft + console.warn on missing key; STC-SEC1D source-tree literal scan defends against re-introduction (added cycle 2 / 2026-05-12 from security audit `_docs/05_security/`, ticket AZ-501) | FT-P-61, FT-N-17; NFT-SEC-09b (STC-SEC1D); STC-T1 (env-decl typecheck) | n/a — env-var plumbing + console-warn assertion; no `results_report.md` row required | Covered (manual deliverable AZ-501 AC-6 — old key revocation at Google Cloud Console — tracked separately, not a test) | | AC-44 | Vite + PostCSS upgraded past CVE-2026-39363 / GHSA-p9ff-h696-f583 / GHSA-4w7w-66w2-5vf9 / GHSA-qx2v-qp2m-jg93 in both roots via `package.json` `overrides` flooring transitive resolutions to safe versions (added cycle 2 / 2026-05-12 from security audit, ticket AZ-502) | `bun audit` (zero advisories in both roots after `bun install`) | n/a — supply-chain hygiene; verified by audit tool exit code | Covered (CI gate `bun audit --severity high` in `.woodpecker/build-arm.yml` is a Phase B follow-up — see `_docs/05_security/infrastructure_review.md` F-INF-1) | ## Restrictions Coverage | RID | Restriction (short) | Tests / Mechanism | Coverage | |-----|---------------------|-------------------|----------| | H1 | ARM64-only production image | NFT-RES-LIM-03 (image base check); the build pipeline produces ARM64 only — meta-config | Covered | | H2 | Edge-device deployment target | NFT-PERF-10, NFT-RES-LIM-08 | Covered (documentary) | | H3 | No GPU expectation in UI image | environment.md (no GPU in test rig); NFT-SEC-10 (no ML libs) — together they verify the constraint | Covered (by composition) | | H4 | HTML5 video + canvas + EventSource on Chromium / Firefox latest 2 | FT-P-34 | Covered | | S1 | TypeScript strict mode | STC-S1: static read of `tsconfig.json` for `"strict": true` (planned static check — added by Phase 3 if accepted) | NOT COVERED — Phase 3 to add | | S2 | React 19 | STC-S2: `package.json` dep version pin | NOT COVERED — Phase 3 to add | | S3 | Vite 6 | STC-S3: `package.json` dep version pin | NOT COVERED — Phase 3 to add | | S4 | Bun 1.3.11 | STC-S4: `package.json` `packageManager` field + Dockerfile base image pin | NOT COVERED — Phase 3 to add | | S5 | Static-bundle output only (no Node in prod image) | NFT-RES-LIM-03 | Covered | | S6 | REST + SSE only (no WebSocket / GraphQL / gRPC-Web) | STC-S6: dep scan for `ws`, `socket.io`, `graphql`, `apollo`, `grpc-web` (planned) | NOT COVERED — Phase 3 to add | | S7 | Two React Contexts only (no Redux / Zustand / TanStack Query) | STC-S7: dep scan for `redux`, `zustand`, `@reduxjs/.*`, `@tanstack/.*` | NOT COVERED — Phase 3 to add | | S8 | Tailwind 4 + `az-*` design tokens | STC-S8: dep version pin + presence of token CSS vars | NOT COVERED — Phase 3 to add | | S9 | `leaflet@1.9.4` + `react-leaflet@5` + `leaflet-draw` + `leaflet-polylinedecorator` | STC-S9: `package.json` version pin set | NOT COVERED — Phase 3 to add | | S10 | `chart.js@4` + `react-chartjs-2@4` | STC-S10 | NOT COVERED — Phase 3 to add | | S11 | `@hello-pangea/dnd@18` | STC-S11 | NOT COVERED — Phase 3 to add | | S12 | `i18next` + `react-i18next` with EN + UA bundles only | FT-P-22, FT-P-23 + STC-S12 (no other locale bundle files) | Partially Covered | | S13 | No client-side persistence library | NFT-SEC-01 + STC-S13 (dep scan for `localforage`, `idb`, `dexie`) | Partially Covered | | S14 | No test framework configured today | META — these tests' very existence supersedes this restriction; resolved at Step 5 (Decompose Tests) per `acceptance_criteria.md` AC-14 / S14 note | N/A — meta | | E1 | Air-gap-friendly bundle | NFT-RES-03 (offline boot) + external-dep stubs in environment.md | Partially Covered | | E2 | nginx strips `/api//` per service | NFT-RES-LIM-09, NFT-RES-LIM-10 | Covered | | E3 | `Secure HttpOnly SameSite=Strict` refresh cookie | NFT-SEC-03 | Covered | | E4 | Vite dev proxy at `/api → http://localhost:8080` | dev-only; not testable in production runtime | NOT COVERED — meta-config (Phase 3 to confirm) | | E5 | `AZAION_REVISION` stamped at build | NFT-RES-LIM-13 | Covered | | E6 | OCI image labels | NFT-RES-LIM-12 | Covered | | E7 | Image registry + tag scheme | NFT-RES-LIM-11 | Covered | | E8 | Branch triggers (`dev` / `stage` / `main`) | STC-E8: parse `.woodpecker/build-arm.yml` triggers (planned) | NOT COVERED — Phase 3 to add | | E9 | `client_max_body_size 500M` | NFT-RES-LIM-02 | Covered | | E10 | OpenWeatherMap direct-from-browser today | NFT-SEC-09 (key check) + environment.md `owm-stub` (E2E isolation) | Covered | | O1 | Bilingual UI mandatory | FT-P-22, FT-P-23 | Covered | | O2 | Bearer never in localStorage / sessionStorage | NFT-SEC-01 | Covered | | O3 | `credentials:'include'` on every authenticated fetch | NFT-SEC-04 | Covered | | O4 | RBAC is server-enforced (UI must NOT trust `AuthUser.role`) | meta-design + FT-N-04 (unauth → /login regardless of any client claim); STC-O4 (no `if (user.role === 'admin')` gating sensitive data, only UI hide/show) | Partially Covered — Phase 3 may add the static gate-pattern lint | | O5 | Refresh cookie attributes | NFT-SEC-03 | Covered | | O6 | No hardcoded credentials | NFT-SEC-09 | Covered | | O7 | Spec is source of truth for numeric enums | FT-P-04, FT-P-05, FT-P-06 | Covered | | O8 | Persist what you type (panel widths) | FT-P-37 [Q], FT-P-38 [Q] | Covered (quarantined) | | O9 | Admin can edit existing detection classes (P12) | NOT COVERED — feature missing today (`acceptance_criteria.md` notes P12 violation; PATCH endpoint to re-introduce in Phase B) | NOT COVERED — Phase B target | | O10 | Destructive actions require ConfirmDialog | NFT-SEC-08, FT-P-26, FT-P-27, FT-N-07 | Covered | | O11 | No SSR / RSC | NFT-RES-LIM-03 (no Node in image) + STC-O11 (no `react-dom/server` import) | Partially Covered | | O12 | `mission-planner/` not compiled by production Vite build | NFT-RES-LIM-04 | Covered | | O13 | Bundle size budget ≤ ~2 MB gzipped initial JS | NFT-PERF-01, NFT-RES-LIM-01 | Covered (target — no CI gate today) | | O14 | No CI test step today | META — resolved at Step 5 (Decompose Tests) | N/A — meta | | O15 | No vuln scan / SBOM / image signing | NOT COVERED — Step 6 / security_approach surface; Phase B addition | NOT COVERED | ## Coverage Summary | Category | Total Items | Covered | Partially Covered | Not Covered | N/A (meta) | Coverage % (Covered+Partial) | |----------|-------------|---------|-------------------|-------------|-----------|--------------------| | Acceptance Criteria | 44 | 44 | 0 | 0 | 0 | 100% (cycle-2 deltas: AC-41, AC-42, AC-43, AC-44 added; AC-20 source check no longer quarantined) | | Anti-Criteria | 5 | 5 | 0 | 0 | 0 | 100% | | Restrictions | 41 | 17 | 8 | 13 | 3 | 61% | | **Total** | **90** | **66** | **8** | **13** | **3** | **82%** | Acceptance criterion coverage exceeds the 75 % template threshold. Restriction coverage is short of 75 % because most of the un-covered restrictions are dependency-version pins (S1-S11) for which a single static check pass (planned `STC-S*` family) would lift them to Covered without changing the SPA's observable behavior. ## Uncovered Items Analysis | Item | Reason Not Covered | Risk | Mitigation | |------|-------------------|------|-----------| | S1-S11 (TS strict, React/Vite/Bun version pins, Tailwind, Leaflet, Chart.js, DnD) | Dependency-version restrictions need a static `package.json` / `tsconfig.json` parser pass; not authored in this phase | Drift between pinned and installed versions (e.g., transitive resolution); accidental upgrade in a refactor breaking ADRs (ADR-001 …) | Phase 3 to confirm adding the `STC-S*` family; otherwise Step 4 / Step 8 may add them as part of the testability fix | | S12, S13 partials | `STC-S12` (no other locale bundles) and `STC-S13` (no IndexedDB / localForage / Dexie deps) need explicit dep scans | Low — currently aligned per `package.json`; risk only on dep additions | Phase 3 promotion to a single static dep-scan job (run alongside the dep-license lint) | | E4, E8 | Dev-only proxy and pipeline branch triggers are not consumer-observable from a production build | None today; future risk if the proxy diverges from the prod nginx | Document-only; Phase 3 confirms | | O4 partial (RBAC trust pattern) | No lint rule today; the design intent is captured but not asserted in code | UI accidentally gates sensitive data on a client claim | Phase 3 to add an `STC-O4` lint that bans `if (user.role === 'admin') { /* reveal data */ }` patterns; FT-N-04 already locks the unauth path | | O9 (admin can edit classes — P12) | The feature does not exist today (only add + delete); cannot test absence of a regression | Functionality missing relative to spec | Phase B feature cycle will add `PATCH /api/admin/classes/{id}` plus FT-P-XX in the cycle that ships it; AC-37 / AC-30 already enforce the surrounding contract | | O11 partial | NFT-RES-LIM-03 confirms no Node binary in the image; an explicit "no `react-dom/server` import" lint is missing | Could regress to SSR by accident on a refactor; the image base check would still pass | Phase 3 confirms `STC-O11` | | O15 (vuln scan / SBOM / signing) | Pipeline does not emit any of these today | Supply-chain risk | Addressed at Step 6 / `security_approach.md`; outside the test-spec scope | | AC-11, AC-18, AC-24, AC-40, AC-25 async path | Phase B / target features with quarantined tests | Tests do not gate today — risk = the feature ships without the assertion being un-quarantined | Phase 3 chooses: keep quarantined (gates the day the feature ships) OR downgrade to documentary; recommendation: keep quarantined | | AC-04 enum spec numeric values for `MediaStatus` / `Affiliation` / `CombatReadiness` | The exact spec values are not pinned in `enum_spec_snapshot.json` yet | Tests use symbolic comparison and may silently match a wrong UI state | Phase 3 to require population of the snapshot before AC-04 tests gate CI; recommendation: BLOCK Step 4 until the snapshot is committed | | AC-37 backend ordering | The class-hotkey contract requires `[0..N-1, 20..20+N-1, 40..40+N-1]` ordering from the suite; not yet verified | Hotkey test fails at integration; ambiguous responsibility | Phase 3 to surface; fix can land server-side or via a client-side resort, depending on the verification result | ## Quarantine List (running) The following 17 tests assert against a Phase B target or a Step 4 fix and are quarantined until the implementation lands. Phase 3 will decide their disposition. (Cycle 2 / 2026-05-12 update: NFT-SEC-09 source check REMOVED from this list — closed by AZ-499 + STC-SEC1C; new AC-41 / AC-42 tests added in this cycle are NOT quarantined.) | Test | Reason | Activates when | |------|--------|---------------| | FT-P-01 (bootstrap part) | Bootstrap refresh missing `credentials:'include'` per finding | Step 4 fix | | FT-P-12, FT-P-13 | Async video detect (F7) not wired | Phase B feature cycle | | FT-P-24, FT-P-25 | i18n detector + persistence missing | Step 4 fix | | FT-P-33 (timeout) | ProtectedRoute timeout missing | Step 4 fix | | FT-P-37, FT-P-38 | Panel-width writer missing (`useResizablePanel`) | Step 4 fix | | FT-P-51 | Split surface not on dataset page today | Phase B | | FT-P-54, FT-P-55 | Tile-zoom UX missing (finding #24) | Phase B | | FT-N-03, FT-N-05 | `/admin` and `/settings` role-gates missing | Step 4 / Step 8 | | FT-N-11, FT-N-12, FT-N-13, FT-N-14 | Settings form hygiene fixes missing | Step 4 | | NFT-PERF-03 / NFT-RES-02 | SSE refresh-rotation reconnect missing | Step 8 hardening | | NFT-PERF-08 / NFT-PERF-09 | Tied to FT-P-37 / FT-N-13 quarantines | per above | | NFT-SEC-05, NFT-SEC-06 | Tied to FT-N-03, FT-N-05 | per above | | NFT-RES-04 | Tied to FT-P-33 | per above | ## Phase 3 (Data Validation Gate) — Open Items to Resolve ### Resolved in Phase 3 1. ~~**`enum_spec_snapshot.json`** — populate from the suite spec before AC-04 tests gate CI.~~ → **Resolved:** snapshot committed at `_docs/00_problem/input_data/enum_spec_snapshot.json`. `verification_pending: true` markers remain for `CombatReadiness` and `MediaType` (numeric ordering inferred from schema member-listing); Step 4 .NET-service inspection lifts those. 2. ~~**NFT-RES-09** — no `results_report.md` row binding.~~ → **Resolved:** row 96 added (tainted-canvas fallback observable). 3. ~~**NFT-RES-10** — no `results_report.md` row binding.~~ → **Resolved:** row 97 added (SSE server-disconnect observable; ≤ 10 s indicator-or-reconnect; reconnect attempts ≤ 1 in window). 4. ~~**NFT-PERF-10** — FCP baseline has no `results_report.md` row.~~ → **Resolved:** row 98 added (`FCP ≤ 3 000 ms` on warm-cache navigation to `/flights`, headless Chromium, 2 vCPU / 4 GB edge profile). ### Still open (carry forward to Step 4 / runner) 5. **AC-37 backend ordering** — verify the `annotations/` service response shape and either confirm matches or schedule a fix on the appropriate side. 6. **STC family** — confirm adding the `STC-S*` / `STC-O4` / `STC-O11` static-check IDs to lift the restriction coverage above 75 %. 7. **Quarantine disposition** — accept the quarantine list above; decide whether quarantined tests gate CI today (recommended: no, but they are picked up automatically the day the feature lands). 8. **AC-04 UI enum drift in `src/types/index.ts`** — tests will FAIL until the Step 4 fix lands per `acceptance_criteria.md`; quarantine until Step 4 OR run them and use the failure as the gate to schedule Step 4. The drift list is pinned in `enum_spec_snapshot.json` → `ui_drift_summary` (5 enums). 9. **Parent-suite doc fixes (leftover)** — `../_docs/01_annotations.md` line 208 and `../_docs/09_dataset_explorer.md` line 165 show stale `affiliation: 2 // Hostile` examples; should be 20 per `00_database_schema.md`. Record as a leftover in `_docs/_process_leftovers/` when raised against the parent suite.