# Structural Snapshot — 2026-05-12 (post-cycle 5, AZ-503-foundation + AZ-504)

Cycle 5 delta against `structure_2026-05-12_cycle4.md`. Source of truth: `_docs/02_document/module-layout.md` + on-disk `*.csproj` graph + `_docs/02_document/contracts/`.

## Projects

| Layer | csproj | Cycle 5 delta |
|-------|--------|---------------|
| 1 (Foundation) | `SatelliteProvider.Common` | **+1 file**: `Utils/Uuidv5.cs` (NEW, 80 LoC; RFC 9562 §5.5 SHA-1 UUIDv5 + pinned `TileNamespace` GUID). No new NuGet deps; uses framework-only `System.Security.Cryptography.SHA1` + `System.Buffers.Binary.BinaryPrimitives`. |
| 1 (Foundation) | `SatelliteProvider.DataAccess` | **+1 migration**: `Migrations/014_AddTileIdentityColumns.sql` (NEW, embedded resource); **+4 properties** on `Models/TileEntity.cs` (`FlightId`, `LocationHash`, `ContentSha256`, `LegacyId`); UPSERT in `Repositories/TileRepository.cs` rewritten for the integer-key + flight-aware contract. No new NuGet deps; `pgcrypto` extension enabled by the migration script (PG-server-side, not a NuGet). |
| 1 (Foundation, shared DTO) | `SatelliteProvider.Common` (DTO sub-folder) | **+1 property**: `DTO/UavTileMetadata.FlightId` (`Guid?`, init-only, optional). Contract `uav-tile-upload.md` bumped 1.0.0 → 1.1.0 (additive). |
| 3 (Application) | `SatelliteProvider.Services.TileDownloader` | **+ImportSite**: `using SatelliteProvider.Common.Utils` (for `Uuidv5.Create`) + `using System.Security.Cryptography` (for `SHA256.HashData`) in `TileService.cs` and `UavTileUploadHandler.cs`. `UavTileUploadHandler.BuildUavTileFilePath` gains an optional `Guid? flightId` parameter. No new csproj refs. |
| 3 (Application) | `SatelliteProvider.Services.RegionProcessing` | unchanged |
| 3 (Application) | `SatelliteProvider.Services.RouteManagement` | unchanged |
| 4 (API / Entry) | `SatelliteProvider.Api` | unchanged (`Program.cs` not edited this cycle) |
| 5 (Test-Support) | `SatelliteProvider.TestSupport` | unchanged |
| 6 (Tests) | `SatelliteProvider.Tests` | **+1 test class** (`Uuidv5Tests.cs`); **+3 facts** in `UavTileFilePathTests.cs`; **+2 facts** in `UavTileUploadHandlerTests.cs`. |
| 6 (Tests) | `SatelliteProvider.IntegrationTests` | **+1 ProjectReference** (`SatelliteProvider.Common`) — so raw-SQL seeds can call `Uuidv5.Create` directly; **+2 facts** in `UavUploadTests.cs` (AC-3 multi-flight, AC-4 float-rounding); **+3 facts** in `MigrationTests.cs` (Az503 columns / index / backfill determinism); 1 fact superseded (`NewUniqueConstraintIncludesSourceColumn_AZ484_AC1` → `Az503MigrationSupersedesAz484UniqueIndex`); seed for the pre-existing `MultiSourceCoexistence_AZ484_Cycle2` test repaired to populate `location_hash`. |

