Step-9 (new-task) cycle 1 artifacts for the AZ-483 multi-source tile
storage epic. AZ-485 (UAV upload + quality gate) deferred to a future
Step-9 loop and recorded as planned in the dependencies table.
Co-authored-by: Cursor <cursoragent@cursor.com>
Phase 6 (Verification): smoke run green (format gate + 200/200
unit + integration smoke). verification_report.md captures
metric deltas vs Phase 0 baseline; all 5 ACs met, all 4
constraints honored, 0 regressions.
Phase 7 (Documentation):
- module-layout.md: corrected DataAccess->Common dependency
(was mistakenly documented as "Imports from: (none)" by
prior AZ-315 baseline; csproj reference + 7 import sites
have actually been there since AZ-309).
- architecture_compliance_baseline.md: F5 entry revised to
reflect the actual layering invariant (one-way: Common
MUST NOT import from DataAccess, but DataAccess MAY
import from Common).
- 00_discovery.md: added "Updates Since Baseline" section
enumerating the AZ-309 split + AZ-350 27-change run +
AZ-372 tooling additions; original tree kept as a
2026-05-10 snapshot.
FINAL_report: complete run summary (10 batches, 27 tasks,
3 K=3 cumulative reviews, baseline->final metric table,
remaining items, lessons learned).
Autodev state: advance Step 8 -> Step 9 (New Task);
sub_step reset to phase 0 awaiting-invocation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Batch 24 of 03-code-quality-refactoring run; closes the run.
AZ-375 (C22): GoogleMapsDownloaderV2.DownloadTilesGridAsync now
builds a HashSet<(int X, int Y, int Z)> once from existingTiles
and tests Contains((x, y, zoomLevel)) per cell. Removes the per-cell
FirstOrDefault tolerance scan and the unused _processingConfig
.LatLonTolerance reference at this site.
AZ-377 (C24): promote Earth + tile-pixel constants to a single
home. GeoUtils now exposes EarthRadiusMeters, EarthEquatorial
CircumferenceMeters, MetersPerDegreeLatitude as public const.
MapConfig adds DefaultTileSizePixels (const) wired as the
TileSizePixels property default. TileRepository and Google
MapsDownloaderV2 read those constants instead of duplicating
the literals 6378137, 40075016.686, 111000.0, and 256.
Tests: +6 new (DownloaderRefactorTests, extended GeoUtils
RefactorTests). 200/200 unit tests pass.
Cumulative K=3 review (batches 22-24): PASS_WITH_WARNINGS,
4 Low findings only — see
_docs/03_implementation/reviews/cumulative_review_22-24.md.
Tooling fix: scripts/run-tests.sh --unit-only path now restores
before testing (was failing on SixLabors resolution in clean
container). Stripped stray BOM from MapConfig.cs to satisfy the
.editorconfig charset gate.
Updates _dependencies_table.md to reflect all 27 03-code-quality-
refactoring tasks done; updates _autodev_state.md to refactor
phase 5 (test-sync).
Co-authored-by: Cursor <cursoragent@cursor.com>
Batch 23 of refactor 03-code-quality-refactoring (4 tasks, 5 SP):
- AZ-376 (C23): Delete unused FindExistingTileAsync from
ITileRepository / TileRepository. No callers; method also took the
obsolete `version` arg removed by C06/AZ-357.
- AZ-378 (C25): Repository _logger discipline.
TileRepository.GetTilesByRegionAsync now emits LogWarning when the
query exceeds SlowQueryThresholdMs (500 ms). RegionRepository and
RouteRepository drop the unused ILogger<TRepo> field, parameter, and
using; Program.cs DI registrations updated.
- AZ-379 (C26): Extract `private const string ColumnList` per repo
(TileRepository, RegionRepository, RouteRepository); SELECTs use
$@"SELECT {ColumnList} FROM ..." (C# 10+ const interpolation).
INSERT/UPDATE/DELETE unchanged; route_points single-site SELECT left
inline.
- AZ-380 (C27): Delete dead alias GeoUtils.CalculatePolygonDiagonalDistance.
Tests: +9 new (RepositoryRefactorTests x8, GeoUtilsRefactorTests x1)
covering each AC via reflection / file-content assertions; pattern
mirrors ToolingConfigurationTests (b22) and AcceptanceCriteriaRT2Tests
(b19). Unit suite 181 -> 190, all green. dotnet format clean.
Code review: PASS_WITH_WARNINGS (3 Low findings, all informational or
out-of-scope for this batch). See
_docs/03_implementation/reviews/batch_23_review.md.
Cumulative review counter 2/3; next K=3 review fires after batch 24.
Co-authored-by: Cursor <cursoragent@cursor.com>
Pure whitespace-only cleanup uncovered by the new format gate from the
previous commit. Verified via `git diff -w --stat`: only 4 files differ
when whitespace is ignored, and those differ only by the BOM byte.
Cleanup kinds applied across 22 source files:
- BOM removal (MapConfig.cs, SatTile.cs, GeoUtils.cs,
IntegrationTests/Program.cs)
- CRLF -> LF (IntegrationTests/Program.cs)
- Trailing whitespace on blank lines (Common, Api, DataAccess,
IntegrationTests, Services.RegionProcessing,
Services.TileDownloader)
- Final newline added (RoutePoint.cs, GeoPoint.cs, others)
After this commit `dotnet format whitespace SatelliteProvider.sln
--verify-no-changes` exits 0; AC-1 is enforceable from `scripts/
run-tests.sh` going forward.
Also lands the batch 22 report, code-review report
(PASS_WITH_WARNINGS, 2 Low findings — both deferred per spec),
dependency-table status update (AZ-372 -> Done (In Testing)), task
archive (todo/ -> done/), and autodev state update.
Co-authored-by: Cursor <cursoragent@cursor.com>
F1 (Low/Maintainability): module-layout.md docs stale on DataAccess
project reference after AZ-370; tracked for refactor Phase 7.
F2 (Low/Maintainability): redundant builder.Services.AddHttpClient()
in Program.cs after AZ-374; deferred per batch 21 design note.
No Critical/High findings; auto-chain to next batch (AZ-372).
Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces bare strings with two enums in Common/Enums/:
RegionStatus { Queued, Processing, Completed, Failed }
RoutePointType { Start, End, Action, Intermediate }
Adds a Dapper EnumStringTypeHandler<T> (DataAccess/TypeHandlers/)
that round-trips enums to/from lowercase strings, registered once
at startup via DapperEnumTypeHandlers.RegisterAll(). DataAccess now
references Common (project ref) so entities can carry the enum types.
Sites converted: RegionService (5), RouteProcessingService (3),
RoutePointGraphBuilder (4), entity Status/PointType columns. Log
message and summary file format preserved via .ToLowerInvariant().
API JSON contract preserved by adding JsonStringEnumConverter with
JsonNamingPolicy.CamelCase to the http JSON options — single-word
enum members serialize to the same lowercase strings as before.
DTO renamed: Common.DTO.RegionStatus -> RegionStatusResponse to
free the RegionStatus name for the new enum (forced by the task's
explicit enum name); the renamed DTO has no public-API impact at
the JSON wire level. Stale doc references updated.
AC RT2 in _docs/00_problem/acceptance_criteria.md now lists all 4
point types (start/end/action/intermediate).
Tests: 171 / 171 unit + 5 / 5 smoke green (was 141 + 5; +30 new tests
covering type handler round-trip, set/parse, unknown-value rejection,
idempotent registration, and the AC RT2 doc check).
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
Replace 9 nearly-identical catch blocks in
RegionService.ProcessRegionAsync with a single catch (Exception ex)
that delegates to RegionFailureClassifier.Classify, returning a
typed (category, errorMessage) pair. Preserves all original error
messages stored in region summary files; failure-path call into
HandleProcessingFailureAsync is unchanged.
Net source delta: -38 lines in RegionService, +71 lines in new
RegionFailureClassifier (pure static), +10 unit tests covering
each category, precedence, status-code propagation, null guard.
Tests: 68 unit (was 58) + 5 smoke + 3 stub-contract integration
tests pass.
Co-authored-by: Cursor <cursoragent@cursor.com>
AZ-353: Centralize 500 handling via GlobalExceptionHandler /
AddProblemDetails / UseExceptionHandler. Sanitized ProblemDetails
body carries a generic title, RFC9110 type link, and the request's
TraceIdentifier as correlationId; the leaky exception message stays
server-side in the ERR log entry. Strip per-endpoint
try/catch (Exception) wrappers and the unused ILogger<Program>
parameters they served. Preserve the typed ArgumentException catch
in CreateRoute (AC-3). The handler maps BadHttpRequestException
back to its framework-supplied StatusCode so model-binding /
malformed-body failures stay 4xx instead of being promoted to 500.
AZ-354: Extract CorsConfigurationValidator (pure static helpers)
and wire it into Program.cs. Production with empty
CorsConfig:AllowedOrigins and no CorsConfig:AllowAnyOrigin opt-in
now throws InvalidOperationException at host startup. Development
keeps the permissive default but logs a warning post-build. Adds
the explicit CorsConfig:AllowAnyOrigin escape hatch.
AZ-356: GetSatelliteTilesByMgrs and UploadImage now return
Results.Problem(StatusCode 501) with ProblemDetails. Added
.ProducesProblem(501) so swagger.json documents the
not-implemented status.
Tests: SatelliteProvider.Tests now references SatelliteProvider.Api
(downward, idiomatic) so unit tests can reach the new helpers.
+9 CorsConfigurationValidator unit tests, +3
GlobalExceptionHandler unit tests, +3 StubAndErrorContractTests
integration tests (added to smoke + full suites).
58/58 unit + 5/5 smoke + 3/3 stub-contract pass.
Code review verdict: PASS.
Batch report: _docs/03_implementation/batch_08_report.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
AZ-351: Resolve ILogger<DatabaseMigrator> directly from DI in
Program.cs instead of casting ILogger<Program> (which always
returned null). Migrator now logs through Serilog at startup.
AZ-352: Drop empty catch in
RouteProcessingService.ExtractTileCoordinatesFromFilename. Convert
the method from private static to internal instance so it can use
the existing _logger (per coderule: side-effecting code must not be
static). Add typed null-guard via ArgumentNullException.ThrowIfNull
so unexpected exceptions propagate. Adds InternalsVisibleTo on the
RouteManagement csproj for SatelliteProvider.Tests, plus 4 unit
tests in RouteProcessingServiceTests.cs covering AC-1 (valid /
malformed / non-numeric) and AC-2 (null path propagation).
AZ-363: Delete _totalEnqueued / _totalDequeued fields and the two
non-atomic ++ writes in RegionRequestQueue. Fields were write-only
dead code and a thread-safety hazard.
Tests: 44/44 unit + 5/5 smoke (scripts/run-tests.sh --smoke).
Code review verdict: PASS, 0 findings, 0 auto-fix attempts.
Batch report: _docs/03_implementation/batch_07_report.md.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds _docs/04_refactoring/02-coupling-refactoring/FINAL_report.md
recording goal achievement, baseline-vs-final metrics, batch
summaries, and lessons learned. Advances autodev state from
Step 8 (Refactor) to Step 9 (New Task) — Phase A baseline setup
is now complete; Phase B feature cycle starts on next invocation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Phase C of architecture coupling refactor (epic AZ-309). Closes the
last baseline finding (F5 — DataAccess incorrectly documented as
importing Common) and synchronizes the rest of _docs/02_document/
with the post-split project layout from AZ-312/313/314:
- module-layout.md: per-component sections for the three new csprojs
with explicit ProjectReferences and the no-cross-sibling-reference
invariant the split enforces.
- architecture.md: components and internal-communication tables
updated to show calls flow through Common interfaces.
- architecture_compliance_baseline.md: F1..F5 marked Resolved with
task IDs and commit refs; baseline summary now 0 findings.
- diagrams/components.md, components/03_tile_downloader/description.md,
modules/{common_interfaces,services_tile_service,
services_google_maps_downloader,tests_unit}.md updated for the
split, RateLimitException relocation, and new ITileService methods.
Documentation-only batch — no code, no tests, no build changes.
Epic AZ-309 complete (6 tasks across 3 batches).
Co-authored-by: Cursor <cursoragent@cursor.com>
- Baseline metrics, list of changes, and analysis (research findings,
refactoring roadmap) for the coupling refactor run
- Six task specs AZ-310..AZ-315 covering endpoint routing through
ITileService, Services csproj split, consumer rewire, DI extension
methods, and docs sync
- Existing test coverage assessment for Phase 3 safety net gate
- Dependencies table updated with the refactor block
Co-authored-by: Cursor <cursoragent@cursor.com>
Add a fast integration profile so Step 7 (and future autodev
re-entries) can verify the full stack in ~2 min instead of ~15 min,
without losing access to the long-running coverage when needed.
- TestRunMode.cs: smoke flag + tightened poll/timeout values.
- Program.cs: env var INTEGRATION_TESTS_MODE / --smoke|--full CLI
switch; smoke runs Tile + 200m region + simple route + ZIP route +
Security; full keeps the existing sequence.
- RegionTests / ExtendedRouteTests: read timeouts from TestRunMode
instead of hardcoding 120/180/360.
- docker-compose.tests.yml: forwards INTEGRATION_TESTS_MODE to the
integration-tests container (default 'full').
- scripts/run-tests.sh: adds --unit-only / --smoke / --full flags,
loads .env automatically, fails fast if GOOGLE_MAPS_API_KEY is
missing.
Step 7 result: all tests passed in 111.86 s wall-clock (35/35 unit +
5/5 smoke integration scenarios incl. SEC-01..04). Report saved to
_docs/03_implementation/test_run_step7.md.
State advanced to Step 8 (Refactor).
Co-authored-by: Cursor <cursoragent@cursor.com>
Final implementation report for the test step. All six test tasks
(AZ-285..AZ-290) completed across three batches. Code review verdicts:
all PASS_WITH_WARNINGS. Run-Tests handoff recorded for Step 7 per
implement skill Step 16.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add SatelliteProvider.DataAccess project reference to test csproj
(enables mocking ITileRepository / IRegionRepository / IRouteRepository)
- Replace DummyTest placeholder with InfrastructureTests covering:
* All mockable interfaces (ISatelliteDownloader, repos, queue, services)
can be mocked via Moq
* TileService can be constructed with mocked dependencies
* Test coordinate fixtures load with expected values
- Add Fixtures/TestCoordinates.cs with REG-01..REG-03 + ROUTE-01/04/06
shared test data
- Archive AZ-285 to _docs/02_tasks/done/
- Batch 1 review report: PASS_WITH_WARNINGS (Low/Spec-Gap deferred AC-2,
Low/Maintainability pre-existing FluentAssertions 8.x license note)
Verification: docker dotnet test run — 4/4 tests pass in 2.35s.
Co-authored-by: Cursor <cursoragent@cursor.com>