# Batch Report

**Batch**: 25 (cycle 1)
**Tasks**: AZ-484 (Multi-source tile storage schema)
**Date**: 2026-05-11

## Task Results

| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|---------------|-------|-------------|--------|
| AZ-484 Multi-source tile storage schema | Done | 11 source files + 4 docs | new + updated unit tests; new integration migration tests (handed off to Run Tests) | 7/7 ACs covered | None |

## AC Test Coverage: All covered (7/7)

| AC | Test |
|----|------|
| AC-1 | `MultiSourceInsertCoexistsUnderNewIndex_AZ484_AC1`, `NewUniqueConstraintIncludesSourceColumn_AZ484_AC1` (integration) |
| AC-2 | `MostRecentAcrossSourcesSelection_AZ484_AC2` (integration) |
| AC-3 | `SameSourceUpsertReplacesPreviousRow_AZ484_AC3` (integration) |
| AC-4 | `BackfillUpdateAssignsGoogleMapsAndCapturedAt_AZ484_AC4` (integration TEMP-table simulation of migration UPDATE) |
| AC-5 | `BuildTileEntity_SetsGoogleMapsSourceAndUtcCapturedAt_AZ484_AC5` (unit) |
| AC-6 | Existing 200 unit + 5 smoke pass unchanged — verified via the full suite run (handed off to autodev Step 11) |
| AC-7 | Documents amended in this batch; contract `tile-storage.md` Status flipped from `draft` to `frozen` |

## Code Review Verdict: PASS
Report: `_docs/03_implementation/reviews/batch_25_cycle1_review.md`

## Auto-Fix Attempts: 0
## Stuck Agents: None

## Pre-Implementation Audit (Risk 3 mitigation)

`new TileEntity` and `Mock<ITileRepository>` sites surveyed before edits:

| Site | Action |
|------|--------|
| `SatelliteProvider.Services.TileDownloader/TileService.cs:146` (`BuildTileEntity`) | Updated — sets `Source = TileSource.GoogleMaps`, `CapturedAt = DateTime.UtcNow` |
| `SatelliteProvider.Tests/TileServiceTests.cs:84` (BT-02 cached) | Updated — explicit `Source` + `CapturedAt = DateTime.UtcNow` |
| `SatelliteProvider.Tests/TileServiceTests.cs:139` (AZ-357 prior-year) | Updated — explicit `Source` + `CapturedAt = DateTime.UtcNow.AddYears(-1)` to mirror the prior-year semantic |
| `SatelliteProvider.Tests/TileServiceTests.cs:264` (`GetTileAsync` known-id) | Updated — explicit `Source` + `CapturedAt` |
| `SatelliteProvider.Tests/TileServiceTests.cs:342` (AZ-310 RepoHit) | Updated — inline `TileEntity` initializer expanded with explicit fields |
| `SatelliteProvider.Tests/InfrastructureTests.cs:23, :65` (mock-only, no `TileEntity` construction) | No change required — mocks return defaults that no test asserts on |
| `SatelliteProvider.Tests/RepositoryRefactorTests.cs` ColumnList assertion | Updated — added `source` + `captured_at as CapturedAt` to expected column list |

**Note on the task spec's "RegionServiceTests ~3 sites" estimate**: that count was inaccurate — `SatelliteProvider.Tests/RegionServiceTests.cs` does not reference `TileEntity` or `ITileRepository`. No edit was needed there.

## Files Changed

### New
- `SatelliteProvider.DataAccess/Migrations/013_AddTileSourceAndCapturedAt.sql`
- `SatelliteProvider.Common/Enums/TileSource.cs`
- `SatelliteProvider.DataAccess/TypeHandlers/TileSourceTypeHandler.cs`

### Modified — production code
- `SatelliteProvider.DataAccess/Models/TileEntity.cs` (added `Source`, `CapturedAt`)
- `SatelliteProvider.DataAccess/Repositories/TileRepository.cs` (ColumnList + 4 SQL methods)
- `SatelliteProvider.DataAccess/TypeHandlers/EnumStringTypeHandler.cs` (registered `TileSourceTypeHandler`)
- `SatelliteProvider.Services.TileDownloader/TileService.cs` (`BuildTileEntity` stamps Source + CapturedAt)

### Modified — tests
- `SatelliteProvider.Tests/TileServiceTests.cs`
- `SatelliteProvider.Tests/RepositoryRefactorTests.cs`
- `SatelliteProvider.Tests/EnumStringTypeHandlerTests.cs`
- `SatelliteProvider.IntegrationTests/MigrationTests.cs`

### Modified — documentation
- `_docs/02_document/architecture.md` (Architecture Vision + System Context)
- `_docs/02_document/glossary.md` (Tile Source, Captured At, Layer 1/2 disambiguation)
- `_docs/02_document/module-layout.md` (Common Public API listing)
- `_docs/02_document/contracts/data-access/tile-storage.md` (Status: `draft` → `frozen`)

## Design Notes

**Wire-format mismatch motivating `TileSourceTypeHandler`.** The generic `EnumStringTypeHandler<T>` emits `value.ToString().ToLowerInvariant()`, which would produce `'googlemaps'` for `TileSource.GoogleMaps`. The v1.0.0 contract requires `'google_maps'`. A dedicated `TileSourceTypeHandler` keeps the snake_case mapping localized and avoids leaking case-conversion logic into the generic handler. Round-trip and unknown-value tests are colocated with the existing handler test class.

**`DISTINCT ON` for region reads.** PostgreSQL's `DISTINCT ON` was chosen over a self-join or window function because the new 5-column unique index can serve as the prefix sort, keeping the change a near-zero overhead for a region query. The outer `ORDER BY latitude DESC, longitude ASC, updated_at DESC` preserves the pre-AZ-484 caller-facing row order.

**Migration transactionality (Risk 1 mitigation).** The migration is wrapped in `BEGIN ... COMMIT`. The IntegrationTests TEMP-table tests cover the backfill semantics; the live-schema test verifies the final post-013 index shape (and that the legacy 4-column index was actually dropped).

## Next Batch
None — AZ-484 is the only task in this cycle. AZ-485 (UAV upload + quality gate) is deferred to a future Step 9 loop and is recorded in `_docs/02_tasks/_dependencies_table.md` under Step 9 cycle 1.

## Handoff to Step 11 (Run Tests)

Per `/implement` skill Step 16: the autodev next step is Run Tests, so this batch does NOT execute the full suite locally. The `test-run` skill owns the full-suite gate. Pre-conditions required:
- `dotnet test SatelliteProvider.Tests` should pass (200 unit + new AZ-484 unit tests).
- `scripts/run-tests.sh --smoke` should pass with the live API + Postgres (5 smoke + new AZ-484 integration migration tests).

If `test-run` reports a failure in either suite, surface it; the existing infrastructure tests for AZ-357 dedupe semantics and the new AZ-484 selection / UPSERT tests are the highest-signal checks.
