# Batch Report — Batch 02 cycle 2 **Batch**: 02 (cycle 2) **Tasks**: AZ-488 (UAV tile upload endpoint + 5-rule quality gate) **Date**: 2026-05-11 ## Task Results | Task | Status | Files Modified | Tests | AC Coverage | Issues | |------|--------|---------------|-------|-------------|--------| | AZ-488_uav_tile_upload | Done | 9 modified + 13 added (`UavTileBatchUploadRequest.cs`, `UavQualityConfig.cs`, `UavTileMetadata.cs`, `UavTileBatchUploadResponse.cs`, `PermissionsRequirement.cs`, `UavTileQualityGate.cs`, `UavTileUploadHandler.cs`, `UavTileImageFactory.cs`, `UavTileQualityGateTests.cs`, `UavTileUploadHandlerTests.cs`, `UavTileFilePathTests.cs`, `PermissionsRequirementTests.cs`, `UavUploadTests.cs`, contract doc `uav-tile-upload.md`); `SatelliteProvider.Api/DTOs/UploadImageRequest.cs` deleted | All green (unit 253/253 + smoke integration including `UavUploadTests`) | 10/10 ACs covered | 0 blockers; 4 Low findings (see review) | ## AC Test Coverage: All covered (10 of 10) ## Code Review Verdict: PASS_WITH_WARNINGS ## Auto-Fix Attempts: 1 (in-flight build fix: removed unused `using Microsoft.AspNetCore.Http;` in `UavTileUploadHandler.cs` after first `--unit-only` revealed it broke Service-layer build) ## Stuck Agents: None ## What was implemented - New batch DTOs replacing the old stub: `UavTileBatchUploadRequest` (multipart envelope with JSON `metadata` + `IFormFileCollection`) in `Api/DTOs`; `UavTileMetadata`, `UavTileBatchMetadataPayload`, `UavTileBatchUploadResponse`, `UavTileUploadResultItem`, `UavTileUploadStatus`, and the closed `UavTileRejectReasons` enumeration in `Common/DTO` (placed in Common so Layer 3 services can reference them without a Service → API back-edge). Legacy `UploadImageRequest` deleted. - New config: `Common/Configs/UavQualityConfig.cs` (MinBytes/MaxBytes/MaxAgeDays/CapturedAtFutureSkewSeconds/MinLuminanceVariance/MaxBatchSize/LuminanceSampleSize). `appsettings.json` ships defaults under `UavQuality`. - New service `Services.TileDownloader.UavTileQualityGate` (impls `IUavTileQualityGate`) running the 5 rules in fixed order (Format → Size → Dimensions → Captured-at → Uniformity). Welford's online variance on a 32×32 ImageSharp downsample keeps the heuristic ~< 50 ms / item. `TimeProvider` injected for deterministic age tests. - New service `Services.TileDownloader.UavTileUploadHandler` (impls `IUavTileUploadHandler`) orchestrating envelope validation (batch size / mismatch / malformed JSON), per-item gate run, file-first-then-row persistence (`./tiles/uav/{z}/{x}/{y}.jpg`), and per-item result construction. Uses `TileSourceConverter.ToWireValue(TileSource.Uav)` per L-001. - New authorization: `Api/Authentication/PermissionsRequirement.cs` + `PermissionsAuthorizationHandler` reading the `permissions` claim — tolerates both repeated-string and JSON-array shapes. `SatellitePermissions.UavUploadPolicy` ("RequiresGpsPermission") wires the `GPS` permission requirement. - `Program.cs` wires: `UavQualityConfig` binding, Kestrel `MaxRequestBodySize = MaxBatchSize × MaxBytes = 500 MiB`, `FormOptions.MultipartBodyLengthLimit` + `ValueLengthLimit`, `IUavTileQualityGate` + `IUavTileUploadHandler` + `PermissionsAuthorizationHandler` DI registrations, `AddAuthorization(RequiresGpsPermission policy)`, Swagger `MapType` so the multipart shape renders correctly, and the new `UploadUavTileBatch` endpoint replacing the 501 stub. - Tests: - Unit: `UavTileQualityGateTests` (11 — every rule happy + reject + ordering), `UavTileUploadHandlerTests` (5 — happy/mixed/oversize/mismatch/invalid JSON), `UavTileFilePathTests` (3 — path shape + invariants), `PermissionsRequirementTests` (12 — claim shape coverage), `UavTileImageFactory` test utility. - Integration: `UavUploadTests.RunAll` (AC-1 happy, AC-2 mixed-batch, AC-3 multi-source coexistence with pre-seeded `google_maps` row, AC-4 same-source UPSERT with file overwrite + db refresh, AC-5 401 no-token, AC-6 403 wrong perm, AC-8 oversized 400). `StubAndErrorContractTests` updated to drop the old 501-stub assertion. - Docs: - **New frozen contract** `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0 — endpoint shape, request/response, 5-rule quality gate, closed reject-reason enum, file-path layout, concurrency model, versioning rules, test cases. - `architecture.md`: UAV ingestion is live; permission-handler description; ADR-004 updated for the per-source file-path split (UAV under `./tiles/uav/`, google_maps grandfathered at bare `./tiles/`). - `glossary.md`: `UAV Tile Upload`, `Quality Gate`, and all 7 reject-reason constants. - `modules/api_program.md`: new endpoint row, new local DTOs section, DI registration steps including the body-size cap math, security policy description, configuration section adds `UavQuality`. - `components/03_tile_downloader/description.md`: documents the two new public types, their dependencies, and the file-path divergence vs. legacy Google Maps tiles. - `data_model.md`: `file_path` semantics now per-source (UAV vs google_maps). - `tests/performance-tests.md`: PT-08 (UAV upload latency NFR) added with Status `Deferred — harness work tracked in PT-07 leftover`. `_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md` updated with the PT-08 follow-on instruction so PT-08 lands when PT-07 lands. ## Test results (Step 10 verification) - **Unit**: 253/253 passed (single docker container, `dotnet/sdk:8.0`, ~3.2 s test time after restore). - **Integration (smoke)**: all green including the new `UavUploadTests` suite (which runs before the smoke/full branching). - **Pre-existing AZ-487 test bugs surfaced and fixed in separate `fix:` commits** (see below) — were masked by a CS0104 build error. ## Pre-existing fixes shipped alongside this batch Three small `fix:` commits were made on `dev` BEFORE the AZ-488 batch commit because they were blocking the test gate for AZ-488: 1. `753be43 [AZ-487] fix: resolve CS0104 ambiguity in AuthN tests` — `Microsoft.Extensions.DependencyInjection.AuthenticationServiceCollectionExtensions` collided with our same-named class in `SatelliteProvider.Api.Authentication`. Resolved via `using` alias. 2. `f64d0d7 [AZ-487] fix: JWT factory + tests now pass on net8.0` — `JwtTokenFactory.Create` with a negative lifetime produced `Expires < NotBefore`, which `JwtSecurityToken` rejects at construction. Shifted `notBefore` behind `expires` for non-positive lifetimes. Also disabled `MapInboundClaims` in `JwtTokenFactoryTests` so assertions read the factory's actual claim names ("sub", "email", "permissions") rather than `.NET`-default `ClaimTypes.*` aliases. 3. `11b7074 [AZ-487] fix: integration-test JWT factory handles negative lifetime` — same `Expires < NotBefore` issue in the integration-test side's own copy at `SatelliteProvider.IntegrationTests/JwtTestHelpers.cs`. All three are AZ-487 test-side hygiene that became observable only after the CS0104 build error was lifted. They are independent of the AZ-488 feature commit; user implicitly approved option B during the autodev pause. ## Open follow-ups (non-blocking) - **Doc-folder choice (F1, carried over from batch 01)**: `_docs/02_document/components/01_web_api/description.md` referenced by the spec doesn't exist; updates went into `modules/api_program.md` instead. Needs an operator decision on whether to add a stub `01_web_api` folder or formalize the convention. - **`File.WriteAllBytesAsync(byte[])` allocation** (F4 in review): up to 5 MiB array copy per accepted tile. Replace with `FileStream.WriteAsync(ReadOnlyMemory, ct)` when PT-08 measurement begins. Not blocking — Rule 5 decode + downsample dominates the gate cost target. - **PT-08 runner-script scenario**: deferred to land with the PT-07 harness expansion (per cycle 1 retro Action 2 / AZ-488 § Risk 4). Tracked in `_docs/_process_leftovers/2026-05-11_perf-pt07-harness.md`. - **Coordinate external consumers** for AZ-488: `gps-denied-onboard` and any mission-planner client that posts to `/api/satellite/upload` must attach a Bearer token with `permissions: ["GPS"]` (or the JSON-array shape `"[\"GPS\"]"` — handler accepts both). Coordination is the operator's at Step 16 (Deploy). ## Next: Step 11 (Run Functional Tests) — autodev auto-chain Cycle 2 batches all closed. Next autodev step is `test-run` → `deploy` (per `flows/existing-code.md` auto-chain rules).