mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 20:31:12 +00:00
[AZ-489] [AZ-490] ADR-010 design pass: operator-mission as cold-start anchor
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>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
**Task**: AZ-325_c10_cache_provisioner
|
||||
**Name**: C10 CacheProvisioner
|
||||
**Description**: Implement `CacheProvisioner` (per the contract `_docs/02_document/contracts/c10_provisioning/cache_provisioner.md`), the public top-level orchestrator that composes AZ-321 (EngineCompiler), AZ-322 (DescriptorBatcher), and AZ-323 (ManifestBuilder) into a single idempotent F1 build pipeline. Acquires a `cache_root/.c10.lock` filesystem lockfile to enforce CP-INV-4. Computes the build-identity hash from the same canonical inputs AZ-323 hashes (model_ids + calibration_sha256 + tiles_coverage_sha256 + sector_class + bbox + zoom_levels) and compares to the existing `Manifest.json`'s `manifest_hash`; on match → `outcome=IDEMPOTENT_NO_OP`. On mismatch (or no prior Manifest) → run engine compile → descriptor population → Manifest build, then walk `cache_root` to confirm every file is listed in the new Manifest's `artifacts` section, raising `ManifestCoverageError` on orphans (with rollback to prior-good Manifest). Empty corpus → `BuildReport(outcome=FAILURE, failure_reason="run C11 TileDownloader first")` per description.md § 5.
|
||||
**Description**: Implement `CacheProvisioner` (per the contract `_docs/02_document/contracts/c10_provisioning/cache_provisioner.md` v1.1.0), the public top-level orchestrator that composes AZ-321 (EngineCompiler), AZ-322 (DescriptorBatcher), and AZ-323 (ManifestBuilder) into a single idempotent F1 build pipeline. Acquires a `cache_root/.c10.lock` filesystem lockfile to enforce CP-INV-4. Computes the build-identity hash from the same canonical inputs AZ-323 hashes (model_ids + calibration_sha256 + tiles_coverage_sha256 + sector_class + bbox + zoom_levels **+ takeoff_origin + flight_id**) and compares to the existing `Manifest.json`'s `manifest_hash`; on match → `outcome=IDEMPOTENT_NO_OP`. On mismatch (or no prior Manifest) → run engine compile → descriptor population → Manifest build (passing `request.takeoff_origin` and `request.flight_id` to AZ-323), then walk `cache_root` to confirm every file is listed in the new Manifest's `artifacts` section, raising `ManifestCoverageError` on orphans (with rollback to prior-good Manifest). Empty corpus → `BuildReport(outcome=FAILURE, failure_reason="run C11 TileDownloader first")` per description.md § 5. **A request whose `takeoff_origin` differs from the prior Manifest's by ≥ 1 mm is treated as a new build identity (CP-INV-8) — this is the contract that lets `ManifestVerifier` reject a re-planned route at boot.**
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-263_initial_structure, AZ-269_config_loader, AZ-266_log_module, AZ-303_c6_storage_interfaces, AZ-321_c10_engine_compiler, AZ-322_c10_descriptor_batcher, AZ-323_c10_manifest_builder
|
||||
**Component**: c10_provisioning (epic AZ-252 / E-C10)
|
||||
@@ -40,13 +40,13 @@ This task delivers the orchestrator + its frozen contract. It does NOT compile e
|
||||
2. **Tile gathering**: call `tile_metadata_store.query_by_bbox(bbox, zoom_levels, sector_class)`.
|
||||
- If empty → return `BuildReport(outcome=FAILURE, failure_reason="no tiles in C6 for the requested scope; run C11 TileDownloader first", engines_built=0, ...)`. ERROR log; release lock.
|
||||
3. **Build-identity hash for idempotence check**:
|
||||
- Compute `request_hash = sha256(canonical_json(model_ids + calibration_sha256 + tiles_coverage_sha256 + sector_class + bbox + zoom_levels))`. The `model_ids` come from the configured backbone list; `calibration_sha256` from streaming the calibration_path; `tiles_coverage_sha256` from sorting the tile rows by `(zoom, lat, lon, source)` and hashing per AZ-323's algorithm.
|
||||
- Compute `request_hash = sha256(canonical_json(model_ids + calibration_sha256 + tiles_coverage_sha256 + sector_class + bbox + zoom_levels + takeoff_origin_tuple_or_none + flight_id_or_none))`. The `model_ids` come from the configured backbone list; `calibration_sha256` from streaming the calibration_path; `tiles_coverage_sha256` from sorting the tile rows by `(zoom, lat, lon, source)` and hashing per AZ-323's algorithm. `takeoff_origin_tuple_or_none` is `(lat_deg, lon_deg, alt_m)` rounded to 9 decimal places when `request.takeoff_origin is not None`, otherwise the JSON `null` sentinel (CP-INV-8). The hashing formula MUST match AZ-323 exactly so AZ-325's idempotence decision agrees with AZ-323's emitted `build.manifest_hash`.
|
||||
- Read existing `Manifest.json` if present; parse only the `build.manifest_hash` field (don't run full verification — that's AZ-324's job). If `existing.manifest_hash == request_hash` → return `BuildReport(outcome=IDEMPOTENT_NO_OP, manifest_hash=existing.manifest_hash, manifest_path=existing_path, engines_built=0, engines_reused=0, descriptors_generated=0, elapsed_s, failure_reason=None)`. INFO log; release lock.
|
||||
4. **Active build path**:
|
||||
- Snapshot prior-good Manifest (rename to `Manifest.json.prev` if present) for rollback.
|
||||
- Compose engine compile request from configured backbones; call `engine_compiler.compile_engines_for_corpus(...)` → `engine_entries`.
|
||||
- Compose descriptor populate request (filter, callback hooked to logger); call `descriptor_batcher.populate_descriptors(...)` → `DescriptorBatchReport`. If `outcome=failure` → restore prior Manifest, release lock, return `BuildReport(outcome=FAILURE, failure_reason=batch.failure_reason, ...)`.
|
||||
- Compose Manifest build input from engine entries + descriptor index path + calibration + key_path; call `manifest_builder.build_manifest(...)` → `ManifestArtifact`.
|
||||
- Compose Manifest build input from engine entries + descriptor index path + calibration + key_path **+ `request.takeoff_origin` + `request.flight_id`** (ADR-010); call `manifest_builder.build_manifest(...)` → `ManifestArtifact`. Both fields default to `None` when the caller did not supply them (e.g., legacy C12 invocation without `--flight-id`).
|
||||
5. **Coverage check** (CP-INV-3 / D-C10-3):
|
||||
- Walk `cache_root` recursively (`pathlib.Path.rglob`); collect every regular file path EXCLUDING `Manifest.json`, `Manifest.json.sha256`, `Manifest.json.sig`, `Manifest.json.prev`, `.c10.lock`, and any `.sha256` sidecar (sidecars are implicit per the AZ-280 pattern, paired with their primary).
|
||||
- Build expected set: every `path` in `manifest.artifacts.engines + descriptor_index + calibration` (resolved relative to `cache_root`).
|
||||
@@ -152,6 +152,21 @@ Given a populated cache and identical request
|
||||
When `build_cache_artifacts` runs
|
||||
Then wall-clock ≤ 1 min (CP-TC-13 / NFR C10-PT-01); the bound work is the build-identity hash computation, which is dominated by `tiles_coverage_sha256` over 1000 tiles (~5 ms hashing)
|
||||
|
||||
**AC-14: `takeoff_origin` mismatch triggers full rebuild (ADR-010 / CP-INV-8)**
|
||||
Given a prior Manifest built with `takeoff_origin = A`
|
||||
When `build_cache_artifacts` is called with the SAME bbox / zooms / sector / calibration / tiles, but `takeoff_origin = B (B ≠ A by ≥ 1 mm)`
|
||||
Then `outcome=SUCCESS` (NOT `IDEMPOTENT_NO_OP`); the new Manifest replaces the old; the new `manifest_hash` differs from the prior; the new Manifest's `flight.takeoff_origin` matches B
|
||||
|
||||
**AC-15: `takeoff_origin = None` propagates through with no flight block in Manifest (back-compat)**
|
||||
Given a `BuildRequest` with `takeoff_origin = None` and `flight_id = None`
|
||||
When `build_cache_artifacts` runs
|
||||
Then `outcome=SUCCESS`; the produced Manifest has no `flight.takeoff_origin` key (AZ-323's AC-14); idempotence still works for subsequent identical-without-origin invocations
|
||||
|
||||
**AC-16: `flight_id` participation in idempotence**
|
||||
Given a prior Manifest built with `flight_id = X, takeoff_origin = A`
|
||||
When `build_cache_artifacts` runs with `flight_id = Y, takeoff_origin = A` (only `flight_id` differs)
|
||||
Then `outcome=SUCCESS` (NOT `IDEMPOTENT_NO_OP`); `flight_id` is part of the build identity per CP-INV-8
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
**Performance**
|
||||
@@ -177,6 +192,9 @@ Then wall-clock ≤ 1 min (CP-TC-13 / NFR C10-PT-01); the bound work is the buil
|
||||
| AC-2 | Warm re-run with identical request | IDEMPOTENT_NO_OP; zero phase calls |
|
||||
| AC-3 | Different bbox after prior build | SUCCESS; atomic replace; old Manifest gone |
|
||||
| AC-4 | Empty C6 query | FAILURE; hint string; lock released |
|
||||
| AC-14 | Warm re-run with different takeoff_origin | SUCCESS; new manifest_hash; phases called |
|
||||
| AC-15 | Build with takeoff_origin=None | SUCCESS; Manifest has no flight.takeoff_origin |
|
||||
| AC-16 | Warm re-run with different flight_id only | SUCCESS; new manifest_hash |
|
||||
| AC-5 | Pre-acquire lock externally; run | BuildLockHeldError |
|
||||
| AC-6 | Inject orphan file before coverage walk | ManifestCoverageError; prior Manifest restored |
|
||||
| AC-7 | Same as AC-6 with `coverage_strict=False` | SUCCESS; WARN log |
|
||||
|
||||
Reference in New Issue
Block a user