[AZ-845][AZ-846][AZ-847] Refactor 02: relocate RouteSpec + widen lint

Cycle-3 refactor run 02-az507 (RouteSpec relocation + module-layout
refresh + AZ-270 lint widening). Single batch of 3 tasks; epic AZ-844.

AZ-845 — Relocate RouteSpec DTO to _types/route.py (rule-9 fix):
  * New canonical home: src/gps_denied_onboard/_types/route.py
    (frozen+slots dataclass; full docstring carried over verbatim).
  * c11_tile_manager/route_client.py imports from _types.route.
  * replay_input/tlog_route.py and replay_input/__init__.py keep
    re-exports for backward-compat (RouteSpec in __all__).
  * 5 test files updated to import from _types.route for symmetry.
  * Identity-preserving re-export verified by new test
    test_az845_routespec_canonical_home_and_reexport_identity.

AZ-846 — Refresh module-layout.md cycle-3 entries:
  * c11_tile_manager Internal list rewritten with all 8 internals
    (alphabetised) — corrects a stale entry that referenced files
    (satellite_provider_*.py) that no longer exist.
  * shared/replay_input file list adds errors.py (cycle-2 carry),
    tlog_ground_truth.py (cycle-2 carry), tlog_route.py (cycle-3 NEW).
  * shared/_types section registers route.py with provenance line.
  * Out-of-scope cycle-2 carry-overs (replay_api/, cli/render_map.py,
    helpers/gps_compare.py, etc.) intentionally untouched.

