Files
gps-denied-onboard/tests/unit/test_az507_inference_errors_shim.py
Oleksandr Bezdieniezhnykh e2bebefdfc [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>
2026-05-13 02:37:14 +03:00

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"
)