# 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.
