# RTSP Session + Reconnect + AI-Lock Signal

**Task**: AZ-657_frame_ingest_rtsp_session
**Name**: RTSP session lifecycle + bounded reconnect + AI-lock plumb-through
**Description**: Open the RTSP session to the ViewPro A40, recover from transient connection loss with bounded exponential backoff (1 s → 30 s cap), and plumb through the `bringCameraDown`/`bringCameraUp` AI-lock signal so downstream consumers can skip detection.
**Complexity**: 3 points
**Dependencies**: AZ-640_initial_structure
**Component**: frame_ingest
**Tracker**: AZ-657
**Epic**: AZ-627

## Problem

The RTSP session is the foundation of the perception pipeline. It must (a) open against the camera at startup, (b) recover from drops with bounded backoff (no infinite retry), and (c) carry the `ai_locked` flag through to every emitted `Frame` so that downstream consumers (`detection_client`, `movement_detector`) know to skip detection while the local supervisor is asserting an RC-takeover lock.

## Outcome

- `RtspSession::open(config) -> Result<Self, OpenError>` opens with TCP or UDP transport per camera config.
- On stream loss the session reopens with exponential backoff `1 s → 2 s → 4 s ...` capped at 30 s.
- A subscription to `bringCameraDown` / `bringCameraUp` toggles `ai_locked` on every subsequently emitted frame.
- Health surface: `reopens_total`, `last_frame_age_ms`, `session_state ∈ {closed, connecting, streaming, failing}`, `ai_locked`.
- Camera output-format mismatch (unexpected SPS/PPS) hard-fails at session open with an explicit error; never silently picks a wrong decode path.

## Scope

### Included
- RTSP client (FFmpeg / GStreamer binding or pure-Rust client — pick what `shared` pins).
- Backoff state machine.
- AI-lock signal source subscription (the supervisor channel is implementation-defined; the local supervisor signals over a unix-domain socket per `architecture.md`).
- Session state surface.

### Excluded
- Frame decoding (task 19).
- Multi-consumer publisher (task 20).

## Acceptance Criteria

**AC-1: Open against ViewPro A40 (fixture)**
Given a fixture RTSP server (e.g. `MediaMTX`) replaying a sample stream
When `RtspSession::open(...)` is called
Then it returns `Ok` within ≤2 s and `session_state = "streaming"`.

**AC-2: Reconnect on drop**
Given a healthy session for 5 s
When the fixture RTSP server is killed and restarted
Then the session reopens within ≤5 s and `reopens_total` increments by 1.

**AC-3: SPS/PPS mismatch hard-fails**
Given a fixture stream that announces an unsupported codec profile
When `RtspSession::open(...)` is called
Then it returns `Err(UnsupportedProfile { details })`; no silent decode-path selection.

**AC-4: AI-lock toggles ai_locked flag**
Given a healthy session emitting frames
When `bringCameraDown` is asserted
Then subsequent emitted frames have `ai_locked = true`; when `bringCameraUp` is asserted, they revert to `false`.

## Non-Functional Requirements

**Performance**
- Reconnect latency: ≤5 s from camera availability (per `description.md §8`).

**Reliability**
- Bounded backoff cap configurable; no infinite retry.

## Runtime Completeness

- **Named capability**: RTSP transport against ViewPro A40 + AI-lock signal plumb.
- **Production code that must exist**: real RTSP session; real AI-lock subscription.
- **Allowed external stubs**: `MediaMTX` or `live555-test` as fixture in dev/CI.
- **Unacceptable substitutes**: bypassing AI-lock entirely is unacceptable — it is a safety boundary.
