# 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.
