# Cumulative Code Review — Batches 01–04 cycle 8

**Batch range**: 01-04 (cycle 8)
**Cycle**: 8 (Strict input validation across all public API endpoints)
**Date**: 2026-05-23
**Verdict**: PASS_WITH_WARNINGS
**Trigger**: Implement skill Step 14.5 (K=3 default → first cumulative review at batch 4 because the cycle ran 1→2→3→4 contiguously; review covers the full batch range since the cycle's first batch)

## Scope

| Batch | Tasks | Surfaces touched |
|-------|-------|------------------|
| 01 | AZ-812 | `SatelliteProvider.Common/DTO/RequestRegionRequest.cs`, `SatelliteProvider.Api/Program.cs`, `SatelliteProvider.IntegrationTests/{Models,RegionTests,IdempotentPostTests,SecurityTests,RegionFieldRenameTests,Program}.cs`, `scripts/run-performance-tests.sh`, `_docs/02_document/modules/{common_dtos,api_program}.md` |
| 02 | AZ-808 + AZ-811 | `SatelliteProvider.Api/Validators/{RegionRequestValidator,GetTileByLatLonQueryValidator,RejectUnknownQueryParamsEndpointFilter}.cs` (NEW), `SatelliteProvider.Api/DTOs/GetTileByLatLonQuery.cs` (NEW), `SatelliteProvider.Common/DTO/RequestRegionRequest.cs` (`[JsonRequired]`), `SatelliteProvider.Api/{Program,Swagger/ParameterDescriptionFilter}.cs`, `SatelliteProvider.Tests/Validators/{RegionRequestValidatorTests,GetTileByLatLonQueryValidatorTests,RejectUnknownQueryParamsEndpointFilterTests}.cs` (NEW), `SatelliteProvider.IntegrationTests/{RegionRequestValidationTests,GetTileByLatLonValidationTests,ProblemDetailsAssertions,TileInventoryValidationTests,RegionFieldRenameTests,TileTests,JwtIntegrationTests,SecurityTests,Program}.cs`, `scripts/{probe_region_validation,probe_latlon_validation,run-performance-tests}.sh`, `README.md`, `_docs/02_document/contracts/api/{region-request,tile-latlon}.md` (NEW v1.0.0), `_docs/02_document/modules/{api_program,common_uuidv5}.md`, `_docs/02_document/{system-flows,tests/blackbox-tests,tests/security-tests}.md` |
| 03 | AZ-809 | `SatelliteProvider.Common/DTO/{CreateRouteRequest,RoutePoint,GeofencePolygon,GeoPoint}.cs` (`[JsonRequired]`), `SatelliteProvider.Api/Validators/{CreateRouteRequestValidator,RoutePointValidator,GeofencePolygonValidator}.cs` (NEW), `SatelliteProvider.Api/Program.cs`, `SatelliteProvider.Tests/Validators/{CreateRouteRequestValidatorTests,RoutePointValidatorTests,GeofencePolygonValidatorTests}.cs` (NEW), `SatelliteProvider.IntegrationTests/{CreateRouteValidationTests,Program}.cs`, `scripts/probe_route_validation.sh` (NEW), `_docs/02_document/contracts/api/route-creation.md` (NEW v1.0.0), `_docs/02_document/modules/{api_program,common_dtos}.md`, `_docs/02_document/{system-flows,tests/blackbox-tests,tests/security-tests}.md` |
| 04 | AZ-810 | `SatelliteProvider.Common/DTO/UavTileMetadata.cs` (`[JsonRequired]`), `SatelliteProvider.Api/Validators/{UavTileBatchMetadataPayloadValidator,UavTileMetadataValidator,UavUploadValidationFilter}.cs` (NEW), `SatelliteProvider.Api/Program.cs`, `SatelliteProvider.Tests/Validators/{UavTileBatchMetadataPayloadValidatorTests,UavTileMetadataValidatorTests}.cs` (NEW), `SatelliteProvider.IntegrationTests/{UavUploadValidationTests,Program}.cs`, `scripts/probe_upload_validation.sh` (NEW), `_docs/02_document/contracts/api/uav-tile-upload.md` (v1.1.0 → v1.2.0), `_docs/02_document/modules/api_program.md` |

## Phase-by-Phase Summary (cumulative)

### Phase 1: Context Loading

The 4 batches share a coherent theme — **strict input validation across every public API endpoint**, anchored on the cycle-7 (`tile-inventory.md` v2.0.0 + `error-shape.md` v1.0.0 + `InventoryRequestValidator` + `GlobalExceptionHandler`) infrastructure. The cycle covers the full surface:

| Endpoint | Method | Batch | Pattern | Result |
|---------|--------|-------|---------|--------|
| `POST /api/satellite/request` | JSON body | 02 (AZ-808) | `.WithValidation<RequestRegionRequest>()` | All inputs validated |
| `POST /api/satellite/route` | JSON body | 03 (AZ-809) | `.WithValidation<CreateRouteRequest>()` + nested DTO chain | All inputs validated |
| `POST /api/satellite/upload` | multipart | 04 (AZ-810) | `.AddEndpointFilter<UavUploadValidationFilter>()` | All inputs validated |
| `GET /api/satellite/tiles/latlon` | query params | 02 (AZ-811) | `.WithValidation<GetTileByLatLonQuery>() + RejectUnknownQueryParamsEndpointFilter` | All inputs validated |
| `POST /api/satellite/tiles/inventory` | JSON body | 07 (AZ-794+795+796) | `.WithValidation<TileInventoryRequest>()` | Pre-existing |
| `GET /api/satellite/region/{id}` | path Guid | n/a | Framework Guid coercion | Reads only — strict-validation N/A by design |
| `GET /api/satellite/route/{id}` | path Guid | n/a | Framework Guid coercion | Reads only — strict-validation N/A by design |

AZ-812 (batch 1) was the prerequisite renaming work that aligned `Region` input wire to OSM `lat`/`lon` — the same convention every subsequent cycle-8 batch standardized on.

### Phase 2: Spec Compliance

| Batch | ACs claimed | ACs covered | Spec gaps |
|-------|-------------|-------------|-----------|
| 01 (AZ-812) | 6 | 6 | 0 |
| 02 (AZ-808) | 8 | 8 | 0 |
| 02 (AZ-811) | 9 | 9 | 0 |
| 03 (AZ-809) | 9 | 9 | 0 |
| 04 (AZ-810) | 9 | 9 | 0 |
| **Total** | **41** | **41** | **0** |

Cumulative AC pass rate: 100 % across 41 acceptance criteria. All published contracts (`region-request.md` v1.0.0, `tile-latlon.md` v1.0.0, `route-creation.md` v1.0.0, `uav-tile-upload.md` v1.2.0) are internally consistent with each other and with `error-shape.md` v1.0.0.

### Phase 3: Code Quality (cumulative)

**Validator file inventory** (cycle-8 additions):

| File | Lines | RuleFor count | Cross-field rules | Status |
|------|-------|---------------|-------------------|--------|
| `RegionRequestValidator.cs` | ~45 | 6 | 0 | Clean, SRP |
| `GetTileByLatLonQueryValidator.cs` | ~30 | 3 | 0 | Clean, SRP |
| `RejectUnknownQueryParamsEndpointFilter.cs` | ~60 | n/a (filter, not validator) | n/a | Clean, reusable |
| `CreateRouteRequestValidator.cs` | ~95 | 7 | 1 (createTilesZip ⇒ requestMaps) | Clean, RuleForEach chains |
| `RoutePointValidator.cs` | ~40 | 2 | 0 | Clean (OverridePropertyName documented inline) |
| `GeofencePolygonValidator.cs` | ~60 | 4 | 2 (NW-of-SE corners) | Clean, nested GeoCornerValidator |
| `UavTileBatchMetadataPayloadValidator.cs` | ~50 | 3 + RuleForEach | 0 | Clean, SRP |
| `UavTileMetadataValidator.cs` | ~60 | 5 | 0 | Clean (FlightId deliberate no-op documented inline) |
| `UavUploadValidationFilter.cs` | ~120 | n/a (filter) | 1 (items.Count == files.Count) | Clean, SRP (parse → validate → cross-field) |

**Consistency observations**:

- All validators follow the cycle-7 pattern: file-private class, `AbstractValidator<T>`, `RuleFor` chains, `WithMessage(...)` carrying user-friendly text. Per-item `RuleForEach` uses `SetValidator(new ChildValidator(...))` consistently.
- `[JsonRequired]` placement on the DTO is the cycle-8 standard for "the deserializer rejects missing axes". Five DTOs got the annotation across the cycle (`RequestRegionRequest`, `CreateRouteRequest`, `RoutePoint`, `GeofencePolygon`/`GeoPoint`, `UavTileMetadata`/`UavTileBatchMetadataPayload`).
- `ArgumentNullException.ThrowIfNull` used consistently in validator constructors that take `IOptions<TConfig>`. Test fixtures supply test-only `Microsoft.Extensions.Options.Options.Create(new TConfig{...})`.
- No silent error suppression in any of the cycle's new code (verified by grepping the new files for `catch`/`empty/`).
- File-level XML/// comments are absent (project convention — DTOs and validators rely on filenames + brief in-file comment blocks). Where non-obvious decisions were made (`OverridePropertyName` ordering in `RoutePointValidator`, `FlightId` deliberate no-op in `UavTileMetadataValidator`, `metadata.` prefix in `UavUploadValidationFilter`), an inline comment captures the *why*.

### Phase 4: Security Quick-Scan (cumulative)

Cycle 8 is fundamentally a security cycle: it tightens every endpoint's input validation. Threat-model deltas:

- **Attack surface reduced**: Every public endpoint now rejects unknown fields, type mismatches, and out-of-range values BEFORE the handler runs. `UnmappedMemberHandling.Disallow` (cycle 7) is now backed by per-endpoint FluentValidation rules at all four POST/upload endpoints + the one GET query-param endpoint. Pre-cycle-8, a hostile caller could send `{"latitude": 91, "extra": "fingerprint"}` to `POST /api/satellite/request` and the handler would either silently ignore the extra field or crash on the bad latitude (sensitive log info). Now the request is rejected at the filter layer with a stable ValidationProblemDetails body.
- **DoS surface bounded**: Each list-bearing payload now has an explicit cap — `points.Count <= 500` (route), `items.Count <= 100` (UAV upload), `coords.Count <= 1000` (tile inventory, cycle 7). Multipart body size still bounded by Kestrel's `MaxRequestBodySize`.
- **Fingerprinting reduced**: Unknown-field rejection (via `UnmappedMemberHandling.Disallow`) prevents attackers from probing for hidden fields. Every validator produces an identically-shaped `ValidationProblemDetails` so error responses don't leak server state.
- **Auth model unchanged**: Cycle 8 did NOT change authn/authz — every endpoint retained its `RequireAuthorization(...)` chain. The validation filter runs AFTER authorization (no validator burns CPU for unauthenticated callers).
- **No new secrets**: Verified via grep for the cycle's diff (no API keys, no connection strings, no JWT secrets in code).
- **No new PII in logs**: Validators don't log payload contents. Exception handler logs only correlation IDs and exception types for 5xx, and for 4xx writes the ProblemDetails to the response body (caller's own input).

