mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 13:11: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:
@@ -2,8 +2,8 @@
|
||||
|
||||
**Owner**: c5_state (epic AZ-260 / E-C5)
|
||||
**Producer task**: AZ-381 (Protocol + DTOs + factory + composition + concrete `ISam2GraphHandle`)
|
||||
**Consumer tasks**: AZ-382 (iSAM2 + IncrementalFixedLagSmoother wiring), AZ-383 (Factor adds), AZ-384 (Marginals + outputs), AZ-385 (Source-label + spoof gate), AZ-386 (ESKF baseline), AZ-387 (Smoothed history → FDR), AZ-388 (AC-5.2 fallback), AZ-389 (Orthorectifier → C6).
|
||||
**Version**: 1.0.0
|
||||
**Consumer tasks**: AZ-382 (iSAM2 + IncrementalFixedLagSmoother wiring), AZ-383 (Factor adds), AZ-384 (Marginals + outputs), AZ-385 (Source-label + spoof gate), AZ-386 (ESKF baseline), AZ-387 (Smoothed history → FDR), AZ-388 (AC-5.2 fallback), AZ-389 (Orthorectifier → C6), AZ-490 (set_takeoff_origin — operator-provided warm-start).
|
||||
**Version**: 1.1.0
|
||||
**Status**: active
|
||||
**Last Updated**: 2026-05-11
|
||||
**Module-layout home**: `src/gps_denied_onboard/components/c5_state/interface.py`, `src/gps_denied_onboard/components/c5_state/__init__.py`, `src/gps_denied_onboard/runtime_root/state_factory.py`
|
||||
@@ -23,6 +23,9 @@ The shared `ImuPreintegrator` (AZ-276), `SE3Utils` (AZ-277), and `WgsConverter`
|
||||
```python
|
||||
@runtime_checkable
|
||||
class StateEstimator(Protocol):
|
||||
# AZ-490 / ADR-010: operator-provided warm-start. MUST be called before any add_*.
|
||||
def set_takeoff_origin(self, origin: LatLonAlt, sigma_m: float) -> None: ...
|
||||
|
||||
def add_vio(self, vio: VioOutput) -> None: ...
|
||||
def add_pose_anchor(self, pose: PoseEstimate) -> None: ...
|
||||
def add_fc_imu(self, imu_window: ImuWindow) -> None: ...
|
||||
@@ -43,6 +46,8 @@ class StateEstimator(Protocol):
|
||||
8. **Spoof-rejection events ALWAYS land in FDR + GCS STATUSTEXT** — never silent (R07; C5-ST-01).
|
||||
9. **AC-5.2 fallback on 3 s no-estimate** — if `current_estimate()` would raise OR the keyframe window is empty for ≥3 s, downstream C8 emits FC IMU-only.
|
||||
10. **`covariance_6x6` is always SPD** — both strategies enforce; on numerical failure raise `EstimatorFatalError`.
|
||||
11. **`set_takeoff_origin(origin, sigma_m)` is a `INIT`-state-only entrypoint** (AZ-490, ADR-010). Calling it after the estimator has transitioned to `TRACKING` raises `StateEstimatorConfigError`. Inside `INIT` it is idempotent — re-invocation overwrites the prior with the new origin + sigma. `sigma_m` MUST be positive and finite; otherwise raise `StateEstimatorConfigError`. The origin is consumed as a Bayesian prior on the initial pose key (iSAM2: `PriorFactorPose3` with covariance = `diag(sigma_m^2, sigma_m^2, (2*sigma_m)^2, ...)` in ENU position + orientation order; ESKF: nominal-state seed + position-block covariance = `sigma_m^2 * I_3`).
|
||||
12. **Spoof-promotion gate has THREE clauses, not two** — Principle #11 amended. Re-promote a previously-spoofed FC GPS source only when ALL of: (i) FC `gps_health == STABLE_NON_SPOOFED` for ≥ `spoof_promotion_min_stable_s`, (ii) the next satellite-anchored frame agrees with the FC GPS within `spoof_promotion_visual_consistency_tol_m`, AND (iii) the FC's reported position is within `spoof_promotion_bounded_delta_m` (default 200 m) of the companion's last emitted `PoseEstimate`. The bounded-delta clause also gates the takeoff path when a Manifest `takeoff_origin` is present.
|
||||
|
||||
### DTOs (in `_types/state.py`)
|
||||
|
||||
@@ -111,7 +116,10 @@ Config schema additions:
|
||||
- `config.state.keyframe_window_size` (int, default 15) — D-C5-3 K=10–20
|
||||
- `config.state.spoof_promotion_min_stable_s` (float, default 10.0) — AC-NEW-2
|
||||
- `config.state.spoof_promotion_visual_consistency_tol_m` (float, default 30.0) — AC-NEW-8
|
||||
- `config.state.spoof_promotion_bounded_delta_m` (float, default 200.0) — Principle #11 amended, ADR-010
|
||||
- `config.state.no_estimate_fallback_s` (float, default 3.0) — AC-5.2
|
||||
- `config.state.default_takeoff_origin_sigma_horiz_m` (float, default 50.0) — AZ-490 default horizontal sigma when caller omits
|
||||
- `config.state.default_takeoff_origin_sigma_vert_m` (float, default 100.0) — AZ-490 default vertical sigma when caller omits
|
||||
|
||||
## Test expectations summarised by Invariant
|
||||
|
||||
@@ -127,6 +135,11 @@ Config schema additions:
|
||||
| 8 | Spoof-rejection logging | FDR + GCS STATUSTEXT both fire on every gate decision |
|
||||
| 9 | AC-5.2 timeout | 3 s no estimate → fallback signal emitted |
|
||||
| 10 | SPD covariance | every emitted `covariance_6x6` is SPD |
|
||||
| 11a | `set_takeoff_origin` after `TRACKING` | raises `StateEstimatorConfigError` |
|
||||
| 11b | `set_takeoff_origin` with `sigma_m <= 0` or non-finite | raises `StateEstimatorConfigError` |
|
||||
| 11c | `set_takeoff_origin` twice in `INIT` | second call wins; covariance updated to new sigma |
|
||||
| 11d | First `current_estimate` after `set_takeoff_origin` + no sensor samples | returns `EstimatorOutput` with `position_wgs84 == origin`, `covariance_6x6` reflecting `sigma_m^2` in the position block |
|
||||
| 12 | Bounded-delta gate | FC GPS frame with |Δ| > 200 m vs last emitted `PoseEstimate` is rejected even when stable + non-spoofed for ≥ 10 s + visual-consistent |
|
||||
|
||||
## Producer-task / consumer-task split
|
||||
|
||||
|
||||
Reference in New Issue
Block a user