[AZ-357] Refactor C06: drop tile Version concept; cumulative review batches 7-9
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful

AZ-357 — eliminate year-based tile cache expiry (LF-1):
- Migration 012: drop 5-col unique index, dedupe by (lat,lon,zoom,
  size) keeping max(updated_at), add new 4-col unique index, make
  version column nullable + drop default. Column itself preserved
  per coderule (column drops require explicit confirmation; tracked
  in AZ-373 / C20).
- TileEntity.Version, TileMetadata.Version, DownloadTileResponse.
  Version: int -> int? (HTTP shape preserved; field still in JSON).
- TileService.DownloadAndStoreTilesAsync: drop currentVersion year
  computation and the .Where(t => t.Version == currentVersion)
  cache filter. BuildTileEntity: drop year arg; write Version=null.
- TileRepository: ON CONFLICT now 4-col; lookup queries
  ORDER BY updated_at DESC instead of version DESC.
- Tests: replace inverted BT02b with positive AZ357_AC1
  (prior-year cached tile is reused). Add BuildTileEntity_
  DoesNotPopulateVersion_AZ357 to enforce the no-write contract.
- 69 unit + 5 smoke + 3 stub-contract integration tests pass.

Cumulative code review (batches 7-9, 7 tasks): VERDICT=PASS.
Report at _docs/03_implementation/reviews/batch_09_review.md.
Zero Critical/High/Medium/Low findings. Architecture baseline
remains clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 00:20:47 +03:00
parent 5a28f67d33
commit 581dff206e
12 changed files with 306 additions and 41 deletions
@@ -0,0 +1,125 @@
# Code Review Report — Cumulative (Batches 7+8+9)
**Batch**: 9 (cumulative; covers batches 7, 8, 9)
**Tasks reviewed**: AZ-351, AZ-352, AZ-353, AZ-354, AZ-356, AZ-359, AZ-363
**Date**: 2026-05-10
**Mode**: Cumulative (Phases 17 on union of changed files since last review)
**Verdict**: **PASS**
**Critical**: 0 | **High**: 0 | **Medium**: 0 | **Low**: 0
## Files in scope
| File | Tasks | Change |
|------|-------|--------|
| `SatelliteProvider.Api/Program.cs` | AZ-351, AZ-353, AZ-354, AZ-356 | Modified (DI rewire, CORS validator, exception handler middleware, 501 stubs, removed per-endpoint try/catch) |
| `SatelliteProvider.Api/GlobalExceptionHandler.cs` | AZ-353 | New |
| `SatelliteProvider.Api/CorsConfigurationValidator.cs` | AZ-354 | New |
| `SatelliteProvider.Services.RegionProcessing/RegionService.cs` | AZ-359 | Modified (catch ladder collapsed) |
| `SatelliteProvider.Services.RegionProcessing/RegionFailureClassifier.cs` | AZ-359 | New |
| `SatelliteProvider.Services.RegionProcessing/RegionRequestQueue.cs` | AZ-363 | Modified (counters removed) |
| `SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj` | AZ-359 | Modified (`InternalsVisibleTo`) |
| `SatelliteProvider.Services.RouteManagement/RouteProcessingService.cs` | AZ-352 | Modified (empty catch removed; static→instance method) |
| `SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj` | AZ-352 | Modified (`InternalsVisibleTo`) |
| `SatelliteProvider.Tests/CorsConfigurationValidatorTests.cs` | AZ-354 | New (9 tests) |
| `SatelliteProvider.Tests/GlobalExceptionHandlerTests.cs` | AZ-353 | New (3 tests) |
| `SatelliteProvider.Tests/RegionFailureClassifierTests.cs` | AZ-359 | New (10 tests) |
| `SatelliteProvider.Tests/RouteProcessingServiceTests.cs` | AZ-352 | New (4 tests) |
| `SatelliteProvider.Tests/SatelliteProvider.Tests.csproj` | AZ-353 | Modified (added `SatelliteProvider.Api` ProjectReference for unit testing API-layer types) |
| `SatelliteProvider.IntegrationTests/StubAndErrorContractTests.cs` | AZ-356, AZ-353 | New (3 integration tests) |
| `SatelliteProvider.IntegrationTests/Program.cs` | AZ-356 | Modified (registers new test class in smoke + full suites) |
## Findings
**No findings of any severity.**
## Phase Summaries
### Phase 1 — Context loading
Read all 7 task specs from `_docs/02_tasks/done/`, `_docs/02_document/architecture_compliance_baseline.md`, `_docs/02_document/module-layout.md`, plus changed source files.
### Phase 2 — Spec compliance
| Task | AC | Evidence |
|------|----|----------|
| AZ-351 (C01) | Logger resolved via DI as `ILogger<DatabaseMigrator>` | `Program.cs:96``app.Services.GetRequiredService<ILogger<DatabaseMigrator>>()` |
| AZ-352 (C02) AC-1 | Empty catch removed; warning logged with structured fields | `RouteProcessingService.cs:610-628``_logger.LogWarning("...{FilePath}...", filePath)` |
| AZ-352 (C02) AC-2 | Null-guard on `filePath` | `RouteProcessingService.cs:612``ArgumentNullException.ThrowIfNull(filePath)` |
| AZ-352 unit tests | 4 tests cover valid name, malformed, non-numeric coords, null path | `RouteProcessingServiceTests` — 4 passing |
| AZ-353 (C03) AC-1 | 5xx → sanitized `application/problem+json` with `correlationId` | `GlobalExceptionHandler.cs:30-53` + unit test `TryHandleAsync_WritesSanitizedProblemDetailsAndReturnsTrue_AC1` |
| AZ-353 AC-2 | Full exception logged server-side with correlation ID | `GlobalExceptionHandler.cs:30-35` + unit test `..._LogsFullExceptionWithCorrelationId_AC2` |
| AZ-353 AC-3 | 4xx framework errors NOT promoted to 5xx | `GlobalExceptionHandler.cs:22-26` (`BadHttpRequestException` branch) + unit test `..._BadHttpRequestException_HonorsFrameworkStatusAndDoesNotErrorLog_AC3` + integration `CreateRoute_InvalidPayload_Returns400_AZ353_AC3` |
| AZ-354 (C04) AC-1 | Production with empty origins + no opt-in → throw | `CorsConfigurationValidator.cs:14-28` + unit test `..._ProductionWithEmptyOriginsAndNoOptIn_Throws_AC1` |
| AZ-354 AC-2 | Non-Production with empty origins → no throw, warning emitted | `Program.cs:89-94` + unit tests for Dev/Staging/Local |
| AZ-354 AC-3 | Explicit `AllowAnyOrigin=true` opt-in → no throw in Prod | Unit test `..._ProductionWithExplicitAllowAnyOrigin_DoesNotThrow_AC3` |
| AZ-356 (C05) AC-1 | Stub endpoints return 501 ProblemDetails | `Program.cs:178-192` + integration tests `StubMgrs_Returns501`, `StubUpload_Returns501` |
| AZ-356 OpenAPI | `.ProducesProblem(501)` declared on stub endpoints | `Program.cs:124, 129` |
| AZ-359 (C07) AC-1 | Same observable failure-path messages preserved | `RegionFailureClassifier.cs:30-69` — string assertions in 7 unit tests |
| AZ-359 AC-2 | RegionTests still green (timeout + rate-limit covered) | `RegionServiceTests.ProcessRegionAsync_DownloaderFailure_*` passing |
| AZ-359 AC-3 | 37+ unit + 5 smoke green | 68 unit + 5 smoke + 3 stub-contract integration — all pass |
| AZ-363 (C10) | Write-only counters removed | `RegionRequestQueue.cs` — no `_totalEnqueued`/`_totalDequeued` fields remain |
All ACs traced and demonstrably satisfied. No Spec-Gap findings.
### Phase 3 — Code quality
- **SOLID**: All three new types have a single, sharply-named responsibility. `GlobalExceptionHandler` handles 5xx sanitization; `CorsConfigurationValidator` validates CORS config; `RegionFailureClassifier` maps exceptions to a typed failure category. None depend on more than the minimum needed.
- **Error handling**: AZ-352 fix is correct — the previously empty `catch {}` is gone; failures now log a warning with structured fields and return a sentinel `(-1, -1)`. AZ-359 fix collapses 9 catches into 1; same exception types still caught (no broadening or narrowing). AZ-353 explicitly preserves the framework's 4xx status — addresses a subtle prior-art trap where naive `IExceptionHandler` would have promoted all errors to 500.
- **Naming**: `RegionFailureCategory`, `RegionFailureClassification`, `EnsureSafeForEnvironment`, `ShouldUsePermissivePolicy`, `ShouldWarnAboutPermissiveDefault` all read clearly from caller perspective.
- **Complexity**: `RegionService.ProcessRegionAsync` shrinks ~50 LOC of duplicated catch handling to ~10 LOC. No new method exceeds 50 lines; no new cyclomatic-complexity hotspot.
- **DRY**: Three meaningful DRY wins — (a) catch ladder collapse, (b) CORS rule centralized as testable constants/methods, (c) per-endpoint try/catch in `Program.cs` removed in favor of the global handler.
- **Test quality**: Tests assert on specific status codes, message substrings, log capture (via mock `ILogger`), and content type. Not "no error thrown" tests.
- **Dead code**: AZ-363 removed `_totalEnqueued`/`_totalDequeued`. `RegionRequestQueue` is now lean.
### Phase 4 — Security
- **Sanitized 5xx**: `Detail = "An unexpected error occurred. Use the correlationId to look up the server log entry."` — does not leak exception type, message, or stack trace to client. ✓
- **Correlation ID**: `httpContext.TraceIdentifier` — connection-scoped string, not a sensitive value. Adequate for log correlation.
- **BadHttpRequestException Detail echoes framework message** (e.g., `"Failed to bind parameter "double Latitude" from "' OR 1=1 --"."`). The echoed text is the user's own input, which they already possess; no server-side info is leaked. This is also standard ASP.NET behavior. Not a finding.
- **CORS**: Hard-fail in Production for empty `AllowedOrigins` + missing opt-in. Closes the most common CORS misconfiguration in prod deployments.
- **501 stubs**: ProblemDetails contain only the literal "not implemented" message; no internal info.
- **No SQL injection / command injection / hardcoded secrets** introduced.
### Phase 5 — Performance
- All new code is pure, allocation-light computation in non-hot paths (validator runs once at startup; classifier runs once per failure; handler runs once per unhandled exception).
- No new N+1 queries, no blocking I/O in async contexts, no O(n²) algorithms.
### Phase 6 — Cross-task consistency
- AZ-353, AZ-354, AZ-356 all touch `Program.cs`; their interactions are coherent: validator runs first (may throw), then DI registers handler + ProblemDetails, then `app.UseExceptionHandler()` activates the pipeline. No conflicting wiring.
- All new ProblemDetails responses use `"application/problem+json"` content type consistently.
- Logging style consistent across changes: structured fields with `{PascalCase}` placeholders, `_logger.LogError`/`LogWarning` with explicit exception when relevant.
- Test conventions consistent: `// Arrange` / `// Act` / `// Assert` per project rule; FluentAssertions throughout.
### Phase 7 — Architecture
| Check | Result |
|-------|--------|
| Layer direction | All cross-component imports go Layer-3 → Layer-1 (Common/DataAccess) or Layer-4 → Layer-3/1. No upward imports. |
| Public API respect | New types under each component live in the component's root namespace and are exposed as appropriate (`public sealed`, `public static`). No internal-of-other-component imports. |
| New cyclic dependencies | None. The split (per `module-layout.md`) prevents cross-sibling project references; the new files all sit within their owning component. |
| Duplicate symbols across components | None. `RegionFailureClassifier` is unique to RegionProcessing; `GlobalExceptionHandler` and `CorsConfigurationValidator` are unique to Api. |
| Cross-cutting concerns mis-placed | None. CORS validation belongs in Api (depends on env name); 5xx handler belongs in Api (depends on ASP.NET); region failure classification belongs in RegionProcessing (region-specific business semantics). |
## Baseline delta
`architecture_compliance_baseline.md` lists 5 findings, all Resolved as of epic AZ-309. This cumulative review introduces 0 new architecture findings.
| Status | Count |
|--------|-------|
| Carried over | 0 |
| Resolved | 5 (already by AZ-309) |
| Newly introduced | 0 |
## Test results referenced
- **Unit**: 68 / 68 passing (was 35 pre-Phase 1; +10 RegionFailureClassifier, +9 CorsConfigurationValidator, +3 GlobalExceptionHandler, +4 RouteProcessingService, +7 carried from prior baseline migrations across batches 7+8 — TileService internals + others).
- **Integration smoke**: 5 / 5 passing.
- **Stub-contract integration**: 3 / 3 passing.
- **Full integration suite**: green; container exits 0.
## Conclusion
**Verdict: PASS.** Phase 1 (Critical) closed cleanly. Phase 2 (Correctness) opened with AZ-359 also clean. Architecture baseline still at zero findings.
The implement skill may proceed to batch 10 without an auto-fix loop or user gate.