Net effect: cycle 8 closes a meaningful class of input-handling defects without introducing new attack surface.

### Phase 5: Performance Scan (cumulative)

- Per-request overhead: each validator runs in microseconds (in-memory rule checks against record fields). Worst case is `CreateRouteRequest` with `points.Count = 500` × per-point validator = ~1 ms estimated. UAV upload at `items.Count = 100` × per-item validator = ~200 µs. Neither approaches the cost of the downstream DB ops or tile downloads.
- Multipart endpoint: `UavUploadValidationFilter` calls `ReadFormAsync` once; the buffered form is reused by the downstream handler (ASP.NET caches `IFormCollection` on the request). Net cost: zero extra IO.
- No N+1, no blocking I/O, no synchronous DB calls in any validator.
- Pre-existing performance harness (`scripts/run-performance-tests.sh` PT-01..PT-07) was updated by AZ-812 (batch 1) to use the new `lat`/`lon` URL shape; PT thresholds were re-verified against the post-cycle-8 stack and remain green.

### Phase 6: Cross-Task Consistency (cumulative)

- **ProblemDetails / ValidationProblemDetails shape**: every cycle-8 endpoint produces the same RFC 7807 body per `error-shape.md` v1.0.0 — verified by both `ProblemDetailsAssertions.AssertValidationProblem` (status + title + errors object) and `AssertErrorsContainsMention` (substring-permissive match on either keys or messages). The shared helper was promoted to `ProblemDetailsAssertions.cs` in batch 2; batches 3 + 4 consume it without re-deriving local copies.
- **Error key naming**: all four batches follow the camelCase JSON-path convention (per `error-shape.md` Inv-4). Nested collections use indexed paths (`items[0].latitude`, `points[1].lon`, `geofences.polygons[0].northWest`). Where FluentValidation's default key would diverge from the wire (e.g. `Latitude` C# vs `lat` wire), an `OverridePropertyName` is applied — and the override is documented in code AND in `api_program.md` so a future reader cannot remove it by accident.
- **Cross-task collision check**: No two validators share a class name. No two `MapPost` chains accidentally apply the same filter twice. No two contract docs reference each other circularly. No two `[JsonRequired]` placements conflict (each DTO is owned by exactly one cycle-8 task).
- **Test fixture consistency**: `ProblemDetailsAssertions` is now the single source of truth for ProblemDetails shape assertions across all four batches (batch 1's `RegionFieldRenameTests` was migrated to use it in batch 2; batches 3 + 4 used it from day one). `JwtTestHelpers` (cycle 3) was unchanged.
- **Contract version coherence**: `region-request.md` v1.0.0, `tile-latlon.md` v1.0.0, `route-creation.md` v1.0.0, `uav-tile-upload.md` v1.2.0 — all reference `error-shape.md` v1.0.0. The version-bump on UAV upload (vs the v1.0.0 baseline for the three other new contracts) reflects that UAV upload had a pre-existing v1.1.0 contract from AZ-488 + AZ-503; the cycle-8 changes were additive (no breaking changes to the v1.1.0 shape).

### Phase 7: Architecture Compliance (cumulative)

- **Layer direction**: No cross-component dependencies added or removed. New validators + filters live in `SatelliteProvider.Api/Validators/` (Layer 4 = WebApi). New `[JsonRequired]` attributes touch DTOs in `SatelliteProvider.Common/DTO/` (Layer 0 = Common). `SatelliteProvider.Common` does not depend on FluentValidation — the attribute is `System.Text.Json.Serialization.JsonRequiredAttribute`, no new package reference needed.
- **Public API respect**: No internal symbols newly exposed. DTOs were already public (cycle-2 + cycle-5 + cycle-6 work). Validators are internal-by-default (file-private class) — only `IValidator<T>` resolves via DI.
- **No cycles**: dependency graph for the cycle-8 work:
  - `SatelliteProvider.Common` → (FluentValidation? NO — only `System.Text.Json.Serialization`)
  - `SatelliteProvider.Api/Validators/*` → (`FluentValidation`, `Microsoft.Extensions.Options`, `Common.DTO`, `Common.Configs`) — no cycle.
  - `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs` → (`FluentValidation`, `Microsoft.AspNetCore.Http`, `Common.DTO`) — no cycle.
- **DI surface**: `AddValidatorsFromAssemblyContaining<Program>()` (cycle 7) discovers the new validators automatically. The `UavUploadValidationFilter` is registered as transient (matches the existing endpoint-filter registration in batch 2 cycle 8 for `RejectUnknownQueryParamsEndpointFilter`).
- **Documentation alignment**: `_docs/02_document/modules/api_program.md` was updated in all four batches; the cumulative diff is internally consistent (no contradictory descriptions, no overlapping section headers, no broken cross-references). `_docs/02_document/contracts/api/` gained three new files (`region-request.md`, `tile-latlon.md`, `route-creation.md`) and one bumped file (`uav-tile-upload.md`). `_docs/02_document/system-flows.md` F1/F2/F4 were updated to reflect the validator filter step.
- **No ADRs to breach**: the project has no `_docs/02_document/adr/` folder (verified via Glob). Future architectural decisions about validator placement / endpoint-filter ordering would warrant an ADR, but the cycle-8 work is convention-following, not convention-setting.

## Baseline Delta (cumulative)

| Class | Count | Notes |
|-------|-------|-------|
| Carried over | 0 | Cycle-7 retro had no Architecture-class entries to carry; cycle-1 baseline empty |
| Resolved | 0 | None — cycle 8 is strictly additive |
| Newly introduced | 1 | F1 in batch 4: `FixedTimeProvider` duplication has crossed the cycle-2-advisory "promote to shared" threshold (3+ consumers). Tracked as a Low-priority follow-up PBI. |

## Cumulative Findings (new this cycle)

Per-batch findings are listed in their respective `reviews/batch_NN_cycle8_review.md` files. The cumulative scan surfaces **no NEW finding categories** beyond what the per-batch reviews already captured. The cumulative-only observations are:

1. **DRY threshold crossed for `FixedTimeProvider` test helper** (Low / Maintainability, traced from batch 4 F1)
   - Cycle 2 introduced `FixedTimeProvider` in two test files (`UavTileQualityGateTests`, `UavTileUploadHandlerTests`) with a file-comment advisory: "if a third consumer appears, promote to `SatelliteProvider.TestSupport`."
   - Cycle 8 batch 4 added two more consumers (`UavTileBatchMetadataPayloadValidatorTests`, `UavTileMetadataValidatorTests`). Total = 4.
   - Recommended action: open follow-up PBI "Promote `FixedTimeProvider` test helper to `SatelliteProvider.TestSupport`" (≈1 SP, mechanical).

2. **`PostBatch` multipart helper duplicated across integration test suites** (Low / Maintainability, traced from batch 4 F2)
   - `UavUploadTests.cs` (cycle 2) and `UavUploadValidationTests.cs` (cycle 8 batch 4) both define an identical `PostBatch(client, metadata, files)` helper.
   - Recommended action: bundle with item 1 above into a single "test helper consolidation" follow-up PBI, OR open as a separate ≈1 SP PBI.

3. **Wire-shape input/output naming asymmetry on the route endpoints** (Info / Wire-shape asymmetry, traced from batch 3 F3)
   - Cycle 8 standardized `RoutePoint` input wire on OSM `lat`/`lon` (via `[JsonPropertyName]` on `RoutePoint`).
   - The corresponding response DTO `RoutePointDto` still serializes its underlying C# `Latitude`/`Longitude` properties verbatim.
   - This asymmetry is pre-existing; AZ-809 documented it in `route-creation.md` but did not unify (would be a breaking change to existing clients of `GET /api/satellite/route/{id}`).
   - Recommended action: open a successor PBI (cycle 9 candidate) to consider unifying via a `lat`/`lon` rename on `RoutePointDto` — would be a `route-creation.md` v2.0.0 + a corresponding integration-test migration. Coordinate with any external consumer of the GET response.

4. **Service-layer `RouteValidator` retention** (Info / Defence-in-depth, traced from batch 3 F2)
   - The pre-cycle-8 service-layer `RouteValidator` covers roughly the same surface as the new `CreateRouteRequestValidator`. The pre-cycle-8 path was kept as a defence-in-depth backstop in case some non-HTTP code path enqueues a route.
   - Recommended action: defer to a follow-up PBI (cycle 9 candidate). Cleanup is mechanical but needs verification that no background path bypasses the API layer.

5. **Validator filter taxonomy is now stable** (Info / Architecture)
   - Cycle 8 established three validator filter patterns:
     - JSON body → `.WithValidation<T>()` (cycle-7 generic filter; used by AZ-808 + AZ-809)
     - Multipart envelope → bespoke `UavUploadValidationFilter` (AZ-810)
     - Query parameters → `.WithValidation<TQuery>()` + `RejectUnknownQueryParamsEndpointFilter` + nullable DTO + `NotNull` + `CascadeMode.Stop` (AZ-811; pattern is reusable)
   - All three produce identically-shaped `ValidationProblemDetails` per `error-shape.md` v1.0.0.
   - Recommended action: codify the three patterns in `_docs/02_document/modules/api_program.md::Api/Validators` as a decision matrix so the next endpoint author knows which to use. (Already partially done — the existing section names each filter but does not present the matrix explicitly.)

## Recurring patterns to surface for cycle-8 retrospective

1. **The "publish a v1.0.0 contract per new endpoint" cadence is sustainable**: cycle 8 produced 3 new contract docs + 1 version bump in 4 batches, each one self-consistent with `error-shape.md` v1.0.0 and cross-referenced from the validator file. The new-task / decompose skills already point at this template; cycle 8 confirms it scales.
2. **`[JsonRequired]` + `UnmappedMemberHandling.Disallow` + FluentValidation is the canonical pattern**: every cycle-8 endpoint uses the three layers (deserializer rejects missing/unknown axes, FluentValidation rejects business-rule violations). Worth a one-paragraph entry in `_docs/02_document/architecture.md` so the pattern is discoverable by the next contributor.
3. **Probe scripts have proven valuable** as an out-of-process verification check during validator development: batches 02, 03, 04 each shipped a `probe_<endpoint>_validation.sh` script that exercises every failure mode via `curl`. Several cycle-8 mid-batch fixes (AZ-811 binder short-circuit, AZ-809 `OverridePropertyName` discovery) were found via probe scripts before the integration tests caught them.
4. **Mid-batch root-cause investigations were captured in the per-batch reports**: batch 2 (AZ-811 binder short-circuit) and batch 3 (`OverridePropertyName` quirk) both carry detailed "Auto-Fix Attempts" sections explaining the failure mode, the diagnostic step, and the fix. This is the pattern `coderule.mdc` "Debugging Over Contemplation" calls for — worth normalizing in the implement skill's batch-report template.

## Verdict Logic

- 0 Critical, 0 High, 0 Medium.
- 4 Low findings across the 4 batches (1 in batch 1, 0 in batch 2, 1 in batch 3, 2 in batch 4) — all surfaced as per-batch findings; cumulative scan found NO new categories beyond what each batch review already captured.
- 4 Info findings — all are pre-existing or design-decision items, all documented, all with clear follow-up PBI candidates.
- → **PASS_WITH_WARNINGS**.

## Recommendation to /implement

Cumulative review passes. All four batches of cycle 8 are accepted. **Cycle 8 implementation phase is complete** — implement skill should:

1. Commit batch 4 (AZ-810).
2. Transition AZ-810 → In Testing in tracker.
3. Archive AZ-810's task spec to `_docs/02_tasks/done/`.
4. Hand back to autodev orchestrator for Step 11 (Run Tests), which will run the full integration suite to ratify cycle 8 end-to-end before the cycle's implementation report is sealed.

Follow-up PBIs surfaced by this cumulative review (not blocking cycle-8 closure):

- (Low, ~1 SP) Promote `FixedTimeProvider` test helper to `SatelliteProvider.TestSupport`.
- (Low, ~1 SP) Promote `PostBatch` multipart helper to a shared `UavUploadMultipartFixture`.
- (Info, ~2 SP) Codify validator-filter decision matrix in `_docs/02_document/modules/api_program.md::Api/Validators`.
- (Info, ~3 SP — coordination required) Unify response-side `RoutePointDto` to use `lat`/`lon` wire keys (v2.0.0 of `route-creation.md`).
- (Info, ~2 SP) Decide whether to retire service-layer `RouteValidator` now that the API layer strictly validates.
