mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 09:01:14 +00:00
[AZ-263] [AZ-264] [AZ-265] Decompose: layout, helpers epic, replay epic
Decompose Step 1 + Step 1.5 + new cycle-1 epics: - Step 1 (Bootstrap): AZ-263 spec at _docs/02_tasks/todo/. Single top-level Python package src/gps_denied_onboard/ + nested components/ subpackage per user feedback (replaces earlier src/gps_denied/ + sibling src/components/ split). - Step 1.5 (Module Layout): _docs/02_document/module-layout.md is the file-ownership map consumed by /implement Step 4. Covers all 14 components + cross-cuttings (_types, config, logging, fdr_client, helpers x8, frame_source, clock, runtime_root, cli/replay, healthcheck), 5-layer layering, and the Build-Time Exclusion Map for all 4 binaries (airborne, research, operator-tooling, replay-cli). - New epic AZ-264 (E-CC-HELPERS): re-homes the 8 shared helpers from per-component child-issues into a single cross-cutting epic per the decompose skill cross-cutting rule. R14 (LightGlue circular dep) is structurally prevented because both C2.5 and C3 import gps_denied_onboard.helpers.lightglue_runtime. - New epic AZ-265 (E-DEMO-REPLAY): offline replay mode (video + tlog -> per-tick coordinate stream). 8 child tasks, 27-32 pts. Reuses C8 FcAdapter via TlogReplayFcAdapter strategy + new VideoFileFrameSource + JsonlReplaySink + compose_replay composition root + gps-denied-replay CLI + auto-sync via IMU take-off detection (per how_to_test.md). NO ROS dependency. - Plan Final report at FINAL_report.md. - _autodev_state.md updated with handoff notes for Step 2 execution in a fresh chat (~290 MCP calls expected; epic ordering documented). Step 2 task PLAN approved (97 implementation tasks across 18 epics) but EXECUTION deferred per user choice to a fresh chat. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
# GPS-Denied Onboard Pose Estimation — Planning Report
|
||||
|
||||
> Generated 2026-05-09 at the close of greenfield Plan (Step 3) — Final / Quality Checklist phase.
|
||||
> Sources: `architecture.md`, `glossary.md`, `system-flows.md`, `data_model.md`, `risk_mitigations.md`, `epics.md`, `tests/traceability-matrix.md`, `tests/*.md`, `components/**/*.md`, `common-helpers/*.md`, `deployment/*.md`.
|
||||
|
||||
## Executive Summary
|
||||
|
||||
A Jetson Orin Nano Super-hosted Python+C++ companion replaces real GPS for fixed-wing UAVs in GPS-denied/spoofed environments by fusing pre-flight-cached `satellite-provider` tiles, live nav-camera frames (3 Hz), and FC IMU/attitude (100–200 Hz) through a hierarchical pipeline `VIO → VPR → re-rank → matcher → AdHoP-conditional refinement → pose → state` and emits a WGS84 fix with honest 6×6 covariance + provenance label to the FC at 5 Hz, all inside a 400 ms p95 latency budget. Plan decomposes the system into **14 components + 8 common helpers**, **22 numbered system-level Mermaid flows across F1–F10**, **39 acceptance criteria + 20 restrictions** with **92.4 % inclusive / 89.8 % strict test coverage**, and **19 Jira epics (AZ-244..AZ-262)** with a total estimated effort of **~295–477 story points** (range across t-shirt sizes).
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Fixed-wing UAVs operating in eastern/southern Ukraine must continue to navigate when the FC's GPS is denied or actively spoofed. The companion provides a drop-in replacement WGS84 fix derived from pre-cached `satellite-provider` imagery + onboard vision + FC IMU, with honest covariance and a `{satellite_anchored | visual_propagated | dead_reckoned}` provenance label, while remaining read-only against `satellite-provider` in flight (post-landing tile uploads run from the operator workstation only). Mission profile: 8 h flights, ~60 km/h cruise, ≤1 km AGL, ≤400 km² total cached area, 25 W TDP, -20 °C to +50 °C envelope.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Single Python-with-C++-extensions monolithic process per binary track on the Jetson Orin Nano Super (8 GB shared LPDDR5, 25 W, JetPack 6.2). Cross-component coupling routes through a shared GTSAM substrate so posterior covariance is recovered natively (`Marginals.marginalCovariance`, D-C5-5 = (c)). Interface-first components live under `src/components/<component>/` with constructor-injected dependencies; per-binary CMake `BUILD_*` flags select which `Strategy` implementations are linked into each artifact (`production-binary` = OKVIS2 + KltRansac + UltraVPR + …; `research-binary` = all candidate strategies for the IT-12 comparative study). The operator-side Tile Manager (C11) is a separate binary excluded from the airborne CMake target — process-level isolation is the primary defence for ADR-004 (no in-flight `satellite-provider` I/O in either direction).
|
||||
|
||||
**Technology stack**: Python 3.10 + C++17, TensorRT 10.3 (JetPack 6.2 pin) / ONNX Runtime + TRT EP / pure PyTorch FP16 baseline; OpenCV ≥ 4.12.0; GTSAM iSAM2 + `IncrementalFixedLagSmoother`; FAISS HNSW; PostgreSQL 16 (`tiles` schema mirrored from `satellite-provider`); pymavlink + MAVLink 2.0 signing for ArduPilot, YAMSPy + INAV-Toolkit MSP2 for iNav.
|
||||
|
||||
**Deployment**: two execution tiers — Tier-1 workstation Docker (fast/cheap, AC-bound jobs that do not require Jetson hardware) and Tier-2 self-hosted Jetson runner (AC-NEW-1 / NFT-PERF / NFT-LIM); pre-flight + post-landing operator tooling runs on the operator workstation; companion image is bare JetPack 6.2 (no Docker on Tier-2 to keep INT8 calibration cache trustworthy per D-C10-6).
|
||||
|
||||
See `architecture.md` for the full ADR set (ADR-001..ADR-009), 12 architectural principles, and per-component intent statements.
|
||||
|
||||
## Component Summary
|
||||
|
||||
| # | Component | Purpose | Dependencies | Epic |
|
||||
|---|-----------|---------|-------------|------|
|
||||
| 01 | C1 VIO | Pluggable `VioStrategy` (OKVIS2 default, VINS-Mono research, KltRansac mandatory simple-baseline) producing per-frame `VioOutput` | C7, helpers (IMU preintegrator, SE3 utils) | AZ-254 |
|
||||
| 02 | C2 VPR | Pre-cached satellite-tile retrieval (UltraVPR primary, MegaLoc/MixVPR/SelaVPR/EigenPlaces/NetVLAD/SALAD candidates) behind `VprStrategy` | C6, C7 | AZ-255 |
|
||||
| 03 | C2.5 Re-rank | Single-pair LightGlue inlier-count rerank K=10 → N=3 | C2, C7, helper LightGlue runtime | AZ-256 |
|
||||
| 04 | C3 Matcher | DISK+LightGlue cross-domain matching + RANSAC + reprojection residual filter | C2.5, C7, helper RANSAC filter | AZ-257 |
|
||||
| 05 | C3.5 AdHoP | Conditional refinement when initial reprojection residual exceeds threshold | C3, C7 | AZ-258 |
|
||||
| 06 | C4 Pose | OpenCV `solvePnPRansac` (IPPE) wrapped in GTSAM `Marginals` for native 6×6 covariance | C3.5, C5 (shared GTSAM substrate) | AZ-259 |
|
||||
| 07 | C5 State | GTSAM iSAM2 + `IncrementalFixedLagSmoother` (K=10–20); spoof-promotion gate; AC-4.5 internal smoothing | C1, C4, C8 inbound, C13 | AZ-260 |
|
||||
| 08 | C6 Tile Cache | PostgreSQL btree spatial index + filesystem `./tiles/{zoom}/{x}/{y}.jpg` mirroring `satellite-provider`; FAISS HNSW VPR descriptor index | E-BOOT, E-CC-LOG, E-CC-CONF | AZ-250 |
|
||||
| 09 | C7 Inference Runtime | TensorRT 10.3 engines (Polygraphy / trtexec / IBuilderConfig hybrid); ORT+TRT EP fallback; PyTorch FP16 baseline | E-BOOT, E-CC-CONF, E-CC-FDR-CLIENT | AZ-249 |
|
||||
| 10 | C8 FC + GCS Adapter | `pymavlink` `GPS_INPUT` for ArduPilot (signed) + `MSP2_SENSOR_GPS` for iNav (unsigned, accepted residual risk); honest 6×6 → 2×2 covariance projection; GCS 1–2 Hz downsampled telemetry | C5, E-CC-CONF, E-CC-LOG | AZ-261 |
|
||||
| 11 | C10 Pre-flight Cache Provisioning | Builds model-derived cache (descriptors, engines, manifest, content hashes); F2 takeoff verifier; does NOT touch `satellite-provider` (network I/O lives in C11) | C6, C7, E-CC-LOG | AZ-252 |
|
||||
| 12 | C11 Tile Manager | Operator-side `TileDownloader` (pre-flight) + `TileUploader` (post-landing, gated `flight_state == ON_GROUND`); excluded from airborne image | C6, E-CC-CONF, E-CC-LOG | AZ-251 |
|
||||
| 13 | C12 Operator Pre-flight Tooling | CLI subcommands (`download`, `build-cache`, `upload-pending`, `reloc-confirm`); sector classification UI hook; FDR retrieval helpers | C10, C11, E-CC-LOG | AZ-253 |
|
||||
| 14 | C13 Flight Data Recorder | Per-flight ≤64 GB NVM ring (estimates + IMU + emitted MAVLink + health + mid-flight tiles + ≤0.1 Hz failed-tile thumbnails); raw nav/AI-cam frames excluded | E-BOOT, E-CC-LOG, E-CC-CONF, E-CC-FDR-CLIENT | AZ-248 |
|
||||
|
||||
**Cross-cutting epics** (not components, but shared concerns): E-BOOT (AZ-244), E-CC-LOG (AZ-245), E-CC-CONF (AZ-246), E-CC-FDR-CLIENT (AZ-247).
|
||||
**System-level test epic**: E-BBT (AZ-262) parents the FT/NFT scenarios across all components.
|
||||
|
||||
**Implementation order** (from dependency graph in `epics.md`):
|
||||
|
||||
1. **Phase 1 (foundation, parallel)**: E-BOOT (AZ-244) → E-CC-LOG (AZ-245), E-CC-CONF (AZ-246), E-CC-FDR-CLIENT (AZ-247).
|
||||
2. **Phase 2 (data + runtime + recorder)**: E-C13 (AZ-248), E-C7 (AZ-249), E-C6 (AZ-250).
|
||||
3. **Phase 3 (operator tooling chain)**: E-C11 (AZ-251) → E-C10 (AZ-252) → E-C12 (AZ-253).
|
||||
4. **Phase 4 (perception pipeline)**: E-C1 (AZ-254), E-C2 (AZ-255) → E-C2.5 (AZ-256) → E-C3 (AZ-257) → E-C3.5 (AZ-258) → E-C4 (AZ-259).
|
||||
5. **Phase 5 (state + emission)**: E-C5 (AZ-260) → E-C8 (AZ-261).
|
||||
6. **Phase 6 (system tests)**: E-BBT (AZ-262) — hardens behind every other epic.
|
||||
|
||||
## System Flows
|
||||
|
||||
| Flow | Description | Key Components |
|
||||
|------|-------------|---------------|
|
||||
| F1 | Pre-flight cache provisioning | C11 (TileDownloader), C6, C10, C12 |
|
||||
| F2 | Takeoff load (cold-start TTFF, AC-NEW-1) | C10, C7, C6, C8, C5 |
|
||||
| F3 | Steady-state per-frame estimation (the F3 hot path) | C1, C2, C2.5, C3, C3.5, C4, C5, C8, C13 |
|
||||
| F4 | Mid-flight tile generation + local cache write | C5 (orthorectifier subpath), C6, C13 |
|
||||
| F5 | Visual blackout + spoofed-GPS failsafe (AC-NEW-8) | C1, C5, C8 (STATUSTEXT) |
|
||||
| F6 | Sharp-turn / disconnected-segment re-localization | C2, C2.5, C3, C5 |
|
||||
| F7 | Spoofing-promotion via EKF source-set switch (AC-NEW-2) | C5 (SourceLabelStateMachine), C8 (AP D-C8-2) |
|
||||
| F8 | Companion reboot recovery | C13 (FDR replay), C5, C8 |
|
||||
| F9 | GCS telemetry stream (AC-6.1) | C8 (GcsAdapter) |
|
||||
| F10 | Post-landing tile upload (D-PROJ-2 contract) | C11 (TileUploader), C6, C12 |
|
||||
|
||||
Reference `system-flows.md` for the per-flow Mermaid sequence diagrams + flowcharts; `_docs/02_document/diagrams/components.drawio` is the visual companion for component boundaries.
|
||||
|
||||
## Risk Summary
|
||||
|
||||
| Level | Count | Key Risks |
|
||||
|-------|-------|-----------|
|
||||
| Critical | 0 | — |
|
||||
| High | 3 | R01 (D-PROJ-2 ingest endpoint not yet shipped), R03 (MAVLink-2.0 signing handshake no precedent — IT-3 gated), R11 (AC-NEW-4/AC-NEW-7 multi-flight statistical headroom — D-PROJ-3 deferred) |
|
||||
| Medium | 8 | R02 (ADR-004 process-isolation regression), R04 (TRT engine SM/JP/TRT mismatch), R05 (iSAM2 silent factor-add failure), R06 (VPR top-1 false positive), R07 (premature spoof re-promotion), R08 (tile freshness drift in active-conflict sectors), R10 (`Marginals` latency under thermal throttle), R12 (single deployment camera) |
|
||||
| Low | 3 | R09 (per-flight signing key compromise), R13 (FDR queue overrun), R14 (C2.5 ↔ C3 LightGlue circular dependency — resolved via shared helper) |
|
||||
|
||||
**Iterations completed**: 1 (initial Step 4 risk pass; R02 enforcement scope extended, R14 resolved via helper ownership).
|
||||
**All Critical/High risks mitigated**: Yes — R01 has e2e mock-suite-sat-service fixture + leftover tracking; R03 is gated by IT-3 with documented D-C8-2-FALLBACK options (ADR-008); R11 is mitigated by AC-text relaxation 2026-05-09 (Monte-Carlo-with-CI) plus D-PROJ-3 carryforward.
|
||||
|
||||
Reference `risk_mitigations.md` for the full register, per-risk trigger conditions, and detailed mitigations.
|
||||
|
||||
## Test Coverage
|
||||
|
||||
The test suite is organised as scenario specs (no source code yet). Per-component tests live under each component's `tests.md`; cross-component / system-level scenarios live under `_docs/02_document/tests/`.
|
||||
|
||||
### Per-component scenario counts (from each `components/*/tests.md`)
|
||||
|
||||
| Component | Component-internal tests file |
|
||||
|-----------|------------------------------|
|
||||
| C1 | `components/01_c1_vio/tests.md` |
|
||||
| C2 | `components/02_c2_vpr/tests.md` |
|
||||
| C2.5 | `components/03_c2_5_rerank/tests.md` |
|
||||
| C3 | `components/04_c3_matcher/tests.md` |
|
||||
| C3.5 | `components/05_c3_5_adhop/tests.md` |
|
||||
| C4 | `components/06_c4_pose/tests.md` |
|
||||
| C5 | `components/07_c5_state/tests.md` |
|
||||
| C6 | `components/08_c6_tile_cache/tests.md` |
|
||||
| C7 | `components/09_c7_inference/tests.md` |
|
||||
| C8 | `components/10_c8_fc_adapter/tests.md` |
|
||||
| C10 | `components/11_c10_provisioning/tests.md` |
|
||||
| C11 | `components/12_c11_tilemanager/tests.md` |
|
||||
| C12 | `components/13_c12_operator_tooling/tests.md` |
|
||||
| C13 | `components/14_c13_fdr/tests.md` |
|
||||
|
||||
### System-level scenario suites (`_docs/02_document/tests/`)
|
||||
|
||||
| File | Scenario family |
|
||||
|------|-----------------|
|
||||
| `tests/blackbox-tests.md` | FT-P-* (positive functional) + FT-N-* (negative functional) |
|
||||
| `tests/performance-tests.md` | NFT-PERF-* (Tier-2) |
|
||||
| `tests/resource-limit-tests.md` | NFT-LIM-* |
|
||||
| `tests/security-tests.md` | NFT-SEC-* |
|
||||
| `tests/resilience-tests.md` | NFT-RES-* |
|
||||
| `tests/test-data.md` | Fixture inventory (Derkachi flight + AerialVL S03 + e2e mock-suite-sat-service) |
|
||||
| `tests/environment.md` | Test environment + Tier-1/Tier-2 split |
|
||||
| `tests/traceability-matrix.md` | Single source of truth for AC ↔ scenario coverage |
|
||||
|
||||
### Acceptance Criteria + Restrictions coverage (from `tests/traceability-matrix.md`, revised 2026-05-09)
|
||||
|
||||
| Category | Total | Covered | PARTIAL | Not Covered | Coverage % (PARTIAL counted half) |
|
||||
|----------|-------|---------|---------|-------------|------------------------------------|
|
||||
| Acceptance Criteria | 39 | 35 | 2 | 2 | 92.3 % |
|
||||
| Restrictions | 20 | 18 | 1 | 1 | 92.5 % |
|
||||
| **Total** | **59** | **53** | **3** | **3** | **92.4 %** (strict 89.8 %) |
|
||||
|
||||
Both the inclusive reading (PARTIAL = covered) and the strict reading clear the 75 % gate with margin. Remaining PARTIAL / NOT COVERED rows: AC-8.6 scene-change subset (labeled change-pair dataset not available), AC-NEW-5 hot-soak chamber (physical hardware), AC-7.1 / AC-7.2 + RESTRICT-CAM-2 (no AI-camera fixture), RESTRICT-HW-2 chamber portion (paired with AC-NEW-5).
|
||||
|
||||
## Epic Roadmap
|
||||
|
||||
| Order | Epic | Component | T-shirt | Story points | Dependencies |
|
||||
|-------|------|-----------|---------|--------------|-------------|
|
||||
| 1 | AZ-244: E-BOOT — Bootstrap & Initial Structure | repo scaffolding | M | 13–21 | — |
|
||||
| 2 | AZ-245: E-CC-LOG — Structured JSON Logging | cross-cutting | S | 5–8 | E-BOOT |
|
||||
| 3 | AZ-246: E-CC-CONF — Configuration & Composition Root | cross-cutting | S | 5–8 | E-BOOT |
|
||||
| 4 | AZ-247: E-CC-FDR-CLIENT — FDR Producer Client | cross-cutting | M | 8–13 | E-BOOT, E-CC-LOG |
|
||||
| 5 | AZ-248: E-C13 — Flight Data Recorder | C13 | L | 21–34 | E-BOOT, E-CC-LOG, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 6 | AZ-249: E-C7 — On-Jetson Inference Runtime | C7 | L | 21–34 | E-BOOT, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 7 | AZ-250: E-C6 — Tile Cache + Spatial Index | C6 | M | 13–21 | E-BOOT, E-CC-LOG, E-CC-CONF |
|
||||
| 8 | AZ-251: E-C11 — Tile Manager | C11 | M | 13–21 | E-C6, E-CC-CONF, E-CC-LOG |
|
||||
| 9 | AZ-252: E-C10 — Pre-flight Cache Provisioning | C10 | M | 13–21 | E-C6, E-C7, E-CC-LOG |
|
||||
| 10 | AZ-253: E-C12 — Operator Pre-flight Tooling | C12 | M | 13–21 | E-C10, E-C11, E-CC-LOG |
|
||||
| 11 | AZ-254: E-C1 — Visual / Visual-Inertial Odometry | C1 | XL | 34–55 | E-BOOT, E-CC-FDR-CLIENT, E-C7 |
|
||||
| 12 | AZ-255: E-C2 — Visual Place Recognition | C2 | L | 21–34 | E-C6, E-C7, E-CC-FDR-CLIENT |
|
||||
| 13 | AZ-256: E-C2.5 — Inlier-based Re-rank | C2.5 | S | 5–8 | E-C2, E-C7, E-C6 (shared LightGlue helper) |
|
||||
| 14 | AZ-257: E-C3 — Cross-Domain Matcher | C3 | L | 21–34 | E-C2.5, E-C7 |
|
||||
| 15 | AZ-258: E-C3.5 — AdHoP-Conditional Refinement | C3.5 | M | 8–13 | E-C3, E-C7 |
|
||||
| 16 | AZ-259: E-C4 — Pose Estimator | C4 | M | 13–21 | E-C3.5, E-C5 (shared GTSAM substrate) |
|
||||
| 17 | AZ-260: E-C5 — State Estimator | C5 | XL | 34–55 | E-C1, E-C4 (shared graph), E-CC-FDR-CLIENT |
|
||||
| 18 | AZ-261: E-C8 — FC + GCS Adapter | C8 | L | 21–34 | E-C5, E-CC-CONF, E-CC-LOG |
|
||||
| 19 | AZ-262: E-BBT — Blackbox Tests (FT/NFT scenarios) | system-level tests | M | 13–21 | every component epic |
|
||||
|
||||
**Total estimated effort**: 295–477 story points across 19 epics (sum of t-shirt-band lower / upper bounds from `epics.md`). The two XL epics (E-C1 = AZ-254 and E-C5 = AZ-260) carry ~22 % of the upper-bound estimate alone.
|
||||
|
||||
## Key Decisions Made
|
||||
|
||||
| # | Decision | Rationale | Alternatives Rejected |
|
||||
|---|----------|-----------|----------------------|
|
||||
| 1 | Single Python+C++ monolith per binary track (ADR-001) | Latency budget (400 ms p95) + GTSAM substrate sharing + Jetson 8 GB shared memory | Multi-process (D-C1-1-SUB-A=(b)) — too much IPC overhead; ROS — adds runtime weight without flight-controller benefit |
|
||||
| 2 | Build-time exclusion of unused `Strategy` implementations via per-binary CMake `BUILD_*` flags (ADR-002) | Binary size on 8 GB shared Jetson, AC-NEW-1 30 s boot budget, accidental-selection risk | Runtime-only selection — bigger binary, larger attack surface, harder cold-start; component licenses do not drive this decision |
|
||||
| 3 | Shared GTSAM substrate between C4 (pose) and C5 (state) (ADR-003, D-C5-5 = (c)) | Native posterior covariance via `Marginals.marginalCovariance`; one numerical foundation | Independent EKF in C5 + Jacobian-only covariance — cannot deliver honest 6×6 |
|
||||
| 4 | Process-level isolation of operator-side Tile Manager (ADR-004) — C11 not linked into airborne image | AC-8.4 in-flight no-write; defeats reflection / config-error attack vectors | Runtime gate alone — reflection / DI bug could re-introduce code path |
|
||||
| 5 | Two execution tiers (ADR-005): Tier-1 workstation Docker + Tier-2 self-hosted Jetson | Cost (Jetson runner saturated) + AC-NEW-1 must run on real hardware | Single-tier Jetson-only — too slow / expensive for pre-merge CI |
|
||||
| 6 | D-CROSS-LATENCY-1 hybrid (ADR-006): K=3 baseline auto-degrades to K=2 + Jacobian covariance under thermal throttle | Preserves AC-4.1 at +50 °C ambient at the cost of ~5–10 % accuracy | Static K=2 — wastes covariance precision in nominal conditions; static K=3 — blows the budget under throttle |
|
||||
| 7 | Spoof-promotion gate (ADR-008): re-promote only after ≥10 s `gps_health == STABLE_NON_SPOOFED` AND visual-consistency check passes | AC-NEW-2 / AC-NEW-8 floor; defends against attacker turning spoof off briefly | Time-only gate (≥30 s) — slower mission recovery, still fool-able by transient honest GPS during attack |
|
||||
| 8 | Interface-first components with constructor injection (ADR-009) | Multiple interchangeable strategies on the same interface (C1 has 3, C2 has 6+, C8 has 2) — selection via composition root only | Service-locator / global registry — couples runtime to import order, breaks tests, breaks build-time exclusion |
|
||||
| 9 | OpenCV pinned to ≥4.12.0 (Mode B Fact #112) | CVE-2025-53644 mitigation; IPPE flags for solvePnP D-C4-1=(b) | Older OpenCV — known CVE; newer beta — not pinned in JetPack 6.2 |
|
||||
| 10 | DISK + LightGlue replaces SuperPoint+SuperGlue for cross-domain matching (D-C3-1 = (a)) | License — SP+SG is Magic Leap noncommercial canonical; DISK+LightGlue is BSD-3-Clause | SuperPoint+SuperGlue — license incompatibility; XFeat — promising but unproven cross-domain |
|
||||
| 11 | AC-NEW-4 / AC-NEW-7 text relaxed 2026-05-09 to Monte-Carlo-over-current-data with stated 95 % CI | D-PROJ-3 multi-flight fixture acquisition is out of scope this cycle; literal "≥100 flights" wording cannot be met | Block planning on D-PROJ-3 — cycle would not close; relaxed wording is documented residual risk in R11 |
|
||||
|
||||
## Open Questions
|
||||
|
||||
| # | Question | Impact | Assigned To |
|
||||
|---|----------|--------|-------------|
|
||||
| 1 | D-PROJ-2: parent-suite `satellite-provider` ingest endpoint + multi-flight voting layer not yet implemented service-side | F10 post-landing upload depends on this; R01; e2e mock-suite-sat-service fixture stands in for tests | Parent suite (cross-workspace; tracked in `_docs/_process_leftovers/2026-05-09_satellite-provider-design-tasks.md`) |
|
||||
| 2 | D-PROJ-3: multi-flight fixture acquisition (Maxar Open Data Ukraine + AerialVL S03 + own multi-flight data) | AC-NEW-4 / AC-NEW-7 statistical headroom; R11 carryforward to next Plan cycle | Project lead |
|
||||
| 3 | D-C8-2 runtime gate: companion-driven `MAV_CMD_SET_EKF_SOURCE_SET` switch lacks deployed precedent | R03 — IT-3 ArduPilot SITL validation is the lock gate; D-C8-2-FALLBACK options recorded in ADR-008 | Onboard team (gated by IT-3) |
|
||||
| 4 | D-C2-12: DINOv2-feature-based matcher evaluation as a possible C3 backbone alternative | Could close D-C3-1 retrain cost concern; carryforward to next Plan cycle | Onboard team |
|
||||
| 5 | AC-NEW-5 hot-soak chamber test (25 W @ +50 °C, 8 h, no throttle) | Physical hardware required; release-tag-blocking gate; out of scope for data-acquisition this cycle | Project lead |
|
||||
| 6 | AC-7.1 / AC-7.2 / RESTRICT-CAM-2: AI-camera object localization pipeline | Out of scope for this cycle; deferred to a follow-up Plan cycle scoped to AI-camera integration | Future cycle (next Plan) |
|
||||
| 7 | AC-8.6 scene-change subset: needs labeled change-pair dataset | Stale-tile match in active-conflict sectors — scene-change recall unmeasured (scale-ratio half is covered) | Future cycle |
|
||||
|
||||
## Artifact Index
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `architecture.md` | System architecture, ADR set (ADR-001..ADR-009), 12 architectural principles, technology stack |
|
||||
| `glossary.md` | Canonical project terminology (locked Phase 2a.0) |
|
||||
| `system-flows.md` | F1–F10 Mermaid sequence diagrams + flowcharts + per-flow data flow tables |
|
||||
| `data_model.md` | PostgreSQL `tiles` (mirrored from `satellite-provider`) + flight + manifest schema; FAISS / TRT artifact layout; FDR record schema |
|
||||
| `risk_mitigations.md` | 14-row risk register (R01..R14) with per-risk mitigation + contingency |
|
||||
| `epics.md` | Local plan E-* IDs, t-shirt sizes, story-point ranges, dependencies, child-issue breakdowns; canonical `E-*` ↔ `AZ-NN` mapping |
|
||||
| `components/01_c1_vio/description.md` … `components/14_c13_fdr/description.md` | Per-component spec (interface, implementations, dependencies, ACs, NFRs, risks) |
|
||||
| `components/01_c1_vio/tests.md` … `components/14_c13_fdr/tests.md` | Per-component test scenarios |
|
||||
| `common-helpers/01_helper_imu_preintegrator.md` … `common-helpers/08_helper_descriptor_normaliser.md` | Shared helpers (IMU preintegrator, SE3 utils, LightGlue runtime, WGS converter, SHA-256 sidecar, engine filename schema, RANSAC filter, descriptor normaliser) |
|
||||
| `tests/traceability-matrix.md` | AC ↔ scenario coverage; restriction ↔ scenario coverage |
|
||||
| `tests/blackbox-tests.md`, `tests/performance-tests.md`, `tests/security-tests.md`, `tests/resource-limit-tests.md`, `tests/resilience-tests.md` | System-level FT / NFT scenario specs |
|
||||
| `tests/test-data.md`, `tests/environment.md` | Fixture inventory + Tier-1/Tier-2 environment definition |
|
||||
| `deployment/containerization.md`, `deployment/ci_cd_pipeline.md`, `deployment/environment_strategy.md`, `deployment/observability.md`, `deployment/deployment_procedures.md` | Deployment plan |
|
||||
| `diagrams/components.drawio` | Component-level diagram (visual companion to `components/`) |
|
||||
| `diagrams/flows/00_index.md` | Per-flow index pointing into `system-flows.md` |
|
||||
|
||||
## Quality Checklist Verification
|
||||
|
||||
All 8 checklist sections from `.cursor/skills/plan/steps/07_quality-checklist.md` pass for this cycle:
|
||||
|
||||
- **Blackbox Tests**: every AC + restriction in `tests/traceability-matrix.md`; restrictions verified by ≥1 scenario each (RESTRICT-CAM-2 deferred with documented mitigation); positive (FT-P) + negative (FT-N) balanced; Tier-1 Docker + Tier-2 Jetson defined in `tests/environment.md`; consumer treats system as black box (FC + GCS contracts only); CI integration in `deployment/ci_cd_pipeline.md`.
|
||||
- **Architecture**: covers all `solution.md` capabilities; technology choices justified in § 2 of `architecture.md` + ADR-001..ADR-009; deployment model in `deployment/`; blackbox findings F6–F10 reflected (Tier split, mock-suite-sat-service, two-binary CI emit).
|
||||
- **Data Model**: every persistent entity from `architecture.md` § 4 defined in `data_model.md`; relationships have explicit FK cardinality; migration strategy is additive-only with ADR-recorded deprecation requirement; seed data = `satellite-provider` mirror of `tiles`; backward compatibility = `tiles` schema frozen on canonical columns.
|
||||
- **Deployment**: containerization covers Tier-1 workstation Docker; CI/CD pipeline includes lint, test, security (SBOM diff, ASan), build, deploy stages; environment strategy covers dev / staging / production (Tier-1 + Tier-2 + Jetson production image); observability covers structured JSON logging + FDR + GCS STATUSTEXT; deployment procedures include rollback (per-flight key zeroisation, FDR rollover) and health checks (F2 takeoff verifier).
|
||||
- **Components**: 14 components each follow SRP; no circular dependencies (R14 resolved via shared LightGlue helper); inter-component interfaces defined as `Protocol`/`ABC` per ADR-009; no orphan components — every component appears in at least one F1–F10 flow; every blackbox scenario traceable through the component dependency graph.
|
||||
- **Risks**: all High risks have mitigations + contingency (R01, R03, R11); mitigations reflected in `architecture.md` (ADR-004 enforcement scope, ADR-008 D-C8-2-FALLBACK), `tests/security-tests.md` (NFT-SEC-02 egress test), and `tests/traceability-matrix.md` (AC-text relaxation).
|
||||
- **Tests**: every AC + restriction covered by ≥1 test (with PARTIAL/NOT-COVERED items having documented mitigation); 4 test types per component represented where applicable (unit/contract inside per-component `tests.md` + integration/performance/security/resilience at system level); test data management defined in `tests/test-data.md`.
|
||||
- **Epics**: E-BOOT (AZ-244) "Bootstrap & Initial Structure" present; E-BBT (AZ-262) "Blackbox Tests" present; every component maps to a component epic (C1..C8, C10..C13 → AZ-254, AZ-255, AZ-256, AZ-257, AZ-258, AZ-259, AZ-260, AZ-261, AZ-252, AZ-251, AZ-253, AZ-248); dependency order matches `epics.md` "Implementation order"; acceptance criteria are measurable (per-epic IT/PT/ST IDs trace back to `traceability-matrix.md`).
|
||||
+214
-39
@@ -1,6 +1,6 @@
|
||||
# Work-Item Epics — gps-denied-onboard Plan cycle 1
|
||||
|
||||
This file is the local epic draft for Plan Step 6. Tracker IDs (`AZ-XXX`) are populated when each epic is created in Jira (project `AZ`). Until then, every entry carries `Tracker: pending`.
|
||||
This file is the local epic draft for Plan Step 6. Tracker IDs (`AZ-XXX`) are now populated for every epic — they live in Jira project `AZ`. The canonical `E-*` ↔ `AZ-NN` mapping below is the source of truth referenced from each Jira epic's description.
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -10,29 +10,35 @@ This file is the local epic draft for Plan Step 6. Tracker IDs (`AZ-XXX`) are po
|
||||
- **Cross-cutting epics** parent exactly one shared implementation task; component epics consuming the concern declare a dependency, never re-implement locally.
|
||||
- **Dependency rule**: no epic depends on a later one in this index.
|
||||
|
||||
## Decompose-time amendment (cycle 1, dated 2026-05-10)
|
||||
|
||||
Row 20 (E-CC-HELPERS / AZ-264) was added during Decompose Step 2 to comply with the cross-cutting rule. The 8 shared helpers (`ImuPreintegrator`, `SE3Utils`, `LightGlueRuntime`, `WgsConverter`, `Sha256Sidecar`, `EngineFilenameSchema`, `RansacFilter`, `DescriptorNormaliser`) were originally listed as child issues inside their largest-consumer component epics (e.g., `ImuPreintegrator` under E-C1 child #5, `LightGlueRuntime` under E-C2.5 child #2). Those child-issue listings are now superseded — helper ownership moves to E-CC-HELPERS, and component epics consume helpers as dependencies. The original component epic descriptions in Jira still reference the helpers in their child-issue tables; those will be reconciled at the next epic-edit pass (or at Step 4 cross-verification).
|
||||
|
||||
## Index
|
||||
|
||||
| # | Epic ID | Title | Type | Tracker | T-shirt | Story Pts | Depends on |
|
||||
|---|---------|-------|------|---------|---------|-----------|------------|
|
||||
| 1 | E-BOOT | Bootstrap & Initial Structure | bootstrap | Tracker: pending | M | 13–21 | — |
|
||||
| 2 | E-CC-LOG | Cross-Cutting: Structured JSON Logging | cross-cutting | Tracker: pending | S | 5–8 | E-BOOT |
|
||||
| 3 | E-CC-CONF | Cross-Cutting: Configuration & Composition Root | cross-cutting | Tracker: pending | S | 5–8 | E-BOOT |
|
||||
| 4 | E-CC-FDR-CLIENT | Cross-Cutting: FDR Producer Client (lock-free queue + record schema) | cross-cutting | Tracker: pending | M | 8–13 | E-BOOT, E-CC-LOG |
|
||||
| 5 | E-C13 | C13 Flight Data Recorder (writer thread + segments + cap) | component | Tracker: pending | L | 21–34 | E-BOOT, E-CC-LOG, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 6 | E-C7 | C7 On-Jetson Inference Runtime | component | Tracker: pending | L | 21–34 | E-BOOT, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 7 | E-C6 | C6 Tile Cache + Spatial Index | component | Tracker: pending | M | 13–21 | E-BOOT, E-CC-LOG, E-CC-CONF |
|
||||
| 8 | E-C11 | C11 Tile Manager (TileDownloader + TileUploader) | component | Tracker: pending | M | 13–21 | E-C6, E-CC-CONF, E-CC-LOG |
|
||||
| 9 | E-C10 | C10 Pre-flight Cache Provisioning | component | Tracker: pending | M | 13–21 | E-C6, E-C7, E-CC-LOG |
|
||||
| 10 | E-C12 | C12 Operator Pre-flight Tooling | component | Tracker: pending | M | 13–21 | E-C10, E-C11, E-CC-LOG |
|
||||
| 11 | E-C1 | C1 Visual / Visual-Inertial Odometry | component | Tracker: pending | XL | 34–55 | E-BOOT, E-CC-FDR-CLIENT, E-C7 |
|
||||
| 12 | E-C2 | C2 Visual Place Recognition | component | Tracker: pending | L | 21–34 | E-C6, E-C7, E-CC-FDR-CLIENT |
|
||||
| 13 | E-C2.5 | C2.5 Inlier-based Re-rank | component | Tracker: pending | S | 5–8 | E-C2, E-C7, E-C6 (LightGlue helper shared with C3) |
|
||||
| 14 | E-C3 | C3 Cross-Domain Matcher | component | Tracker: pending | L | 21–34 | E-C2.5, E-C7 |
|
||||
| 15 | E-C3.5 | C3.5 AdHoP-Conditional Refinement | component | Tracker: pending | M | 8–13 | E-C3, E-C7 |
|
||||
| 16 | E-C4 | C4 Pose Estimator | component | Tracker: pending | M | 13–21 | E-C3.5, E-C5 (shared GTSAM substrate; co-developed) |
|
||||
| 17 | E-C5 | C5 State Estimator | component | Tracker: pending | XL | 34–55 | E-C1, E-C4 (shared graph), E-CC-FDR-CLIENT |
|
||||
| 18 | E-C8 | C8 FC + GCS Adapter | component | Tracker: pending | L | 21–34 | E-C5, E-CC-CONF, E-CC-LOG |
|
||||
| 19 | E-BBT | Blackbox Tests (FT/NFT scenarios) | tests | Tracker: pending | M | 13–21 | every component epic ships its component-internal tests under its own epic; this one parents the suite-level FT/NFT scenarios in `_docs/02_document/tests/*.md` |
|
||||
| 1 | E-BOOT | Bootstrap & Initial Structure | bootstrap | AZ-244 | M | 13–21 | — |
|
||||
| 2 | E-CC-LOG | Cross-Cutting: Structured JSON Logging | cross-cutting | AZ-245 | S | 5–8 | E-BOOT |
|
||||
| 3 | E-CC-CONF | Cross-Cutting: Configuration & Composition Root | cross-cutting | AZ-246 | S | 5–8 | E-BOOT |
|
||||
| 4 | E-CC-FDR-CLIENT | Cross-Cutting: FDR Producer Client (lock-free queue + record schema) | cross-cutting | AZ-247 | M | 8–13 | E-BOOT, E-CC-LOG |
|
||||
| 5 | E-C13 | C13 Flight Data Recorder (writer thread + segments + cap) | component | AZ-248 | L | 21–34 | E-BOOT, E-CC-LOG, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 6 | E-C7 | C7 On-Jetson Inference Runtime | component | AZ-249 | L | 21–34 | E-BOOT, E-CC-CONF, E-CC-FDR-CLIENT |
|
||||
| 7 | E-C6 | C6 Tile Cache + Spatial Index | component | AZ-250 | M | 13–21 | E-BOOT, E-CC-LOG, E-CC-CONF |
|
||||
| 8 | E-C11 | C11 Tile Manager (TileDownloader + TileUploader) | component | AZ-251 | M | 13–21 | E-C6, E-CC-CONF, E-CC-LOG |
|
||||
| 9 | E-C10 | C10 Pre-flight Cache Provisioning | component | AZ-252 | M | 13–21 | E-C6, E-C7, E-CC-LOG |
|
||||
| 10 | E-C12 | C12 Operator Pre-flight Tooling | component | AZ-253 | M | 13–21 | E-C10, E-C11, E-CC-LOG |
|
||||
| 11 | E-C1 | C1 Visual / Visual-Inertial Odometry | component | AZ-254 | XL | 34–55 | E-BOOT, E-CC-FDR-CLIENT, E-C7 |
|
||||
| 12 | E-C2 | C2 Visual Place Recognition | component | AZ-255 | L | 21–34 | E-C6, E-C7, E-CC-FDR-CLIENT |
|
||||
| 13 | E-C2.5 | C2.5 Inlier-based Re-rank | component | AZ-256 | S | 5–8 | E-C2, E-C7, E-C6 (LightGlue helper shared with C3) |
|
||||
| 14 | E-C3 | C3 Cross-Domain Matcher | component | AZ-257 | L | 21–34 | E-C2.5, E-C7 |
|
||||
| 15 | E-C3.5 | C3.5 AdHoP-Conditional Refinement | component | AZ-258 | M | 8–13 | E-C3, E-C7 |
|
||||
| 16 | E-C4 | C4 Pose Estimator | component | AZ-259 | M | 13–21 | E-C3.5, E-C5 (shared GTSAM substrate; co-developed) |
|
||||
| 17 | E-C5 | C5 State Estimator | component | AZ-260 | XL | 34–55 | E-C1, E-C4 (shared graph), E-CC-FDR-CLIENT |
|
||||
| 18 | E-C8 | C8 FC + GCS Adapter | component | AZ-261 | L | 21–34 | E-C5, E-CC-CONF, E-CC-LOG |
|
||||
| 19 | E-BBT | Blackbox Tests (FT/NFT scenarios) | tests | AZ-262 | M | 13–21 | every component epic ships its component-internal tests under its own epic; this one parents the suite-level FT/NFT scenarios in `_docs/02_document/tests/*.md` |
|
||||
| 20 | E-CC-HELPERS | Cross-Cutting: Common Helpers (8 shared utilities) | cross-cutting | AZ-264 | M | 13–21 | E-BOOT, E-CC-LOG (added in Decompose Step 2 — supersedes per-component helper child-issues from cycle 1) |
|
||||
| 21 | E-DEMO-REPLAY | Offline replay mode (video + tlog → per-tick coordinate stream) | feature | AZ-265 | M | 22–27 | E-C1, E-C2, E-C2.5, E-C3, E-C3.5, E-C4, E-C5, E-C8, E-CC-CONF (added in Decompose Step 2 — enables parent-suite UI demo via subprocess + JSONL streaming) |
|
||||
|
||||
## High-level component dependency diagram
|
||||
|
||||
@@ -57,10 +63,13 @@ flowchart TB
|
||||
C5[E-C5 State]
|
||||
C8[E-C8 FC Adapter]
|
||||
BBT[E-BBT Blackbox Tests]
|
||||
HELP[E-CC-HELPERS Common Helpers]
|
||||
DEMO[E-DEMO-REPLAY Offline Replay Mode]
|
||||
|
||||
BOOT --> LOG --> FDRC --> C13
|
||||
BOOT --> CONF --> C13
|
||||
BOOT --> CONF --> C7
|
||||
BOOT --> LOG --> HELP
|
||||
C13 -.-> C7
|
||||
CONF --> C6 --> C11
|
||||
C6 --> C10
|
||||
@@ -77,13 +86,30 @@ flowchart TB
|
||||
FDRC --> C5
|
||||
C8 --> BBT
|
||||
C12 --> BBT
|
||||
HELP -.-> C1
|
||||
HELP -.-> C2
|
||||
HELP -.-> C25
|
||||
HELP -.-> C3
|
||||
HELP -.-> C35
|
||||
HELP -.-> C4
|
||||
HELP -.-> C5
|
||||
HELP -.-> C6
|
||||
HELP -.-> C7
|
||||
HELP -.-> C8
|
||||
HELP -.-> C10
|
||||
HELP -.-> C11
|
||||
HELP -.-> C12
|
||||
C1 --> DEMO
|
||||
C5 --> DEMO
|
||||
C8 --> DEMO
|
||||
CONF --> DEMO
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## E-BOOT — Bootstrap & Initial Structure
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-244
|
||||
**Type**: bootstrap
|
||||
**T-shirt**: M | **Story points**: 13–21
|
||||
**Owner**: onboard team
|
||||
@@ -188,7 +214,7 @@ T-shirt M; 13–21 story points across child PBIs (each ≤ 5 points).
|
||||
|
||||
## E-CC-LOG — Cross-Cutting: Structured JSON Logging
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-245
|
||||
**Type**: cross-cutting
|
||||
**T-shirt**: S | **Story points**: 5–8
|
||||
|
||||
@@ -294,7 +320,7 @@ T-shirt S; 5–8 points.
|
||||
|
||||
## E-CC-CONF — Cross-Cutting: Configuration & Composition Root
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-246
|
||||
**Type**: cross-cutting
|
||||
**T-shirt**: S | **Story points**: 5–8
|
||||
|
||||
@@ -390,7 +416,7 @@ T-shirt S; 5–8 points.
|
||||
|
||||
## E-CC-FDR-CLIENT — Cross-Cutting: FDR Producer Client
|
||||
|
||||
**Tracker**: pending
|
||||
**Tracker**: AZ-247
|
||||
**Type**: cross-cutting
|
||||
**T-shirt**: M | **Story points**: 8–13
|
||||
|
||||
@@ -494,7 +520,7 @@ T-shirt M; 8–13 points.
|
||||
|
||||
## E-C13 — C13 Flight Data Recorder
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-248 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -598,7 +624,7 @@ T-shirt L; 21–34 points.
|
||||
|
||||
## E-C7 — C7 On-Jetson Inference Runtime
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-249 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -706,7 +732,7 @@ T-shirt L; 21–34 points.
|
||||
|
||||
## E-C6 — C6 Tile Cache + Spatial Index
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-250 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -810,7 +836,7 @@ Per `components/08_c6_tile_cache/tests.md`.
|
||||
|
||||
## E-C11 — C11 Tile Manager (TileDownloader + TileUploader)
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-251 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -921,7 +947,7 @@ Per `components/12_c11_tilemanager/tests.md`.
|
||||
|
||||
## E-C10 — C10 Pre-flight Cache Provisioning
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-252 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1023,7 +1049,7 @@ Per `components/11_c10_provisioning/tests.md`.
|
||||
|
||||
## E-C12 — C12 Operator Pre-flight Tooling
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-253 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1124,7 +1150,7 @@ Per `components/13_c12_operator_tooling/tests.md`.
|
||||
|
||||
## E-C1 — C1 Visual / Visual-Inertial Odometry
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
**Tracker**: AZ-254 | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1230,7 +1256,7 @@ Per `components/01_c1_vio/tests.md` + suite-level FT-P-02 / FT-P-04 / FT-P-05.
|
||||
|
||||
## E-C2 — C2 Visual Place Recognition
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-255 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1331,7 +1357,7 @@ Per `components/02_c2_vpr/tests.md`.
|
||||
|
||||
## E-C2.5 — C2.5 Inlier-based Re-rank
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: S | **Story points**: 5–8
|
||||
**Tracker**: AZ-256 | **Type**: component | **T-shirt**: S | **Story points**: 5–8
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1425,7 +1451,7 @@ Per `components/03_c2_5_rerank/tests.md`.
|
||||
|
||||
## E-C3 — C3 Cross-Domain Matcher
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-257 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1523,7 +1549,7 @@ Per `components/04_c3_matcher/tests.md`.
|
||||
|
||||
## E-C3.5 — C3.5 AdHoP-Conditional Refinement
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 8–13
|
||||
**Tracker**: AZ-258 | **Type**: component | **T-shirt**: M | **Story points**: 8–13
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1617,7 +1643,7 @@ Per `components/05_c3_5_adhop/tests.md`.
|
||||
|
||||
## E-C4 — C4 Pose Estimator
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-259 | **Type**: component | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1720,7 +1746,7 @@ Per `components/06_c4_pose/tests.md`.
|
||||
|
||||
## E-C5 — C5 State Estimator
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
**Tracker**: AZ-260 | **Type**: component | **T-shirt**: XL | **Story points**: 34–55
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1835,7 +1861,7 @@ Per `components/07_c5_state/tests.md`.
|
||||
|
||||
## E-C8 — C8 FC + GCS Adapter
|
||||
|
||||
**Tracker**: pending | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
**Tracker**: AZ-261 | **Type**: component | **T-shirt**: L | **Story points**: 21–34
|
||||
|
||||
### System context
|
||||
|
||||
@@ -1948,7 +1974,7 @@ Per `components/10_c8_fc_adapter/tests.md`.
|
||||
|
||||
## E-BBT — Blackbox Tests (FT/NFT scenarios)
|
||||
|
||||
**Tracker**: pending | **Type**: tests | **T-shirt**: M | **Story points**: 13–21
|
||||
**Tracker**: AZ-262 | **Type**: tests | **T-shirt**: M | **Story points**: 13–21
|
||||
|
||||
### System context
|
||||
|
||||
@@ -2042,6 +2068,155 @@ This epic IS the testing strategy for system-level scenarios. Per-component test
|
||||
|
||||
---
|
||||
|
||||
## E-DEMO-REPLAY — Offline replay mode (video + tlog → per-tick coordinate stream)
|
||||
|
||||
**Tracker**: AZ-265
|
||||
**Type**: feature (deployment-adjacent)
|
||||
**T-shirt**: M | **Story points**: 27–32
|
||||
**Added**: Decompose Step 2 (cycle 1, 2026-05-10)
|
||||
**Source notes**: `_docs/how_to_test.md` (user-written demo requirements — auto-sync incorporated as child task #8)
|
||||
|
||||
### System context
|
||||
|
||||
Demonstrate the GPS-denied positioning pipeline against historical flight data: a video file from the nav camera + a `.tlog` file from the FC. The replay mode runs the **same C1–C5 inference pipeline** the airborne binary runs; only the input transport (live camera → video file; live MAVLink → tlog) and output sink (FC MAVLink emit → JSONL) differ. NO ROS dependency is added — replay reuses the existing C8 `FcAdapter` interface via the strategy pattern.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph LIVE[Airborne mode — unchanged]
|
||||
CAM[Live camera] --> C1L[C1 VIO]
|
||||
FCL[Live FC MAVLink] --> C8L[C8 inbound]
|
||||
C8L --> C1L
|
||||
C1L --> C2L[C2..C5]
|
||||
C2L --> C8OL[C8 outbound] --> FCL
|
||||
end
|
||||
subgraph REPLAY[Replay mode — this epic]
|
||||
VID[Video file .mp4/.h264] --> VFFS[VideoFileFrameSource] --> C1R[C1 VIO]
|
||||
TLOG[tlog file] --> TLR[TlogReplayFcAdapter] --> C1R
|
||||
C1R --> C2R[C2..C5]
|
||||
C2R --> RSINK[JsonlReplaySink] --> JSONL[results.jsonl - one EstimatorOutput per tick]
|
||||
end
|
||||
```
|
||||
|
||||
### Problem / Context
|
||||
|
||||
The parent-suite UI (in `ui/` workspace, out of scope for this repo) needs to demo the GPS-denied positioning end-to-end. Per-component fixtures or simulators would not give the demo end-to-end fidelity. Instead, replay mode runs the production pipeline against historical inputs — demo confidence equals field test confidence on the same footage.
|
||||
|
||||
ROS as the input transport was considered and rejected: the system is MAVLink-native; introducing ROS would (a) add a major new dependency, (b) split production vs. demo code paths, and (c) duplicate code. Reusing the existing C8 `FcAdapter` interface with a tlog-replay strategy is strictly better.
|
||||
|
||||
### Scope
|
||||
|
||||
**In scope**:
|
||||
- `FrameSource` interface (formalised cross-cutting; previously implicit "camera ingest thread") + `VideoFileFrameSource` strategy + `LiveCameraFrameSource` retrofit (no-op restructure of existing camera plumbing).
|
||||
- `TlogReplayFcAdapter` strategy (new C8 `FcAdapter` impl) parsing pymavlink `.tlog` files and emitting `ImuWindow` / `AttitudeWindow` / `GpsHealth` / `FlightStateSignal` at tlog timestamp cadence.
|
||||
- `ReplaySink` interface + `JsonlReplaySink` impl (one `EstimatorOutput` per line).
|
||||
- `compose_replay(config) -> ReplayRoot` composition root extending E-CC-CONF (AZ-246).
|
||||
- `Clock` injection (per R-DEMO-4) so timer-driven logic in C1–C5 works in both wall-clock (live) and tlog-simulated (replay) modes.
|
||||
- `gps-denied-replay` CLI: `--video PATH --tlog PATH --output results.jsonl --camera-calibration calib.json --config config.yaml --pace {realtime,asap} [--time-offset-ms N]`.
|
||||
- Fourth Docker image `gps-denied-replay-cli` (Python + C1–C5 + cpp/* + replay strategies; NO C6/C10/C11/C12; NO HTTP server).
|
||||
- E2E replay test on a 1–2 min Derkachi clip + matching tlog asserting estimated track within ≤ 100 m of ground-truth GPS for ≥ 80 % of ticks.
|
||||
|
||||
**Out of scope**:
|
||||
- ROS / ROS2 dependency.
|
||||
- HTTP wrapper microservice (parent-suite UI backend shells out to the CLI; defer until subprocess-shape is proven insufficient).
|
||||
- Modifying any C1–C5 component to be replay-aware — they MUST remain mode-agnostic.
|
||||
- C6 mid-flight write path (replay reads a pre-built tile cache; doesn't write).
|
||||
|
||||
### Architecture notes
|
||||
|
||||
- ADR-001 / ADR-002 / ADR-009 all apply unchanged.
|
||||
- New `BUILD_*` flags: `BUILD_VIDEO_FILE_FRAME_SOURCE`, `BUILD_TLOG_REPLAY_ADAPTER`, `BUILD_REPLAY_SINK_JSONL`. Default ON for the new replay-cli binary; OFF for airborne, research, and operator-tooling.
|
||||
- New cross-cutting `FrameSource` interface lives at `src/gps_denied_onboard/frame_source/` (Layer 1 Foundation per `module-layout.md` § layering).
|
||||
- `compose_replay` lives in `runtime_root.py` alongside `compose_root` and `compose_operator`.
|
||||
|
||||
### Interface specification
|
||||
|
||||
```python
|
||||
class FrameSource(Protocol):
|
||||
def next_frame(self) -> NavCameraFrame | None: ...
|
||||
def close(self) -> None: ...
|
||||
|
||||
class VideoFileFrameSource(FrameSource):
|
||||
def __init__(self, video_path: Path, frame_rate_hz: float, camera_id: str): ...
|
||||
|
||||
class TlogReplayFcAdapter(FcAdapter): # FcAdapter from AZ-261 / E-C8
|
||||
def __init__(self, tlog_path: Path, target_fc_dialect: enum {ARDUPILOT, INAV}): ...
|
||||
|
||||
class ReplaySink(Protocol):
|
||||
def emit(self, output: EstimatorOutput) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
|
||||
class JsonlReplaySink(ReplaySink):
|
||||
def __init__(self, output_path: Path): ...
|
||||
|
||||
def compose_replay(config: Config) -> ReplayRoot: ...
|
||||
```
|
||||
|
||||
### Data flow
|
||||
|
||||
Startup → load config / calibration → process tlog + video timestamp-aligned → for each frame: camera-ingest → C1 → C2 → C2.5 → C3 → C3.5 → C4 → C5 → emit `EstimatorOutput` to `JsonlReplaySink`. End of input → close sink → exit.
|
||||
|
||||
`--pace realtime` paces frames at wall-clock; `--pace asap` runs uncapped (default). The injected `Clock` is wall-clock-derived in `realtime` mode and tlog-timestamp-derived in `asap` mode so component fallback timers (e.g., AC-5.2 3 s no-estimate fallback) trigger consistently in both.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- E-C1, E-C2, E-C2.5, E-C3, E-C3.5, E-C4, E-C5, E-C8 (every per-frame component).
|
||||
- E-CC-CONF (AZ-246) for `compose_root` extension.
|
||||
- E-CC-HELPERS (AZ-264) for `WgsConverter` (tlog GPS → local-tangent-plane).
|
||||
- Does NOT depend on E-C6 / E-C10 / E-C11 / E-C12 (replay reads pre-built cache; no operator-side workflows).
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- AC-1: CLI exits 0 on a valid 1-min fixture and produces JSONL with one `EstimatorOutput` line per tlog tick (within ±5 % of `GLOBAL_POSITION_INT` count).
|
||||
- AC-2: Each line is a valid JSON object matching the `EstimatorOutput` schema.
|
||||
- AC-3: For a fixture with known ground-truth GPS, the L2 horizontal distance ≤ 100 m for ≥ 80 % of ticks (matches AC-1.3 cumulative-drift bound).
|
||||
- AC-4: Replay binary contains C1–C5 + replay strategies; SBOM diff CI step verifies absence of C6/C10/C11/C12.
|
||||
- AC-5: Same input → same output (deterministic) within ≤ 1e-6 float drift in position fields.
|
||||
- AC-6: `--pace realtime` runs the 1-min fixture in 60 ± 5 s; `--pace asap` in ≤ 30 s on Tier-1 hardware.
|
||||
- AC-7: Without `--time-offset-ms`, the CLI auto-detects the video ↔ tlog offset by correlating video motion-onset (or first-frame timestamp) with the tlog IMU take-off pattern (sustained vertical accel > 0.5 g + change in attitude rate > 1 rad/s lasting ≥ 0.5 s, matching the typical quadcopter take-off signature). On a fixture with known correct offset, the auto-detected offset is within ± 200 ms of ground truth. If auto-detect confidence is < 80 % the CLI logs a WARN and proceeds with the best-guess offset; `--time-offset-ms N` always overrides the auto-detect.
|
||||
- AC-8: If neither auto-detect nor manual offset can produce > 95 % of frames with at least one matching IMU window within ± 100 ms, the CLI exits with code 2 and prints both the auto-detected offset (if any) and the percentage of frames-with-IMU-window so the operator can debug.
|
||||
|
||||
### Non-functional requirements
|
||||
|
||||
- Cold-start ≤ 5 s (not subject to AC-NEW-1's 30 s budget — that's airborne-only).
|
||||
- Throughput ≥ 5 × real time on Jetson AGX Orin for `--pace asap`.
|
||||
- Memory ≤ 4 GB resident (lean image; no FAISS index unless tile lookup is needed).
|
||||
|
||||
### Risks & mitigations
|
||||
|
||||
- **R-DEMO-1**: Tlog ↔ video timestamp drift across long flights, AND the more-common case that recordings on the operator workstation are not synchronised at all (camera and FC start independently, often minutes apart). **Mitigation**: auto-sync via IMU take-off detection (AC-7) is the default; `--time-offset-ms N` is the manual override. If take-off pattern is ambiguous (e.g., fixed-wing hand-launch instead of quadcopter, or tlog includes pre-arm motion), CLI WARNs and falls back to the manual override.
|
||||
- **R-DEMO-2**: Pymavlink slow on multi-GB tlogs. **Mitigation**: stream-parse, never materialise; benchmark + document throughput floor.
|
||||
- **R-DEMO-3**: Demo footage missing required FC messages (HIL mode etc.). **Mitigation**: CLI fails fast at startup listing missing message types and the components that need them.
|
||||
- **R-DEMO-4**: Production C1–C5 paths bake real-time-cadence assumptions (e.g., 5 s fallback timer). **Mitigation**: `Clock` injection (wall-clock for live, tlog-derived for replay); documented as ADR amendment in next architecture-doc cycle.
|
||||
|
||||
### Effort
|
||||
|
||||
T-shirt M; 27–32 points across 8 child tasks.
|
||||
|
||||
### Child issues
|
||||
|
||||
| # | Title | Pts |
|
||||
|---|-------|-----|
|
||||
| 1 | `FrameSource` interface (cross-cutting) + `VideoFileFrameSource` strategy + `LiveCameraFrameSource` retrofit | 3 |
|
||||
| 2 | `TlogReplayFcAdapter` strategy (pymavlink stream parser → inbound DTOs) | 5 |
|
||||
| 3 | `ReplaySink` interface + `JsonlReplaySink` impl | 3 |
|
||||
| 4 | `compose_replay(config)` + `Clock` injection (per R-DEMO-4) | 3 |
|
||||
| 5 | `gps-denied-replay` CLI entrypoint + arg parser + camera-calibration loader | 3 |
|
||||
| 6 | `gps-denied-replay-cli` Dockerfile + GitHub Actions matrix entry + SBOM diff (excludes C6/C10/C11/C12) | 3 |
|
||||
| 7 | E2E replay fixture test (Derkachi 1–2 min clip + tlog; AC-3 ≤100 m ≥ 80 % assertion) | 5 |
|
||||
| 8 | Auto-sync of video ↔ tlog via IMU take-off detection (AC-7 / AC-8; `--time-offset-ms` remains the manual override) | 5 |
|
||||
|
||||
### Key constraints
|
||||
|
||||
- ADR-001 / ADR-002 / ADR-009.
|
||||
- C1–C5 components MUST remain mode-agnostic; replay-aware logic lives only in the composition root, the new strategies, and the CLI.
|
||||
- No HTTP server in any companion binary (airborne or replay); HTTP wrapper, if added later, lives in operator-tooling per `module-layout.md` Layer-4 placement.
|
||||
|
||||
### Testing strategy
|
||||
|
||||
Unit tests under `tests/unit/frame_source/`, `tests/unit/c8_fc_adapter/test_tlog_replay_adapter.py`, `tests/unit/c8_fc_adapter/test_replay_sink.py`, `tests/unit/cli/test_replay_cli.py`. E2E under `tests/e2e/replay/` running the CLI against the Derkachi fixture (Tier-1 capable; gated by `RUN_REPLAY_E2E=1` in CI). No FT/NFT scenarios at this epic — those live in E-BBT.
|
||||
|
||||
---
|
||||
|
||||
## Lessons applied (Step 6 step-0 retrospective)
|
||||
|
||||
`_docs/LESSONS.md` does not yet exist (this is the project's first cycle), so no prior estimation/architecture/dependencies lessons were folded into the sizing above. When this cycle ends, the Final step's quality checklist should propose a lessons file capturing:
|
||||
|
||||
@@ -0,0 +1,433 @@
|
||||
# Module Layout
|
||||
|
||||
**Language**: python (with C++ native libraries linked via pybind11 from a parallel `cpp/` tree)
|
||||
**Layout Convention**: src-layout (single top-level package `src/gps_denied_onboard/`)
|
||||
**Root**: `src/gps_denied_onboard/`
|
||||
**Last Updated**: 2026-05-10
|
||||
|
||||
This file is the authoritative file-ownership map consumed by the `/implement` skill (Step 4 File Ownership). Per-task specs in `_docs/02_tasks/` remain purely behavioral — they do NOT carry file paths. All component → filesystem mapping lives here.
|
||||
|
||||
Bootstrap reference: `_docs/02_tasks/todo/AZ-263_initial_structure.md`. Architecture reference: `_docs/02_document/architecture.md` (ADR-001 monolith, ADR-002 build-time exclusion, ADR-009 interface-first DI).
|
||||
|
||||
## Layout Rules
|
||||
|
||||
1. The single top-level Python package is `src/gps_denied_onboard/`. All imports are rooted there. No sibling packages live under `src/`.
|
||||
2. Each component owns ONE folder under `src/gps_denied_onboard/components/`. Folder name = component slug (lowercase, snake_case, e.g. `c1_vio`, `c2_vpr`, `c2_5_rerank`).
|
||||
3. Cross-cutting concerns own ONE folder each directly under `src/gps_denied_onboard/`: `_types/`, `helpers/`, `config/`, `logging/`, `fdr_client/`, `frame_source/`, `clock/`. Plus `runtime_root.py` and `healthcheck.py` at the package root.
|
||||
4. Native (C++) libraries live under `cpp/` (parallel to `src/`, NOT nested), built by CMake; per-component pybind11 wrappers live at `src/gps_denied_onboard/components/<component>/_native/<name>.py` and import the resulting `.so` from a CMake-known path.
|
||||
5. **Public API surface per component** = the files listed in each component's `Public API` list below. Anything not listed is internal and MUST NOT be imported from another component.
|
||||
6. The composition root is `src/gps_denied_onboard/runtime_root.py`. It is the ONLY place that may import concrete strategy implementations across components — every other cross-component dependency is constructor-injected against an interface (ADR-009).
|
||||
7. Tests mirror the component graph 1:1 at `tests/unit/<component>/`. Cross-component scenarios live in `tests/integration/`, `tests/e2e/`, `tests/perf/`, `tests/security/`, `tests/resilience/`.
|
||||
8. Build-time exclusion (ADR-002): each `<component>/_native/` and the corresponding `cpp/<lib>/` carry a CMake `BUILD_<NAME>` flag. The composition root validator refuses to wire a strategy whose flag is OFF.
|
||||
|
||||
## Per-Component Mapping
|
||||
|
||||
### Component: c1_vio
|
||||
|
||||
- **Epic**: AZ-254 (E-C1 VIO)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c1_vio/`
|
||||
- **Public API**:
|
||||
- `src/gps_denied_onboard/components/c1_vio/__init__.py` (re-exports `VioStrategy`, `VioOutput`)
|
||||
- `src/gps_denied_onboard/components/c1_vio/interface.py` (`VioStrategy` Protocol)
|
||||
- **Internal (do NOT import from other components)**:
|
||||
- `src/gps_denied_onboard/components/c1_vio/okvis2.py` (production-default; links `cpp/okvis2/`)
|
||||
- `src/gps_denied_onboard/components/c1_vio/vins_mono.py` (research-only; gated by `BUILD_VINS_MONO=ON`)
|
||||
- `src/gps_denied_onboard/components/c1_vio/klt_ransac.py` (mandatory simple-baseline)
|
||||
- `src/gps_denied_onboard/components/c1_vio/_native/`
|
||||
- **Owns (exclusive write during implementation)**: `src/gps_denied_onboard/components/c1_vio/**`, `cpp/okvis2/**`, `cpp/vins_mono/**`, `cpp/klt_ransac/**`, `tests/unit/c1_vio/**`
|
||||
- **Imports from**: `_types`, `helpers.imu_preintegrator`, `helpers.se3_utils`, `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c2_vpr`, `c5_state`, `c13_fdr`, `runtime_root`
|
||||
|
||||
### Component: c2_vpr
|
||||
|
||||
- **Epic**: AZ-255 (E-C2 VPR)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c2_vpr/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `VprStrategy`, `VprQuery`, `VprResult`)
|
||||
- `interface.py` (`VprStrategy` Protocol)
|
||||
- **Internal**:
|
||||
- `ultra_vpr.py` (primary), `mega_loc.py`, `mix_vpr.py`, `sela_vpr.py`, `eigen_places.py`, `net_vlad.py`, `salad.py`
|
||||
- `_native/`
|
||||
- **Owns**: `src/gps_denied_onboard/components/c2_vpr/**`, `tests/unit/c2_vpr/**`
|
||||
- **Imports from**: `_types`, `helpers.descriptor_normaliser`, `components.c6_tile_cache` (Public API only — TileStore query interface), `components.c7_inference` (InferenceRuntime), `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c2_5_rerank`, `runtime_root`
|
||||
|
||||
### Component: c2_5_rerank
|
||||
|
||||
- **Epic**: AZ-256 (E-C2.5 Rerank)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c2_5_rerank/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `RerankStrategy`, `RerankResult`)
|
||||
- `interface.py` (`RerankStrategy` Protocol)
|
||||
- **Internal**:
|
||||
- `inlier_based_reranker.py` (single-pair LightGlue inlier count K=10→N=3)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c2_5_rerank/**`, `tests/unit/c2_5_rerank/**`
|
||||
- **Imports from**: `_types`, `helpers.lightglue_runtime`, `helpers.descriptor_normaliser`, `helpers.ransac_filter`, `helpers.se3_utils`, `components.c6_tile_cache` (Public API), `components.c7_inference`, `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c3_matcher`, `runtime_root`
|
||||
|
||||
### Component: c3_matcher
|
||||
|
||||
- **Epic**: AZ-257 (E-C3 Cross-Domain Matcher)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c3_matcher/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `CrossDomainMatcher`, `MatchResult`)
|
||||
- `interface.py` (`CrossDomainMatcher` Protocol)
|
||||
- **Internal**:
|
||||
- `disk_lightglue.py` (DISK + LightGlue)
|
||||
- `aliked_lightglue.py` (ALIKED + LightGlue)
|
||||
- `xfeat.py`
|
||||
- `_native/`
|
||||
- **Owns**: `src/gps_denied_onboard/components/c3_matcher/**`, `tests/unit/c3_matcher/**`
|
||||
- **Imports from**: `_types`, `helpers.lightglue_runtime` (R14: SHARED with C2.5 — owned by helper, NOT by C3), `helpers.descriptor_normaliser`, `helpers.se3_utils`, `components.c7_inference`, `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c3_5_adhop`, `runtime_root`
|
||||
|
||||
### Component: c3_5_adhop
|
||||
|
||||
- **Epic**: AZ-258 (E-C3.5 AdHoP Refinement)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c3_5_adhop/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `AdHoPRefinementStrategy`)
|
||||
- `interface.py` (`AdHoPRefinementStrategy` Protocol)
|
||||
- **Internal**: `default_refiner.py`
|
||||
- **Owns**: `src/gps_denied_onboard/components/c3_5_adhop/**`, `tests/unit/c3_5_adhop/**`
|
||||
- **Imports from**: `_types`, `helpers.ransac_filter`, `helpers.se3_utils`, `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c4_pose`, `runtime_root`
|
||||
|
||||
### Component: c4_pose
|
||||
|
||||
- **Epic**: AZ-259 (E-C4 Pose Estimator)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c4_pose/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `PoseEstimator`, `PoseEstimate`, `EstimatorOutput`)
|
||||
- `interface.py` (`PoseEstimator` Protocol)
|
||||
- **Internal**:
|
||||
- `opencv_pnp_estimator.py` (OpenCV `solvePnPRansac` + GTSAM Marginals for covariance)
|
||||
- `_native/` (GTSAM bindings via `cpp/gtsam_bindings/`)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c4_pose/**`, `cpp/gtsam_bindings/**` (shared with c5_state — see ownership note below), `tests/unit/c4_pose/**`
|
||||
- **Imports from**: `_types`, `helpers.ransac_filter`, `helpers.se3_utils`, `helpers.wgs_converter`, `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c5_state`, `runtime_root`
|
||||
|
||||
> **Joint native ownership note**: `cpp/gtsam_bindings/` is a thin pybind11 wrapper used by both C4 (Marginals for covariance) and C5 (iSAM2 + IncrementalFixedLagSmoother). Implementation task for `cpp/gtsam_bindings/` is owned by **c5_state** (the heavier consumer); c4_pose imports it READ-ONLY. See Layering table below.
|
||||
|
||||
### Component: c5_state
|
||||
|
||||
- **Epic**: AZ-260 (E-C5 State Estimator)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c5_state/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `StateEstimator`, `EstimatorOutput`, `EstimatorHealth`)
|
||||
- `interface.py` (`StateEstimator` Protocol)
|
||||
- **Internal**:
|
||||
- `gtsam_isam2_estimator.py` (production-default; iSAM2 + IncrementalFixedLagSmoother)
|
||||
- `eskf_baseline.py` (mandatory simple-baseline)
|
||||
- `_native/`
|
||||
- **Owns**: `src/gps_denied_onboard/components/c5_state/**`, `cpp/gtsam_bindings/**` (primary owner; see joint-native note above), `tests/unit/c5_state/**`
|
||||
- **Imports from**: `_types`, `helpers.imu_preintegrator`, `helpers.se3_utils`, `helpers.wgs_converter`, `components.c4_pose` (Public API: `PoseEstimate`), `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c8_fc_adapter`, `c13_fdr`, `runtime_root`
|
||||
|
||||
### Component: c6_tile_cache
|
||||
|
||||
- **Epic**: AZ-250 (E-C6 Tile Cache & Vector Index)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c6_tile_cache/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `TileStore`, `Tile`, `TileQualityMetadata`, `TileRecord`, `SectorClassification`)
|
||||
- `interface.py` (`TileStore` Protocol — query/get/put surface; concrete impls swappable)
|
||||
- **Internal**:
|
||||
- `postgres_filesystem_store.py` (Postgres mirror + filesystem mmap + FAISS HNSW; production-default)
|
||||
- `_native/` (`cpp/faiss_index/` wrapper)
|
||||
- `_alembic/` (migration scripts; `0001_initial.sql` shipped in bootstrap)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c6_tile_cache/**`, `cpp/faiss_index/**`, `tests/unit/c6_tile_cache/**`
|
||||
- **Imports from**: `_types`, `helpers.sha256_sidecar`, `helpers.wgs_converter`, `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c2_vpr`, `c2_5_rerank`, `c3_matcher`, `c10_provisioning`, `c11_tile_manager`, `runtime_root`
|
||||
|
||||
### Component: c7_inference
|
||||
|
||||
- **Epic**: AZ-249 (E-C7 Inference Runtime)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c7_inference/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `InferenceRuntime`, `EngineCacheEntry`)
|
||||
- `interface.py` (`InferenceRuntime` Protocol)
|
||||
- **Internal**:
|
||||
- `tensorrt_runtime.py` (production-default; TensorRT 10.3)
|
||||
- `onnx_trt_runtime.py` (ONNX Runtime + TensorRT EP)
|
||||
- `pytorch_fp16_runtime.py` (research-only baseline)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c7_inference/**`, `tests/unit/c7_inference/**`
|
||||
- **Imports from**: `_types`, `helpers.engine_filename_schema`, `helpers.sha256_sidecar`, `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c2_vpr`, `c2_5_rerank`, `c3_matcher`, `c10_provisioning`, `runtime_root`
|
||||
|
||||
### Component: c8_fc_adapter
|
||||
|
||||
- **Epic**: AZ-261 (E-C8 FC + GCS Adapter)
|
||||
- **Replay extensions epic**: AZ-265 (E-DEMO-REPLAY) — adds `tlog_replay_adapter.py` + `replay_sink.py` as gated strategies
|
||||
- **Directory**: `src/gps_denied_onboard/components/c8_fc_adapter/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `FcAdapter`, `GcsAdapter`, `ReplaySink`, `EmittedExternalPosition`)
|
||||
- `interface.py` (`FcAdapter`, `GcsAdapter`, `ReplaySink` Protocols)
|
||||
- **Internal**:
|
||||
- `pymavlink_ardupilot_adapter.py` (ArduPilot Plane via pymavlink)
|
||||
- `msp2_inav_adapter.py` (iNav via MSP2)
|
||||
- `mavlink_gcs_adapter.py` (1–2 Hz downsampled summary to QGroundControl)
|
||||
- `tlog_replay_adapter.py` (replay-only `FcAdapter`; gated `BUILD_TLOG_REPLAY_ADAPTER`; AZ-265)
|
||||
- `replay_sink.py` (`ReplaySink` interface + `JsonlReplaySink` impl; gated `BUILD_REPLAY_SINK_JSONL`; AZ-265)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c8_fc_adapter/**`, `tests/unit/c8_fc_adapter/**`
|
||||
- **Imports from**: `_types`, `helpers.wgs_converter`, `helpers.se3_utils`, `components.c5_state` (Public API: `EstimatorOutput`), `config`, `logging`, `fdr_client`, `clock` (for replay timer-injection)
|
||||
- **Consumed by**: `c1_vio` (back-channel: ImuSample, AttitudeWindow), `c5_state` (back-channel: ImuSample, FlightStateSignal, GpsHealth), `runtime_root` (live + operator + replay binaries)
|
||||
|
||||
> **Back-channel note**: C8 is the source of inbound IMU / attitude / GPS-health signals from the FC. C1 and C5 receive these via constructor-injected `FcAdapter` (typed against the interface, not the concrete adapter). This is NOT a layering violation — C8's role spans both the outbound emit path AND the inbound telemetry source.
|
||||
|
||||
### Component: c10_provisioning
|
||||
|
||||
- **Epic**: AZ-252 (E-C10 Cache Provisioner)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c10_provisioning/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `CacheProvisioner`, `Manifest`, `EngineCacheEntry`)
|
||||
- `interface.py` (`CacheProvisioner` Protocol)
|
||||
- **Internal**:
|
||||
- `default_provisioner.py` (engine compile + descriptors + manifest + content-hash gate)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c10_provisioning/**`, `tests/unit/c10_provisioning/**`
|
||||
- **Imports from**: `_types`, `helpers.sha256_sidecar`, `helpers.engine_filename_schema`, `helpers.wgs_converter`, `components.c6_tile_cache` (Public API), `components.c7_inference` (Public API: engine compile surface), `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c12_operator_tooling`, `runtime_root` (operator binary only — excluded from airborne via `BUILD_C10_PROVISIONING=OFF` for airborne build per ADR-002)
|
||||
|
||||
### Component: c11_tile_manager
|
||||
|
||||
- **Epic**: AZ-251 (E-C11 Tile Downloader/Uploader)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c11_tile_manager/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `TileDownloader`, `TileUploader`)
|
||||
- `interface.py` (`TileDownloader`, `TileUploader` Protocols)
|
||||
- **Internal**:
|
||||
- `satellite_provider_downloader.py` (REST client against parent-suite `satellite-provider`)
|
||||
- `satellite_provider_uploader.py` (post-landing batch upload, D-PROJ-2 ingest contract)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c11_tile_manager/**`, `tests/unit/c11_tile_manager/**`
|
||||
- **Imports from**: `_types`, `helpers.sha256_sidecar`, `helpers.wgs_converter`, `components.c6_tile_cache` (Public API), `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `c12_operator_tooling`, `runtime_root` (operator binary only — `BUILD_C11_TILE_MANAGER=OFF` for airborne)
|
||||
|
||||
### Component: c12_operator_tooling
|
||||
|
||||
- **Epic**: AZ-253 (E-C12 Operator Pre-flight Tooling)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c12_operator_tooling/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `CacheBuildWorkflow`, `OperatorReLocService`)
|
||||
- `interface.py`
|
||||
- **Internal**:
|
||||
- `cache_build_workflow.py` (CLI orchestrator)
|
||||
- `operator_reloc_service.py` (CLI; GUI deferred per epic)
|
||||
- `sector_classifier.py` (operator sets `SectorClassification` → C6)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c12_operator_tooling/**`, `tests/unit/c12_operator_tooling/**`
|
||||
- **Imports from**: `_types`, `helpers.wgs_converter`, `components.c6_tile_cache` (Public API), `components.c10_provisioning` (Public API), `components.c11_tile_manager` (Public API), `config`, `logging`, `fdr_client`
|
||||
- **Consumed by**: `runtime_root` (operator binary only — `BUILD_C12_OPERATOR_TOOLING=OFF` for airborne)
|
||||
|
||||
### Component: c13_fdr
|
||||
|
||||
- **Epic**: AZ-248 (E-C13 FDR Writer)
|
||||
- **Directory**: `src/gps_denied_onboard/components/c13_fdr/`
|
||||
- **Public API**:
|
||||
- `__init__.py` (re-exports `FdrWriter`)
|
||||
- `interface.py` (`FdrWriter` Protocol)
|
||||
- **Internal**:
|
||||
- `default_fdr_writer.py` (writer thread + segment rotation + ≤64 GB cap)
|
||||
- **Owns**: `src/gps_denied_onboard/components/c13_fdr/**`, `tests/unit/c13_fdr/**`
|
||||
- **Imports from**: `_types`, `fdr_client.records` (FdrRecord schema only — schema lives in cross-cutting fdr_client; the consumer-side writer lives here), `config`, `logging`
|
||||
- **Consumed by**: `runtime_root` (every component's `fdr_client` producer ultimately writes to the C13 writer at process root)
|
||||
|
||||
> **C13 / fdr_client split**: the producer-side `FdrClient` (lock-free SPSC queue + record schema) lives in `src/gps_denied_onboard/fdr_client/` (cross-cutting; AZ-247 / E-CC-FDR-CLIENT). The consumer-side `FdrWriter` (writer thread + segment rotation) lives in `components/c13_fdr/` (AZ-248 / E-C13). This split is intentional: every component depends on the producer interface, but only the writer process implements the consumer.
|
||||
|
||||
## Shared / Cross-Cutting
|
||||
|
||||
### shared/_types
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/_types/`
|
||||
- **Purpose**: Cross-component DTOs (NavCameraFrame, ImuSample, ImuWindow, AttitudeWindow, FlightStateSignal, GpsHealth, VioOutput, VprQuery, VprResult, RerankResult, MatchResult, PoseEstimate, EstimatorOutput, EstimatorHealth, Tile, TileQualityMetadata, TileRecord, SectorClassification, CameraCalibration, EmittedExternalPosition, Manifest, EngineCacheEntry). **Type-only stubs**: zero implementation logic.
|
||||
- **Owned by**: AZ-263 (Bootstrap task); subsequent additions are type-only edits owned by the proposing component task.
|
||||
- **Consumed by**: every component, every cross-cutting module, the composition root.
|
||||
|
||||
### shared/config
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/config/`
|
||||
- **Purpose**: YAML config loader + validation + dataclass schemas (per-flight config + camera calibration JSON loader).
|
||||
- **Owned by**: AZ-263 (Bootstrap); subsequent schema fields added by the consuming component task touching `schema.py` only.
|
||||
- **Consumed by**: composition root + every component constructor that reads config.
|
||||
|
||||
### shared/logging
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/logging/`
|
||||
- **Purpose**: Structured JSON logging (one JSON object per line; no narrative log lines).
|
||||
- **Owned by**: AZ-245 (E-CC-LOG — Cross-Cutting Logging) — bootstrap creates the entrypoint stub satisfying the contract.
|
||||
- **Consumed by**: every component (via `from gps_denied_onboard.logging.structured import get_logger`).
|
||||
|
||||
### shared/fdr_client
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/fdr_client/`
|
||||
- **Purpose**: Producer-side API for FDR records (lock-free SPSC queue per producer, drop-oldest on overrun) + the FdrRecord schema.
|
||||
- **Owned by**: AZ-247 (E-CC-FDR-CLIENT — Cross-Cutting FDR Client).
|
||||
- **Consumed by**: every component's producer code path; the consumer (writer thread) is C13.
|
||||
|
||||
### shared/helpers/imu_preintegrator
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/imu_preintegrator.py`
|
||||
- **Purpose**: IMU preintegration utility (see `_docs/02_document/common-helpers/01_helper_imu_preintegrator.md`).
|
||||
- **Owned by**: AZ-264 (E-CC-HELPERS — Common Helpers); per-helper tasks live under that epic.
|
||||
- **Consumed by**: c1_vio, c5_state.
|
||||
|
||||
### shared/helpers/se3_utils
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/se3_utils.py`
|
||||
- **Purpose**: SE(3) math utilities (`02_helper_se3_utils.md`).
|
||||
- **Owned by**: AZ-264.
|
||||
- **Consumed by**: c1_vio, c2_5_rerank, c3_matcher, c3_5_adhop, c4_pose, c5_state, c8_fc_adapter.
|
||||
|
||||
### shared/helpers/lightglue_runtime
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/lightglue_runtime.py`
|
||||
- **Purpose**: Shared LightGlue inference runtime (`03_helper_lightglue_runtime.md`). **R14 fix**: this helper is the single owner; both C2.5 (single-pair inlier counter) and C3 (matcher) import it. Neither depends on the other.
|
||||
- **Owned by**: AZ-264.
|
||||
- **Consumed by**: c2_5_rerank, c3_matcher.
|
||||
|
||||
### shared/helpers/wgs_converter
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/wgs_converter.py`
|
||||
- **Purpose**: WGS84 ↔ local-tangent-plane conversion utilities (`04_helper_wgs_converter.md`).
|
||||
- **Owned by**: AZ-264.
|
||||
- **Consumed by**: c4_pose, c5_state, c6_tile_cache, c8_fc_adapter, c10_provisioning, c11_tile_manager, c12_operator_tooling.
|
||||
|
||||
### shared/helpers/sha256_sidecar
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/sha256_sidecar.py`
|
||||
- **Purpose**: Content-hash sidecar files (D-C10-3 content-hash gate; `05_helper_sha256_sidecar.md`).
|
||||
- **Owned by**: AZ-264.
|
||||
- **Consumed by**: c6_tile_cache, c7_inference, c10_provisioning, c11_tile_manager.
|
||||
|
||||
### shared/helpers/engine_filename_schema
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/engine_filename_schema.py`
|
||||
- **Purpose**: Self-describing TensorRT engine filename schema (D-C10-7; `06_helper_engine_filename_schema.md`).
|
||||
- **Owned by**: AZ-264.
|
||||
- **Consumed by**: c7_inference, c10_provisioning.
|
||||
|
||||
### shared/helpers/ransac_filter
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/ransac_filter.py`
|
||||
- **Purpose**: Generic RANSAC inlier filter (`07_helper_ransac_filter.md`).
|
||||
- **Owned by**: AZ-264.
|
||||
- **Consumed by**: c2_5_rerank, c3_5_adhop, c4_pose.
|
||||
|
||||
### shared/helpers/descriptor_normaliser
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/helpers/descriptor_normaliser.py`
|
||||
- **Purpose**: Descriptor normalisation utility (`08_helper_descriptor_normaliser.md`).
|
||||
- **Owned by**: AZ-264.
|
||||
- **Consumed by**: c2_vpr, c2_5_rerank, c3_matcher.
|
||||
|
||||
### shared/frame_source
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/frame_source/`
|
||||
- **Purpose**: `FrameSource` interface (formalised cross-cutting; previously implicit "camera ingest thread" in architecture) + `LiveCameraFrameSource` (existing live path, retrofitted) + `VideoFileFrameSource` (replay-only; reads `.mp4` / `.h264` and emits `NavCameraFrame` at configured FPS).
|
||||
- **Owned by**: AZ-265 (E-DEMO-REPLAY); the interface itself + `LiveCameraFrameSource` retrofit are cycle-1 deliverables under AZ-265 child task #1 (Decompose Step 2 amendment — interface was previously implicit).
|
||||
- **Consumed by**: `c1_vio` (constructor-injected), `runtime_root` (composes the right strategy per binary).
|
||||
|
||||
### shared/clock
|
||||
|
||||
- **Directory**: `src/gps_denied_onboard/clock/`
|
||||
- **Purpose**: `Clock` interface + `WallClock` (live) + `TlogDerivedClock` (replay). Per R-DEMO-4: production C1–C5 paths bake real-time-cadence assumptions (e.g., AC-5.2 3 s no-estimate fallback timer); injected `Clock` lets replay mode trip those timers consistently against tlog timestamps rather than wall-clock.
|
||||
- **Owned by**: AZ-265 (E-DEMO-REPLAY) — child task #4 (`compose_replay` + `Clock` injection).
|
||||
- **Consumed by**: `c1_vio`, `c5_state`, `c8_fc_adapter`, any component with timer-driven fallback logic; `runtime_root` (selects WallClock for live/research/operator, TlogDerivedClock for replay).
|
||||
|
||||
### shared/runtime_root
|
||||
|
||||
- **File**: `src/gps_denied_onboard/runtime_root.py`
|
||||
- **Purpose**: Composition root — config → strategy resolution → graph wiring (ADR-009). The ONLY place that may import concrete strategy classes across components. Per-binary CMake `BUILD_*` flags + composition root validator enforce ADR-002 build-time exclusion. Hosts `compose_root(config)` (airborne), `compose_operator(config)` (operator), and `compose_replay(config)` (replay-cli).
|
||||
- **Owned by**: AZ-263 (Bootstrap stub); per-component additions that wire a new strategy are owned jointly by the bootstrap epic and the consuming component task (touching `runtime_root.py` is allowed only via the explicit "wire-in" task in each component's epic). The `compose_replay` extension is owned by AZ-265 child task #4.
|
||||
- **Consumed by**: the airborne binary entrypoint + the operator-tooling binary entrypoint + the research/comparative binary entrypoint + the replay-cli binary entrypoint.
|
||||
|
||||
### shared/cli/replay
|
||||
|
||||
- **File**: `src/gps_denied_onboard/cli/replay.py`
|
||||
- **Purpose**: `gps-denied-replay` CLI entrypoint. Args: `--video PATH --tlog PATH --output results.jsonl --camera-calibration calib.json --config config.yaml --pace {realtime,asap} [--time-offset-ms N]`.
|
||||
- **Owned by**: AZ-265 (E-DEMO-REPLAY) — child task #5.
|
||||
- **Consumed by**: the `gps-denied-replay-cli` Docker image entrypoint; parent-suite UI backend (subprocess shell-out per AZ-265 architecture decision).
|
||||
|
||||
### shared/healthcheck
|
||||
|
||||
- **File**: `src/gps_denied_onboard/healthcheck.py`
|
||||
- **Purpose**: Importable healthcheck callable used by Dockerfile `HEALTHCHECK CMD` and CI smoke.
|
||||
- **Owned by**: AZ-263.
|
||||
- **Consumed by**: companion-tier1 Dockerfile, operator-tooling Dockerfile, CI smoke job.
|
||||
|
||||
## Allowed Dependencies (Layering)
|
||||
|
||||
Read top-to-bottom; an upper layer may import from a lower layer but NEVER the reverse. Cross-layer violations are **Architecture** findings in code-review (High severity).
|
||||
|
||||
| Layer | Components / Modules | May import from |
|
||||
|-------|---------------------|-----------------|
|
||||
| 5. Entry / Composition | `runtime_root`, `cli/replay`, `healthcheck` | 1, 2, 3, 4 |
|
||||
| 4. Adapters | c8_fc_adapter (incl. `tlog_replay_adapter` + `replay_sink`), c11_tile_manager, c10_provisioning, c12_operator_tooling, `frame_source/VideoFileFrameSource` + `frame_source/LiveCameraFrameSource` | 1, 2, 3 (limited — see notes) |
|
||||
| 3. Domain (runtime path) | c1_vio, c2_vpr, c2_5_rerank, c3_matcher, c3_5_adhop, c4_pose, c5_state, c13_fdr | 1, 2 |
|
||||
| 2. Infrastructure | c6_tile_cache, c7_inference | 1 |
|
||||
| 1. Foundation (shared) | `_types`, `config`, `logging`, `fdr_client`, `helpers/*`, `frame_source` (interface only), `clock` | (none) |
|
||||
|
||||
**Layer-specific notes**:
|
||||
|
||||
- **Layer 3 → Layer 4 is BANNED**. Domain components must not import adapter-layer components. C1's reception of FC telemetry happens via a constructor-injected `FcAdapter` interface (the interface lives in `c8_fc_adapter` Public API) — C1 imports the *interface* from a Layer-4 component's Public API, which is technically a downward-pointing import on the dependency graph, but the runtime data flow is Layer 4 → Layer 3 (FC → C1). This is the standard "interface lives at the producer" Hexagonal pattern; flagged here so the cross-verification step (Step 4) doesn't false-positive it.
|
||||
- **C3 → C2.5 is BANNED at runtime** (R14): both must import `helpers.lightglue_runtime` instead. Enforced by the absence of any `from gps_denied_onboard.components.c2_5_rerank import ...` line inside `c3_matcher/`.
|
||||
- **`runtime_root.py` may import any component's concrete impl**; everywhere else, cross-component imports go through the consumed component's Public API only.
|
||||
|
||||
## Build-Time Exclusion Map (ADR-002)
|
||||
|
||||
Four binaries are built from this codebase: **airborne** (Tier-1 + Tier-2 production), **research** (IT-12 comparative-study, links every strategy), **operator-tooling** (pre-flight workflows on operator workstation), **replay-cli** (offline `gps-denied-replay` against video + tlog; AZ-265).
|
||||
|
||||
| CMake flag | Components / native libs gated | Airborne | Research | Operator-tooling | Replay-cli |
|
||||
|-----------|-------------------------------|----------|----------|------------------|------------|
|
||||
| `BUILD_OKVIS2` | c1_vio/okvis2, cpp/okvis2 | ON | ON | OFF | ON |
|
||||
| `BUILD_VINS_MONO` | c1_vio/vins_mono, cpp/vins_mono | OFF | ON | OFF | OFF |
|
||||
| `BUILD_KLT_RANSAC` | c1_vio/klt_ransac, cpp/klt_ransac | ON (mandatory baseline) | ON | OFF | ON |
|
||||
| `BUILD_VPR_<variant>` (UltraVPR, MegaLoc, MixVPR, SelaVPR, EigenPlaces, NetVLAD, SALAD) | c2_vpr/<variant> | UltraVPR ON, others OFF | all ON | OFF | UltraVPR ON, others OFF |
|
||||
| `BUILD_TENSORRT_RUNTIME` | c7_inference/tensorrt_runtime | ON | ON | ON (operator pre-compiles engines) | ON |
|
||||
| `BUILD_PYTORCH_RUNTIME` | c7_inference/pytorch_fp16_runtime | OFF | ON | OFF | OFF |
|
||||
| `BUILD_C10_PROVISIONING` | c10_provisioning | OFF | OFF | ON | OFF |
|
||||
| `BUILD_C11_TILE_MANAGER` | c11_tile_manager | OFF | OFF | ON | OFF |
|
||||
| `BUILD_C12_OPERATOR_TOOLING` | c12_operator_tooling | OFF | OFF | ON | OFF |
|
||||
| `BUILD_GTSAM_BINDINGS` | cpp/gtsam_bindings (used by c4_pose + c5_state) | ON | ON | OFF | ON |
|
||||
| `BUILD_FAISS_INDEX` | cpp/faiss_index (used by c6_tile_cache) | ON | ON | ON | OFF (replay reads pre-built cache only) |
|
||||
| `BUILD_VIDEO_FILE_FRAME_SOURCE` | `frame_source/VideoFileFrameSource` (AZ-265) | OFF | OFF | OFF | ON |
|
||||
| `BUILD_TLOG_REPLAY_ADAPTER` | `c8_fc_adapter/tlog_replay_adapter` (AZ-265) | OFF | OFF | OFF | ON |
|
||||
| `BUILD_REPLAY_SINK_JSONL` | `c8_fc_adapter/replay_sink` (AZ-265) | OFF | OFF | OFF | ON |
|
||||
| `BUILD_REPLAY_CLI` | `cli/replay.py` entrypoint + `compose_replay` wiring (AZ-265) | OFF | OFF | OFF | ON |
|
||||
| `BUILD_LIVE_CAMERA_FRAME_SOURCE` | `frame_source/LiveCameraFrameSource` (AZ-265 retrofit) | ON | ON | OFF | OFF |
|
||||
|
||||
The composition root validator at startup refuses to wire a strategy whose `BUILD_*` flag is OFF (raises `ConfigurationError` pointing at the offending strategy name + the missing flag).
|
||||
|
||||
Build-time exclusion is enforced by:
|
||||
- CMake reading `cmake/build_options.cmake` per binary target.
|
||||
- Per-binary CI matrix entry in `.github/workflows/ci.yml` (4 parallel build jobs).
|
||||
- `ci/sbom_diff.py` step asserting each binary's SBOM contains exactly the expected component set (e.g., the airborne SBOM MUST NOT contain `c11_tile_manager`; the replay-cli SBOM MUST contain C1–C5 + replay strategies and MUST NOT contain `c10_provisioning`).
|
||||
|
||||
## Layout Conventions (reference)
|
||||
|
||||
| Language | Root | Per-component path | Public API file | Test path |
|
||||
|----------|------|-------------------|-----------------|-----------|
|
||||
| Python (this project) | `src/gps_denied_onboard/` | `src/gps_denied_onboard/components/<component>/` | `src/gps_denied_onboard/components/<component>/__init__.py` (re-exports) + `interface.py` | `tests/unit/<component>/` |
|
||||
| Python (generic) | `src/<pkg>/` | `src/<pkg>/<component>/` | `src/<pkg>/<component>/__init__.py` | `tests/<component>/` |
|
||||
| C# (.NET) | `src/` | `src/<Component>/` | `src/<Component>/<Component>.cs` | `tests/<Component>.Tests/` |
|
||||
| Rust | `crates/` | `crates/<component>/` | `crates/<component>/src/lib.rs` | `crates/<component>/tests/` |
|
||||
| TypeScript / React | `packages/` or `src/` | `src/<component>/` | `src/<component>/index.ts` | `src/<component>/__tests__/` |
|
||||
| Go | `./` | `internal/<component>/` or `pkg/<component>/` | `internal/<component>/doc.go` | `internal/<component>/*_test.go` |
|
||||
|
||||
## Self-Verification Checklist
|
||||
|
||||
- [x] Every component in `_docs/02_document/components/` has a Per-Component Mapping entry (14 components: c1_vio, c2_vpr, c2_5_rerank, c3_matcher, c3_5_adhop, c4_pose, c5_state, c6_tile_cache, c7_inference, c8_fc_adapter, c10_provisioning, c11_tile_manager, c12_operator_tooling, c13_fdr).
|
||||
- [x] Every shared / cross-cutting concern has a Shared section entry (_types, config, logging, fdr_client, frame_source, clock, helpers/* × 8, runtime_root, cli/replay, healthcheck).
|
||||
- [x] Layering table covers every component; foundation at Layer 1.
|
||||
- [x] No component's `Imports from` list points at a component in a higher layer (back-channel exception for C8 → C1/C5 documented as interface-at-producer pattern).
|
||||
- [x] Paths follow Python `src/`-layout convention with single top-level package `gps_denied_onboard/`.
|
||||
- [x] No two components own overlapping paths. Joint native ownership of `cpp/gtsam_bindings/` resolved: c5_state is primary owner; c4_pose READ-ONLY.
|
||||
- [x] Replay-mode additions (AZ-265) covered: new `frame_source/` and `clock/` cross-cuttings, new C8 strategies (`tlog_replay_adapter`, `replay_sink`), new `cli/replay.py` entrypoint, and a fourth `replay-cli` binary added to the Build-Time Exclusion Map.
|
||||
|
||||
## How the implement skill consumes this
|
||||
|
||||
The `/implement` skill's Step 4 (File Ownership) reads this file and, for each task in the batch:
|
||||
|
||||
1. Resolve the task's Component field to a Per-Component Mapping entry.
|
||||
2. Set OWNED = the component's `Owns` glob.
|
||||
3. Set READ-ONLY = the Public API files of every component listed in `Imports from`, plus all `shared/*` Public API files.
|
||||
4. Set FORBIDDEN = every other component's Owns glob.
|
||||
|
||||
Execution inside a batch is already sequential. This mapping is still required because it enforces scope discipline per task — preventing a task from drifting into files that belong to another component.
|
||||
@@ -0,0 +1,377 @@
|
||||
# Initial Project Structure
|
||||
|
||||
**Task**: AZ-263_initial_structure
|
||||
**Name**: Initial Structure
|
||||
**Description**: Scaffold the gps-denied-onboard companion project skeleton — Python+C++ monolith with per-binary CMake `BUILD_*` flags (ADR-002), interface-first components under `src/gps_denied_onboard/components/<component>/` (ADR-009), three Dockerfiles + two compose files for Tier-1, GitHub Actions workflow with dual-binary emit + SBOM diff, PostgreSQL 16 schema mirrored from `satellite-provider`, and the test structure that the per-component test specs already require.
|
||||
**Complexity**: 5 points
|
||||
**Dependencies**: None
|
||||
**Component**: Bootstrap
|
||||
**Tracker**: AZ-263
|
||||
**Epic**: AZ-244 (E-BOOT)
|
||||
|
||||
## Project Folder Layout
|
||||
|
||||
```
|
||||
gps-denied-onboard/
|
||||
├── pyproject.toml # Python project (ruff, mypy --strict, pytest, coverage gates per ci_cd_pipeline.md)
|
||||
├── CMakeLists.txt # Top-level CMake — drives BUILD_VINS_MONO / BUILD_SALAD / etc.
|
||||
├── cmake/
|
||||
│ ├── dependencies.cmake # Pinned commits/refs for OKVIS2, VINS-Mono, GTSAM, FAISS, OpenCV ≥4.12.0 (D-CROSS-CVE-1)
|
||||
│ ├── build_options.cmake # The BUILD_* option() set — single source of truth for ADR-002
|
||||
│ └── strategies.cmake # Helper to register a strategy implementation behind its BUILD_* flag
|
||||
├── .clang-format
|
||||
├── .clang-tidy
|
||||
├── .cmake-format.yaml
|
||||
├── .editorconfig
|
||||
├── .env.example # All env vars from § Environment Variables below
|
||||
├── .dockerignore # Verbatim from deployment/containerization.md § .dockerignore
|
||||
├── .gitignore # Excludes build/, *.engine, *.calib, *.index, .venv/, __pycache__/, tests/fixtures/large_replays/
|
||||
├── README.md # Pointers into _docs/02_document/
|
||||
│
|
||||
├── docker/
|
||||
│ ├── companion-tier1.Dockerfile # Multi-stage: system-deps → python-deps → cpp-build → runtime
|
||||
│ ├── operator-tooling.Dockerfile # python:3.10-slim; installs C11 + C12 + healthcheck
|
||||
│ ├── mock-suite-sat-service.Dockerfile # Lives next to fixtures/mock-suite-sat-service/ but referenced from compose
|
||||
│ └── db-init/
|
||||
│ └── 01_seed.sql.example # Template — real seed lives under tests/fixtures/seed-db.sql
|
||||
│
|
||||
├── docker-compose.yml # Local dev: companion + mock-sat + db + operator-tooling (verbatim from containerization.md § Local Development)
|
||||
├── docker-compose.test.yml # Tier-1 integration/blackbox: extends docker-compose.yml + e2e-runner sidecar
|
||||
│
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ ├── ci.yml # Tier-1 lint + unit + integration + dual-binary build + SBOM diff + security
|
||||
│ ├── ci-tier2.yml # Self-hosted Jetson runner (labels: [self-hosted, jetson, orin-nano-super]); AC-bound NFTs
|
||||
│ ├── release.yml # Tag-on-main: JetPack image build + operator tooling tarball
|
||||
│ └── cve-rescan.yml # Monthly scheduled CVE re-scan (D-CROSS-CVE-1 + GTSAM)
|
||||
│
|
||||
├── ci/
|
||||
│ ├── sbom_diff.py # ADR-002 enforcement — deployment SBOM ⊂ research SBOM, vins_mono/salad/etc. excluded
|
||||
│ └── opencv_pin_gate.py # Asserts resolved OpenCV ≥ 4.12.0
|
||||
│
|
||||
├── src/
|
||||
│ └── gps_denied_onboard/ # Top-level Python package (single package per src-layout convention)
|
||||
│ ├── __init__.py
|
||||
│ ├── runtime_root.py # Composition root — config → strategy resolution → graph wiring (ADR-009)
|
||||
│ ├── healthcheck.py # Used by Dockerfile HEALTHCHECK + CI smoke
|
||||
│ ├── config/
|
||||
│ │ ├── loader.py # YAML config loader + validation (per-flight + camera calibration JSON)
|
||||
│ │ └── schema.py # Config dataclasses
|
||||
│ ├── logging/
|
||||
│ │ └── structured.py # Structured JSON logging (E-CC-LOG / AZ-245); no narrative log lines
|
||||
│ ├── fdr_client/ # E-CC-FDR-CLIENT / AZ-247: lock-free queue + record schema; producer-side API
|
||||
│ │ ├── client.py
|
||||
│ │ ├── records.py # FdrRecord schema (estimates / IMU / MAVLink / health / tile / thumbnail)
|
||||
│ │ └── queue.py # Lock-free SPSC queue per producer, drop-oldest on overrun
|
||||
│ ├── _types/ # Cross-component DTOs (Protocol/dataclass — no implementation here)
|
||||
│ │ ├── nav.py # NavCameraFrame, ImuSample, ImuWindow, AttitudeWindow, FlightStateSignal, GpsHealth
|
||||
│ │ ├── vio.py # VioOutput
|
||||
│ │ ├── vpr.py # VprQuery, VprResult, RerankResult
|
||||
│ │ ├── matching.py # MatchResult
|
||||
│ │ ├── pose.py # PoseEstimate, EstimatorOutput, EstimatorHealth
|
||||
│ │ ├── tile.py # Tile, TileQualityMetadata, TileRecord, SectorClassification
|
||||
│ │ ├── calibration.py # CameraCalibration (intrinsics + distortion + body-to-camera + acquisition_method)
|
||||
│ │ ├── emitted.py # EmittedExternalPosition (per-FC encoded)
|
||||
│ │ └── manifests.py # Manifest, EngineCacheEntry
|
||||
│ ├── helpers/ # Common helpers (shared utilities — no component owns them)
|
||||
│ │ ├── imu_preintegrator.py # _docs/02_document/common-helpers/01_helper_imu_preintegrator.md
|
||||
│ │ ├── se3_utils.py # 02_helper_se3_utils.md
|
||||
│ │ ├── lightglue_runtime.py # 03_helper_lightglue_runtime.md (shared by C2.5 + C3 — owned by helper, NOT C3, R14 fix)
|
||||
│ │ ├── wgs_converter.py # 04_helper_wgs_converter.md
|
||||
│ │ ├── sha256_sidecar.py # 05_helper_sha256_sidecar.md (D-C10-3 content-hash gate)
|
||||
│ │ ├── engine_filename_schema.py # 06_helper_engine_filename_schema.md (D-C10-7 self-describing engine names)
|
||||
│ │ ├── ransac_filter.py # 07_helper_ransac_filter.md
|
||||
│ │ └── descriptor_normaliser.py # 08_helper_descriptor_normaliser.md
|
||||
│ └── components/ # Subpackage — every component has interface.py + default impl + optional alt impls + tests/
|
||||
│ ├── c1_vio/ # AZ-254: VioStrategy (OKVIS2 default, VINS-Mono research-only, KltRansac mandatory baseline)
|
||||
│ ├── c2_vpr/ # AZ-255: VprStrategy (UltraVPR primary; MegaLoc/MixVPR/SelaVPR/EigenPlaces/NetVLAD/SALAD)
|
||||
│ ├── c2_5_rerank/ # AZ-256: InlierBasedReranker (single-pair LightGlue inlier count K=10→N=3)
|
||||
│ ├── c3_matcher/ # AZ-257: CrossDomainMatcher (DISK+LightGlue / ALIKED+LightGlue / XFeat)
|
||||
│ ├── c3_5_adhop/ # AZ-258: AdHoPRefinementStrategy
|
||||
│ ├── c4_pose/ # AZ-259: PoseEstimator (OpenCV solvePnPRansac + GTSAM Marginals)
|
||||
│ ├── c5_state/ # AZ-260: StateEstimator (GTSAM iSAM2 + IncrementalFixedLagSmoother + ESKF baseline)
|
||||
│ ├── c6_tile_cache/ # AZ-250: TileStore (Postgres mirror + filesystem mmap + FAISS HNSW)
|
||||
│ ├── c7_inference/ # AZ-249: InferenceRuntime (TensorRT 10.3 / ONNX Runtime+TRT EP / PyTorch FP16)
|
||||
│ ├── c8_fc_adapter/ # AZ-261: FcAdapter (PymavlinkArdupilotAdapter + Msp2InavAdapter) + GcsAdapter
|
||||
│ ├── c10_provisioning/ # AZ-252: CacheProvisioner (engine compile + descriptors + manifest + content-hash)
|
||||
│ ├── c11_tile_manager/ # AZ-251: TileDownloader + TileUploader (operator-side ONLY — excluded from airborne via CMake)
|
||||
│ ├── c12_operator_tooling/ # AZ-253: CacheBuildWorkflow + OperatorReLocService (CLI; GUI deferred)
|
||||
│ └── c13_fdr/ # AZ-248: FdrWriter (writer thread + segment rotation + ≤64 GB cap)
|
||||
│
|
||||
├── cpp/ # Native libraries linked from src/gps_denied_onboard/components/* via pybind11
|
||||
│ ├── okvis2/ # OKVIS2 wrapper, exposes Python C-extension; C1 production-default
|
||||
│ ├── vins_mono/ # Built only when BUILD_VINS_MONO=ON (research binary)
|
||||
│ ├── klt_ransac/ # Mandatory simple-baseline; always linked
|
||||
│ ├── gtsam_bindings/ # Thin wrapper over GTSAM iSAM2 + IncrementalFixedLagSmoother + Marginals
|
||||
│ ├── faiss_index/ # FAISS HNSW wrapper (mmap-backed)
|
||||
│ └── pybind11/ # Submodule
|
||||
│
|
||||
├── tests/
|
||||
│ ├── conftest.py # Pytest fixtures (Postgres bring-up, SITL containers, Derkachi corpus mount)
|
||||
│ ├── unit/ # Per-component pytest tests (mirrors src/gps_denied_onboard/components/ structure)
|
||||
│ │ ├── c1_vio/ ... # one folder per component
|
||||
│ ├── integration/ # IT-* scenarios from _docs/02_document/tests/blackbox-tests.md (Tier-1 capable)
|
||||
│ ├── e2e/ # End-to-end runner — entrypoint for docker-compose.test.yml
|
||||
│ │ ├── Dockerfile # Slim pytest container
|
||||
│ │ ├── conftest.py
|
||||
│ │ └── scenarios/ # FT-P-* / FT-N-* / NFT-LIM (Tier-1) scenarios
|
||||
│ ├── perf/ # NFT-PERF-* (Tier-2) scenarios
|
||||
│ ├── security/ # NFT-SEC-* scenarios (incl. NFT-SEC-02 network egress, NFT-SEC-05 DNS blackhole)
|
||||
│ ├── resilience/ # NFT-RES-* scenarios
|
||||
│ └── fixtures/
|
||||
│ ├── flight_derkachi/ # Multi-GB; gitignored; provisioned via DVC or out-of-band
|
||||
│ ├── tiles_corpus/ # Curated tile snapshots for FT-P-15/16
|
||||
│ ├── seed-db.sql # PostgreSQL seed mirroring satellite-provider's tile rows
|
||||
│ ├── calibration/
|
||||
│ │ ├── adti26.json # Test-fixture camera calibration
|
||||
│ │ └── adti20.json # Production calibration (D-PROJ-1 hybrid output, when available)
|
||||
│ ├── mock-suite-sat-service/ # .NET 8 minimal API implementing the planned D-PROJ-2 ingest contract (e2e fixture only)
|
||||
│ └── ardupilot_sitl/ # Helper compose for AP SITL (used by IT-3, NFT-PERF-04 AP)
|
||||
│
|
||||
├── scripts/
|
||||
│ ├── run-tests.sh # Tier-1 wrapper around docker-compose.test.yml (deferred from test-spec Phase 4)
|
||||
│ └── run-performance-tests.sh # Tier-2 wrapper (deferred from test-spec Phase 4)
|
||||
│
|
||||
└── _docs/ # Already exists (problem, solution, document, tasks)
|
||||
```
|
||||
|
||||
### Layout Rationale
|
||||
|
||||
- **`src/gps_denied_onboard/`** is the single top-level Python package (per Python `src/`-layout convention). All imports are rooted here: `from gps_denied_onboard.components.c5_state import StateEstimator`, `from gps_denied_onboard.helpers.lightglue_runtime import LightGlueRuntime`, `from gps_denied_onboard._types.pose import EstimatorOutput`. One package per repo keeps `pip install -e .`, `mypy --strict`, and coverage globs unambiguous.
|
||||
- **`gps_denied_onboard/components/<component>/`** — every component is its own folder with `interface.py` (Protocol/ABC) + one or more concrete impls + a private `_internal/` if needed. The `components/` subpackage exists (rather than flattening) because (a) per-component tooling globs (`--cov-fail-under=90` on `src/gps_denied_onboard/components/c5_state/**` and `.../c8_fc_adapter/**` per `ci_cd_pipeline.md § Unit`) need a clean prefix, (b) it preserves the ADR-009 architectural boundary between "components" (strategy-pattern units) and cross-cutting infra (`config/`, `logging/`, `_types/`, `helpers/`, `fdr_client/`), and (c) it gives a 1:1 mirror with `_docs/02_document/components/`. No reaching across folders for siblings; collaborators are constructor-injected. Build-time exclusion is enforced by CMake reading `cmake/build_options.cmake` and by the composition root validator refusing to wire a strategy whose `BUILD_*` flag is OFF.
|
||||
- **`cpp/` is parallel to `src/`** rather than nested under it because CMake is the source of truth for native builds; the Python package imports the resulting `.so` files via pybind11 wrappers stored in `src/gps_denied_onboard/components/<component>/_native/` (not shown above to keep the tree readable — added per component task in Step 2).
|
||||
- **`gps_denied_onboard/helpers/`** is the home of every shared utility called out in `_docs/02_document/common-helpers/*.md`. R14 (LightGlue circular dep) is structurally prevented because both C2.5 and C3 import `gps_denied_onboard.helpers.lightglue_runtime`, never each other.
|
||||
- **`tests/` mirrors the component graph 1:1** so a developer landing on `src/gps_denied_onboard/components/c5_state/` can find its tests at `tests/unit/c5_state/`. Cross-component scenarios live in `tests/integration/`, `tests/e2e/`, `tests/perf/`, `tests/security/`, `tests/resilience/` matching the system-level `_docs/02_document/tests/*.md` files.
|
||||
- **`docker/` and `docker-compose*.yml`** correspond to `_docs/02_document/deployment/containerization.md`. The bootstrap implementer copies the YAML bodies from there verbatim (see § Acceptance Criteria below).
|
||||
- **`.github/workflows/`** mirrors `_docs/02_document/deployment/ci_cd_pipeline.md`. Two workflows: Tier-1 (everything except AC-bound NFTs) and Tier-2 (AC-bound NFTs on self-hosted Jetson).
|
||||
|
||||
## DTOs and Interfaces
|
||||
|
||||
Detailed DTO field lists are already in `architecture.md § 4 Data Model Overview` and per-component `description.md § 2 Internal Interfaces`. The bootstrap task creates **type-only stubs** (no implementation logic) for the cross-component DTOs listed in `src/gps_denied_onboard/_types/` so that downstream component tasks can import and depend on them.
|
||||
|
||||
### Shared DTOs (cross-component — bootstrap creates type stubs)
|
||||
|
||||
| DTO | File | Used By | Source |
|
||||
|-----|------|---------|--------|
|
||||
| `NavCameraFrame` | `_types/nav.py` | C1, C2, C13 | architecture.md § 4 |
|
||||
| `ImuSample`, `ImuWindow`, `AttitudeWindow` | `_types/nav.py` | C1, C5, C8 | C8 inbound spec |
|
||||
| `FlightStateSignal`, `GpsHealth` | `_types/nav.py` | C5, C8, C11 | C8 inbound spec |
|
||||
| `VioOutput` | `_types/vio.py` | C1 → C5; C13 (FDR) | C1 spec |
|
||||
| `VprQuery`, `VprResult` | `_types/vpr.py` | C2 → C2.5 | C2 spec |
|
||||
| `RerankResult` | `_types/vpr.py` | C2.5 → C3 | C2.5 spec |
|
||||
| `MatchResult` | `_types/matching.py` | C3, C3.5 → C4 | C3 spec |
|
||||
| `PoseEstimate` | `_types/pose.py` | C4 → C5; C13 | C4 spec |
|
||||
| `EstimatorOutput`, `EstimatorHealth` | `_types/pose.py` | C5 → C8, C13 | C5 spec |
|
||||
| `Tile`, `TileRecord`, `TileQualityMetadata`, `SectorClassification` | `_types/tile.py` | C6, C10, C11 | C6 / data_model.md § 2.1 |
|
||||
| `CameraCalibration` | `_types/calibration.py` | C1, C3, C4 | architecture.md § 4 |
|
||||
| `EmittedExternalPosition` | `_types/emitted.py` | C8 outbound | C8 spec |
|
||||
| `Manifest`, `EngineCacheEntry` | `_types/manifests.py` | C10, C7 | C10 spec / data_model.md § 2.4–2.5 |
|
||||
| `FdrRecord` (and per-record-type discriminator) | `gps_denied_onboard/fdr_client/records.py` | producer side: every component; consumer side: C13 | C13 spec |
|
||||
|
||||
### Component Interfaces (bootstrap creates empty `interface.py` per component)
|
||||
|
||||
For each `src/gps_denied_onboard/components/<component>/`, bootstrap creates:
|
||||
|
||||
- `interface.py` — the `Protocol` / `ABC` taken verbatim from the component spec's § 2 Internal Interfaces
|
||||
- `__init__.py` exporting the interface symbol
|
||||
- `_native/` placeholder if the component has a C++ binding (C1, C5, C6, C7)
|
||||
- `tests/__init__.py` placeholder under `tests/unit/<component>/`
|
||||
|
||||
Concrete implementations are NOT created here — they are the subject of Step 2 component tasks.
|
||||
|
||||
| Component | Interface | Defined in (spec) |
|
||||
|-----------|-----------|-------------------|
|
||||
| C1 | `VioStrategy` | `components/01_c1_vio/description.md § 2` |
|
||||
| C2 | `VprStrategy`, `VprPipeline` | `components/02_c2_vpr/description.md § 2` |
|
||||
| C2.5 | `Reranker` | `components/03_c2_5_rerank/description.md § 2` |
|
||||
| C3 | `CrossDomainMatcher` | `components/04_c3_matcher/description.md § 2` |
|
||||
| C3.5 | `AdHoPRefinementStrategy` | `components/05_c3_5_adhop/description.md § 2` |
|
||||
| C4 | `PoseEstimator` | `components/06_c4_pose/description.md § 2` |
|
||||
| C5 | `StateEstimator` | `components/07_c5_state/description.md § 2` |
|
||||
| C6 | `TileStore`, `DescriptorIndex` | `components/08_c6_tile_cache/description.md § 2` |
|
||||
| C7 | `InferenceRuntime`, `EngineLoader` | `components/09_c7_inference/description.md § 2` |
|
||||
| C8 | `FcAdapter`, `GcsAdapter` | `components/10_c8_fc_adapter/description.md § 2` |
|
||||
| C10 | `CacheProvisioner` | `components/11_c10_provisioning/description.md § 2` |
|
||||
| C11 | `TileDownloader`, `TileUploader` | `components/12_c11_tilemanager/description.md § 2` |
|
||||
| C12 | `CacheBuildWorkflow`, `OperatorReLocService` | `components/13_c12_operator_tooling/description.md § 2` |
|
||||
| C13 | `FdrWriter` (consumer side) | `components/14_c13_fdr/description.md § 2` |
|
||||
|
||||
## CI/CD Pipeline
|
||||
|
||||
Bootstrap creates the workflow files matching `_docs/02_document/deployment/ci_cd_pipeline.md`. Stages (verbatim mapping):
|
||||
|
||||
| Stage | File | Trigger | Quality Gate |
|
||||
|-------|------|---------|--------------|
|
||||
| Lint | `.github/workflows/ci.yml` | every push, every PR | `ruff` + `mypy --strict` (Python); `clang-format --dry-run` + `clang-tidy` (C++); `cmake-format --check` (CMake); `yamllint` + `markdownlint-cli` (YAML/MD) |
|
||||
| Unit | `.github/workflows/ci.yml` | every push, every PR | `pytest --cov-fail-under=75` per component; `--cov-fail-under=90` on C5, C8; C++ `gtest + lcov` ≥75% (90% on `klt_ransac`) |
|
||||
| Integration (Tier-1) | `.github/workflows/ci.yml` | every push, every PR | `docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from e2e-runner --build` |
|
||||
| Build (dual binary) | `.github/workflows/ci.yml` | every push, every PR | matrix: `[deployment (BUILD_VINS_MONO=OFF, BUILD_SALAD=OFF), research (all ON)]` — both must build green |
|
||||
| SBOM diff | `.github/workflows/ci.yml` | after build | `ci/sbom_diff.py`: deployment SBOM excludes `vins_mono`, `salad`, `c11_tilemanager` (R02 enforcement); deployment ⊂ research |
|
||||
| Security | `.github/workflows/ci.yml` | after build | `pip-audit`, `dotnet list package --vulnerable` for mock-sat, Trivy on images, `ci/opencv_pin_gate.py` ≥4.12.0 |
|
||||
| Push images (Tier-1) | `.github/workflows/ci.yml` | merge to `dev`/`stage`/`main` | tag `${BRANCH}-${BUILD_KIND}-${SHORT_SHA}`, push to GHCR; PRs do NOT push |
|
||||
| Build (Tier-2 deployment) | `.github/workflows/ci-tier2.yml` | merge to `dev`/`stage`/`main` | native build on `[self-hosted, jetson, orin-nano-super]`; SBOM byte-equal to Tier-1 deployment |
|
||||
| AC-bound NFTs (Tier-2) | `.github/workflows/ci-tier2.yml` | merge events; manual on PR | NFT-PERF-01..04, NFT-LIM-01..04, NFT-RES-03/04, NFT-SEC-01/03/05, IT-12 — thresholds in `tests/traceability-matrix.md` |
|
||||
| JetPack image build | `.github/workflows/release.yml` | tag on `main` | image built + signed + attested; checksum signed by Tier-1 secret manager |
|
||||
| Operator tooling tarball | `.github/workflows/release.yml` | tag on `main` | bundle: `operator-tooling` image + `mock-suite-sat-service` image + compose + verification script + `_docs/02_document/` |
|
||||
| CVE re-scan (monthly) | `.github/workflows/cve-rescan.yml` | scheduled cron | re-runs security stage against pinned versions; pages onboard team on new High/Critical |
|
||||
|
||||
### Pipeline Configuration Notes
|
||||
|
||||
- **Manual-trigger override**: AC-bound NFTs initially run on manual trigger only while Tier-2 runner is being provisioned (see `deployment_procedures.md § Tier-2 enablement`). The merge gate on `dev` excludes Tier-2; `stage`/`main` retain the full gate.
|
||||
- **Caching keys** per `ci_cd_pipeline.md § Caching Strategy`: Python deps keyed by `pyproject.toml` hash; C++ build deps keyed by `cmake/dependencies.cmake` hash; Docker layers keyed by Dockerfile + dep-file hashes; TRT engine cache (Tier-2) keyed by `engine_cache_bundle_hash` from `data_model.md § 2.4`.
|
||||
- **Notifications**: Slack `#gps-denied-ci` for build/Tier-2 failures + SBOM diff fail PR comments; email + page on JetPack signature mismatch.
|
||||
|
||||
## Environment Strategy
|
||||
|
||||
Per `architecture.md § 3 Deployment Model`. Bootstrap defines **dev-tier1** and **production-operator-workstation** for local execution; **staging-tier2** and **production** are bare JetPack and have no Dockerfile (the JetPack image is assembled by `release.yml`).
|
||||
|
||||
| Environment | Purpose | Configuration Notes |
|
||||
|-------------|---------|---------------------|
|
||||
| `dev-tier1` | Local dev, lint, unit, most integration | Workstation Docker; `LOG_LEVEL=DEBUG`; `mock-suite-sat-service` Docker; test-fixture calibration `adti26.json`; both BUILD_* tracks available |
|
||||
| `staging-tier1` | CI runs that don't require Jetson | GitHub-hosted x86_64 runner; same Docker as dev-tier1 |
|
||||
| `staging-tier2` | CI runs that require Jetson (AC-bound) | Self-hosted Jetson runner; bare JetPack 6.2; no Docker; both binary tracks built |
|
||||
| `production` | Deployed companion image on a UAV | Jetson Orin Nano Super; bare JetPack; `LOG_LEVEL=INFO` to FDR; per-flight MAVLink signing key generated at takeoff load + zeroised at FDR rollover; **no inbound network listening** + DNS blackhole + `iptables OUTPUT REJECT` (NFT-SEC-05) |
|
||||
| `production-operator-workstation` | Pre-flight tile download + cache build (C10) + post-landing tile upload (C11) + FDR retrieval | Operator's Linux workstation; Docker for `satellite-provider` mirror + `operator-tooling` |
|
||||
|
||||
### Environment Variables (`.env.example`)
|
||||
|
||||
| Variable | dev-tier1 | staging-tier2 | production | Description |
|
||||
|----------|-----------|---------------|------------|-------------|
|
||||
| `GPS_DENIED_FC_PROFILE` | `ardupilot_plane` | `ardupilot_plane` or `inav` (matrix) | exactly one of `ardupilot_plane` / `inav` per build | Selects FC adapter at composition root |
|
||||
| `GPS_DENIED_TIER` | `1` | `2` | `2` | Read by composition root + tests to gate hardware-dependent paths |
|
||||
| `DB_URL` | `postgresql://gps_denied:dev@db:5432/gps_denied` | `postgresql://gps_denied:dev@db:5432/gps_denied` | local Postgres on companion (NVM) | C6 PostgreSQL connection |
|
||||
| `SATELLITE_PROVIDER_URL` | `http://mock-sat:5100` | mix: real `satellite-provider` (download) + `mock-sat:5100` (upload until D-PROJ-2 ships) | not set in flight (no egress); operator workstation only | Pre-flight tile fetch |
|
||||
| `CAMERA_CALIBRATION_PATH` | `/fixtures/calibration/adti26.json` | `/fixtures/calibration/adti26.json` | `/etc/gps-denied/calibration/adti20.json` | Loaded once at startup |
|
||||
| `LOG_LEVEL` | `DEBUG` | `INFO` | `INFO` | Structured JSON output |
|
||||
| `LOG_SINK` | `console` | `journald` + `fdr` | `fdr` only (per-flight ≤64 GB) | E-CC-LOG sink selection |
|
||||
| `MAVLINK_SIGNING_KEY` | dev key from `tests/fixtures/mavlink_signing/dev_key` | per-flight key from test config | generated at takeoff load; rotated per flight; logged to FDR | D-C8-9 / R03 |
|
||||
| `BUILD_VINS_MONO` | both | both (matrix) | `OFF` (production-only) | CMake-time exclusion (ADR-002) |
|
||||
| `BUILD_SALAD` | both | both | `OFF` | ADR-002 |
|
||||
| `BUILD_C11_TILE_MANAGER` | `ON` (operator tooling image) | `OFF` (airborne) / `ON` (operator tooling) | `OFF` (R02 process isolation) | Excludes operator-only code path from airborne image |
|
||||
| `INFERENCE_BACKEND` | `tensorrt` if GPU; else `pytorch_fp16` | `tensorrt` | `tensorrt` | C7 backend selection |
|
||||
| `FDR_PATH` | `/var/lib/gps-denied/fdr` | `/var/lib/gps-denied/fdr` | host NVM mount (≤64 GB) | C13 FDR ring path |
|
||||
| `TILE_CACHE_PATH` | `/var/lib/gps-denied/tiles` | `/var/lib/gps-denied/tiles` | host NVM mount | C6 filesystem tile body store |
|
||||
|
||||
## Database Migration Approach
|
||||
|
||||
**Migration tool**: `alembic` (Python; matches Python+C++ stack and integrates with `pyproject.toml`).
|
||||
**Strategy**: versioned migration scripts under `db/migrations/`. **Additive-only by default per `data_model.md § 1 principle #5`** — migrations may add tables/columns/indexes/CHECK constraints, may NEVER rename or drop existing columns without an ADR-recorded deprecation window. The `tiles` schema specifically is **frozen on the canonical columns** (mirrored from `satellite-provider`) and only extensible via additive onboard-only columns.
|
||||
|
||||
### Initial Schema (migration `0001_initial.sql`)
|
||||
|
||||
Per `data_model.md § 2`:
|
||||
|
||||
- `tiles` — mirrored from `satellite-provider` (canonical columns) + onboard-only additive columns (`source` CHECK in `('googlemaps', 'onboard_ingest')`, `tile_quality_metadata` jsonb on `onboard_ingest` rows, `voting_status` enum)
|
||||
- `flights` — per-flight metadata; FK target for `engine_cache_entries` and `manifests`
|
||||
- `sector_classifications` — operator-set; drives freshness threshold via `apply_freshness_threshold`
|
||||
- `manifests` — D-C10-1 idempotence (hash of model + calibration + corpus + sector classification)
|
||||
- `engine_cache_entries` — TRT engine + INT8 calibration cache keyed by SM/JP/TRT/precision tuple (D-C10-7)
|
||||
|
||||
Seed data (`tests/fixtures/seed-db.sql`): a curated subset of `satellite-provider` tile rows for `tests/fixtures/tiles_corpus/` so Tier-1 integration tests have deterministic input.
|
||||
|
||||
## Test Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── conftest.py # Postgres bring-up via docker-compose; AP/iNav SITL fixtures; Derkachi corpus mount
|
||||
├── unit/<component>/ # Per-component pytest tests; coverage gate per ci_cd_pipeline.md § Unit
|
||||
├── integration/ # IT-* scenarios (Tier-1 capable); pytest-driven; uses docker-compose.test.yml
|
||||
├── e2e/ # FT-P-* / FT-N-* / NFT-LIM (Tier-1) scenarios — runs inside e2e-runner sidecar
|
||||
│ ├── Dockerfile
|
||||
│ ├── conftest.py
|
||||
│ └── scenarios/
|
||||
├── perf/ # NFT-PERF-* (Tier-2 only); pytest + jetson-stats integration
|
||||
├── security/ # NFT-SEC-* incl. NFT-SEC-02 network egress + NFT-SEC-05 DNS blackhole + NFT-SEC-01 cache poisoning
|
||||
├── resilience/ # NFT-RES-* — Monte-Carlo + scheduled-perturbation
|
||||
└── fixtures/
|
||||
├── flight_derkachi/ # Multi-GB; gitignored; provisioned via DVC or out-of-band
|
||||
├── tiles_corpus/ # Curated tile snapshots for FT-P-15/16
|
||||
├── seed-db.sql
|
||||
├── calibration/{adti26,adti20}.json
|
||||
├── mavlink_signing/dev_key
|
||||
├── mock-suite-sat-service/ # .NET 8 minimal API; ASP.NET Core; implements POST /api/satellite/tiles/ingest
|
||||
└── ardupilot_sitl/
|
||||
```
|
||||
|
||||
### Test Configuration Notes
|
||||
|
||||
- **Test runner**: `pytest` for everything Python; `gtest` for C++ unit tests built under CMake `BUILD_TESTING=ON`. Both run from the same Tier-1 CI workflow.
|
||||
- **Fixture lifecycle**: `conftest.py` brings Postgres + mock-sat + ArduPilot SITL up via `docker-compose.test.yml`; tests that need real Jetson hardware (NFT-PERF, NFT-LIM, NFT-RES on real fixtures, IT-12) live under `tests/perf/` + `tests/resilience/` and are guarded by `pytest.mark.tier2` so they're auto-skipped on Tier-1.
|
||||
- **Test isolation**: Postgres uses transaction-rollback fixtures for unit/integration tests; e2e tests get a fresh DB volume per scenario. Tile cache and FDR mount tmpfs in CI.
|
||||
- **Test data management**: per `tests/test-data.md`. Large fixtures (Derkachi, tile corpus) live under `tests/fixtures/large_replays/` which is gitignored + `.dockerignore`'d (mounted as volume in `docker-compose.test.yml`).
|
||||
- **Source of truth for scenario specs**: `_docs/02_document/tests/*.md` — implementation tasks (Step 9 Decompose Tests) bind these specs to actual test files. Bootstrap only creates the directory scaffolding.
|
||||
|
||||
## Implementation Order
|
||||
|
||||
Driven by the epic dependency graph in `_docs/02_document/epics.md § Implementation order`:
|
||||
|
||||
| Order | Component | Reason |
|
||||
|-------|-----------|--------|
|
||||
| 1 | Bootstrap (this task) — repo skeleton, Docker, CI, DB schema | foundation; no dependencies |
|
||||
| 2 | E-CC-LOG (AZ-245), E-CC-CONF (AZ-246) — cross-cutting | unblocks every component's structured logging and config loading |
|
||||
| 3 | E-CC-FDR-CLIENT (AZ-247) — FDR producer client | every component's FDR producer depends on the lock-free queue + record schema |
|
||||
| 4 | E-C13 (AZ-248) — FDR writer | consumer side; needs records.py from #3; also unblocks E-C7 instrumentation |
|
||||
| 5 | E-C7 (AZ-249) — inference runtime + E-C6 (AZ-250) — tile cache | parallel; enable C2 / C3 / C3.5 development |
|
||||
| 6 | E-C11 (AZ-251) — Tile Manager (operator-side) | unblocks F1 + F10 tooling |
|
||||
| 7 | E-C10 (AZ-252) — pre-flight cache provisioning → E-C12 (AZ-253) — operator tooling | sequential; F1 chain |
|
||||
| 8 | E-C1 (AZ-254) — VIO | independent of #5–#7; can start in parallel after #2 |
|
||||
| 9 | E-C2 (AZ-255) → E-C2.5 (AZ-256) → E-C3 (AZ-257) → E-C3.5 (AZ-258) → E-C4 (AZ-259) | F3 hot path; sequential per pipeline order |
|
||||
| 10 | E-C5 (AZ-260) — state estimator | needs C1 + C4; the largest single epic |
|
||||
| 11 | E-C8 (AZ-261) — FC + GCS adapter | needs C5; gates IT-3 (R03 lock) |
|
||||
| 12 | E-BBT (AZ-262) — system-level FT/NFT scenarios | hardens behind every other epic; per-component tests live in their own epics |
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Project scaffolded matching the layout above**
|
||||
Given the structure plan above
|
||||
When the implementer executes this task
|
||||
Then every folder shown in `## Project Folder Layout` exists, every `__init__.py` / `interface.py` placeholder exists, and `pyproject.toml` + `CMakeLists.txt` + `cmake/dependencies.cmake` + `cmake/build_options.cmake` parse without error.
|
||||
|
||||
**AC-2: DTO type stubs importable**
|
||||
Given the bootstrap output
|
||||
When `python -c "from gps_denied_onboard._types import nav, vio, vpr, matching, pose, tile, calibration, emitted, manifests"` is run
|
||||
Then no `ImportError` raises (the modules need not implement methods — only declare the classes/Protocols/dataclasses).
|
||||
|
||||
**AC-3: Three Dockerfiles + two compose files match `containerization.md`**
|
||||
Given the bootstrap output
|
||||
When `docker compose -f docker-compose.yml config --quiet` and `docker compose -f docker-compose.test.yml config --quiet` are run
|
||||
Then both exit zero (compose files are syntactically valid + service references resolve). The Dockerfile bodies match the per-image specifications in `_docs/02_document/deployment/containerization.md` § Component Dockerfiles.
|
||||
|
||||
**AC-4: CI workflow stubs are syntactically valid**
|
||||
Given the bootstrap output
|
||||
When the GitHub Actions workflows under `.github/workflows/` are validated (`actionlint` or equivalent)
|
||||
Then they parse without error AND the dual-binary build matrix (deployment + research) is present in `ci.yml` per ADR-002.
|
||||
|
||||
**AC-5: Database migration tool wired**
|
||||
Given the bootstrap output
|
||||
When `alembic check` is run against an empty database
|
||||
Then it reports `0001_initial.sql` as the head revision and the schema matches the canonical `tiles` columns from `satellite-provider` (with the additive onboard-only columns documented in `data_model.md § 2.1.1`).
|
||||
|
||||
**AC-6: Health checks defined**
|
||||
Given the bootstrap output
|
||||
Then `src/gps_denied_onboard/healthcheck.py` exists and is importable; the operator-tooling Dockerfile + companion-tier1 Dockerfile both reference it via `HEALTHCHECK CMD`; mock-suite-sat-service Dockerfile defines `GET /healthz` returning 200 when storage backend is mounted.
|
||||
|
||||
**AC-7: Structured logging entrypoint exists**
|
||||
Given the bootstrap output
|
||||
Then `src/gps_denied_onboard/logging/structured.py` exists, exports `get_logger(name)`, and emits one JSON object per log line (no narrative log lines per E-CC-LOG / AZ-245 contract).
|
||||
|
||||
**AC-8: `.env.example` documents every variable used by the composition root**
|
||||
Given the bootstrap output
|
||||
Then `.env.example` matches the table in § Environment Strategy above; missing required variables cause `runtime_root.py` to refuse startup with a clear error pointing at the offending variable name.
|
||||
|
||||
**AC-9: Stub tests pass**
|
||||
Given the scaffolded project
|
||||
When `pytest -q` runs against `tests/unit/`
|
||||
Then every component folder has at least one `test_smoke.py` whose only assertion is that the component's interface is importable; the run exits zero.
|
||||
|
||||
**AC-10: SBOM diff script + OpenCV pin gate exist and run on stub builds**
|
||||
Given the bootstrap output
|
||||
Then `ci/sbom_diff.py --deployment <stub> --research <stub>` runs without exception; `ci/opencv_pin_gate.py` exits zero when fed a `pyproject.toml` with `opencv-python ≥ 4.12.0`.
|
||||
|
||||
## Out of Scope (deferred to other tasks)
|
||||
|
||||
- Concrete implementations of any component interface — that's Step 2 component tasks (one task per child issue from `epics.md`).
|
||||
- Module layout — that's Decompose Step 1.5 (`module-layout.md`).
|
||||
- Test scenario implementation — that's Step 9 Decompose Tests + Step 10 Implement Tests.
|
||||
- D-PROJ-2 ingest contract implementation in `mock-suite-sat-service` — initial Dockerfile + minimal API placeholder only; the actual contract is implemented as a scoped task once the parent-suite design lands.
|
||||
- TensorRT engine compilation, GTSAM iSAM2 substrate wiring, FAISS HNSW index build — owned by C7, C5, C6 epics respectively.
|
||||
+35
-5
@@ -2,12 +2,42 @@
|
||||
|
||||
## Current Step
|
||||
flow: greenfield
|
||||
step: 3
|
||||
name: Plan
|
||||
step: 6
|
||||
name: Decompose
|
||||
status: in_progress
|
||||
sub_step:
|
||||
phase: 21
|
||||
name: plan-step6-epics-local-draft
|
||||
detail: "Step 5 complete; Step 6 epics.md drafted locally (18 epics, 2054 lines, chunked write per new large-file-writes rule); awaiting user decision on Jira push"
|
||||
phase: 3
|
||||
name: decompose-step2-task-decomposition
|
||||
detail: |
|
||||
Step 1 (Bootstrap) and Step 1.5 (Module Layout) approved by user. Step 2 task PLAN approved
|
||||
(97 implementation tasks across 18 epics — see chat plan table). Two new epics created during
|
||||
Step 2 prep: AZ-264 (E-CC-HELPERS) re-homes the 8 shared helpers per the cross-cutting rule;
|
||||
AZ-265 (E-DEMO-REPLAY) adds offline replay mode (video + tlog → JSONL per-tick coordinate
|
||||
stream; subprocess-shaped UI integration; no ROS dependency). epics.md and module-layout.md
|
||||
updated to reflect both new epics. Per user's choice (option D), Step 2 EXECUTION
|
||||
(~290 MCP calls to write specs + create tickets + rename files) is deferred to a fresh chat.
|
||||
Resume in fresh chat by reading this state file + epics.md + module-layout.md, then
|
||||
iterate epic-by-epic in dependency order: AZ-245, AZ-246, AZ-247, AZ-264, AZ-248, AZ-249,
|
||||
AZ-250, AZ-251, AZ-252, AZ-253, AZ-254, AZ-255, AZ-256, AZ-257, AZ-258, AZ-259, AZ-260,
|
||||
AZ-261, AZ-265 (replay tasks last; depend on every C1–C5 epic).
|
||||
retry_count: 0
|
||||
cycle: 1
|
||||
tracker: jira
|
||||
|
||||
## Resume notes (fresh chat handoff)
|
||||
|
||||
When resuming Step 2 execution in a fresh chat, the agent should:
|
||||
|
||||
1. Read `_docs/_autodev_state.md` (this file).
|
||||
2. Read `_docs/02_document/epics.md` (canonical E-* ↔ AZ-NN map; rows 17–21).
|
||||
3. Read `_docs/02_document/module-layout.md` (file-ownership map for the implement skill).
|
||||
4. Read `_docs/02_tasks/todo/AZ-263_initial_structure.md` (already-completed bootstrap task spec).
|
||||
5. Replay any leftovers in `_docs/_process_leftovers/` per `tracker.mdc`.
|
||||
6. Begin Step 2 execution one epic at a time, in the order listed above. Per-epic
|
||||
per-task workflow: write `todo/[##]_[short_name].md` → create Jira ticket parented to
|
||||
the right epic → rename file to `todo/[AZ-NN]_[short_name].md` → update Tracker/Epic
|
||||
fields inside the file → repeat. Brief progress note after each epic completes.
|
||||
7. After all 19 epics' tasks are written, run Step 4 (Cross-Verification) to produce
|
||||
`_docs/02_tasks/_dependencies_table.md` and resolve any cross-task dependency drift.
|
||||
8. Then auto-chain to Step 3 (Blackbox Test Tasks) for E-BBT (AZ-262) per the
|
||||
user's earlier choice to defer test-spec Phase 4 to the Decompose pass.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Testing strategy without real flight.
|
||||
|
||||
upload tlog file
|
||||
upload video synced with tlog
|
||||
|
||||
|
||||
system should:
|
||||
1. extract timestamps, imu and gps from the tlog file.
|
||||
2. usually video and tlog aren't synchronized. So system should synchronize them by itself.
|
||||
Usual test is done on the quadcopters, so usually it starts from the drone on the ground and ends with the drone on the ground. These sessions are clearly visible in the chart IMU data of the tlog file. So, system can check the duration of the video and events in IMU chart in tlog. Then it can analyze by IMU the moment of actual take off and sync them
|
||||
3. then make SITL and provide IMU and frames to the gps denied onboard system
|
||||
Reference in New Issue
Block a user