# Code Review Report **Batch**: 3 **Tasks**: AZ-270 (Composition Root), AZ-272 (FdrRecord Schema), AZ-279 (WgsConverter), AZ-281 (EngineFilenameSchema), AZ-283 (DescriptorNormaliser) **Date**: 2026-05-11 **Verdict**: PASS_WITH_WARNINGS ## Scope Batch 3 closes the E-CC-CONF epic (AZ-270 ships the real composition root with strategy registry and tier gates) and kicks off E-CC-FDR-CLIENT (AZ-272 ships the wire-format schema). Three Layer-1 helpers — WGS converter, engine-filename schema, descriptor normaliser — land in the same batch because each is a small, contract-frozen unit that dozens of downstream component tasks gate on. ## Phase 1: Context Loading Read: - `_docs/02_tasks/todo/AZ-270_compose_root.md` (6 ACs + 2 NFRs) - `_docs/02_tasks/todo/AZ-272_fdr_record_schema.md` (6 ACs + 2 NFRs) - `_docs/02_tasks/todo/AZ-279_wgs_converter.md` (9 ACs + 2 NFRs) - `_docs/02_tasks/todo/AZ-281_engine_filename_schema.md` (11 ACs + 2 NFRs) - `_docs/02_tasks/todo/AZ-283_descriptor_normaliser.md` (12 ACs + 2 NFRs) - Five contracts under `_docs/02_document/contracts/` - `_docs/02_document/module-layout.md` (`runtime_root.py` ownership, helper layer envelope, Layer-1 import rules) Ownership envelopes resolved per `module-layout.md`: - AZ-270 owns `src/gps_denied_onboard/runtime_root.py` (replaces the AZ-263 stub) + `tests/unit/test_az270_compose_root.py` + adjusts the existing `tests/unit/test_runtime_root_env_gate.py` for the new `compose_root(config)` signature - AZ-272 owns `src/gps_denied_onboard/fdr_client/records.py` + `src/gps_denied_onboard/fdr_client/__init__.py` re-exports + `tests/unit/test_az272_fdr_record_schema.py` - AZ-279 owns `src/gps_denied_onboard/helpers/wgs_converter.py` + `tests/unit/test_az279_wgs_converter.py` - AZ-281 owns `src/gps_denied_onboard/helpers/engine_filename_schema.py` + `tests/unit/test_az281_engine_filename_schema.py` - AZ-283 owns `src/gps_denied_onboard/helpers/descriptor_normaliser.py` + `tests/unit/test_az283_descriptor_normaliser.py` Adjacent hygiene this batch (per `coderule.mdc` scope-discipline rule): - New DTOs in `src/gps_denied_onboard/_types/geo.py` (`LatLonAlt`, `BoundingBox`) and added `EngineCacheKey` + `HostCapabilities` to `src/gps_denied_onboard/_types/manifests.py`. The AZ-279 and AZ-281 contracts explicitly import these from `_types`; AZ-263 left them un-declared, so this batch closes the gap. - `pyproject.toml` amended with two new pinned dependencies (`pyproj` for AZ-279, `orjson` for AZ-272). Both names appear verbatim in the upstream contract documents. ## Phase 2: Spec Compliance ### AZ-270 — Composition Root | AC | Verification | |----|--------------| | AC-1 Default deployment composes | `test_ac1_default_deployment_composes` builds a 3-component graph and asserts every slot is populated | | AC-2 Strategy/build-flag mismatch rejected | `test_ac2_strategy_not_linked_raises_with_payload` asserts `StrategyNotLinkedError(strategy_name, component_slug, available_strategies)` payload matches | | AC-3 Operator-side excludes airborne | `test_ac3_operator_excludes_airborne_only` registers a strategy with `tier="airborne"`, asserts the same strategy in `compose_operator` raises | | AC-4 Reachability proof | `test_ac4_runtime_root_smoke_exit_zero` runs `compose_root(Config())` with no component blocks and asserts a fully-formed `RuntimeRoot` | | AC-5 Construction order respects deps | `test_ac5_construction_order_respects_dependencies` registers in reverse order, asserts the topo pass orders dependents after dependencies | | AC-6 Single import point enforced | `test_ac6_only_compose_root_imports_concrete_strategies` AST-walks every file under `components/` and asserts no cross-component imports | | NFR-reliability all-or-nothing | `test_nfr_reliability_partial_construction_closed_on_failure` injects a failing factory, asserts every prior `_Closable` had `close()` called | The implementation provides a `_Registration` dataclass + global `_STRATEGY_REGISTRY` keyed by `(component_slug, strategy_name)`, a `register_strategy()` entrypoint (the only sanctioned write-path), a `clear_strategy_registry()` helper for test isolation, a Kahn-style topo-sort over the `depends_on` graph, and a `_close_partial_instances` best-effort teardown hook. ### AZ-272 — FdrRecord Schema | AC | Verification | |----|--------------| | AC-1 Every kind round-trips | `test_ac1_roundtrip_every_known_kind` parametrised over all 10 v1.0.0 kinds with kind-specific payload fixtures | | AC-2 Forward-compat payload | `test_ac2_forward_compatible_unknown_payload_field_preserved` (and `_ac2b_unknown_top_level_field_preserved` for the top-level bucket) | | AC-3 Unknown future kind opaque | `test_ac3_unknown_future_kind_returned_opaquely` | | AC-4 Missing/non-int schema_version | `test_ac4_missing_schema_version_raises` + `test_ac4_non_integer_schema_version_raises` | | AC-5 Overrun shape | `test_ac5_overrun_missing_dropped_count_rejected_on_parse` + `test_ac5_overrun_zero_dropped_count_rejected_on_serialise` | | AC-6 Producer ID required | `test_ac6_empty_producer_id_rejected_on_serialise` | | Invariant inline-blob cap | `test_nfr_oversized_inline_blob_rejected` | | Pure determinism | `test_nfr_serialise_is_pure_byte_identical` | Tier-2 perf NFR (`serialise` p99 ≤ 20 µs; `parse` p99 ≤ 50 µs) is deferred to the Tier-2 perf suite — same pattern as batch-2 NFR-perf. ### AZ-279 — WgsConverter | AC | Verification | |----|--------------| | AC-1 ECEF round-trip | `test_ac1_ecef_roundtrip` over 5 globally-distributed samples within `atol=1e-9` deg + `1e-6` m | | AC-2 ENU 10 km round-trip | `test_ac2_enu_roundtrip_within_10_km` asserts horizontal residual < 1 m, vertical < 1 cm | | AC-3 Slippy-map z18 round-trip | `test_ac3_slippy_map_tile_roundtrip_z18_contains_input` pinned to `(153295, 88392)` per OSM convention | | AC-4 Lat range guard | `test_ac4_web_mercator_latitude_range_guard` | | AC-5 Zoom range guard | `test_ac5_zoom_range_guard` | | AC-6 Tile-xy range guard | `test_ac6_tile_xy_range_guard` | | AC-7 ECEF shape contract | `test_ac7_ecef_shape_contract` | | AC-8 Determinism | `test_ac8_determinism_byte_equal_outputs` (`tobytes()` equality) | | AC-9 No upward imports | `test_ac9_no_upward_imports_to_components` (AST scan) | Web-Mercator max-lat constant is `arctan(sinh(pi))` ≈ 85.0511287798066° (matches the OSM-documented constant). `_enu_to_ecef_rotation` implements the canonical local-tangent-plane basis at `(lat, lon)`; ENU sign convention is `(east, north, up)`. ### AZ-281 — EngineFilenameSchema | AC | Verification | |----|--------------| | AC-1 Reference example | `test_ac1_reference_example_builds_exact_string` | | AC-2 Round-trip identity | `test_ac2_roundtrip_identity_over_10_random_tuples` (seeded `random.Random(2026)`) | | AC-3 Host-match exact | `test_ac3_matches_host_exact_match` | | AC-4 Host-mismatch no exception | `test_ac4_matches_host_tuple_mismatch_returns_false` (sm + trt variants) | | AC-5 Precision enum strictness | `test_ac5_precision_enum_strictness` | | AC-6 Model char set | `test_ac6_model_name_character_set_rejection` | | AC-7 Reserved separator | `test_ac7_reserved_separator_collision_rejected` | | AC-8 Version format | `test_ac8_three_segment_version_rejected` | | AC-9 Parse malformed | `test_ac9_parse_rejects_malformed_filename` | | AC-10 `.engine` suffix | `test_ac10_parse_requires_engine_suffix` | | AC-11 No upward imports | `test_ac11_no_upward_imports_to_components` (AST scan) | The implementation backs everything on a single anchored regex `_FILENAME_RE` plus explicit `_validate_*` helpers for the producer path. Round-trip identity holds by construction because `parse` extracts the same five fields the regex consumed. ### AZ-283 — DescriptorNormaliser | AC | Verification | |----|--------------| | AC-1 Unit-vector example | `test_ac1_unit_vector_example` | | AC-2 Batch normalisation | `test_ac2_batch_normalisation` | | AC-3 fp16 dtype preserved | `test_ac3_fp16_dtype_preservation` | | AC-4 fp32 dtype preserved | `test_ac4_fp32_dtype_preservation` | | AC-5 Zero-vector safe | `test_ac5_zero_vector_handling` + `_ac5b_zero_row_in_batch_remains_zero` | | AC-6 fp32 idempotence | `test_ac6_idempotence_fp32` (`tobytes()` equality) | | AC-7 fp16 idempotence | `test_ac7_idempotence_fp16_within_half_precision_tol` | | AC-8 No in-place mutation | `test_ac8_no_in_place_mutation` | | AC-9 Metric source-of-truth | `test_ac9_metric_is_inner_product_exact_string` | | AC-10 fp64 rejected | `test_ac10_float64_dtype_rejected` | | AC-11 Shape contract | `test_ac11_shape_contract_single_rejects_2d` + `_batch_rejects_1d` | | AC-12 No upward imports | `test_ac12_no_upward_imports_to_components` (AST scan) | The implementation routes through `float32` internally for norm stability, then casts back to the caller dtype (no silent up-cast). The batch path uses `np.where(norms == 0.0, 1.0, norms)` to avoid division-by-zero without branching per row. No Spec-Gap findings. ## Phase 3: Code Quality - **SRP** — each module owns exactly one concern. `runtime_root.py` is marginally larger (registry + topo + compose + entrypoint) but every internal function has a sharp name; nothing leaks responsibility into generic "candidate"/"data" helpers. - **Error handling** — every public surface raises one typed exception: `StrategyNotLinkedError`, `FdrSchemaError`, `WgsConversionError`, `EngineFilenameSchemaError`, `DescriptorNormaliserError`. Library errors (`orjson.JSONDecodeError`, `pyproj.ProjError`-tier exceptions) are wrapped at the public boundary. - **Naming** — public symbols match the contract files verbatim. `RuntimeRoot.construction_order` is an additive field the contract permits but does not mandate; it's the observable used by AC-5 tests. - **Complexity** — no function exceeds 50 lines; the busiest function is `_compose` at ~30 lines. - **DRY** — shared helpers (`_enu_to_ecef_rotation`, `_validate_envelope_outgoing`, `_validate_overrun_payload`) are module-private; no duplication across helpers. - **Test quality** — every AC has a directly-mapped test that asserts the contractually-named behaviour. AST-based import scans for the "no upward imports" invariant rather than string matches. - **Dead code** — the previous AZ-263 `compose_root` stub returned a hollow `RuntimeRoot(binary, profile)`; replaced verbatim. The previous `runtime_root.py` had a `dataclass` import that is no longer needed at module level — confirmed it is still used (the new file imports it via `dataclass` for `RuntimeRoot`/`OperatorRoot`). ## Phase 4: Security Quick-Scan - **No SQL string interpolation** anywhere in the batch. - **No `shell=True` / `eval` / `exec`**. - **No hardcoded secrets** — operator/airborne env-var lists are identifiers only. - **No insecure deserialization** — `orjson.loads` is the production decoder; type-validated before any field is used. YAML loading is in the AZ-269 path (out of scope here) and uses `yaml.safe_load`. - **`_FILENAME_RE` is anchored** with `^...$`; no ReDoS surface (the pattern is linear-time on input length). - **`atomic_write` is delegated** to the AZ-280 helper (`Sha256Sidecar`); this batch does not introduce new disk-write code paths. No security findings. ## Phase 5: Performance Scan - `serialise` / `parse` go through `orjson` (C-extension); a single allocation per record. No O(n²) loops. - `WgsConverter._ECEF_FROM_LLA` and `_LLA_FROM_ECEF` are module-level cached `pyproj.Transformer` instances; no per-call transformer setup cost. - `DescriptorNormaliser.l2_normalise_batch` uses NumPy vectorised `np.linalg.norm(..., axis=1, keepdims=True)`; no Python-level row iteration. - `_topo_order` is Kahn-style DFS at O(V+E) in the strategy graph (sub-msec for any realistic component count). NFR microbenchmarks (AZ-272 serialise / parse latency; AZ-279 per-helper latency; AZ-283 per-vector + batch latency) need Tier-2 hardware; same deferral pattern as batch 2. ## Phase 6: Cross-Task Consistency - `fdr_client.records` imports `orjson` only; no upward imports. - `helpers/wgs_converter.py` imports `pyproj`, `numpy`, `_types.geo` only; no component imports. - `helpers/engine_filename_schema.py` imports `re` and `_types.manifests` only. - `helpers/descriptor_normaliser.py` imports `numpy` only. - `runtime_root.py` imports `config` (allowed — it's the consumer) and no `components.*` modules (because no concrete components exist yet; AC-6 enforces this going forward). - The `_close_partial_instances` cleanup hook uses `getattr(inst, "close", None)` so it does not require components to implement a particular interface — they opt in by exposing `.close()`. No cross-task consistency findings. ## Phase 7: Architecture Compliance Per `module-layout.md`: - `helpers/*` (Layer 1): allowed imports are `_types`, stdlib, and named external deps (pyproj, numpy, orjson). PASS for AZ-279 / AZ-281 / AZ-283. - `runtime_root.py` (composition root): allowed to import concrete strategies from `components.*` — but none exist yet, so this permission is unused. - `fdr_client/records.py` (Layer 0 / cross-cutting): allowed imports are stdlib + `orjson`. PASS. - The strategy registry is global state inside `runtime_root.py`. This is intentional and matches the ADR-009 "interface-first DI" prescription — the registry is filled by bootstrap modules, then consumed by `compose_*`. Tests reset it via the `clear_strategy_registry()` fixture. No new circular imports. The import graph `runtime_root → config → (stdlib + pyyaml)` and `fdr_client.records → (stdlib + orjson)` are both acyclic. No Architecture findings. ## Findings | # | Severity | Category | File:Line | Title | |---|----------|----------|-----------|-------| | 1 | Low | Maintainability | _types/manifests.py | Two engine-cache types coexist (`EngineCacheEntry` and `EngineCacheKey`) | | 2 | Low | Performance | tests/unit/test_az272*.py + test_az279*.py + test_az283*.py | NFR-perf microbenchmarks deferred to Tier-2 | | 3 | Low | Scope | pyproject.toml | Batch 3 added `pyproj` + `orjson` deps that AZ-263 had not pinned | | 4 | Low | Architecture | src/gps_denied_onboard/runtime_root.py + tests | AC-6 architecture lint relies on a tests-only AST scan | ### Finding Details **F1: Two engine-cache types coexist** (Low / Maintainability) - Location: `src/gps_denied_onboard/_types/manifests.py` - Description: `EngineCacheEntry` (AZ-263 stub, carries `engine_path`, `content_hash`, `int8_calibration_path`) and `EngineCacheKey` (AZ-281, the five-tuple filename key) sit side by side. They serve different purposes — a `Key` is the parsed filename tuple, an `Entry` is the cache row that also tracks a content hash. C10's Manifest will need both. Worth a follow-up doc note in `_types/manifests.py` so the next reader doesn't think one supersedes the other. - Suggestion: capture a short docstring sentence on each, calling out the other. - Task: AZ-281 **F2: NFR-perf microbenchmarks deferred** (Low / Performance) - Location: AZ-272 / AZ-279 / AZ-283 perf NFRs - Description: Same pattern as batch 2. Tier-2 hardware-pinned budgets (serialise/parse latency, per-helper p99, batch p99) cannot be validated locally; AZ-428..AZ-431 own the Tier-2 perf suite. - Suggestion: add corresponding `tests/perf/` files when AZ-428..AZ-431 lands. - Task: AZ-272, AZ-279, AZ-283 **F3: pyproject.toml dep amendment** (Low / Scope) - Location: `pyproject.toml::dependencies` - Description: Batch 3 added `pyproj>=3.6,<4.0` and `orjson>=3.9,<4.0`. Both are named-backend deps the upstream contract documents call out. Same justification as batch 2's `gtsam` / `atomicwrites` amendment. - Suggestion: none — recorded so the AZ-263 implementation report and the Product-Implementation-Completeness audit reflect the batch-3 dep addition. - Task: AZ-272, AZ-279 **F4: AC-6 architecture lint lives in tests** (Low / Architecture) - Location: `tests/unit/test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies` - Description: The "only compose_root imports concrete strategies" invariant is enforced by an AST scan inside the unit suite. A future CI lane that runs `importlinter` would catch the same thing earlier. For v1.0.0 the unit-test gate is sufficient (and necessary — without any concrete strategies yet, there's no need for a separate tool). - Suggestion: when the first concrete component strategy is wired in, consider adding an `importlinter.cfg` so the check runs at lint time too. - Task: AZ-270 ## Verdict **PASS_WITH_WARNINGS**. Four Low-severity findings, all informational follow-ups (per-NFR deferrals, dep amendment, docstring polish, and a future lint-tier upgrade). Per the Auto-Fix Gate matrix, Low findings continue to commit without escalation. ## Test Run Summary - **Local**: 203 passed, 2 skipped (cmake configure, actionlint — both CI-gated). Includes 64 new batch-3 tests. - **Coverage**: every AC across all five tasks has at least one corresponding test. NFR-perf budgets are deferred to Tier-2.