Files
Oleksandr Bezdieniezhnykh 909f69cb3a [AZ-505] Tile inventory endpoint + HTTP/2 + Leaflet covering index
Production code:
- POST /api/satellite/tiles/inventory (XOR body, 5000-cap,
  most-recent-per-location_hash select, present/absent shaping).
- Kestrel HttpProtocols.Http1AndHttp2 on every listener (AC-5).
- Migration 015 creates tiles_leaflet_path covering index over
  (location_hash, captured_at DESC, updated_at DESC, id DESC)
  INCLUDE (file_path, source); drops superseded idx_tiles_location_hash.
- TileRepository.GetByTileCoordinatesAsync rewired to filter by
  location_hash (Index Only Scan via tiles_leaflet_path).
- TileRepository.GetTilesByLocationHashesAsync added with Npgsql-
  direct ANY($1::uuid[]) binding (Dapper IEnumerable expansion is
  incompatible with the array form).
- Uuidv5.LocationHashForTile centralises the UUIDv5(TileNamespace,
  "{z}/{x}/{y}") formula — single source of truth for the cross-repo
  invariant (gps-denied-onboard parity).

Contracts:
- New: contracts/api/tile-inventory.md v1.0.0.
- Bumped: contracts/data-access/tile-storage.md to v2.0.0 (joint
  ownership by AZ-503-foundation + AZ-505: schema + covering index +
  GetByTileCoordinatesAsync rewrite).

Tests:
- TileInventoryTests covers AC-1, AC-2 (DB-level), AC-4, AC-6.
- Http2MultiplexingTests covers AC-5 (20 concurrent multiplexed GETs
  over h2c via SocketsHttpHandler + AppContext Http2Unencrypted switch).
- LeafletPathIndexOnlyTests covers AC-3 (EXPLAIN (ANALYZE, BUFFERS)
  asserts Index Only Scan over tiles_leaflet_path with heap_blocks=0).

Docs:
- architecture.md, system-flows.md, data_model.md, module-layout.md,
  glossary.md, modules/api_program.md, modules/dataaccess_tile_repository.md,
  components/02_data_access/description.md all updated to reference the
  v2.0.0 tile-storage contract + new tile-inventory contract + AC-7.

Reports:
- batch_01_cycle6_report.md, batch_01_cycle6_review.md,
  implementation_completeness_cycle6_report.md (PASS),
  implementation_report_tile_inventory_cycle6.md.

Task spec moved todo/ -> done/.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 21:16:37 +03:00

6.4 KiB

Batch Report

Batch: 01 (cycle 6) Tasks: AZ-505 — Tile inventory endpoint + HTTP/2 + Leaflet covering index Date: 2026-05-12

Task Results

Task Status Files Modified Tests AC Coverage Issues
AZ-505_tile_inventory_http2_leaflet_index Done 13 source + 9 doc + 1 migration New: TileInventoryTests.cs (6 sub-tests), Http2MultiplexingTests.cs, LeafletPathIndexOnlyTests.cs. Wired into both smoke + full suites. 6/6 functional ACs covered; AC-7 doc-gate satisfied None remaining; one Medium / Maintainability auto-fix landed (consolidate ComputeLocationHashUuidv5.LocationHashForTile).

Changes

