Update autodev state and dependencies table for Phase 2 progress
ci/woodpecker/push/02-build-push Pipeline failed

- Changed autodev state sub_step to reflect new phase and task details: updated phase from 7 to 2, renamed task to 'refactor-analysis-gate', and revised detail to indicate the creation of new tasks AZ-844, AZ-845, AZ-846, and AZ-847, awaiting Phase-2 gate.
- Updated dependencies table with the latest task counts and complexity points, reflecting the addition of new tasks and the closure of AZ-777 in Jira. Total tasks now stand at 173 with 557 complexity points.
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-23 17:11:50 +03:00
parent ade0c86f2b
commit 9dc04cc677
12 changed files with 758 additions and 6 deletions
@@ -0,0 +1,15 @@
# ADR Impact — Run 02-az507-routespec-relocation
**Date**: 2026-05-23
## Scan result
`_docs/02_document/adr/` does not exist in this workspace. No `Status: Accepted` ADR files are in scope.
**Status**: `No ADRs in scope` — ADR Superseding Gate (refactor SKILL.md phase 2b.1) is satisfied trivially. No Violation rows. No Drift rows. No Aligned rows. Task creation may proceed.
## Rationale (per SKILL.md phase 2b.1 step 1)
> "If the directory does not exist or contains only the index, log `No ADRs in scope` to `RUN_DIR/analysis/adr_impact.md` and skip the rest of this gate."
This run logs the result and proceeds. The architectural rule that the run does enforce — `module-layout.md` rule 9 (AZ-507 cross-component contract surface) — is documented in `module-layout.md` and `architecture.md § Architecture Vision`, not in an ADR. The refactor strengthens that documented rule (by widening its lint enforcement in C03) rather than overturning it; no supersede path is needed.
@@ -0,0 +1,70 @@
# Refactoring Roadmap — Run 02-az507-routespec-relocation
**Date**: 2026-05-23
**Run**: `_docs/04_refactoring/02-az507-routespec-relocation/`
## Weak Points Assessment
| # | Location | Description | Impact | Proposed Solution |
|---|----------|-------------|--------|------------------|
| W1 | `src/gps_denied_onboard/components/c11_tile_manager/route_client.py:56` | Imports `RouteSpec` from `gps_denied_onboard.replay_input.tlog_route`, violating module-layout.md rule 9 (AZ-507 cross-component contract surface). | High — the next task that imports a similarly-placed DTO compounds the drift; current AZ-270 lint cannot catch it (W3). | C01: relocate the DTO to `_types/route.py`. |
| W2 | `_docs/02_document/module-layout.md` (c11_tile_manager Internal list, shared/replay_input file list) | Stale relative to on-disk reality — cycle-3 additions (`route_client.py`, `tlog_route.py`) and 7 cycle-2-era cycle-internal files are unregistered in their respective sections. | Medium — `/implement` Step 4 ownership check would BLOCK any future task touching unregistered areas. Severity escalates to High if a fourth consecutive cycle leaves it stale. | C02: refresh the c11_tile_manager Internal list, the shared/replay_input file list, and add `_types/route.py`. Defer cycle-2 carry-overs outside these sections. |
| W3 | `tests/unit/test_az270_compose_root.py:194-219` | The AC-6 lint walks `components/**/*.py` and only flags `components.<X> → components.<Y>` edges, not the full rule-9 allow-list. | Medium — rule-9 enforcement is partially honor-system; F1 is the concrete consequence. | C03: widen the AST walker to enforce the full allow-list. |
## Gap Analysis
| AC of this run | Current state | Target state |
|---|---|---|
| Rule-9 violations resolved | 1 (route_client → replay_input) | 0 |
| `module-layout.md` cycle-3 entries registered | Missing: `route_client.py`, `tlog_route.py`, plus 7 cycle-2-era omissions in two sections | All cycle-3 entries registered; 9 omissions in the c11 + replay_input sections fixed; new `_types/route.py` registered |
| AZ-270 lint scope = rule-9 scope | Narrow (one prefix only) | Full allow-list enforced |
## Phased Roadmap
This run is a single phase by intent — three small structural fixes that share the same root cause (rule-9 enforcement gap). Sequencing within the phase:
1. **C01 → first** (the structural fix). Lands `_types/route.py`, retires the violating import, keeps producer-side back-compat via re-export.
2. **C02 → second** (depends on C01 because the new `_types/route.py` entry needs the file to exist). Documentation refresh; no code touch.
3. **C03 → third** (depends on C01 because the widened lint must see a clean codebase). The new lint becomes a gate for any future PR.
| Phase | Items | Rationale |
|-------|-------|-----------|
| Phase 1 (this run) | C01, C02, C03 | All three resolve the same cumulative-review FAIL surface; bundling them ensures rule-9 enforcement is consistent across code, doc, and lint after the run. |
No Phase 2 or Phase 3. The cumulative review's "out of scope" items (cycle-2 doc carry-overs, the shared_types/route.md contract doc, `architecture_compliance_baseline.md`) belong to other tasks and are explicitly deferred — not folded into this roadmap.
## Hardening tracks
| Track | Recommendation | Rationale |
|-------|----------------|-----------|
| A — Technical Debt | Skip | The run *is* technical-debt remediation (closing a rule-9 enforcement gap). Adding a separate track would expand scope artificially. |
| B — Performance Optimization | Skip | No performance concern in scope. Relocation is identity-preserving; tests do not measure perf deltas. |
| C — Security Review | Skip | No security surface affected. `RouteSpec` carries waypoint coordinates only (already shipped to operator's tlog input); the move does not change any auth, transport, or input-validation path. |
| D — All of the above | Skip | See A/B/C. |
| E — None | **Selected (default for this run)** | All three changes are themselves the structural fix; orthogonal hardening would dilute scope. The cycle-3 retrospective list captures the broader debt items (cycle-2 carry-overs, baseline doc) for separate runs. |
This default is recorded explicitly so the user can override at the Phase 2 BLOCKING gate. If the user wants Track C (security audit on the route-extraction path) or Track A (folding the cycle-2 carry-overs into this run), the roadmap and task list will be regenerated.
## Selected items
All `Selected`:
- C01 — Relocate `RouteSpec` to `_types/route.py` (2 SP, low risk).
- C02 — Refresh `module-layout.md` cycle-3 entries (2 SP, low risk).
- C03 — Widen `test_az270_compose_root` lint to full rule-9 allow-list (2 SP, medium risk).
**Total**: 6 SP across 3 tasks. Each task is within the user-rule cap (≤ 5 SP per task; recommended 2-3).
## Applicability gate
| Recommendation | Status | Notes |
|---|---|---|
| C01 | Selected | No constraint mismatches; identity-preserving move; backward compat via re-export. |
| C02 | Selected | Doc-only; no test impact; scope-disciplined (cycle-2 carry-overs explicitly deferred). |
| C03 | Selected | Risk-flagged: widening may expose unrelated rule-9 violation. STOP-and-surface protocol applies if encountered. |
No `Rejected`, no `Experimental only`, no `Needs user decision`. The Phase 2 applicability gate passes for task creation.
## ADR-supersede gate
`No ADRs in scope` — see `adr_impact.md`. Gate satisfied; no Violation/Drift/Aligned rows.
@@ -0,0 +1,66 @@
# Research Findings — Run 02-az507-routespec-relocation
**Date**: 2026-05-23
**Mode**: guided
**Scope**: structural relocation of one DTO + module-layout doc refresh + lint widening
## Project Constraint Matrix (extracted)
| Constraint | Source | Statement |
|-----------|--------|-----------|
| AZ-507 cross-component contract surface | `_docs/02_document/architecture.md` § Architecture Vision; `_docs/02_document/module-layout.md` rule 9 | `components/<X>/*.py` may only import from `_types/*`, `_types.inference_errors`, `helpers/*`, `config`, `logging`, `fdr_client`, `clock`, `frame_source` (interface only), and its own subpackage. |
| Cross-component DTOs live in `_types/*` | `_types/geo.py`, `_types/tile.py`, `_types/inference.py`, `_types/calibration.py`, `_types/pose.py`, `_types/state.py`, `_types/nav.py`, `_types/manifests.py`, `_types/vpr.py`, `_types/matcher.py`, `_types/matching.py`, `_types/rerank.py`, `_types/thermal.py`, `_types/emitted.py`, `_types/fc.py` (15 existing DTO files) | The user-confirmed precedent. Every shared DTO sits under `_types/`. The pattern is explicit at the package level: `_types/__init__.py` is just a marker (`"""Cross-component DTOs (type-only stubs)."""`). |
| AZ-270 lint coverage | `_docs/02_document/module-layout.md` rule 9 (cites `test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategies`) | Documented as enforced by the lint; F3 of cycle-3 cumulative review confirms the lint scope is narrower than the rule. |
| Frozen + slots DTO contract | AZ-355 AC-2 (cited in `_types/geo.py`) | DTOs that cross component boundaries must use `frozen=True, slots=True` to prevent mutation-through-aliasing. |
| Epic AZ-835 acceptance criteria | `_docs/02_tasks/done/AZ-835_e2e_real_flight_validation_epic.md` and child task specs (AZ-836..AZ-840) | The replay-flow behaviour must remain functionally identical after the refactor — RouteSpec waypoint extraction, satellite-provider POST, e2e orchestrator behaviour. |
| Backward-compat for test imports | tests/* (5 files import RouteSpec from `replay_input.tlog_route` directly) | Test code is allowed to use module-level paths; only `components/<X>/*.py` is gated by rule 9. Re-export from `tlog_route.py` keeps test imports stable, so updating tests is hygiene rather than correctness. |
## Current state analysis
`RouteSpec` is currently defined at `gps_denied_onboard.replay_input.tlog_route:54-79` and re-exported from `gps_denied_onboard.replay_input` (`__init__.py:34`). The producer (`extract_route_from_tlog` at `tlog_route.py:82`) lives alongside the DTO in the same module — that part is correct (the function is a `replay_input/` concern, not a `_types/` concern). The DTO itself is consumed across a component boundary (c11) which makes it a cross-component DTO by behaviour, but its file home does not reflect that. Every other cross-component DTO in the codebase lives under `_types/*`. The asymmetry is the F1 finding.
**Strengths to preserve**:
- `RouteSpec` is `frozen=True, slots=True` — already AZ-355-compliant; the move does not relax this.
- The extractor (`extract_route_from_tlog`) is correctly placed in `replay_input/` and uses the DTO via local import; this composition is preserved post-move.
- Tests cover both producer-side (14 unit tests) and consumer-side (full route_client AC suite plus integration). Phase 6 has a strong safety net.
**Weakness being corrected**:
- The DTO's file home does not match its semantic role (cross-component contract surface).
- The AZ-270 lint cannot detect the asymmetry because its check is narrower than the rule it claims to enforce.
## Alternative approaches considered
| # | Approach | Verdict | Why |
|---|----------|---------|-----|
| 1 | Move `RouteSpec` to `_types/route.py` (the recommended path) | **Selected** | Matches the user-confirmed precedent (`_types/inference.py`, `_types/tile.py`, etc.), satisfies rule 9 at c11's import site, identity-preserving (Python class object identity is preserved across imports), behaviour-neutral. |
| 2 | Move `RouteSpec` to `_types/replay.py` (group with other replay-related types if they appear later) | Rejected | No other replay-related shared DTOs exist today. Naming the file `route.py` mirrors the naming convention of other `_types/*.py` files (one DTO topic per file: `geo`, `tile`, `pose`, `nav`, etc.). Premature speculative grouping. |
| 3 | Move `RouteSpec` to `_types/contracts/route.py` (introduce a sub-namespace) | Rejected | `_types/` is currently flat. Introducing a sub-namespace for one DTO is over-engineering and would require updating the rule-9 allow-list (`_types/*` already matches recursively in the lint, but the documentation pattern would diverge). |
| 4 | Amend rule 9 to admit `replay_input.tlog_route` as an allowed import for components | Rejected (architecture-change path; option D in the original FAIL gate) | The user explicitly chose option B (mechanical refactor) over option D (rule amendment). Option 4 would weaken rule 9 and break the layering invariant, which is why the user rejected it. |
| 5 | Keep `RouteSpec` in `replay_input/tlog_route.py` and add a custom shim under `_types/` that re-exports it (no real move) | Rejected | Cosmetic — does not satisfy the underlying rule because the c11 import would still resolve to a `replay_input` module via the shim. The lint's correct widened form (C03) would still flag the original location as the canonical home. |
**Selected: Approach 1.** No library replacement, no SDK addition, no framework introduction. Therefore the `context7` per-mode verification gate (SKILL phase 2a) is not triggered — the gate fires only for replacement libraries/SDKs/frameworks/services. This is a structural code move within the existing codebase.
## API capability verification
**Not applicable.** The refactor introduces no new library, SDK, framework, or service. The "replacement" is the file home of a dataclass within the same Python package. No `context7` lookup is required (the gate is explicit: "for every replacement library/SDK/framework"). No MVE is required (no external API to verify). The project's pinned mode is unchanged because no mode exists to pin — it's a pure-Python dataclass relocation.
## Constraint-fit table
| Recommendation | Pinned mode/config | Constraints checked | API capability evidence | Mismatches/disqualifiers | Status |
|---|---|---|---|---|---|
| C01 — relocate `RouteSpec` to `_types/route.py` | N/A — Python dataclass, no library mode | AZ-507 rule 9, frozen+slots invariant (AZ-355), Epic AZ-835 ACs, test backward compat | N/A — no external API | None | Selected |
| C02 — refresh `module-layout.md` | N/A — documentation | AZ-507 rule 9 (the rule the doc enforces), scope discipline (cycle-2 carry-overs deferred to a separate task) | N/A | None | Selected |
| C03 — widen AZ-270 lint | N/A — internal AST walker, stdlib `ast` module | Rule-9 allow-list as the predicate; preserves existing AC-6 narrow check as a strict subset | N/A — stdlib only | Risk: may expose unrelated rule-9 violation (mitigated by STOP-and-surface protocol if encountered) | Selected |
All three changes are `Selected`. No `Rejected`, `Experimental only`, or `Needs user decision` rows — the applicability gate (Phase 2 BLOCKING) passes for all three.
## References
- `_docs/02_document/architecture.md` § Architecture Vision (AZ-507 cross-component contract surface)
- `_docs/02_document/module-layout.md` rule 9 (AZ-507 enforcement)
- `_docs/03_implementation/cumulative_review_batches_104-109_cycle3_report.md` (F1, F2, F3 — the source findings)
- `src/gps_denied_onboard/_types/geo.py` (canonical pattern for `_types/<topic>.py`)
- `src/gps_denied_onboard/_types/inference.py`, `_types/tile.py`, `_types/calibration.py` (additional precedent — user-cited examples)
- `tests/unit/test_az270_compose_root.py:194-219` (current narrow lint)
@@ -0,0 +1,100 @@
# Baseline Metrics — Run 02-az507-routespec-relocation
**Date**: 2026-05-23
**Run**: `_docs/04_refactoring/02-az507-routespec-relocation/`
**Mode**: guided
**Source**: cycle-3 cumulative review (`_docs/03_implementation/cumulative_review_batches_104-109_cycle3_report.md`) — F1, F2, F3
**Scope**: mechanical relocation of cross-component DTO + module-layout doc refresh + AZ-270 lint scope expansion
## Why a minimal baseline is appropriate for this run
The standard Phase-0 baseline metric grid (overall coverage, complexity, code smells, performance, dependencies, build time) is **not the right instrument** for this refactoring run. The work is a structural relocation of one frozen dataclass + a documentation refresh + a lint widening. Behaviour does not change; performance does not change; coverage does not change; dependency count does not change. A LOC-and-cyclomatic-complexity baseline would record near-zero deltas and would obscure the actual signal — whether the architectural rule (`module-layout.md` rule 9) is satisfied after the run.
What matters here, and is captured below, is:
1. The **structural baseline**: one rule-9 violation today (F1).
2. The **test baseline**: which tests cover the affected import paths and that they pass at HEAD (the safety net for Phase 4).
3. The **doc baseline**: which artifacts are stale (F2) and what "complete" looks like.
4. The **lint baseline**: what AZ-270 currently catches vs. what rule 9 says it should catch (F3).
Phases 5/6 verify that (a) the structural baseline goes from 1 → 0 rule-9 violations, (b) every test still passes, (c) the doc baseline is reconciled, and (d) the lint baseline is widened.
## 1. Structural baseline (rule-9 violations)
Source of truth: `_docs/02_document/module-layout.md` rule 9 (AZ-507 cross-component contract surface).
| # | File | Importer (Component) | Imported (Module) | Allow-listed for importer? |
|---|------|----------------------|-------------------|----------------------------|
| 1 | `src/gps_denied_onboard/components/c11_tile_manager/route_client.py:56` | `c11_tile_manager` | `gps_denied_onboard.replay_input.tlog_route` (`RouteSpec`) | **NO**`replay_input` not in c11's allow-list |
Search method: `rg "^from gps_denied_onboard\." src/gps_denied_onboard/components` filtered against the allow-list (`_types/*`, `_types.inference_errors`, `helpers/*`, `config`, `logging`, `fdr_client`, `clock`, `frame_source`).
**Target post-run**: 0 violations.
## 2. Test baseline (safety net for Phase 4)
Files that import `RouteSpec`, `SatelliteProviderRouteClient`, or `RouteSeedResult` (i.e. the symbols the relocation touches):
**Production source** (must be updated):
- `src/gps_denied_onboard/components/c11_tile_manager/route_client.py` — defines the import to be re-pointed
- `src/gps_denied_onboard/components/c11_tile_manager/__init__.py` — public API re-exports (no import path change)
- `src/gps_denied_onboard/replay_input/tlog_route.py` — defines `RouteSpec` today (will lose the local definition, gain an import + alias)
- `src/gps_denied_onboard/replay_input/__init__.py` — public API re-exports (will re-export from `_types.route` instead of `tlog_route`)
**Tests** (verify still pass; update imports only if they reach into the pre-relocation internal path `replay_input.tlog_route` directly):
- `tests/unit/replay_input/test_tlog_route.py` (14 tests; producer-side)
- `tests/unit/c11_tile_manager/test_route_client.py` (consumer-side unit tests)
- `tests/integration/c11_tile_manager/test_route_client_e2e.py` (integration)
- `tests/e2e/replay/conftest.py`
- `tests/e2e/replay/_operator_pre_flight.py`
- `tests/e2e/replay/test_operator_pre_flight_driver.py`
- `tests/e2e/replay/test_operator_pre_flight_integration.py`
- `tests/e2e/replay/_e2e_orchestrator.py`
- `tests/e2e/replay/test_e2e_orchestrator_unit.py`
- `tests/fixtures/derkachi_c6/seed_route.py`
**HEAD test status (asserted, not measured here)**: per cycle-3 batch reports 104, 106, 107, 108, 108b, 109, every committed batch ended with passing tests at the per-batch full run. The cumulative review (FAIL on F1) is a static-analysis verdict, not a test-run verdict — no test failures are attributable to F1 today. Phase 4 will run the affected test files first; Phase 6 runs the project's full test gate per the existing-code flow's test policy.
## 3. Doc baseline (F2 surface area)
`_docs/02_document/module-layout.md` is stale relative to on-disk reality. The following entries diverge today:
**c11_tile_manager — Internal list** lists 2 files (`satellite_provider_downloader.py`, `satellite_provider_uploader.py`); on-disk has 8 internal files plus `route_client.py` (cycle-3 NEW). Missing entries: `_types.py`, `config.py`, `errors.py`, `idempotent_retry.py`, `signing_key.py`, `tile_downloader.py`, `tile_uploader.py`, `route_client.py`.
**shared/replay_input file list** lists `__init__.py`, `interface.py`, `tlog_video_adapter.py`, `auto_sync.py`, `tests/`; on-disk adds `errors.py` (cycle-2 carry), `tlog_ground_truth.py` (cycle-2 carry), `tlog_route.py` (cycle-3 NEW). After the relocation, `tlog_route.py` stays (it still owns `extract_route_from_tlog`); `_types/route.py` is added.
**Cycle-2 carry-overs** still unaddressed (out of this run's scope unless the user expands it; surfaced in F2 of the cumulative review):
- `replay_api/` package (7 files; needs Per-Component Mapping entry).
- `cli/render_map.py`, `cli/replay_api_entrypoint.py` (need Shared section entries).
- `helpers/gps_compare.py`, `helpers/accuracy_report.py` (need Shared section entries).
**Target post-run** (in-scope): c11_tile_manager Internal list refreshed (route_client + the 7 long-standing internals); shared/replay_input file list refreshed (tlog_route + tlog_ground_truth + errors); new `_types/route.py` registered. Cycle-2 carry-overs are deferred to a separate doc-only task unless user expands scope.
## 4. Lint baseline (F3)
`tests/unit/test_az270_compose_root.py:194-219` (`test_ac6_only_compose_root_imports_concrete_strategies`) walks `src/gps_denied_onboard/components/**/*.py` and flags only edges whose `node.module` starts with `gps_denied_onboard.components.` AND whose leaf-component is not the importer's component. The full rule-9 allow-list (8 prefixes plus `frame_source` interface-only restriction) is NOT enforced.
**Concrete miss demonstrated by F1**: the c11 → replay_input edge passes this lint silently because `replay_input` is not under `components/`.
**Target post-run** (in-scope): expand the lint to enforce rule 9's full allow-list. Remaining design choices (whether to allow `frame_source` non-interface modules, whether to treat `runtime_root` exception case-sensitively) are addressed in C03's task spec.
## 5. Functionality inventory
This run touches no public-feature surfaces. The DTO `RouteSpec` continues to be re-exported from `gps_denied_onboard.replay_input` (the public package), so consumers using `from gps_denied_onboard.replay_input import RouteSpec` see no change. Consumers reaching into `replay_input.tlog_route` directly (an internal-module path) will need their imports updated — this set is small and lives entirely under `tests/`. There is no operator-facing CLI / endpoint / config schema change.
## Self-verification
- [x] RUN_DIR created with auto-incremented prefix (`02-az507-routespec-relocation`; previous: `01-testability-refactoring`)
- [x] All metric categories reasoned about — standard categories noted N/A with reason; relevant baselines (structural, test, doc, lint) captured
- [x] Functionality inventory complete (no functionality change in scope)
- [x] Measurements are reproducible (rg + glob commands documented)
## BLOCKING — Phase 0 gate
Awaiting user confirmation of:
1. The minimal-baseline rationale (no LOC/coverage/perf metrics for a mechanical relocation).
2. The structural / test / doc / lint baseline above as the "before" state Phase 6 will compare against.
3. The scope decision: cycle-2 doc carry-overs are **OUT** of this run unless explicitly expanded.
If confirmed, Phase 1 produces `RUN_DIR/list-of-changes.md` (already drafted alongside this file as the guided-mode input).
@@ -0,0 +1,59 @@
# Logical Flow Analysis — Run 02-az507-routespec-relocation
**Date**: 2026-05-23
**Scope**: data path of `RouteSpec` from producer (replay_input) to consumer (c11_tile_manager) and back to operator-pre-flight orchestration
## Documented flow (from architecture / Epic AZ-835 spec)
```
tlog (binary) ──► extract_route_from_tlog (replay_input/tlog_route)
└─► RouteSpec (frozen dataclass, immutable)
└─► SatelliteProviderRouteClient.seed_region (components/c11_tile_manager/route_client)
└─► RouteSeedResult ─► satellite-provider POST /api/satellite/route
─► (HTTP success) tile coverage primed
```
## Trace through code (HEAD)
| Step | File | Behaviour |
|------|------|-----------|
| 1. Produce | `replay_input/tlog_route.py:166` (`extract_route_from_tlog` return) | Constructs `RouteSpec(waypoints, suggested_region_size_meters, source_tlog, source_segment, total_distance_meters)` |
| 2. Hold | (consumer-side variable) | `RouteSpec` instance is `frozen=True, slots=True` — cannot be mutated by either side |
| 3. Consume | `components/c11_tile_manager/route_client.py:56` import | Reads `route.waypoints`, `route.suggested_region_size_meters` to build the satellite-provider POST body |
| 4. Validate | `components/c11_tile_manager/route_client.py` (RouteValidationError path) | Validates `route` shape against c11's RouteValidationError preconditions; pure read access |
| 5. Carry | `tests/e2e/replay/_operator_pre_flight.py:72` import | Operator-pre-flight harness threads the same RouteSpec through the e2e flow |
## Identity & equality semantics post-relocation
The relocation moves the **definition** of `RouteSpec` from `gps_denied_onboard.replay_input.tlog_route` to `gps_denied_onboard._types.route`. After the move:
- Python's class identity is preserved across imports — `gps_denied_onboard.replay_input.tlog_route.RouteSpec is gps_denied_onboard._types.route.RouteSpec``True` (the same class object is bound at two names).
- `dataclasses.is_dataclass(...)`, `isinstance(...)`, `__eq__`, and `__hash__` are unchanged because they derive from the class object, not from the import path.
- `frozen=True, slots=True` semantics are preserved (no per-instance dict, no setattr after construction).
- The `__module__` attribute of the class becomes `gps_denied_onboard._types.route` (not `gps_denied_onboard.replay_input.tlog_route`). This is observable via:
- `pickle` (module path is encoded; pickled objects from before the move would fail to unpickle after — but no production code path pickles `RouteSpec`; checked: no `pickle.dumps(route)` or equivalent in src/ or tests/)
- `repr(RouteSpec)` (shows `<class 'gps_denied_onboard._types.route.RouteSpec'>` post-move)
- `RouteSpec.__module__` (changes — but no test inspects this; checked: no `__module__` assertion in tests/)
## Contradictions / data-loss / wasted-work checks
Per Phase 1 step 1c categories:
- **Fixed-size vs dynamic-size assumptions**: N/A — `RouteSpec.waypoints` is `tuple[tuple[float, float], ...]`, length is data-driven (1 to `max_waypoints`). No fixed-size pad/truncate path.
- **Loop scoping**: N/A — RouteSpec is a leaf DTO, no internal loop semantics.
- **Wasted computation**: N/A — relocation does not change call sites.
- **Silent data loss**: N/A — relocation is a name-only change at the type level; the values stored in `RouteSpec` instances are unchanged.
- **Doc drift**: confirmed by F2 of cumulative review — `module-layout.md` diverges from on-disk reality. Remediation is in scope as C02.
## Cross-component edge analysis (rule-9 audit, post-relocation)
| Edge | Importer | Imported | Allow-listed? | Status |
|------|----------|----------|---------------|--------|
| Pre-relocation | `c11_tile_manager/route_client.py` | `replay_input.tlog_route.RouteSpec` | NO | violation (F1) |
| Post-relocation | `c11_tile_manager/route_client.py` | `_types.route.RouteSpec` | YES (`_types/*` is in c11's allow-list) | compliant |
No other rule-9 cross-component edge becomes a violation as a side effect of this move. The producer side (`replay_input/tlog_route.py``_types/route.py`) is a coordinator → DTO edge, which is always allowed (DTOs have no allow-list restriction; they're consumed everywhere).
## Conclusion
The relocation is a pure structural change with no behavioural, performance, or contract-shape side effects. The only observable difference is `RouteSpec.__module__`, which is not asserted on by any code path. Phase 4 execution can proceed as a mechanical move; Phase 6 verification is satisfied if all tests pass and the rule-9 audit reports zero violations.
@@ -0,0 +1,71 @@
# 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 5479); 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.