Architecture, contracts, and task amendments for the flight-route-driven preflight + cold-start origin feature (ADR-010). No source code touched in this commit; the implementation commits for AZ-489 / AZ-490 / AZ-419 land separately. * architecture.md: ADR-010, new Principle #14, amended Principle #11, external systems gain flights service + Mission Planner UI, data model gains Flight / Waypoint / TakeoffOrigin. * system-flows.md: F1 gains phase 0 (Flight resolve), F2 gains cold-start ladder, F7 gains mid-flight bounded-delta GPS gate. * glossary.md: Flight, Flights API, Mid-flight bounded-delta GPS gate, Mission Planner UI, Takeoff origin, Waypoint. * C10: description + cache_provisioner + manifest_verifier bumped to v1.1 carrying takeoff_origin + flight_id in the manifest hash. * C12: description updated + new flights_api_client.md contract v1.0. * C5: description + state_estimator_protocol bumped to v1.1 with set_takeoff_origin + 3-clause spoof-promotion gate. * AZ-323/324/325/326/328/419 amended in place. AZ-490 spec created (C5 set_takeoff_origin entrypoint). * Dependencies table: 142 tasks / 478 pts / 15 forward edges (2 new tasks, 2 backward deps, 2 forward deps from AZ-419). * Leftovers cleared: 2026-05-11 Jira transition entries for AZ-355 and AZ-386 are deleted (Jira reconnected; both already transitioned in their respective implementation commits). Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
Contract: CacheProvisioner (C10)
Type: Python Protocol (@runtime_checkable) — local in-process API.
Producer task: AZ-325_c10_cache_provisioner
Consumers:
- C12 Operator Tooling — orchestrates the F1 build sequence
C11 TileDownloader → CacheProvisioner.build_artifactsand surfaces theBuildReportto the operator (E-C12 / AZ-253). - C13 FDR — out of scope for build (F1 is offline / pre-flight); F2's verify is owned by the
ManifestVerifiercontract.
Purpose
CacheProvisioner is the public top-level surface for the C10 build phase. It composes EngineCompiler (AZ-321), DescriptorBatcher (AZ-322), and ManifestBuilder (AZ-323) into a single idempotent operation that the operator runs after C11 TileDownloader has populated C6. The Provisioner enforces D-C10-1 idempotence (skip rebuild when the build-identity hash matches the prior Manifest), D-C10-3 ManifestCoverageError (every shipped artifact under cache_root MUST be in the Manifest — no smuggled files), and D-C10-6 hardware-tied engine reuse (delegated to AZ-321). It does NOT touch satellite-provider (per epic § Architecture notes); tile I/O is C11's responsibility.
Public Surface
from pathlib import Path
from typing import Protocol, runtime_checkable
@runtime_checkable
class CacheProvisioner(Protocol):
"""Public top-level orchestrator for C10 cache build.
Idempotent: if the prior Manifest's build-identity hash matches the
request's, returns `outcome=IDEMPOTENT_NO_OP` without rebuilding.
Otherwise composes engine compile + descriptor population + Manifest
write + coverage check.
"""
def build_cache_artifacts(self, request: BuildRequest) -> BuildReport: ...
def compile_engines_for_corpus(self, request: EngineCompileRequest) -> tuple[EngineCacheEntry, ...]: ...
DTOs
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
class SectorClassification(Enum):
ACTIVE_CONFLICT = "active_conflict"
STABLE_REAR = "stable_rear"
class BuildOutcome(Enum):
SUCCESS = "success"
FAILURE = "failure"
IDEMPOTENT_NO_OP = "idempotent_no_op"
@dataclass(frozen=True)
class Bbox:
lat_min: float
lon_min: float
lat_max: float
lon_max: float
@dataclass(frozen=True)
class BuildRequest:
bbox: Bbox
zoom_levels: tuple[int, ...]
sector_class: SectorClassification
calibration_path: Path
cache_root: Path
key_path: Path # operator signing key per C10-ST-01
takeoff_origin: LatLonAlt | None = None # ADR-010 + AZ-489: planned takeoff position from Flight.waypoints[0]; baked into Manifest body + build-identity hash
flight_id: UUID | None = None # ADR-010: pass-through provenance of which Flight produced the build
@dataclass(frozen=True)
class BuildReport:
outcome: BuildOutcome
engines_built: int
engines_reused: int
descriptors_generated: int
manifest_hash: str | None
manifest_path: Path | None
failure_reason: str | None
elapsed_s: float
(EngineCompileRequest and EngineCacheEntry are AZ-321's; re-exported for convenience.)
Exceptions
| Exception | When raised | Caller action |
|---|---|---|
BuildLockHeldError |
Another build_cache_artifacts invocation holds the cache_root lockfile (per description.md § 7 race-condition mitigation). |
Operator waits / kills the other process; not retried automatically. |
ManifestCoverageError |
After build, an orphan file exists under cache_root that is not listed in the Manifest. |
Build is rolled back to prior-good Manifest (if present); operator inspects the orphan. |
EngineBuildError, CalibrationCacheError |
Propagated from AZ-321 / AZ-298. | Operator triages GPU / calibration. |
DescriptorBatchError |
Propagated from AZ-322. | Operator triages GPU OOM / model. |
ManifestWriteError |
Propagated from AZ-323 (key fingerprint mismatch in operator mode, key load failure, atomic-write failure). | Operator inspects key / disk. |
BuildOutcome.FAILURE is reserved for soft failures captured in BuildReport (missing tiles in C6, coverage warning when configured non-strict). Hard errors raise.
Invariants
| ID | Invariant | Why |
|---|---|---|
| CP-INV-1 | Idempotence: if Manifest.json exists at cache_root AND its manifest_hash equals the build-identity hash for the new request → outcome=IDEMPOTENT_NO_OP, ZERO new compiles, ZERO new embeds, ZERO new Manifest writes; the existing Manifest is left untouched. |
D-C10-1; warm re-run ≤ 1 min envelope (C10-PT-01). |
| CP-INV-2 | A failed build_cache_artifacts does NOT leave the cache in a worse state than at the start: new engines may exist (cache hits) but the Manifest is either the previous-good one OR rolled back; the FAISS index is either the previous-good one OR atomically replaced. |
Operators can retry safely. |
| CP-INV-3 | After a SUCCESS outcome, ManifestCoverageError has been verified absent: every file under cache_root (recursively, excluding the Manifest itself + sidecars + sig) is listed in the Manifest's artifacts. |
D-C10-3 — no smuggled artifacts in the takeoff cache. |
| CP-INV-4 | Concurrent build_cache_artifacts calls on the same cache_root are mutually exclusive via a filesystem lockfile at cache_root/.c10.lock. |
description.md § 7 race-condition mitigation. |
| CP-INV-5 | cache_root must already exist; build_cache_artifacts does NOT create the directory tree (operator workflow places it). |
Avoids accidental builds in unintended paths. |
| CP-INV-6 | No network calls (no satellite-provider, no Postgres TLS to a remote DB beyond the local instance, no metric push). |
Epic § Architecture notes: C10 is workstation-local. |
| CP-INV-7 | The operator key file at request.key_path is opened exactly once (via AZ-323's signer) and zeroized when out of scope; this contract does NOT cache the key in memory across calls. |
Operator key hygiene. |
| CP-INV-8 | takeoff_origin is treated as one more identity field by the build-identity hash. If the prior Manifest carries takeoff_origin=A and a new request carries takeoff_origin=B != A (with all other fields equal), the build is NOT idempotent and proceeds; the verifier (AZ-324) at boot then refuses any cache whose manifest origin disagrees with the manifest-on-disk's origin. |
ADR-010: cache identity must include the origin or boot-time consistency breaks. |
| CP-INV-9 | When takeoff_origin is None, the prior cold-start ladder (FC-EKF-GPS via AZ-419) remains the only origin source. C10 does not invent a default origin from the bbox; that decision is for C12. |
Single-responsibility — C10 records, C12 decides. |
Non-Goals
- Tile fetch from
satellite-provider— owned by E-C11 / C11 TileDownloader. - Engine deserialization at takeoff — owned by E-C7 / AZ-298 + C5 takeoff arming.
- Manifest verification — owned by AZ-324's
ManifestVerifier(separate contract). - Multi-cache management (rotating between sector caches) — operator runs
build_cache_artifactsper cache_root. - Garbage collection of stale engines — explicit operator action; not part of the build flow.
- Resumable build (mid-build process kill → resume from last batch) — out of scope; restart from scratch.
Versioning
- v1.0.0 — initial Protocol surface (this document).
- Breaking changes: changing
BuildRequestshape, removing aBuildOutcome, adding a required field — bump major. - Additive changes: new optional kwarg, new
BuildOutcomevalue, new field onBuildReport— bump minor. Consumers MUST handle unknown outcomes gracefully (treat as FAILURE). - Patch: clarifications, doc edits.
| Version | Date | Notes | Author |
|---|---|---|---|
| 1.0.0 | 2026-05-10 | Initial contract — produced by AZ-325 (E-C10 decomposition) | autodev |
| 1.1.0 | 2026-05-11 | Additive: BuildRequest.takeoff_origin + BuildRequest.flight_id (defaults None for back-compat); CP-INV-8 + CP-INV-9. Consumer requires the Manifest hash to include takeoff_origin when set. ADR-010 + AZ-489. |
autodev |
Test Cases (consumer side)
| ID | Scenario | Expected Outcome |
|---|---|---|
| CP-TC-1 | Cold build with all dependencies satisfied | outcome=SUCCESS; counts > 0; Manifest at cache_root/Manifest.json |
| CP-TC-2 | Warm build, identical request | outcome=IDEMPOTENT_NO_OP; counts all 0; Manifest unchanged on disk |
| CP-TC-3 | Warm build, different bbox | outcome=SUCCESS; rebuild happens; new Manifest replaces old (atomic) |
| CP-TC-4 | C6 has zero tiles for the requested scope | outcome=FAILURE; failure_reason directs operator to run C11 first |
| CP-TC-5 | Concurrent invocation while another build in progress | BuildLockHeldError; second invocation does not corrupt state |
| CP-TC-6 | An orphan file exists under cache_root after build |
ManifestCoverageError; rolled back to prior Manifest if present |
| CP-TC-7 | Operator key file fingerprint not in allowlist (operator mode) | ManifestWriteError (propagated from AZ-323); ZERO file writes |
| CP-TC-8 | EngineBuildError mid-compile |
Exception propagates; partial cache state consistent (atomic engines on disk for those that succeeded; Manifest NOT updated) |
| CP-TC-9 | DescriptorBatchError (persistent CUDA OOM) |
Exception propagates; engines may be on disk; Manifest NOT updated |
| CP-TC-10 | Conformance: isinstance(impl, CacheProvisioner) |
True |
| CP-TC-11 | compile_engines_for_corpus directly callable for re-compile-only flows |
Returns tuple[EngineCacheEntry, ...]; no descriptor / Manifest work |
| CP-TC-12 | Cold build wall-clock benchmark on Tier-1 dev workstation, 1k tiles, 3 backbones | ≤ 12 min (NFR C10-PT-01) |
| CP-TC-13 | Warm idempotent re-run benchmark | ≤ 1 min (NFR C10-PT-01) |
| CP-TC-14 | Build with takeoff_origin=A → second build with same request + takeoff_origin=A |
outcome=IDEMPOTENT_NO_OP |
| CP-TC-15 | Build with takeoff_origin=A → second build with same request + takeoff_origin=B (B != A) |
outcome=SUCCESS (re-build); new Manifest hash differs from prior |
| CP-TC-16 | BuildRequest.takeoff_origin=None with no prior Manifest |
outcome=SUCCESS; Manifest written without takeoff_origin field |