# Deploy Report — Cycle 13 (AZ-1126) **Date**: 2026-06-26 **Cycle**: 13 **Scope**: Migrate UAV upload `capturedAt` to `DateTimeOffset` (F-AZ810-2 closure). ## What is shipping ### Code changes | Area | Change | |------|--------| | `UavTileMetadata.CapturedAt` | `DateTime` → `DateTimeOffset` | | `UtcOffsetRequiredDateTimeOffsetConverter` | **New** — rejects offset-less ISO-8601 at deserialization | | `UavTileMetadataValidator` | Freshness rules use `UtcDateTime` without `DateTimeKind` branching | | `UavTileQualityGate` / `UavTileUploadHandler` | UTC comparisons without manual kind normalization | | `UavUploadValidationFilter` | Propagates converter `JsonException.Message` for metadata parse failures | | Unit + integration tests | Offset-less rejection + backward-compatible `Z` / `+00:00` happy paths | | `uav-tile-upload.md` | v1.2.0 → **v1.2.1** (offset requirement documented) | ### Database migrations **None.** ### Configuration changes | Setting | Change | |---------|--------| | New env vars | **None** | | Container image | Rebuild only — same `aspnet:10.0` base; no Dockerfile changes | | Consumer contracts | `uav-tile-upload.md` patch bump — wire shape unchanged for offset-aware clients | ### Contract changes (consumer-visible) | Contract | Change | Consumer action | |----------|--------|-----------------| | `uav-tile-upload.md` v1.2.1 | `capturedAt` must include explicit UTC offset (`Z` or `+00:00`); offset-less strings → HTTP 400 | UAV upload clients sending `"2026-06-26T12:00:00"` without offset must add `Z` or `+00:00` | | REST / gRPC wire shapes (other) | Unchanged | No action | ## Verification gates passed in this cycle | Gate | Result | Evidence | |------|--------|----------| | Step 11 — Functional tests | **PASS** | Full suite via `./scripts/run-tests.sh` (mode=full) | | Step 12 — Test-Spec Sync | **PASS** | Traceability AZ-1126 AC-1..AC-4 | | Step 13 — Update Docs | **PASS** | `ripple_log_cycle13.md`, module + contract docs | | Step 14 — Security Audit | **PASS** (delta) | `security_report_cycle13.md`; F-AZ810-2 **resolved** | | Step 15 — Performance Test | **PASS** | `perf_2026-06-26_cycle13.md` — 11/11 thresholds | ## Security carry-overs (post-cycle-13) | ID | Status | |----|--------| | F-AZ810-2 | **Resolved** (AZ-1126) | | D-AZ795-1 | Open — FluentValidation 12.0.0 → 12.1.1 | | D2-cy4 | Open — test SDK JWT advisory (test-runtime only) | ## Operator runbook 1. **Commit and push** cycle-13 changes to `origin/dev`; confirm Woodpecker `01-test` + `02-build-push` green. 2. **No migration** — deploy new API image only. 3. **Smoke-test** after deploy: - POST `/api/satellite/upload` with valid JPEG + `capturedAt` lacking offset → HTTP 400 referencing `capturedAt` - POST with `capturedAt` as ISO-8601 UTC (`...Z` or `...+00:00`) → HTTP 200 on otherwise valid payload - Region, route, inventory, gRPC tile delivery happy paths unchanged 4. **Notify UAV upload consumers** (`gps-denied-onboard`, mission planner) of the offset requirement per contract v1.2.1. ## Release note `/release` prerequisites (`scripts/deploy.sh`, `_docs/04_release/`) are **not present** in this repo — production promotion remains operator-driven (image build + compose on target host). Step 16.5 should be **skipped** unless release infrastructure is onboarded. **Verdict**: Cleared for retrospective (Step 17). Release (16.5) skipped — no release execution harness.