Files
satellite-provider/_docs/03_implementation/batch_04_cycle8_report.md
T
Oleksandr Bezdieniezhnykh b763da3f24
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-810] Clamp UAV test-fixture coordinates to OSM-valid range
The AZ-810 metadata validator rejects lat outside [-90, 90] and lon
outside [-180, 180]. Two NextTestCoordinate() helpers seeded their
counter from `(Ticks/TicksPerSecond) % 1_000_000` and returned
`60 + n*0.0005`, producing lat well above 90° for almost any seed
(e.g. n=200000 -> lat=160). Pre-AZ-810 there was no validator and no
DB constraint, so the out-of-range values were silently accepted; the
new validator (correctly) rejected them at HTTP 400.

Clamp both helpers to non-overlapping OSM-valid ranges:
  - UavUploadTests.cs:           lat in [50, 70),  lon in [10, 40)
  - UavUploadValidationTests.cs: lat in [-70, -50), lon in [-40, -10)

Non-overlap (not the prior +5_000_000 counter offset) is what now
guarantees AZ-488 and AZ-810 suites don't collide on the per-source
UNIQUE index when both run against the same DB.

No production code change; AZ-810 validator behaviour is unchanged.

Also:
- Correct AC-9 in batch_04_cycle8_report.md: the original claim
  ("verified by tracing source") was a false-PASS; the autodev
  Step 11 test run surfaced the gap. Now confirmed by full-suite
  green (scripts/run-tests.sh --full).
- Add ring-buffer lesson on AC-verification standards for input-
  validation changes: tracing fixture variables to their generators
  is insufficient; only a green integration-test run is sound
  evidence for a "no-regression" AC.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-23 14:20:45 +03:00

11 KiB

Batch Report

Batch: 04 (cycle 8) Tasks: AZ-810 (POST /api/satellite/upload strict metadata validation, multipart envelope) Date: 2026-05-23

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-810_upload_metadata_validation Done 12 files (5 new) 13 validator unit tests + 16 integration tests added; full integration-test pass deferred to autodev Step 11 (Run Tests) 9/9 ACs covered 2 Low (DRY in test helpers — FixedTimeProvider, PostBatch); 1 Info (metadata-key wire shape, documented)

AC Test Coverage (9/9 ACs)

