mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 07:31:13 +00:00
[AZ-507] [AZ-323] [AZ-324] C10 Manifest build + verify + AZ-270 hygiene
AZ-507: codify cross-component import rule. Added _types/inference_errors.py shim re-exporting EngineBuildError + CalibrationCacheError from c7_inference; narrowed C10 EngineCompiler's except Exception to the two typed errors so unknown exceptions propagate (AC-3). Rewrote module-layout.md "Imports from" sections for 9 components + added Rule 9; appended an architecture.md ADR-009 note explaining why components must go through _types/*. AZ-323: ManifestBuilder + Ed25519ManifestSigner. Canonical JSON via orjson OPT_SORT_KEYS+OPT_INDENT_2, atomic-write Manifest.json + sha sidecar + .sig via AZ-280, operator-key fingerprint allowlist gate (C10-ST-01), ADR-010 takeoff_origin + flight_id baked into Manifest AND manifest_hash so re-planned routes change the cache identity (AC-15/AC-16). 20 unit tests cover all 16 ACs. AZ-324: ManifestVerifierImpl. Fail-closed Steps A-D: Manifest.json sidecar self-hash, Ed25519 trust-key set, schema parse with absolute/.. path rejection + takeoff_origin in-bbox check, stream SHA-256 per artifact with multi-failure accumulation. Operator mode re-derives tiles_coverage_sha256 from C6; airborne mode trusts the signed aggregate. 19 unit tests cover all 17 ACs. Composition root: c10_factory.build_manifest_builder + build_manifest_verifier + c6_tile_metadata_store_to_tiles_query adapter (the one place that legitimately imports both C6 and C10 without violating the AZ-270 lint). Dependency: pinned cryptography>=43.0,<46.0 in pyproject.toml. Tests: 1300 passed, 80 skipped (env-only), ruff clean for all AZ-323/324 files. AZ-306 (FAISS) intentionally deferred to batch 35 — needs C++ pybind11 toolchain not present in this environment. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""AZ-507 — Layer-0 typed-error envelope shim tests.
|
||||
|
||||
Covers AC-2 (shim re-exports resolve and are identical to the c7
|
||||
canonical classes) and AC-1 / AC-5 (module-layout.md + architecture.md
|
||||
documentation invariants).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from gps_denied_onboard._types.inference_errors import (
|
||||
CalibrationCacheError as ShimCalibrationCacheError,
|
||||
)
|
||||
from gps_denied_onboard._types.inference_errors import (
|
||||
EngineBuildError as ShimEngineBuildError,
|
||||
)
|
||||
from gps_denied_onboard.components.c7_inference.errors import (
|
||||
CalibrationCacheError as CanonicalCalibrationCacheError,
|
||||
)
|
||||
from gps_denied_onboard.components.c7_inference.errors import (
|
||||
EngineBuildError as CanonicalEngineBuildError,
|
||||
)
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def test_ac2_shim_engine_build_error_identity_with_canonical() -> None:
|
||||
# The shim must NOT introduce a fresh subclass — consumers catching
|
||||
# ShimEngineBuildError MUST catch what c7 actually raises.
|
||||
assert ShimEngineBuildError is CanonicalEngineBuildError
|
||||
|
||||
|
||||
def test_ac2_shim_calibration_cache_error_identity_with_canonical() -> None:
|
||||
assert ShimCalibrationCacheError is CanonicalCalibrationCacheError
|
||||
|
||||
|
||||
def test_ac2_shim_module_has_no_runtime_side_effects() -> None:
|
||||
# Importing the shim must not register a component config block or
|
||||
# otherwise mutate global state. Re-importing the module twice in the
|
||||
# same process must remain a no-op.
|
||||
import importlib
|
||||
|
||||
import gps_denied_onboard._types.inference_errors as shim
|
||||
|
||||
first = importlib.reload(shim)
|
||||
second = importlib.reload(shim)
|
||||
assert first.EngineBuildError is second.EngineBuildError
|
||||
assert first.CalibrationCacheError is second.CalibrationCacheError
|
||||
|
||||
|
||||
def test_ac1_module_layout_has_no_cross_component_public_api_imports() -> None:
|
||||
# AZ-507's primary deliverable: every line that previously said
|
||||
# `components.X (Public API)` in an "Imports from" line must now point
|
||||
# at `_types` (or be removed). We grep for the offending pattern and
|
||||
# assert zero matches.
|
||||
layout = (
|
||||
_REPO_ROOT / "_docs" / "02_document" / "module-layout.md"
|
||||
).read_text(encoding="utf-8")
|
||||
offenders = re.findall(
|
||||
r"components\.[a-z0-9_]+\s*\(Public API\)",
|
||||
layout,
|
||||
)
|
||||
assert offenders == [], (
|
||||
"module-layout.md still names cross-component Public API imports "
|
||||
f"after AZ-507: {offenders}"
|
||||
)
|
||||
|
||||
|
||||
def test_ac5_architecture_doc_codifies_cross_component_rule() -> None:
|
||||
# AZ-507 AC-5: the architecture doc must carry a one-paragraph rule
|
||||
# that cross-component imports go through `_types/*.py` (DTOs +
|
||||
# typed-error envelopes), never `components.X (Public API)`.
|
||||
arch = (
|
||||
_REPO_ROOT / "_docs" / "02_document" / "architecture.md"
|
||||
).read_text(encoding="utf-8")
|
||||
# Look for the rule sentence (case-insensitive) and the explicit
|
||||
# AZ-507 reference so future readers can trace the decision.
|
||||
assert "AZ-507" in arch, "architecture.md must reference AZ-507"
|
||||
assert re.search(
|
||||
r"cross-component imports go through `_types",
|
||||
arch,
|
||||
flags=re.IGNORECASE,
|
||||
), (
|
||||
"architecture.md must state the cross-component import rule "
|
||||
"introduced by AZ-507"
|
||||
)
|
||||
Reference in New Issue
Block a user