mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-23 01:21:13 +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-324_c10_manifest_verifier
|
||||
**Name**: C10 ManifestVerifier
|
||||
**Description**: Implement `ManifestVerifier` (per the contract `_docs/02_document/contracts/c10_provisioning/manifest_verifier.md`), the read-only validator that AC-NEW-1 places between F2 takeoff and any engine deserialization. Loads `Manifest.json`, verifies its sidecar SHA-256 matches the Manifest bytes, parses the Ed25519 detached signature at `Manifest.json.sig`, verifies it against the caller-supplied `trusted_public_keys` tuple, parses the Manifest schema (rejecting absolute paths and schema violations), and walks every per-artifact entry re-hashing it via AZ-280's sidecar pattern. Returns a `VerificationResult` with `outcome ∈ {PASS, FAIL}`, the union of all `VerifyFailReason` values that fired, the populated `per_artifact_checks` list, and `elapsed_ms`. Fail-closed: any deviation in signature, schema, key trust, or hashes yields `FAIL` with detailed reasons. Never raises on a verify failure — only on environment errors (Manifest.json missing → `MANIFEST_NOT_FOUND` is still `FAIL`, not raise).
|
||||
**Description**: Implement `ManifestVerifier` (per the contract `_docs/02_document/contracts/c10_provisioning/manifest_verifier.md` v1.1.0), the read-only validator that AC-NEW-1 places between F2 takeoff and any engine deserialization. Loads `Manifest.json`, verifies its sidecar SHA-256 matches the Manifest bytes, parses the Ed25519 detached signature at `Manifest.json.sig`, verifies it against the caller-supplied `trusted_public_keys` tuple, parses the Manifest schema (rejecting absolute paths and schema violations), validates the optional `flight.takeoff_origin` block (well-formed `LatLonAlt` + inside `build.bbox` per ADR-010 + AZ-490), and walks every per-artifact entry re-hashing it via AZ-280's sidecar pattern. Returns a `VerificationResult` with `outcome ∈ {PASS, FAIL}`, the union of all `VerifyFailReason` values that fired, the populated `per_artifact_checks` list, the pass-through `takeoff_origin` + `flight_id` (or `None` when absent from the Manifest body), and `elapsed_ms`. Fail-closed: any deviation in signature, schema, key trust, hashes, or origin validity yields `FAIL` with detailed reasons. Never raises on a verify failure — only on environment errors (Manifest.json missing → `MANIFEST_NOT_FOUND` is still `FAIL`, not raise).
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-263_initial_structure, AZ-269_config_loader, AZ-266_log_module, AZ-280_sha256_sidecar, AZ-281_engine_filename_schema
|
||||
**Component**: c10_provisioning (epic AZ-252 / E-C10)
|
||||
@@ -56,9 +56,15 @@ This task delivers the verifier + its frozen contract. It does NOT compile engin
|
||||
- If `trusted_public_keys` is empty: append `UNTRUSTED_PUBLIC_KEY`; return `FAIL`.
|
||||
5. **Step C — Schema parse**:
|
||||
- `orjson.loads(manifest_bytes)` → dict.
|
||||
- Validate required keys: `schema_version`, `build` (with sub-keys `bbox`, `zoom_levels`, `sector_class`, `built_at`, `manifest_hash`), `artifacts` (with `engines`, `descriptor_index`, `calibration`, `tiles_coverage`), `signing_public_key_fingerprint`.
|
||||
- Validate required keys: `schema_version`, `build` (with sub-keys `bbox`, `zoom_levels`, `sector_class`, `built_at`, `manifest_hash`), `artifacts` (with `engines`, `descriptor_index`, `calibration`, `tiles_coverage`), `signing_public_key_fingerprint`. `flight` block is OPTIONAL (added in schema v1.1, ADR-010).
|
||||
- Validate types: `engines` is list of `{path: str, sha256: str}`; `descriptor_index`, `calibration` are `{path: str, sha256: str}`; `tiles_coverage` is `{sha256: str, tile_count: int}`.
|
||||
- Validate path-relative-only: every `path` value must be relative (no leading `/`, no `..` segments). Append `SCHEMA_VIOLATION` per offending field; if any, return `FAIL`.
|
||||
- **Flight block (ADR-010 / AZ-490)**:
|
||||
- If `flight` key absent → `takeoff_origin = None`, `flight_id = None`; continue.
|
||||
- If `flight` present → parse `flight_id` (`UUID` or `None`) and `takeoff_origin` (optional block).
|
||||
- If `flight.takeoff_origin` present → validate `lat_deg ∈ [-90, 90]`, `lon_deg ∈ [-180, 180]`, `alt_m` finite (no NaN/Inf). Append `TAKEOFF_ORIGIN_INVALID` to `fail_reasons` and the offending field name to `fail_details` if any check fails.
|
||||
- If `flight.takeoff_origin` is well-formed → check it falls inside `build.bbox` (`bbox.lat_min ≤ lat ≤ bbox.lat_max`, `bbox.lon_min ≤ lon ≤ bbox.lon_max`). Append `TAKEOFF_ORIGIN_OUT_OF_BBOX` if not.
|
||||
- The `takeoff_origin` is populated on `VerificationResult` whenever the block parsed (even on FAIL), per MV-INV-9, so operators see what was attempted.
|
||||
6. **Step D — Per-artifact hash walk** (only reached if Steps A–C all passed):
|
||||
- For each engine, descriptor_index, calibration entry:
|
||||
- Compute `actual_path = manifest_path.parent / entry.path`.
|
||||
@@ -166,6 +172,26 @@ Given `trusted_public_keys = ()`
|
||||
When verify runs
|
||||
Then `fail_reasons=(UNTRUSTED_PUBLIC_KEY,)` regardless of Manifest validity; per-artifact walk does NOT happen
|
||||
|
||||
**AC-14: Manifest with no `flight` block parses cleanly (back-compat)**
|
||||
Given a v1.0 Manifest (no `flight` block) that is otherwise valid + signed
|
||||
When verify runs
|
||||
Then `outcome=PASS`; `VerificationResult.takeoff_origin is None`; `VerificationResult.flight_id is None`
|
||||
|
||||
**AC-15: Well-formed in-bbox `takeoff_origin` passes through**
|
||||
Given a v1.1 Manifest with `flight.takeoff_origin = (50.0, 36.2, 200.0)` inside the recorded bbox
|
||||
When verify runs
|
||||
Then `outcome=PASS`; `VerificationResult.takeoff_origin == LatLonAlt(50.0, 36.2, 200.0)`
|
||||
|
||||
**AC-16: Malformed `takeoff_origin` (lat=200) fails closed**
|
||||
Given a Manifest with `flight.takeoff_origin.lat_deg = 200`
|
||||
When verify runs
|
||||
Then `outcome=FAIL`; `fail_reasons` contains `TAKEOFF_ORIGIN_INVALID`; `fail_details` names `lat_deg`; the `takeoff_origin` field on `VerificationResult` is still populated for diagnostics
|
||||
|
||||
**AC-17: Out-of-bbox `takeoff_origin` fails closed**
|
||||
Given a Manifest whose `flight.takeoff_origin = (10.0, 10.0, 0)` while `build.bbox` covers `(49.5..50.5, 35.5..36.5)`
|
||||
When verify runs
|
||||
Then `outcome=FAIL`; `fail_reasons` contains `TAKEOFF_ORIGIN_OUT_OF_BBOX`
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
**Performance**
|
||||
@@ -197,6 +223,10 @@ Then `fail_reasons=(UNTRUSTED_PUBLIC_KEY,)` regardless of Manifest validity; per
|
||||
| AC-9 | Operator mode + drifted tile | TILES_COVERAGE_MISMATCH |
|
||||
| AC-10 | Airborne mode | tiles_coverage matched=True |
|
||||
| AC-11 | Conformance check | True |
|
||||
| AC-14 | v1.0 Manifest (no flight block) | PASS; takeoff_origin=None; flight_id=None |
|
||||
| AC-15 | v1.1 Manifest, valid in-bbox origin | PASS; takeoff_origin populated |
|
||||
| AC-16 | Malformed origin (lat=200) | FAIL; TAKEOFF_ORIGIN_INVALID; field name in details |
|
||||
| AC-17 | Out-of-bbox origin | FAIL; TAKEOFF_ORIGIN_OUT_OF_BBOX |
|
||||
| AC-12 | Inspect elapsed_ms | All non-negative; ordered as expected |
|
||||
| AC-13 | Empty trusted keys | FAIL; UNTRUSTED |
|
||||
| NFR-perf-airborne | 5 artifact bench, no tile re-walk | p99 ≤ 100 ms |
|
||||
|
||||
Reference in New Issue
Block a user