mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 20:41:12 +00:00
[AZ-303] C6 storage interfaces: Protocols + DTOs + factories
Freezes the c6_tile_cache Public API per
_docs/02_document/contracts/c6_tile_cache/{tile_store,tile_metadata_store,
descriptor_index}.md v1.0.0:
- Three runtime_checkable Protocols (TileStore 4-method, TileMetadataStore
9-method, DescriptorIndex 5-method) in components/c6_tile_cache/interface.py.
- Frozen DTOs + enums (TileId, TileMetadata, TileMetadataPersistent,
TileQualityMetadata, Bbox, SectorBoundary, HnswParams, IndexMetadata,
TileSource, FreshnessLabel, VotingStatus, SectorClassification) in
components/c6_tile_cache/_types.py. Constructor-time validation rejects
out-of-range zoom_level / lat / lon and inverted Bbox.
- TilePixelHandle ABC for read-only mmap access (Invariant I-4).
- TileCacheError family (6 subtypes) + IndexBuildError (deliberately
outside the family) in components/c6_tile_cache/errors.py.
- C6TileCacheConfig per-component config block, registered on package
import; validates known runtime labels at construction time.
- Composition-root factories build_tile_store / build_tile_metadata_store /
build_descriptor_index in runtime_root/storage_factory.py, with lazy
concrete-impl imports gated by BUILD_FAISS_INDEX (AC-5 / Risk 2:
no module-level FAISS import when the flag is OFF).
- RuntimeNotAvailableError defined in runtime_root/errors.py to be shared
with AZ-297 (composition-time error, distinct from per-component
runtime errors).
51 conformance tests cover all 10 ACs + NFR-perf-factory (p99 build_*
under 50 ms across 1000 calls) + NFR-reliability-error-family. AC-9
introspects each contract file's Shape table and asserts method
parity against the runtime Protocol.
Retired the AZ-263 scaffolding SectorClassification (dataclass) and
TileQualityMetadata from _types/tile.py since their canonical home is
now c6_tile_cache._types; Tile and TileRecord remain in _types/tile.py
until c3_matcher (AZ-344) and c11_tile_manager (AZ-316/319) retire
their interface stubs.
Full unit-test sweep: 791 passed, 2 pre-existing environment skips.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
"""Composition-root errors shared across runtime factories.
|
||||
|
||||
These are raised at composition time (``build_*`` factory entry) and
|
||||
NOT during the running flight. Components own their per-runtime error
|
||||
families; this module owns the cross-component selection error.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class RuntimeNotAvailableError(RuntimeError):
|
||||
"""Raised when ``build_*`` is asked for a runtime whose compile-time
|
||||
``BUILD_*`` flag is OFF.
|
||||
|
||||
Used by:
|
||||
- ``runtime_root.storage_factory.build_descriptor_index`` (AZ-303 / E-C6,
|
||||
``BUILD_FAISS_INDEX`` gate)
|
||||
- ``runtime_root.inference_factory.build_inference_runtime`` (AZ-297 / E-C7,
|
||||
``BUILD_TENSORRT_RUNTIME`` / ``BUILD_ONNX_TRT_EP_RUNTIME`` /
|
||||
``BUILD_PYTORCH_FP16_RUNTIME`` gates)
|
||||
|
||||
The message MUST name the requested runtime label so the operator can
|
||||
correlate against ``.env``'s ``BUILD_*`` matrix without guessing.
|
||||
"""
|
||||
@@ -0,0 +1,150 @@
|
||||
"""C6 storage composition-root factories (AZ-303).
|
||||
|
||||
Three factories selected by ``config.components['c6_tile_cache']``:
|
||||
|
||||
- :func:`build_tile_store` — JPEG body store.
|
||||
- :func:`build_tile_metadata_store` — Postgres metadata + LRU + voting.
|
||||
- :func:`build_descriptor_index` — FAISS HNSW vector index.
|
||||
|
||||
The factories lazily import concrete impl modules. A ``BUILD_*`` flag
|
||||
that is OFF MUST NOT cause the concrete module to be imported — that
|
||||
is the AC-5 / Risk-2 invariant. We honour it by reading the flag
|
||||
from :mod:`os.environ` BEFORE the ``import`` statement.
|
||||
|
||||
The concrete impls (``postgres_filesystem_store``, ``faiss_descriptor_index``)
|
||||
are produced by separate downstream tasks (AZ-305 / AZ-306). Until they
|
||||
land, requesting any runtime raises :class:`RuntimeNotAvailableError`
|
||||
with a message that names the missing impl module — the failure is
|
||||
explicit, not silent.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from gps_denied_onboard.runtime_root.errors import RuntimeNotAvailableError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gps_denied_onboard.components.c6_tile_cache import (
|
||||
C6TileCacheConfig,
|
||||
DescriptorIndex,
|
||||
TileMetadataStore,
|
||||
TileStore,
|
||||
)
|
||||
from gps_denied_onboard.config.schema import Config
|
||||
|
||||
__all__ = [
|
||||
"build_descriptor_index",
|
||||
"build_tile_metadata_store",
|
||||
"build_tile_store",
|
||||
]
|
||||
|
||||
|
||||
def _is_build_flag_on(flag_name: str) -> bool:
|
||||
"""Read a compile-time ``BUILD_*`` flag from the environment.
|
||||
|
||||
``ON`` / ``1`` / ``true`` / ``yes`` (case-insensitive) → ``True``.
|
||||
Anything else (including unset) → ``False``. Defaults to OFF to
|
||||
keep test envs honest about which runtimes they advertise.
|
||||
"""
|
||||
raw = os.environ.get(flag_name, "")
|
||||
return raw.strip().lower() in {"on", "1", "true", "yes"}
|
||||
|
||||
|
||||
def _c6_config(config: "Config") -> "C6TileCacheConfig":
|
||||
"""Pull the registered C6 config block.
|
||||
|
||||
``c6_tile_cache.__init__`` registers it on import; if the package
|
||||
has not been imported, :class:`KeyError` here is the right
|
||||
failure mode — silent fallback would mask a missing import.
|
||||
"""
|
||||
return config.components["c6_tile_cache"]
|
||||
|
||||
|
||||
def build_tile_store(config: "Config") -> "TileStore":
|
||||
"""Construct the :class:`TileStore` impl selected by config.
|
||||
|
||||
Today only ``"postgres_filesystem"`` is wired; the runtime label
|
||||
is validated at config-load time so unknown labels never reach
|
||||
here. Concrete impl is produced by AZ-305.
|
||||
"""
|
||||
block = _c6_config(config)
|
||||
runtime = block.store_runtime
|
||||
if runtime == "postgres_filesystem":
|
||||
try:
|
||||
from gps_denied_onboard.components.c6_tile_cache.postgres_filesystem_store import ( # noqa: PLC0415
|
||||
PostgresFilesystemStore,
|
||||
)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise RuntimeNotAvailableError(
|
||||
f"TileStore runtime {runtime!r} is configured but its "
|
||||
"concrete impl module "
|
||||
"'c6_tile_cache.postgres_filesystem_store' has not been "
|
||||
"built into this binary yet (AZ-305 pending)."
|
||||
) from exc
|
||||
return PostgresFilesystemStore(config)
|
||||
raise RuntimeNotAvailableError(
|
||||
f"TileStore runtime {runtime!r} is not buildable in this binary."
|
||||
)
|
||||
|
||||
|
||||
def build_tile_metadata_store(config: "Config") -> "TileMetadataStore":
|
||||
"""Construct the :class:`TileMetadataStore` impl selected by config.
|
||||
|
||||
Today the same ``PostgresFilesystemStore`` class implements both
|
||||
:class:`TileStore` and :class:`TileMetadataStore` (single-row
|
||||
transactional store across the two surfaces). The factories
|
||||
return the same instance shape but stay separate so a future
|
||||
SQLite Tier-0 variant can swap one without the other.
|
||||
"""
|
||||
block = _c6_config(config)
|
||||
runtime = block.metadata_runtime
|
||||
if runtime == "postgres_filesystem":
|
||||
try:
|
||||
from gps_denied_onboard.components.c6_tile_cache.postgres_filesystem_store import ( # noqa: PLC0415
|
||||
PostgresFilesystemStore,
|
||||
)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise RuntimeNotAvailableError(
|
||||
f"TileMetadataStore runtime {runtime!r} is configured "
|
||||
"but its concrete impl module "
|
||||
"'c6_tile_cache.postgres_filesystem_store' has not been "
|
||||
"built into this binary yet (AZ-305 pending)."
|
||||
) from exc
|
||||
return PostgresFilesystemStore(config)
|
||||
raise RuntimeNotAvailableError(
|
||||
f"TileMetadataStore runtime {runtime!r} is not buildable in this binary."
|
||||
)
|
||||
|
||||
|
||||
def build_descriptor_index(config: "Config") -> "DescriptorIndex":
|
||||
"""Construct the :class:`DescriptorIndex` impl selected by config.
|
||||
|
||||
Gated by ``BUILD_FAISS_INDEX``: if the flag is OFF, the concrete
|
||||
module is NOT imported (sys.modules invariant; AC-5) and
|
||||
:class:`RuntimeNotAvailableError` is raised at composition time.
|
||||
"""
|
||||
block = _c6_config(config)
|
||||
runtime = block.descriptor_index_runtime
|
||||
if runtime == "faiss_hnsw":
|
||||
if not _is_build_flag_on("BUILD_FAISS_INDEX"):
|
||||
raise RuntimeNotAvailableError(
|
||||
f"DescriptorIndex runtime {runtime!r} requires "
|
||||
"BUILD_FAISS_INDEX=ON in this binary; the flag is OFF."
|
||||
)
|
||||
try:
|
||||
from gps_denied_onboard.components.c6_tile_cache.faiss_descriptor_index import ( # noqa: PLC0415
|
||||
FaissDescriptorIndex,
|
||||
)
|
||||
except ModuleNotFoundError as exc:
|
||||
raise RuntimeNotAvailableError(
|
||||
f"DescriptorIndex runtime {runtime!r} is configured but "
|
||||
"its concrete impl module "
|
||||
"'c6_tile_cache.faiss_descriptor_index' has not been "
|
||||
"built into this binary yet (AZ-306 pending)."
|
||||
) from exc
|
||||
return FaissDescriptorIndex(config)
|
||||
raise RuntimeNotAvailableError(
|
||||
f"DescriptorIndex runtime {runtime!r} is not buildable in this binary."
|
||||
)
|
||||
Reference in New Issue
Block a user