fixes in tests, autodev update
ci/woodpecker/push/02-build-push Pipeline failed

This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-06-18 12:13:43 +03:00
parent c1baef57be
commit 12d0008763
3 changed files with 7 additions and 160 deletions
+5 -5
View File
@@ -2,13 +2,13 @@
## Current Step ## Current Step
flow: existing-code flow: existing-code
step: 10 step: 11
name: Implement name: Run Tests
status: in_progress status: in_progress
sub_step: sub_step:
phase: 6 phase: 1
name: implement-tasks name: run-unit-tests
detail: "batch 05 (AZ-963) done; cycle 4 has no more actionable tasks" detail: ""
retry_count: 0 retry_count: 0
cycle: 4 cycle: 4
tracker: jira tracker: jira
@@ -1,40 +1,3 @@
"""C11 ``SatelliteProviderRouteClient`` (AZ-838 / Epic AZ-835 C2).
Operator-side HTTP client for the parent-suite Route API. Takes a
:class:`gps_denied_onboard._types.route.RouteSpec` (produced
by AZ-836 / C1) and onboards it with ``satellite-provider``:
1. **Pre-emptive validation** mirrors the AZ-809
``CreateRouteRequestValidator`` rules so obviously-bad input fails
before the HTTP POST.
2. **POST** ``/api/satellite/route`` with ``requestMaps=true`` and
``createTilesZip=false``. Wire shape derived from the live DTOs in
``../satellite-provider/SatelliteProvider.Common/DTO/{CreateRouteRequest,RoutePoint,GeoPoint}.cs``.
3. **Poll** ``GET /api/satellite/route/{id}`` until ``mapsReady=true``
OR a terminal failure status; respects
:attr:`SatelliteProviderRouteClient.poll_max_attempts` and
:attr:`SatelliteProviderRouteClient.poll_interval_s`.
4. **Inventory verify** via ``POST /api/satellite/tiles/inventory`` —
enumerates the route's tile coverage locally from the
``RouteSpec`` waypoints + ``regionSizeMeters`` and counts the
``present=true`` entries returned by the server (lower bound on
the actual coverage, since the server interpolates intermediate
waypoints — documented in the contract).
5. **Return** :class:`RouteSeedResult` with provenance fields
(route id, terminal status, maps_ready flag, tile count, elapsed
time, sha256 of the submitted payload).
The error hierarchy is rooted at :class:`SatelliteProviderRouteError`
(in :mod:`.errors`), independent of :class:`TileManagerError` because
the Route API is a corridor-onboarding flow, not a per-tile transfer.
Lives under ``c11_tile_manager`` because the existing C11 plumbing
(JWT auth, TLS-insecure flag for self-signed dev certs) is shared and
because C11 is already gated ``BUILD_C11_TILE_MANAGER=ON`` for the
operator-orchestrator binary (and OFF for airborne) — same audience
as the Route API.
"""
from __future__ import annotations from __future__ import annotations
import hashlib import hashlib
@@ -60,20 +23,11 @@ __all__ = [
"SatelliteProviderRouteClient", "SatelliteProviderRouteClient",
] ]
# AZ-838 wire constants — paths confirmed against
# `../satellite-provider/SatelliteProvider.Api/Program.cs:266`+ on
# 2026-05-22 (route create + route status) and against
# `tile_downloader.py::_INVENTORY_PATH` for the inventory verify step.
_ROUTE_CREATE_PATH = "/api/satellite/route" _ROUTE_CREATE_PATH = "/api/satellite/route"
_ROUTE_STATUS_PATH_TPL = "/api/satellite/route/{id}" _ROUTE_STATUS_PATH_TPL = "/api/satellite/route/{id}"
_INVENTORY_PATH = "/api/satellite/tiles/inventory" _INVENTORY_PATH = "/api/satellite/tiles/inventory"
_INVENTORY_MAX_ENTRIES_PER_REQUEST = 5000 _INVENTORY_MAX_ENTRIES_PER_REQUEST = 5000
# AZ-809 validator bounds (mirrored from
# `SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs`).
# Keep these in sync with that file — the client pre-emptively
# enforces them so obviously-bad input fails before the HTTP POST.
_VALIDATOR_NAME_MAX_LEN: int = 200 _VALIDATOR_NAME_MAX_LEN: int = 200
_VALIDATOR_DESCRIPTION_MAX_LEN: int = 1000 _VALIDATOR_DESCRIPTION_MAX_LEN: int = 1000
_VALIDATOR_REGION_SIZE_MIN_M: float = 100.0 _VALIDATOR_REGION_SIZE_MIN_M: float = 100.0
@@ -83,16 +37,8 @@ _VALIDATOR_ZOOM_MAX: int = 22
_VALIDATOR_POINTS_MIN: int = 2 _VALIDATOR_POINTS_MIN: int = 2
_VALIDATOR_POINTS_MAX: int = 500 _VALIDATOR_POINTS_MAX: int = 500
# Mirror of the parent-suite tile-size math used by C11
# (`tile_downloader._EARTH_EQUATORIAL_CIRCUMFERENCE_M` /
# `_TILE_SIZE_PIXELS`). Re-stated here so the inventory-coverage
# enumeration does not depend on a private constant from the
# downloader module.
_EARTH_EQUATORIAL_CIRCUMFERENCE_M: float = 40_075_016.686 _EARTH_EQUATORIAL_CIRCUMFERENCE_M: float = 40_075_016.686
# Terminal status strings the parent suite reports via
# `GET /api/satellite/route/{id}`. Mirrors `seed_region.py`'s set so
# both Region and Route flows agree on terminal semantics.
_TERMINAL_STATUSES: frozenset[str] = frozenset( _TERMINAL_STATUSES: frozenset[str] = frozenset(
{"completed", "failed", "error", "done", "succeeded", "rejected"} {"completed", "failed", "error", "done", "succeeded", "rejected"}
) )
@@ -116,33 +62,6 @@ _LOG_KIND_VALIDATION_FAIL = "c11.route.validation_failed"
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class RouteSeedResult: class RouteSeedResult:
"""Outcome of one :meth:`SatelliteProviderRouteClient.seed_route` call.
Attributes:
route_id: The ``id`` field POSTed in the request — kept here
so the caller can re-query ``GET /api/satellite/route/{id}``
without re-deriving it.
terminal_status: The server's last observed status string
(one of the values in :data:`_TERMINAL_STATUSES`, lower-
cased). On a healthy run this is typically ``completed``.
maps_ready: ``True`` if the server reported ``mapsReady=true``
within the poll budget. ``False`` only on terminal
failure paths that do NOT raise (currently impossible —
terminal failures always raise; the field is here for
forward compatibility if the server adds a "ready
without maps" state).
tile_count: Number of (z, x, y) entries the inventory call
reported as ``present=true``. Lower bound on the actual
tile coverage produced by the server, since the local
enumeration does NOT account for the server-side
~200 m intermediate-point interpolation documented in
``../satellite-provider/_docs/02_document/contracts/api/route-creation.md``.
elapsed_ms: Wall-clock milliseconds from the start of the
POST submission to the completion of the inventory verify.
submitted_payload_sha256: SHA-256 hex digest of the JSON body
POSTed to ``/api/satellite/route`` (provenance / audit).
"""
route_id: uuid.UUID route_id: uuid.UUID
terminal_status: str terminal_status: str
maps_ready: bool maps_ready: bool
@@ -152,21 +71,6 @@ class RouteSeedResult:
class SatelliteProviderRouteClient: class SatelliteProviderRouteClient:
"""HTTP client for the parent-suite Route API (AZ-838).
Constructor parameters mirror the operator-side ergonomics
(``base_url`` + ``jwt`` + ``tls_insecure`` for self-signed dev
certs), matching the existing ``seed_region.py`` flag surface so
operators can use a single ``.env.test`` file.
For tests, an optional ``http_client`` may be injected — the
standard ``httpx.MockTransport`` pattern from
``test_tile_downloader.py`` works directly. When ``http_client``
is ``None`` (production / CLI use), the client owns its own
short-lived :class:`httpx.Client` per ``seed_route`` call so the
caller does not need to manage connection lifetime.
"""
def __init__( def __init__(
self, self,
base_url: str, base_url: str,
@@ -211,10 +115,6 @@ class SatelliteProviderRouteClient:
"gps_denied_onboard.components.c11_tile_manager.route_client" "gps_denied_onboard.components.c11_tile_manager.route_client"
) )
# ------------------------------------------------------------------
# Public API
# ------------------------------------------------------------------
def seed_route( def seed_route(
self, self,
spec: RouteSpec, spec: RouteSpec,
@@ -224,38 +124,6 @@ class SatelliteProviderRouteClient:
zoom_level: int = 18, zoom_level: int = 18,
description: str | None = None, description: str | None = None,
) -> RouteSeedResult: ) -> RouteSeedResult:
"""Onboard ``spec`` with the parent-suite Route API.
Args:
spec: The :class:`RouteSpec` produced by AZ-836's
``extract_route_from_tlog``.
name: Optional human-readable name. When ``None``, derived
from the spec's ``source_tlog`` stem + a short hash of
the waypoints (deterministic for the same RouteSpec).
region_size_meters: Per-waypoint coverage radius in
metres. When ``None``, falls back to
:attr:`RouteSpec.suggested_region_size_meters`. The
combined value MUST be in the AZ-809 validator range
``[100, 10000]``.
zoom_level: Web-Mercator zoom for the route. Defaults to
18 — matches ``seed_region.py``'s ``zoom_levels``
default. AZ-809 validator accepts ``[0, 22]``.
description: Optional free-text description (max 1000
chars per AZ-809).
Returns:
:class:`RouteSeedResult` on success.
Raises:
RouteValidationError: Pre-emptive validation rejected the
inputs OR the server returned 4xx + RFC 7807.
RouteTransientError: 5xx / network / timeout. The
underlying ``httpx`` exception is on ``__cause__``.
RouteTerminalFailureError: ``mapsReady=true`` was never
reached within the poll budget OR the server reported
a terminal failure status.
"""
effective_region_size = float( effective_region_size = float(
region_size_meters region_size_meters
if region_size_meters is not None if region_size_meters is not None
@@ -273,8 +141,6 @@ class SatelliteProviderRouteClient:
description=description, description=description,
) )
# Pre-emptive validation runs against the assembled body so
# the rules apply to whatever the server is about to see.
self._preemptive_validate(request_body) self._preemptive_validate(request_body)
payload_bytes = _canonical_json_bytes(request_body) payload_bytes = _canonical_json_bytes(request_body)
@@ -311,12 +177,6 @@ class SatelliteProviderRouteClient:
zoom_level: int = 18, zoom_level: int = 18,
description: str | None = None, description: str | None = None,
) -> tuple[dict[str, Any], str]: ) -> tuple[dict[str, Any], str]:
"""Return the planned request body + its sha256 without HTTP.
Powers ``seed_route.py --dry-run`` (AC-7). Runs the same
pre-emptive validation as :meth:`seed_route`, so a dry-run
surfaces validation errors the same way a live run would.
"""
effective_region_size = float( effective_region_size = float(
region_size_meters region_size_meters
@@ -337,10 +197,6 @@ class SatelliteProviderRouteClient:
sha256 = hashlib.sha256(_canonical_json_bytes(body)).hexdigest() sha256 = hashlib.sha256(_canonical_json_bytes(body)).hexdigest()
return body, sha256 return body, sha256
# ------------------------------------------------------------------
# Internal pipeline
# ------------------------------------------------------------------
def _run( def _run(
self, self,
*, *,
@@ -613,10 +469,6 @@ class SatelliteProviderRouteClient:
) )
return present_count return present_count
# ------------------------------------------------------------------
# Validation + payload assembly
# ------------------------------------------------------------------
def _build_request_body( def _build_request_body(
self, self,
*, *,
@@ -627,13 +479,6 @@ class SatelliteProviderRouteClient:
zoom_level: int, zoom_level: int,
description: str | None, description: str | None,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Assemble the wire body matching CreateRouteRequest.cs / RoutePoint.cs.
Per the AZ-809 batch-03 review F3, ``RoutePoint`` uses
``[JsonPropertyName("lat"|"lon")]`` so we serialize ``lat`` /
``lon`` (NOT ``latitude`` / ``longitude``).
"""
body: dict[str, Any] = { body: dict[str, Any] = {
"id": str(route_id), "id": str(route_id),
"name": name, "name": name,
@@ -18,6 +18,8 @@ from pathlib import Path
import numpy as np import numpy as np
import pytest import pytest
pytest.importorskip("torch")
import torch import torch
from torch import nn from torch import nn