mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 22:41:14 +00:00
[AZ-808] [AZ-809] [AZ-810] [AZ-811] [AZ-812] Cycle 8 close
Closes cycle 8 (Strict input validation across every public API endpoint). After 4 batches, every JSON-body, multipart-envelope, and query-parameter endpoint rejects unknown fields, missing required axes, type mismatches, and business-rule violations BEFORE the handler runs, all surfacing RFC 7807 ValidationProblemDetails per error-shape.md v1.0.0. Artifacts: - cumulative_review_batches_01-04_cycle8_report.md PASS_WITH_WARNINGS. Cross-batch consistency check across all 5 cycle-8 tasks: 0 Critical / 0 High / 0 Medium / 4 Low (all surfaced as per-batch findings; no NEW cumulative-only categories). 5 follow-up PBI candidates surfaced (test-helper consolidation, validator filter decision matrix in docs, RoutePointDto wire-shape unification, service-layer RouteValidator retirement decision). - implementation_completeness_cycle8_report.md PASS. Every cycle-8 task promise is implemented as production behaviour. Production code verified for scaffold / placeholder / NotImplemented markers: none found in any cycle-8 validator. All five pipelines (region POST, lat/lon GET, route POST, upload POST, inventory POST) WIRED. - implementation_report_strict_validation_cycle8.md Final cycle implementation report. 41 / 41 ACs covered across 5 tasks (AZ-812, AZ-808, AZ-811, AZ-809, AZ-810). 63 new unit test methods + 52 new integration test methods + 4 new curl probe scripts + 3 new contract docs (region-request, tile-latlon, route-creation) + 1 contract version bump (uav-tile-upload v1.1.0 -> v1.2.0). Handoff to autodev Step 11 (Run Tests) documented. Autodev state transitions Step 10 (Implement) -> Step 11 (Run Tests). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# Product Implementation Completeness Gate — Cycle 8
|
||||
|
||||
**Cycle**: 8
|
||||
**Date**: 2026-05-23
|
||||
**Scope**: AZ-812, AZ-808, AZ-811, AZ-809, AZ-810 (4 batches; cycle theme: strict input validation at every public API endpoint)
|
||||
|
||||
## Inputs Reviewed
|
||||
|
||||
- `_docs/02_tasks/done/AZ-812_region_field_rename_to_osm.md`
|
||||
- `_docs/02_tasks/done/AZ-808_region_endpoint_validation.md`
|
||||
- `_docs/02_tasks/done/AZ-811_latlon_get_endpoint_validation.md`
|
||||
- `_docs/02_tasks/done/AZ-809_route_endpoint_validation.md`
|
||||
- `_docs/02_tasks/done/AZ-810_upload_metadata_validation.md`
|
||||
- `_docs/02_document/architecture.md`
|
||||
- `_docs/02_document/system-flows.md`
|
||||
- `_docs/02_document/module-layout.md`
|
||||
- `_docs/02_document/modules/api_program.md`
|
||||
- `_docs/02_document/contracts/api/region-request.md` v1.0.0 (this cycle)
|
||||
- `_docs/02_document/contracts/api/tile-latlon.md` v1.0.0 (this cycle)
|
||||
- `_docs/02_document/contracts/api/route-creation.md` v1.0.0 (this cycle)
|
||||
- `_docs/02_document/contracts/api/uav-tile-upload.md` v1.2.0 (this cycle)
|
||||
- `_docs/02_document/contracts/api/error-shape.md` v1.0.0 (cycle 7)
|
||||
- `_docs/03_implementation/batch_0{1,2,3,4}_cycle8_report.md`
|
||||
- `_docs/03_implementation/reviews/batch_0{1,2,3,4}_cycle8_review.md`
|
||||
- `_docs/03_implementation/cumulative_review_batches_01-04_cycle8_report.md`
|
||||
- Source code under each task's ownership envelope (`SatelliteProvider.Api/Validators/*`, `SatelliteProvider.Api/Program.cs`, `SatelliteProvider.Common/DTO/{RequestRegionRequest, CreateRouteRequest, RoutePoint, GeofencePolygon, GeoPoint, UavTileMetadata}.cs`, `SatelliteProvider.Api/DTOs/GetTileByLatLonQuery.cs`)
|
||||
|
||||
## Per-Task Classification
|
||||
|
||||
### AZ-812 — Region API field rename (Latitude/Longitude → Lat/Lon, OSM convention)
|
||||
|
||||
**Verdict**: PASS
|
||||
|
||||
Evidence (source code, not tests or reports):
|
||||
|
||||
- **`SatelliteProvider.Common/DTO/RequestRegionRequest.cs`** — record properties renamed from `Latitude`/`Longitude` to `Lat`/`Lon`. `[JsonPropertyName("lat")]` and `[JsonPropertyName("lon")]` attributes attached so the wire format is exactly `{"lat":..,"lon":..}`. Verified at the source.
|
||||
- **`SatelliteProvider.Api/Program.cs::RequestRegion` handler** — accesses `req.Lat`/`req.Lon` instead of the pre-cycle-8 `Latitude`/`Longitude`. Verified by grep.
|
||||
- **`scripts/run-performance-tests.sh`** — PT-03/04/05/07 JSON bodies use `{"lat":..,"lon":..}` after the rename.
|
||||
|
||||
Search for unresolved markers in modified source: no `placeholder` / `TODO` / `NotImplemented` / `scaffold` / `fake` matches.
|
||||
|
||||
End-to-end production pipeline check: `POST /api/satellite/request` accepts `{"lat":..,"lon":..}`, deserializes to `RequestRegionRequest`, handler reads `req.Lat`/`req.Lon`, downstream `IRegionService` + `IRegionRequestQueue` enqueues + returns the region ID. The legacy `{"latitude":..,"longitude":..}` shape is rejected at the deserializer level via `UnmappedMemberHandling.Disallow` (cycle 7). No mocks, no scaffolded fallbacks.
|
||||
|
||||
### AZ-808 — Region POST strict validation
|
||||
|
||||
**Verdict**: PASS
|
||||
|
||||
Evidence (source code, not tests or reports):
|
||||
|
||||
- **`SatelliteProvider.Api/Validators/RegionRequestValidator.cs`** — FluentValidation `AbstractValidator<RequestRegionRequest>` with 6 rules: `Id` non-empty, `Lat` ∈ [-90, 90], `Lon` ∈ [-180, 180], `SizeMeters` ∈ [100, 10000], `ZoomLevel` ∈ [0, 22], `StitchTiles` is bool (handled via `[JsonRequired]`).
|
||||
- **`SatelliteProvider.Common/DTO/RequestRegionRequest.cs`** — `[JsonRequired]` on `Id`, `Lat`, `Lon`, `SizeMeters`, `ZoomLevel`, `StitchTiles` (verified via earlier session reads).
|
||||
- **`SatelliteProvider.Api/Program.cs:252`** — `.WithValidation<RequestRegionRequest>()` chained onto the `MapPost("/api/satellite/request", ...)` endpoint. Verified via Grep.
|
||||
|
||||
Search for unresolved markers: no matches in `RegionRequestValidator.cs`.
|
||||
|
||||
End-to-end production pipeline check: any invalid `POST /api/satellite/request` (out-of-range, missing field, unknown field, type mismatch) is rejected before the handler runs — the request never reaches `IRegionRequestQueue.EnqueueAsync` or any database operation. ValidationProblemDetails (RFC 7807) returned per `error-shape.md` v1.0.0.
|
||||
|
||||
### AZ-811 — lat/lon GET endpoint strict validation
|
||||
|
||||
**Verdict**: PASS
|
||||
|
||||
Evidence (source code, not tests or reports):
|
||||
|
||||
- **`SatelliteProvider.Api/DTOs/GetTileByLatLonQuery.cs`** — nullable record (`double? Lat`, `double? Lon`, `int? Zoom`) so missing values surface as null rather than the default-zero coercion the binder would otherwise apply. Required so the validator's `NotNull` rule can fire (instead of `NotNull` being shadowed by the default value).
|
||||
- **`SatelliteProvider.Api/Validators/GetTileByLatLonQueryValidator.cs`** — `CascadeMode.Stop` + `NotNull` + range checks for `Lat`/`Lon`/`Zoom`.
|
||||
- **`SatelliteProvider.Api/Validators/RejectUnknownQueryParamsEndpointFilter.cs`** — reusable filter that compares the request's query keys against an allow-list (`[lat, lon, zoom]`) and rejects unknown keys with the same `ValidationProblemDetails` shape.
|
||||
- **`SatelliteProvider.Api/Program.cs:212-218`** — `MapGet("/api/satellite/tiles/latlon", ...)` chain wires `.AddEndpointFilter(new RejectUnknownQueryParamsEndpointFilter(new[] { "lat", "lon", "zoom" }))` + `.WithValidation<GetTileByLatLonQuery>()` + `.Produces<DownloadTileResponse>(200)` + `.ProducesProblem(400)`. Verified via Grep.
|
||||
- **`SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs`** — describes the `lat`/`lon`/`zoom` query parameters in OpenAPI (post-rename).
|
||||
|
||||
Search for unresolved markers: no matches.
|
||||
|
||||
End-to-end production pipeline check: `GET /api/satellite/tiles/latlon?lat=...&lon=...&zoom=...` either (a) reaches the handler with non-null nullable values (validator passed) and the `.Value` deref drives `ITileService.DownloadTileAsync`, OR (b) is rejected at the filter chain with HTTP 400 + ValidationProblemDetails. No silent default-zero coercion. No mocks on the success path.
|
||||
|
||||
### AZ-809 — Route POST strict validation
|
||||
|
||||
**Verdict**: PASS
|
||||
|
||||
Evidence (source code, not tests or reports):
|
||||
|
||||
- **`SatelliteProvider.Common/DTO/{CreateRouteRequest, RoutePoint, GeofencePolygon, GeoPoint}.cs`** — `[JsonRequired]` annotations added to every non-optional axis. `RoutePoint` carries `[JsonPropertyName("lat")]`/`[JsonPropertyName("lon")]` for the OSM input wire.
|
||||
- **`SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs`** — 7 root rules (id non-empty + 4 range rules on `regionSizeMeters`/`zoomLevel` + `points` count + cross-field `createTilesZip ⇒ requestMaps`) + `RuleForEach(req => req.Points).SetValidator(new RoutePointValidator())` + `RuleForEach(req => req.Geofences!.Polygons).SetValidator(new GeofencePolygonValidator()).OverridePropertyName("geofences.polygons")`. The `OverridePropertyName` on the deep expression is documented inline because FluentValidation drops the parent path otherwise.
|
||||
- **`SatelliteProvider.Api/Validators/RoutePointValidator.cs`** — `OverridePropertyName("lat"/"lon")` chained after each range rule so error keys match the wire format.
|
||||
- **`SatelliteProvider.Api/Validators/GeofencePolygonValidator.cs`** — nested `GeoCornerValidator` (file-private) + cross-field NW-of-SE invariants on `Lat` (NW.Lat > SE.Lat) and `Lon` (NW.Lon < SE.Lon).
|
||||
- **`SatelliteProvider.Api/Program.cs:268`** — `.WithValidation<CreateRouteRequest>()` chained onto the `MapPost("/api/satellite/route", ...)` endpoint. Verified via Grep.
|
||||
|
||||
Search for unresolved markers: no matches.
|
||||
|
||||
End-to-end production pipeline check: any invalid `POST /api/satellite/route` is rejected before the handler runs. The handler delegates to `IRouteService.CreateRouteAsync` which (a) persists the route, (b) computes intermediate points via `GeoUtils.Interpolate`, (c) enqueues region requests if `requestMaps=true`. The validator runs strictly upstream of all three. The cross-field `NW.Lat > SE.Lat` rule prevents NaN-geometry payloads from reaching the interpolator. The pre-cycle-8 service-layer `RouteValidator` remains as a defence-in-depth backstop (documented in `route-creation.md` Validator Cleanup Advisory).
|
||||
|
||||
### AZ-810 — UAV upload metadata strict validation (multipart envelope)
|
||||
|
||||
**Verdict**: PASS
|
||||
|
||||
Evidence (source code, not tests or reports):
|
||||
|
||||
- **`SatelliteProvider.Common/DTO/UavTileMetadata.cs`** — `[JsonRequired]` on `Latitude`/`Longitude`/`TileZoom`/`TileSizeMeters`/`CapturedAt` (`UavTileMetadata` record) and `Items` (`UavTileBatchMetadataPayload` record). `FlightId` deliberately stays nullable per AZ-503 anonymous-flight semantics; file-comment block documents the AZ-810 rationale.
|
||||
- **`SatelliteProvider.Api/Validators/UavTileBatchMetadataPayloadValidator.cs`** — root validator: `Items` NotNull + NotEmpty + `Must(<= MaxBatchSize)` + `RuleForEach(p => p.Items).SetValidator(new UavTileMetadataValidator(qualityConfig, timeProvider))`. TimeProvider is threaded through to the per-item validator.
|
||||
- **`SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs`** — per-item validator: lat ∈ [-90, 90], lon ∈ [-180, 180], tileZoom ∈ [0, 22], tileSizeMeters > 0, capturedAt within `[now - MaxAgeDays, now + CapturedAtFutureSkewSeconds]`. `FlightId` intentionally not validated beyond JSON shape (rationale documented inline).
|
||||
- **`SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs`** — `IEndpointFilter` for the multipart endpoint. Reads the `metadata` form field, deserializes with the strict global `JsonSerializerOptions` (so `UnmappedMemberHandling.Disallow` applies), runs `IValidator<UavTileBatchMetadataPayload>`, then enforces `items.Count == files.Count`. FluentValidation errors prefixed with `metadata.` so wire key is `metadata.items[0].latitude` (full path).
|
||||
- **`SatelliteProvider.Api/Program.cs:128 + 239`** — `builder.Services.AddTransient<UavUploadValidationFilter>()` (line 128, transient lifetime: fresh instance per request; no shared mutable state to amortize) + `.AddEndpointFilter<UavUploadValidationFilter>()` (line 239 in the `MapPost("/api/satellite/upload", ...)` chain) + `.Accepts<UavTileBatchUploadRequest>("multipart/form-data")` + `.Produces<UavTileBatchUploadResponse>(200)` + `.ProducesProblem(400)`. Verified via Grep.
|
||||
|
||||
Search for unresolved markers: no matches.
|
||||
|
||||
End-to-end production pipeline check: any invalid `POST /api/satellite/upload` is rejected before the handler runs — the request never reaches `IUavTileUploadHandler.HandleAsync`. The downstream handler retains its own envelope checks as defence-in-depth (covers direct handler callers in unit tests). For valid requests, the multipart body is buffered once by `ReadFormAsync` and the cached `IFormCollection` is reused by the downstream handler (ASP.NET caches it on the request). Per-item `IUavTileQualityGate` remains the byte-level quality gate (unchanged from AZ-488).
|
||||
|
||||
## System Pipeline Audit
|
||||
|
||||
The cycle-8 work does NOT introduce new pipelines — it tightens the input validation on existing pipelines. The relevant production pipelines and their classifications:
|
||||
|
||||
| Pipeline | Cycle-8 touchpoint | Classification | Evidence |
|
||||
|----------|-------------------|----------------|----------|
|
||||
| `POST /api/satellite/request → IRegionRequestQueue → IRegionService` | AZ-808 validator + AZ-812 field rename added at the entry edge | WIRED | `Program.cs:252` (validator chain) + handler reads `req.Lat`/`req.Lon` (post-rename) |
|
||||
| `GET /api/satellite/tiles/latlon → ITileService.DownloadTileAsync` | AZ-811 validator + filter added at the entry edge | WIRED | `Program.cs:212-218` (validator + filter chain) + handler `.Value` deref |
|
||||
| `POST /api/satellite/tiles/inventory → ITileService.GetInventoryAsync` | Cycle 7 (`InventoryRequestValidator`) — not touched by cycle 8 | WIRED (pre-existing) | `Program.cs:227` (`.WithValidation<TileInventoryRequest>()`) |
|
||||
| `POST /api/satellite/route → IRouteService.CreateRouteAsync` | AZ-809 validator chain added at the entry edge | WIRED | `Program.cs:268` (validator chain) + cross-field invariants + nested DTO chain |
|
||||
| `POST /api/satellite/upload → IUavTileUploadHandler.HandleAsync` | AZ-810 multipart filter + validator added at the entry edge | WIRED | `Program.cs:128 + 239` (DI registration + endpoint chain) |
|
||||
|
||||
No pipeline is `PARTIALLY WIRED` or `NOT WIRED`. Every pipeline has its full validator chain in production code; the handlers are unchanged behaviorally (they retain pre-cycle-8 logic plus, where applicable, defence-in-depth backstops).
|
||||
|
||||
## Gate Verdict: PASS
|
||||
|
||||
Every promise from the 5 cycle-8 task specs is implemented as production behaviour.
|
||||
|
||||
- No FAIL.
|
||||
- No BLOCKED.
|
||||
- No PARTIALLY WIRED.
|
||||
- No remediation tasks required.
|
||||
- Proceed to /implement Step 16 (Final Test Run). Per the existing-code flow, the next autodev step (Step 11 — Run Tests) owns the full-suite gate, so /implement Step 16 hands off to autodev Step 11 rather than re-running the suite.
|
||||
|
||||
## Files / Symbols Checked
|
||||
|
||||
Production code:
|
||||
|
||||
- `SatelliteProvider.Api/Program.cs` (DI registrations + endpoint chains — lines 100-128 + 208-280)
|
||||
- `SatelliteProvider.Api/Validators/RegionRequestValidator.cs` (AZ-808)
|
||||
- `SatelliteProvider.Api/Validators/GetTileByLatLonQueryValidator.cs` (AZ-811)
|
||||
- `SatelliteProvider.Api/Validators/RejectUnknownQueryParamsEndpointFilter.cs` (AZ-811)
|
||||
- `SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs` (AZ-809)
|
||||
- `SatelliteProvider.Api/Validators/RoutePointValidator.cs` (AZ-809)
|
||||
- `SatelliteProvider.Api/Validators/GeofencePolygonValidator.cs` (AZ-809)
|
||||
- `SatelliteProvider.Api/Validators/UavTileBatchMetadataPayloadValidator.cs` (AZ-810)
|
||||
- `SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs` (AZ-810)
|
||||
- `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs` (AZ-810)
|
||||
- `SatelliteProvider.Api/DTOs/GetTileByLatLonQuery.cs` (AZ-811)
|
||||
- `SatelliteProvider.Common/DTO/RequestRegionRequest.cs` (AZ-812 + AZ-808)
|
||||
- `SatelliteProvider.Common/DTO/{CreateRouteRequest, RoutePoint, GeofencePolygon, GeoPoint}.cs` (AZ-809)
|
||||
- `SatelliteProvider.Common/DTO/UavTileMetadata.cs` (AZ-810)
|
||||
- `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs` (AZ-811)
|
||||
|
||||
Cross-task scaffold-marker search (`rg -i 'placeholder|TODO|NotImplemented|scaffold|fake'` against `SatelliteProvider.Api/Validators/`): no matches in any cycle-8 production validator. The only `return null` is in `GlobalValidatorConfig.cs:24` (cycle 7), inside the `PropertyNameResolver` callback where returning null means "use the default name policy" — that is the documented sentinel value, not a stub.
|
||||
|
||||
Cross-cycle architectural compliance: every cycle-8 production code addition lives in the cycle's existing ownership layer (`SatelliteProvider.Api/Validators/` for validators + filters, `SatelliteProvider.Common/DTO/` for DTOs). No public-API surface expansion in lower layers. No new cross-component dependencies.
|
||||
Reference in New Issue
Block a user