mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 13:21:13 +00:00
[AZ-507] [AZ-323] [AZ-324] C10 Manifest build + verify + AZ-270 hygiene
AZ-507: codify cross-component import rule. Added _types/inference_errors.py shim re-exporting EngineBuildError + CalibrationCacheError from c7_inference; narrowed C10 EngineCompiler's except Exception to the two typed errors so unknown exceptions propagate (AC-3). Rewrote module-layout.md "Imports from" sections for 9 components + added Rule 9; appended an architecture.md ADR-009 note explaining why components must go through _types/*. AZ-323: ManifestBuilder + Ed25519ManifestSigner. Canonical JSON via orjson OPT_SORT_KEYS+OPT_INDENT_2, atomic-write Manifest.json + sha sidecar + .sig via AZ-280, operator-key fingerprint allowlist gate (C10-ST-01), ADR-010 takeoff_origin + flight_id baked into Manifest AND manifest_hash so re-planned routes change the cache identity (AC-15/AC-16). 20 unit tests cover all 16 ACs. AZ-324: ManifestVerifierImpl. Fail-closed Steps A-D: Manifest.json sidecar self-hash, Ed25519 trust-key set, schema parse with absolute/.. path rejection + takeoff_origin in-bbox check, stream SHA-256 per artifact with multi-failure accumulation. Operator mode re-derives tiles_coverage_sha256 from C6; airborne mode trusts the signed aggregate. 19 unit tests cover all 17 ACs. Composition root: c10_factory.build_manifest_builder + build_manifest_verifier + c6_tile_metadata_store_to_tiles_query adapter (the one place that legitimately imports both C6 and C10 without violating the AZ-270 lint). Dependency: pinned cryptography>=43.0,<46.0 in pyproject.toml. Tests: 1300 passed, 80 skipped (env-only), ruff clean for all AZ-323/324 files. AZ-306 (FAISS) intentionally deferred to batch 35 — needs C++ pybind11 toolchain not present in this environment. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,27 +19,38 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from gps_denied_onboard.components.c10_provisioning import (
|
||||
BackboneSpec,
|
||||
Ed25519ManifestSigner,
|
||||
EngineCompiler,
|
||||
ManifestBuilder,
|
||||
ManifestVerifierImpl,
|
||||
TileHashRecord,
|
||||
TilesByBboxQuery,
|
||||
)
|
||||
from gps_denied_onboard.components.c10_provisioning.config import (
|
||||
BackboneConfig,
|
||||
C10ProvisioningConfig,
|
||||
)
|
||||
from gps_denied_onboard.helpers.sha256_sidecar import Sha256Sidecar
|
||||
from gps_denied_onboard.logging import get_logger
|
||||
from gps_denied_onboard.runtime_root.inference_factory import (
|
||||
build_inference_runtime,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gps_denied_onboard.clock import Clock
|
||||
from gps_denied_onboard.components.c6_tile_cache import TileMetadataStore
|
||||
from gps_denied_onboard.config.schema import Config
|
||||
|
||||
__all__ = [
|
||||
"build_backbone_specs",
|
||||
"build_engine_compiler",
|
||||
"build_manifest_builder",
|
||||
"build_manifest_verifier",
|
||||
"c6_tile_metadata_store_to_tiles_query",
|
||||
]
|
||||
|
||||
|
||||
def build_engine_compiler(config: "Config") -> EngineCompiler:
|
||||
def build_engine_compiler(config: Config) -> EngineCompiler:
|
||||
"""Construct a wired :class:`EngineCompiler` from ``config``.
|
||||
|
||||
The factory:
|
||||
@@ -61,7 +72,7 @@ def build_engine_compiler(config: "Config") -> EngineCompiler:
|
||||
return EngineCompiler(inference_runtime=runtime, logger=logger)
|
||||
|
||||
|
||||
def build_backbone_specs(config: "Config") -> tuple[BackboneSpec, ...]:
|
||||
def build_backbone_specs(config: Config) -> tuple[BackboneSpec, ...]:
|
||||
"""Materialise :class:`BackboneSpec` tuple from
|
||||
``config.components['c10_provisioning'].backbones``.
|
||||
|
||||
@@ -83,3 +94,128 @@ def _backbone_spec_from_config(
|
||||
expected_input_shape=tuple(backbone.expected_input_shape),
|
||||
input_name=backbone.input_name,
|
||||
)
|
||||
|
||||
|
||||
def build_manifest_builder(
|
||||
config: Config,
|
||||
*,
|
||||
tile_metadata_store: TileMetadataStore,
|
||||
clock: Clock,
|
||||
) -> ManifestBuilder:
|
||||
"""Construct a wired :class:`ManifestBuilder` (AZ-323).
|
||||
|
||||
The ``tile_metadata_store`` argument is the AZ-303 C6 store; this
|
||||
factory wraps it in the consumer-side
|
||||
:class:`TilesByBboxQuery` adapter so the C10 module never imports
|
||||
``components.c6_tile_cache`` directly (AZ-270 + AZ-507 boundary).
|
||||
|
||||
``clock`` is supplied explicitly rather than re-resolved through
|
||||
a clock factory because the composition root selects the clock
|
||||
strategy (WallClock for live, TlogDerivedClock for replay) per
|
||||
AZ-398 and threads the SAME instance through every consumer.
|
||||
"""
|
||||
|
||||
block: C10ProvisioningConfig = config.components["c10_provisioning"]
|
||||
sidecar = Sha256Sidecar()
|
||||
signer = Ed25519ManifestSigner()
|
||||
logger = get_logger("c10_provisioning.manifest")
|
||||
tiles_query = c6_tile_metadata_store_to_tiles_query(tile_metadata_store)
|
||||
return ManifestBuilder(
|
||||
sidecar=sidecar,
|
||||
signer=signer,
|
||||
tile_metadata_store=tiles_query,
|
||||
logger=logger,
|
||||
clock=clock,
|
||||
config=block.manifest,
|
||||
)
|
||||
|
||||
|
||||
def build_manifest_verifier(
|
||||
config: Config,
|
||||
*,
|
||||
clock: Clock,
|
||||
tile_metadata_store: TileMetadataStore | None = None,
|
||||
with_tile_store: bool = False,
|
||||
) -> ManifestVerifierImpl:
|
||||
"""Construct a wired :class:`ManifestVerifierImpl` (AZ-324).
|
||||
|
||||
``with_tile_store=True`` (operator C12 mode) requires
|
||||
``tile_metadata_store`` to be supplied — the verifier re-derives
|
||||
``tiles_coverage_sha256`` from C6 and reports drift.
|
||||
``with_tile_store=False`` (airborne C5 mode) trusts the recorded
|
||||
aggregate after the Ed25519 signature passes (MV-INV-5); the
|
||||
``tile_metadata_store`` argument is ignored.
|
||||
"""
|
||||
|
||||
sidecar = Sha256Sidecar()
|
||||
logger = get_logger("c10_provisioning.verify")
|
||||
# AZ-324 silently accepting a tile_metadata_store with
|
||||
# `with_tile_store=False` would mask a composition-root mistake
|
||||
# (operator mode wired in an airborne binary by accident); we keep
|
||||
# the airborne path explicit by ignoring the argument here.
|
||||
if with_tile_store:
|
||||
if tile_metadata_store is None:
|
||||
raise ValueError(
|
||||
"build_manifest_verifier(with_tile_store=True) requires "
|
||||
"tile_metadata_store; supply None or set with_tile_store=False"
|
||||
)
|
||||
tiles_query: TilesByBboxQuery | None = c6_tile_metadata_store_to_tiles_query(
|
||||
tile_metadata_store
|
||||
)
|
||||
else:
|
||||
tiles_query = None
|
||||
return ManifestVerifierImpl(
|
||||
sidecar=sidecar,
|
||||
logger=logger,
|
||||
clock=clock,
|
||||
tile_metadata_store=tiles_query,
|
||||
)
|
||||
|
||||
|
||||
def c6_tile_metadata_store_to_tiles_query(
|
||||
tile_metadata_store: TileMetadataStore,
|
||||
) -> TilesByBboxQuery:
|
||||
"""Adapt the C6 ``TileMetadataStore`` to the C10 ``TilesByBboxQuery`` cut.
|
||||
|
||||
Lives in the composition root because it is the only place that
|
||||
may import both C6 and C10 (the AZ-270 lint allows
|
||||
``runtime_root``). C6 returns ``TileMetadata`` rows; AZ-323 needs
|
||||
a ``TileHashRecord`` with ``(zoom, lat, lon, source, sha256_hex)``
|
||||
and nothing else.
|
||||
"""
|
||||
|
||||
from gps_denied_onboard.components.c6_tile_cache import (
|
||||
SectorClassification as C6SectorClassification,
|
||||
)
|
||||
|
||||
class _C6TilesAdapter:
|
||||
def __init__(self, store: TileMetadataStore) -> None:
|
||||
self._store = store
|
||||
|
||||
def query_by_bbox(
|
||||
self,
|
||||
*,
|
||||
bbox,
|
||||
zoom_levels,
|
||||
sector_class,
|
||||
):
|
||||
c6_sector = C6SectorClassification(sector_class)
|
||||
rows = self._store.query_by_bbox(
|
||||
bbox=bbox,
|
||||
zoom_levels=zoom_levels,
|
||||
sector_class=c6_sector,
|
||||
)
|
||||
return tuple(
|
||||
TileHashRecord(
|
||||
zoom=row.tile_id.zoom_level,
|
||||
lat=row.tile_id.lat,
|
||||
lon=row.tile_id.lon,
|
||||
source=row.source.value
|
||||
if hasattr(row.source, "value")
|
||||
else str(row.source),
|
||||
sha256_hex=row.content_sha256_hex,
|
||||
)
|
||||
for row in rows
|
||||
)
|
||||
|
||||
return _C6TilesAdapter(tile_metadata_store)
|
||||
|
||||
Reference in New Issue
Block a user