# 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.
