[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:
Oleksandr Bezdieniezhnykh
2026-05-12 04:21:44 +03:00
parent 48281db9e9
commit f925af9de3
12 changed files with 1539 additions and 63 deletions
@@ -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."
)