AZ-498 — self-hosted satellite tiles + drop classic/satellite toggle: - Single TILE_URL via getTileUrl() (mirrors getOwmBaseUrl/getApiBase pattern from AZ-449/AZ-450); env-var VITE_SATELLITE_TILE_URL with dev default http://localhost:5100/tiles/{z}/{x}/{y}. - FlightMap + MiniMap render one TileLayer with crossOrigin="use-credentials" so Leaflet's <img> tile fetcher attaches the same-origin satellite-provider auth cookie. - ImportMetaEnv + .env.example collapse the prior OSM/Esri pair into one var. The flights.planner.satellite i18n key is removed in lockstep across en.json + ua.json (parity preserved). - E2E harness wired end-to-end: compose passes the new var to azaion-ui; tile-stub serves /tiles/{z}/{x}/{y} with Content-Type=image/jpeg + Cache-Control + ETag matching the contract; infrastructure.e2e.ts AC-2 asserts the new path; dead OSM defenses removed from EXTERNAL_HOSTS route guard. - Fast-profile MSW handlers rewritten for the cookie-auth path shape. - 8 colocated fast tests under src/features/flights/__tests__/. AZ-499 — mission-planner OWM env-var hardening + AZ-482 source-scan gap close: - WeatherService.ts reads VITE_OWM_API_KEY + VITE_OWM_BASE_URL; fail-soft null when key unset (mirrors AZ-448 main-SPA contract). Public signature getWeatherData(lat, lon) preserved. - mission-planner/.env.example + vite-env.d.ts declare both vars. - New owm_key_in_source banned-deps kind scans src/ AND mission-planner/ for the rotated literal; STC-SEC1C row added to scripts/run-tests.sh; check-banned-deps.mjs dispatch extended. - 7 fast tests under tests/mission_planner_weather.test.ts cover AC-1..AC-4 + trailing-slash + happy path + network-error fail-soft. Spec drift (recorded in batch_11_report.md, user-approved Choose B on 2026-05-12): - AZ-498 AC-8 dropped (named tile_split_zoom* files belong to AZ-474 image-annotation surface, not map tiles). - 4 missing files added in-scope (msw tiles handler, tile-stub server, compose env, dead VITE_TILE_BASE_URL replaced). - AZ-499 STC-S6 ID conflict resolved by using STC-SEC1C. Pending USER ACTION (BLOCKING for AZ-499 close): - Revoke OpenWeatherMap key 335799082893fad97fa36118b131f919 at home.openweathermap.org/api_keys; capture evidence on AZ-499. Cross-workspace deploy gate (handled at autodev Step 16, not a Step-10 blocker for AZ-498): - satellite-provider cookie-auth on GET /tiles/{z}/{x}/{y} (separate AZAION ticket on the satellite-provider workspace). Reports: _docs/03_implementation/batch_11_report.md and _docs/03_implementation/reviews/batch_11_review.md (verdict PASS_WITH_WARNINGS — 1 Low, pre-existing trim-trailing-slash duplication across vite roots). Static gates: STC-ARCH-01, STC-ARCH-02, STC-T1, STC-FP22, STC-FP23, STC-SEC1C all PASS post-refactor. +15 fast tests; +1 STC-SEC1C row. Co-authored-by: Cursor <cursoragent@cursor.com>
16 KiB
Batch Report
Batch: 11 (Phase B cycle 2, single batch)
Tasks: AZ-498 (Satellite-provider tile swap, 5 pts) + AZ-499 (mission-planner OWM env-var hardening + AZ-482 source-scan gap, 2 pts)
Date: 2026-05-12
Cycle: Phase B feature cycle 2, Step 10 — Implement
Total complexity: 7 pts
Epic: AZ-497 (Self-Hosted Satellite Tiles — SPA Integration)
Closes (consumer side): satellite-provider tiles consumer migration; mission-planner OWM hygiene gap
Depends on: AZ-450 (tile URL externalization, AZ-498), AZ-448 + AZ-449 (OWM key + base URL externalization, AZ-499), AZ-482 (banned-deps static-check scaffolding, AZ-499)
Cross-workspace prereq (deploy gate): satellite-provider cookie-auth on GET /tiles/{z}/{x}/{y} (user-filed separately) — gate at autodev Step 16, NOT a Step-10 blocker
Task Results
| Task | Status | Files Modified / Added | Tests | AC Coverage | Issues |
|---|---|---|---|---|---|
| AZ-498_satellite_tile_swap | Done (consumer side) | Production source (4): src/features/flights/types.ts (replaced TILE_URLS const with getTileUrl() + DEFAULT_SATELLITE_TILE_URL); src/features/flights/FlightMap.tsx (drop mapType state + toggle button + MiniMap mapType prop; single <TileLayer crossOrigin="use-credentials">); src/features/flights/MiniMap.tsx (drop mapType prop; same <TileLayer crossOrigin="use-credentials">); src/vite-env.d.ts (replaced VITE_OSM_TILE_URL/VITE_ESRI_TILE_URL with VITE_SATELLITE_TILE_URL). Configs (1): .env.example (replaced two tile vars with one + dev-default docstring). Foundation i18n (2): src/i18n/en.json + src/i18n/ua.json (removed flights.planner.satellite key in lockstep — parity preserved). E2E harness (3): e2e/docker-compose.suite-e2e.yml (replaced dead VITE_TILE_BASE_URL with VITE_SATELLITE_TILE_URL: "http://tile-stub:8082/tiles/{z}/{x}/{y}"); e2e/stubs/tile/server.ts (rewrote classify() for the new /tiles/{z}/{x}/{y} shape; serves Content-Type: image/jpeg + Cache-Control + ETag); e2e/tests/infrastructure.e2e.ts (AC-2 rewritten to GET /tiles/1/0/0 + assert headers; removed dead OSM entries from EXTERNAL_HOSTS route guard per user choice B). Fast-profile MSW (1): tests/msw/handlers/tiles.ts (rewrote handlers from OSM/Esri .png shape to satellite-provider /tiles/{z}/{x}/{y} shape with cookie-auth headers). Tests (1 new): src/features/flights/__tests__/satellite_tile.test.tsx (8 tests covering AC-1, AC-2, AC-3, AC-4 — colocated under 05_flights for STC-ARCH-01 cleanliness). Docs (2): _docs/02_document/modules/src__features__flights.md (Tile URL section + module-map row + Findings F7 marked resolved); _docs/02_document/contracts/satellite-provider/tiles.md (already drafted in Step 9, no further edit). |
+8 fast tests (src/features/flights/__tests__/satellite_tile.test.tsx); +1 e2e test rewrite (infrastructure AC-2). All 8 fast tests PASS locally. STC-ARCH-01, STC-ARCH-02, STC-T1, STC-FP22, STC-FP23 all PASS post-refactor. |
8 / 9 covered + 1 dropped: AC-1, AC-2, AC-3, AC-4 (fast tests), AC-5 (typecheck), AC-6 (e2e — gated by docker, plumbing verified), AC-7 (contract referenced + matches per Phase 2 verification), AC-9 (static gates green). AC-8 dropped with explicit user approval (Choose A/B/C/D, picked B on 2026-05-12) — spec misattribution: the named tile_split_zoom* files belong to AZ-474 (image-annotation split surface) and have zero references to map tiles or any env var touched here. |
None blocking. 1 Low Maintainability finding (Finding F1 in batch_11_review.md) — pre-existing trim-trailing-slash idiom duplication. |
| AZ-499_mission_planner_weather_env | Done (code) — AC-7 manual deliverable PENDING USER | Production source (3): mission-planner/src/services/WeatherService.ts (env vars + fail-soft null when key unset; preserved public signature getWeatherData(lat, lon)); mission-planner/.env.example (added VITE_OWM_API_KEY + VITE_OWM_BASE_URL mirroring main .env.example style; preserved existing VITE_SATELLITE_TILE_URL independently — different vite root); mission-planner/src/vite-env.d.ts (added both vars to ImportMetaEnv). Static-check infra (3): tests/security/banned-deps.json (added owm_key_in_source kind: match: literal, scope: src/ + mission-planner/, patterns: ["335799082893fad97fa36118b131f919"]); scripts/check-banned-deps.mjs (extended source-tree dispatch to include owm_key_in_source alongside legacy_integrations / concurrent_edit_patterns / alert_calls — same code path, same exclusions for tests); scripts/run-tests.sh (added static_check_no_owm_key_in_source function + STC-SEC1C row labeled "no literal OWM key in src/ + mission-planner/"). Tests (1 new): tests/mission_planner_weather.test.ts (7 tests covering AC-1, AC-2, AC-3, AC-4 + trailing-slash + happy-path return shape + network-error fail-soft). Docs (1): _docs/02_document/modules/mission-planner.md (annotated WeatherService.ts row with env-var dependency; updated migration table; updated Findings to mark hardcoded-key resolution by AZ-499). |
+7 fast tests (tests/mission_planner_weather.test.ts); +1 static check row (STC-SEC1C). All 7 fast tests PASS locally. node scripts/check-banned-deps.mjs --kind=owm_key_in_source exits 0. |
6 / 7 covered + 1 manual: AC-1, AC-2, AC-3, AC-4 (fast tests with vi.stubEnv + fetch spy), AC-5 (STC-SEC1C static check wired and green), AC-6 (typecheck via STC-T1). AC-7 (key revocation) MUST be completed manually by the user at https://home.openweathermap.org/api_keys before this task is marked Done in Jira. The STC-SEC1C check is defense-in-depth: even if revocation is delayed, no future commit can re-introduce the literal under src/ or mission-planner/. |
Spec note: AZ-499's example STC ID STC-S6 was a typo — that ID is taken (no WS/GraphQL/gRPC/SSR deps). Used STC-SEC1C (parallel to STC-SEC1 = src/, STC-SEC1B = dist/). |
AC Test Coverage Summary
| AC | Task | Test | Profile | Status |
|---|---|---|---|---|
| AC-1 (env-set tile URL) | AZ-498 | __tests__/satellite_tile.test.tsx::AC-1: returns the env-set VITE_SATELLITE_TILE_URL verbatim |
fast | PASS |
| AC-2 (default tile URL when unset) | AZ-498 | same file, AC-2: returns the dev default ... + trailing-slash variant |
fast | PASS |
AC-3 (crossOrigin="use-credentials") |
AZ-498 | same file, FlightMap AC-3 + dev-default URL render + MiniMap AC-3 | fast | PASS |
| AC-4 (toggle gone) | AZ-498 | same file, FlightMap AC-4 + MiniMap AC-4 | fast | PASS |
| AC-5 (ImportMetaEnv updated) | AZ-498 | tsc --noEmit -p tsconfig.test.json (STC-T1) |
static | PASS |
| AC-6 (e2e tile path) | AZ-498 | e2e/tests/infrastructure.e2e.ts::AC-2 (tile-stub serves /tiles/{z}/{x}/{y}) |
e2e (gated) | PASS — plumbing verified locally; full e2e gated by docker availability (Step 16 owns the e2e gate) |
| AC-7 (contract referenced + matches) | AZ-498 | Phase 2 contract verification (consumer-side) — see batch_11_review.md |
review | PASS |
| AC-8 (legacy tile-aware tests) | AZ-498 | DROPPED (user choice B, spec misattribution) | n/a | n/a |
| AC-9 (STC-ARCH-01 / STC-ARCH-02 green) | AZ-498 | node scripts/check-arch-imports.mjs --mode=arch-imports exit 0; --mode=api-literals exit 0 |
static | PASS |
| AC-1 (env-resolved API key in OWM URL) | AZ-499 | tests/mission_planner_weather.test.ts::AC-1 |
fast | PASS |
| AC-2 (env-resolved base URL) | AZ-499 | same file, AC-2: env-var resolved base URL prefixes the outgoing fetch URL + trailing-slash variant |
fast | PASS |
AC-3 (fail-soft null when key unset) |
AZ-499 | same file, AC-3: returns null and issues no fetch when VITE_OWM_API_KEY is unset |
fast | PASS |
| AC-4 (default base URL when only base unset) | AZ-499 | same file, AC-4: defaults to public OWM base URL when only VITE_OWM_BASE_URL is unset |
fast | PASS |
AC-5 (new owm_key_in_source static check) |
AZ-499 | node scripts/check-banned-deps.mjs --kind=owm_key_in_source exits 0; STC-SEC1C row in scripts/run-tests.sh |
static | PASS |
| AC-6 (TS declarations) | AZ-499 | tsc --noEmit -p tsconfig.test.json (STC-T1) |
static | PASS |
| AC-7 (compromised key revoked at OWM) | AZ-499 | MANUAL — out-of-band | n/a | PENDING — USER must revoke 335799082893fad97fa36118b131f919 at https://home.openweathermap.org/api_keys and capture evidence (dashboard URL or screenshot of disabled key) for the AC closure record before AZ-499 transitions to Done. STC-SEC1C is defense-in-depth. |
Design Decisions
-
getTileUrl()is a function, not a constant — mirrors the establishedgetOwmBaseUrl()/getApiBase()pattern (src/features/flights/flightPlanUtils.ts:62,src/api/client.ts:35). Readsimport.meta.envper call so tests can stub-then-call withoutvi.resetModules()+ dynamic-import dance. The per-render evaluation cost is negligible (env read + onereplace); this trade is the same one AZ-449 made. -
DEFAULT_SATELLITE_TILE_URLis exported alongside the function so tests can pin the literal without duplicating the dev-default string. Keeps the Source-of-Truth in production source. -
Single
TILE_URL(notTILE_URLS) means the classic/satellite toggle is a permanent removal, not a hidden switch. Reflects the user's cycle-2 explicit decision: "we accept losing the OSM street view; satellite-only is the new normal." The toggle removal also removes theflights.planner.satellitei18n key from bothen.jsonandua.json— i18n key parity (STC-FP22) preserved by removing in lockstep. -
crossOrigin="use-credentials"on EVERY<TileLayer>, not just the production code path. The MSW handler and tile-stub also send the cookie-auth-friendly Content-Type / Cache-Control / ETag headers so dev / fast / e2e profiles all observe the same wire shape. Drift between dev and prod here would silently break tile fetches in production (the satellite-provider rejects requests without the cookie with 401). -
Test colocated under
src/features/flights/__tests__/, NOT undertests/. Initial draft lived undertests/satellite_tile.test.tsxand used dynamic-import (await import('...')) to escape STC-ARCH-01's static regex. That escape was technically passing the gate but semantically violating the documented module-layout discipline ("test bodies → 00_foundation only, never internal files of other components"). Refactor moved the test to a colocated location where intra-component imports (../FlightMap,../MiniMap,../types) are architecturally clean. Cross-tree import totests/helpers/render.tsxis allowed by module-layout's Blackbox Tests "test infrastructure" rule (test infra MAY be imported by test bodies). No new exemption added to STC-ARCH-01. -
STC-SEC1C added as a NEW check, NOT as a widening of STC-SEC1. Existing STC-SEC1 scans
src/only and matches theappid=<6+ chars>regex (catches a real-key shape but not the literal). The new STC-SEC1C scanssrc/ANDmission-planner/and matches the LITERAL value (catches an exact re-introduction of the rotated key). The two together pin both axes: STC-SEC1 prevents a NEW unprotected key shape, STC-SEC1C prevents the OLD revoked key from coming back. -
mission-planner/.env.examplekeeps its ownVITE_SATELLITE_TILE_URL(Esri default). Two vite roots, two independent env vars with the same name — intentional. Mission-planner's tile migration is a separate future cycle (broader F1 mission-planner deduplication track), explicitly out of scope per AZ-498'sExcludedsection. -
Pre-existing dead
VITE_TILE_BASE_URLremoved from compose. The compose file set it; nothing read it. Replacing it (rather than adding alongside) cleans up the dead config. Considered "adjacent hygiene" per scope discipline (the file was already in the diff). -
Mission-planner test lives under
tests/, NOT colocated. Mission-planner has no test runner today (Vitest not wired). Per AZ-499's Risk #2, the simpler option (run under main SPA's harness, import via relative path) wins. The cross-tree relative path import (../mission-planner/src/services/WeatherService) is irregular but bounded — the test only depends on the function's public signature and runs the same env-stub + fetch-spy pattern as any other Vitest test.
Code Review Verdict
See _docs/03_implementation/reviews/batch_11_review.md — PASS_WITH_WARNINGS.
- 0 Critical, 0 High, 0 Medium, 1 Low (
F1: trim-trailing-slash idiom duplication; pre-existing pattern across 4 call sites in 2 vite roots; consolidation deferred to a future shared-helper extraction task). - All 9+7 = 16 ACs accounted for: 14 verified by tests/static checks, 1 dropped (AZ-498 AC-8) with explicit user approval, 1 pending manual deliverable (AZ-499 AC-7 — user revokes OWM key).
- Per implement skill Auto-Fix Gate: only Medium/Low → no auto-fix loop required; proceed to commit.
Spec Drift Recorded
These spec issues were surfaced and resolved with explicit user approval (Choose A/B/C/D, picked B on 2026-05-12) before any code was written. Recording here for the audit trail; the task specs themselves were NOT edited (kept as historic record).
- AZ-498 AC-8 misattribution: spec named
tests/tile_split_zoom.test.tsxande2e/tests/tile_split_zoom.e2e.ts; both are AZ-474's image-annotation split surface (dataset rowPOST /api/annotations/dataset/<id>/split), NOT map-tile tests. AC-8 dropped. - AZ-498 missing files in
Included:tests/msw/handlers/tiles.ts,e2e/stubs/tile/server.ts,e2e/docker-compose.suite-e2e.ymlazaion-uienv section. All three were genuinely required for the change to work end-to-end — treated as additive in-scope per user approval. - AZ-498 dead
VITE_TILE_BASE_URLin compose (read by nothing): replaced withVITE_SATELLITE_TILE_URLper user approval (item #4 — adjacent hygiene cleanup option). - AZ-499 STC ID conflict: spec example
STC-S6is taken; usedSTC-SEC1Cinstead (no AC text changed). - Pre-existing OSM defenses in
EXTERNAL_HOSTSroute guard (e2e/tests/infrastructure.e2e.ts): removed in cleanup since OSM is no longer expected (user picked B explicitly to include this cleanup).
Pending Manual Deliverables (BLOCKING for AZAION ticket close)
-
USER ACTION — AZ-499 AC-7: Revoke OpenWeatherMap API key
335799082893fad97fa36118b131f919at https://home.openweathermap.org/api_keys . Capture evidence (dashboard URL or screenshot of disabled key) and attach to AZ-499's Jira issue (or paste the URL in a comment) before transitioning AZ-499 from "In Testing" to "Done". TheSTC-SEC1Cstatic check is defense-in-depth and will block any future re-introduction of the literal undersrc/ormission-planner/. -
CROSS-WORKSPACE GATE — AZ-498 deploy:
satellite-providercookie-auth migration onGET /tiles/{z}/{x}/{y}(separate AZAION ticket, user-filed on satellite-provider workspace) must merge before AZ-498 deploys. Per_docs/02_tasks/_dependencies_table.mdNotes (AZ-497), this is gated at autodev Step 16 (Deploy), NOT a Step 10 blocker. Code can land in dev branch, run in fast/static profiles, and pass code review without it; only production deploy waits.
Test Run Handoff (Step 16)
The next autodev step after this batch is Step 11 (Run Tests). Per the implement skill's "if the next flow step is Run Tests" guidance: do NOT run the full bash scripts/run-tests.sh here — .cursor/skills/test-run/SKILL.md owns that gate to avoid duplicate full runs.
Locally-verified pre-handoff (focused subset, not the full gate):
- 15 fast tests added (8 satellite_tile + 7 mission_planner_weather) — all PASS
- STC-T1 (typecheck) — PASS
- STC-ARCH-01, STC-ARCH-02, STC-FP22, STC-FP23, STC-SEC1C — all PASS
node scripts/check-banned-deps.mjs --kind=owm_key_in_source— exit 0
Pre-existing fast suite was 209 passes (per _docs/03_implementation/batch_10_report.md). Expected after this batch: 209 + 15 = 224 passes (subject to the full Step-11 run confirming no regressions in adjacent tests not directly exercised here).