mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 05:41:13 +00:00
Update autodev state and dependencies table for Phase 2 progress
ci/woodpecker/push/02-build-push Pipeline failed
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:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,69 @@
|
||||
# Relocate RouteSpec DTO to _types/route.py (AZ-507 rule 9 fix)
|
||||
|
||||
**Task**: AZ-845_refactor_relocate_routespec
|
||||
**Name**: Relocate `RouteSpec` from `replay_input/tlog_route.py` to `_types/route.py`
|
||||
**Description**: Resolve cycle-3 cumulative review F1 (High Architecture). Move the `RouteSpec` cross-component DTO to `_types/route.py` so the `c11_tile_manager.route_client` import becomes rule-9 compliant. Producer-side keeps backward-compat re-export so test imports do not break.
|
||||
**Complexity**: 2 SP
|
||||
**Dependencies**: None (anchor task of refactor run 02-az507-routespec-relocation)
|
||||
**Component**: `_types/` (new file `route.py`); `replay_input/` (`tlog_route.py`, `__init__.py` modify); `components/c11_tile_manager/` (`route_client.py` modify)
|
||||
**Tracker**: AZ-845 (https://denyspopov.atlassian.net/browse/AZ-845)
|
||||
**Parent Epic**: AZ-844 (Refactor 02 — RouteSpec relocation + module-layout refresh + AZ-270 lint widening)
|
||||
|
||||
Jira AZ-845 is the authoritative spec; this file is the in-workspace mirror.
|
||||
|
||||
## Problem
|
||||
|
||||
`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). Per the rule, `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. `replay_input` is not in this allow-list. Every other cross-component DTO already lives under `_types/*` (`_types/geo.py`, `_types/tile.py`, `_types/inference.py`, etc.); `RouteSpec` is the asymmetric outlier.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `RouteSpec` is defined in `src/gps_denied_onboard/_types/route.py` (frozen+slots dataclass; full docstring carried over verbatim).
|
||||
- `c11_tile_manager/route_client.py:56` imports `RouteSpec` from `gps_denied_onboard._types.route`.
|
||||
- `replay_input/tlog_route.py` continues to use `RouteSpec` internally (extractor return type) by importing from `_types.route`; keeps `RouteSpec` in `__all__` for backward-compat re-export.
|
||||
- `replay_input/__init__.py` re-exports `RouteSpec` from `_types.route` directly.
|
||||
- All existing tests pass at HEAD.
|
||||
- Rule-9 audit reports zero violations after the move.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- New file: `src/gps_denied_onboard/_types/route.py` with `RouteSpec` dataclass.
|
||||
- Modify `src/gps_denied_onboard/replay_input/tlog_route.py` (remove local definition, add import).
|
||||
- Modify `src/gps_denied_onboard/replay_input/__init__.py` (re-export from `_types.route`).
|
||||
- Modify `src/gps_denied_onboard/components/c11_tile_manager/route_client.py:56` (the rule-9 fix) plus the docstring snippet at file-top that names the source module.
|
||||
- Optional hygiene: update 5 test files that import `RouteSpec` from `replay_input.tlog_route` directly (`tests/unit/replay_input/test_tlog_route.py`, `tests/unit/c11_tile_manager/test_route_client.py`, `tests/e2e/replay/_operator_pre_flight.py`, `tests/e2e/replay/test_e2e_orchestrator_unit.py`, `tests/e2e/replay/test_operator_pre_flight_driver.py`) to import from `_types.route` for symmetry.
|
||||
|
||||
### Excluded
|
||||
|
||||
- `RouteExtractionError` does NOT relocate — it is a `replay_input/`-specific error not imported by any `components/<X>/*.py` file.
|
||||
- `extract_route_from_tlog` does NOT relocate — extraction logic is a `replay_input/` concern; only the DTO moves.
|
||||
- No contract document at `_docs/02_document/contracts/shared_types/route.md`.
|
||||
- No behaviour, performance, or contract-shape changes.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
| # | Criterion |
|
||||
|---|-----------|
|
||||
| AC-1 | `_types/route.py` contains `RouteSpec` with `@dataclass(frozen=True, slots=True)`, identical fields to the original (`waypoints`, `suggested_region_size_meters`, `source_tlog`, `source_segment`, `total_distance_meters`), and the full original docstring. |
|
||||
| AC-2 | `route_client.py:56` reads `from gps_denied_onboard._types.route import RouteSpec`; rule-9 audit reports zero violations across `components/**/*.py`. |
|
||||
| AC-3 | `replay_input/tlog_route.py` imports `RouteSpec` from `_types.route`; `extract_route_from_tlog` returns `RouteSpec`; `RouteSpec` is in `__all__` so `from replay_input.tlog_route import RouteSpec` resolves via re-export. |
|
||||
| AC-4 | `from gps_denied_onboard.replay_input import RouteSpec` resolves to the same class object as `_types.route.RouteSpec` (verified by `is` identity check in test). |
|
||||
| AC-5 | `pytest tests/unit/replay_input/test_tlog_route.py tests/unit/c11_tile_manager/test_route_client.py` passes — no failures, no skipped tests beyond pre-existing skips. |
|
||||
|
||||
## Constraints
|
||||
|
||||
- `RouteSpec` MUST remain `frozen=True, slots=True` (AZ-355 AC-2).
|
||||
- `RouteSpec.__module__` MAY change to `gps_denied_onboard._types.route` (intended observable change; no test asserts on it).
|
||||
- `from gps_denied_onboard.replay_input import RouteSpec` MUST keep working.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1 — pickle / serialization break**: confirmed by grep — no `pickle.dumps(route)` exists in `src/` or `tests/`. Risk does not materialize.
|
||||
|
||||
**Risk 2 — hidden import grep missed**: producer-side keeps `RouteSpec` in its namespace via re-import + `__all__`; lazy importers using the old path resolve correctly.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- This is the anchor of refactor run 02-az507-routespec-relocation. AZ-846 (module-layout refresh) and AZ-847 (lint widening) are blocked by this task (Jira "Blocks" links recorded).
|
||||
- After this task lands, run the rule-9 audit script (the widened lint from AZ-847 once it lands) to confirm zero violations.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Refresh module-layout.md cycle-3 entries (c11 + replay_input + _types/route)
|
||||
|
||||
**Task**: AZ-846_refactor_module_layout_cycle3
|
||||
**Name**: Refresh `module-layout.md` for cycle-3 file additions and the new `_types/route.py`
|
||||
**Description**: Resolve cycle-3 cumulative review F2 (Medium Architecture). Update the c11_tile_manager Internal list, the shared/replay_input file list, and the `_types/` section in `module-layout.md` so they match on-disk reality. Cycle-2 carry-overs OUTSIDE these three sections are explicitly out of scope (deferred to a separate doc task).
|
||||
**Complexity**: 2 SP
|
||||
**Dependencies**: AZ-845 (the new `_types/route.py` file must exist before this task can register it)
|
||||
**Component**: `_docs/02_document/module-layout.md` (single file)
|
||||
**Tracker**: AZ-846 (https://denyspopov.atlassian.net/browse/AZ-846)
|
||||
**Parent Epic**: AZ-844 (Refactor 02 — RouteSpec relocation + module-layout refresh + AZ-270 lint widening)
|
||||
|
||||
Jira AZ-846 is the authoritative spec; this file is the in-workspace mirror.
|
||||
|
||||
## Problem
|
||||
|
||||
`module-layout.md` is stale. Cycle-3 cumulative review F2 documents:
|
||||
|
||||
- **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 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 by AZ-845).
|
||||
|
||||
`/implement` Step 4 (File Ownership) treats `module-layout.md` as authoritative; staleness BLOCKS any future task touching unregistered areas. F2 is currently Medium; severity escalates to High if a fourth consecutive cycle leaves it stale.
|
||||
|
||||
## Outcome
|
||||
|
||||
- c11_tile_manager Internal list registers all 8 internals + `route_client.py`.
|
||||
- shared/replay_input file list registers `errors.py`, `tlog_ground_truth.py`, `tlog_route.py`.
|
||||
- `_types/` section registers `route.py` with a one-line description matching the convention of other `_types/*.py` entries.
|
||||
- `git diff` shows additions only to those three sections — no other section, rule, or rule-text edit.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Append cycle-3 + relevant cycle-2-carry entries to the c11_tile_manager Internal list, the shared/replay_input file list, and the `_types/` section.
|
||||
|
||||
### Excluded
|
||||
|
||||
- **Cycle-2 carry-overs OUTSIDE these sections**: `replay_api/` Per-Component Mapping entry, `cli/render_map.py`, `cli/replay_api_entrypoint.py`, `helpers/gps_compare.py`, `helpers/accuracy_report.py`. These are recorded in the cycle-3 retrospective and require a separate follow-up doc task with its own AZ ID.
|
||||
- No code changes.
|
||||
- No changes to `module-layout.md` rule numbering or rule text. Only the per-section file inventories are updated.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
| # | Criterion |
|
||||
|---|-----------|
|
||||
| AC-1 | c11_tile_manager Internal list contains all 8 existing internals (`_types.py`, `config.py`, `errors.py`, `idempotent_retry.py`, `signing_key.py`, `tile_downloader.py`, `tile_uploader.py`) plus `route_client.py`, alphabetised. |
|
||||
| AC-2 | shared/replay_input file list adds `errors.py`, `tlog_ground_truth.py`, `tlog_route.py` with one-line descriptions matching the existing convention. |
|
||||
| AC-3 | `_types/` section includes `route.py` with a one-line description of `RouteSpec` (waypoints + region size + source tlog provenance), identifying its producer (`replay_input/tlog_route.py`) and consumer (`c11/route_client.py`). |
|
||||
| AC-4 | Diff of `module-layout.md` shows edits to ONLY the three named sections; no edits to other sections, rule numbering, or rule text. |
|
||||
|
||||
## Constraints
|
||||
|
||||
- Single file modified: `_docs/02_document/module-layout.md`.
|
||||
- No tests required — documentation update.
|
||||
- Scope discipline: cycle-2 doc carry-overs outside the three sections remain deferred.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1 — scope creep into cycle-2 carry-overs**: the Excluded list is explicit; Phase-4 implementer reviews the diff against ACs and rejects entries outside the three named sections at review.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Widen test_az270_compose_root lint to enforce full rule-9 allow-list
|
||||
|
||||
**Task**: AZ-847_refactor_az270_lint_widening
|
||||
**Name**: Widen `test_ac6_only_compose_root_imports_concrete_strategies` to enforce the full rule-9 allow-list
|
||||
**Description**: Resolve cycle-3 cumulative review F3 (Medium Maintainability). Replace the AZ-270 lint's narrow `components → components` check with a full rule-9 allow-list check, so any future cross-component drift is caught at lint time rather than at cumulative-review time. Strict superset of the existing AC-6 check.
|
||||
**Complexity**: 2 SP
|
||||
**Dependencies**: AZ-845 (the widened lint must see a clean codebase to pass; running it against pre-AZ-845 HEAD is what AC-4 demonstrates as a one-time verification)
|
||||
**Component**: `tests/unit/test_az270_compose_root.py` (single file)
|
||||
**Tracker**: AZ-847 (https://denyspopov.atlassian.net/browse/AZ-847)
|
||||
**Parent Epic**: AZ-844 (Refactor 02 — RouteSpec relocation + module-layout refresh + AZ-270 lint widening)
|
||||
|
||||
Jira AZ-847 is the authoritative spec; this file is the in-workspace mirror.
|
||||
|
||||
## Problem
|
||||
|
||||
`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. Imports from `replay_input`, `replay_api`, `runtime_root`, `cli/*`, and `frame_source` non-interface modules pass silently. F1 of the cycle-3 cumulative review (the c11 → replay_input edge) is the concrete consequence.
|
||||
|
||||
`module-layout.md` rule 9 documents this lint as the enforcement mechanism for the rule. Reviewers reasonably assume the lint covers the documented allow-list; in practice it covers only one of the eight prefixes. The asymmetry is the F3 finding.
|
||||
|
||||
## Outcome
|
||||
|
||||
- `test_ac6_only_compose_root_imports_concrete_strategies` enforces the full rule-9 allow-list: a `components/<X>/*.py` ImportFrom node is allowed iff the imported module matches one of: `gps_denied_onboard.components.<X>.*` (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 — see Constraints).
|
||||
- The widened lint is a strict superset of the existing AC-6 narrow check.
|
||||
- After AZ-845 lands, the widened lint reports zero violations.
|
||||
- The test docstring cites `module-layout.md` rule 9, not just AZ-270 AC-6.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
- Modify `tests/unit/test_az270_compose_root.py` — the `test_ac6_*` test and its docstring.
|
||||
- Add a small allow-list constant at module scope (single source of truth).
|
||||
- Verify by `pytest tests/unit/test_az270_compose_root.py` after AZ-845 lands.
|
||||
|
||||
### Excluded
|
||||
|
||||
- Changes to other tests in the same file.
|
||||
- Changes to production code.
|
||||
- The `frame_source` interface-only enforcement: if AST-level disambiguation between interface and non-interface modules within `frame_source/*` is not feasible, allow-list only the explicit interface module path and reject other `frame_source.*` paths. Document in the test docstring.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
| # | Criterion |
|
||||
|---|-----------|
|
||||
| AC-1 | The lint flags any ImportFrom in `components/**/*.py` whose `module` starts with `gps_denied_onboard.` and is NOT in the rule-9 allow-list. |
|
||||
| AC-2 | Strict superset of the existing AC-6 narrow check — every cross-component edge previously flagged is still flagged. |
|
||||
| AC-3 | After AZ-845 lands, the widened lint reports zero violations. |
|
||||
| AC-4 | Against the codebase BEFORE AZ-845 (verified during implementation by running the new lint on a temp checkout of pre-relocation HEAD), the lint produces a failure naming the c11 → replay_input edge and citing rule 9. |
|
||||
| AC-5 | The test docstring cites `module-layout.md` rule 9 (AZ-507 cross-component contract surface) and lists the allow-list. |
|
||||
|
||||
## Constraints
|
||||
|
||||
- `frame_source` interface-only requirement: if AST-level disambiguation is not feasible, allow-list only the explicit interface module path. Document the chosen disambiguation strategy in the test docstring. Surface to user if the documented intent and codebase reality disagree.
|
||||
- The existing test name MAY remain (preserves AZ-270 audit trail) or be renamed; if renamed, update `module-layout.md` rule 9's enforcement-citation.
|
||||
- Single file modified: `tests/unit/test_az270_compose_root.py`. No production source change.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1 — widening exposes another rule-9 violation**: STOP-and-surface protocol. The implement skill MUST stop and present the additional violation as a scope-decision Choose to the user, NOT auto-bundle into this task. Remediation of any newly-exposed violation is a separate AZ ticket.
|
||||
|
||||
**Risk 2 — false positive on `gps_denied_onboard.frame_source` non-interface module**: documented disambiguation strategy in the test docstring. If wrong, the failure surfaces as a deterministic test failure, not silent drift; surface to user.
|
||||
@@ -0,0 +1,178 @@
|
||||
# Cumulative Code Review — Cycle 3 — Batches 104–109
|
||||
|
||||
**Date**: 2026-05-23
|
||||
**Scope**: union of files changed across cycle-3 batches 104, 106, 107, 108, 108b, 109
|
||||
**Tasks covered**: AZ-777 spec refresh + Phase 1 + Phase 2; AZ-836 (Epic AZ-835 C1); AZ-838 (Epic AZ-835 C2); AZ-839 (Epic AZ-835 C3) + 108b fixture-path fix; AZ-840 (Epic AZ-835 C4)
|
||||
**Mode**: cumulative (all 7 phases)
|
||||
**Verdict**: **FAIL** (0 Critical, 1 High, 2 Medium, 0 Low)
|
||||
**Baseline file**: `_docs/02_document/architecture_compliance_baseline.md` — **still absent** (carried over from cycle 2 retro action), no `## Baseline Delta` section emitted (see Notes)
|
||||
|
||||
## Scope of files reviewed
|
||||
|
||||
**Production source** (6 files):
|
||||
1. `src/gps_denied_onboard/components/c11_tile_manager/tile_downloader.py` — modified (b104; AZ-777 Phase 1 contract adaptation: `_LIST_PATH` / `_GET_PATH` aligned with `Program.cs:187-209`)
|
||||
2. `src/gps_denied_onboard/components/c11_tile_manager/route_client.py` — **new** (b107; ~600 LOC; `SatelliteProviderRouteClient`, `RouteSeedResult`, helpers)
|
||||
3. `src/gps_denied_onboard/components/c11_tile_manager/errors.py` — modified (b107; new `SatelliteProviderRouteError` + `RouteValidationError` + `RouteTransientError` + `RouteTerminalFailureError`)
|
||||
4. `src/gps_denied_onboard/components/c11_tile_manager/__init__.py` — modified (b107; re-exports new public surface)
|
||||
5. `src/gps_denied_onboard/replay_input/tlog_route.py` — **new** (b106; `RouteSpec`, `RouteExtractionError`, `extract_route_from_tlog`)
|
||||
6. `src/gps_denied_onboard/replay_input/__init__.py` — modified (b106; re-exports new public surface)
|
||||
|
||||
**Tests** (10 files): `tests/unit/c11_tile_manager/test_tile_downloader.py` (rewritten, b104; 14 ACs), `tests/unit/replay_input/test_tlog_route.py` (new, b106; 14 tests), `tests/e2e/satellite_provider/__init__.py` + `test_smoke.py` (new, b104; 2 tier-2 tests), `tests/e2e/replay/_operator_pre_flight.py` (new, b108; ~430 LOC), `tests/e2e/replay/conftest.py` (modified, b108+b108b), `tests/e2e/replay/test_operator_pre_flight_driver.py` (new, b108; 11 unit tests), `tests/e2e/replay/_e2e_orchestrator.py` (new, b109; 656 LOC), `tests/e2e/replay/test_e2e_orchestrator_unit.py` (new, b109; 17 unit tests), `tests/e2e/replay/test_az835_e2e_real_flight.py` (new, b109; tier-2 integration).
|
||||
|
||||
**CLI / fixtures** (2 files): `tests/fixtures/derkachi_c6/seed_route.py` (new, b107), `scripts/mint_dev_jwt.py` (new, b104).
|
||||
|
||||
**Compose / env** (2 files): `docker-compose.test.jetson.yml` (modified, b104), `.env.test.example` (modified, b104).
|
||||
|
||||
## Findings
|
||||
|
||||
| # | Severity | Category | File | Title |
|
||||
|---|----------|----------|------|-------|
|
||||
| F1 | High | Architecture | `src/gps_denied_onboard/components/c11_tile_manager/route_client.py:56` | `RouteSpec` DTO placement violates AZ-507 cross-component contract surface |
|
||||
| F2 | Medium | Architecture | `_docs/02_document/module-layout.md` | Module layout stale — cycle-3 additions unregistered (cycle-2 carry-over worsened) |
|
||||
| F3 | Medium | Maintainability | `tests/unit/test_az270_compose_root.py:194` | `test_ac6_only_compose_root_imports_concrete_strategies` lint scope is narrower than module-layout.md rule 9 |
|
||||
|
||||
### Finding Details
|
||||
|
||||
**F1: `RouteSpec` DTO placement violates AZ-507 cross-component contract surface** (High / Architecture)
|
||||
|
||||
- Location: `src/gps_denied_onboard/components/c11_tile_manager/route_client.py:56`
|
||||
- Description: `route_client.py` (a `components/c11_tile_manager/*.py` file) imports `RouteSpec` from `gps_denied_onboard.replay_input.tlog_route`. Per `module-layout.md` rule 9 (AZ-507 cross-component contract surface):
|
||||
|
||||
> "the only places a `components/<X>/*.py` file may import are: its own subpackage (`gps_denied_onboard.components.<X>.*`), `_types/*`, `_types.inference_errors`, `helpers/*`, `config`, `logging`, `fdr_client`, `clock`, `frame_source` (interface only)."
|
||||
|
||||
`replay_input` is not in this allow-list. The architecture rationale: cross-component DTOs reach consumers through `_types/*`, not through cross-cutting coordinator packages. The current placement makes c11 (an Adapter, Layer 4) structurally depend on `replay_input` (a coordinator, Layer 4) — a Layer 4 → Layer 4 cross-cutting edge that the layering table does not declare as allowed.
|
||||
|
||||
- Impact: The dependency is **intentional and documented** — AZ-838 task spec line 19 explicitly specifies `from gps_denied_onboard.replay_input.tlog_route import RouteSpec`, and the route_client docstring acknowledges the source (`Takes a gps_denied_onboard.replay_input.tlog_route.RouteSpec (produced by AZ-836 / C1)`). But "intentional" does not equal "compliant"; the architecture rule was not amended at decompose time, and the AZ-270 lint is too narrow to catch this case (see F3). The next task that imports a similarly-placed DTO will compound the drift.
|
||||
|
||||
- Suggestion: relocate `RouteSpec` (plus `RouteExtractionError` if exported as part of the cross-component surface) to `src/gps_denied_onboard/_types/route.py`. After the move, both `c11_tile_manager.route_client` and `replay_input.tlog_route` import the DTO from `_types`, which is in both modules' allow-lists. AZ-836's `extract_route_from_tlog` continues to live in `replay_input/`; AZ-838's `SatelliteProviderRouteClient` continues to live in `c11_tile_manager/`. The behavioral surface is unchanged. Estimated complexity: 2 SP (move + update imports + verify AZ-838/AZ-836 tests + module-layout.md update).
|
||||
|
||||
- Tasks: AZ-838 (primary — owns the violating import), AZ-836 (secondary — owns the DTO definition).
|
||||
|
||||
**F2: Module layout stale — cycle-3 additions unregistered (cycle-2 carry-over worsened)** (Medium / Architecture)
|
||||
|
||||
- Location: `_docs/02_document/module-layout.md`
|
||||
- Description: cycle 3 introduced new package files that are not registered in the authoritative file-ownership map. The cycle-2 cumulative review (`98-102`) already flagged 6 unregistered cycle-2 additions (F1 there); none of those carry-overs have been resolved, and cycle 3 added more:
|
||||
- **c11_tile_manager Internal list** (currently lists `satellite_provider_downloader.py` + `satellite_provider_uploader.py`): 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).
|
||||
- **shared/replay_input file list** (currently lists `__init__.py`, `interface.py`, `tlog_video_adapter.py`, `auto_sync.py`, `tests/`): missing `errors.py` (cycle-2 carry), `tlog_ground_truth.py` (cycle-2 carry), **`tlog_route.py`** (cycle-3 NEW).
|
||||
- **Carried over from cycle-2 review** (still unregistered): `replay_api/` package (7 files), `cli/render_map.py`, `cli/replay_api_entrypoint.py`, `helpers/gps_compare.py`, `helpers/accuracy_report.py`.
|
||||
- Impact: `/implement` Step 4 (File Ownership) resolves a task's `Component` field against this file. Any future task touching the unregistered areas will hit the BLOCKING ownership check at Step 4 — the skill explicitly STOPs when the component isn't found and forbids guessing from prose. Cycle-3 batches 104–109 happened to operate inside already-listed component directories (c11_tile_manager/**, replay_input/**) so the staleness did not block them, but the next task that needs a new component or extends `replay_api/` will block.
|
||||
- Suggestion: cycle-3 Step 13 (Update Docs) should reconcile module-layout.md with on-disk reality. The minimum: refresh the c11_tile_manager Internal list, the shared/replay_input file list, and add the cycle-2 carry-over entries (replay_api Per-Component Mapping entry, cli additions, helpers additions, replay_input file list completion). Severity escalates to High if a fourth consecutive cycle leaves the file stale.
|
||||
- Tasks: AZ-838, AZ-836 (primary, cycle-3 contributors); AZ-700, AZ-697, AZ-699, AZ-701 (secondary, cycle-2 carry).
|
||||
|
||||
**F3: `test_ac6_only_compose_root_imports_concrete_strategies` lint scope is narrower than module-layout.md rule 9** (Medium / Maintainability)
|
||||
|
||||
- Location: `tests/unit/test_az270_compose_root.py:194-219`
|
||||
- Description: `module-layout.md` rule 9 documents `test_az270_compose_root.test_ac6_only_compose_root_imports_concrete_strategies` as the lint that "enforces this on every `components/**/*.py`". In practice the lint only checks for `gps_denied_onboard.components.<other_component>` import edges — it walks `components/**/*.py`, parses `ImportFrom` nodes, and flags only when `node.module.startswith("gps_denied_onboard.components.")` with a different leaf component. The full rule-9 allow-list (`_types/*`, `_types.inference_errors`, `helpers/*`, `config`, `logging`, `fdr_client`, `clock`, `frame_source` interface only) is NOT enforced. Imports from `replay_input`, `replay_api`, `runtime_root`, `cli/*`, `frame_source` non-interface modules, etc. all pass the lint silently. F1 is the concrete consequence: the c11 → replay_input import slipped through both code review and the AZ-270 lint.
|
||||
- Impact: `module-layout.md` rule 9 is documented as enforced; in practice it is partially enforced, partially honor-system. Reviewers (human or AI) reading the rule-9 paragraph reasonably assume the lint covers it; the test name and docstring reinforce that. The asymmetry is a maintainability risk — the rule and its enforcement diverge silently.
|
||||
- Suggestion: either expand `test_ac6_only_compose_root_imports_concrete_strategies` to enforce the full allow-list (one extra branch in the AST walker), or amend rule 9 to admit the additional imports the codebase actually relies on (with a documented rationale per module). The first is preferable — the rule's intent is structural, and lint coverage matters more than rule wording.
|
||||
- Tasks: cross-cutting; surface in cycle-3 retrospective.
|
||||
|
||||
## Verdict Logic
|
||||
|
||||
- 0 Critical → no FAIL trigger from Critical
|
||||
- 1 High (F1) → **FAIL trigger**
|
||||
- 2 Medium (F2, F3) → not a verdict driver
|
||||
- 0 Low
|
||||
|
||||
Result: **FAIL** — `/implement` Step 14.5 gate stops. Per `implement/SKILL.md` Step 14.5 + the auto-fix matrix, F1 (High Architecture) **escalates** rather than auto-fixes; F2 + F3 are eligible for Medium-Style auto-fix on the matrix but the High-Architecture finding alone gates the whole report. Re-run requires user direction (Choose A/B/C in the implement skill's Step 14.5 escalation block).
|
||||
|
||||
## Phase-by-Phase Notes
|
||||
|
||||
### Phase 1 — Context Loading
|
||||
|
||||
Inputs read:
|
||||
- Task specs: AZ-836 (`done/`), AZ-838 (`done/`), AZ-839 (`done/`), AZ-840 (`done/`), AZ-777 (refreshed spec; closure logged in `done/`); AZ-841 (`todo/`), AZ-842 (`todo/`); Epic AZ-835 (`todo/`).
|
||||
- Batch reports: `batch_104_cycle3_report.md`, `batch_106_cycle3_report.md`, `batch_107_cycle3_report.md`, `batch_108_cycle3_report.md`, `batch_108b_cycle3_report.md`, `batch_109_cycle3_report.md`.
|
||||
- Architecture / layout: `_docs/02_document/module-layout.md` (rule 9 + per-component sections + Layering Table); `_docs/02_document/architecture.md` (header read-through; full re-read deferred to per-finding evidence).
|
||||
- Last cumulative review: `_docs/03_implementation/cumulative_review_batches_98-102_cycle2_report.md` (carry-over baseline).
|
||||
- Restrictions / solution overview: not re-read (already covered in per-batch reviews).
|
||||
- ADR directory: `_docs/02_document/adr/` does NOT exist; ADR compliance check skipped (logged in Phase 7 below).
|
||||
|
||||
### Phase 2 — Spec Compliance
|
||||
|
||||
Cross-batch promise points (per-batch ACs already verified in batch reports):
|
||||
|
||||
- **AZ-836 (`RouteSpec` + extractor) → AZ-838 (`SatelliteProviderRouteClient`)**: AZ-838 task spec line 19 explicitly specifies `from gps_denied_onboard.replay_input.tlog_route import RouteSpec`. Implementation matches. The DTO contract is not formally documented in `_docs/02_document/contracts/c11_tilemanager/` — Spec-Gap candidate, but downgrades because both producer and consumer are owned by the same Epic (AZ-835) and the Epic spec describes the DTO shape inline. Note (not a separate finding): if `RouteSpec` survives F1 remediation by moving to `_types/route.py`, a contract `_docs/02_document/contracts/shared_types/route.md` is the right home.
|
||||
- **AZ-838 (`SatelliteProviderRouteClient`) → AZ-839 (C3 fixture, `populate_c6_from_route`)**: the fixture's driver imports `SatelliteProviderRouteClient` and uses `seed_route()`; signature matches AZ-838's `seed_route(spec, *, name=None) -> RouteSeedResult`. Cross-batch wiring sound.
|
||||
- **AZ-839 (C3 fixture, `PopulatedC6Cache`) → AZ-840 (orchestrator)**: AZ-840's `_e2e_orchestrator.write_effective_replay_config` overlays `c6_tile_cache.root_dir` onto the operator YAML using the cache_root the C3 fixture chose. AZ-840 batch report documents the contract; per-test fixtures consume `PopulatedC6Cache` directly. Sound.
|
||||
- **AZ-777 contract adaptation (b104) → satellite-provider real endpoints**: `tile_downloader.py` `_LIST_PATH` / `_GET_PATH` now point at the real endpoints (`/api/satellite/tiles/inventory` + `/tiles/{z}/{x}/{y}`). The leftover `_docs/_process_leftovers/2026-05-21_az777_complexity_override.md` 2026-05-21 addendum recorded this as the "largest single sub-deliverable of the refreshed Phase 1". Implementation matches.
|
||||
|
||||
No Spec-Gap findings.
|
||||
|
||||
### Phase 3 — Code Quality
|
||||
|
||||
- All cycle-3 production modules (`tlog_route.py`, `route_client.py`, expanded `errors.py`, modified `tile_downloader.py`) carry module + class + function docstrings consistent with the project pattern (cycle-2 baseline preserved).
|
||||
- `route_client.py` is ~600 LOC with one class (`SatelliteProviderRouteClient`) plus one DTO (`RouteSeedResult`) plus module-level helpers. The class has 5 public methods (validate, seed_route, _post_route, _poll_route_status, _verify_inventory). Each method is single-responsibility. No method exceeds the 50-line / cyclomatic-10 thresholds enumerated in the skill's Phase 3 list (per code reading; not measured).
|
||||
- `tlog_route.py` `extract_route_from_tlog` uses Douglas-Peucker for waypoint coarsening — correct choice per AZ-836 spec.
|
||||
- Tests follow Arrange / Act / Assert per coderule (verified by sampling `test_tlog_route.py` and `test_e2e_orchestrator_unit.py`; no exhaustive enumeration).
|
||||
|
||||
No Code Quality findings.
|
||||
|
||||
### Phase 4 — Security Quick-Scan
|
||||
|
||||
- `route_client.py` HTTP client uses `httpx.Client` with `timeout` parameter (no infinite hangs), argv-style request construction (no shell), and bearer-token auth via the existing C11 plumbing. No secrets in source.
|
||||
- `route_client.py` JSON request payload built via `json.dumps` on dataclass fields → no injection.
|
||||
- `route_client.py` URL construction uses `_ROUTE_STATUS_PATH_TPL.format(id=...)` where `id` is a UUID returned by the server — type-bounded, no injection surface.
|
||||
- `tile_downloader.py` modifications (b104) are confined to `_LIST_PATH` / `_GET_PATH` constants (per batch report); no new auth/parsing surface.
|
||||
- `scripts/mint_dev_jwt.py` (new, b104): JWT minting tooling for dev/test JWT signing keys. Per file naming (`mint_dev_jwt.py`) and per the `.env.test.example` pairing this is intended for non-prod use; not reviewed line-by-line in this pass.
|
||||
|
||||
No Security findings.
|
||||
|
||||
### Phase 5 — Performance Scan
|
||||
|
||||
- `route_client._poll_route_status` polls with default 5 s interval, max 60 attempts (= 5 min ceiling) using `time.sleep`. Configurable via constructor. Standard polling, not a perf concern.
|
||||
- `route_client._enumerate_route_tile_coords` walks the route's `regionSizeMeters × N waypoints` tile coverage locally; per AZ-838 batch report this is ~50–100 tiles for the Derkachi route. O(N) over waypoints.
|
||||
- `tlog_route.extract_route_from_tlog` runs Douglas-Peucker on the active GPS segment; per the unit test, completes in milliseconds for the Derkachi clip.
|
||||
- `_operator_pre_flight.py` and `_e2e_orchestrator.py` run inside the test harness; performance is bounded by the wall-clock budget (15 min on Tier-2).
|
||||
|
||||
No Performance findings.
|
||||
|
||||
### Phase 6 — Cross-Task Consistency
|
||||
|
||||
- **Sequential Epic chain**: AZ-836 (C1) → AZ-838 (C2) → AZ-839 (C3) → AZ-840 (C4). Each batch's "Files changed" is disjoint at the production level (C1 in `replay_input/`, C2 in `c11_tile_manager/`, C3+C4 in `tests/e2e/replay/`). No conflicting patterns; the test layer wires the production chain together via the orchestrator.
|
||||
- **Symbol uniqueness**: `RouteSpec`, `RouteExtractionError`, `extract_route_from_tlog`, `SatelliteProviderRouteClient`, `RouteSeedResult`, `SatelliteProviderRouteError`, `RouteValidationError`, `RouteTransientError`, `RouteTerminalFailureError`, `OrchestratorStep`, `OrchestrationFailure`, `OrchestrationReport`, `PopulatedC6Cache` — each defined exactly once across cycle-3 production + tests. No duplicates.
|
||||
- **AZ-839 b108b fix**: the hot-fix renamed `tile_store_path = cache_root / "tile_store"` → `cache_root / "tiles"` to match `PostgresFilesystemStore` layout. Cross-task consistency preserved (the path AZ-840 reads now matches the path AZ-839 writes).
|
||||
|
||||
No Cross-Task Consistency findings.
|
||||
|
||||
### Phase 7 — Architecture Compliance
|
||||
|
||||
**Layer-direction analysis** (against module-layout.md "Allowed Dependencies" + rule 9):
|
||||
|
||||
- `replay_input/tlog_route.py` (Layer 4 cross-cutting coordinator): imports `_types.geo` (Layer 1), `helpers.gps_compare` (Layer 1), `helpers.wgs_converter` (Layer 1), and intra-package `replay_input.errors` + `replay_input.tlog_ground_truth`. All imports are downward (Layer 4 → Layer 1) or intra-package. Compliant.
|
||||
- `c11_tile_manager/route_client.py` (Layer 4 component): imports own subpackage (`c11_tile_manager.errors`) + third-party (`httpx`) + **`replay_input.tlog_route.RouteSpec`** — see F1. The cross-cutting `replay_input` is not in c11's allow-list per rule 9. Architecture finding F1 (High).
|
||||
- `c11_tile_manager/tile_downloader.py` (Layer 4 component): modifications confined to constants. No new cross-component edges introduced.
|
||||
|
||||
**Public API respect**:
|
||||
- `c11_tile_manager.__init__.py` re-exports the new public surface (`RouteSeedResult`, `SatelliteProviderRouteClient`, plus the new error classes). Consumers calling `from gps_denied_onboard.components.c11_tile_manager import SatelliteProviderRouteClient` reach the package's public surface. ✅
|
||||
- `replay_input.__init__.py` re-exports `RouteSpec`, `RouteExtractionError`, `extract_route_from_tlog`. ✅
|
||||
- The F1 violation is a public API respect violation in the OPPOSITE direction: `c11.route_client` reaches into `replay_input.tlog_route` (a sub-module path) rather than the package's `__init__` re-export — but the deeper issue is that no direction of this import is rule-9-compliant.
|
||||
|
||||
**Cyclic-dependency check**:
|
||||
- New edges this cycle: `c11_tile_manager.route_client → replay_input.tlog_route` (F1) + `c11_tile_manager.route_client → c11_tile_manager.errors` (intra-package).
|
||||
- `replay_input.tlog_route → c11_tile_manager.*`? No (verified via grep). Acyclic.
|
||||
- `replay_input/__init__.py` re-exports `RouteSpec` from `tlog_route`. No back-edge to c11.
|
||||
- No new cycles introduced.
|
||||
|
||||
**Duplicate-symbol check**: see Phase 6 — no duplicates.
|
||||
|
||||
**Cross-cutting concerns not locally re-implemented**: none observed. Logging via `logging.getLogger(_COMPONENT)`, FDR via `fdr_client`, helpers consumed from canonical locations.
|
||||
|
||||
**ADR compliance**: `_docs/02_document/adr/` directory does **not exist**. The check is skipped per `code-review/SKILL.md` Phase 7 #6 ("If the directory does not exist or has only the index file, ADRs are skipped — log this skip in the report so the absence is visible"). Carry-over: `module-layout.md` references ADR-001 (monolith), ADR-002 (build-time exclusion), ADR-009 (interface-first DI), ADR-011 (replay-as-configuration) inline; these are documented in `architecture.md` but not as standalone ADR files. If the ADR directory is created in cycle-N (per a future retro action), this skip should retroactively re-evaluate the cycle-3 batches against any ADR whose `Evidence` overlaps the cycle-3 changed-file set.
|
||||
|
||||
**Single Architecture finding**: F1 — c11.route_client imports a non-allow-listed package. Documented but unaddressed at the architecture level.
|
||||
|
||||
## Notes
|
||||
|
||||
- **No `## Baseline Delta` section**: `_docs/02_document/architecture_compliance_baseline.md` was identified in the cycle-2 LESSONS entry (2026-05-20 architecture) and again in the cycle-2 cumulative review notes as a cycle-2 Step 6 (Decompose) prerequisite. The baseline file was NOT created in cycle 2 retrospective and was NOT created in cycle 3 either. Carry-over → cycle-3 retrospective. Without the baseline, "carried over / resolved / newly introduced" structural-violation accounting is not possible; F1 is therefore counted as "newly introduced this cycle" by inspection (`route_client.py` is a cycle-3-new file), and F2 is "carried over from cycle 2 with worsening" by inspection of the cycle-2 cumulative review F1.
|
||||
- **Cumulative-review cadence drift continues**: `/implement` Step 14.5 says K=3 default. Cycle 3 has 6 completed batches (104, 106, 107, 108, 108b, 109) without a cumulative review until this make-up review. Two cumulative reviews were due (after 104+106+107, after 108+108b+109). Cycle-2 cumulative review (`98-102`) noted the same drift and flagged it for the cycle-2 retrospective; the action did not land. Recurring. Cycle-3 retrospective should pick it up — possible mechanism: a `cumulative_review_pending: true` marker in `_docs/_autodev_state.md` that the implement skill flips on at K-batch boundaries and clears only on review file write, surfacing in the autodev Status Summary footer.
|
||||
- **AZ-270 lint coverage gap**: F3 documents the gap explicitly. Adjacent: the existing-code flow's Phase A Step 2 (Architecture Baseline Scan) feeds Step 4 (Code Testability Revision) and would also benefit from a tighter lint, since baseline-mode code-review uses the same `module-layout.md` rule 9 as enforcement input.
|
||||
- **Suite docs (parent)**: `<workspace-root>/../docs` does not exist (probed during R1 reconciliation). No suite-level cross-reference applies to this review.
|
||||
|
||||
## Artifacts
|
||||
|
||||
- Verdict consumed by: `/implement` Step 14.5 gate (FAIL → STOP, escalate via Choose A/B/C — auto-fix not eligible for High Architecture).
|
||||
- F1 carried forward to cycle-3 retrospective for action assignment; remediation candidate: 2-SP refactor task to relocate `RouteSpec` to `_types/route.py`.
|
||||
- F2 carried forward to cycle-3 Step 13 (Update Docs) at minimum; severity escalation watch if the staleness persists into cycle 4.
|
||||
- F3 carried forward to cycle-3 retrospective; remediation candidate: 1-SP test-update task to expand `test_ac6_only_compose_root_imports_concrete_strategies`.
|
||||
- Architecture compliance baseline action: blocked across cycle 2 → cycle 3; surface in cycle-3 retrospective with explicit owner.
|
||||
@@ -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 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.
|
||||
@@ -6,9 +6,9 @@ step: 10
|
||||
name: Implement
|
||||
status: in_progress
|
||||
sub_step:
|
||||
phase: 7
|
||||
name: batch-loop
|
||||
detail: "batch 110 next; full-suite gate after AZ-840 C4 ship"
|
||||
phase: 3
|
||||
name: refactor-safety-net
|
||||
detail: "02-az507; Phase 2 confirmed; ready for Phase 3 safety-net check in fresh session"
|
||||
retry_count: 0
|
||||
cycle: 3
|
||||
tracker: jira
|
||||
|
||||
Reference in New Issue
Block a user