Implements two new C12 services and rebalances the C11/C12 boundary in one atomic commit: * AZ-329 PostLandingUploadOrchestrator — gates C11 upload on the `flight_footer` FDR record's `clean_shutdown` field; 4 refusal modes; new FdrFooterReader Protocol + LocalFdrFooterReader. * AZ-330 OperatorReLocService — AC-3.4 visual-loss re-localization hint; reuses shared LatLonAlt; OperatorCommandTransport Protocol cut (E-C8 owns the future pymavlink concrete); new FDR record kind `c12.reloc.requested`; log redaction (lat/lon 5 decimals, reason 200 chars). * AZ-523 C11 internal flight-state gate removed (SRP refactor): `confirm_flight_state` / `FlightStateSignal` use / `FlightStateNotOnGroundError` deleted from C11; TileUploader contract bumped to v2.0.0 (frozen) with migration note; AZ-317 superseded. * AZ-524 Package rename `c12_operator_tooling` → `c12_operator_orchestrator` across source, tests, pyproject, CMake, Dockerfile, compose, CI, runtime-root services class (`OperatorOrchestratorServices`) + factory function (`build_operator_orchestrator`), logger namespaces, config slug, docs, and the E-C12 epic title. Tests: 1543 passed, 80 skipped (all environment gates). Targeted AC suite (AZ-329 + AZ-330 + FdrFooterReader): 37 passed. Cold-start NFR-perf still ≤ 500 ms p99. Tracker: AZ-317 → Done (superseded); AZ-319 v2.0.0 contract bump comment; AZ-329/AZ-330 → In Testing; AZ-253 epic renamed; AZ-523 + AZ-524 created and closed as audit-trail tickets. See `_docs/03_implementation/batch_44_cycle1_report.md`. Co-authored-by: Cursor <cursoragent@cursor.com>
33 KiB
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_orchestrator/ # 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 Pythonsrc/-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 keepspip install -e .,mypy --strict, and coverage globs unambiguous.gps_denied_onboard/components/<component>/— every component is its own folder withinterface.py(Protocol/ABC) + one or more concrete impls + a private_internal/if needed. Thecomponents/subpackage exists (rather than flattening) because (a) per-component tooling globs (--cov-fail-under=90onsrc/gps_denied_onboard/components/c5_state/**and.../c8_fc_adapter/**perci_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 readingcmake/build_options.cmakeand by the composition root validator refusing to wire a strategy whoseBUILD_*flag is OFF.cpp/is parallel tosrc/rather than nested under it because CMake is the source of truth for native builds; the Python package imports the resulting.sofiles via pybind11 wrappers stored insrc/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 importgps_denied_onboard.helpers.lightglue_runtime, never each other.tests/mirrors the component graph 1:1 so a developer landing onsrc/gps_denied_onboard/components/c5_state/can find its tests attests/unit/c5_state/. Cross-component scenarios live intests/integration/,tests/e2e/,tests/perf/,tests/security/,tests/resilience/matching the system-level_docs/02_document/tests/*.mdfiles.docker/anddocker-compose*.ymlcorrespond 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— theProtocol/ABCtaken verbatim from the component spec's § 2 Internal Interfaces__init__.pyexporting the interface symbol_native/placeholder if the component has a C++ binding (C1, C5, C6, C7)tests/__init__.pyplaceholder undertests/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_orchestrator/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 ondevexcludes Tier-2;stage/mainretain the full gate. - Caching keys per
ci_cd_pipeline.md § Caching Strategy: Python deps keyed bypyproject.tomlhash; C++ build deps keyed bycmake/dependencies.cmakehash; Docker layers keyed by Dockerfile + dep-file hashes; TRT engine cache (Tier-2) keyed byengine_cache_bundle_hashfromdata_model.md § 2.4. - Notifications: Slack
#gps-denied-cifor 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 fromsatellite-provider(canonical columns) + onboard-only additive columns (sourceCHECK in('googlemaps', 'onboard_ingest'),tile_quality_metadatajsonb ononboard_ingestrows,voting_statusenum)flights— per-flight metadata; FK target forengine_cache_entriesandmanifestssector_classifications— operator-set; drives freshness threshold viaapply_freshness_thresholdmanifests— 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:
pytestfor everything Python;gtestfor C++ unit tests built under CMakeBUILD_TESTING=ON. Both run from the same Tier-1 CI workflow. - Fixture lifecycle:
conftest.pybrings Postgres + mock-sat + ArduPilot SITL up viadocker-compose.test.yml; tests that need real Jetson hardware (NFT-PERF, NFT-LIM, NFT-RES on real fixtures, IT-12) live undertests/perf/+tests/resilience/and are guarded bypytest.mark.tier2so 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 undertests/fixtures/large_replays/which is gitignored +.dockerignore'd (mounted as volume indocker-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.