# 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//*.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/` 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/` → `_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..*`, `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.` 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..*` 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.