# MapObjects Pre-Flight Pull

**Task**: AZ-646_mission_client_mapobjects_pull
**Name**: Pre-flight MapObjects GET + cached-fallback handshake
**Description**: After mission fetch succeeds, GET `/missions/{id}/mapobjects` (and `/ignored` if separated). Surface the bundle to `mapobjects_store`. On failure, surface BIT degradation — operator must acknowledge cached fallback or abort. Never silent.
**Complexity**: 3 points
**Dependencies**: AZ-640_initial_structure, AZ-644_mission_client_pull_and_schema
**Component**: mission_client
**Tracker**: AZ-646
**Epic**: AZ-638

## Problem

The MapObjects working copy is hydrated pre-flight from the central `missions` API. The pull must complete before `mission_executor` proceeds past `BIT_OK`. On pull failure the system must NOT silently proceed; instead, `mission_executor`'s BIT (F9) surfaces a degraded state — the operator either acknowledges cached fallback (signed acknowledgement per Q9) or aborts.

## Outcome

- `MissionClient::pull_mapobjects(mission_id) -> Result<MapObjectsBundle, PullError>` performs a `GET /missions/{id}/mapobjects` (and `/ignored` if the API splits them) and returns a typed `MapObjectsBundle { map_objects, ignored_items, fetched_at, schema_version, fallback_used: bool }`.
- On 200, the bundle is handed to `mapobjects_store` for hydration; `mapobjects_pull_state = synced`.
- On error or timeout, `pull_state = failed`; the typed error is surfaced to `mission_executor` (F9 BIT degrades, never silent).
- Health fields: `mapobjects_pull_state`, `last_mapobjects_pull_ts`.

## Scope

### Included
- GET endpoint(s) call.
- Schema validation of the bundle (using the shared MapObjects schema in `shared/contracts/`).
- Cached-fallback semantics — the **cache** itself lives in `mapobjects_store` (task 28); this task only knows to set `fallback_used = true` if it uses cached on operator ack.
- Health surface fields above.

### Excluded
- The cache storage itself (lives in `mapobjects_store`).
- Operator-acknowledgement flow (`operator_bridge`).
- BIT orchestration (`mission_executor`).

## Acceptance Criteria

**AC-1: Happy path pull**
Given a fixture API that returns a schema-valid MapObjects bundle
When `pull_mapobjects("M1")` is called
Then it returns `Ok(bundle)`, `pull_state = synced`, and the bundle reaches `mapobjects_store` for hydration.

**AC-2: Schema-invalid is rejected**
Given the API returns a 200 with a missing required field
When `pull_mapobjects("M1")` is called
Then it returns `Err(SchemaInvalid)` and `pull_state = failed`; no silent acceptance.

**AC-3: Network failure surfaces to F9**
Given the API is unreachable
When `pull_mapobjects("M1")` is called
Then it returns `Err(Unreachable)`, `pull_state = failed`, and the error is observable by `mission_executor`'s BIT path.

**AC-4: 30 km × 30 km area completes within budget**
Given a fixture bundle the size of a 30 km × 30 km mission area
When the pull is performed on a 100 Mbps loopback link
Then the call completes in ≤30 s.

## Non-Functional Requirements

**Performance**
- ≤30 s for a 30 km × 30 km mission area on healthy connectivity (per `description.md §8`).

**Reliability**
- Never silent on failure.

## Contract

- MapObjects bundle schema: `shared/contracts/mapobjects-bundle.json`. Owner: `../_docs/02_missions.md` §7.13 extension.
- Canonical typed model: `data_model.md §MapObjectsBundle`.

## Runtime Completeness

- **Named capability**: HTTPS GET against the central MapObjects extension + schema validation.
- **Production code that must exist**: real HTTPS GET; real schema validator.
- **Allowed external stubs**: `wiremock`/`mockito`.
- **Unacceptable substitutes**: skipping schema validation in production.
