mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 19:21:14 +00:00
[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>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# 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 `int` → `int?` (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 DESC` → `ORDER BY updated_at DESC` (latest row wins per AC-1).
|
||||
- `GetTilesByRegionAsync`: `ORDER BY version DESC, latitude DESC, longitude ASC` → `ORDER 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.
|
||||
Reference in New Issue
Block a user