mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 11:41:12 +00:00
[AZ-838] SatelliteProviderRouteClient + seed_route.py CLI (E-AZ-835 C2)
ci/woodpecker/push/02-build-push Pipeline failed
ci/woodpecker/push/02-build-push Pipeline failed
Operator-side HTTP client + CLI that takes a RouteSpec from AZ-836 and onboards it via satellite-provider's POST /api/satellite/route: pre-emptive AZ-809 validation, request submission, polling until mapsReady, and POST /api/satellite/tiles/inventory verify. Lives in c11_tile_manager (shared parent-suite HTTP/JWT plumbing, shared BUILD_C11_TILE_MANAGER gate); error hierarchy split off SatelliteProviderRouteError to keep the tile path and route path independent. 30 unit tests + 1 RUN_E2E-gated integration test. Pre-emptive validator tracks the actual AZ-809 server bounds (points [2,500], zoom [0,22]) instead of the AZ-838 spec's narrower client-only bounds; flagged as F1 in batch_107_cycle3_report.md for user decision (accept-and-update-spec / revert-to-spec). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
# SatelliteProviderRouteClient + seed_route.py CLI
|
||||
|
||||
**Task**: AZ-838_satellite_provider_route_client
|
||||
**Name**: SatelliteProviderRouteClient + seed_route.py CLI: POST tlog-derived route to satellite-provider (AZ-835 C2)
|
||||
**Description**: Second building block of Epic AZ-835. Consumer-side HTTP client + CLI wrapper that takes a `RouteSpec` (from AZ-836 / C1) and registers it with satellite-provider's `POST /api/satellite/route` endpoint, polls until `mapsReady=true`, and returns the inventory size for downstream consumption.
|
||||
**Complexity**: 3 SP
|
||||
**Dependencies**: AZ-836 (C1, RouteSpec dataclass + extractor — hard code dep); AZ-777 Phase 1 (existing satellite-provider HTTP plumbing patterns + JWT handling — done); AZ-809 (Route API validation — SOFT prereq, client pre-emptively validates so it's correct without it); AZ-835 (parent Epic)
|
||||
**Component**: new `src/gps_denied_onboard/satellite_provider/route_client.py` + new CLI `tests/fixtures/derkachi_c6/seed_route.py`
|
||||
**Tracker**: AZ-838 (https://denyspopov.atlassian.net/browse/AZ-838)
|
||||
**Parent Epic**: AZ-835
|
||||
|
||||
Jira AZ-838 is the authoritative spec; this file is the in-workspace mirror.
|
||||
|
||||
## Public surface
|
||||
|
||||
```python
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from gps_denied_onboard.replay_input.tlog_route import RouteSpec # AZ-836
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RouteSeedResult:
|
||||
route_id: uuid.UUID
|
||||
terminal_status: str
|
||||
maps_ready: bool
|
||||
tile_count: int
|
||||
elapsed_ms: int
|
||||
submitted_payload_sha256: str
|
||||
|
||||
class SatelliteProviderRouteError(Exception): ...
|
||||
class RouteValidationError(SatelliteProviderRouteError): ... # 4xx + ProblemDetails
|
||||
class RouteTransientError(SatelliteProviderRouteError): ... # 5xx / network / timeout
|
||||
class RouteTerminalFailureError(SatelliteProviderRouteError): ... # mapsReady never reached
|
||||
|
||||
class SatelliteProviderRouteClient:
|
||||
def __init__(self, base_url: str, jwt: str, *, tls_insecure: bool = False,
|
||||
request_timeout_s: float = 30.0, poll_interval_s: float = 5.0,
|
||||
poll_max_attempts: int = 60): ...
|
||||
def seed_route(self, spec: RouteSpec, *, name: str | None = None) -> RouteSeedResult: ...
|
||||
```
|
||||
|
||||
## Wire shape
|
||||
|
||||
No formal Route API contract doc exists in `../satellite-provider/_docs/02_document/contracts/api/` as of 2026-05-22. DTOs are the source of truth:
|
||||
|
||||
- `../satellite-provider/SatelliteProvider.Common/DTO/CreateRouteRequest.cs` (top-level)
|
||||
- `../satellite-provider/SatelliteProvider.Common/DTO/RoutePoint.cs` (`[JsonPropertyName("lat")] Latitude`, `[JsonPropertyName("lon")] Longitude` — input/output naming asymmetry flagged in AZ-809 AC-10; consume `lat`/`lon` in the JSON)
|
||||
- `../satellite-provider/SatelliteProvider.Common/DTO/GeoPoint.cs` (nested geofence point)
|
||||
|
||||
Probe 2026-05-22: 2-point route + `requestMaps=true` completes end-to-end in ~15 s.
|
||||
|
||||
## Behaviour
|
||||
|
||||
- **Pre-emptive validation** against AZ-809 rules — surface as `RouteValidationError` BEFORE HTTP POST:
|
||||
- `points` non-empty AND `len(points) <= 100`
|
||||
- `id` non-zero Guid
|
||||
- `regionSizeMeters > 0` AND `<= 10000`
|
||||
- `zoomLevel` in 15..18 (per AZ-777 Phase 2 bbox config)
|
||||
- Each point's `lat` in -90..90, `lon` in -180..180
|
||||
- **Submit** `POST /api/satellite/route` with `requestMaps=true`, `createTilesZip=false`.
|
||||
- **Poll** `GET /api/satellite/route/{id}` every `poll_interval_s` up to `poll_max_attempts` until `mapsReady=true` OR terminal failure. Log cadence at INFO.
|
||||
- **Return** `RouteSeedResult`; `tile_count` from a final `POST /api/satellite/tiles/inventory` enumerating the route's tile coverage (computed locally from waypoints + `regionSizeMeters`).
|
||||
- **Raise** `RouteTerminalFailureError` on terminal failure (`.detail` = SP response JSON).
|
||||
- **Raise** `RouteTransientError` on 5xx / network / timeout (`__cause__` = underlying `httpx` exception).
|
||||
- **Raise** `RouteValidationError` on 4xx; parse RFC 7807 `errors` dict into `field_errors`.
|
||||
|
||||
## CLI (`tests/fixtures/derkachi_c6/seed_route.py`)
|
||||
|
||||
Mirrors `seed_region.py` (AZ-777 Phase 2):
|
||||
|
||||
- Env: `SATELLITE_PROVIDER_URL`, `SATELLITE_PROVIDER_API_KEY`, `SATELLITE_PROVIDER_TLS_INSECURE`, optional `--auto-mint-jwt` (uses `scripts/mint_dev_jwt.py`)
|
||||
- Required: `--tlog <path>` (delegates to AZ-836's `extract_route_from_tlog`)
|
||||
- Optional: `--max-waypoints` (10), `--region-size-meters` (500), `--name`, `--output-summary <path>`, `--dry-run`
|
||||
- Exit codes: 0 success, 71 config malformed, 72 missing env, 73 SP unreachable, 74 4xx, 75 5xx / terminal failure, 76 inventory verification mismatch
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
| # | Criterion |
|
||||
|---|-----------|
|
||||
| AC-1 | POSTs wire shape exactly per `CreateRouteRequest.cs` + `RoutePoint.cs` + `GeoPoint.cs` |
|
||||
| AC-2 | Polls `GET /api/satellite/route/{id}` until `mapsReady=true` OR terminal failure; respects `poll_max_attempts` + `poll_interval_s` |
|
||||
| AC-3 | 4xx + RFC 7807 ProblemDetails → `RouteValidationError`; `field_errors` populated from `errors` dict |
|
||||
| AC-4 | 5xx / network / timeout → `RouteTransientError`; `__cause__` = underlying `httpx` exc |
|
||||
| AC-5 | Terminal failure → `RouteTerminalFailureError`; `.detail` = SP response JSON |
|
||||
| AC-6 | Pre-emptive validation rejects (BEFORE HTTP POST): empty `points`, >100 `points`, missing/zero `id`, missing/zero `regionSizeMeters`, OOR `zoomLevel`, OOR lat/lon |
|
||||
| AC-7 | `seed_route.py --dry-run --tlog <derkachi.tlog>`: extracts route, prints planned payload + sha256, exit 0, no HTTP |
|
||||
| AC-8 | `seed_route.py --tlog <derkachi.tlog>` against Jetson SP: exit 0, prints `RouteSeedResult`, optional summary JSON |
|
||||
| AC-9 | Unit tests (mocked HTTPX): happy path, 400+ProblemDetails, 500 transient, terminal failure, timeout, dry-run, missing env, all pre-emptive validation cases |
|
||||
| AC-10 | Integration test gated by `RUN_E2E=1` + `SATELLITE_PROVIDER_URL`: Derkachi route seeded, `tile_count > 0`, `maps_ready=True` |
|
||||
|
||||
## Out of scope
|
||||
|
||||
- FAISS index from seeded tiles (AZ-835 C3 / C5)
|
||||
- C6 cache population (AZ-835 C3 — new `operator_pre_flight_setup` fixture)
|
||||
- Modifying satellite-provider source (Route API consumed as-is)
|
||||
- Multi-route batching (one RouteSpec → one POST)
|
||||
- Authentication beyond existing JWT pattern (AZ-494)
|
||||
|
||||
## References
|
||||
|
||||
- Parent Epic: AZ-835 — https://denyspopov.atlassian.net/browse/AZ-835
|
||||
- Sibling: AZ-836 (C1) — RouteSpec source
|
||||
- Mirror CLI: `tests/fixtures/derkachi_c6/seed_region.py` (AZ-777 Phase 2)
|
||||
- HTTP patterns: `src/gps_denied_onboard/components/c11_tile_manager/tile_downloader.py` (AZ-316/777 Phase 1)
|
||||
- DTOs (in `../satellite-provider/`): `SatelliteProvider.Common/DTO/{CreateRouteRequest,RoutePoint,GeoPoint}.cs`
|
||||
- Soft prereq: AZ-809 (Route API validation in satellite-provider)
|
||||
Reference in New Issue
Block a user