# Test Specification — C11 Tile Manager (TileDownloader + TileUploader)

Component-scoped. Suite-level coverage in `_docs/02_document/tests/*.md`. C11 was renamed and expanded in this Plan cycle: it now owns BOTH operator-side network I/O directions against `satellite-provider`. Strict ADR-004 enforcement: never loaded into the airborne companion image.

## Acceptance Criteria Traceability

| AC ID | Acceptance Criterion (one-line) | Test IDs | Coverage |
|-------|---------------------------------|----------|----------|
| AC-8.1 | Imagery via Suite Sat Service offline cache, ≥0.5 m/px | FT-P-15, **C11-IT-01** (download) | Covered |
| AC-8.2 | Tile freshness <6 mo (active-conflict) / <12 mo (rear) | FT-N-05, **C11-IT-02** (download-side gate) | Covered |
| AC-8.3 | Imagery pre-loaded onto companion before flight | FT-P-15, FT-P-16, **C11-IT-01** | Covered |
| AC-NEW-6 | System rejects/downgrades stale tiles | FT-N-05, FT-N-06, **C11-IT-02** | Covered |
| AC-8.4 + D-PROJ-2 (POST contract) | Mid-flight tile generation + post-landing upload to satellite-provider | FT-P-17, **C11-IT-03** (upload) | Covered (against e2e-test fixture until D-PROJ-2 ships) |
| RESTRICT-SAT-1 | Onboard cache offline-only; no in-flight Service calls | NFT-SEC-02, NFT-SEC-05, **C11-ST-01** | Covered |
| ADR-004 | Process isolation; C11 never airborne | **C11-ST-01**, **C11-ST-02** | Covered (R02 enforcement) |

---

## Component-Internal Tests

### C11-IT-01: TileDownloader fetch + sector-classified freshness gate + write to C6

**Summary**: given a Derkachi-area request, `TileDownloader.fetch` retrieves tiles from the real `satellite-provider` (or fixture in tests), enforces the per-sector freshness gate, and writes accepted tiles into C6 with byte-identical filesystem layout.

**Traces to**: AC-8.1, AC-8.2, AC-8.3, AC-NEW-6

**Description**: configure the fetch job with the Derkachi bbox + zoom range + sector classification (active_conflict + stable_rear mix); run `fetch`; assert (a) all returned tiles within the bbox land in C6, (b) tiles with `produced_at` older than the per-sector threshold are downgrade-flagged or rejected, (c) the `DownloadBatchReport` reports counts that sum to the request size.

