Files
gps-denied-onboard/.planning/REQUIREMENTS.md
T
Yuzviak a11ed15187 docs: add Phase 1 ADRs and update PROJECT.md with completed decisions
ADR 0002: hexagonal/ports-and-adapters architecture — components/ layout,
  protocol.py per component, composition root, core/ for concentrated math.
ADR 0003: @dataclass(slots=True, frozen=True) on hot path; Pydantic retained
  only at REST/config/DB boundaries. Pose/GPSPoint migration deferred to Phase 2.
ADR 0004: Stage 2 as independent iteration — own phases 1-6, own requirements,
  stage1 code treated as MVP starting capital.

PROJECT.md: Stage 2 Key Decisions updated from Pending → Accepted with Phase 1
  implementation notes, deferred work list, and final architecture summary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:23:09 +03:00

177 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Requirements: GPS-Denied Onboard Navigation System — Stage 2
**Defined:** 2026-05-10
**Stage:** 2 (independent iteration)
**Branch:** `stage2` (HEAD = stage1; v1.0 archived)
**Core Value:** The flight controller must receive valid MAVLink GPS_INPUT at 5-10Hz with position accuracy ≤50m for 80% of frames — without this, the UAV cannot navigate in GPS-denied airspace.
---
## Stage 2 Requirements
Stage 2 is a self-contained iteration. Phases are numbered 16 within this stage. Stage 1 work (its 36 v1 requirements + 7 phases) is archived in `.planning/archive/v1.0/` as starting capital, not as active backlog.
The stage 1 codebase (ESKF + cuVSLAM + GPR + MAVLink + pipeline + 195 passing tests) is treated as MVP — refactoring is allowed and expected. Concept-level ideas from the parallel `try02` branch are re-implemented (not merged).
### ARCH — Hexagonal architecture & composition
- [ ] **ARCH-01**: Codebase reorganized to `src/gps_denied/components/{vio, satellite_matcher, gpr, anchor_verifier, safety_state, flight_recorder, mavlink_io, coordinate_transforms}/`, each containing `protocol.py` + concrete implementations + (where applicable) `native/` for backend bridges
- [ ] **ARCH-02**: Hot-path data types (`FrameState`, `IMUSample`, `PositionEstimate`, `VOEstimate`, `SatelliteAnchor`) implemented as `@dataclass(slots=True, frozen=True)` in `src/gps_denied/hot_types/`; Pydantic retained only for REST/config/DB boundary schemas
- [ ] **ARCH-03**: Explicit DI composition root `src/gps_denied/pipeline/composition.py` exposes `build_pipeline(env: Literal["jetson", "x86_dev", "ci", "sitl"]) -> Pipeline` that wires environment-specific implementations
- [ ] **ARCH-04**: `core/` retained for concentrated math (ESKF, factor graph, RANSAC, coordinate transforms) — these stay as pure-function single files, NOT split into `interfaces.py + types.py + impl.py`
- [ ] **ARCH-05**: All component Protocols defined with `typing.Protocol`; concrete adapters implement them; `Pipeline` constructor takes Protocol-typed dependencies (no concrete imports inside pipeline orchestration)
- [ ] **ARCH-06**: Per-environment YAML configuration in `config/{jetson,x86_dev,ci,sitl}.yaml`, loaded via `pydantic-settings` into a typed `RuntimeConfig` model passed to `build_pipeline`
- [ ] **ARCH-07**: All 195 stage1 tests + 8 SITL skipped continue to pass after refactor; no regression in accuracy benchmarks
### AC — Formal acceptance criteria document
- [ ] **AC-01**: `_docs/00_problem/acceptance_criteria.md` rewritten with formal AC-1.x…AC-NEW-x list adapted from `try02` and validated against this project's actual constraints
- [ ] **AC-02**: Each AC entry includes (a) numeric thresholds, (b) validation method, (c) at least one test ID linking to `tests/`
- [ ] **AC-03**: Position accuracy AC (50m@80%, 20m@50%, anchor age tracking, drift bounds) bound to `tests/integration/accuracy/` and `tests/e2e/`
- [ ] **AC-04**: Failure-mode AC (visual blackout, spoofing promotion, dead reckoning, ≥3 disconnected segments) bound to `tests/blackbox/failure_modes/`
- [ ] **AC-05**: Real-time performance AC (<400ms p95 e2e, <8GB RAM, ≥5Hz GPS_INPUT output) bound to a benchmark harness producing CI-tracked metrics
- [ ] **AC-06**: Traceability matrix `.planning/AC-TRACEABILITY.md` generated linking every AC ID → test ID(s) → implementing component(s)
### SAFE — Safety anchor state machine
- [ ] **SAFE-01**: `components/safety_state/SafetyAnchorStateMachine` owns authoritative `source_label ∈ {satellite_anchored, vo_extrapolated, dead_reckoned}` for every emitted `PositionEstimate`
- [ ] **SAFE-02**: Covariance growth is monotonic in non-anchored modes; resets only on accepted satellite anchor
- [ ] **SAFE-03**: `anchor_age_ms` recorded on every estimate; transitions to `vo_extrapolated` after configurable max-age threshold
- [ ] **SAFE-04**: State machine receives anchor decisions from `AnchorVerifier`, never raw VPR top-K — bad candidates cannot poison the state
- [ ] **SAFE-05**: Tile write eligibility flag exposed (`can_persist_tile: bool`) — false in `dead_reckoned` mode to prevent corrupt tile cache writes
- [ ] **SAFE-06**: Unit tests cover all 9 state transitions; property-based test asserts covariance never decreases without an accepted anchor
### VERIFY — Geometry-gated anchor verification
- [ ] **VERIFY-01**: `components/anchor_verifier/GeometryGatedAnchorVerifier` accepts/rejects satellite candidate matches based on configurable gates: min inliers, max mean reprojection error (px), max homography condition number
- [ ] **VERIFY-02**: Rejection reason string emitted on every reject (`"too_few_inliers"`, `"mre_above_threshold"`, `"degenerate_homography"`, `"freshness_expired"`)
- [ ] **VERIFY-03**: Freshness check integrates with sector classification (active-conflict <6mo, stable-rear <12mo) — expired tiles produce `freshness_expired` reject
- [ ] **VERIFY-04**: Verifier benchmark mode evaluates multiple matcher profiles on the same frame for offline comparison
- [ ] **VERIFY-05**: Unit tests cover each gate independently; integration test with real Azaion frame verifies end-to-end accept/reject
### FDR — Flight data recorder
- [ ] **FDR-01**: `components/flight_recorder/FlightRecorder` Protocol with `append_event(event)` and `export() -> FdrExportResult`
- [ ] **FDR-02**: `InMemoryFlightRecorder` impl with bounded segments and configurable segment+storage byte limits
- [ ] **FDR-03**: `DiskFlightRecorder` impl writing append-only JSONL segments under `data/fdr/{flight_id}/segment-NNNN.jsonl`
- [ ] **FDR-04**: Health states `ok / degraded (≥90% storage) / critical (limit reached)` exposed via `health` property
- [ ] **FDR-05**: Pipeline emits FDR events at every state transition, anchor decision, MAVLink send, and pipeline error
- [ ] **FDR-06**: AC-NEW-3 forensic-thumbnail rate (≤0.1Hz on tile-generation failures) wired through FDR with size budget enforcement
### VPR — Conditional + multi-scale visual place recognition
- [ ] **VPR-01**: VPR retrieval triggered conditionally — DINOv2 forward runs only on re-loc triggers (cold start, sharp turn AC-3.2, σ_xy > 50m, VO failure ≥2 frames, disconnected segment AC-3.3); steady-state uses geometric prior (IMU+VO predicted position) ranking by distance
- [ ] **VPR-02**: VPR chunks decoupled from storage tiles — chunks sized to ground footprint (600-800m at deployment altitude band) with 40-50% overlap; any frame footprint falls fully inside ≥1 chunk
- [ ] **VPR-03**: Multi-scale FAISS index — fine-scale (z=20-derived) + coarse-scale (z=17 or z=18) descriptor sets; coarse used in active-conflict sectors for change-robust retrieval
- [ ] **VPR-04**: Dynamic top-K — K=5 in stable sectors with σ_xy ≤ 20m, K=20 in active-conflict, K=50 on expanding-window fallback
- [ ] **VPR-05**: Chunking and indexing integrated into existing `chunk_manager.py`/`gpr.py` without breaking stage1 GPR API contracts
### MAVOUT — MAVLink output: source labels, dual-channel scaffold
- [ ] **MAVOUT-01**: Every emitted `GPS_INPUT` includes `source_label`, `anchor_age_ms`, `covariance_semimajor_m` propagated from `PositionEstimate` (mapped into `horiz_accuracy` and a custom STATUSTEXT for label/age)
- [ ] **MAVOUT-02**: `ODOMETRY` emitter scaffolded behind feature flag (`config.mavlink.odometry_enabled`); flag is false in stage 2; integration test asserts ODOMETRY is intentionally absent on the wire
- [ ] **MAVOUT-03**: Spoofing-promotion latency monitor — listens to `GPS_RAW_INT`/`EKF_STATUS_REPORT`/`SYS_STATUS`; promotes own estimate to FC primary within <3s when real-GPS health rolling avg < threshold; emits `STATUSTEXT` on every promotion/demotion
- [ ] **MAVOUT-04**: Visual blackout handling — pipeline switches to `dead_reckoned` within ≤1 processed frame OR ≤400ms when camera produces no usable signal; emits `VISUAL_BLACKOUT_IMU_ONLY` STATUSTEXT @ 1-2Hz
### FIXTURE — Real-flight integration fixture (Azaion 10.05.2026)
- [ ] **FIXTURE-01**: `tests/integration/azaion_flight/` integration test suite consuming `Data/Azaion/10.05.2026/` (tlog + cropped EO video + MAVLink CSV)
- [ ] **FIXTURE-02**: Preprocessing script `scripts/prep_azaion_fixture.py` producing — (a) HUD-stripped EO frames at 0.7 fps, (b) IMU/GPS/ATTITUDE CSV from tlog, (c) timestamp-aligned manifest
- [ ] **FIXTURE-03**: MAVLink replay test — feed tlog through `MAVLinkBridge` parser, assert all `GLOBAL_POSITION_INT`/`RAW_IMU`/`ATTITUDE` messages decoded without error
- [ ] **FIXTURE-04**: ESKF real-IMU smoke test — replay IMU samples through `ESKFCore.predict`, assert no NaN/Inf, bounded covariance growth
- [ ] **FIXTURE-05**: VO smoke test on cropped EO frames using ORB-SLAM3 backend — assert ≥30% frame registration success
- [ ] **FIXTURE-06**: GPS-denial simulation — mask `GPS_RAW_INT` for t∈[180s, 280s], replay rest of stream, assert pipeline switches to `vo_extrapolated` and back to `satellite_anchored` correctly
- [ ] **FIXTURE-07**: Azaion fixture documented in `_docs/00_problem/fixtures.md` with ground-truth references and known limitations (low altitude, multirotor dynamics, HUD overlay)
### TEST — Test taxonomy & infrastructure
- [ ] **TEST-01**: `tests/` reorganized to `tests/{unit,integration,blackbox,sitl,e2e}/`; existing tests redistributed by category
- [ ] **TEST-02**: `pyproject.toml` test markers updated — `pytest -m unit` / `-m integration` / etc.; CI runs unit+integration on every push, blackbox on PR, sitl+e2e nightly
- [ ] **TEST-03**: AC traceability auto-generated — pytest plugin tags each test with `@pytest.mark.ac("AC-1.1")`; `scripts/gen_ac_traceability.py` produces the matrix in `.planning/AC-TRACEABILITY.md`
### OBS — Observability & tooling
- [ ] **OBS-01**: Structured JSON logging via `structlog` with `correlation_id` (frame_id) propagated through pipeline; Pydantic logging schemas at boundaries
- [ ] **OBS-02**: CLI tool `gps_denied` (typer-based) with subcommands — `replay --tlog ... --video ...`, `benchmark --scenario ...`, `bench-ac AC-1.1` for AC-driven benchmark runs
- [ ] **OBS-03**: Per-environment Docker images split — `Dockerfile.x86_dev` for CI/dev, `Dockerfile.jetson` (multi-stage with TRT engine prebuild step) for hardware
---
## Stage 3 candidates (parking lot)
- Mid-flight tile generation + write-back to Azaion Satellite Service (AC-8.4)
- On-device hardware validation on Jetson Orin Nano Super
- Dual-channel MAVLink ODOMETRY enabled (depends on ArduPilot fixes for EKF3 source switching)
- AC-NEW-1 cold-boot time-to-first-fix bench (<30s, 50× cold reboot)
- BASALT VIO backend evaluation (only if cuVSLAM hits a blocker)
## Out of Scope (Stage 2)
- Migration to PostgreSQL (SQLite remains embedded default; Postgres optional for ground station only)
- Folder-per-component layout for `core/` math files (ESKF/factor graph stay concentrated)
- Real microservices with separate processes / IPC
- Pydantic on per-frame hot path (dataclasses replace it)
- Mobile/web ground station UI
- Multi-UAV coordination
## Traceability
Populated by roadmapper on 2026-05-10. Test IDs will be filled in by `/gsd:plan-phase` and `/gsd:implement` as each phase produces concrete tests.
| REQ | Phase | Tests |
|-----|-------|-------|
| ARCH-01 | Phase 1 | _pending plan-phase_ |
| ARCH-02 | Phase 1 | _pending plan-phase_ |
| ARCH-03 | Phase 1 | _pending plan-phase_ |
| ARCH-04 | Phase 1 | _pending plan-phase_ |
| ARCH-05 | Phase 1 | _pending plan-phase_ |
| ARCH-06 | Phase 1 | _pending plan-phase_ |
| ARCH-07 | Phase 1 | _pending plan-phase_ |
| AC-01 | Phase 2 | _pending plan-phase_ |
| AC-02 | Phase 2 | _pending plan-phase_ |
| AC-03 | Phase 2 | _pending plan-phase_ |
| AC-04 | Phase 2 | _pending plan-phase_ |
| AC-05 | Phase 2 | _pending plan-phase_ |
| AC-06 | Phase 2 | _pending plan-phase_ |
| TEST-01 | Phase 2 | _pending plan-phase_ |
| TEST-02 | Phase 2 | _pending plan-phase_ |
| TEST-03 | Phase 2 | _pending plan-phase_ |
| OBS-01 | Phase 2 | _pending plan-phase_ |
| SAFE-01 | Phase 3 | _pending plan-phase_ |
| SAFE-02 | Phase 3 | _pending plan-phase_ |
| SAFE-03 | Phase 3 | _pending plan-phase_ |
| SAFE-04 | Phase 3 | _pending plan-phase_ |
| SAFE-05 | Phase 3 | _pending plan-phase_ |
| SAFE-06 | Phase 3 | _pending plan-phase_ |
| VERIFY-01 | Phase 3 | _pending plan-phase_ |
| VERIFY-02 | Phase 3 | _pending plan-phase_ |
| VERIFY-03 | Phase 3 | _pending plan-phase_ |
| VERIFY-04 | Phase 3 | _pending plan-phase_ |
| VERIFY-05 | Phase 3 | _pending plan-phase_ |
| VPR-01 | Phase 4 | _pending plan-phase_ |
| VPR-02 | Phase 4 | _pending plan-phase_ |
| VPR-03 | Phase 4 | _pending plan-phase_ |
| VPR-04 | Phase 4 | _pending plan-phase_ |
| VPR-05 | Phase 4 | _pending plan-phase_ |
| FDR-01 | Phase 4 | _pending plan-phase_ |
| FDR-02 | Phase 4 | _pending plan-phase_ |
| FDR-03 | Phase 4 | _pending plan-phase_ |
| FDR-04 | Phase 4 | _pending plan-phase_ |
| FDR-05 | Phase 4 | _pending plan-phase_ |
| FDR-06 | Phase 4 | _pending plan-phase_ |
| MAVOUT-01 | Phase 5 | _pending plan-phase_ |
| MAVOUT-02 | Phase 5 | _pending plan-phase_ |
| MAVOUT-03 | Phase 5 | _pending plan-phase_ |
| MAVOUT-04 | Phase 5 | _pending plan-phase_ |
| FIXTURE-01 | Phase 6 | _pending plan-phase_ |
| FIXTURE-02 | Phase 6 | _pending plan-phase_ |
| FIXTURE-03 | Phase 6 | _pending plan-phase_ |
| FIXTURE-04 | Phase 6 | _pending plan-phase_ |
| FIXTURE-05 | Phase 6 | _pending plan-phase_ |
| FIXTURE-06 | Phase 6 | _pending plan-phase_ |
| FIXTURE-07 | Phase 6 | _pending plan-phase_ |
| OBS-02 | Phase 6 | _pending plan-phase_ |
| OBS-03 | Phase 6 | _pending plan-phase_ |
**Coverage:** 52/52 requirements mapped. No orphans, no duplicates.