mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 13:11:14 +00:00
[AZ-270] [AZ-272] [AZ-279] [AZ-281] [AZ-283] Compose root + FDR schema + 3 Layer-1 helpers
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>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# 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 contract
|
||||
- `orjson>=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.py` imports only `numpy`, `pyproj`, `_types.geo`,
|
||||
and stdlib. AC-9 AST scan verifies no `components.*` imports.
|
||||
- `helpers/engine_filename_schema.py` imports only `re`, `_types.manifests`,
|
||||
and stdlib. AC-11 AST scan verifies.
|
||||
- `helpers/descriptor_normaliser.py` imports only `numpy` + stdlib.
|
||||
AC-12 AST scan verifies.
|
||||
- `fdr_client/records.py` imports only `orjson` + stdlib + the
|
||||
`dataclasses` decorator. No upward imports.
|
||||
- `runtime_root.py` imports from `gps_denied_onboard.config` only (the
|
||||
composition root is permitted to consume the config loader). The AC-6
|
||||
AST scan over every file under `components/` 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:
|
||||
|
||||
1. Two engine-cache types coexist (`EngineCacheEntry` vs `EngineCacheKey`)
|
||||
— recommend a docstring sentence on each.
|
||||
2. NFR-perf microbenchmarks deferred to Tier-2 perf suite.
|
||||
3. `pyproject.toml` dep amendment (pyproj + orjson) — by the consumer
|
||||
batch as designed.
|
||||
4. AC-6 architecture lint lives in tests; future `importlinter` upgrade
|
||||
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
|
||||
@@ -0,0 +1,331 @@
|
||||
# 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.
|
||||
@@ -8,7 +8,7 @@ status: in_progress
|
||||
sub_step:
|
||||
phase: 14
|
||||
name: loop-next-batch
|
||||
detail: "batch 2 of N committed"
|
||||
detail: "batch 3 of N committed"
|
||||
retry_count: 0
|
||||
cycle: 1
|
||||
tracker: jira
|
||||
|
||||
Reference in New Issue
Block a user