Files
satellite-provider/_docs/03_implementation/batch_10_report.md
T
Oleksandr Bezdieniezhnykh 581dff206e
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-357] Refactor C06: drop tile Version concept; cumulative review batches 7-9
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>
2026-05-11 00:20:47 +03:00

6.9 KiB
Raw Blame History

Batch 10 Report — Refactor 03 Phase 2 (continued)

Date: 2026-05-10 Epic: AZ-350 (03-code-quality-refactoring) Status: Complete, pushed (after batch 11 commit, riding with 09 cumulative review)

Scope (1 task / 5 SP)

ID C-ID Title Points Component
AZ-357 C06 Drop tile Version concept; latest row wins; new migration 5 Services.TileDownloader + DataAccess

Single-task batch — DB migration is higher risk and benefits from dedicated review focus.

Changes

Migration

  • NEW SatelliteProvider.DataAccess/Migrations/012_DropTileVersionConstraint.sql
    • Drops idx_tiles_unique_location (5-column).
    • Dedupes by 4-column key using ROW_NUMBER() OVER (PARTITION BY ... ORDER BY updated_at DESC, id DESC) — keeps latest row per cell, deterministic tie-break by id.
    • Recreates idx_tiles_unique_location on (latitude, longitude, tile_zoom, tile_size_meters).
    • ALTER COLUMN version DROP NOT NULL and DROP DEFAULT so new rows can store NULL.
    • Column itself preserved (per coderule.mdc — no column drops without confirmation; covered by AZ-373 / C20 separately).

Production

  • MODIFIED SatelliteProvider.DataAccess/Models/TileEntity.cs
    • Version changed from intint? (matches the now-nullable column).
  • MODIFIED SatelliteProvider.Common/DTO/TileMetadata.cs
    • Version changed to int? to surface the nullable column to consumers (HTTP shape preserved per the task constraint — the field is still present in JSON).
  • MODIFIED SatelliteProvider.Api/Program.cs (DownloadTileResponse)
    • Version changed to int? for the same reason.
  • MODIFIED SatelliteProvider.DataAccess/Repositories/TileRepository.cs
    • InsertAsync.ON CONFLICT clause: 5-col → 4-col (drops version).
    • GetByTileCoordinatesAsync: ORDER BY version DESCORDER BY updated_at DESC (latest row wins per AC-1).
    • GetTilesByRegionAsync: ORDER BY version DESC, latitude DESC, longitude ASCORDER BY latitude DESC, longitude ASC, updated_at DESC (after migration there's at most 1 row per cell so version-ordering is meaningless; updated_at is the meaningful tie-break).
    • FindExistingTileAsync left untouched — slated for deletion in AZ-376 / C23.
  • MODIFIED SatelliteProvider.Services.TileDownloader/TileService.cs
    • Removed var currentVersion = DateTime.UtcNow.Year; and the .Where(t => t.Version == currentVersion) cache filter (root cause of LF-1: cache expiring on Jan 1).
    • BuildTileEntity signature: dropped the currentVersion parameter; now writes Version = null. New code never writes the deprecated year value.
    • All 3 call sites updated to drop the year argument.

Tests

  • MODIFIED SatelliteProvider.Tests/TileServiceTests.cs
    • Replaced DownloadAndStoreTilesAsync_IgnoresStaleVersionCachedTiles_BT02b with DownloadAndStoreTilesAsync_TreatsCachedTileFromPriorYearAsFresh_AZ357_AC1 — same setup with a Version = Year - 1 row, but inverted assertion: the cached tile IS reused (not re-downloaded). Directly proves AC-1 (cache survives year boundary).
    • Added BuildTileEntity_DoesNotPopulateVersion_AZ357 — captures the entity passed to InsertAsync and asserts Version == null. Enforces the "new code does not write to it" constraint.

Verification

  • Unit tests: 69 / 69 passing (was 68 → +2 new AZ-357 tests, 1 inverted/replaced test = net +1).
  • Integration smoke + full suite: green. Container exits 0. The 20-point extended-route test ran 690 tiles end-to-end with the new schema applied to a fresh Postgres volume — exercises:
    • Insert path: writes Version = null, conflicts on the new 4-col key.
    • Read path: GetTilesByRegionAsync returns tiles ordered by updated_at DESC.
    • GetOrDownloadTileAsync cache-hit path: tile lookup uses ORDER BY updated_at DESC.

Acceptance criteria coverage

AC Evidence
AC-1 Cache survives year boundary Unit test TreatsCachedTileFromPriorYearAsFresh_AZ357_AC1: prior-year Version row reused; InsertAsync not called.
AC-2 Migration runs cleanly on populated tile data (Partial) Migration applied successfully against an integration test DB during container startup. Dedupe SQL is correct by construction (ROW_NUMBER OVER PARTITION BY ... ORDER BY updated_at DESC, id DESC). Not explicitly tested with pre-staged duplicates — see "Known coverage gap" below. Consistent with how migration 004 (which used the same pattern) was originally verified.
AC-3 Upsert behaves on the new key New InsertAsync.ON CONFLICT (latitude, longitude, tile_zoom, tile_size_meters) clause; integration suite re-runs identical (lat,lon,zoom,size) inserts during the route test (690 tiles processed without unique-violation errors).
AC-4 37 unit + 5 smoke tests stay green 69 unit + 5 smoke + 3 stub-contract green.

Known coverage gap (AC-2, partial)

The migration's dedupe DELETE has not been exercised against a pre-populated table containing rows that violate the new 4-column constraint. Reasons not addressed in this batch:

  • The integration test stack starts with a fresh DB volume, so the migration runs against an empty table.
  • Inserting test duplicates after migration startup is impossible (the new constraint blocks it).
  • Adding a pre-init SQL injection (docker-compose command: or an init script in the postgres image) is out of scope for a 5 SP refactor and would touch CI tooling.

Mitigation: the SQL pattern (ROW_NUMBER OVER PARTITION BY ... ORDER BY updated_at DESC, id DESC) is well-understood and matches the established project precedent (migration 004 used a similar DELETE...USING pattern with no test). Production rollout should follow the spec's risk mitigation: capture pre-migration row counts, dry-run against a populated copy.

This gap is recorded in _docs/_process_leftovers/ if user wants follow-up tracking; otherwise treat as accepted risk consistent with prior migrations.

Behavior preservation

  • HTTP shape: DownloadTileResponse still has version field. JSON output is "version": null for new tiles, "version": 2025 (or other year) for tiles inserted before this migration. Consumers parsing as int? (most JSON libraries default to nullable) are unaffected; consumers parsing as int would need to handle null. None observed in the suite.
  • Cache semantics: stricter (cache survives year flip) — the intended behavior. The replaced test asserted the bug; the new test asserts the fix.

Up next

  • Batch 11: AZ-362 (idempotent POST contract for caller-supplied GUIDs, 3 SP) — Api + RegionProcessing + RouteManagement. Depends on AZ-353 (done in batch 8). This will be the next-and-final batch this session unless paused.
  • After batch 11, K=3 cumulative review trigger fires again (batches 10, 11, 12) — but only 2 batches new, so falls below threshold. Continue per user direction.