# List of Changes

**Run**: 02-az507-routespec-relocation
**Mode**: guided
**Source**: `_docs/03_implementation/cumulative_review_batches_104-109_cycle3_report.md` (cycle-3 cumulative review, FAIL verdict, F1 + F2 + F3)
**Date**: 2026-05-23

## Summary

Resolve the cycle-3 cumulative review's FAIL verdict by (a) relocating the `RouteSpec` DTO to its rule-9-compliant home in `_types/route.py`, (b) refreshing the stale `module-layout.md` cycle-3 file inventory, and (c) widening the AZ-270 lint to enforce the full rule-9 allow-list rather than only `components → components` edges. The work is mechanical — no behaviour, no performance, no contract shape changes.

## Changes

### C01: Relocate `RouteSpec` DTO from `replay_input/tlog_route.py` to `_types/route.py`

- **File(s)**:
  - **NEW**: `src/gps_denied_onboard/_types/route.py` — owns the `RouteSpec` dataclass definition (frozen, slots, with full docstring carried over verbatim).
  - **MOD**: `src/gps_denied_onboard/replay_input/tlog_route.py` — remove the local `RouteSpec` class definition (lines 54–79); add `from gps_denied_onboard._types.route import RouteSpec` near the existing `_types.geo` import; keep `RouteSpec` in `__all__` so `from replay_input.tlog_route import RouteSpec` continues to resolve (test code uses this path; it's a re-export, not a violation).
  - **MOD**: `src/gps_denied_onboard/replay_input/__init__.py` — change line 34 to import `RouteSpec` from `gps_denied_onboard._types.route` directly (canonical), keep importing `RouteExtractionError` and `extract_route_from_tlog` from `tlog_route` (they stay there).
  - **MOD**: `src/gps_denied_onboard/components/c11_tile_manager/route_client.py:56` — change to `from gps_denied_onboard._types.route import RouteSpec` (the actual rule-9 fix). Also update the docstring snippet at file-top that reads `Takes a gps_denied_onboard.replay_input.tlog_route.RouteSpec` → `Takes a gps_denied_onboard._types.route.RouteSpec`.
  - **MOD (optional, hygiene)**: test imports — 5 test files (`tests/unit/replay_input/test_tlog_route.py:46`, `tests/unit/c11_tile_manager/test_route_client.py:49`, `tests/e2e/replay/_operator_pre_flight.py:72`, `tests/e2e/replay/test_e2e_orchestrator_unit.py:37`, `tests/e2e/replay/test_operator_pre_flight_driver.py:61`) currently import `RouteSpec` from `replay_input.tlog_route`. They continue to work via the re-export (see above). Updating them to import from `_types.route` is hygiene, not correctness; recommended but not blocking. The integration test `tests/integration/c11_tile_manager/test_route_client_e2e.py:26` imports `extract_route_from_tlog` (not `RouteSpec`) — no change needed. The lazy import in `tests/e2e/replay/conftest.py:406` and the CLI fixture `tests/fixtures/derkachi_c6/seed_route.py:80` import `extract_route_from_tlog` only — no change needed.

- **Problem**: `components/c11_tile_manager/route_client.py:56` imports `RouteSpec` from `gps_denied_onboard.replay_input.tlog_route`. Per `module-layout.md` rule 9, `components/<X>/*.py` may only import from a finite allow-list (`_types/*`, `_types.inference_errors`, `helpers/*`, `config`, `logging`, `fdr_client`, `clock`, `frame_source` interface only). `replay_input` is not in this list — it's a Layer-4 cross-cutting coordinator, and Layer-4 → Layer-4 cross-cutting edges are not declared as allowed in the layering table. The import was committed in batch 107 (AZ-838); the AZ-270 lint did not catch it because the lint walks only `components → components` edges (see C03).
- **Change**: Move the DTO definition to `_types/route.py`, where it sits among the other shared DTOs (`_types/geo.py`, `_types/tile.py`, `_types/inference.py`, etc.). Update the c11 import to point at the new location. Producer-side (`replay_input/tlog_route.py`) re-imports the DTO so its own return type, `__all__`, and existing test imports keep working — that's a coordinator importing from `_types/*`, a flow that is always allowed for non-`components/<X>` modules.
- **Rationale**: `_types/*` is the architecturally designated home for cross-component DTOs (per AZ-507; per `_docs/02_document/architecture.md` `## Architecture Vision`). Every other shared DTO already lives there. Putting `RouteSpec` there makes the c11 → DTO edge a `components/<X>` → `_types/*` edge, which is allow-listed. This matches the pattern for `_types/inference.py`, `_types/tile.py`, `_types/calibration.py`, `_types/pose.py`, etc. — the user-confirmed precedent.
- **Constraint Fit**:
  - AZ-507 cross-component contract surface — satisfied (the violating edge becomes compliant).
  - Epic AZ-835 acceptance criteria — preserved; behaviour unchanged.
  - `RouteSpec` immutability (`frozen=True, slots=True`) — preserved verbatim.
  - Backward compatibility for producer-side test imports (`from replay_input.tlog_route import RouteSpec`) — preserved via re-export.
  - No public-API / CLI / endpoint shape change — confirmed in baseline_metrics §5.
- **Risk**: low (mechanical move; identity-preserving; logical-flow analysis confirms no observable side effects beyond `__module__`, which no code asserts on).
- **Dependencies**: None.

### C02: Refresh `module-layout.md` to register cycle-3 additions + new `_types/route.py`

- **File(s)**: `_docs/02_document/module-layout.md` (single file).
- **Problem**: The cumulative review's F2 surfaces that `module-layout.md` is stale. Cycle-2 carry-overs are still unaddressed; cycle 3 added more entries that are not registered. Specifically:
  - **c11_tile_manager Internal list** is missing `_types.py`, `config.py`, `errors.py`, `idempotent_retry.py`, `signing_key.py`, `tile_downloader.py`, `tile_uploader.py`, **`route_client.py`** (cycle-3 NEW from batch 107).
  - **shared/replay_input file list** is missing `errors.py` (cycle-2 carry), `tlog_ground_truth.py` (cycle-2 carry), **`tlog_route.py`** (cycle-3 NEW from batch 106).
  - **`_types/` file list** does not yet include `route.py` (added in C01).
- **Change**: Append the missing entries to bring `module-layout.md` in sync with on-disk reality for the c11_tile_manager, replay_input, and `_types/` sections. Add `_types/route.py` to the `_types/` section with a one-line description (consistent with how the other `_types/*.py` files are listed). Cycle-2 carry-overs *outside* these three sections (`replay_api/`, `cli/render_map.py`, `cli/replay_api_entrypoint.py`, `helpers/gps_compare.py`, `helpers/accuracy_report.py`) are NOT in this run's scope — they remain on the cycle-3 retrospective list and should be addressed in a follow-up doc task that is independent of the architectural fix here.
- **Rationale**: `/implement` Step 4 (File Ownership) treats `module-layout.md` as authoritative; staleness there is a BLOCKING gate when a future task touches an unregistered area. F2 is currently Medium; the cumulative review notes severity escalates to High if a fourth consecutive cycle leaves it stale. Resolving the cycle-3 portion now keeps the fix scoped to the same surface as C01 + the route_client + tlog_route additions that triggered the cumulative review in the first place.
- **Constraint Fit**:
  - `module-layout.md` rule 9 — strengthened (the document now reflects what `_types/*` actually owns).
  - No code or behavioural change.
  - Scope discipline — does NOT pull in cycle-2 carry-overs outside the run's three sections; they are deferred to a separate task.
- **Risk**: low (doc-only; reviewable by diff; no test impact).
- **Dependencies**: C01 (the `_types/route.py` entry depends on the file existing).

### C03: Expand `test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategies` to enforce the full rule-9 allow-list

- **File(s)**:
  - **MOD**: `tests/unit/test_az270_compose_root.py:194-219` — replace the current narrow check (`node.module.startswith("gps_denied_onboard.components.")` with a different leaf-component) with a check that walks `components/**/*.py`, parses each `ImportFrom`, and for any `node.module` starting with `gps_denied_onboard.` asserts the importable target is in the rule-9 allow-list (i.e. matches one of: `gps_denied_onboard.components.<own-component>.*`, `gps_denied_onboard._types.*`, `gps_denied_onboard._types.inference_errors`, `gps_denied_onboard.helpers.*`, `gps_denied_onboard.config`, `gps_denied_onboard.logging`, `gps_denied_onboard.fdr_client`, `gps_denied_onboard.clock`, `gps_denied_onboard.frame_source` interface-only).
  - **MOD (test docstring)**: update the test's docstring to cite the full rule-9 paragraph, not just AC-6 of AZ-270.
- **Problem**: F3 of the cumulative review documents that `module-layout.md` rule 9 is described as "enforced by `test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategies`", but the lint actually checks only one of the eight allow-listed prefixes — only the `gps_denied_onboard.components.<other_component>` exclusion. Imports from `replay_input`, `replay_api`, `runtime_root`, `cli/*`, and `frame_source` non-interface modules pass silently. F1 is the concrete consequence; the next task that imports from a similarly-placed module would compound the drift.
- **Change**: Widen the AST walker to a single-branch decision: "is the imported module rooted in `gps_denied_onboard.` AND not in the rule-9 allow-list (parameterised against the importer's own component for the `components.<own>.*` clause)? → fail with a message that names the offending edge and the rule." The existing error message format (compose-root test failure) is preserved; only the predicate is widened.
- **Rationale**: Lint coverage matters more than rule wording. F3 surfaces a maintainability risk: the rule and its enforcement diverge silently. Closing the gap forecloses the F1 class of regressions at lint time, not at cumulative-review time.
- **Constraint Fit**:
  - `module-layout.md` rule 9 — enforced as documented.
  - Existing AZ-270 AC-6 — preserved (the new check is a strict superset of the old check).
  - No behaviour change in production code.
  - Self-check: running the widened lint at HEAD (before C01 lands) reproduces F1 as a lint failure; running it at the C01 + C02 tip reproduces zero violations. This is the test the run hinges on.
- **Risk**: medium — the widening will catch any *other* in-flight rule-9 violation hiding in the codebase, which could surface a second remediation task. If the widened lint exposes an unrelated violation, the implement skill should STOP and surface it for a scope decision rather than auto-bundle. Risk is reduced by the fact that rule-9 audits during code review have not flagged anything else.
- **Dependencies**: C01 must land first (otherwise the widened lint fails on the very edge C01 fixes; running tests in the order C01 → C03 means C03 sees a clean baseline). C02 ordering is independent.

## Out of scope for this run

- **Cycle-2 module-layout carry-overs** outside the three sections C02 touches (`replay_api/` Per-Component Mapping, `cli/render_map.py`, `cli/replay_api_entrypoint.py`, `helpers/gps_compare.py`, `helpers/accuracy_report.py`) — recorded as cycle-3 retrospective follow-up; needs a separate doc task with its own AZ ID.
- **Contract documentation for `RouteSpec` at `_docs/02_document/contracts/shared_types/route.md`** — the cumulative review noted this as a possible Spec-Gap follow-up. It is a documentation addition, not a refactor. Defer to whoever owns the Spec-Gap workflow; do not bundle here.
- **`architecture_compliance_baseline.md`** — separate cycle-2 retrospective action that has been outstanding for two cycles; recorded as such in the cumulative review's footer note. Out of this run's scope.
