# Research Problem — UAV Batch Upload (AZ-485 family) ## Context - **Project**: SatelliteProvider — .NET 8.0 ASP.NET Core service that downloads/persists satellite imagery tiles. Currently single-source (Google Maps); AZ-484 just shipped multi-source storage (`source` + `captured_at` columns, 5-col unique index, `tile-storage` v1.0.0 frozen contract). - **Cycle**: 2 (started after cycle 1 closed AZ-484 today, 2026-05-11). - **Task family** (4 tasks under epic AZ-483, accepted by user): - **AZ-485** — UAV batch upload endpoint + per-tile persistence loop (5 SP) - **AZ-486** — Quality gate (6 checks + thresholds + reject reasons) (3 SP) - **AZ-487** — JWT auth middleware (3 SP) - **AZ-488** — Geofence whitelist (2 SP) - **Confirmed user requirements** (from new-task Step 1): 1. Batch upload (multipart with metadata + N image files per request) 2. Minimum metadata only per tile: `latitude`, `longitude`, `tile_zoom`, `tile_size_meters`, `captured_at` (UTC), image bytes 3. Quality gate enforces all of: dimensions, byte size, blank/uniform detection, captured_at age, format=JPEG, geofence containment 4. JWT auth (HS256, signing key in env, required claims: `sub` + valid `exp`) 5. Sync responses: 200 + per-tile `{ tileId, status, reason? }` array; 4xx for auth/format-of-batch failures 6. File storage: same `./tiles/{z}/{x}/{y}.jpg` layout as Google Maps tiles - **Project constraints to respect**: - .NET 8.0 only (no .NET 9 features) - Existing libraries: ImageSharp 3.1.11, Npgsql 9.0.2, Dapper 2.1.35, Serilog 8.0.3 - No auth currently exists in the API (per Security Audit Step 14, 2026-05-11) - The "geofence" type in the codebase (`SatelliteProvider.Common.DTO.GeofencePolygon`) is actually a bounding box (NorthWest + SouthEast corners), not an arbitrary polygon. There is no existing point-in-polygon implementation. - The frozen `_docs/02_document/contracts/data-access/tile-storage.md` v1.0.0 is the producer-side contract this work must implement (`source='uav'`, `captured_at` per-row, per-source UPSERT) - Lessons applied: L-001 (Dapper enum bypass — no new persisted enums planned but the rule applies if any sneak in) ## Specific unknowns to investigate ### Q1 — JWT auth middleware in ASP.NET Core 8 for an internal service (AZ-487) - Recommended pattern for an internal/trusted-network API that needs to validate HS256 tokens issued by an external system? - `Microsoft.AspNetCore.Authentication.JwtBearer` (built-in, recommended? version-pinned for ASP.NET Core 8) - vs. third-party (e.g., custom middleware, OpenIddict, etc.) - Configuration shape — where does the signing key live? appsettings vs env var vs key vault? (Project constraint: this codebase has no secret manager today.) - Required claims minimum: just `sub` and unexpired `exp` per user choice. Confirm `JwtBearerOptions.TokenValidationParameters` setup that validates *only* signature + `exp` and tolerates missing `aud`/`iss` (or reject — which is the safer ASP.NET Core 8 default?). - Swagger integration: how to surface "Bearer " in the Swagger UI for the upload endpoint without changing the existing public endpoints? - 401 response shape — does the global `GlobalExceptionHandler` need to know about it, or does `[Authorize]` short-circuit before the handler? - Test strategy — how do current `SatelliteProvider.IntegrationTests` (which call the live API via `HttpClient` per `RouteTestHelpers`) cleanly inject a valid JWT? Pattern for a "test issuer" using the same signing key. ### Q2 — Multipart batch upload in ASP.NET Core 8 minimal API (AZ-485) - Idiomatic shape for `multipart/form-data` with N files + a JSON metadata document in a minimal API endpoint: - `[FromForm]` model binding - vs. manual `MultipartReader` for streaming (relevant if batches are large) - Request size limits in ASP.NET Core 8 (Kestrel `MaxRequestBodySize` and form options) — defaults and how to override for the upload endpoint only. - Memory / streaming trade-off: how big can the per-batch `IFormFile[]` get before we should switch to streaming via `MultipartReader`? Practical batch-size guidance for satellite tiles (~50–500 KB each). - Response shape for partial-success batches — common patterns (HTTP 200 with mixed array, HTTP 207 Multi-Status, HTTP 422 with per-item errors). - Backpressure / concurrency limit on the per-tile persistence loop — should this be sequential or fanned out? Trade-off vs. Postgres connection pool size (current Npgsql defaults). ### Q3 — Image quality heuristics for satellite tiles using ImageSharp 3.1.11 (AZ-486) - API surface in **ImageSharp 3.1.11 specifically** for: - Reading width/height without full decode (`Image.IdentifyAsync` returns `ImageInfo`) - Detecting actual format from magic bytes vs. trusting `Content-Type` - Computing a "uniformity / blankness" metric efficiently — luminance stddev across the image, or histogram-based, or a perceptual hash - Performance budget: per-tile quality check ideally under ~10ms on a 256×256 JPEG. Confirm which ImageSharp APIs are streaming vs. full-decode. - Robustness against adversarial inputs — e.g., a 1-pixel image with a JPEG header. What does ImageSharp 3.1.11 throw, and is there a safe-decode pattern? - Threshold-tuning approach — recommended way to tune the blank-detection threshold against a small fixture set of real satellite tiles vs. uniform tiles, without having that fixture set today. ### Q4 — Geofence whitelist: bbox vs. polygon (AZ-488) - The codebase's existing geofence type is a **bounding box** (`NorthWest`+`SouthEast`). Two options for the AZ-488 whitelist: - **Option A**: reuse the existing bbox shape (simple, consistent with current code, but coarser — a UAV tile inside a non-rectangular real-world allowed area can't be expressed) - **Option B**: introduce a true polygon (lat/lon list) + ray-casting point-in-polygon, store as JSON in config. Requires writing the ray-cast utility (or pulling in `NetTopologySuite`). - Recommendation: which one is the right fit given that the user only said "geofence" without specifying shape and the existing code is bbox? Compare with NetTopologySuite as a third option. - Storage: where does the whitelist live? `appsettings.json` array, dedicated config file, dedicated DB table? - Performance: typical UAV batch is N tiles vs. M whitelist polygons — is M small enough (e.g., < 10 polygons, < 1000 vertices) that O(N×M) ray-cast per batch is fine? ### Q5 — Per-tile persistence loop: file-system + DB consistency (AZ-485) - Pattern for "write JPEG to disk, then write DB row" with rollback on either failure: - Two-phase: write file with a `.tmp` suffix, INSERT, then rename. On INSERT failure, delete `.tmp`. - vs. write DB row first (ON CONFLICT does UPSERT), then write file. On file failure, what happens to the row? (Could leave a row pointing at a missing file path — bad.) - Idempotency: if the same (lat, lon, zoom, size, source='uav') tile is uploaded twice, the per-source UPSERT already handles the DB side — but does the file get overwritten cleanly? Atomic file replace pattern. - Concurrency: two simultaneous batch uploads for adjacent tiles — any shared resource that needs locking? ## Out of scope for this research - Distributed UAV fleet management, queueing, or real-time streaming (these are upstream concerns the user has not raised) - Async/202 + status polling response model (user explicitly said sync 200) - Per-tile encryption, compression beyond JPEG, or alternate image formats - External IdP integration (HS256 with shared secret only) - UAV mission/operator/sensor metadata (user explicitly said "not necessary for now") ## Acceptance criteria for the research output The research deliverable (`solution_draft01.md`) must: 1. Recommend a concrete library + config approach for **Q1 (JWT)**. Include: package name + version, code shape for `builder.Services.AddAuthentication().AddJwtBearer(...)`, key storage recommendation, integration-test pattern. 2. Recommend a concrete request shape + size-limit approach for **Q2 (multipart)**. Include: `[FromForm]` vs streaming decision criteria, response shape choice with rationale, concurrency-limit recommendation. 3. Recommend a concrete API call sequence in **ImageSharp 3.1.11** for **Q3 (quality heuristics)**. Include: blank-detection threshold starting point + tuning plan, performance budget validation, error-handling pattern. 4. Recommend bbox-vs-polygon-vs-NTS for **Q4 (geofence)**. Include: exact-fit verdict per the project's actual operating context (M < 10 polygons, simple ray-cast acceptable, or pull in NTS). 5. Recommend a file+DB consistency pattern for **Q5**. Include: failure modes covered, rollback shape, idempotency story. 6. Include a "do not use" / rejected list for each question — alternatives the research considered and rejected with one-line evidence. ## Execution mode Mode A — Initial Research. Output class: **Technical-component selection** (recommends specific libraries, ASP.NET Core 8 modes, ImageSharp APIs, optional NTS). Per-mode API capability verification applies to every recommended library. Saved Minimum Viable Examples required for: `Microsoft.AspNetCore.Authentication.JwtBearer` JWT validation, ASP.NET Core 8 minimal-API multipart, ImageSharp 3.1.11 luminance stddev.