AC Coverage
AC-1 All 14 documented rules enforced. Deserializer (rules 1, 12, 13, 14): [JsonRequired] on UavTileMetadata.{Latitude, Longitude, TileZoom, TileSizeMeters, CapturedAt} + UavTileBatchMetadataPayload.Items (missing axes); UnmappedMemberHandling.Disallow from cycle-7 (unknown root + nested fields); System.Text.Json standard type coercion (malformed flightId UUID, nested type-mismatch). Filter (rules 2, 3): UavUploadValidationFilter rejects missing metadata form field, malformed metadata JSON. FluentValidation (rules 4, 5, 7-11): UavTileBatchMetadataPayloadValidator (items empty / over cap / per-item dispatch via RuleForEach) + UavTileMetadataValidator (lat/lon/tileZoom ranges, tileSizeMeters > 0, capturedAt freshness window). Cross-field (rule 6): items.Count == files.Count enforced after the per-payload validator. Each rule has at least one positive + one negative integration test.
AC-2 Happy path: UavUploadValidationTests.HappyPath_Returns200 (well-formed metadata + 1 valid file) returns HTTP 200. AZ-488 happy paths (UavUploadTests.SingleItemValidJpeg_Returns200, multi-item batch, multi-source upserts) all use metadata that passes the new validator — verified by tracing each AZ-488 payload against the new rules. Full integration-test run gating deferred to autodev Step 11.
AC-3 Validators in own files: SatelliteProvider.Api/Validators/UavTileBatchMetadataPayloadValidator.cs + SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs. Unit tests in SatelliteProvider.Tests/Validators/UavTileBatchMetadataPayloadValidatorTests.cs (4 methods) + SatelliteProvider.Tests/Validators/UavTileMetadataValidatorTests.cs (9 methods) = 13 total (≥11 required).
AC-4 Integration tests in SatelliteProvider.IntegrationTests/UavUploadValidationTests.cs — 16 methods (≥13 required): happy + 15 failure modes covering rules 2-14 + AC-4-mandated nested type-mismatch.
AC-5 Contract _docs/02_document/contracts/api/uav-tile-upload.md bumped v1.1.0 → v1.2.0. New "Metadata validation" section enumerates all 14 rules, the three enforcement layers (deserializer / FluentValidation / cross-field), and the error-shape mapping. v1.2.0 changelog entry references AZ-810.
AC-6 _docs/02_document/modules/api_program.md::POST /api/satellite/upload endpoint description updated; Api/Validators section gained entries for UavTileBatchMetadataPayloadValidator, UavTileMetadataValidator, UavUploadValidationFilter; Common/DTO (AZ-488) updated to note [JsonRequired] additions; DI Registration list gained the UavUploadValidationFilter transient registration.
AC-7 [JsonRequired] annotations on UavTileMetadata + UavTileBatchMetadataPayload propagate to Swashbuckle's OpenAPI as required: [latitude, longitude, tileZoom, tileSizeMeters, capturedAt] and required: [items]. Endpoint chain in Program.cs declares .Accepts<UavTileBatchUploadRequest>("multipart/form-data") + .Produces<UavTileBatchUploadResponse>(200) + .ProducesProblem(400). Explicit OpenAPI range annotations omitted per existing project pattern (FluentValidation messages convey the range to API consumers via ValidationProblemDetails.errors).
AC-8 Probe script scripts/probe_upload_validation.sh — happy + 14 failure modes via curl. Reuses probe_route_validation.sh structure (JWT mint, status-code assertion, --exit-on-fail driver).
AC-9 No regression in AZ-488: validator rules align with the field shape AZ-488 tests send (tileZoom = 18, tileSizeMeters = 200.0, capturedAt = UtcNow or recent past, items.Count ∈ [1, 100], no unknown fields). The defence-in-depth check (IUavTileQualityGate per-item rejects post-validator) is unchanged and still runs in the handler. Step 11 caveat (resolved): the integration test run exposed a latent bug in UavUploadTests.NextTestCoordinate — the pre-existing seed (Ticks/TicksPerSecond) % 1_000_000 produced latitudes far above 90° (e.g. n=200_000 → lat=160), which previously slipped through silently (no validator, no DB constraint) but AZ-810 correctly rejects. Fixed in UavUploadTests.cs (clamped to lat ∈ [50,70), lon ∈ [10,40)) and UavUploadValidationTests.cs (clamped to lat ∈ [-70,-50), lon ∈ [-40,-10) — non-overlapping range for per-source UNIQUE-index safety). No production code change; AZ-810 validator behaviour unchanged.

Code Review Verdict: PASS_WITH_WARNINGS

See _docs/03_implementation/reviews/batch_04_cycle8_review.md for the two Low findings (test-helper DRY: FixedTimeProvider duplicated across 4 test files; PostBatch duplicated across 2 integration suites) and one Info finding (metadata-key wire shape).

Cumulative Code Review: PASS_WITH_WARNINGS

See _docs/03_implementation/cumulative_review_batches_01-04_cycle8_report.md for the cycle-8 cross-batch consistency check. The cumulative scan surfaced no new finding categories beyond the per-batch reviews; the cycle-8 implementation phase is approved for closure.

Auto-Fix Attempts: 0

No mid-batch failures required auto-fix. The validator + filter design was straightforward because cycle 8 batches 02 + 03 had already established the wiring pattern (.WithValidation<T>() for JSON bodies; cycle-7 GlobalExceptionHandler for deserializer failures) — AZ-810's only novel surface was the multipart endpoint filter, which composed cleanly with the existing infrastructure.

Stuck Agents: None

Files Modified

AZ-810 (UAV upload validator + multipart filter)