**Project count**: 9 (unchanged from cycle 4 — AZ-503-foundation adds files to existing projects, doesn't add a new csproj).

## Cross-Project Import Edges (compile-time `ProjectReference`)

| Edge | Count | Cycle 5 delta |
|------|-------|----------------|
| Api → {Common, DataAccess, TileDownloader, RegionProcessing, RouteManagement} | 5 | unchanged |
| TileDownloader → {Common, DataAccess} | 2 | unchanged |
| DataAccess → {Common} | 1 | unchanged |
| RegionProcessing → {Common, DataAccess} | 2 | unchanged |
| RouteManagement → {Common, DataAccess} | 2 | unchanged |
| Tests → {Api, TileDownloader, RegionProcessing, RouteManagement, Common, DataAccess, TestSupport} | 7 | unchanged |
| IntegrationTests → {TestSupport, **Common (NEW)**} | **2** | **+1** edge (motivated by AZ-503 — seeds need the production `Uuidv5.Create` algorithm to compute `location_hash` for raw-SQL inserts) |

**Total ProjectReference edges**: **21** (cycle 4: 20). Net delta: +1 edge.

## Source-import sites — cycle 5 delta

| Importer | Imports from | Cycle 5 delta |
|----------|--------------|---------------|
| `SatelliteProvider.Services.TileDownloader/TileService.cs` | `SatelliteProvider.Common.Utils` (Uuidv5), `System.Security.Cryptography` (SHA256) | NEW (AZ-503 deterministic identity + content hash) |
| `SatelliteProvider.Services.TileDownloader/UavTileUploadHandler.cs` | `SatelliteProvider.Common.Utils` (Uuidv5), `System.Security.Cryptography` (SHA256) | NEW (same — UAV write path) |
| `SatelliteProvider.IntegrationTests/UavUploadTests.cs` | `SatelliteProvider.Common.Utils` (Uuidv5) | NEW (seed helper for raw-SQL inserts) |
| `SatelliteProvider.Tests/Uuidv5Tests.cs` | `SatelliteProvider.Common.Utils` | NEW (unit-test class) |
| All other source files | unchanged | — |

**5 new source-level import lines** across 4 files; all to either the new internal `SatelliteProvider.Common.Utils.Uuidv5` utility or the framework `System.Security.Cryptography.SHA256`. **Zero new third-party imports.**

## Graph properties

- **Cycles in project import graph**: 0 (clean DAG — unchanged)
- **Average ProjectReferences per component**: 21 / 9 = **~2.3** (cycle 4: ~2.2). Net delta: +0.1 (one new IntegrationTests → Common edge).
- **Max in-degree**: Common (still highest — now at **7** incoming edges: Api, TileDownloader, DataAccess, RegionProcessing, RouteManagement, Tests, **IntegrationTests (new)**). Cycle 4 had Common at 6.
- **Max out-degree**: Tests (7 — unchanged).
- **TestSupport position**: leaf-of-test-subgraph; no production-layer importers (unchanged).
- **The new IntegrationTests → Common edge is justified**: integration-test seeds need the same deterministic `Uuidv5` algorithm the production code uses, otherwise the new NOT NULL `location_hash` column would force every seed to encode the SHA-1 byte order by hand. Reusing `SatelliteProvider.Common.Utils.Uuidv5` keeps the algorithm in one place (which is also where the cross-repo invariant with `gps-denied-onboard/components/c6_tile_cache/_uuid.py` lives).

## NuGet dependency hygiene (cycle 5)

| Package | Cycle-4 version | Cycle-5 version | Status |
|---------|-----------------|-----------------|--------|
| All NuGet packages across all 9 csproj files | unchanged | **unchanged** | **Zero NuGet bumps this cycle.** AZ-503-foundation uses framework-only types (`SHA1`, `SHA256`, `BinaryPrimitives`); AZ-504 is a 2-line shell-script edit. |
| Carry-overs (still OPEN) | Cycle-3 D2 (`Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks` flag), cycle-4 D4 (`Microsoft.IdentityModel.Tokens` 7.0.3 + `System.IdentityModel.Tokens.Jwt` 7.0.3 in TestSupport), `Serilog.AspNetCore` 8.0.3 fallback | unchanged | All three remain explicitly out of cycle-5 scope per `coderule.mdc` "scope discipline". |

## Database schema surface (cycle 5 delta)

| Object | Change | Source |
|--------|--------|--------|
| `tiles` table | **+4 columns**: `flight_id uuid NULL`, `location_hash uuid NOT NULL` (backfilled deterministically for pre-existing rows), `content_sha256 bytea NULL`, `legacy_id uuid NULL` | `014_AddTileIdentityColumns.sql` |
| `idx_tiles_unique_location_source` (AZ-484, float-based) | **DROPPED** | same |
| `idx_tiles_unique_location` (pre-AZ-484, defensive duplicate cleanup) | **DROPPED** | same |
| `idx_tiles_unique_identity` (integer-key + COALESCE(flight_id, zero-UUID)) | **CREATED** (unique) | same |
| `idx_tiles_location_hash` (location_hash lookups for the future AZ-505 inventory endpoint) | **CREATED** (non-unique) | same |
| Postgres extension `pgcrypto` | **CREATE EXTENSION IF NOT EXISTS** (used during the migration's session-scoped `pg_temp.uuidv5` PL/pgSQL function only) | same |

The migration runs in a single transaction and is idempotent under DbUp's journal. No table or column was renamed (per `coderule.mdc` "Do not rename any database objects without confirmation").

## Architecture / contract surface (cycle 5 delta)

- **Contract bumped**: `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0 → **v1.1.0** (additive — adds optional `metadata.flightId: uuid?`; adds derived `tileId` field in the response). Backward-compatible with cycle-4 clients.
- **No new public-API contract files.** (AZ-505 will introduce `inventory.md` next cycle.)
- **New per-flight on-disk path layout** for UAV tiles: `./tiles/uav/{flightId or 'none'}/{z}/{x}/{y}.jpg`. Additive — legacy paths under `./tiles/uav/{z}/{x}/{y}.jpg` are not moved. Documented in `uav-tile-upload.md` "File-path layout" section.
- **New cross-repo invariant**: the `TileNamespace` GUID `5b8d0c2e-7f1a-4d3b-9c5e-1f3a8e7d2b6c` in `SatelliteProvider.Common/Utils/Uuidv5.cs` must match the same constant in `gps-denied-onboard/components/c6_tile_cache/_uuid.py` (sibling workspace). Cycle-5 commit covers the satellite-provider side; the gps-denied-onboard side will be handled when AZ-505 / the consumer-side work runs.

## Net Architecture delta vs cycle 4

- **Resolved (closed by this cycle)**: 0 architecture-level findings closed (the cycle-3 perf-harness leftover is half-closed — AZ-504 script fix is verified working, but the exit-0 deletion criterion is blocked by recurring local DNS noise; replay #5 documented).
- **Newly introduced (informational only)**:
  - 2 Low informational findings in the security audit (F1-cy5 `metadata.flightId` not authenticated provenance; F2-cy5 `pgcrypto` deployment runbook gap on managed Postgres). Both are long-term recommendations, not code defects.
  - 1 Low Maintainability finding in the AZ-503 code review (the `contentSha256` soft-NULL guard in `TileService.BuildTileEntity` when `File.Exists` is false — practically unreachable, defensively kept).
  - 0 new Medium, 0 new High, 0 new Critical.
- **+1 cross-project edge** (IntegrationTests → Common) — justified (cross-repo invariant deduplication).
- **Contract delta**: 1 minor version bump on `uav-tile-upload.md` (1.0.0 → 1.1.0, additive).
- **Schema delta**: +1 migration (014), +4 columns, +2 indexes, -2 indexes, +1 Postgres extension.
- **Net Architecture delta**: 0 net architecture-level findings (the 3 new Lows are informational only, and the +1 edge + minor contract bump are net-neutral structural additions, not violations).

## What this snapshot says about cycle 5's shape

Cycle 5 is the project's first **schema-changing cycle since cycle 1's AZ-484** — a database migration with backfill, a new internal cross-component algorithm (`Uuidv5`), a contract minor bump, and a new on-disk layout for one tile source. Quantitatively it's small (4 SP delivered = 3 SP foundation + 1 SP script fix), but it lays foundational identity infrastructure that next cycle's AZ-505 (5 SP — inventory endpoint, HTTP/2, Leaflet covering index) is blocked on. The structural delta is bounded (`+1` import edge, `+1` minor contract version, +1 migration, +4 columns, +2 indexes, -2 indexes, 0 new csproj, 0 new NuGet bumps), and the DAG remains acyclic with the same 9 projects.
