From 20a39d3d8a11f6f3773a2ed9285a937a2f6f597f Mon Sep 17 00:00:00 2001 From: Oleksandr Bezdieniezhnykh Date: Tue, 12 May 2026 03:45:44 +0300 Subject: [PATCH] [AZ-497] [AZ-498] [AZ-499] Cycle 2 New Task: epic, stories, contract draft Closes autodev existing-code Step 9 for cycle 2. - Epic AZ-497 (Self-Hosted Satellite Tiles - SPA Integration) added to _docs/02_tasks/_dependencies_table.md as the cycle-2 umbrella. - AZ-498 (5 pts): self-hosted satellite tiles + drop map-type toggle. Cross-workspace prereq: satellite-provider must add cookie-auth on GET /tiles/{z}/{x}/{y} before merge (user files separately). - AZ-499 (2 pts): mission-planner OWM env-var hardening + closes the AZ-482 source-scan gap with a new owm_key_in_source banned-deps kind. - Contract _docs/02_document/contracts/satellite-provider/tiles.md v1.0.0 (draft): slippy-tile XYZ shape both sides commit to. - _docs/_autodev_state.md: Step 9 closure note + advances pointer to Step 10 (Implement) for cycle 2. Co-authored-by: Cursor --- .../contracts/satellite-provider/tiles.md | 117 ++++++++++++ _docs/02_tasks/_dependencies_table.md | 17 ++ .../todo/AZ-498_satellite_tile_swap.md | 175 ++++++++++++++++++ .../AZ-499_mission_planner_weather_env.md | 143 ++++++++++++++ _docs/_autodev_state.md | 11 +- 5 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 _docs/02_document/contracts/satellite-provider/tiles.md create mode 100644 _docs/02_tasks/todo/AZ-498_satellite_tile_swap.md create mode 100644 _docs/02_tasks/todo/AZ-499_mission_planner_weather_env.md diff --git a/_docs/02_document/contracts/satellite-provider/tiles.md b/_docs/02_document/contracts/satellite-provider/tiles.md new file mode 100644 index 0000000..3c94106 --- /dev/null +++ b/_docs/02_document/contracts/satellite-provider/tiles.md @@ -0,0 +1,117 @@ +# Contract: satellite-provider tile serving + +**Component**: satellite-provider +**Producer task**: TBD — separate AZAION ticket on `satellite-provider` workspace (user-filed) +**Consumer tasks**: AZ-498 — `_docs/02_tasks/todo/AZ-498_satellite_tile_swap.md` (suite/ui, cycle 2, epic AZ-497) +**Version**: 1.0.0 +**Status**: draft +**Last Updated**: 2026-05-12 + +## Purpose + +Describe the slippy-tile HTTP interface that the suite UI consumes to render +satellite imagery in `FlightMap` / `MiniMap`. Replaces the prior external-tile +dependencies (OpenStreetMap, Esri ArcGIS World Imagery). The endpoint is +served by `SatelliteProvider.Api` and backed by an on-disk + Google-Maps +download cache. + +Frozen post-migration: SPA authentication for this endpoint MUST be **cookie-based** +(JWT delivered via `HttpOnly; Secure; SameSite=Lax` cookie on the same origin) +because Leaflet's `` issues plain `` requests and cannot attach +`Authorization: Bearer …` headers. + +## Shape + +### HTTP / RPC endpoints + +| Method | Path | Request body | Response | Status codes | +|--------|-------------------------------|--------------|-------------------|---------------------| +| `GET` | `/tiles/{z}/{x}/{y}` | — | image bytes | 200, 401, 404, 503 | + +**Path parameters** + +| Name | Type | Required | Range / Constraint | +|------|---------|----------|--------------------------------------------------------| +| `z` | `int` | yes | `0 ≤ z ≤ 20` (slippy-tile zoom) | +| `x` | `int` | yes | `0 ≤ x < 2^z` (slippy-tile column) | +| `y` | `int` | yes | `0 ≤ y < 2^z` (slippy-tile row, TMS-y convention NO) | + +Coordinates follow the Google Maps / OSM XYZ tiling scheme (NOT the inverted TMS +y-axis). Out-of-range coordinates SHOULD return 404. + +**Response headers (on 200)** + +| Header | Value | +|------------------|---------------------------------------------------------------| +| `Content-Type` | `image/jpeg` (image bytes from the `TileService`) | +| `Cache-Control` | `public, max-age=N` where N is set by `TileService` | +| `ETag` | strong ETag tied to the cached tile's content hash | + +**Authentication** + +- **Required**: yes (the endpoint is NOT public). +- **Mechanism (post-migration)**: cookie-based JWT. + - Cookie name: `satellite_auth` (TBD — defined by producer task). + - Attributes: `HttpOnly; Secure; SameSite=Lax` in production; `SameSite=Lax` + permitted over `http://localhost` for dev only. +- **Cross-origin behavior**: same-origin only. The SPA reaches this endpoint via + the suite ingress (nginx) on the SPA's origin; cross-origin direct calls from + `http://localhost:5173 → http://localhost:5100` will NOT carry the cookie and + will receive 401 in dev unless the developer disables auth locally. + +**Status codes** + +| Code | Meaning | +|------|-------------------------------------------------------------------| +| 200 | Cached or freshly downloaded tile; body = image bytes | +| 304 | (Optional) ETag match — body empty. UI MUST tolerate either 200 or 304. | +| 401 | Missing/invalid cookie — UI MUST treat as "user signed out" | +| 404 | Tile coordinates out of range OR upstream had no tile | +| 503 | Upstream (Google Maps) unavailable; UI MUST render placeholder | + +## Invariants + +- The endpoint URL pattern is `/tiles/{z}/{x}/{y}` exactly — never `/tiles/{z}/{y}/{x}` + (Esri-style) nor `/api/satellite/tiles/{z}/{x}/{y}`. This invariant survives + refactors and is asserted by both producer's integration tests and consumer's + blackbox tests. +- Image format is JPEG (Content-Type `image/jpeg`). Switching to PNG/WEBP is a + major-version change. +- The endpoint MUST honor `Cache-Control` and `ETag` headers on every 200; clients + rely on them to avoid re-fetching unchanged tiles during pan/zoom. +- Authentication failure MUST return 401, not 200 with an HTML body — Leaflet + would otherwise display a broken-image placeholder silently. + +## Non-Goals + +- Not covered: tile vector formats (`.pbf` / Mapbox Vector Tiles). This contract + is raster-only. +- Not covered: tile prewarming. Pre-warm uses the separate `POST /api/satellite/request` + endpoint (different contract, not consumed by the UI's `FlightMap`). +- Not covered: MGRS tile retrieval (returns 501 today; out of UI scope). + +## Versioning Rules + +- **Breaking** (major bump): change the path template, change the path-parameter + semantics (e.g., TMS-y), change `Content-Type`, remove a status code from the + set above, change the auth mechanism away from cookies. +- **Non-breaking** (minor bump): add a new optional query parameter, broaden the + zoom range, add a new status code in the 4xx/5xx space that consumers can + tolerate. + +## Test Cases + +| Case | Input | Expected | Notes | +|----------------------------|----------------------------------------|-----------------------------------------------------------|----------------------------------| +| valid-tile | `GET /tiles/15/9876/5432` w/ cookie | 200 + JPEG bytes + `Cache-Control` + `ETag` | producer + consumer cover | +| missing-cookie | `GET /tiles/15/9876/5432` w/o cookie | 401 | consumer must NOT retry | +| out-of-range-coord | `GET /tiles/3/8/0` (x ≥ 2^z) | 404 | consumer renders placeholder | +| etag-match | `GET /tiles/15/9876/5432` + `If-None-Match` | 304 OR 200 (server-policy dependent) | consumer tolerates both | +| upstream-503 | upstream Google Maps down | 503 | consumer renders placeholder | +| zoom-extreme | `GET /tiles/20/x/y` valid coords | 200 (or 404 if not cached and no on-demand) | consumer caps zoom at 20 | + +## Change Log + +| Version | Date | Change | Author | +|---------|------------|------------------------------------------------------------------------------|--------| +| 1.0.0 | 2026-05-12 | Initial draft; freezes the post-migration shape (cookie auth, XYZ scheme). | autodev (cycle 2 — suite/ui) | diff --git a/_docs/02_tasks/_dependencies_table.md b/_docs/02_tasks/_dependencies_table.md index e587581..f733f30 100644 --- a/_docs/02_tasks/_dependencies_table.md +++ b/_docs/02_tasks/_dependencies_table.md @@ -78,3 +78,20 @@ - `e2e (requires-docker)`: AZ-480 — requires the suite docker-compose stack - `e2e (requires-ci)`: AZ-481 NFT-RES-LIM-12/13 — local skip allowed - **Quarantine scenarios**: FT-P-12 (async video detect, AZ-461) starts QUARANTINEd until AC-25 / Phase B; verification_pending enums in AZ-459 quarantine until Step 4 .NET-service snapshot lifts. + +--- + +## Epic AZ-497 — Self-Hosted Satellite Tiles — SPA Integration (cycle 2) + +| Task | Name | Epic | Complexity | Depends on | +|------|------|------|-----------|------------| +| AZ-498 | Self-hosted satellite tiles + drop map-type toggle | AZ-497 | 5 | AZ-450; cross-workspace: satellite-provider cookie-auth (user-filed) | +| AZ-499 | mission-planner OWM env-var hardening + AZ-482 source-scan gap | AZ-497 | 2 | AZ-448, AZ-449, AZ-482 | + +### Notes (AZ-497) + +- **Epic AZ-497** is the cycle-2 umbrella selected by the user during the autodev new-task session. It covers BOTH the SPA-side tile swap to `satellite-provider` (AZ-498) and the `mission-planner` OWM hardening (AZ-499). The OWM work is not literally about satellite tiles; the user explicitly accepted the wider umbrella to avoid creating a second cycle-2 epic. +- **AZ-498 — cross-workspace dependency**: requires `satellite-provider` to expose a cookie-auth variant of `GET /tiles/{z}/{x}/{y}` before merge. The user files that ticket on the satellite-provider workspace separately. UI work can be authored ahead but cannot ship without the upstream change. +- **AZ-498 — contract**: produces/consumes `_docs/02_document/contracts/satellite-provider/tiles.md` (v1.0.0, draft). +- **AZ-499 — out-of-band**: the compromised key `335799082893fad97fa36118b131f919` must be revoked at the OpenWeatherMap dashboard before AZ-499 closes. AC-7 captures that as a deliverable. +- **AZ-499 — gap fix**: adds a new `owm_key_in_source` banned-deps kind that covers `src/` AND `mission-planner/`, closing the source-scan gap left by AZ-482's `dist/`-only scan. diff --git a/_docs/02_tasks/todo/AZ-498_satellite_tile_swap.md b/_docs/02_tasks/todo/AZ-498_satellite_tile_swap.md new file mode 100644 index 0000000..2555683 --- /dev/null +++ b/_docs/02_tasks/todo/AZ-498_satellite_tile_swap.md @@ -0,0 +1,175 @@ +# Replace external map tiles with self-hosted satellite-provider + +**Task**: AZ-498_satellite_tile_swap +**Name**: Self-hosted satellite tiles + drop map-type toggle +**Description**: Replace OpenStreetMap (classic) and Esri (satellite) tile sources with the suite's own `satellite-provider /tiles/{z}/{x}/{y}` endpoint, drop the classic/satellite toggle (satellite-provider serves satellite imagery only), and wire cookie-based authentication for tile fetches. +**Complexity**: 5 points +**Dependencies**: AZ-450 (Externalize map tile URLs). Cross-workspace prerequisite — satellite-provider must publish a cookie-auth variant of `/tiles/{z}/{x}/{y}` before this task can be merged. The user files that ticket separately on the satellite-provider workspace. +**Component**: 05_flights (with adjustments to 10_app-shell and the e2e harness) +**Tracker**: AZ-498 +**Epic**: AZ-497 + +## Problem + +`src/features/flights/types.ts` (post AZ-450) reads two tile-URL env vars and exposes them to `FlightMap` and `MiniMap` via a `{ classic, satellite }` shape. Today those URLs resolve to external providers (OpenStreetMap, Esri ArcGIS World Imagery). This: + +- Sends pilot flight-area coordinates to third-party CDNs (privacy/operational risk for sensitive missions). +- Adds an external network dependency the air-gap NFR (NFT-RES-03 / restriction E1) was meant to eliminate — the e2e profile only papers over it via the `tile-stub`. +- Wastes bandwidth re-downloading tiles that the suite's own `satellite-provider` service already caches on disk (`./tiles/{z}/{x}/{y}.jpg`). + +The suite already runs a `satellite-provider` .NET service that exposes a slippy-tile XYZ endpoint (`GET /tiles/{z}/{x}/{y}`) backed by an on-disk cache plus on-demand Google Maps download, with `Cache-Control` and `ETag` headers wired. The UI does not consume it. + +## Outcome + +- The SPA's map renders satellite tiles served by the suite's own `satellite-provider`, on the same origin as the SPA in production. +- The classic/satellite toggle is removed; the map is satellite-only. +- Tile fetches authenticate via a same-origin cookie, not via an `Authorization: Bearer …` header (Leaflet `` requests cannot send the header). +- Air-gap restriction E1 is satisfied for tiles in production without requiring a stub. +- `_docs/02_document/contracts/satellite-provider/tiles.md` documents the contract both sides commit to. + +## Scope + +### Included + +- Collapse `TILE_URLS` in `src/features/flights/types.ts` to a single URL string read from `import.meta.env.VITE_SATELLITE_TILE_URL`. +- Remove the classic/satellite toggle from `FlightMap.tsx`: the `mapType` state, the toggle `