[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:
Oleksandr Bezdieniezhnykh
2026-05-11 02:03:36 +03:00
parent 8e71f6c002
commit 3acc7f33dd
24 changed files with 2381 additions and 97 deletions
@@ -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.
+1 -1
View File
@@ -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