Option A (minimum-deprecation, 2 SP) per user complexity-budget decision. Auto-sync stays importable as a raising stub for one cycle so external callers see a clean ReplayInputAdapterError instead of an ImportError. Full physical removal is filed as AZ-908 (cycle-5+ backlog). Production: - auto_sync.py: 700+ LOC -> 56-line no-op stub raising "auto-sync removed; supply --imu CSV instead" - tlog_video_adapter.py: 700+ LOC -> 105-line deprecated stub; ReplayInputAdapter.open() raises immediately, close() is a no-op - _replay_branch.py: dropped legacy auto-sync branch + _build_auto_sync_config; _validate_replay_paths now requires imu_csv_path; replay_input_adapter_factory parameter removed - cli/replay.py: --time-offset-ms / --skip-auto-sync / --auto-trim emit DeprecationWarning + stderr line; values ignored - tlog_replay_adapter.py + tlog_ground_truth.py docstrings: AUDIT-ONLY Tests: - DELETED test_az405_auto_sync, test_az405_replay_input_adapter, test_az698_window_alignment (covered code no longer runs) - ADDED test_az895_auto_sync_deprecated_stub (5 parametrised, pins AC-1) - test_az402_replay_cli: deprecation warnings + ignored-value asserts - test_az401_compose_root_replay: new imu_csv_path-required gate; deleted the calibration-loading test that relied on the removed replay_input_adapter_factory injection point - test_derkachi_real_tlog: xfail reason refreshed to AZ-848 + AZ-883 (AC-4 "AZ-848-scoped reason") Docs: - module-layout.md: replay_input file list flags deprecated modules, adds csv_ground_truth.py - _dependencies_table.md: +AZ-908 row, preamble + totals updated (179 -> 180 tasks, 567 -> 570 SP) - AZ-908 backlog spec added; AZ-895 spec moved todo -> done - batch_03_cycle4_report.md written Touched-module tests green (111 passed, 1 skipped). Full unit suite green: 2287 passed, 85 skipped, 1 deselected (pre-existing flaky perf test, unrelated). Co-authored-by: Cursor <cursoragent@cursor.com>
54 KiB
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
- The single top-level Python package is
src/gps_denied_onboard/. All imports are rooted there. No sibling packages live undersrc/. - 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). - Cross-cutting concerns own ONE folder each directly under
src/gps_denied_onboard/:_types/,helpers/,config/,logging/,fdr_client/,frame_source/,clock/,replay_input/. Plusruntime_root.pyandhealthcheck.pyat the package root. - Native (C++) libraries live under
cpp/(parallel tosrc/, NOT nested), built by CMake; per-component pybind11 wrappers live atsrc/gps_denied_onboard/components/<component>/_native/<name>.pyand import the resulting.sofrom a CMake-known path. - Public API surface per component = the files listed in each component's
Public APIlist below. Anything not listed is internal and MUST NOT be imported from another component. - 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). - Tests mirror the component graph 1:1 at
tests/unit/<component>/. In-process cross-component scenarios that import SUT source live undertests/integration/. The blackbox / e2e test harness — which MUST NOT import SUT source and exercises the system only via public boundaries (MAVLink / MSP2 / HTTP / filesystem) — lives at the repo-roote2e/directory and is owned by theblackbox_testscross-cutting entry (Shared section). Performance, resilience, security, and resource-limit scenarios that are also boundary-driven likewise live undere2e/tests/<category>/; only in-process performance/security micro-tests (if any) would live undertests/perf/,tests/security/,tests/resilience/. - Build-time exclusion (ADR-002): each
<component>/_native/and the correspondingcpp/<lib>/carry a CMakeBUILD_<NAME>flag. The composition root validator refuses to wire a strategy whose flag is OFF. - AZ-507 cross-component contract surface — the only places a
components/<X>/*.pyfile 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_typesfiles (e.g._types.inference.EngineCacheEntry), typed-error envelopes in_types.inference_errors, and consumer-side structuralProtocolcuts defined locally inside each consuming component (e.g.c10_provisioning.engine_compiler.CompileEngineCallable). NEVERfrom gps_denied_onboard.components.<other_component> import ...— the AZ-270test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategieslint enforces this on everycomponents/**/*.py. The composition root (runtime_root/*) is the single exception; it wires concrete strategies into duck-typed Protocol parameters via constructor injection. Two narrow carve-outs apply to the lint's enforcement oncomponents/**/*.py(AZ-847, source of truth: docstring oftests/unit/test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategies): (a) bench exclusion —components/<X>/bench/**files are skipped entirely, since benchmark / measurement code legitimately constructs production strategies viaruntime_root.*factories (that is its job); (b) self-registration carve-out — anImportFromwhose module starts withgps_denied_onboard.runtime_root.AND every imported name starts withregister_is allowed (the registry pattern, e.g.c5_state.gtsam_isam2_estimator.registercallingruntime_root.state_factory.register_state_estimator). Any other import fromruntime_root.*inside a Layer-3 component (e.g.build_*factories,compose_root,StrategyNotLinkedError) remains a violation. This rule is the architectural contract paired with the AZ-270 lint; seearchitecture.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-exportsVioStrategy,VioOutput)src/gps_denied_onboard/components/c1_vio/interface.py(VioStrategyProtocol)
- Internal (do NOT import from other components):
src/gps_denied_onboard/components/c1_vio/okvis2.py(production-default; linkscpp/okvis2/)src/gps_denied_onboard/components/c1_vio/vins_mono.py(research-only; gated byBUILD_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-exportsVprStrategy,VprQuery,VprResult)interface.py(VprStrategyProtocol)
- 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. NEVERfrom gps_denied_onboard.components.c6_tile_cache import ...orfrom gps_denied_onboard.components.c7_inference import ...insidec2_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-exportsReRankStrategy,RerankResult,RerankCandidate,RerankErrorfamily,C2_5RerankConfig)interface.py(ReRankStrategyProtocol)config.py(C2_5RerankConfigdataclass; registered on import;strategy,top_n,debug_per_frame_logfields)errors.py(RerankError,RerankBackboneError,RerankAllCandidatesFailedError)
- Internal:
inlier_based_reranker.py(InlierCountReRanker— single-pair LightGlue inlier count K=10→N=3, AZ-343; module-levelcreate()factory entry-point consumed byruntime_root.rerank_factory.build_rerank_strategy; gated byBUILD_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. TheTileStore,TilePixelHandle, andTileCacheErrorfamily 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. NEVERfrom gps_denied_onboard.components.c6_tile_cache import ...insidec2_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-exportsCrossDomainMatcher,MatchResult,MatcherHealth,CandidateMatchSet,MatcherError,MatcherBackboneError,InsufficientInliersError,C3MatcherConfig)interface.py(CrossDomainMatcherProtocol)config.py(C3MatcherConfig)errors.py(error hierarchy)
- Internal:
_health_window.py(RollingHealthWindowaccumulator; 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. NEVERfrom gps_denied_onboard.components.c7_inference import ...insidec3_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-exportsConditionalRefiner,C3_5RefinerConfig)interface.py(ConditionalRefinerProtocol)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. NEVERfrom gps_denied_onboard.components.c7_inference import ...insidec3_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-exportsPoseEstimator,PoseEstimate,EstimatorOutput)interface.py(PoseEstimatorProtocol)
- Internal:
opencv_pnp_estimator.py(OpenCVsolvePnPRansac+ GTSAM Marginals for covariance)_native/(GTSAM bindings viacpp/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 forcpp/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-exportsStateEstimator,EstimatorOutput,EstimatorHealth)interface.py(StateEstimatorProtocol)
- 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(PoseEstimateDTO lives here),helpers.imu_preintegrator,helpers.se3_utils,helpers.wgs_converter,config,logging,fdr_client. NEVERfrom gps_denied_onboard.components.c4_pose import ...insidec5_state/*.py— thePoseEstimateDTO 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
TileCacheErrorwith documented subtypes (TileNotFoundError,TileFsError,TileMetadataError,ContentHashMismatchError,FreshnessRejectionError,CacheBudgetExhaustedError,IndexUnavailableError) + siblingIndexBuildError(offline build envelope, not in theTileCacheErrorfamily)
- Internal:
faiss_descriptor_index.py(AZ-306 — production-defaultDescriptorIndexstrategy backed by thefaiss-cpuPyPI wheel; HNSW32 search + atomic rebuild + triple-sidecar coherence + warm-up;from_configclassmethod consumed byruntime_root.storage_factory.build_descriptor_index. Gated byBUILD_FAISS_INDEXat the factory boundary, NOT at module import.)_tile_pixel_handle.py(TilePixelHandleABC)_types.py(DTOs / enums; consumed via the Public API re-exports)_uuid_namespace.py(AZ-304 — pinnedTILE_NAMESPACE_UUID+derive_tile_id/derive_location_hashhelpers; cross-repo coordinated withsatellite-provider)migrations.py(AZ-304 —apply_migrations(config) -> MigrationResultrunner invoked by the composition root at startup)postgres_filesystem_store.py(AZ-305 — production-defaultTileStore+TileMetadataStoreimpl over Postgres mirror + filesystem;PostgresFilesystemStore.from_configopens its ownpsycopg_pool.ConnectionPooland constructs anAZ-307 FreshnessGate)freshness_gate.py(AZ-307 —ACTIVE_CONFLICTreject +STABLE_REARdowngrade perfreshness_gate.mdv1.0.0)cache_budget_enforcer.py(AZ-308 — RESTRICT-SAT-2 10 GiB hard cap;CacheBudgetEnforcer.reserve_headroom+BudgetEnforcedTileStorewrite-decorator)tools.py(AZ-305 — operator dump CLI invoked viapython -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.iniat repo root points here;0001_initial.pyshipped by AZ-263 bootstrap,0002_c6_tile_identity_and_lru.pyand forward owned by AZ-304+ migrations). AZ-306 retired thecpp/faiss_index/placeholder in favour of thefaiss-cpuPyPI wheel; theBUILD_FAISS_INDEXflag is preserved as a runtime/factory gate (consumed byruntime_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(InferenceRuntimeProtocol)- DTOs re-exported from
_types.inference+_types.thermal:BuildConfig,EngineCacheEntry,EngineHandle,OptimizationProfile,PrecisionMode,ThermalState - Component services:
EngineGate+HostTuple(AZ-301),ThermalStatePublisher+ThermalReading+ThermalSourceProtocol (AZ-302),ManifestReader+ManifestReaderProtocol+DeploymentManifest(AZ-301),ArchitectureFactory+default_registry+register_architecture(AZ-300) - Config block:
C7InferenceConfig(registered on import) - Error family rooted at
RuntimeErrorwith documented subtypes (InferenceError,EngineBuildError,EngineDeserializeError,EngineHashMismatchError,EngineSchemaMismatchError,EngineSidecarMissingError,CalibrationCacheError,OutOfMemoryError,TelemetryUnavailableError)
- Internal:
architecture_registry.py(AZ-300; family of registeredArchitectureFactorycallables consumed byPytorchFp16Runtime)config.py(C7InferenceConfigdataclass; 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+ManifestReaderfor 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+noop_mavlink_transport.pyas gated strategies; live transport code is retrofitted asSerialMavlinkTransportbehind a newMavlinkTransportProtocol seam (no behaviour change) so the C8 outbound encoders are byte-identical between live and replay (replay protocol Invariant 5) - Directory:
src/gps_denied_onboard/components/c8_fc_adapter/ - Public API:
__init__.py(re-exportsFcAdapter,GcsAdapter,ReplaySink,MavlinkTransport,EmittedExternalPosition)interface.py(FcAdapter,GcsAdapter,MavlinkTransportProtocols;ReplaySinkProtocol lives inreplay_sink.pyper the replay contract)
- 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-modeFcAdapter; gatedBUILD_TLOG_REPLAY_ADAPTER; ON in airborne per ADR-011; AZ-265)csv_replay_adapter.py(CsvReplayFcAdapter— outbound shim for the AZ-894 CSV-driven replay path; sameFcAdapterProtocol parity astlog_replay_adapter; gatedBUILD_TLOG_REPLAY_ADAPTERfor the airborne replay binary; AZ-894)replay_sink.py(ReplaySinkinterface +JsonlReplaySinkimpl; gatedBUILD_REPLAY_SINK_JSONL; ON in airborne per ADR-011; AZ-265)noop_mavlink_transport.py(NoopMavlinkTransportfor replay-mode outbound bytes; gatedBUILD_REPLAY_SINK_JSONL; ON in airborne; AZ-265 / AZ-400)serial_mavlink_transport.py(SerialMavlinkTransportretrofit of the existing live-mode UART transport; AZ-265 / AZ-400 no-op restructure)
- Owns:
src/gps_denied_onboard/components/c8_fc_adapter/**,tests/unit/c8_fc_adapter/** - Imports from:
_types(EstimatorOutputDTO lives here),helpers.wgs_converter,helpers.se3_utils,config,logging,fdr_client,clock(for replay timer-injection). NEVERfrom gps_denied_onboard.components.c5_state import ...insidec8_fc_adapter/*.py— theEstimatorOutputDTO is consumed exclusively via_types. - Consumed by:
c1_vio(back-channel: ImuSample, AttitudeWindow),c5_state(back-channel: ImuSample, FlightStateSignal, GpsHealth),runtime_root(live + operator binaries; replay is a mode of the airborne binary per ADR-011, not a separate composition root)
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-exportsCacheProvisioner,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(CacheProvisionerProtocol,BackboneEmbedderProtocol — AZ-322)- Config block:
C10ProvisioningConfig(registered on import)
- Internal:
engine_compiler.py(AZ-321; per-model TRT compile + hardware-tied cache reuse +CompileEngineCallablestructural cut of the C7 InferenceRuntime)config.py(AZ-321;BackboneConfig+C10ProvisioningConfigdataclasses)descriptor_batcher.py(AZ-322;DescriptorBatcher+ DTOs + consumer-side ProtocolsTilesByBboxBatchQuery/TilePixelOpener/DescriptorIndexRebuilder)c7_engine_embedder.py(AZ-322;C7EngineBackboneEmbedderadapter wrapping AZ-297InferenceRuntime+ 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 adaptersc6_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 DTOsEngineCacheEntry,BuildConfig,PrecisionMode,OptimizationProfile,HostCapabilities,TileMetadata, etc.),_types.inference_errors(AZ-507 typed-error envelope forEngineBuildError+CalibrationCacheError),helpers.sha256_sidecar,helpers.engine_filename_schema,helpers.wgs_converter,config,logging,fdr_client. TheInferenceRuntime.compile_enginesurface (c7) and theTileMetadataStore.query_by_bboxsurface (c6) are obtained via constructor-injected consumer-side structural Protocol cuts (theCompileEngineCallablecut already lives inengine_compiler.py; AZ-323 / AZ-324 will define analogousquery_by_bboxcuts insidec10_provisioning/). NEVERfrom gps_denied_onboard.components.c6_tile_cache import ...orfrom gps_denied_onboard.components.c7_inference import ...insidec10_provisioning/*.py. - Consumed by:
c12_operator_orchestrator,runtime_root(operator binary only — excluded from airborne viaBUILD_C10_PROVISIONING=OFFfor 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-exportsTileDownloader,TileUploader)interface.py(TileDownloader,TileUploaderProtocols)
- Internal:
_types.py(component-internal DTOs / enums consumed by the Public API re-exports)config.py(C11Config+C11RetryConfigdataclasses; registered on import)errors.py(component error family —TileManagerError+ Tile* subtypes; AZ-838-era additions:SatelliteProviderRouteError,RouteValidationError,RouteTransientError,RouteTerminalFailureError)idempotent_retry.py(AZ-320 — bounded retry decorator + per-flight signing-key state)route_client.py(AZ-838 —SatelliteProviderRouteClientfor the parent-suite Route API; cycle-3 NEW from batch 107)signing_key.py(AZ-318 — per-flight MAVLink 2.0 signing key handshake + key rotation)tile_downloader.py(AZ-316 — REST client against parent-suitesatellite-provider)tile_uploader.py(AZ-319 — 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. NEVERfrom gps_denied_onboard.components.c6_tile_cache import ...insidec11_tile_manager/*.py. - Consumed by:
c12_operator_orchestrator,runtime_root(operator binary only —BUILD_C11_TILE_MANAGER=OFFfor airborne)
Component: c12_operator_orchestrator
- Epic: AZ-253 (E-C12 Operator Pre-flight Orchestrator)
- Directory:
src/gps_denied_onboard/components/c12_operator_orchestrator/ - Public API:
__init__.py(re-exportsCacheBuildWorkflow,OperatorReLocService)interface.py
- Internal:
cache_build_workflow.py(CLI orchestrator)operator_reloc_service.py(CLI; GUI deferred per epic)sector_classifier.py(operator setsSectorClassification→ C6)
- Owns:
src/gps_denied_onboard/components/c12_operator_orchestrator/**,tests/unit/c12_operator_orchestrator/** - 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. NEVERfrom gps_denied_onboard.components.c6_tile_cache import ...,from gps_denied_onboard.components.c10_provisioning import ..., orfrom gps_denied_onboard.components.c11_tile_manager import ...insidec12_operator_orchestrator/*.py. - Consumed by:
runtime_root(operator binary only —BUILD_C12_OPERATOR_ORCHESTRATOR=OFFfor airborne)
Component: c13_fdr
- Epic: AZ-248 (E-C13 FDR Writer)
- Directory:
src/gps_denied_onboard/components/c13_fdr/ - Public API:
__init__.py(re-exportsFdrWriter)interface.py(FdrWriterProtocol)
- 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'sfdr_clientproducer ultimately writes to the C13 writer at process root)
C13 / fdr_client split: the producer-side
FdrClient(lock-free SPSC queue + record schema) lives insrc/gps_denied_onboard/fdr_client/(cross-cutting; AZ-247 / E-CC-FDR-CLIENT). The consumer-sideFdrWriter(writer thread + segment rotation) lives incomponents/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, RouteSpec). 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.
- Files (selected — see directory for full list):
route.py(AZ-845 / Epic AZ-835 C1 — canonical home):RouteSpec(waypoints + suggested region size + source tlog provenance), produced byreplay_input/tlog_route.py(re-exported), consumed bycomponents/c11_tile_manager/route_client.py
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.pyonly. - 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 →
KeypointSetProtocol + placeholderOpenCvOrbExtractorimpl (AZ-343 scope expansion). Lets every consumer that feedsLightGlueRuntime.matchreach for the SAME extractor (same descriptor distribution, samedescriptor_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_orchestrator.
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/helpers/iso_timestamps
- File:
src/gps_denied_onboard/helpers/iso_timestamps.py - Purpose: Single Layer-1 UTC ISO-8601 timestamp source for FDR record envelopes. Two surfaces —
iso_ts_now() -> str(wall-clock, microsecond precision, formatYYYY-MM-DDTHH:MM:SS.ffffffZ) andiso_ts_from_clock(clock: Clock) -> str(Clock-injected, nanosecond precision, formatYYYY-MM-DDTHH:MM:SS.fffffffffZ). Both terminate with the canonicalZsuffix matching the FDR_TSfixture. Replaces the duplicated private_iso_ts_now(AZ-508) and_iso_ts_from_clock(AZ-526) one-liners that previously lived insidec6_tile_cache,c7_inference,c2_vpr, andc12_operator_orchestrator. Stateless functions; stdlib + Layer-1gps_denied_onboard.clock.Clockonly. - Owned by: AZ-264 (AZ-508 + AZ-526 consolidation tasks).
- Consumed by: c6_tile_cache (
cache_budget_enforcer,postgres_filesystem_store,freshness_gate) + c7_inference (onnx_trt_ep_runtime,thermal_publisher) viaiso_ts_now; c2_vpr (net_vlad,ultra_vpr,_faiss_bridge) + c12_operator_orchestrator (operator_reloc_service) viaiso_ts_from_clock. Future C3 / C4 / C5 FDR producers should import the appropriate helper rather than redefining the one-liner locally.
shared/frame_source
- Directory:
src/gps_denied_onboard/frame_source/ - Purpose:
FrameSourceinterface (formalised cross-cutting; previously implicit "camera ingest thread" in architecture) +LiveCameraFrameSource(existing live path, retrofitted) +VideoFileFrameSource(replay-only; reads.mp4/.h264and emitsNavCameraFrameat configured FPS). - Owned by: AZ-265 (E-DEMO-REPLAY); the interface itself +
LiveCameraFrameSourceretrofit 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:
Clockinterface +WallClock(live + replay-realtime) +TlogDerivedClock(replay-asap). Per R-DEMO-4: production C1–C5 paths bake real-time-cadence assumptions (e.g., AC-5.2 3 s no-estimate fallback timer); injectedClocklets replay mode trip those timers consistently against tlog timestamps rather than wall-clock. - Owned by: AZ-265 (E-DEMO-REPLAY) — task AZ-398 (
FrameSource+Clock). - Consumed by:
c1_vio,c5_state,c8_fc_adapter, any component with timer-driven fallback logic;runtime_root(selects the strategy perconfig.mode+config.replay.pace).
shared/replay_input
- Directory:
src/gps_denied_onboard/replay_input/ - Purpose: Layer-4 cross-cutting coordinator. Under AZ-894 the production replay pipeline drives off the operator's IMU+GPS CSV via
CsvReplayFcAdapter. The legacy(video, tlog)auto-sync surface was deprecated by AZ-895 and will be physically removed by AZ-908. The composition root, in replay mode, builds the CSV bundle (frame source + CSV FC adapter + clock) and wires the returnedReplayInputBundleinto the same C1–C5 pipeline as live. New under ADR-011 (replaces the v1.0.0 design where replay was a separate composition root).__init__.py(re-exportsReplayInputAdapter,ReplayInputBundle,AutoSyncDecision,AutoSyncConfig,ReplayInputAdapterError,CsvGpsFix,CsvGroundTruth,load_csv_ground_truth, plus the AZ-697 / AZ-836 surfaces:TlogGpsFix,TlogGroundTruth,load_tlog_ground_truth,RouteSpec,RouteExtractionError,extract_route_from_tlog)csv_ground_truth.py(AZ-894 —load_csv_ground_truth+CsvGpsFix/CsvGroundTruth; the canonical replay ground-truth surface)interface.py(ReplayInputAdapterclass declaration +ReplayInputBundleDTO +AlignedWindow/AutoSyncConfig/AutoSyncDecisionDTOs — the auto-sync DTOs are deprecated by AZ-895 and slated for removal in AZ-908)errors.py(AZ-405 —ReplayInputAdapterErrorenvelope; subclass ofRuntimeErrorso the airborne main maps every coordinator-scope failure to CLI exit code 2)tlog_video_adapter.py— DEPRECATED (AZ-895):ReplayInputAdapter.open()raisesReplayInputAdapterError; retained as an import-stable stub for one cycle. AZ-908 removes it.auto_sync.py— DEPRECATED (AZ-895): every detector + validator raisesReplayInputAdapterError; retained as an import-stable stub for one cycle. AZ-908 removes it.tlog_ground_truth.py(AZ-697 —load_tlog_ground_truth+TlogGpsFix/TlogGroundTruthfor direct binary tlog GPS-truth extraction; AUDIT-ONLY after AZ-895, retained for the AZ-699 / AZ-701 validation paths against legacy.tlogarchives)tlog_route.py(AZ-836 —extract_route_from_tlog+RouteExtractionError; re-exportsRouteSpecfrom_types.route. Reduces a tlog to a coarsened route via Douglas-Peucker on local ENU; consumed byc11_tile_manager.route_client.SatelliteProviderRouteClient.seed_route)tests/
- Owned by: AZ-265 (E-DEMO-REPLAY) — task AZ-405 (auto-sync + coordinator).
- Consumed by:
runtime_root(replay-mode branch ofcompose_root);cli/replay.py. Layer-4 module: imports from Layer 1 (frame_sourceinterface,clockinterface,_types,config,logging,fdr_client,helpers.wgs_converter) and instantiates Layer-4 strategies (c8_fc_adapter.tlog_replay_adapter,frame_source.video_file_frame_source). Does NOT import from Layer 3 (no component-level dependencies).
shared/runtime_root
- Package:
src/gps_denied_onboard/runtime_root/(Python package; a single-file layout was the Plan-era sketch — cycle 1 grew it into a package because the airborne bootstrap, the registry seam, and the per-component factories each have non-trivial concerns and own their own tests). - 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. - Public surface (cycle-1):
__init__.py— hostscompose_root(config, *, pre_constructed=...)(airborne; serves bothconfig.mode == "live"andconfig.mode == "replay"per ADR-011) andcompose_operator(config)(operator-orchestrator). Also owns the module-level_STRATEGY_REGISTRYdict, theregister_strategy(...)API (AZ-591), and theStrategyNotLinkedErrorenvelope. No separatecompose_replayfunction — replay is a configuration ofcompose_root, not a sibling composition root.airborne_bootstrap.py—register_airborne_strategies()populates the 7 airborne strategy slots in_STRATEGY_REGISTRY;build_pre_constructed(config)builds the airborne 12-key infrastructure dict in 6 dependency-ordered phases (A→F);AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYSis the documented per-component dependency map;AirborneBootstrapErrorenvelope. See architecture.md § ADR-009 cycle-1 implementation note for the rationale._replay_branch.py— replay-only frame source / FC adapter / clock / mavlink transport / replay sink wiring; merged overpre_constructedbycompose_root(replay entries take precedence — they own the replay-only seam per ADR-011).- Per-component factory modules (
c10_factory.py,c11_factory.py,c12_factory.py,clock_factory.py,fc_factory.py,frame_source_factory.py,inference_factory.py,matcher_factory.py,pose_factory.py,refiner_factory.py,rerank_factory.py,state_factory.py,storage_factory.py,vio_factory.py,vpr_factory.py,warm_start_wiring.py,spoof_recovery_sink.py,errors.py) — each builds one component's strategies behind that component'sBUILD_*flag gates.
- Owned by: AZ-263 (Bootstrap stub) for the package skeleton; AZ-591 owns
_STRATEGY_REGISTRY+register_strategy; AZ-618 (umbrella → AZ-619..AZ-624) ownsairborne_bootstrap.py+build_pre_constructed+ per-phase seeds; AZ-625 owns the eager(StateEstimator, ISam2GraphHandle)build (Phase E.5); AZ-687 owns the replay-mode component-block guard. Per-component additions that wire a new strategy are owned jointly by the bootstrap epic and the consuming component task (touching the package is allowed only via the explicit "wire-in" task in each component's epic). The replay-mode branch ofcompose_rootis owned by AZ-401. - Consumed by: the airborne binary entrypoint (live + replay modes), the operator-orchestrator binary entrypoint, and the research/comparative binary entrypoint. Tests stub the registry via
clear_strategy_registry()and the infrastructure dict via the samepre_constructed=...kwarg the production caller uses.
shared/cli/replay
- File:
src/gps_denied_onboard/cli/replay.py - Purpose:
gps-denied-replayconsole-script wrapper around the airborne entrypoint. Args:--video PATH --tlog PATH --output results.jsonl --camera-calibration calib.json --config config.yaml --mavlink-signing-key PATH --pace {realtime,asap} [--time-offset-ms N]. Loads the config, setsconfig.mode = "replay"and the replay-specific paths, and dispatches into the SAME companion entry point as the livegps-denied-onboardCLI. No standalone composition root, no separate process model — just a mode-config wrapper per ADR-011. - Owned by: AZ-265 (E-DEMO-REPLAY) — child task #5.
- Consumed by: the parent-suite UI backend (subprocess shell-out per AZ-265 architecture decision; the operator runs the same airborne Docker image with
gps-denied-replayas the entry command).
shared/healthcheck
- File:
src/gps_denied_onboard/healthcheck.py - Purpose: Importable healthcheck callable used by Dockerfile
HEALTHCHECK CMDand CI smoke. - Owned by: AZ-263.
- Consumed by: companion-tier1 Dockerfile, operator-orchestrator Dockerfile, CI smoke job.
blackbox_tests (cross-cutting test harness)
- Directory:
e2e/(repo root, NOT undertests/) - Purpose: Tier-1 Docker + Tier-2 Jetson blackbox test harness. The runner image is fully separated from the SUT and exercises the system through declared public boundaries only (frame source replay, FC inbound/outbound via SITL, tile-cache mount, MAVLink via mavproxy, FDR filesystem, mock Suite Sat Service). Owns the docker-compose test environment, Jetson Tier-2 runner scripts, fixture builders, runner image, conftest, pytest plugins (csv reporter, evidence bundler), helper modules, and per-category test trees (
positive/,negative/,performance/,resilience/,security/,resource_limit/). - Owned by: epic AZ-262 (E-BBT) — task specs AZ-406 (infrastructure bootstrap), AZ-407..AZ-446 (fixture builders + per-scenario tests + Tier-2 harness wrapper + CSV reporter).
- Owns (exclusive write during implementation):
e2e/** - Imports from: nothing inside
src/gps_denied_onboard/**. The runner image MUST NOT import any SUT module; the only legal interaction surfaces are MAVLink / MSP2 / HTTP / filesystem. Reads RO from_docs/00_problem/input_data/**(bind-mounted test data) and_docs/02_document/tests/**(test specs that drive AC mapping). May import standard ground-side libraries (pymavlink,opencv-python,numpy,scipy,geopy,pytest, etc.) and themsp_gps_toyRust binary via subprocess. - FORBIDDEN:
src/gps_denied_onboard/**(any product source),tests/unit/**,tests/integration/**,cpp/**(native source trees),db/migrations/**. Product-side tests undertests/unit/<component>/remain owned by the respective component per its existing Per-Component Mapping entry. - Consumed by: CI matrix (Tier-1 docker-compose entrypoint, Tier-2 Jetson runner harness); operator manual Tier-2 invocation via
./e2e/jetson/run-tier2.sh. - Layering note: blackbox_tests is an external observer of the SUT — it does not sit in the production layering table. Treat it as a separate harness outside Layers 1–5. The "no Layer-3 → Layer-4 imports" and "interface-at-producer" rules do not apply (no production code lives here).
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 + noop_mavlink_transport + serial_mavlink_transport), c11_tile_manager, c10_provisioning, c12_operator_orchestrator, frame_source/VideoFileFrameSource + frame_source/LiveCameraFrameSource, replay_input |
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
FcAdapterinterface (the interface lives inc8_fc_adapterPublic 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_runtimeinstead. Enforced by the absence of anyfrom gps_denied_onboard.components.c2_5_rerank import ...line insidec3_matcher/. runtime_root.pymay 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 + ADR-011)
Three binaries are built from this codebase: airborne (Tier-1 + Tier-2 production; runs BOTH live and replay modes from a single image per ADR-011), research (IT-12 comparative-study, links every strategy + the same replay strategies as airborne), operator-orchestrator (pre-flight workflows on operator workstation). There is no separate replay-cli binary.
| CMake flag | Components / native libs gated | Airborne | Research | Operator-tooling |
|---|---|---|---|---|
BUILD_OKVIS2 |
c1_vio/okvis2, cpp/okvis2 | ON | ON | OFF |
BUILD_VINS_MONO |
c1_vio/vins_mono, cpp/vins_mono | OFF | ON | OFF |
BUILD_KLT_RANSAC |
c1_vio/klt_ransac, cpp/klt_ransac | ON (mandatory baseline) | ON | OFF |
BUILD_VPR_<variant> (UltraVPR, MegaLoc, MixVPR, SelaVPR, EigenPlaces, NetVLAD, SALAD) |
c2_vpr/ | UltraVPR ON, others OFF | all ON | OFF |
BUILD_TENSORRT_RUNTIME |
c7_inference/tensorrt_runtime | ON | ON | ON (operator pre-compiles engines) |
BUILD_PYTORCH_RUNTIME |
c7_inference/pytorch_fp16_runtime | OFF | ON | OFF |
BUILD_C10_PROVISIONING |
c10_provisioning | OFF | OFF | ON |
BUILD_C11_TILE_MANAGER |
c11_tile_manager | OFF | OFF | ON |
BUILD_C12_OPERATOR_ORCHESTRATOR |
c12_operator_orchestrator | OFF | OFF | ON |
BUILD_GTSAM_BINDINGS |
cpp/gtsam_bindings (used by c4_pose + c5_state) | ON | ON | OFF |
BUILD_FAISS_INDEX |
c6_tile_cache FaissDescriptorIndex (faiss-cpu wheel; runtime gate at runtime_root.storage_factory — no native target) |
ON | ON | ON |
BUILD_VIDEO_FILE_FRAME_SOURCE |
frame_source/VideoFileFrameSource (AZ-265) |
ON (replay mode) | ON (replay mode) | OFF |
BUILD_TLOG_REPLAY_ADAPTER |
c8_fc_adapter/tlog_replay_adapter (AZ-265) |
ON (replay mode) | ON (replay mode) | OFF |
BUILD_REPLAY_SINK_JSONL |
c8_fc_adapter/replay_sink + c8_fc_adapter/noop_mavlink_transport (AZ-265) |
ON (replay mode) | ON (replay mode) | OFF |
BUILD_LIVE_CAMERA_FRAME_SOURCE |
frame_source/LiveCameraFrameSource (AZ-265 retrofit) |
ON | ON | 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). In airborne, all three replay-mode BUILD_* flags default ON so the same image serves both live and replay modes; an operator deployment that wishes to remove replay capability can flip them OFF at build time (the resulting binary will still run live mode normally).
Build-time exclusion is enforced by:
- CMake reading
cmake/build_options.cmakeper binary target. - Per-binary CI matrix entry in
.github/workflows/ci.yml(3 parallel build jobs: airborne, research, operator-orchestrator). ci/sbom_diff.pystep asserting each binary's SBOM contains exactly the expected component set (e.g., the airborne SBOM MUST NOT containc11_tile_managerorc12_operator_orchestrator; the operator-orchestrator SBOM MUST NOT containc1_vioor any replay strategy). Note: there is no per-replay SBOM diff under ADR-011 — replay runs from the airborne image, which is already SBOM-diffed.
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
- 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_orchestrator, c13_fdr). - Every shared / cross-cutting concern has a Shared section entry (_types, config, logging, fdr_client, frame_source, clock, replay_input, helpers/* × 8, runtime_root, cli/replay, healthcheck, blackbox_tests).
- Layering table covers every component; foundation at Layer 1.
- No component's
Imports fromlist points at a component in a higher layer (back-channel exception for C8 → C1/C5 documented as interface-at-producer pattern). - Paths follow Python
src/-layout convention with single top-level packagegps_denied_onboard/. - No two components own overlapping paths. Joint native ownership of
cpp/gtsam_bindings/resolved: c5_state is primary owner; c4_pose READ-ONLY. - Replay-mode additions (AZ-265 / ADR-011) covered: new
frame_source/+clock/+replay_input/cross-cuttings; new C8 strategies (tlog_replay_adapter,replay_sink,noop_mavlink_transport,serial_mavlink_transport); newcli/replay.pyconsole-script wrapper; replay-modeBUILD_*flags default ON in the airborne and research binaries (no separate replay-cli binary).
How the implement skill consumes this
The /implement skill's Step 4 (File Ownership) reads this file and, for each task in the batch:
- Resolve the task's Component field to a Per-Component Mapping entry.
- Set OWNED = the component's
Ownsglob. - Set READ-ONLY = the Public API files of every component listed in
Imports from, plus allshared/*Public API files. - 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.