# Batch 48 / Cycle 1 — Implementation Report **Date**: 2026-05-13 **Tasks**: AZ-508 — ISO-timestamp helper consolidation (2pt) **Total complexity**: 2 points **Result**: PASS (per-batch code review) **Jira tracker state**: AZ-508 transitioned To Do → In Progress (prior session) → In Testing (this batch) ## Scope Hygiene PBI from cumulative reviews batches 28–30 (Finding F3) and batches 31–33 (Finding F2). Consolidates the duplicated private `_iso_ts_now()` one-liners across `c6_tile_cache` and `c7_inference` into a single Layer-1 helper at `src/gps_denied_onboard/helpers/iso_timestamps.py` (`iso_ts_now() -> str`, RFC 3339 UTC microsecond `Z`-suffix). Closes F2 before the upcoming C2 batches (AZ-339 / AZ-340) and the C4/C3 batches (AZ-358 / AZ-349) would have added the 8th and 9th copies of the helper. ## Spec Drift Resolution (User-Approved Choose A) The task spec assumed: - **5** call-sites needing migration. - AC-2 format regex `\.\d{6}\+00:00$`. - "FDR records standardize on `+00:00` per `test_az272_fdr_record_schema.py`". On-disk reality at Batch 48 start: - **3** call-sites: c6's three callers had already been locally consolidated under `src/gps_denied_onboard/components/c6_tile_cache/_timestamp.py` (export `iso_ts_now`, same `Z`-suffix format); `tensorrt_runtime.py` carries no local `_iso_ts_now` definition. - All 3 existing helpers produce `%Y-%m-%dT%H:%M:%S.%fZ` (`Z` suffix). - The canonical FDR `ts` fixture is `_TS = "2026-05-11T00:00:00.000000Z"` (also `Z` suffix). The `+00:00` strings in that test file belong to *other* DTO fields (manifest `generated_at_iso`, `observed_at_iso`), not the FDR `ts` envelope. Picking the spec's `+00:00` literally would have silently changed FDR record `ts` bit-shape — explicitly **Excluded** by the spec ("no schema change") and would have broken AC-5 (FDR schema tests pass unmodified). User selected **Choose A**: Z-format helper, byte-identical to existing 3 helpers + FDR `_TS` fixture; AC-2 regex corrected in the spec; drift documented in this report and in the spec's preamble. ## Files Changed ### Production (new) - `src/gps_denied_onboard/helpers/iso_timestamps.py` — `iso_ts_now() -> str`. Stateless free function; stdlib `datetime` + `timezone` only; format `datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")`. ### Production (modified) - `src/gps_denied_onboard/helpers/__init__.py` — re-exports `iso_ts_now` through the helpers package facade (alphabetical insertion in both the import block and `__all__`). - `src/gps_denied_onboard/components/c6_tile_cache/cache_budget_enforcer.py` — import path `c6_tile_cache._timestamp` → `helpers.iso_timestamps` (one-line edit; call-sites untouched). - `src/gps_denied_onboard/components/c6_tile_cache/postgres_filesystem_store.py` — same path swap (2 call-sites untouched). - `src/gps_denied_onboard/components/c6_tile_cache/freshness_gate.py` — same path swap (2 call-sites untouched). - `src/gps_denied_onboard/components/c7_inference/onnx_trt_ep_runtime.py` — removed local `def _iso_ts_now`; added top-of-file `from gps_denied_onboard.helpers.iso_timestamps import iso_ts_now as _iso_ts_now` (preserves the `_iso_ts_now` symbol at 2 call-sites); removed the now-unused `from datetime import datetime, timezone`. - `src/gps_denied_onboard/components/c7_inference/thermal_publisher.py` — same pattern (1 call-site preserved); removed the now-unused `from datetime import datetime, timezone`. ### Production (deleted) - `src/gps_denied_onboard/components/c6_tile_cache/_timestamp.py` — the c6-internal consolidation shim is no longer needed; the three c6 callers now import directly from `helpers.iso_timestamps`. The Layer-1 helper supersedes the Layer-2 component-local one. ### Tests (new) - `tests/unit/test_az508_iso_timestamps.py` — 9 tests: - `test_ac1_import_and_call_returns_str` (AC-1) - `test_ac2_format_matches_canonical_regex` (AC-2 format) - `test_ac2_fromisoformat_roundtrip_yields_utc_aware_datetime` (AC-2 parseability) - `test_ac3_two_successive_calls_are_non_decreasing` (AC-3) - `test_ac4_no_other_iso_ts_now_definition_exists_in_src` (AC-4 AST-walk over `src/`) - `test_ac4_c6_and_c7_callers_import_from_helpers` (AC-4 explicit call-site sweep — 5 files) - `test_ac6_helper_uses_stdlib_only` (AC-6, stdlib whitelist) - `test_helper_is_layer_1_no_component_imports` (Constraint § Layer-1 discipline) - `test_helper_public_surface_is_minimal` (defensive: `__all__` is exactly `["iso_ts_now"]`) ### Docs (modified) - `_docs/02_document/module-layout.md` — appended `### shared/helpers/iso_timestamps` row to the helpers section. - `_docs/02_tasks/todo/AZ-508_hygiene_iso_timestamps_consolidation.md` — drift note added to the description; AC-2 regex corrected from `\+00:00$` to `Z$`. - `_docs/03_implementation/reviews/batch_48_review.md` (new) — per-batch code review (PASS). - `_docs/_process_leftovers/2026-05-11_d_cross_cve_1_opencv_pin_deferred.md` — replay-attempt timestamp + reason updated (PyPI shows `gtsam==4.2.1` but `requires_dist: numpy<2.0.0`; block unchanged). ## Acceptance Criteria Coverage | AC | Description | Status | Test | |----|-------------|--------|------| | AC-1 | Helper exists, importable, callable, returns `str` | PASS | `test_ac1_import_and_call_returns_str` | | AC-2 | Format regex match + `fromisoformat` round-trip | PASS | `test_ac2_format_matches_canonical_regex`, `test_ac2_fromisoformat_roundtrip_yields_utc_aware_datetime` | | AC-3 | Lexicographic monotonicity under normal clock | PASS | `test_ac3_two_successive_calls_are_non_decreasing` | | AC-4 | All call sites migrated; no stray definitions | PASS | `test_ac4_no_other_iso_ts_now_definition_exists_in_src`, `test_ac4_c6_and_c7_callers_import_from_helpers` | | AC-5 | FDR schema tests pass unmodified | PASS | `tests/unit/test_az272_fdr_record_schema.py` runs unchanged (35 tests pass) | | AC-6 | No new third-party dependencies | PASS | `test_ac6_helper_uses_stdlib_only` + `pyproject.toml` unchanged | | AC-7 | AZ-270 layering lint passes | PASS | `tests/unit/test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies` | ## Test Results - **Focused suite** (tests/unit/test_az508_iso_timestamps.py + test_az272_fdr_record_schema.py + tests/unit/c6_tile_cache/ + tests/unit/c7_inference/): **216 passed, 53 environment-skipped, 0 failed** in 7.97s. Environment-gated skips (Docker compose for c6 integration, CUDA / TensorRT / Jetson hardware for c7) — unchanged from Batch 47. - **Focused per-helper**: 9/9 new AZ-508 tests PASS in 1.36s. - **Layering lint**: AZ-270 + AZ-507 lints PASS (no cross-component import violations introduced). - **Lint**: `ruff check` clean on every file authored or modified by this batch. 3 pre-existing ruff findings (B905, RUF022, RUF023) in `onnx_trt_ep_runtime.py` left untouched per `coderule.mdc` "fix pre-existing lints only if in the modified area". ## Architectural Decisions 1. **Layer-1 helper supersedes the c6 internal `_timestamp.py`**. The c6-internal consolidation shipped earlier (cumulative review 28–30 F3) was a Layer-2 stop-gap pending the cross-component consolidation. With the Layer-1 helper now in place, the c6 shim is redundant and was deleted; the three c6 callers now import the cross-component helper directly. This collapses two abstractions into one. 2. **Preserve `_iso_ts_now` symbol at c7 call-sites via import alias**. The c7 modules each have 1–2 in-method `_iso_ts_now()` call-sites. Rather than edit every call-site, the new module-level `from gps_denied_onboard.helpers.iso_timestamps import iso_ts_now as _iso_ts_now` keeps the local symbol name unchanged → minimal diff per c7 file and minimal risk of accidentally missing a call-site. Matches the task spec's preferred form (Scope § Included bullet 2). 3. **Stdlib-only helper, no Clock dependency**. The helper is wall-clock metadata about when an FDR record envelope was emitted — explicitly NOT a payload field that might need an injectable Clock for tests. The task spec calls this out (Excluded § "Any change to flight_clock / wall_clock"), and the new module docstring reiterates it so future contributors do not route `age_seconds`-style fields through it. 4. **AST-walk AC-4 test instead of a grep gate**. AC-4 originally contemplated a CI-level `grep -rn "def _iso_ts_now" src/` gate. The in-repo test in `test_az508_iso_timestamps.py` uses an AST walk instead — it catches both `def _iso_ts_now` AND `def iso_ts_now` defined anywhere outside the helper, with file paths in the assertion message, and runs as part of the standard unit suite (no separate CI hook needed). The same test also enforces the consumer-side import discipline so the regression surface is "the import or the test breaks", not "a stray definition lingers". ## Carried-over Findings (from prior batches) - **F1 from cumulative review 43–45 / Batch 46 / Batch 47**: closed by this batch. `_iso_ts_now` is now in exactly one place. - **F2 from Batch 46 / Batch 47** (spec→impl drift on C7 API names for AZ-339 / AZ-340 / AZ-358 / AZ-349): **NOT addressed in this batch**; belongs to a spec-hygiene PBI to be filed before AZ-339 starts. - **D-CROSS-CVE-1 opencv pin leftover**: replay attempted at Batch 48 bootstrap; PyPI shows `gtsam==4.2.1` (newer than 4.2 referenced in the leftover) but still pins `numpy<2.0.0`. Block unchanged; leftover timestamp updated. ## New Findings (per Batch 48 code review) None. ## Jira Tracker - AZ-508 was already `In Progress` at Batch 48 start (transitioned in a prior `/autodev` session per the state file). Read-back via `getJiraIssue` confirmed `status.name == "In Progress"` before any source edits. Will transition `In Progress → In Testing` after this batch's commit lands. - Task spec archived to `_docs/02_tasks/done/AZ-508_hygiene_iso_timestamps_consolidation.md` as part of the implement skill Step 13. ## Next With AZ-508 closed, the carried-over F1 from batches 43–47 is resolved and the next C2 / C4 / C3 batches can ship without re-adding the helper. Top Batch 49 candidates (per Batch 47's "Next" list, updated): - **AZ-339** — MegaLoc + MixVPR strategies (5pt). Same skeleton as UltraVPR (B47) but two strategies; F2 spec-hygiene PBI may want to ship first. - **AZ-340** — SelaVPR + EigenPlaces + SALAD strategies (5pt). Closes the C2 alternative-backbone buffet. - **AZ-358** — C4 OpenCV/GTSAM pose estimator (5pt). Changes pace from C2-heavy streak; opens C4. - **AZ-389** — C5 internal orthorectifier for mid-flight tiles (3pt). - **Spec-hygiene PBI** for F2 (C7 API drift in AZ-339 / AZ-340 / AZ-358 / AZ-349 specs) — strongly recommended before AZ-339. Per autodev orchestrator: Batch 48 is the third batch since the last cumulative review (batches 43–45). The implement skill Step 14.5 triggers a **cumulative review batches 46–48** before Batch 49 starts.