mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 08:21:13 +00:00
[AZ-489] [AZ-490] ADR-010 design pass: operator-mission as cold-start anchor
Architecture, contracts, and task amendments for the flight-route-driven preflight + cold-start origin feature (ADR-010). No source code touched in this commit; the implementation commits for AZ-489 / AZ-490 / AZ-419 land separately. * architecture.md: ADR-010, new Principle #14, amended Principle #11, external systems gain flights service + Mission Planner UI, data model gains Flight / Waypoint / TakeoffOrigin. * system-flows.md: F1 gains phase 0 (Flight resolve), F2 gains cold-start ladder, F7 gains mid-flight bounded-delta GPS gate. * glossary.md: Flight, Flights API, Mid-flight bounded-delta GPS gate, Mission Planner UI, Takeoff origin, Waypoint. * C10: description + cache_provisioner + manifest_verifier bumped to v1.1 carrying takeoff_origin + flight_id in the manifest hash. * C12: description updated + new flights_api_client.md contract v1.0. * C5: description + state_estimator_protocol bumped to v1.1 with set_takeoff_origin + 3-clause spoof-promotion gate. * AZ-323/324/325/326/328/419 amended in place. AZ-490 spec created (C5 set_takeoff_origin entrypoint). * Dependencies table: 142 tasks / 478 pts / 15 forward edges (2 new tasks, 2 backward deps, 2 forward deps from AZ-419). * Leftovers cleared: 2026-05-11 Jira transition entries for AZ-355 and AZ-386 are deleted (Jira reconnected; both already transitioned in their respective implementation commits). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `set_takeoff_origin` (AZ-490, ADR-010) | `origin: LatLonAlt, sigma_m: float` | `None` | No | `EstimatorConfigError`, `EstimatorFatalError` |
|
||||
| `add_vio` | `VioOutput` | `None` | No | `EstimatorDegradedError`, `EstimatorFatalError` |
|
||||
| `add_pose_anchor` | `PoseEstimate` | `None` | No | `EstimatorDegradedError`, `EstimatorFatalError` |
|
||||
| `add_fc_imu` | `ImuWindow` | `None` | No | `EstimatorDegradedError` |
|
||||
@@ -76,7 +77,8 @@ C5 is bounded by design — no unbounded growth.
|
||||
|
||||
**State Management**:
|
||||
- iSAM2 graph + Values + Marginals lifecycle for the flight.
|
||||
- Source-label state machine: tracks the AC-NEW-2 / AC-NEW-8 spoofing-promotion gate (≥10 s + visual consistency check before re-promoting a previously-spoofed FC GPS source).
|
||||
- Cold-start ladder (ADR-010, AZ-490): `set_takeoff_origin(origin, sigma_m)` MUST be invoked before any `add_vio` / `add_fc_imu` / `add_pose_anchor` call. iSAM2 attaches a `PriorFactorPose3` on the initial pose key with covariance derived from `sigma_m` (default 50 m horizontal, 100 m vertical); ESKF seeds the nominal position and writes the position-block of the error covariance to `sigma_m^2`. The method is idempotent within `INIT` state (re-invocation overwrites the prior); once the estimator transitions to `TRACKING`, further calls raise `EstimatorConfigError`.
|
||||
- Source-label state machine: tracks the AC-NEW-2 / AC-NEW-8 spoofing-promotion gate (≥10 s + visual consistency check + ≤ 200 m bounded-delta before re-promoting a previously-spoofed FC GPS source).
|
||||
- Last-anchor-age timer for AC-1.3 binning.
|
||||
|
||||
**Key Dependencies**:
|
||||
@@ -88,9 +90,10 @@ C5 is bounded by design — no unbounded growth.
|
||||
| Eigen | matches GTSAM | Lie-algebra math |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `EstimatorConfigError`: `set_takeoff_origin` called after `TRACKING` state, OR called with a malformed `LatLonAlt` / non-positive `sigma_m`. Caller must surface to operator; takeoff blocked.
|
||||
- `EstimatorDegradedError`: factor add yielded poor convergence; covariance inflated; emit `EstimatorOutput` with degraded label.
|
||||
- `EstimatorFatalError`: iSAM2 numerical failure, KEYFRAME_LIMIT exceeded, etc.; emit no `EstimatorOutput` for this tick. AC-5.2 fallback (3 s no estimate → FC IMU-only) applies.
|
||||
- Spoof-promotion gate: never re-introduce a previously-spoofed FC GPS source until BOTH (i) FC `gps_health == STABLE_NON_SPOOFED` for ≥ 10 s AND (ii) the next satellite-anchored frame agrees with the FC GPS within a configurable tolerance (AC-NEW-8). Document every reject in FDR + GCS STATUSTEXT.
|
||||
- Spoof-promotion gate (Principle #11 amended, AZ-385 + AZ-490 follow-up): never re-introduce a previously-spoofed FC GPS source until ALL THREE hold — (i) FC `gps_health == STABLE_NON_SPOOFED` for ≥ 10 s, (ii) the next satellite-anchored frame agrees with the FC GPS within a configurable tolerance, AND (iii) the FC's reported position is within ≤ 200 m of the companion's last emitted `PoseEstimate`. The same gate is applied at takeoff when a Manifest `takeoff_origin` is present: an FC GPS reading that disagrees with the operator origin by > 200 m is logged as suspect and the operator origin wins. Document every reject in FDR + GCS STATUSTEXT.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: build the **model-derived** pre-flight cache artifacts on top of an already-populated tile store, and verify them at takeoff. After C11 `TileDownloader` has fetched tiles into C6, C10 orchestrates: compile/deserialize TensorRT engines via C7 → batch each tile through C2's backbone for descriptors → atomically write FAISS HNSW index with SHA-256 sidecars (D-C10-3) → write Manifest with hash of (model + calibration + corpus + sector_class) for D-C10-1 idempotence. At F2 takeoff load, run `verify_manifest` (D-C10-3 SHA-256 content-hash gate) before allowing the system to arm.
|
||||
**Purpose**: build the **model-derived** pre-flight cache artifacts on top of an already-populated tile store, and verify them at takeoff. After C11 `TileDownloader` has fetched tiles into C6, C10 orchestrates: compile/deserialize TensorRT engines via C7 → batch each tile through C2's backbone for descriptors → atomically write FAISS HNSW index with SHA-256 sidecars (D-C10-3) → write Manifest with hash of (model + calibration + corpus + sector_class **+ takeoff_origin**) for D-C10-1 idempotence. The `takeoff_origin` is supplied by C12 (derived from `Flight.waypoints[0]` via the `FlightsApiClient`, ADR-010 + AZ-489); C10 treats it as one more identity field and bakes it into both the Manifest body and the manifest-hash. At F2 takeoff load, run `verify_manifest` (D-C10-3 SHA-256 content-hash gate) before allowing the system to arm; the verifier also surfaces `takeoff_origin` so the companion's composition root can pass it to `C5.set_takeoff_origin(origin, sigma_m)` before any sensor sample (AZ-490).
|
||||
|
||||
**C10 does NOT touch `satellite-provider`.** Tile I/O — both download (F1 inbound) and post-landing upload (F10) — lives in C11 (Tile Manager). C10 reads tiles from C6, writes engines + descriptors + manifest to filesystem and Postgres. The split is operational: C11 carries the operator-side network identity (TLS API key for download, per-flight signing key for upload) and the airborne-exclusion property (ADR-004); C10 carries the model identity and the takeoff-load verifier — neither of which need to leave the workstation/companion enclave at runtime.
|
||||
|
||||
@@ -43,6 +43,8 @@ BuildRequest:
|
||||
sector_class: enum {active_conflict, stable_rear} # baked into manifest
|
||||
calibration_path: Path
|
||||
cache_root: Path
|
||||
takeoff_origin: LatLonAlt | None # ADR-010 / AZ-489; baked into manifest + hash
|
||||
flight_id: UUID | None # ADR-010; pass-through provenance, baked into manifest
|
||||
|
||||
BuildReport:
|
||||
engines_built: int
|
||||
@@ -52,12 +54,14 @@ BuildReport:
|
||||
outcome: enum {success, failure, idempotent_no_op}
|
||||
failure_reason: string (optional)
|
||||
|
||||
Manifest: see data_model.md
|
||||
Manifest: see data_model.md (carries takeoff_origin + flight_id when set; hash includes them)
|
||||
EngineCacheEntry: see data_model.md
|
||||
|
||||
VerificationResult:
|
||||
manifest_hash_match: bool
|
||||
per_artifact_hash_match: dict[Path, bool]
|
||||
takeoff_origin: LatLonAlt | None # passed through from manifest for C5 warm-start (AZ-490)
|
||||
flight_id: UUID | None
|
||||
outcome: enum {pass, fail}
|
||||
fail_reasons: list[string]
|
||||
```
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: operator-facing tooling on the workstation that **sequences** the F1 cache-build workflow (calls C11 `TileDownloader` then C10 `CacheProvisioner`), drives sector classification, freshness pipeline, calibration loading, and (post-flight) triggers the C11 `TileUploader`. Provides the human-in-the-loop entry point for everything that needs operator judgement (active-conflict vs stable-rear sector classification, AC-3.4 ≥3-frame outage operator re-loc requests, network configuration to `satellite-provider`).
|
||||
**Purpose**: operator-facing tooling on the workstation that **sequences** the F1 cache-build workflow (calls `FlightsApiClient` → C11 `TileDownloader` → C10 `CacheProvisioner`), drives sector classification, freshness pipeline, calibration loading, and (post-flight) triggers the C11 `TileUploader`. Provides the human-in-the-loop entry point for everything that needs operator judgement (active-conflict vs stable-rear sector classification, AC-3.4 ≥3-frame outage operator re-loc requests, network configuration to `satellite-provider`).
|
||||
|
||||
**Architectural Pattern**: Coordinator (single concrete `OperatorTooling` today). Two interfaces: `CacheBuildWorkflow` (pre-flight UX) and `OperatorReLocService` (mid-flight operator re-loc requests, AC-3.4). The latter is consumed via the GCS link by C8 (operator commands subscription).
|
||||
**Architectural Pattern**: Coordinator (single concrete `OperatorTooling` today). Three interfaces: `CacheBuildWorkflow` (pre-flight UX), `FlightsApiClient` (read the operator-authored `Flight` from the parent-suite `flights` REST service or a local JSON export — AZ-489, ADR-010), and `OperatorReLocService` (mid-flight operator re-loc requests, AC-3.4). `OperatorReLocService` is consumed via the GCS link by C8 (operator commands subscription); `FlightsApiClient` is operator-workstation-only and never reaches the airborne companion (Principle #9).
|
||||
|
||||
**Upstream dependencies**:
|
||||
- Operator (human input).
|
||||
- C11 `TileDownloader` (operator-workstation-side) — invoked first in F1 to populate C6 from `satellite-provider`.
|
||||
- C10 CacheProvisioner (companion-side) — invoked second in F1, over USB/Eth.
|
||||
- Operator (human input — flight ID or flight file path, sector classification, calibration path).
|
||||
- `flights` REST service (parent-suite `suite/flights/`) — read via `FlightsApiClient` to fetch the operator-authored `Flight` (waypoints + altitudes); offline alternative is a local JSON export in the same DTO shape.
|
||||
- C11 `TileDownloader` (operator-workstation-side) — invoked second in F1 to populate C6 from `satellite-provider` for the bbox derived from the `Flight`.
|
||||
- C10 CacheProvisioner (companion-side) — invoked third in F1, over USB/Eth, with `takeoff_origin = Flight.waypoints[0]` and the computed bbox.
|
||||
- Camera calibration artifact (operator workstation filesystem).
|
||||
|
||||
**Downstream consumers**:
|
||||
@@ -24,12 +25,21 @@
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `build_cache` | `bbox, sector_class, calibration_path, satellite_provider_url, api_key` | `CacheBuildReport` (wraps C11 `DownloadBatchReport` + C10 `BuildReport`) | No (operator-facing; minutes) | `CacheBuildError` (wraps SatelliteProviderError, EngineBuildError, etc.) |
|
||||
| `build_cache` | `flight_id` (online) OR `flight_file: Path` (offline), `sector_class`, `calibration_path`, `satellite_provider_url`, `api_key` | `CacheBuildReport` (wraps `FlightResolveReport` + C11 `DownloadBatchReport` + C10 `BuildReport`) | No (operator-facing; minutes) | `CacheBuildError` (wraps `FlightNotFoundError`, `FlightsApiUnreachableError`, `SatelliteProviderError`, `EngineBuildError`, etc.) |
|
||||
| `trigger_post_landing_upload` | `flight_id` | C11 `UploadBatchReport` | No (operator-facing; minutes) | `CacheBuildError` wrapper around `FlightStateNotOnGroundError`, `SignatureRejectedError`, etc. |
|
||||
| `verify_companion_ready` | `companion_address` | `ReadinessReport` | No | `CompanionUnreachableError`, `ContentHashMismatchError` |
|
||||
| `set_sector_classification` | `area, sector_class` | `None` | No | — |
|
||||
| `apply_freshness_threshold` | `sector_class` | `int (months)` | No | — |
|
||||
|
||||
### Interface: `FlightsApiClient` (AZ-489, ADR-010)
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
|--------|-------|--------|-------|-------------|
|
||||
| `fetch_flight` | `flight_id: UUID, base_url: str, auth_token: str` | `FlightDto` (with ordered `WaypointDto[]`) | No (HTTPS; seconds) | `FlightsApiUnreachableError`, `FlightsApiAuthError`, `FlightNotFoundError`, `FlightsApiSchemaError` |
|
||||
| `load_flight_file` | `path: Path` | `FlightDto` (same shape as `fetch_flight`) | No | `FlightFileNotFoundError`, `FlightsApiSchemaError` |
|
||||
| `bbox_from_waypoints` | `waypoints: list[WaypointDto], buffer_m: float` | `BoundingBox` | No | `EmptyWaypointsError` |
|
||||
| `takeoff_origin_from_flight` | `flight: FlightDto` | `LatLonAlt` (from `waypoints[0]`) | No | `EmptyWaypointsError` |
|
||||
|
||||
### Interface: `OperatorReLocService`
|
||||
|
||||
| Method | Input | Output | Async | Error Types |
|
||||
@@ -38,11 +48,23 @@
|
||||
|
||||
**Input/Output DTOs**:
|
||||
```
|
||||
FlightDto: mirror of suite/flights/Database/Entities/Flight.cs (id, name, status, waypoints, ...)
|
||||
WaypointDto: mirror of suite/flights/Database/Entities/Waypoint.cs (ordinal, lat, lon, alt, objective, source)
|
||||
|
||||
FlightResolveReport:
|
||||
source: enum {flights_api, flight_file}
|
||||
flight_id: UUID
|
||||
waypoint_count: int
|
||||
bbox: BoundingBox (computed envelope + buffer)
|
||||
takeoff_origin: LatLonAlt (waypoints[0])
|
||||
raw_flight_dto: FlightDto (preserved for FDR + debug)
|
||||
|
||||
CacheBuildReport:
|
||||
flight_resolve_report: FlightResolveReport
|
||||
download_report: DownloadBatchReport (see C11 spec)
|
||||
build_report: BuildReport (see C10 spec)
|
||||
outcome: enum {success, failure, idempotent_no_op}
|
||||
failure_phase: enum {download, build, none}
|
||||
failure_phase: enum {flight_resolve, download, build, none}
|
||||
failure_reason: string (optional)
|
||||
|
||||
ReadinessReport:
|
||||
@@ -50,6 +72,7 @@ ReadinessReport:
|
||||
content_hashes_pass: bool
|
||||
engines_present: bool
|
||||
calibration_present: bool
|
||||
takeoff_origin_in_manifest: bool # ADR-010: warn-but-not-fail if absent (FC-EKF fallback path remains)
|
||||
outcome: enum {ready, not_ready}
|
||||
not_ready_reasons: list[string]
|
||||
|
||||
@@ -63,7 +86,8 @@ ReLocHint:
|
||||
|
||||
C12 itself does NOT expose HTTP. It **consumes**:
|
||||
|
||||
- `satellite-provider`'s REST API **only via C11 `TileDownloader`** — C12 itself never holds the TLS / API-key credentials. The credential boundary is C11 (operator-workstation-side); C12 sequences and reports.
|
||||
- `satellite-provider`'s REST API **only via C11 `TileDownloader`** — C12 itself never holds the TLS / API-key credentials for satellite tiles. The credential boundary is C11 (operator-workstation-side); C12 sequences and reports.
|
||||
- The parent-suite `flights` REST service via its own `FlightsApiClient` (AZ-489, ADR-010). C12 holds the TLS + suite credentials for the flights service because the `Flight` shape is a C12-owned concern (bbox + takeoff-origin derivation). The companion never reaches the flights service.
|
||||
- The GCS link's MAVLink path (operator commands path), for AC-3.4 re-loc requests.
|
||||
|
||||
C12 may eventually expose a thin local HTTP API for an operator GUI. **Plan-phase carryforward, deferred** — the current cycle ships only a CLI.
|
||||
@@ -98,10 +122,14 @@ C12 holds the operator workstation's local cache staging area + per-area sector
|
||||
| Click / Typer | per project pin | CLI framework |
|
||||
| paramiko / ssh / fabric | per project pin | Companion bring-up over SSH (USB/Eth) |
|
||||
| pymavlink | bundled per D-C8-3 | GCS-link operator commands (AC-3.4) |
|
||||
| (httpx is NOT a direct C12 dep) | — | All `satellite-provider` HTTP traffic is owned by C11; C12 calls C11 in-process |
|
||||
| httpx | per project pin | `FlightsApiClient` HTTPS calls to the parent-suite `flights` REST service (AZ-489) |
|
||||
| pydantic | per project pin | `FlightDto` / `WaypointDto` validation in `FlightsApiClient` (online + offline paths) |
|
||||
| (`satellite-provider` HTTP traffic stays owned by C11) | — | C12 calls C11 in-process |
|
||||
|
||||
**Error Handling Strategy**:
|
||||
- `CacheBuildError`: wraps the underlying error from C11/C10/C7/C6 with operator-friendly text + remediation hint. Includes `failure_phase: download | build` so the operator knows which step to retry.
|
||||
- `CacheBuildError`: wraps the underlying error from C11/C10/C7/C6/`FlightsApiClient` with operator-friendly text + remediation hint. Includes `failure_phase: flight_resolve | download | build` so the operator knows which step to retry.
|
||||
- `FlightsApiUnreachableError`, `FlightsApiAuthError`, `FlightNotFoundError`, `FlightsApiSchemaError`: surfaced by `FlightsApiClient` (online path). Offline path raises `FlightFileNotFoundError` / `FlightsApiSchemaError`. Each maps to an actionable operator hint.
|
||||
- `EmptyWaypointsError`: the resolved `Flight` carries zero waypoints — cannot derive bbox or takeoff origin; refuse build.
|
||||
- `CompanionUnreachableError`: pre-flight bring-up failure; operator must check the wire/SSH config.
|
||||
- `ContentHashMismatchError`: post-stage tampering detected; refuse to mark as ready; operator must re-run F1.
|
||||
- `GcsLinkError` (AC-3.4 path): GCS link drop. Re-loc request is best-effort; operator may need to re-issue when link recovers.
|
||||
|
||||
Reference in New Issue
Block a user