Files
ui/_docs/02_document/contracts/satellite-provider/tiles.md
T
Oleksandr Bezdieniezhnykh 20a39d3d8a [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 <cursoragent@cursor.com>
2026-05-12 03:45:44 +03:00

118 lines
6.6 KiB
Markdown

# 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=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) |