Path Kind
SatelliteProvider.Common/DTO/UavTileMetadata.cs [JsonRequired] on Latitude/Longitude/TileZoom/TileSizeMeters/CapturedAt (UavTileMetadata record) and Items (UavTileBatchMetadataPayload record). FlightId stays nullable per AZ-503 anonymous-flight semantics. File-comment block updated with the AZ-810 rationale.
SatelliteProvider.Api/Validators/UavTileBatchMetadataPayloadValidator.cs NEW — root validator: Items NotNull + NotEmpty + Must(<= MaxBatchSize) + RuleForEach.SetValidator(new UavTileMetadataValidator(...)). TimeProvider threaded through to the per-item validator.
SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs NEW — per-item validator: lat ∈ [-90, 90], lon ∈ [-180, 180], tileZoom ∈ [0, 22], tileSizeMeters > 0, capturedAt within [now - MaxAgeDays, now + CapturedAtFutureSkewSeconds]. FlightId deliberately not validated (shape-only via the deserializer).
SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs NEWIEndpointFilter for the multipart endpoint. Reads metadata form field, deserializes with the strict global JsonSerializerOptions, runs the validator, enforces items.Count == files.Count. FluentValidation errors prefixed with metadata. so the wire key is metadata.items[0].latitude. Manual ValidationProblemDetails on form-shape failures (missing form, missing field, malformed JSON, null payload).
SatelliteProvider.Api/Program.cs Registered UavUploadValidationFilter as transient (AddTransient<UavUploadValidationFilter>()); wired .AddEndpointFilter<UavUploadValidationFilter>() + .Accepts<UavTileBatchUploadRequest>("multipart/form-data") + .Produces<UavTileBatchUploadResponse>(200) + .ProducesProblem(400) onto the MapPost("/api/satellite/upload", ...) chain. Order: RequireAuthorization first, then AddEndpointFilter, then handler. Transient lifetime mirrors RejectUnknownQueryParamsEndpointFilter (each request gets a fresh instance; no shared mutable state to amortize).
SatelliteProvider.Tests/Validators/UavTileBatchMetadataPayloadValidatorTests.cs NEW — 4 unit tests covering: happy single-item, items NotEmpty, items count > MaxBatchSize, per-item failure propagation with indexed paths (items[1].latitude).
SatelliteProvider.Tests/Validators/UavTileMetadataValidatorTests.cs NEW — 9 unit tests covering: all valid → pass, lat out of range, lon out of range, tileZoom out of range, tileSizeMeters non-positive, capturedAt future, capturedAt too old, flightId null → pass, flightId set → pass. Uses local FixedTimeProvider (see review F1 for DRY follow-up).
SatelliteProvider.IntegrationTests/UavUploadValidationTests.cs NEW — 16 end-to-end tests against the live endpoint. Happy + 15 failure modes (rules 2-14 + AC-4 nested type-mismatch). Uses ProblemDetailsAssertions.AssertValidationProblem + AssertErrorsContainsMention.
SatelliteProvider.IntegrationTests/Program.cs Wired UavUploadValidationTests.RunAll into BOTH the smoke and the full suites (matches batch-2/3 cycle-8 pattern).
scripts/probe_upload_validation.sh NEW — bash + curl probe of happy + 14 failure modes. Reuses probe_route_validation.sh structure (JWT mint, status-code assertion driver).
_docs/02_document/contracts/api/uav-tile-upload.md Version bumped v1.1.0 → v1.2.0. New "Metadata validation" section (the 14 rules + 3 enforcement layers + error-shape mapping). Expanded "HTTP 400 — envelope error" section with the new failure shapes. v1.2.0 changelog entry.
_docs/02_document/modules/api_program.md POST /api/satellite/upload endpoint description updated; Api/Validators section gained 3 entries for the new files; Common/DTO (AZ-488) section gained a [JsonRequired] note; DI Registration list gained a UavUploadValidationFilter transient-registration entry.

Tracker

  • AZ-810: To Do → In Progress (batch 4 start) → In Testing (post-implementation, post-cumulative-review, pre-commit). The full-suite run in autodev Step 11 will ratify the In-Testing transition before the cycle-8 implementation report seals the cycle.

Next Batch

None — batch 4 was the final batch of cycle 8. Cycle 8's strict-validation theme is fully wrapped:

Endpoint Validator Cycle 8 batch
POST /api/satellite/request RegionRequestValidator 02 (AZ-808)
POST /api/satellite/route CreateRouteRequestValidator + nested chain 03 (AZ-809)
POST /api/satellite/upload UavTileBatchMetadataPayloadValidator + UavUploadValidationFilter 04 (AZ-810)
GET /api/satellite/tiles/latlon GetTileByLatLonQueryValidator + RejectUnknownQueryParamsEndpointFilter 02 (AZ-811)
POST /api/satellite/tiles/inventory InventoryRequestValidator (cycle 7)
GET /api/satellite/region/{id} (read-only by path Guid; strict-validation N/A)
GET /api/satellite/route/{id} (read-only by path Guid; strict-validation N/A)

Implement skill should hand back to autodev for Step 11 (Run Tests) → Step 12 (tracker transition) → Step 13 (archive) → cycle implementation report → Step 14 loop exit.