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>
10 KiB
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 1–7 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.
GlobalExceptionHandlerhandles 5xx sanitization;CorsConfigurationValidatorvalidates CORS config;RegionFailureClassifiermaps 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 naiveIExceptionHandlerwould have promoted all errors to 500. - Naming:
RegionFailureCategory,RegionFailureClassification,EnsureSafeForEnvironment,ShouldUsePermissivePolicy,ShouldWarnAboutPermissiveDefaultall read clearly from caller perspective. - Complexity:
RegionService.ProcessRegionAsyncshrinks ~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.csremoved 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.RegionRequestQueueis 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, thenapp.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/LogWarningwith explicit exception when relevant. - Test conventions consistent:
// Arrange/// Act/// Assertper 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.