Files
Oleksandr Bezdieniezhnykh 1802d32107 [AZ-488] UAV tile batch upload + 5-rule quality gate
Replaces the 501 stub at POST /api/satellite/upload with a multipart
batch endpoint that ingests UAV-captured tiles, runs each item through
a 5-rule quality gate, and persists accepted tiles via the AZ-484
multi-source storage path with source='uav'.

Quality gate (in fixed order, first failure wins): JPEG format
(content-type + magic), size band 5 KiB-5 MiB, exact 256x256
dimensions, captured-at age (no future >30 s skew, no older than
7 days), luminance variance on 32x32 downsample. Closed reject-reason
enumeration in v1.0.0 contract.

Authorization: custom PermissionsRequirement / PermissionsAuthorization
Handler that reads the JWT `permissions` claim (tolerates both
repeated-string and JSON-array shapes). Endpoint protected by
RequiresGpsPermission policy; 401 without token, 403 without GPS perm.

Persistence: file-first to ./tiles/uav/{z}/{x}/{y}.jpg, then
ITileRepository.InsertAsync UPSERT (per-source UPSERT contract from
AZ-484). Per-item failures reported in response without aborting the
batch. Kestrel MaxRequestBodySize and FormOptions limits set to
MaxBatchSize x MaxBytes (default 100 x 5 MiB = 500 MiB).

New frozen contract: _docs/02_document/contracts/api/uav-tile-upload.md
v1.0.0. PT-08 NFR added to performance-tests.md as Deferred (harness
work tracked in PT-07 leftover, per AZ-488 § Risk 4).

Tests: 11 quality-gate unit tests, 5 handler unit tests, 3 file-path
unit tests, 12 permission-handler unit tests, 7 integration tests
(AC-1..AC-6, AC-8). All 253 unit tests + smoke integration suite
green.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 23:50:49 +03:00

8.2 KiB
Raw Permalink Blame History

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<UavTileBatchUploadRequest> 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 testsMicrosoft.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.0JwtTokenFactory.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<byte>, 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-rundeploy (per flows/existing-code.md auto-chain rules).