# Batch Report **Batch**: 03 (cycle 8) **Tasks**: AZ-809 (POST /api/satellite/route strict validation) **Date**: 2026-05-22 ## Task Results | Task | Status | Files Modified | Tests | AC Coverage | Issues | |------|--------|---------------|-------|-------------|--------| | AZ-809_route_endpoint_validation | Done | 18 files (8 new) | smoke pass (mode=smoke, exit 0); 16 integration tests + 26 validator unit tests added | 9/9 ACs covered | 1 Low (in-flight `OverridePropertyName` on deep expression — root-caused, documented, captured as advisory) | ## AC Test Coverage (9/9 ACs) | AC | Coverage | |----|----------| | AC-1 | All 14 documented rules enforced. Deserializer: missing `[JsonRequired]` axes (`id`, `name`, `regionSizeMeters`, `zoomLevel`, `points`, `requestMaps`, `createTilesZip`, per-point `lat`/`lon`, per-polygon `northWest`/`southEast`, per-corner `lat`/`lon`, `geofences.polygons`) + unknown-field rejection + type-mismatch. FluentValidation: non-zero `id`, name+description length, `regionSizeMeters` ∈ \[100, 10000\], `zoomLevel` ∈ \[0, 22\], `points` count ∈ \[2, 500\], per-point lat/lon ranges, per-polygon NW-of-SE invariants, cross-field `createTilesZip ⇒ requestMaps`. Each rule has at least one positive + one negative integration test. | | AC-2 | Happy path: `CreateRouteValidationTests.HappyPath_Returns200` (well-formed body, requestMaps=false → no background side effects) returns HTTP 200. Smoke green. | | AC-3 | Wired via `.WithValidation()` + `.Accepts<>` + `.Produces<>` + `.ProducesProblem(400)` in `Program.cs` MapPost chain. | | AC-4 | `[JsonRequired]` added to every non-optional axis on `CreateRouteRequest`, `RoutePoint`, `Geofences`, `GeofencePolygon`, `GeoPoint`. Tested by `EmptyBody_Returns400`, `MissingId_Returns400`, `MissingRequestMaps_Returns400`, and the nested type-mismatch `PointsLatTypeMismatch_Returns400`. | | AC-5 | Unit tests in `SatelliteProvider.Tests/Validators/` — `CreateRouteRequestValidatorTests.cs` (16 methods), `RoutePointValidatorTests.cs` (4 methods), `GeofencePolygonValidatorTests.cs` (6 methods). Cover each rule with positive + negative cases. | | AC-6 | Integration tests `SatelliteProvider.IntegrationTests/CreateRouteValidationTests.cs` — 16 methods covering happy path + 15 failure modes (one per rule); all green in smoke. | | AC-7 | New contract `_docs/02_document/contracts/api/route-creation.md` v1.0.0 published. References `error-shape.md` v1.0.0 + the nested DTO chain. Documents the `RoutePoint` (input `lat`/`lon`) vs `RoutePointDto` (output `latitude`/`longitude`) naming asymmetry as an advisory. | | AC-8 | Probe script `scripts/probe_route_validation.sh` covers happy + each failure mode via `curl`. | | AC-9 | `CreateRouteRequestValidator` chains `RoutePointValidator` (via `RuleForEach`) and `GeofencePolygonValidator` (via `RuleForEach` inside `When(Geofences is not null)`). Cross-field invariants on the root (`createTilesZip ⇒ requestMaps`) and per-polygon (`NW.Lat > SE.Lat`, `NW.Lon < SE.Lon`). Defence-in-depth: the legacy `RouteValidator` in `SatelliteProvider.Services.RouteManagement` still runs in the service layer as a backstop; advisory clean-up documented in `route-creation.md`. | ## Code Review Verdict: PASS_WITH_NOTES See `_docs/03_implementation/reviews/batch_03_cycle8_review.md` for the single Low finding (deep-expression `OverridePropertyName`, root-caused and documented inline). ## Auto-Fix Attempts: 1 (mid-batch) - Initial `RoutePointValidator` used `OverridePropertyName("lat")` BEFORE `.InclusiveBetween()`. Build failed with `CS0411: cannot infer type arguments for OverridePropertyName` because FluentValidation's `OverridePropertyName` extension is defined on `IRuleBuilderOptions` — the type only becomes inferable after the first concrete rule (which supplies `TProperty`). Reordered to chain after `InclusiveBetween().WithMessage(...).OverridePropertyName(...)`. Documented in-file so the chain order is not "simplified" by a future reader. - Initial `CreateRouteRequestValidator` used `RuleFor(req => req.Geofences!.Polygons)` and `RuleForEach(req => req.Geofences!.Polygons)` without `OverridePropertyName`. Smoke run unit tests failed: error keys came out as `polygons` and `polygons[0].northWest` (leaf-only), not the full wire path `geofences.polygons` / `geofences.polygons[0].northWest`. Root cause: FluentValidation's default property-name policy drops the parent on deep member expressions. Fix: chain `.OverridePropertyName("geofences.polygons")` on both `RuleFor` and `RuleForEach` rules; documented inline. Smoke re-run after fix: all green. ## Stuck Agents: None ## Files Modified ### AZ-809 (route-creation validator) | Path | Kind | |------|------| | `SatelliteProvider.Common/DTO/CreateRouteRequest.cs` | `[JsonRequired]` on id/name/regionSizeMeters/zoomLevel/points/requestMaps/createTilesZip | | `SatelliteProvider.Common/DTO/RoutePoint.cs` | `[JsonRequired]` on Latitude/Longitude | | `SatelliteProvider.Common/DTO/GeofencePolygon.cs` | `[JsonRequired]` on NorthWest/SouthEast in `GeofencePolygon`; `[JsonRequired]` on `Polygons` in `Geofences` | | `SatelliteProvider.Common/DTO/GeoPoint.cs` | `[JsonRequired]` on Lat/Lon | | `SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs` | **NEW** — root validator with `RuleForEach` chaining + `OverridePropertyName` on the geofences chain | | `SatelliteProvider.Api/Validators/RoutePointValidator.cs` | **NEW** — per-point lat/lon range; `OverridePropertyName("lat"/"lon")` aligns error keys with the wire format | | `SatelliteProvider.Api/Validators/GeofencePolygonValidator.cs` | **NEW** — per-polygon corner range checks + NW-of-SE invariants | | `SatelliteProvider.Api/Program.cs` | `.WithValidation()` + `.Accepts<>` + `.Produces<>` + `.ProducesProblem(400)` on the route POST endpoint | | `SatelliteProvider.Tests/Validators/CreateRouteRequestValidatorTests.cs` | **NEW** — 16 unit tests | | `SatelliteProvider.Tests/Validators/RoutePointValidatorTests.cs` | **NEW** — 4 unit tests | | `SatelliteProvider.Tests/Validators/GeofencePolygonValidatorTests.cs` | **NEW** — 6 unit tests | | `SatelliteProvider.IntegrationTests/CreateRouteValidationTests.cs` | **NEW** — 16 integration tests (happy + 15 failure modes) | | `SatelliteProvider.IntegrationTests/Program.cs` | Wired into smoke + full suites | | `scripts/probe_route_validation.sh` | **NEW** — curl probes for every failure mode + happy path | | `_docs/02_document/contracts/api/route-creation.md` | **NEW** v1.0.0 — contract doc with nested DTO chain + test-cases table | | `_docs/02_document/modules/api_program.md` | CreateRoute handler + Api/Validators (added AZ-809 section) | | `_docs/02_document/modules/common_dtos.md` | DTO descriptions updated with `[JsonRequired]` annotations | | `_docs/02_document/system-flows.md` | F4 (Route Creation) sequence diagram + Preconditions + Error Scenarios | | `_docs/02_document/tests/blackbox-tests.md` | BT-06 wire format clarification; BT-N03/BT-N04/BT-N05 references AZ-809 + error-shape contract | | `_docs/02_document/tests/security-tests.md` | SEC-04 references AZ-809 + GlobalExceptionHandler path | ## Tracker - AZ-809: To Do → In Progress (batch 3 start) → **In Testing** (post-smoke). ## Next Batch Batch 4: AZ-810 — UAV upload metadata validator (multipart envelope). The envelope shape is different from batch 2/3 (multipart vs JSON body), so the validator wiring is via the existing per-item `IUavTileQualityGate` + a new envelope-level FluentValidation rule set on `UavTileBatchMetadataPayload`. Defer non-trivial design choices (whether to keep the cycle-2 in-handler envelope checks as-is or migrate them) to the implementation step.