AZ-270: composition root with strategy registry, tier-gated lookup, topo-order construction, all-or-nothing teardown, StrategyNotLinkedError payload. AZ-272: orjson-backed FdrRecord serialise/parse with forward-compat for unknown payload + top-level fields and canonical overrun-record shape. AZ-279: pyproj-backed WGS84/ECEF/ENU + OSM slippy-map tile math with WgsConversionError for shape/range/zoom guards. AZ-281: strict EngineFilenameSchema build/parse/matches_host with anchored regex + enum validation; round-trip identity by construction. AZ-283: dtype-preserving (fp16/fp32) single + batch L2 normaliser with zero-norm safety and descriptor_metric() source-of-truth. pyproject.toml pins pyproj>=3.6 and orjson>=3.9 (named-backend deps per the AZ-272 / AZ-279 contracts). New DTOs LatLonAlt + BoundingBox and EngineCacheKey + HostCapabilities land in _types/ to back the helper contracts. 203 unit tests pass (64 new). Review verdict: PASS_WITH_WARNINGS; findings are perf-NFR deferrals + dep amendment + minor docstring polish. Co-authored-by: Cursor <cursoragent@cursor.com>
6.2 KiB
Batch Report — Cycle 1 · Batch 3
Tasks shipped: AZ-270, AZ-272, AZ-279, AZ-281, AZ-283
Date: 2026-05-11
Branch: dev
Review verdict: PASS_WITH_WARNINGS — see reviews/batch_03_review.md
Tasks
| ID | Title | Owner Layer | Outcome |
|---|---|---|---|
| AZ-270 | Composition Root | cross-cutting / runtime_root.py |
Implemented — strategy registry, tier gating, topological-order construction, all-or-nothing teardown, StrategyNotLinkedError payload |
| AZ-272 | FdrRecord Schema | cross-cutting / fdr_client/records.py |
Implemented — orjson-backed serialise/parse, forward-compat for unknown payload + top-level fields, overrun-payload contract |
| AZ-279 | WgsConverter | Layer 1 / helpers/wgs_converter.py |
Implemented — pyproj-backed ECEF/ENU with hand-rolled OSM slippy-map tile math + WgsConversionError for shape / range / zoom guards |
| AZ-281 | EngineFilenameSchema | Layer 1 / helpers/engine_filename_schema.py |
Implemented — strict build/parse/matches_host with anchored regex + enum validation |
| AZ-283 | DescriptorNormaliser | Layer 1 / helpers/descriptor_normaliser.py |
Implemented — dtype-preserving single + batch L2 with zero-norm safety + descriptor_metric() source of truth |
Files Changed
Modified:
pyproject.toml (+4 lines: pyproj, orjson pins)
src/gps_denied_onboard/runtime_root.py (composition root impl)
src/gps_denied_onboard/fdr_client/records.py (full schema + serialiser)
src/gps_denied_onboard/fdr_client/__init__.py (re-exports)
src/gps_denied_onboard/helpers/__init__.py (re-exports)
src/gps_denied_onboard/helpers/wgs_converter.py (pyproj-backed converter)
src/gps_denied_onboard/helpers/engine_filename_schema.py (build/parse/matches_host)
src/gps_denied_onboard/helpers/descriptor_normaliser.py (L2 + zero-safe + dtype-preserving)
src/gps_denied_onboard/_types/manifests.py (EngineCacheKey, HostCapabilities)
tests/unit/test_runtime_root_env_gate.py (updated to Config-typed compose_root)
Added:
src/gps_denied_onboard/_types/geo.py (LatLonAlt, BoundingBox)
tests/unit/test_az270_compose_root.py
tests/unit/test_az272_fdr_record_schema.py
tests/unit/test_az279_wgs_converter.py
tests/unit/test_az281_engine_filename_schema.py
tests/unit/test_az283_descriptor_normaliser.py
_docs/03_implementation/reviews/batch_03_review.md
Test Results
$ pytest tests/unit -q --timeout=30
203 passed, 2 skipped in 2.60s
Skips are environment-gated (cmake configure, actionlint), both verified in CI.
AC Coverage
| Task | ACs declared | ACs covered locally | Tier-2 deferred |
|---|---|---|---|
| AZ-270 | 6 + 2 NFR | 6 ACs + NFR-reliability (all-or-nothing) | NFR-perf (compose_root ≤ 750 ms on Jetson) |
| AZ-272 | 6 + 2 NFR | 6 ACs + NFR-reliability + invariant inline-blob | NFR-perf (serialise p99 ≤ 20 µs, parse p99 ≤ 50 µs on Jetson) |
| AZ-279 | 9 + 2 NFR | 9 ACs + determinism | NFR-perf (≤ 200 µs each on Jetson) |
| AZ-281 | 11 + 2 NFR | 11 ACs | NFR-perf (≤ 50 µs each on Jetson) |
| AZ-283 | 12 + 2 NFR | 12 ACs | NFR-perf-vector (≤ 50 µs / D=512) + NFR-perf-batch (≤ 5 ms / N=1000, D=512) |
Tier-2 perf budgets are owned by AZ-428..AZ-431; same deferral as batch 2. The batch-3 modules expose stable APIs so those tasks plug in without further code changes here.
Dependency Pins
This batch amended pyproject.toml with two new runtime pins required
by AZ-272 and AZ-279 contracts:
pyproj>=3.6,<4.0— WGS84 geodesy backend per AZ-279 contractorjson>=3.9,<4.0— FDR wire format per AZ-272 contract
Both names appear verbatim in the upstream contract documents and were the named-backend constraint. AZ-263 left these unpinned; the pins are added by the first batch that needs them — same pattern as batch 2.
Adjacent Hygiene
This batch added four new DTOs to _types/ because the AZ-279 / AZ-281
contracts explicitly import them from _types:
_types/geo.py(new file):LatLonAlt,BoundingBox_types/manifests.py:EngineCacheKey,HostCapabilities
AZ-263 left these out; this batch closes the gap. No other helper or component depends on them yet, so the additions are forward-compatible.
The runtime_root.py signature change (compose_root() → compose_root(config))
forced an update to tests/unit/test_runtime_root_env_gate.py (now passes
Config() so the env-var fail-fast still fires). AC-8 of AZ-263 is
intact.
Architecture Compliance
helpers/wgs_converter.pyimports onlynumpy,pyproj,_types.geo, and stdlib. AC-9 AST scan verifies nocomponents.*imports.helpers/engine_filename_schema.pyimports onlyre,_types.manifests, and stdlib. AC-11 AST scan verifies.helpers/descriptor_normaliser.pyimports onlynumpy+ stdlib. AC-12 AST scan verifies.fdr_client/records.pyimports onlyorjson+ stdlib + thedataclassesdecorator. No upward imports.runtime_root.pyimports fromgps_denied_onboard.configonly (the composition root is permitted to consume the config loader). The AC-6 AST scan over every file undercomponents/confirms no cross-component imports exist (the registry is the only sanctioned cross-component reference path).
No new circular imports.
Review Findings Summary
Verdict: PASS_WITH_WARNINGS. Four Low-severity findings:
- Two engine-cache types coexist (
EngineCacheEntryvsEngineCacheKey) — recommend a docstring sentence on each. - NFR-perf microbenchmarks deferred to Tier-2 perf suite.
pyproject.tomldep amendment (pyproj + orjson) — by the consumer batch as designed.- AC-6 architecture lint lives in tests; future
importlinterupgrade recommended once concrete components ship.
Full details in reviews/batch_03_review.md.
Tracker Transitions
- AZ-270: To Do → In Progress → (batch close) In Testing
- AZ-272: To Do → In Progress → (batch close) In Testing
- AZ-279: To Do → In Progress → (batch close) In Testing
- AZ-281: To Do → In Progress → (batch close) In Testing
- AZ-283: To Do → In Progress → (batch close) In Testing