# Code Review Report **Batch**: AZ-777 Phase 1 (e2e-runner wire + C11 contract adapt + smoke test) **Date**: 2026-05-21 **Verdict**: PASS_WITH_WARNINGS ## Scope AZ-777 is an 8-pt task with 5 explicit STOP-gated phases. This batch delivers **Phase 1 only** — the e2e-runner wiring to the existing parent-suite satellite-provider service + the C11 `HttpTileDownloader` contract adaptation to the AZ-505 v1.0.0 `tile-inventory.md` API + the Tier-2 smoke test that validates the wire. Phases 2–5 (catalog seed via `POST /api/satellite/request`, real `operator_pre_flight_setup` fixture, un-xfail Tier-2 tests, docs) are out of scope for this batch and are gated behind a STOP per the task spec's Risk-5 mitigation. ## Files changed Production (1): - `src/gps_denied_onboard/components/c11_tile_manager/tile_downloader.py` — `_LIST_PATH` / `_GET_PATH` replaced with `_INVENTORY_PATH` (`POST /api/satellite/tiles/inventory`) + `_TILES_PATH` (`GET /tiles/{z}/{x}/{y}`); `_do_enumerate` rewritten to enumerate bbox tile coords client-side and POST chunked inventory requests; `_download_one_tile` re-routes to slippy-map URL; common retry / auth logic refactored into `_send_request`; new module helpers: `_enumerate_bbox_tile_coords`, `_tile_center_latlon`, `_tile_size_meters_at`, `_format_tile_id_str`, `_parse_tile_id_str`, `_chunk_iter`; new constants `_DEFAULT_ESTIMATED_TILE_BYTES` (50 KiB, conservative tile-size estimate since inventory no longer returns content-length hints), `_INVENTORY_MAX_ENTRIES_PER_REQUEST`, `_EARTH_EQUATORIAL_CIRCUMFERENCE_M`, `_TILE_SIZE_PIXELS`. Tests (2): - `tests/unit/c11_tile_manager/test_tile_downloader.py` — all 14 AC tests rewritten to drive `_make_inventory_handler` (POST inventory + GET tile) instead of the old GET-list handler; `_StubTileWriter` rekeyed by call-index instead of by `(z,lat,lon)` strings (the downloader now derives lat/lon from the slippy-map coord, so fixtures cannot fabricate arbitrary lat/lons); `_DEFAULT_ESTIMATED_TILE_BYTES` constant mirrored. All 14 tests PASS. - `tests/e2e/satellite_provider/test_smoke.py` (new) — two Tier-2 smoke tests: (i) raw `POST /api/satellite/tiles/inventory` for a 1-tile Derkachi-bbox query, asserts the documented response schema; (ii) drives the adapted `HttpTileDownloader` against the real service with an in-memory C6 stub (Phase-3 fixture will replace it with real C6). - `tests/e2e/satellite_provider/__init__.py` (new). Compose / env (2): - `docker-compose.test.jetson.yml` — e2e-runner env block: `SATELLITE_PROVIDER_URL` switched from `http://mock-sat:5100` to `https://satellite-provider:8080`; `SATELLITE_PROVIDER_TLS_INSECURE=1` added (dev-only); `SATELLITE_PROVIDER_API_KEY` sourced from `.env.test`; `JWT_*` forwarded for in-container fallback minting; `depends_on: { satellite-provider: { condition: service_healthy } }` added. - `.env.test.example` — new `SATELLITE_PROVIDER_API_KEY` variable with documentation + dev TLS bypass security note. Tooling (2): - `scripts/mint_dev_jwt.py` (new) — HS256 dev-JWT mint helper. Reads JWT secret / iss / aud from env or `.env.test`; emits a signed JWT to stdout. Convenience for dev workflows; production retrieves tokens from the admin API. - `pyproject.toml` — added `pyjwt>=2.8,<3.0` to `[dev]` extras. Tracker docs (1): - `_docs/02_tasks/_dependencies_table.md` — AZ-777 row bumped from 5pt to 8pt (matches the 2026-05-21 decision-log override in `_docs/_process_leftovers/2026-05-21_az777_complexity_override.md`). ## Phase 2 — Spec Compliance | AC | Status | Notes | |----|--------|-------| | AC-1 (`docker compose config` exits 0 with `depends_on satellite-provider`) | ✅ Verified | Compose lint passes locally with the new env block. | | AC-2 unit half (`_do_enumerate` POST inventory + `_download_one_tile` slippy-map GET against stubbed responses) | ✅ Verified | 14/14 unit tests PASS against the new contract. | | AC-2 live half (Bearer-authenticated round-trip against the running service) | ⏸ Deferred to Tier-2 Jetson run | Smoke test gated by `RUN_REPLAY_E2E=1` + `tier2`; auto-skips on dev macOS. | | AC-3..6 | ⏳ Out of scope (Phases 2–5) | Phase 1 → 2 STOP gate. | No spec gaps within Phase 1. AC-2's live validation runs the next time the Jetson harness fires; the test code is in place. ## Findings | # | Severity | Category | File:Line | Title | |---|----------|----------|-----------|-------| | 1 | Medium | Architecture | `src/gps_denied_onboard/runtime_root/c11_factory.py` | TLS_INSECURE flag not plumbed through production composition root | | 2 | Medium | Maintainability | `src/gps_denied_onboard/components/c11_tile_manager/tile_downloader.py:84` | `_DEFAULT_ESTIMATED_TILE_BYTES` is a project-wide guess, not configurable | | 3 | Low | Maintainability | `tests/e2e/satellite_provider/test_smoke.py` | Smoke test passes when catalog is empty (Phase-2 dependency) | ### Finding Details **F1: TLS_INSECURE flag not plumbed through production composition root** (Medium / Architecture) - Location: `src/gps_denied_onboard/runtime_root/c11_factory.py` - Description: `build_tile_downloader` takes a caller-owned `httpx.Client`, so the operator binary that wires C11 is the layer that must honour `SATELLITE_PROVIDER_TLS_INSECURE`. No production caller exists today — `build_tile_downloader` only has the Tier-2 smoke test as a live consumer. Phase 3 of AZ-777 introduces the `operator_pre_flight_setup` fixture that will be the first live caller; the TLS_INSECURE handling will land there. - Suggestion: When Phase 3 ships, the new caller must read `SATELLITE_PROVIDER_TLS_INSECURE` and pass the right `verify=` to `httpx.Client(...)` — mirror the approach used in `tests/e2e/satellite_provider/test_smoke.py::_make_http_client`. Also consider a `WARNING` log line at startup whenever the insecure flag is active so the operator can audit it. - Task: AZ-777 Phase 3 (deferred) **F2: `_DEFAULT_ESTIMATED_TILE_BYTES` is a project-wide guess** (Medium / Maintainability) - Location: `src/gps_denied_onboard/components/c11_tile_manager/tile_downloader.py:84` - Description: The AZ-505 v1.0.0 inventory contract dropped the per-entry `estimatedBytes` field, so the AZ-308 budget pre-check reserves a constant 50 KiB per `present=true` tile. 50 KiB is conservative for typical CARTO Voyager tiles (8-30 KiB) but under-reserves for high-detail UAV uploads (30-80 KiB). The budget can over-reserve safely; under-reserving fails the AZ-308 contract. - Suggestion: Either (a) add the constant to `C11Config` so operators can tune it per imagery source, or (b) file a parent-suite ticket to restore `estimatedBytes` in the inventory response. For Phase 1 the constant is acceptable; revisit in Phase 5 docs. - Task: AZ-777 Phase 5 / future config refactor **F3: Smoke test passes when catalog is empty** (Low / Maintainability) - Location: `tests/e2e/satellite_provider/test_smoke.py:test_smoke_c11_download_via_http_pipeline` - Description: The C11 pipeline smoke asserts SUCCESS with `tiles_downloaded == len(write_calls)`. Pre-Phase-2 the catalog is empty → every entry comes back `present=false` → the test passes with zero downloads, which proves the wire works but does NOT prove tiles actually land in C6. The conditional `if report.tiles_downloaded > 0` block tightens the assertion once the catalog is seeded. - Suggestion: Accepted by design for Phase 1; Phase 2's catalog seed automatically turns this from "wire works" into "tiles land" without test changes. - Task: AZ-777 Phase 2 ## Phase 3 — Code Quality - **SOLID**: `_send_request` consolidates GET / POST retry + auth in one place instead of two near-duplicates; methods stay small (`_send_get` / `_send_post` are 5-line shims over the common path). Slippy-map helpers are module-level pure functions — they don't reach for `self` and don't depend on `httpx`, so the unit tests can reuse them directly. - **Error handling**: every failure path raises a typed C11 error (`SatelliteProviderError`, `RateLimitedError`, `CacheBudgetExceededError`); no bare `except`s; no silently swallowed errors. The Retry-After parser handles both seconds- and HTTP-date forms; OOB values clamp to 0 instead of propagating garbage. - **Naming**: `_inventory_path` / `_tiles_path` / `_tile_id_str` / `_parse_tile_id_str` etc. all read directly against the AZ-505 contract; no surprises. - **Complexity**: `_send_request` is the longest method at ~80 LOC but it's a linear retry ladder; cyclomatic complexity is bounded by the four response branches (transport-error / 401-3 / 429 / 5xx / 200). `_do_enumerate` is 14 LOC. - **Test quality**: every AC test arranges a specific contract scenario (POST inventory + GET tile) and asserts both the downloader's report counts AND the C6 stub's call records. Tests do NOT just "assert no exception". ## Phase 4 — Security Quick-Scan - No SQL strings touched. - JWT mint helper uses PyJWT's `jwt.encode` with HS256; no hand-rolled crypto; secrets come from env, never hardcoded. - `.env.test.example`'s `SATELLITE_PROVIDER_API_KEY` placeholder is `PASTE-MINTED-JWT-HERE` — the smoke test treats that exact string as "unset" and skips, so a developer accidentally committing the placeholder cannot get false confidence. - `Authorization` header redacted in error logs as `Bearer ***` per AZ-316 AC-11; the AC-11 test verifies the real API key never appears in any log record. - `SATELLITE_PROVIDER_TLS_INSECURE` is opt-in via env var; default is verify=True. The dev-only nature is documented in the compose comment, in `.env.test.example`, and (when Phase 3 lands) will be logged at startup. ## Phase 5 — Performance Scan - Inventory POST chunks at 5000 entries per the contract cap; one POST per up-to-5000-tile bbox. - Backoff schedule unchanged (`_DEFAULT_BACKOFF_SCHEDULE_S = (1, 2, 4, 8)`); session retry budget enforced. - `test_nfr_throughput_1000_tiles_under_budget` passes in <1 s locally (budget is 10 s) — no O(n²) bookkeeping regression. - No N+1 patterns; no blocking I/O in async paths (whole module is sync). ## Phase 6 — Cross-Task Consistency Single task in this batch (AZ-777 Phase 1). N/A. ## Phase 7 — Architecture Compliance - **Layer direction**: C11 still does not import C6 directly; the Protocol cuts (`_TileWriterLike`, `_BudgetEnforcerLike`) stay in `tile_downloader.py`. The composition root (`runtime_root/c11_factory.py::_C6DownloadAdapter`) remains the single bridge. - **Public API respect**: no cross-component imports added. - **No new cyclic deps**: no new module-level imports between components. - **Architecture principle #5** (`satellite-provider` owns OSM / CARTO tile network I/O; the onboard companion is read-only via C11 during pre-flight): this batch is the first time C11 is actually wired to consume that contract — the principle is honoured for the first time end-to-end. - **ADR compliance**: ADR-004 (event-driven cross-component comms): C11 → satellite-provider is HTTP, which is explicitly scoped out of ADR-004 (the ADR governs intra-onboard comms, not external-service calls). No drift. No new ADR required — the task spec explicitly states this is execution of existing decisions. ## Verdict justification PASS_WITH_WARNINGS — three Medium / Low findings, no Critical or High. The Medium findings are deferred to later AZ-777 phases or to future tuning, with clear ownership; no blocking gap in Phase 1 itself.