mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 18:41:13 +00:00
[AZ-328] C12 BuildCacheOrchestrator + remote C10 invoker (Batch 43)
Implements F1 pre-flight cache build orchestrator on the operator workstation. Composes C11 TileDownloader (AZ-316), C12 CompanionBringup (AZ-327), C12 FlightsApiClient (AZ-489), and the new RemoteCacheProvisionerInvoker into one sequenced flow guarded by a filelock-backed workstation-side lockfile. Architectural decisions: - Phase-0 flight-resolve runs BEFORE the lockfile (ADR-010): a flight that cannot be resolved is an operator-input error, not a contended- resource error. Enforced by AC-11 + AC-14. - Consumer-side cuts (AZ-507) for C11 + C10 types: local Protocols / mirror DTOs in tile_downloader_cut.py and _types.py; external errors matched by name-based whitelisting so unknown exceptions still propagate per AC-6. Cross-component type translation lives at the composition root (c12_factory). - Failure surfacing: recognised operational failures (download error, companion not ready, build error, flight-resolve error) return as CacheBuildReport(outcome=failure, failure_phase=...). Only lockfile contention raises (BuildLockHeldError) since no phase ever ran. - Workstation-side filelock library (project pin); no custom primitive. - Remote C10 stdout streamed line-by-line as DEBUG with api_key / auth_token redacted before logging (defence-in-depth). - CLI is now a thin adapter; all workflow logic lives in build_cache.py. operator-tool build-cache exit codes map per CacheBuildReport.failure_phase + failure_exception_type. Tests: 116 c12 unit tests pass (29 new for AZ-328 covering 15/15 ACs + NFR-perf-overhead microbench; 7 new for remote_c10_invoker; 3 new for file_lock; test_cli_build_cache rewritten for new orchestrator interface). Full repo suite: 1522 passed, 80 skipped. Also: replays Batch 42's ruff format leftover for c12 flights_api + test_az489 files (formatter ran over the c12 directory after new files were added). Pure whitespace; no behaviour change. Full report: _docs/03_implementation/batch_43_cycle1_report.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,13 +6,19 @@
|
||||
classification map.
|
||||
* :func:`build_companion_bringup` — AZ-327 SSH-based pre-flight
|
||||
verification of the companion's four artifacts.
|
||||
* :func:`build_build_cache_orchestrator` — AZ-328 F1 cache-build
|
||||
orchestrator. Wires the ``filelock`` factory + the remote C10 invoker
|
||||
+ the c11 ``TileDownloader`` adapter on top of the existing AZ-326 /
|
||||
AZ-327 / AZ-489 services. The AZ-507 cross-component cut means we
|
||||
translate c11's real ``DownloadRequest`` / ``DownloadBatchReport`` to
|
||||
the local ``DownloadRequestCut`` / ``DownloadBatchReportCut`` here.
|
||||
* :func:`build_operator_tool` — aggregator that returns the
|
||||
:class:`OperatorToolServices` dataclass the AZ-326 CLI consumes.
|
||||
|
||||
Each ``build_*`` function is intentionally tiny — there is one
|
||||
production strategy per service today and the CLI wiring just plugs
|
||||
the concrete instance into the same composition root method. Sibling
|
||||
tasks AZ-328 / AZ-329 / AZ-330 will each add a single field to
|
||||
tasks AZ-329 / AZ-330 will each add a single field to
|
||||
:class:`OperatorToolServices` without renaming or moving the
|
||||
dataclass.
|
||||
"""
|
||||
@@ -23,9 +29,16 @@ import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from gps_denied_onboard.clock import Clock
|
||||
from gps_denied_onboard.components.c12_operator_tooling.build_cache import (
|
||||
BuildCacheOrchestrator,
|
||||
)
|
||||
from gps_denied_onboard.components.c12_operator_tooling.companion_bringup import (
|
||||
CompanionBringup,
|
||||
)
|
||||
from gps_denied_onboard.components.c12_operator_tooling.file_lock import (
|
||||
FilelockFileLockFactory,
|
||||
)
|
||||
from gps_denied_onboard.components.c12_operator_tooling.flights_api import (
|
||||
FlightsApiClient,
|
||||
HttpxFlightsApiClient,
|
||||
@@ -33,12 +46,18 @@ from gps_denied_onboard.components.c12_operator_tooling.flights_api import (
|
||||
from gps_denied_onboard.components.c12_operator_tooling.paramiko_ssh_session import (
|
||||
ParamikoSshSessionFactory,
|
||||
)
|
||||
from gps_denied_onboard.components.c12_operator_tooling.remote_c10_invoker import (
|
||||
RemoteCacheProvisionerInvoker,
|
||||
)
|
||||
from gps_denied_onboard.components.c12_operator_tooling.remote_sidecar_verifier import (
|
||||
RemoteSidecarVerifier,
|
||||
)
|
||||
from gps_denied_onboard.components.c12_operator_tooling.sector_classification_store import (
|
||||
SectorClassificationStore,
|
||||
)
|
||||
from gps_denied_onboard.components.c12_operator_tooling.tile_downloader_cut import (
|
||||
TileDownloaderCut,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from gps_denied_onboard.components.c12_operator_tooling.config import (
|
||||
@@ -48,6 +67,7 @@ if TYPE_CHECKING:
|
||||
|
||||
__all__ = [
|
||||
"OperatorToolServices",
|
||||
"build_build_cache_orchestrator",
|
||||
"build_companion_bringup",
|
||||
"build_flights_api_client",
|
||||
"build_operator_tool",
|
||||
@@ -57,6 +77,8 @@ __all__ = [
|
||||
|
||||
_C12_LOGGER_NAME = "c12_operator_tooling"
|
||||
_COMPANION_LOGGER_NAME = "c12_operator_tooling.companion_bringup"
|
||||
_BUILD_CACHE_LOGGER_NAME = "c12_operator_tooling.build_cache"
|
||||
_REMOTE_C10_LOGGER_NAME = "c12_operator_tooling.remote_c10_invoker"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -65,15 +87,22 @@ class OperatorToolServices:
|
||||
|
||||
AZ-326 introduced the dataclass and now owns three services
|
||||
(``flights_api_client``, ``sector_classification_store``,
|
||||
``companion_bringup``). Sibling tasks AZ-328 (orchestrator),
|
||||
AZ-329 (post-landing upload), and AZ-330 (operator reloc service)
|
||||
extend this dataclass in-place by appending their own service
|
||||
field — they MUST NOT rename, move, or split it.
|
||||
``companion_bringup``). AZ-328 added ``build_cache_orchestrator``.
|
||||
Sibling tasks AZ-329 (post-landing upload) and AZ-330 (operator
|
||||
reloc service) extend this dataclass in-place by appending their
|
||||
own service field — they MUST NOT rename, move, or split it.
|
||||
|
||||
``build_cache_orchestrator`` is ``None`` when the AZ-328 wiring is
|
||||
not requested (e.g. unit tests for AZ-326 / AZ-327 that don't go
|
||||
through the full build path); the CLI's ``build-cache`` subcommand
|
||||
short-circuits with an EXIT_OK + log when the field is missing /
|
||||
None so the rest of the CLI keeps working.
|
||||
"""
|
||||
|
||||
flights_api_client: FlightsApiClient
|
||||
sector_classification_store: SectorClassificationStore
|
||||
companion_bringup: CompanionBringup
|
||||
build_cache_orchestrator: BuildCacheOrchestrator | None = None
|
||||
|
||||
|
||||
def build_flights_api_client(config: Config) -> FlightsApiClient:
|
||||
@@ -130,13 +159,86 @@ def build_companion_bringup(
|
||||
)
|
||||
|
||||
|
||||
def build_operator_tool(config: Config) -> OperatorToolServices:
|
||||
"""Aggregate the three AZ-326 / AZ-327 / AZ-489 service handles."""
|
||||
return OperatorToolServices(
|
||||
def build_build_cache_orchestrator(
|
||||
config: Config,
|
||||
*,
|
||||
services: OperatorToolServices,
|
||||
tile_downloader: TileDownloaderCut,
|
||||
clock: Clock,
|
||||
logger: logging.Logger | None = None,
|
||||
) -> BuildCacheOrchestrator:
|
||||
"""Build the AZ-328 :class:`BuildCacheOrchestrator` from config + sibling services.
|
||||
|
||||
Caller (production runtime root) is responsible for translating the
|
||||
real c11 ``TileDownloader`` to a :class:`TileDownloaderCut` adapter
|
||||
here — ``c12_operator_tooling`` cannot import c11 directly per
|
||||
AZ-507. The lockfile factory + remote-C10 invoker + SSH factory are
|
||||
constructed in-place; the SSH factory MUST be the same instance as
|
||||
the one wired into ``services.companion_bringup`` (single
|
||||
composition-root construction per AZ-328 Constraints).
|
||||
"""
|
||||
c12_config = _resolve_c12_config(config)
|
||||
companion = c12_config.companion
|
||||
if not str(companion.ssh_keyfile):
|
||||
from gps_denied_onboard.config.schema import ConfigError
|
||||
|
||||
raise ConfigError(
|
||||
"C12CompanionConfig.ssh_keyfile is empty; AZ-328 build_cache_orchestrator "
|
||||
"requires a real SSH private key path"
|
||||
)
|
||||
|
||||
ssh_factory = ParamikoSshSessionFactory(
|
||||
ssh_user=companion.ssh_user,
|
||||
ssh_keyfile=companion.ssh_keyfile,
|
||||
host_key_policy=companion.host_key_policy,
|
||||
)
|
||||
invoker_logger = logger or logging.getLogger(_REMOTE_C10_LOGGER_NAME)
|
||||
orchestrator_logger = logger or logging.getLogger(_BUILD_CACHE_LOGGER_NAME)
|
||||
return BuildCacheOrchestrator(
|
||||
flights_api_client=services.flights_api_client,
|
||||
tile_downloader=tile_downloader,
|
||||
companion_bringup=services.companion_bringup,
|
||||
remote_c10_invoker=RemoteCacheProvisionerInvoker(logger=invoker_logger),
|
||||
ssh_factory=ssh_factory,
|
||||
lock_factory=FilelockFileLockFactory(),
|
||||
logger=orchestrator_logger,
|
||||
clock=clock,
|
||||
config=c12_config.build_cache,
|
||||
)
|
||||
|
||||
|
||||
def build_operator_tool(
|
||||
config: Config,
|
||||
*,
|
||||
tile_downloader: TileDownloaderCut | None = None,
|
||||
clock: Clock | None = None,
|
||||
) -> OperatorToolServices:
|
||||
"""Aggregate the AZ-326 / AZ-327 / AZ-328 / AZ-489 service handles.
|
||||
|
||||
``tile_downloader`` and ``clock`` are optional — without them, the
|
||||
``build_cache_orchestrator`` field is left as ``None`` and the CLI's
|
||||
``build-cache`` subcommand short-circuits gracefully. Production
|
||||
wiring (the suite-level runtime root) supplies real instances.
|
||||
"""
|
||||
base = OperatorToolServices(
|
||||
flights_api_client=build_flights_api_client(config),
|
||||
sector_classification_store=build_sector_classification_store(config),
|
||||
companion_bringup=build_companion_bringup(config),
|
||||
)
|
||||
if tile_downloader is None or clock is None:
|
||||
return base
|
||||
orchestrator = build_build_cache_orchestrator(
|
||||
config,
|
||||
services=base,
|
||||
tile_downloader=tile_downloader,
|
||||
clock=clock,
|
||||
)
|
||||
return OperatorToolServices(
|
||||
flights_api_client=base.flights_api_client,
|
||||
sector_classification_store=base.sector_classification_store,
|
||||
companion_bringup=base.companion_bringup,
|
||||
build_cache_orchestrator=orchestrator,
|
||||
)
|
||||
|
||||
|
||||
def _resolve_c12_config(config: Config) -> C12Config:
|
||||
|
||||
Reference in New Issue
Block a user