AZ-847 — Widen test_az270 lint to enforce full rule-9 allow-list:
  * test_ac6_only_compose_root_imports_concrete_strategies now walks
    every components/<X>/*.py ImportFrom/Import and rejects anything
    not in the rule-9 allow-list (own subpackage + _types + helpers
    + config/logging/fdr_client/clock + frame_source interface-only).
  * Strict superset of the original AC-6 narrow check.
  * Reports zero violations on the codebase post-AZ-845.
  * Two principled carve-outs documented in the test docstring:
    - components/<X>/bench/** path skip (measurement code legitimately
      constructs production strategies via runtime_root factories).
    - register_* lazy self-registration imports from
      runtime_root.<X>_factory (central-registry plugin pattern).
  * Both carve-outs surfaced to user via Choose A/B/C/D Risk-1
    protocol; user skipped both — agent proceeded with documented
    defaults. Doc-only follow-up tracked in
    _docs/_process_leftovers/2026-05-24_az847_rule9_wording_followup.md
    for rule-9 wording update in module-layout.md.

Test results: 2287 passed, 90 skipped (environmental — Docker / CUDA
/ TensorRT / Jetson hardware / fixtures), 0 failed. Focused subset
(replay_input/ + c11_tile_manager/ + test_az270_compose_root.py)
also clean: 169 passed, 1 skipped.

Tracker: AZ-845/846/847 transitioned In Progress -> In Testing.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-24 10:07:20 +03:00
parent 479e9e41af
commit fd52cc9b1d
16 changed files with 288 additions and 68 deletions
@@ -0,0 +1,58 @@
# AZ-847 — Rule-9 wording follow-up (doc-only)
**Timestamp**: 2026-05-24T10:05:00+03:00
**Type**: Decision log + deferred doc-only work item
**Decision-maker**: agent (user skipped Choose A/B/C/D twice — implicit authorization to proceed with reasoned default)
## Context
AZ-847 widens `tests/unit/test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies` to enforce the full rule-9 allow-list documented in `_docs/02_document/module-layout.md`. Implementation surfaced two pre-existing rule-9 violations not anticipated by the task spec:
1. **`components/c1_vio/bench/okvis2.py`** — top-level `from gps_denied_onboard.runtime_root.vio_factory import build_vio_strategy`. The bench module legitimately constructs production strategies via the composition root for measurement purposes (its job).
2. **`components/c4_pose/opencv_gtsam_estimator.py`**, **`components/c5_state/eskf_baseline.py`**, **`components/c5_state/gtsam_isam2_estimator.py`** — lazy `from gps_denied_onboard.runtime_root.<X>_factory import register_<X>_*` inside `def register():`. This is the central-registry self-registration pattern; components plug their `create()` callable into the airborne bootstrap registry by calling `register_<X>_*(_STRATEGY, create)`.
Per AZ-847 Risk 1, both discoveries were surfaced to the user via Choose A/B/C/D (twice — once for bench/, once for self-registration). The user skipped both. Agent proceeded with principled defaults documented in the test docstring:
- **bench/ exclusion**: `components/<X>/bench/**` paths are skipped at lint time.
- **Self-registration carve-out**: `ImportFrom` whose module starts with `gps_denied_onboard.runtime_root.` AND every imported name starts with `register_` is allowed. `build_*`, `compose_root`, error envelopes, etc. from `runtime_root.*` remain forbidden.
Both carve-outs are layered defense — bench/ skip handles measurement-code legitimate `build_*` use; self-registration carve-out handles production registry pattern. Together they let the widened lint pass on the current codebase (zero violations after AZ-845 lands) while preserving rule-9's intent: components must not reach for OTHER components' concrete impls.
## What this leftover defers
This leftover defers the **rule-9 wording update** in `_docs/02_document/module-layout.md` (rule 9, Layout Rules section). The current rule wording lists 8 allow-list entries and ends with: "The composition root (`runtime_root/*`) is the single exception". This sentence does NOT cover:
- `components/<X>/bench/**` files (rule-9 not applicable; measurement code).
- Lazy `register_*` self-registration imports from `runtime_root.<X>_factory` (registry pattern).
Both carve-outs are documented in the test docstring at `tests/unit/test_az270_compose_root.py::test_ac6_only_compose_root_imports_concrete_strategies` (post-AZ-847). To stay honest, the rule-9 wording in `module-layout.md` should be extended to mention these two carve-outs — otherwise reviewers reading rule 9 in isolation will see an apparent mismatch between rule 9 (strict) and the lint (with documented carve-outs).
## Replay condition
This is **NOT** a tracker write blocker — no Jira call is being deferred. It's a **doc-only follow-up**:
- Update rule 9 wording in `_docs/02_document/module-layout.md` to explicitly mention:
- `components/<X>/bench/**` is exempt from rule 9 (benchmark code legitimately constructs production strategies).
- `ImportFrom` of `register_*` callables from `runtime_root.<X>_factory` is allowed for the self-registration pattern.
- Surface this leftover at the start of the next /autodev invocation. The user can either:
- Authorize a tiny doc-only ticket (1 SP) to make the rule-9 update.
- Bundle the rule-9 update into the cycle-3 retrospective output (Step 17) as part of "carry-overs to next cycle".
- Choose a different remediation (e.g., refactor `register_*` helpers into `helpers/strategy_registry.py` so rule 9 stays strict — this was Option C of the original Choose).
Until the wording is updated, the test docstring is the source of truth for the carve-outs. Reviewers should consult both rule 9 AND the docstring.
## Why a leftover, not a new ticket
- Per `.cursor/rules/tracker.mdc`: a leftover is appropriate for "deferred doc work" when the blocking factor is "user input on remediation strategy". The user skipped twice; the doc-only update is a small concern that fits the cycle-3 retro better than a standalone PBI.
- Per user complexity rule (`Create PBI with 2 or 3 points of complexity, could be 5`): this is < 1 SP work, below the typical PBI threshold.
- The technical work (the lint with documented carve-outs) is shipping in AZ-847; the doc-wording update is purely an audit-trail exercise that doesn't block any other work.
## Replay action
On next /autodev invocation, surface this leftover to the user as part of B1 (Process leftovers) and ask whether to:
- (i) update rule-9 wording in module-layout.md now (~1 SP doc edit);
- (ii) bundle into cycle-3 retro output;
- (iii) refactor `register_*` helpers out of `runtime_root.*` into `helpers/strategy_registry.py` (option C of the original Choose; bigger scope).
If user picks (i) or (ii), this leftover is consumed. If (iii), file a new ticket for the refactor and keep this leftover until the refactor lands and the carve-out is removed from the lint.