Contract: tile_uploader
Component: c11_tilemanager
Producer task: AZ-319_c11_tile_uploader
Consumer tasks: AZ-253 (E-C12 Operator Pre-flight Tooling — TBD at C12 decompose time)
Version: 1.0.0
Status: draft
Last Updated: 2026-05-10
Purpose
The TileUploader Protocol is C11's operator-side post-landing upload interface. C12 invokes it during F10 (post-landing) to read mid-flight tiles flagged pending-upload from C6 (source = onboard_ingest, voting_status = pending), package them per the D-PROJ-2 ingest contract sketch, sign each tile payload with the per-flight ephemeral key (AZ-318), and POST to satellite-provider's /api/satellite/tiles/ingest endpoint. Acknowledged tiles are marked uploaded in C6.
The uploader gates on flight_state == ON_GROUND (AZ-317) before any network egress. C11 is operator-side ONLY; ADR-004 forbids the airborne companion image from importing this module.
Shape
Function / method API
| Name |
Signature |
Throws / Errors |
Blocking? |
upload_pending_tiles |
(request: UploadRequest) -> UploadBatchReport |
FlightStateNotOnGroundError, SatelliteProviderError, RateLimitedError, SignatureRejectedError, TileMetadataError |
sync (post-landing; minutes) |
enumerate_pending_tiles |
(flight_id: uuid.UUID | None) -> list[TileMetadata] |
TileMetadataError |
sync (seconds) |
confirm_flight_state |
() -> FlightStateSignal |
FlightStateNotOnGroundError |
sync (≤ 1 ms) |
Data DTOs
| Field |
Type |
Required |
Description |
Constraints |
UploadRequest.flight_id |
UUID | None |
no |
Restricts batch to one flight |
None = all pending across flights |
UploadRequest.batch_size |
int |
yes |
Tiles per HTTP POST |
1 ≤ batch_size ≤ 200 |
UploadBatchReport.batch_uuid |
UUID |
yes |
Parent-suite batch identifier |
Server-assigned per D-PROJ-2 |
UploadBatchReport.per_tile_status |
tuple[PerTileStatus, ...] |
yes |
Per-tile result |
Length = number of tiles attempted in this report |
UploadBatchReport.outcome |
UploadOutcome |
yes |
Aggregate outcome |
success (all queued/duplicate/superseded) | partial (some rejected/timeout) | failure (gate blocked or full failure) |
UploadBatchReport.public_key_fingerprint |
str |
yes |
Identifies the per-flight signing key |
16 hex chars from AZ-318 |
PerTileStatus.status |
IngestStatus |
yes |
Server response status |
queued | rejected | duplicate | superseded |
Invariants
- I-1:
confirm_flight_state is called by upload_pending_tiles BEFORE any C6 read or network egress; if FlightStateNotOnGroundError is raised, NO tiles are read, NO POSTs are issued, NO C6 mutation occurs. The gate is closed by default.
- I-2: Every uploaded tile carries a signature produced by the AZ-318 per-flight key manager's
sign(payload). The parent suite verifies against the public key it received via the safety officer's pre-flight enrolment OR the kind="c11.upload.session.key.public" FDR record.
- I-3: A tile acknowledged as
queued, duplicate, or superseded by the parent suite is marked uploaded in C6 (mark_uploaded(tile_id)); a tile acknowledged as rejected is NOT marked uploaded — it remains pending for human review.
- I-4: The per-flight signing key is zeroised at the end of
upload_pending_tiles regardless of success or failure (try/finally in the caller; AZ-318's end_session()).
- I-5: A
SignatureRejectedError from the parent suite triggers an FDR alert (AZ-318's record_signature_rejection); it is NEVER silently caught.
- I-6: The uploader writes via the AZ-303
TileMetadataStore.mark_uploaded Protocol; it does NOT update the metadata table directly.
- I-7: Partial-success batches are reported (not raised as failures) so the caller can re-invoke for the unacked tiles; idempotent retry behaviour is owned by the AZ-320 decorator that wraps this Protocol's impl.
- I-8: The signed payload includes
capture_timestamp per the D-PROJ-2 contract sketch; the parent suite's nonce / timestamp validation owns replay defence.
Non-Goals
- Not covered: airborne or in-flight uploads (RESTRICT-SAT-1 forbids them; airborne process cannot import this module per ADR-004).
- Not covered: orchestration of when the operator runs F10 — owned by C12.
- Not covered: tile downloads from
satellite-provider — owned by TileDownloader (separate contract).
- Not covered: parent-suite voting / trust-promotion of uploaded tiles — owned by D-PROJ-2 design task #2 (
satellite-provider).
- Not covered: HSM / TPM-backed key storage — out of scope this cycle (in-memory key with zeroisation).
- Not covered: mid-upload key rotation — one key per session.
- Not covered: idempotent retry across partial-success batches — separate task in this epic decorates this contract.
Versioning Rules
- Breaking changes (renamed method, removed required field, changed return type, changed signature contract) require a major version bump. C12 is the sole consumer today; coordinate via Choose A/B/C/D when bumping.
- Non-breaking additions (new optional field on the report, new
IngestStatus enum value the consumer already tolerates via _ = status) require a minor version bump.
Test Cases
| Case |
Input |
Expected |
Notes |
| upload-happy-path |
50 pending tiles, ON_GROUND, parent-suite returns 202 with all queued |
UploadBatchReport.outcome = success; all 50 marked uploaded in C6; signature verifies on each |
C11-IT-03 |
| flight-state-blocks |
FlightStateSource returns IN_FLIGHT |
FlightStateNotOnGroundError; zero C6 reads; zero POSTs |
C11-IT-04 |
| signature-rejected |
Parent suite returns rejected for 1 tile with reason "invalid signature" |
PerTileStatus.status = rejected; outcome = partial; FDR c11.upload.signature_rejected emitted; the tile NOT marked uploaded |
I-5 |
| duplicate-acknowledged |
Parent suite returns duplicate for 5 tiles (already ingested in a prior batch) |
All 5 marked uploaded; outcome = success |
I-3 |
| signing-key-zeroised |
Run a successful upload, then assert the AZ-318 manager's _private_key is None |
Always zeroised; FDR c11.upload.session.key.zeroised recorded |
I-4 |
| signing-key-zeroised-on-failure |
Network drop mid-batch raises SatelliteProviderError, then assert key zeroised |
Always zeroised even on failure |
I-4 |
| empty-pending-set |
No pending tiles |
outcome = success with empty per_tile_status; zero POSTs; zero key generation |
edge case |
| public-key-in-fdr-before-first-post |
Capture FDR records |
kind="c11.upload.session.key.public" precedes any c11.upload.tile.* records |
safety-officer correlation |
Change Log
| Version |
Date |
Change |
Author |
| 1.0.0 |
2026-05-10 |
Initial contract — produced by AZ-319 (E-C11 decomposition) |
autodev |