# Lost-Link Failsafe Ladder (F10)

**Task**: AZ-651_mission_executor_lost_link_ladder
**Name**: Lost-link ladder LinkOk → LinkDegraded → LinkLost → LinkLostInFollow
**Description**: Per-tick evaluation of the operator/Ground-Station modem link state. Default RTL after 30 s grace. Configurable. MAVLink-link loss to ArduPilot itself is a separate, more severe event — health → red, airframe failsafe takes over (we do NOT override it).
**Complexity**: 3 points
**Dependencies**: AZ-640_initial_structure, AZ-648_mission_executor_state_machine, AZ-649_mission_executor_telemetry_forwarding
**Component**: mission_executor
**Tracker**: AZ-651
**Epic**: AZ-636

## Problem

The operator's modem link is critical to safe operation but inherently flaky. The failsafe must escalate predictably from `LinkOk` to `LinkDegraded` (5–30 s) to `LinkLost` (>30 s) to `LinkLostInFollow` (special-cased target-follow case) — each step with a defined behaviour. Default action on `LinkLost` is RTL after a grace window. Crucially, MAVLink-link loss to ArduPilot is a different event — autopilot does NOT override the airframe's built-in failsafe in that case.

## Outcome

- `LostLinkLadder::tick(now, link_state)` updates an enum `LadderState ∈ {LinkOk, LinkDegraded, LinkLost, LinkLostInFollow}` deterministically based on the elapsed time since the last operator-link heartbeat.
- `LinkDegraded` for 5–30 s: health → yellow; events queued; no command to airframe.
- `LinkLost` for >30 s (configurable): trigger RTL via `mavlink_layer`; transition to `LAND`.
- `LinkLostInFollow` (active `TargetFollow` + >30 s): 30 s grace, then RTL.
- MAVLink-link loss to ArduPilot: detected via `mavlink_layer`'s `LinkLost`; health → red; do NOT issue RTL (airframe handles it).
- Health surface: current `LadderState`, time-in-state, RTL trigger count.

## Scope

### Included
- Ladder state machine.
- Subscribe to operator-link state from `telemetry_stream` (forwarded by `operator_bridge` health).
- Subscribe to MAVLink-link state from `mavlink_layer`.
- Configurable thresholds (defaults: degraded=5 s, lost=30 s, follow-grace=30 s).
- RTL command issuance via `mavlink_layer::send_command(MAV_CMD_NAV_RETURN_TO_LAUNCH)`.

### Excluded
- Operator command auth checks (`operator_bridge`).
- Target-follow state ownership (`scan_controller`).

## Acceptance Criteria

**AC-1: Operator-link degraded then recovers**
Given a healthy link
When the operator-link heartbeat stops for 10 s and resumes
Then the ladder reports `LinkOk → LinkDegraded → LinkOk` with correct dwell times; no RTL is issued.

**AC-2: Operator-link lost triggers RTL**
Given a healthy link
When the operator-link heartbeat stops for 31 s
Then the ladder reports `LinkLost`, `send_command(MAV_CMD_NAV_RETURN_TO_LAUNCH)` is issued exactly once, and the state machine transitions to `LAND`.

**AC-3: Lost-in-follow grace then RTL**
Given the system is in `TargetFollow` and the operator-link drops
When the link is down for 30 s (grace), then continues to be down past the grace
Then RTL is triggered after the grace fires, not earlier.

**AC-4: MAVLink loss does NOT trigger autopilot-side RTL**
Given the MAVLink link to ArduPilot is lost (`mavlink_layer` reports `LinkLost`)
When the ladder tick runs
Then health → red, no `MAV_CMD_NAV_RETURN_TO_LAUNCH` is issued by autopilot (airframe failsafe owns the response), and the event is observable.

## Non-Functional Requirements

**Performance**
- Ladder tick: ≤5 ms.

**Reliability**
- All thresholds configurable; no hardcoded defaults beyond the defaults documented above.

## Runtime Completeness

- **Named capability**: F10 lost-link failsafe ladder.
- **Production code that must exist**: real state machine; real RTL command issuance.
- **Unacceptable substitutes**: omitting the `LinkLostInFollow` grace is not acceptable (an operator may have momentary glitches mid-follow).
