mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 20:51:13 +00:00
e2bebefdfc
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>
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""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"
|
|
)
|