**Input data**: scripted fetch request + the real `satellite-provider` (or its test Docker fixture under `tests/fixtures/satellite-provider/`; this is the REAL service's existing GET surface, not the mock).

**Expected result**: tiles in C6 match request scope; freshness flags applied per sector.

**Max execution time**: 4 min on Tier-1 (network-dependent).

---

### C11-IT-02: freshness rejection counts surface in DownloadBatchReport

**Summary**: when the source has stale tiles, `DownloadBatchReport.stale_rejected` is non-zero and matches the count rejected at the C6 boundary.

**Traces to**: AC-NEW-6 (operator visibility)

**Description**: run a fetch that hits a known-stale tile range; assert (a) `stale_rejected > 0`, (b) the value equals C6's freshness-rejection count for that batch.

**Input data**: a synthetic `satellite-provider` response with stale tiles.

**Expected result**: counts match.

**Max execution time**: 60 s.

---

### C11-IT-03: TileUploader posts mid-flight tiles + signs payload

**Summary**: `TileUploader.upload_pending` reads mid-flight tiles from C6's pending-upload set, packages them per the D-PROJ-2 POST contract sketch, signs each payload with the per-flight key, and posts to the configured `/api/satellite/tiles/ingest` endpoint.

**Traces to**: AC-8.4, D-PROJ-2 contract

**Description**: stage C6 with 50 mid-flight tiles flagged pending-upload; bring up the e2e-test `mock-suite-sat-service` fixture (under `tests/fixtures/mock-suite-sat-service/`); call `upload_pending`; assert (a) all 50 tiles POSTed, (b) each payload signature verifies against the test public key, (c) on 202 Accepted, the C6 row is marked uploaded, (d) the fixture's request log shows all 50 tiles in arrival.

**Input data**: scripted C6 + e2e-test mock fixture.

**Expected result**: all 50 uploaded + acknowledged + marked.

**Max execution time**: 5 min.

---

### C11-IT-04: post-landing safety gate lives in C12 (cross-reference)

**Summary**: post-landing safety is owned by C12, not C11. The gate that historically lived in `TileUploader.upload_pending_tiles` was removed in Batch 44 (supersedes AZ-317); the equivalent check now lives in C12's `PostLandingUploadOrchestrator` (AZ-329) and refuses to invoke `TileUploader.upload_pending_tiles` unless the C13 `flight_footer` FDR record records `clean_shutdown=True` for the target flight.

**Traces to**: see `_docs/02_document/components/13_c12_operator_orchestrator/tests.md` → C12-IT-03 for the post-landing safety test.

**Status**: cross-reference only. C11's `TileUploader` no longer exposes `confirm_flight_state` or raises `FlightStateNotOnGroundError`.

---

### C11-IT-05: idempotent uploads on retry

**Summary**: re-running `upload_pending` after a partial-success batch only POSTs the tiles that weren't acknowledged before.

**Traces to**: D-PROJ-2 contract resilience

**Description**: stage 50 pending-upload tiles; call `upload_pending` with the mock configured to return 202 for first 30, 5xx for last 20; assert C6 marks 30 as uploaded. Reset mock to 202 for all; call `upload_pending` again; assert only the remaining 20 are POSTed and the first 30 are NOT re-sent.

**Input data**: scripted with controlled mock failure profile.

**Expected result**: per assertion above.

**Max execution time**: 60 s.

---

## Performance Tests

### C11-PT-01: download throughput

**Traces to**: operator-tooling SLO (AC-8.3 supports this).

**Load scenario**: 10 GB Derkachi area, parallel fetch.

**Expected results**:

| Metric | Target | Failure Threshold |
|--------|--------|-------------------|
| Throughput | ≥ 50 MB/s on a 1 Gbps link | < 20 MB/s |
| `DownloadBatchReport` produced | yes | no report = test failure |

---

### C11-PT-02: upload throughput

**Traces to**: post-landing operator UX (AC-8.4 + D-PROJ-2 timing assumption).

**Load scenario**: 1000 mid-flight tiles, sequential POST + sign.

**Expected results**:

| Metric | Target | Failure Threshold |
|--------|--------|-------------------|
| Throughput | ≥ 20 tile/s with signing | < 10 tile/s |

---

## Security Tests

### C11-ST-01: airborne process cannot import `c11_tilemanager`

**Summary**: per ADR-004 R02 enforcement, the airborne `production-binary` artifact has no path to import the C11 module.

**Traces to**: ADR-004, R02

**Test procedure**:
1. Build the airborne `production-binary`.
2. Inside a sandbox running the artifact, attempt `import c11_tilemanager` (and any submodule).
3. Assert `ModuleNotFoundError` for every variant.
4. Run the `runtime_root.py` self-check; assert it does NOT panic (because `find_spec` returns `None`).

**Pass criteria**: import fails everywhere; self-check passes silently.
**Fail criteria**: any successful import.

---

### C11-ST-02: NFT-SEC-02 network-egress test

**Summary**: the airborne process running in a no-route-to-`satellite-provider` network namespace cannot reach `satellite-provider` over TCP.

**Traces to**: ADR-004 R02 enforcement; covers `RESTRICT-SAT-1`.

**Test procedure**: as documented in `tests/security-tests.md` NFT-SEC-02. Listed here for traceability.

**Pass criteria**: zero outbound connection attempts to `satellite-provider`'s host:port from the airborne process during a 10-min replay.
**Fail criteria**: any attempt.

---

### C11-ST-03: per-flight signing key zeroised after upload completes

**Summary**: after `upload_pending` completes (success or final failure), the per-flight key is zeroised in memory.

**Traces to**: D-C8-9 = (d) (operator-side analogue)

**Test procedure**:
1. Run an upload.
2. Capture the key buffer post-call via test harness.
3. Assert the buffer is zero-filled.

**Pass criteria**: zero-filled.
**Fail criteria**: any non-zero residue.

---

## Acceptance Tests

### C11-AT-01: operator runs F1 download + F10 upload via CLI

**Summary**: end-to-end operator flow exercises both interfaces via the C12-driven CLI.

**Traces to**: AC-8.3, AC-8.4

**Preconditions**:
- Operator workstation with Docker + the operator-tooling tarball.
- A `satellite-provider` (real or test fixture) reachable.

**Steps**:

| Step | Action | Expected Result |
|------|--------|-----------------|
| 1 | `operator-orchestrator download --area derkachi.geojson --since 2026-01` | `DownloadBatchReport` printed; tiles in C6 |
| 2 | `operator-orchestrator build-cache` | C10 builds engines + descriptors + Manifest |
| 3 | (simulate flight) | (covered by other tests) |
| 4 | `operator-orchestrator upload-pending` | Pending-upload tiles POSTed; report printed |

---

## Test Data Management

| Data Set | Source | Size |
|----------|--------|------|
| Real `satellite-provider` Docker fixture (download) | upstream parent-suite Docker | image |
| e2e-test `mock-suite-sat-service` fixture (upload) | `tests/fixtures/mock-suite-sat-service/` | <50 MB image |
| Scripted Derkachi bbox + sector classification | scripted | <1 MB |

**Setup**: bring up the appropriate fixture per test (real for download; e2e-test mock for upload until D-PROJ-2 ships).
**Teardown**: stop fixture containers; clean per-test C6.
**Data isolation**: per-test C6 + per-test fixture instance.
