# 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.
9. **AZ-507 cross-component contract surface** — the only places a `components/<X>/*.py` file may import are: its own subpackage (`gps_denied_onboard.components.<X>.*`), `_types/*`, `_types.inference_errors`, `helpers/*`, `config`, `logging`, `fdr_client`, `clock`, `frame_source` (interface only). Cross-component contracts (Protocols + typed exceptions) reach consumers through `_types/*` modules — DTOs in the canonical `_types` files (e.g. `_types.inference.EngineCacheEntry`), typed-error envelopes in `_types.inference_errors`, and consumer-side structural `Protocol` cuts defined locally inside each consuming component (e.g. `c10_provisioning.engine_compiler.CompileEngineCallable`). NEVER `from gps_denied_onboard.components.<other_component> import ...` — the AZ-270 `test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategies` lint enforces this on every `components/**/*.py`. The composition root (`runtime_root/*`) is the single exception; it wires concrete strategies into duck-typed Protocol parameters via constructor injection. This rule is the architectural contract paired with the AZ-270 lint; see `architecture.md` § Cross-Component Contract Surface for the rationale.

## 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`, `config`, `logging`, `fdr_client`. The TileStore query surface (c6) and the InferenceRuntime surface (c7) are obtained via constructor-injected consumer-side structural Protocol cuts (see AZ-507 cross-component rule below); the composition root wires the concrete c6/c7 strategies in. NEVER `from gps_denied_onboard.components.c6_tile_cache import ...` or `from gps_denied_onboard.components.c7_inference import ...` inside `c2_vpr/*.py`.
- **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`, `RerankCandidate`, `RerankError` family, `C2_5RerankConfig`)
  - `interface.py` (`ReRankStrategy` Protocol)
  - `config.py` (`C2_5RerankConfig` dataclass; registered on import; `strategy`, `top_n`, `debug_per_frame_log` fields)
  - `errors.py` (`RerankError`, `RerankBackboneError`, `RerankAllCandidatesFailedError`)
- **Internal**:
  - `inlier_based_reranker.py` (`InlierCountReRanker` — single-pair LightGlue inlier count K=10→N=3, AZ-343; module-level `create()` factory entry-point consumed by `runtime_root.rerank_factory.build_rerank_strategy`; gated by `BUILD_RERANK_INLIER_COUNT`)
- **Owns**: `src/gps_denied_onboard/components/c2_5_rerank/**`, `src/gps_denied_onboard/runtime_root/rerank_factory.py`, `tests/unit/c2_5_rerank/**`
- **Imports from**: `_types`, `helpers.lightglue_runtime`, `helpers.feature_extractor` (AZ-343 scope expansion), `helpers.descriptor_normaliser`, `helpers.ransac_filter`, `helpers.se3_utils`, `clock`, `config`, `logging`, `fdr_client`. The `TileStore`, `TilePixelHandle`, and `TileCacheError` family from c6 are obtained via constructor-injected consumer-side structural Protocol cuts + shared DTOs in `_types` (see AZ-507 cross-component rule below); composition root wires the concrete c6 strategy in. NEVER `from gps_denied_onboard.components.c6_tile_cache import ...` inside `c2_5_rerank/*.py`.
- **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`, `MatcherHealth`, `CandidateMatchSet`, `MatcherError`, `MatcherBackboneError`, `InsufficientInliersError`, `C3MatcherConfig`)
  - `interface.py` (`CrossDomainMatcher` Protocol)
  - `config.py` (`C3MatcherConfig`)
  - `errors.py` (error hierarchy)
- **Internal**:
  - `_health_window.py` (`RollingHealthWindow` accumulator; constructor-injected into every concrete matcher)
  - `disk_lightglue.py` (DISK + LightGlue, AZ-345)
  - `aliked_lightglue.py` (ALIKED + LightGlue, AZ-346)
  - `xfeat.py` (XFeat, AZ-347)
  - `_native/`
- **Owns**: `src/gps_denied_onboard/components/c3_matcher/**`, `tests/unit/c3_matcher/**`, `src/gps_denied_onboard/runtime_root/matcher_factory.py`
- **Imports from**: `_types`, `helpers.lightglue_runtime` (R14: SHARED with C2.5 — owned by helper, NOT by C3), `helpers.ransac_filter`, `helpers.descriptor_normaliser`, `helpers.se3_utils`, `config`, `logging`, `fdr_client`. The InferenceRuntime surface (c7) is obtained via a constructor-injected consumer-side structural Protocol cut (see AZ-507 cross-component rule below); composition root wires the concrete c7 strategy in. NEVER `from gps_denied_onboard.components.c7_inference import ...` inside `c3_matcher/*.py`.
- **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 `ConditionalRefiner`, `C3_5RefinerConfig`)
  - `interface.py` (`ConditionalRefiner` Protocol)
  - `config.py` (`C3_5RefinerConfig`)
  - `errors.py` (`RefinerError`, `RefinerBackboneError`, `RefinerConfigError` — held internal to the component; consumers reach them only via tests)
- **Internal**:
  - `passthrough_refiner.py` (reference baseline; AZ-348)
  - `adhop_refiner.py` (production-default; AZ-349 pending)
- **Owns**: `src/gps_denied_onboard/components/c3_5_adhop/**`, `tests/unit/c3_5_adhop/**`, `src/gps_denied_onboard/runtime_root/refiner_factory.py`
- **Imports from**: `_types`, `helpers.ransac_filter` (R14: SHARED with C3 and C4 — owned by helper, NOT by C3.5), `helpers.se3_utils`, `config`, `logging`, `fdr_client`. The InferenceRuntime surface (c7) is obtained via a constructor-injected consumer-side structural Protocol cut (see AZ-507 cross-component rule below); composition root wires the concrete c7 strategy in. NEVER `from gps_denied_onboard.components.c7_inference import ...` inside `c3_5_adhop/*.py`.
- **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` (`PoseEstimate` DTO lives here), `helpers.imu_preintegrator`, `helpers.se3_utils`, `helpers.wgs_converter`, `config`, `logging`, `fdr_client`. NEVER `from gps_denied_onboard.components.c4_pose import ...` inside `c5_state/*.py` — the `PoseEstimate` DTO is consumed exclusively via `_types`.
- **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` `__all__`; consult the module for the canonical list):
  - `interface.py` (three Protocols: `TileStore`, `TileMetadataStore`, `DescriptorIndex` — query/get/put surface; concrete impls swappable)
  - DTOs: `TileId`, `TileMetadata`, `TileMetadataPersistent`, `TileQualityMetadata`, `Bbox`, `SectorBoundary`, `HnswParams`, `IndexMetadata`
  - Enums: `TileSource`, `FreshnessLabel`, `VotingStatus`, `SectorClassification`
  - ABC: `TilePixelHandle`
  - Config block: `C6TileCacheConfig` (registered on import)
  - Error family rooted at `TileCacheError` with documented subtypes (`TileNotFoundError`, `TileFsError`, `TileMetadataError`, `ContentHashMismatchError`, `FreshnessRejectionError`, `CacheBudgetExhaustedError`, `IndexUnavailableError`) + sibling `IndexBuildError` (offline build envelope, not in the `TileCacheError` family)
- **Internal**:
  - `faiss_descriptor_index.py` (AZ-306 — production-default `DescriptorIndex` strategy backed by the `faiss-cpu` PyPI wheel; HNSW32 search + atomic rebuild + triple-sidecar coherence + warm-up; `from_config` classmethod consumed by `runtime_root.storage_factory.build_descriptor_index`. Gated by `BUILD_FAISS_INDEX` at the factory boundary, NOT at module import.)
  - `_tile_pixel_handle.py` (`TilePixelHandle` ABC)
  - `_types.py` (DTOs / enums; consumed via the Public API re-exports)
  - `_uuid_namespace.py` (AZ-304 — pinned `TILE_NAMESPACE_UUID` + `derive_tile_id` / `derive_location_hash` helpers; cross-repo coordinated with `satellite-provider`)
  - `migrations.py` (AZ-304 — `apply_migrations(config) -> MigrationResult` runner invoked by the composition root at startup)
  - `postgres_filesystem_store.py` (AZ-305 — production-default `TileStore` + `TileMetadataStore` impl over Postgres mirror + filesystem; `PostgresFilesystemStore.from_config` opens its own `psycopg_pool.ConnectionPool` and constructs an `AZ-307 FreshnessGate`)
  - `freshness_gate.py` (AZ-307 — `ACTIVE_CONFLICT` reject + `STABLE_REAR` downgrade per `freshness_gate.md` v1.0.0)
  - `cache_budget_enforcer.py` (AZ-308 — RESTRICT-SAT-2 10 GiB hard cap; `CacheBudgetEnforcer.reserve_headroom` + `BudgetEnforcedTileStore` write-decorator)
  - `tools.py` (AZ-305 — operator dump CLI invoked via `python -m gps_denied_onboard.components.c6_tile_cache.tools ...`)
  - `errors.py`, `config.py` (component plumbing)
- **Owns**: `src/gps_denied_onboard/components/c6_tile_cache/**`, `tests/unit/c6_tile_cache/**`, `db/migrations/**` (project-level Alembic env owned by c6 — `alembic.ini` at repo root points here; `0001_initial.py` shipped by AZ-263 bootstrap, `0002_c6_tile_identity_and_lru.py` and forward owned by AZ-304+ migrations). AZ-306 retired the `cpp/faiss_index/` placeholder in favour of the `faiss-cpu` PyPI wheel; the `BUILD_FAISS_INDEX` flag is preserved as a runtime/factory gate (consumed by `runtime_root.storage_factory`).
- **Imports from**: `_types`, `helpers.sha256_sidecar`, `helpers.wgs_converter`, `clock`, `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` `__all__`; consult the module for the canonical list):
  - `interface.py` (`InferenceRuntime` Protocol)
  - DTOs re-exported from `_types.inference` + `_types.thermal`: `BuildConfig`, `EngineCacheEntry`, `EngineHandle`, `OptimizationProfile`, `PrecisionMode`, `ThermalState`
  - Component services: `EngineGate` + `HostTuple` (AZ-301), `ThermalStatePublisher` + `ThermalReading` + `ThermalSource` Protocol (AZ-302), `ManifestReader` + `ManifestReaderProtocol` + `DeploymentManifest` (AZ-301), `ArchitectureFactory` + `default_registry` + `register_architecture` (AZ-300)
  - Config block: `C7InferenceConfig` (registered on import)
  - Error family rooted at `RuntimeError` with documented subtypes (`InferenceError`, `EngineBuildError`, `EngineDeserializeError`, `EngineHashMismatchError`, `EngineSchemaMismatchError`, `EngineSidecarMissingError`, `CalibrationCacheError`, `OutOfMemoryError`, `TelemetryUnavailableError`)
- **Internal**:
  - `architecture_registry.py` (AZ-300; family of registered `ArchitectureFactory` callables consumed by `PytorchFp16Runtime`)
  - `config.py` (`C7InferenceConfig` dataclass; registered on import)
  - `engine_gate.py` (AZ-301; D-C10-3 + D-C10-7 takeoff validator)
  - `errors.py` (component error family)
  - `manifest.py` (AZ-301; `DeploymentManifest` + `ManifestReader` for engine sidecar manifests)
  - `onnx_trt_ep_runtime.py` (AZ-299; ONNX Runtime + TensorRT EP fallback strategy + per-flight ORT TRT subgraph cache + one-shot fallback WARN/FDR/GCS alert + CPU-fallback gate)
  - `pytorch_fp16_runtime.py` (AZ-300; research-only / simple-baseline strategy)
  - `tensorrt_runtime.py` (AZ-298; production-default TensorRT 10.3 strategy + INT8 calibration cache trust + GPU memory budget enforcement + `python -m ...tensorrt_runtime compile ...` CLI)
  - `thermal_publisher.py` (AZ-302; 1 Hz background poller, jtop/NVML fallback)
- **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` (`EstimatorOutput` DTO lives here), `helpers.wgs_converter`, `helpers.se3_utils`, `config`, `logging`, `fdr_client`, `clock` (for replay timer-injection). NEVER `from gps_denied_onboard.components.c5_state import ...` inside `c8_fc_adapter/*.py` — the `EstimatorOutput` DTO is consumed exclusively via `_types`.
- **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`, plus AZ-321 surface: `EngineCompiler`, `BackboneSpec`, `EngineCompileRequest`, `EngineCompileResult`, `CompileOutcome`, `EngineCompileSummary`, `CompileEngineCallable`, `BackboneConfig`, `C10ProvisioningConfig`, plus AZ-322 surface: `DescriptorBatcher`, `BackboneEmbedder`, `C7EngineBackboneEmbedder`, `C10BatcherConfig`, `CorpusFilter`, `DescriptorBatchReport`, `ProgressEvent`, `TileBboxRecord`, `BatcherTile`, `TilesByBboxBatchQuery`, `TilePixelOpener`, `DescriptorIndexRebuilder`, `DescriptorBatchError`)
  - `interface.py` (`CacheProvisioner` Protocol, `BackboneEmbedder` Protocol — AZ-322)
  - Config block: `C10ProvisioningConfig` (registered on import)
- **Internal**:
  - `engine_compiler.py` (AZ-321; per-model TRT compile + hardware-tied cache reuse + `CompileEngineCallable` structural cut of the C7 InferenceRuntime)
  - `config.py` (AZ-321; `BackboneConfig` + `C10ProvisioningConfig` dataclasses)
  - `descriptor_batcher.py` (AZ-322; `DescriptorBatcher` + DTOs + consumer-side Protocols `TilesByBboxBatchQuery` / `TilePixelOpener` / `DescriptorIndexRebuilder`)
  - `c7_engine_embedder.py` (AZ-322; `C7EngineBackboneEmbedder` adapter wrapping AZ-297 `InferenceRuntime` + AZ-321 engine path)
  - `default_provisioner.py` (engine compile + descriptors + manifest + content-hash gate, pending)
  - Composition root: `runtime_root/c10_factory.py` (`build_engine_compiler`, `build_backbone_specs`, `build_manifest_builder`, `build_manifest_verifier`, `build_descriptor_batcher` + the C6→C10 adapters `c6_tile_metadata_store_to_tiles_batch_query`, `c6_tile_store_to_pixel_opener`, `c6_descriptor_index_to_rebuilder`)
- **Owns**: `src/gps_denied_onboard/components/c10_provisioning/**`, `tests/unit/c10_provisioning/**`
- **Imports from**: `_types` (cross-component DTOs `EngineCacheEntry`, `BuildConfig`, `PrecisionMode`, `OptimizationProfile`, `HostCapabilities`, `TileMetadata`, etc.), `_types.inference_errors` (AZ-507 typed-error envelope for `EngineBuildError` + `CalibrationCacheError`), `helpers.sha256_sidecar`, `helpers.engine_filename_schema`, `helpers.wgs_converter`, `config`, `logging`, `fdr_client`. The `InferenceRuntime.compile_engine` surface (c7) and the `TileMetadataStore.query_by_bbox` surface (c6) are obtained via constructor-injected consumer-side structural Protocol cuts (the `CompileEngineCallable` cut already lives in `engine_compiler.py`; AZ-323 / AZ-324 will define analogous `query_by_bbox` cuts inside `c10_provisioning/`). NEVER `from gps_denied_onboard.components.c6_tile_cache import ...` or `from gps_denied_onboard.components.c7_inference import ...` inside `c10_provisioning/*.py`.
- **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`, `config`, `logging`, `fdr_client`. The c6 storage surface (`TileStore`, `TileMetadataStore`) is obtained via constructor-injected consumer-side structural Protocol cuts (see AZ-507 cross-component rule below); composition root wires the concrete c6 strategy in. NEVER `from gps_denied_onboard.components.c6_tile_cache import ...` inside `c11_tile_manager/*.py`.
- **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`, `config`, `logging`, `fdr_client`. The c6 / c10 / c11 surfaces (`TileStore`, `TileMetadataStore`, `CacheProvisioner`, `TileDownloader`, `TileUploader`) are obtained via constructor-injected consumer-side structural Protocol cuts (see AZ-507 cross-component rule below); composition root wires the concrete c6/c10/c11 strategies in. NEVER `from gps_denied_onboard.components.c6_tile_cache import ...`, `from gps_denied_onboard.components.c10_provisioning import ...`, or `from gps_denied_onboard.components.c11_tile_manager import ...` inside `c12_operator_tooling/*.py`.
- **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/feature_extractor

- **Directory**: `src/gps_denied_onboard/helpers/feature_extractor.py`
- **Purpose**: Shared image → `KeypointSet` Protocol + placeholder `OpenCvOrbExtractor` impl (AZ-343 scope expansion). Lets every consumer that feeds `LightGlueRuntime.match` reach for the SAME extractor (same descriptor distribution, same `descriptor_dim`) without each strategy reinventing its own preprocessing.
- **Owned by**: AZ-343.
- **Consumed by**: c2_5_rerank (today via `InlierCountReRanker`), c3_matcher (future concrete strategies in AZ-345 / AZ-346 / AZ-347).

### 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` | c6_tile_cache `FaissDescriptorIndex` (faiss-cpu wheel; runtime gate at `runtime_root.storage_factory` — no native target) | 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.
