Security audit (5 phases) → reports under _docs/05_security/. AZ-501 (F-SAST-1, HIGH): Externalize hardcoded Google Geocode key from mission-planner/src/config.ts to VITE_GOOGLE_GEOCODE_KEY via new GeocodeService.ts; fail-soft warn when unset; STC-SEC1D static deny-list gate; +5 unit tests in tests/mission_planner_geocode.test.ts. AZ-502 (F-DEP-1, HIGH): Force vite>=6.4.2 and postcss>=8.5.10 via package.json overrides in both roots; clean reinstall clears all bun audit advisories. Test-spec sync (Step 12) + Update Docs (Step 13) deltas: AC-43, AC-44, NFT-SEC-09b, FT-P-61, FT-N-17, ripple log, batch_12 report. Pending user actions: revoke Google + OWM keys (AC-6 / AZ-499 AC-7). 229 PASS / 13 SKIP / 0 FAIL on static + fast suites. Co-authored-by: Cursor <cursoragent@cursor.com>
20 KiB
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/<service>/ 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
→ Resolved: snapshot committed atenum_spec_snapshot.json— populate from the suite spec before AC-04 tests gate CI._docs/00_problem/input_data/enum_spec_snapshot.json.verification_pending: truemarkers remain forCombatReadinessandMediaType(numeric ordering inferred from schema member-listing); Step 4 .NET-service inspection lifts those.NFT-RES-09 — no→ Resolved: row 96 added (tainted-canvas fallback observable).results_report.mdrow binding.NFT-RES-10 — no→ Resolved: row 97 added (SSE server-disconnect observable; ≤ 10 s indicator-or-reconnect; reconnect attempts ≤ 1 in window).results_report.mdrow binding.NFT-PERF-10 — FCP baseline has no→ Resolved: row 98 added (results_report.mdrow.FCP ≤ 3 000 mson warm-cache navigation to/flights, headless Chromium, 2 vCPU / 4 GB edge profile).
Still open (carry forward to Step 4 / runner)
- AC-37 backend ordering — verify the
annotations/service response shape and either confirm matches or schedule a fix on the appropriate side. - STC family — confirm adding the
STC-S*/STC-O4/STC-O11static-check IDs to lift the restriction coverage above 75 %. - 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).
- AC-04 UI enum drift in
src/types/index.ts— tests will FAIL until the Step 4 fix lands peracceptance_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 inenum_spec_snapshot.json→ui_drift_summary(5 enums). - Parent-suite doc fixes (leftover) —
../_docs/01_annotations.mdline 208 and../_docs/09_dataset_explorer.mdline 165 show staleaffiliation: 2 // Hostileexamples; should be 20 per00_database_schema.md. Record as a leftover in_docs/_process_leftovers/when raised against the parent suite.