mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 17:01:13 +00:00
[AZ-526] Consolidate _iso_ts_from_clock into helpers/iso_timestamps
Closes cumulative review 46-48 F1 (Medium) + F3 (Low). Adds iso_ts_from_clock(clock) alongside iso_ts_now() in the Layer-1 helper; migrates four duplicate definitions in c2_vpr (net_vlad, ultra_vpr, _faiss_bridge) and c12_operator_orchestrator (operator_reloc_service). Output format flipped +00:00 -> Z to align with iso_ts_now() and the canonical FDR _TS fixture (FDR schema test passes unmodified). 18 helper AC tests + 186 sibling tests pass; ruff clean. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
# Batch 49 — Implementation Report (Cycle 1)
|
||||
|
||||
**Tasks**: AZ-526 (Hygiene — consolidate `_iso_ts_from_clock` into `helpers/iso_timestamps.py`)
|
||||
**Date**: 2026-05-13
|
||||
**Cycle**: 1
|
||||
**Status**: COMPLETE (review verdict: PASS_WITH_WARNINGS, one Low / Scope drift finding)
|
||||
|
||||
## What was done
|
||||
|
||||
Added a second public function `iso_ts_from_clock(clock: Clock) -> str` to the Layer-1 helper module that AZ-508 (Batch 48) introduced. Migrated the four duplicate `_iso_ts_from_clock` definitions to import from the helper, closing cumulative review 46-48 Findings F1 (Medium / Maintainability) and F3 (Low / Maintainability).
|
||||
|
||||
### Files added
|
||||
- _None._ Reused `src/gps_denied_onboard/helpers/iso_timestamps.py` and `tests/unit/test_az508_iso_timestamps.py`.
|
||||
|
||||
### Files changed (8)
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/gps_denied_onboard/helpers/iso_timestamps.py` | Added `iso_ts_from_clock(clock)`; extended module docstring; added `TYPE_CHECKING`-guarded `Clock` import. |
|
||||
| `src/gps_denied_onboard/helpers/__init__.py` | Re-exported `iso_ts_from_clock`; added to `__all__`. |
|
||||
| `src/gps_denied_onboard/components/c2_vpr/net_vlad.py` | Deleted local module-level `def _iso_ts_from_clock`; added `from gps_denied_onboard.helpers.iso_timestamps import iso_ts_from_clock as _iso_ts_from_clock`. |
|
||||
| `src/gps_denied_onboard/components/c2_vpr/ultra_vpr.py` | Same as `net_vlad.py`. |
|
||||
| `src/gps_denied_onboard/components/c2_vpr/_faiss_bridge.py` | Rewrote `FaissBridge._iso_ts_from_clock` body to `return iso_ts_from_clock(self._clock)`; deleted stale meta-rule comment; added helper import. |
|
||||
| `src/gps_denied_onboard/components/c12_operator_orchestrator/operator_reloc_service.py` | Same as `_faiss_bridge.py`; also removed now-unused `from datetime import datetime, timezone`. |
|
||||
| `tests/unit/test_az508_iso_timestamps.py` | Added 7 AC tests (AZ-526 AC-1..AC-4); extended `_helper_uses_stdlib_only` to allow `gps_denied_onboard.clock` (Layer-1) and `typing`; updated `_public_surface_is_minimal` to expect `["iso_ts_from_clock", "iso_ts_now"]`; renamed AC-6 test for clarity; added AST scan for stray module-level `iso_ts_from_clock` defs (distinct from the AZ-508 `iso_ts_now` scan). |
|
||||
| `_docs/02_document/module-layout.md` | Updated `shared/helpers/iso_timestamps` entry to describe both functions and list the c2_vpr / c12_operator_orchestrator consumers. |
|
||||
| `_docs/02_tasks/_dependencies_table.md` | Added AZ-526 row (2pt, deps AZ-508 + AZ-398); updated date header and totals. |
|
||||
|
||||
## AC coverage
|
||||
|
||||
| AC | Verified by | Status |
|
||||
|----|-------------|--------|
|
||||
| AC-1 (import + callable) | `test_az526_ac1_import_and_call_returns_str` | PASS |
|
||||
| AC-2 (Z-suffix nanosecond regex) | `test_az526_ac2_format_matches_canonical_ns_regex`, `test_az526_ac2_fromisoformat_truncated_roundtrip_yields_utc` | PASS |
|
||||
| AC-3 (nanosecond accuracy) | `test_az526_ac3_nanosecond_fraction_preserved_verbatim`, `test_az526_ac3_sub_second_distinguishability` | PASS |
|
||||
| AC-4 (call-site migration) | `test_ac4_az526_no_module_level_iso_ts_from_clock_outside_helper`, `test_az526_ac4_all_four_call_sites_import_from_helper`, `test_az526_ac4_no_module_level_local_iso_ts_from_clock_body_in_callers` | PASS (with Spec-Drift caveat — see below) |
|
||||
| AC-5 (FDR schema tests unmodified) | `tests/unit/test_az272_fdr_record_schema.py` re-run unmodified (33 tests pass) | PASS |
|
||||
| AC-6 (AZ-270 lint passes) | `tests/unit/test_az270_compose_root.py` (8 tests pass) | PASS |
|
||||
|
||||
## Test results
|
||||
|
||||
- `tests/unit/test_az508_iso_timestamps.py` — **18 / 18 PASS** (8 pre-existing + 10 added/updated for AZ-526).
|
||||
- `tests/unit/test_az272_fdr_record_schema.py` — **33 / 33 PASS** unmodified.
|
||||
- `tests/unit/test_az270_compose_root.py` — **8 / 8 PASS**.
|
||||
- Component sibling tests for the 4 migrated files (`c2_vpr/`, `c12_operator_orchestrator/`) plus adjacent suites — **186 / 186 PASS** (`pytest -k "az338 or az337 or az489 or az490 or faiss or operator_reloc or net_vlad or ultra_vpr or _faiss"`).
|
||||
- `ruff check` on all 7 changed source/test files — clean.
|
||||
|
||||
## Architectural decisions
|
||||
|
||||
1. **`Clock` import via `TYPE_CHECKING`** — `iso_ts_from_clock` only needs `Clock` for its parameter annotation. Importing it under `TYPE_CHECKING` keeps the runtime dependency surface stdlib-only, simplifies the AC-6 lint, and avoids any risk of importing component code at module load.
|
||||
2. **Output format flip `+00:00` → `Z`** — closes cumulative-46-48 F3. The FDR `ts` field is typed `str` with no schema-level format validation, and the canonical `_TS` fixture in `test_az272_fdr_record_schema.py` already uses `Z`. AC-5 is the safety net; verified by re-running the unmodified schema test suite.
|
||||
3. **Module-level callers preserved their local symbol via `as _iso_ts_from_clock`** — call-site shape unchanged, so the existing `_emit_fdr` / `_publish` builders read identically. Instance-method callers (FaissBridge, OperatorReLocService) became one-line delegations rather than imports because the call-site is `self._iso_ts_from_clock()` — keeping the method preserves the class's encapsulated public-ish surface in tests that mock it.
|
||||
4. **Single test file (`test_az508_iso_timestamps.py`)** — the helper module is one cohesive Layer-1 concern; a separate `test_az526_*.py` file would have fragmented the AC walk across two tracker IDs without semantic benefit. The AZ-526 tests are sectioned under a `# AZ-526` divider.
|
||||
|
||||
## Spec drift noted (carried into review F1)
|
||||
|
||||
AC-4 of the AZ-526 task spec literally reads "zero matches in `c2_vpr/`, `c12_operator_orchestrator/`", but the same spec's § Scope and § Constraints both mandate that the two instance methods "become one-line delegations" — meaning `def _iso_ts_from_clock(self) -> str` remains inside `FaissBridge` and `OperatorReLocService`. Implementation followed Scope/Constraints (delegations preserved). The AST test `test_ac4_az526_no_module_level_iso_ts_from_clock_outside_helper` enforces the **spirit** of AC-4 by scanning only top-level `FunctionDef` nodes; nested class methods are not flagged. Logged as Low / Scope finding F1 in `batch_49_review.md`; no code change.
|
||||
|
||||
## Cumulative review obligation
|
||||
|
||||
This batch closes cumulative-46-48 F1 and F3. F2 (`_assert_engine_output_dim` near-duplication between `net_vlad.py` and `ultra_vpr.py`) is unchanged — leaving for a future hygiene PBI when a third strategy joins (AZ-339 batch). No new cumulative review window required after this batch (next one is at K=3 → batches 49-51).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Code Review Report
|
||||
|
||||
**Batch**: 49 — AZ-526 (Hygiene — consolidate `_iso_ts_from_clock`)
|
||||
**Date**: 2026-05-13
|
||||
**Verdict**: PASS_WITH_WARNINGS
|
||||
|
||||
## Findings
|
||||
|
||||
| # | Severity | Category | File:Line | Title |
|
||||
|---|----------|----------|-----------|-------|
|
||||
| 1 | Low | Scope | `_docs/02_tasks/todo/AZ-526_hygiene_iso_ts_from_clock_consolidation.md` § AC-4 | AC-4 wording contradicts § Scope (instance-method delegations) |
|
||||
|
||||
### Finding Details
|
||||
|
||||
**F1: AC-4 wording contradicts § Scope (instance-method delegations)** (Low / Scope)
|
||||
|
||||
- Location: `_docs/02_tasks/todo/AZ-526_hygiene_iso_ts_from_clock_consolidation.md`, AC-4 vs § Scope / § Constraints.
|
||||
- Description: AC-4 says `grep "def _iso_ts_from_clock|def iso_ts_from_clock" src/` must yield "matches only inside `helpers/iso_timestamps.py`; zero matches in `c2_vpr/`, `c12_operator_orchestrator/`". But § Scope and § Constraints both explicitly mandate that `FaissBridge._iso_ts_from_clock(self)` and `OperatorReLocService._iso_ts_from_clock(self)` "become one-line delegations" — meaning two `def _iso_ts_from_clock` lines remain inside class bodies. Strict reading of AC-4 fails; reading aligned with Scope/Constraints passes.
|
||||
- Implementation followed Scope/Constraints (delegations preserved). The AST-based test `test_ac4_az526_no_module_level_iso_ts_from_clock_outside_helper` enforces the spirit of AC-4 by scanning only top-level `FunctionDef` nodes (so nested ClassDef methods are not flagged).
|
||||
- Suggestion: amend AC-4 to read "Then matches at module level appear only inside `helpers/iso_timestamps.py`; zero free-function matches in `c2_vpr/`, `c12_operator_orchestrator/`. Instance-method one-line delegations are permitted per § Scope." Recorded as drift; no code change needed.
|
||||
- Task: AZ-526.
|
||||
|
||||
## Phase Summary
|
||||
|
||||
### Phase 1 — Context Loading
|
||||
Read task spec AZ-526, cumulative review 46-48 (F1, F3), `_docs/02_document/module-layout.md` § `shared/helpers/iso_timestamps`, and AZ-508 precedent. Mapped 8 changed files (2 helper, 4 component, 1 test, 2 doc) to AZ-526.
|
||||
|
||||
### Phase 2 — Spec Compliance
|
||||
- AC-1: `iso_ts_from_clock` importable from helper. **PASS** (`test_az526_ac1_import_and_call_returns_str`).
|
||||
- AC-2: Format matches `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{9}Z$`. **PASS** (`test_az526_ac2_format_matches_canonical_ns_regex`, `test_az526_ac2_fromisoformat_truncated_roundtrip_yields_utc`).
|
||||
- AC-3: Nanosecond fraction preserved verbatim for `ns=1_234_567_890_123_456_789`. **PASS** (`test_az526_ac3_nanosecond_fraction_preserved_verbatim`, `test_az526_ac3_sub_second_distinguishability`).
|
||||
- AC-4: All four call sites import from helper; no module-level stray defs. **PASS** with F1 drift (see above). Tests `test_ac4_az526_no_module_level_iso_ts_from_clock_outside_helper`, `test_az526_ac4_all_four_call_sites_import_from_helper`, `test_az526_ac4_no_module_level_local_iso_ts_from_clock_body_in_callers`.
|
||||
- AC-5: `tests/unit/test_az272_fdr_record_schema.py` runs unmodified. **PASS** (33 tests, 0 modifications).
|
||||
- AC-6: `tests/unit/test_az270_compose_root.py::test_ac6` passes. **PASS** (8 tests, all green).
|
||||
|
||||
### Phase 3 — Code Quality
|
||||
- `iso_ts_from_clock` body is 4 lines (excluding docstring). SRP satisfied.
|
||||
- Each migrated instance method is now a single `return iso_ts_from_clock(self._clock)` — readable and intent-preserving.
|
||||
- Stale meta-rule comment in `_faiss_bridge.py` deleted (was: "Inlined here rather than importing a shared helper because every other component … defines the same one-liner locally" — that justification is no longer true).
|
||||
- The two module-level callers (`net_vlad.py`, `ultra_vpr.py`) import `iso_ts_from_clock as _iso_ts_from_clock`, preserving call-site shape with no behavioural change.
|
||||
- No dead code introduced; the `datetime`, `timezone` imports unused after the body deletion were removed from `operator_reloc_service.py`.
|
||||
- Tests follow AAA convention with explicit Arrange / Act / Assert markers.
|
||||
|
||||
### Phase 4 — Security Quick-Scan
|
||||
No SQL, no shell, no eval / exec, no dynamic deserialisation, no user-controlled paths. The helper is a pure datetime formatter over an injected protocol method. Out of scope.
|
||||
|
||||
### Phase 5 — Performance Scan
|
||||
The helper is O(1) — two arithmetic operations (`divmod`, `int()`) plus a `strftime` and an f-string. Functionally equivalent to the inlined version it replaces; one extra Python function-call frame per call site, dominated by the I/O cost of `FdrClient.enqueue` (the surrounding caller). Not in any hot path.
|
||||
|
||||
### Phase 6 — Cross-Task Consistency
|
||||
Single-task batch. N/A.
|
||||
|
||||
### Phase 7 — Architecture Compliance
|
||||
- **Layer direction**: `helpers/iso_timestamps.py` imports `gps_denied_onboard.clock.Clock` under `TYPE_CHECKING`. Both modules are Layer-1 per `module-layout.md` Allowed Dependencies table. The runtime `from datetime import datetime, timezone` is stdlib. No upward import. **PASS.**
|
||||
- **Public API respect**: the four callers import from `gps_denied_onboard.helpers.iso_timestamps`, which is the Public API surface declared in `module-layout.md` § `shared/helpers/iso_timestamps`. **PASS.**
|
||||
- **No new cyclic dependencies**: `helpers → clock`; `clock/interface.py` only imports `typing`. No reverse path possible. **PASS.**
|
||||
- **Duplicate symbols**: net reduction (4 module-level / instance-method copies of `_iso_ts_from_clock` → 1 canonical + 2 thin delegations). **PASS.**
|
||||
- **Cross-cutting concerns not locally re-implemented**: this IS the consolidation; the cumulative-46-48 F1 + F3 findings are closed by this batch. **PASS.**
|
||||
|
||||
## Verdict Rationale
|
||||
|
||||
One Low / Scope finding (AC wording drift relative to the spec's own Scope/Constraints) — does not block. No Critical, High, or Medium findings. Verdict: **PASS_WITH_WARNINGS**.
|
||||
Reference in New Issue
Block a user