Production code

  • SatelliteProvider.Common/DTO/TileInventory.cs (new) — TileInventoryRequest (XOR Tiles / LocationHashes), TileCoord, TileInventoryResponse, TileInventoryEntry, TileInventoryLimits.MaxEntriesPerRequest = 5000.
  • SatelliteProvider.Common/Utils/Uuidv5.cs — added LocationHashForTile(int z, int x, int y) static. Single source-of-truth for the cross-repo UUIDv5(TileNamespace, "{z}/{x}/{y}") formula consumed by repository, service, and tests.
  • SatelliteProvider.DataAccess/Repositories/ITileRepository.cs — added GetTilesByLocationHashesAsync(IReadOnlyList<Guid>) → Task<IReadOnlyDictionary<Guid, TileEntity>>.
  • SatelliteProvider.DataAccess/Repositories/TileRepository.cs — rewrote GetByTileCoordinatesAsync to filter by location_hash (index-only-scannable against tiles_leaflet_path); implemented GetTilesByLocationHashesAsync via NpgsqlCommand + NpgsqlDbType.Array | Uuid parameter binding (Dapper's IEnumerable expansion is incompatible with ANY($1::uuid[])); removed the AZ-505 first-pass ComputeLocationHash helper in favour of Uuidv5.LocationHashForTile.
  • SatelliteProvider.Services.TileDownloader/TileService.cs — added GetInventoryAsync(TileInventoryRequest, CancellationToken) implementing the contract's ordering / present-absent / Form-A-vs-Form-B shaping rules; consolidated locationHashName in BuildTileEntity to the same helper.
  • SatelliteProvider.Api/Program.cs — registered POST /api/satellite/tiles/inventory with .RequireAuthorization(), .Accepts<TileInventoryRequest>("application/json"), .Produces<TileInventoryResponse>(200), .ProducesProblem(400), and OpenAPI description referencing the contract. Configured Kestrel HttpProtocols.Http1AndHttp2 on all listener endpoints.
  • SatelliteProvider.DataAccess/Migrations/015_AddTilesLeafletPathIndex.sql (new) — CREATE INDEX tiles_leaflet_path (location_hash, captured_at DESC, updated_at DESC, id DESC) INCLUDE (file_path, source); DROP INDEX IF EXISTS idx_tiles_location_hash. Forward + back-migration documented in the header; lock-window caveat captured per AZ-505 Risk 2.

Tests

  • SatelliteProvider.IntegrationTests/TileInventoryTests.cs (new) — AC-1 (ordering + present/absent shaping, 25-entry interleaved fixture), AC-2 (DB-level proof that the most-recent-via-location_hash selection rule survives the rewrite), AC-4 (perf budget, full-suite only), AC-6 (4 validation cases).
  • SatelliteProvider.IntegrationTests/Http2MultiplexingTests.cs (new) — AC-5 (20 concurrent GETs over a single H2 connection on h2c).
  • SatelliteProvider.IntegrationTests/LeafletPathIndexOnlyTests.cs (new) — AC-3 (EXPLAIN ANALYZE + Index Only Scan regex + Heap Fetches ≤ 1).
  • SatelliteProvider.IntegrationTests/Program.cs — wired the three new test entry points into both RunSmokeSuite and RunFullSuite. Http2MultiplexingTests.RunAll runs early in Main because it sets a process-wide AppContext switch.

Documentation

  • _docs/02_document/contracts/api/tile-inventory.md v1.0.0 (new) — Form A / Form B XOR validation, 5000-entry cap, 7-line invariant table, response field reference, test-case matrix, change log entry.
  • _docs/02_document/contracts/data-access/tile-storage.md v1.0.0 → v2.0.0 (major bump) — captures AZ-503-foundation identity columns + idx_tiles_unique_identity integer UPSERT + tiles_leaflet_path covering index + location_hash-keyed read rule + new GetTilesByLocationHashesAsync method. Added Inv-7 / Inv-8 / Inv-9. Change Log entry names both producing tasks (AZ-503-foundation + AZ-505).
  • _docs/02_document/architecture.md, _docs/02_document/module-layout.md, _docs/02_document/glossary.md, _docs/02_document/data_model.md, _docs/02_document/modules/api_program.md, _docs/02_document/modules/dataaccess_tile_repository.md, _docs/02_document/components/02_data_access/description.md — endpoint row, repo method row, query/index table updates, Location Hash entry promotion, migration 015 entry, AZ-505 + v2.0.0 freeze references.

AC Test Coverage

6/6 functional ACs covered (AC-1, AC-2, AC-3, AC-4, AC-5, AC-6). AC-7 (contract artifacts) is a documentation gate verified by file presence + version-string + Change Log entry.

Code Review Verdict

PASS — see _docs/03_implementation/reviews/batch_01_cycle6_review.md. One Medium / Maintainability auto-fix landed during review (consolidate ComputeLocationHash duplication into Uuidv5.LocationHashForTile). No remaining findings.

Auto-Fix Attempts

1 — ComputeLocationHash duplication; resolved in a single pass.

Stuck Agents

None.

Notes

  • Cross-repo invariant preserved: Uuidv5.TileNamespace = 5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c unchanged; consumed by LocationHashForTile, which the repository / service / tests all now route through. The Python sibling (gps-denied-onboard/components/c6_tile_cache/_uuid.py:TILE_NAMESPACE) is not touched by this PBI per AZ-505 Constraints — sibling-repo concern.
  • Onboard consumer feature flag: gps-denied-onboard AZ-316 retains c11.use_bulk_list_endpoint=false default until this PBI is deployed (per AZ-505 Constraints + Risk 4). Surfaced for cross-cycle visibility; no in-repo work.
  • Leftover replay (cycle 3 perf-harness): _docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md remains open. AZ-505 does NOT touch scripts/run-performance-tests.sh (out of scope; the open leftover names DNS pre-warmup + cloud perf rerun as the unblocking steps). No replay attempted this cycle per the leftover's "no immediate replay planned" entry.

Next Batch

All tasks complete. Cycle 6 implementation closes here. Hand off to Step 11 (test-run) for the full integration-test gate via Docker Compose.