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 <cursoragent@cursor.com>
6.6 KiB
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 <TileLayer> issues plain <img> 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=Laxin production;SameSite=Laxpermitted overhttp://localhostfor dev only.
- Cookie name:
- 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:5100will 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-ControlandETagheaders 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/requestendpoint (different contract, not consumed by the UI'sFlightMap). - 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) |