# C12 — Operator Pre-flight Orchestrator

## 1. High-Level Overview

**Purpose**: operator-facing tooling on the workstation that **sequences** the F1 cache-build workflow (calls `FlightsApiClient` → C11 `TileDownloader` → C10 `CacheProvisioner`), drives sector classification, freshness pipeline, calibration loading, and (post-flight) triggers the C11 `TileUploader`. Provides the human-in-the-loop entry point for everything that needs operator judgement (active-conflict vs stable-rear sector classification, AC-3.4 ≥3-frame outage operator re-loc requests, network configuration to `satellite-provider`).

**Architectural Pattern**: Coordinator (single concrete `OperatorTooling` today). Three interfaces: `CacheBuildWorkflow` (pre-flight UX), `FlightsApiClient` (read the operator-authored `Flight` from the parent-suite `flights` REST service or a local JSON export — AZ-489, ADR-010), and `OperatorReLocService` (mid-flight operator re-loc requests, AC-3.4). `OperatorReLocService` is consumed via the GCS link by C8 (operator commands subscription); `FlightsApiClient` is operator-workstation-only and never reaches the airborne companion (Principle #9).

**Upstream dependencies**:
- Operator (human input — flight ID or flight file path, sector classification, calibration path).
- `flights` REST service (parent-suite `suite/flights/`) — read via `FlightsApiClient` to fetch the operator-authored `Flight` (waypoints + altitudes); offline alternative is a local JSON export in the same DTO shape.
- C11 `TileDownloader` (operator-workstation-side) — invoked second in F1 to populate C6 from `satellite-provider` for the bbox derived from the `Flight`.
- C10 CacheProvisioner (companion-side) — invoked third in F1, over USB/Eth, with `takeoff_origin = Flight.waypoints[0]` and the computed bbox.
- Camera calibration artifact (operator workstation filesystem).

**Downstream consumers**:
- C11 `TileDownloader` (workstation) — invoked at F1 start.
- C10 CacheProvisioner (companion) — invoked via USB/Eth after C11 download completes.
- C11 `TileUploader` — invoked post-landing.
- C8 inbound operator-commands subscription — receives AC-3.4 re-loc requests over the GCS link.

## 2. Internal Interfaces

### Interface: `CacheBuildWorkflow`

| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `build_cache` | `flight_id` (online) OR `flight_file: Path` (offline), `sector_class`, `calibration_path`, `satellite_provider_url`, `api_key` | `CacheBuildReport` (wraps `FlightResolveReport` + C11 `DownloadBatchReport` + C10 `BuildReport`) | No (operator-facing; minutes) | `CacheBuildError` (wraps `FlightNotFoundError`, `FlightsApiUnreachableError`, `SatelliteProviderError`, `EngineBuildError`, etc.) |
| `trigger_post_landing_upload` | `PostLandingUploadRequest` (`flight_id`, `satellite_provider_url`, `api_key`, `batch_size`) | C11 `UploadBatchReport` (re-exposed as `UploadBatchReportCut`) | No (operator-facing; minutes) | `FlightStateNotConfirmedError` (footer missing / unclean / fdr-unreadable / flight-id not found), `SatelliteProviderError`, `SignatureRejectedError` (passthrough from C11) |
| `verify_companion_ready` | `companion_address` | `ReadinessReport` | No | `CompanionUnreachableError`, `ContentHashMismatchError` |
| `set_sector_classification` | `area, sector_class` | `None` | No | — |
| `apply_freshness_threshold` | `sector_class` | `int (months)` | No | — |

### Interface: `FlightsApiClient` (AZ-489, ADR-010)

| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `fetch_flight` | `flight_id: UUID, base_url: str, auth_token: str` | `FlightDto` (with ordered `WaypointDto[]`) | No (HTTPS; seconds) | `FlightsApiUnreachableError`, `FlightsApiAuthError`, `FlightNotFoundError`, `FlightsApiSchemaError` |
| `load_flight_file` | `path: Path` | `FlightDto` (same shape as `fetch_flight`) | No | `FlightFileNotFoundError`, `FlightsApiSchemaError` |
| `bbox_from_waypoints` | `waypoints: list[WaypointDto], buffer_m: float` | `BoundingBox` | No | `EmptyWaypointsError` |
| `takeoff_origin_from_flight` | `flight: FlightDto` | `LatLonAlt` (from `waypoints[0]`) | No | `EmptyWaypointsError` |

### Interface: `OperatorReLocService`

| Method | Input | Output | Async | Error Types |
|--------|-------|--------|-------|-------------|
| `request_reloc` | `reloc_hint: ReLocHint` | `None` (sent over GCS link to companion) | No | `GcsLinkError` |

**Input/Output DTOs**:
```
FlightDto:                         mirror of suite/flights/Database/Entities/Flight.cs (id, name, status, waypoints, ...)
WaypointDto:                       mirror of suite/flights/Database/Entities/Waypoint.cs (ordinal, lat, lon, alt, objective, source)

FlightResolveReport:
  source:                          enum {flights_api, flight_file}
  flight_id:                       UUID
  waypoint_count:                  int
  bbox:                            BoundingBox (computed envelope + buffer)
  takeoff_origin:                  LatLonAlt (waypoints[0])
  raw_flight_dto:                  FlightDto (preserved for FDR + debug)

CacheBuildReport:
  flight_resolve_report:           FlightResolveReport
  download_report:                 DownloadBatchReport (see C11 spec)
  build_report:                    BuildReport (see C10 spec)
  outcome:                         enum {success, failure, idempotent_no_op}
  failure_phase:                   enum {flight_resolve, download, build, none}
  failure_reason:                  string (optional)

ReadinessReport:
  manifest_present:                bool
  content_hashes_pass:             bool
  engines_present:                 bool
  calibration_present:             bool
  takeoff_origin_in_manifest:      bool                       # ADR-010: warn-but-not-fail if absent (FC-EKF fallback path remains)
  outcome:                         enum {ready, not_ready}
  not_ready_reasons:               list[string]

ReLocHint:
  approximate_position_wgs84:      LatLonAlt (operator's best guess)
  confidence_radius_m:             float
  reason:                          string (e.g. "lost track at waypoint 3")
```

## 3. External API Specification

C12 itself does NOT expose HTTP. It **consumes**:

- `satellite-provider`'s REST API **only via C11 `TileDownloader`** — C12 itself never holds the TLS / API-key credentials for satellite tiles. The credential boundary is C11 (operator-workstation-side); C12 sequences and reports.
- The parent-suite `flights` REST service via its own `FlightsApiClient` (AZ-489, ADR-010). C12 holds the TLS + suite credentials for the flights service because the `Flight` shape is a C12-owned concern (bbox + takeoff-origin derivation). The companion never reaches the flights service.
- The GCS link's MAVLink path (operator commands path), for AC-3.4 re-loc requests.

C12 may eventually expose a thin local HTTP API for an operator GUI. **Plan-phase carryforward, deferred** — the current cycle ships only a CLI.

## 4. Data Access Patterns

C12 holds the operator workstation's local cache staging area + per-area sector classification metadata.

### Storage Estimates

| Table/Collection | Est. Row Count (1yr) | Row Size | Total Size | Growth Rate |
|-----------------|---------------------|----------|------------|-------------|
| Sector classification persistence | tens of areas | ~1 KB / area | <100 KB | per operator deployment |
| Local cache staging area | matches the area being prepared | up to ~10 GB pre-stage | bounded by operator workstation NVM | per operation |

### Data Management

**Seed data**: none — operator drives every decision.

**Rollback**: rebuilding the cache from scratch is the rollback action; D-C10-1 idempotence makes it cheap if no inputs changed.

## 5. Implementation Details

**Algorithmic Complexity**: dominated by C11 `TileDownloader` bandwidth + C10's descriptor-batching cost (both offline). C12's own logic is constant-time per operator decision.

**State Management**: holds a session-local view of the operator's classifications + active provisioning job. No long-lived state on the companion.

**Key Dependencies**:

| Library | Version | Purpose |
|---------|---------|---------|
| Click / Typer | per project pin | CLI framework |
| paramiko / ssh / fabric | per project pin | Companion bring-up over SSH (USB/Eth) |
| pymavlink | bundled per D-C8-3 | GCS-link operator commands (AC-3.4) |
| httpx | per project pin | `FlightsApiClient` HTTPS calls to the parent-suite `flights` REST service (AZ-489) |
| pydantic | per project pin | `FlightDto` / `WaypointDto` validation in `FlightsApiClient` (online + offline paths) |
| (`satellite-provider` HTTP traffic stays owned by C11) | — | C12 calls C11 in-process |

**Error Handling Strategy**:
- `CacheBuildError`: wraps the underlying error from C11/C10/C7/C6/`FlightsApiClient` with operator-friendly text + remediation hint. Includes `failure_phase: flight_resolve | download | build` so the operator knows which step to retry.
- `FlightsApiUnreachableError`, `FlightsApiAuthError`, `FlightNotFoundError`, `FlightsApiSchemaError`: surfaced by `FlightsApiClient` (online path). Offline path raises `FlightFileNotFoundError` / `FlightsApiSchemaError`. Each maps to an actionable operator hint.
- `EmptyWaypointsError`: the resolved `Flight` carries zero waypoints — cannot derive bbox or takeoff origin; refuse build.
- `CompanionUnreachableError`: pre-flight bring-up failure; operator must check the wire/SSH config.
- `ContentHashMismatchError`: post-stage tampering detected; refuse to mark as ready; operator must re-run F1.
- `GcsLinkError` (AC-3.4 path): GCS link drop. Re-loc request is best-effort; operator may need to re-issue when link recovers.

## 6. Extensions and Helpers

| Helper | Purpose | Used By |
|--------|---------|---------|
| `WgsConverter` | shared with C4, C5, C6, C8, C10 | C4, C5, C6, C8, C10, C12 |

## 7. Caveats & Edge Cases

**Known limitations**:
- Operator UX is CLI-only this cycle; GUI is a Plan-phase carryforward (deferred).
- AC-3.4 operator re-loc is best-effort over a bandwidth-limited GCS link; the companion must remain useful even when no re-loc hint arrives.

**Potential race conditions**:
- Operator re-runs of F1 while a previous F1 is still in progress: filesystem lockfile in the cache staging area prevents concurrency.

**Performance bottlenecks**:
- Pre-flight tile download (C11) + descriptor build (C10) is the long pole; C12 just shows progress and surfaces failures.

## 8. Dependency Graph

**Must be implemented after**: C11 (both `TileDownloader` and `TileUploader`), C10 (cache artifact build), C8 inbound operator-commands subscription (for AC-3.4).

**Can be implemented in parallel with**: anything not in the above list.

**Blocks**: F1 (without C12 there is no operator entry point), F10 (without C12 there is no post-landing trigger).

## 9. Logging Strategy

| Log Level | When | Example |
|-----------|------|---------|
| ERROR | `CacheBuildError`, `CompanionUnreachableError`, `ContentHashMismatchError`, `GcsLinkError` | `C12 cache build failed: reason=satellite_provider_5xx; retried=3` |
| WARN | freshness downgrade applied; non-fatal stage warning | `C12 freshness downgrade: area=Derkachi; stale_count=42` |
| INFO | start/end of provisioning; sector classification set; AC-3.4 hint sent | `C12 sector classification updated: area=Derkachi; class=active_conflict` |
| DEBUG | per-step progress mirrors of C11 (download) and C10 (build) | `C12 progress: download=12345/87654 (14%); build=engine=2/4` |

**Log format**: structured JSON.
**Log storage**: operator workstation log file (e.g. `~/.azaion/onboard/c12-tooling.log`).
