Files
gps-denied-onboard/_docs/02_document/contracts/c11_tilemanager/tile_uploader.md
T
Oleksandr Bezdieniezhnykh 880eabcb3f Decompose Step 6 snapshot: 140 task specs + contract docs
Closes out greenfield Step 6 (Decompose) for all 14 components
(C1-C13 + cross-cutting helpers/replay). Covers tasks AZ-266..AZ-446
plus the _dependencies_table.md and component contract documents.

State file updated to greenfield Step 7 (Implement), not_started.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 00:39:48 +03:00

8.3 KiB

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

from typing import Protocol, runtime_checkable

@runtime_checkable
class TileUploader(Protocol):
    def upload_pending_tiles(self, request: UploadRequest) -> UploadBatchReport: ...
    def enumerate_pending_tiles(self, flight_id: uuid.UUID | None = None) -> list[TileMetadata]: ...
    def confirm_flight_state(self) -> FlightStateSignal: ...
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

@dataclass(frozen=True)
class UploadRequest:
    flight_id:                  uuid.UUID | None         # None = all flights with pending
    batch_size:                 int                       # tiles per HTTP POST
    satellite_provider_url:     str                       # parent-suite ingest base URL

@dataclass(frozen=True)
class UploadBatchReport:
    batch_uuid:                 uuid.UUID                 # assigned by parent-suite ingest
    per_tile_status:            tuple[PerTileStatus, ...]
    retry_count:                int
    next_retry_at_s:            int | None                # set when partial-success
    outcome:                    UploadOutcome             # success | partial | failure
    public_key_fingerprint:     str                       # 16-hex; from AZ-318

@dataclass(frozen=True)
class PerTileStatus:
    tile_id:                    TileId                    # from c6_tile_cache
    status:                     IngestStatus              # queued | rejected | duplicate | superseded
    rejection_reason:           str | None
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