# Structural Snapshot — 2026-05-12 (post-cycle 3, AZ-491 / AZ-492 / AZ-493 / AZ-494 / AZ-495 / AZ-496)

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

## Projects

| Layer | csproj | Cycle 3 delta |
|-------|--------|---------------|
| 1 (Foundation) | `SatelliteProvider.Common` | unchanged |
| 1 (Foundation) | `SatelliteProvider.DataAccess` | unchanged |
| 3 (Application) | `SatelliteProvider.Services.TileDownloader` | unchanged |
| 3 (Application) | `SatelliteProvider.Services.RegionProcessing` | unchanged |
| 3 (Application) | `SatelliteProvider.Services.RouteManagement` | unchanged |
| 4 (API / Entry) | `SatelliteProvider.Api` | +`Authentication/AuthenticationServiceCollectionExtensions` extended with `iss`/`aud` resolution + fail-fast (AZ-494); `Microsoft.AspNetCore.OpenApi` 8.0.21 → 8.0.25 and `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.21 → 8.0.25 (AZ-496) |
| **5 (Test-Support, NEW)** | **`SatelliteProvider.TestSupport`** | **NEW project** (AZ-491). `IsPackable=false`. Public surface: `JwtTokenFactory` (the canonical HS256 mint helper, with iss/aud parameters added by AZ-494) + `IntegrationTestResetGuard` (the two-guard model added by AZ-493). NuGet refs: `Microsoft.IdentityModel.Tokens 7.0.3` + `System.IdentityModel.Tokens.Jwt 7.0.3`. |
| 6 (Tests) | `SatelliteProvider.Tests` | +`ProjectReference` to `SatelliteProvider.TestSupport` (AZ-491); removed local `TestUtilities/JwtTokenFactory.cs` |
| 6 (Tests) | `SatelliteProvider.IntegrationTests` | +`ProjectReference` to `SatelliteProvider.TestSupport` (AZ-491); +`PerfBootstrap.cs` (AZ-492); +`IntegrationTestDatabaseReset.cs` (AZ-493); `JwtTestHelpers.cs` extended with `MintAuthenticated`/`MintExpired`/`ResolveIssuerOrThrow`/`ResolveAudienceOrThrow` (AZ-491 + AZ-494); removed direct `Microsoft.IdentityModel.JsonWebTokens` PackageReference |

**Project count**: 9 (+1 from cycle 2; new `SatelliteProvider.TestSupport`).

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

| Edge | Count |
|------|-------|
| 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} | 6 (unchanged; cycle-2 baseline) |
| **Tests → TestSupport** | **+1 (AZ-491)** |
| **IntegrationTests → TestSupport** | **+1 (AZ-491)** |

**Total ProjectReference edges**: 20 (+2 vs cycle 2). Both new edges go INTO TestSupport; no production-layer dependency on TestSupport (the IsPackable=false guarantee blocks accidental shipping).

## Source-import sites — cycle 3 delta

| Importer | Imports from | Cycle 3 delta |
|----------|--------------|---------------|
| WebApi `Authentication` | `Microsoft.IdentityModel.Tokens` | +1 site (`ResolveRequiredOrThrow` extension; `ValidateIssuer`/`ValidateAudience` flags on `TokenValidationParameters`) |
| IntegrationTests | `SatelliteProvider.TestSupport` | +N sites (`JwtTokenFactory`, `IntegrationTestResetGuard`) — replaces removed local helpers |
| Tests | `SatelliteProvider.TestSupport` | +N sites (`JwtTokenFactory` in 5 test files; `IntegrationTestResetGuard` in unit `IntegrationTestResetGuardTests`) |
| IntegrationTests | `Npgsql` | +1 site (`IntegrationTestDatabaseReset` uses `NpgsqlConnection` + `NpgsqlCommand`) |
| IntegrationTests | `SixLabors.ImageSharp` (already present) | +1 site (`PerfBootstrap.CreateValidJpeg`) |

## Graph properties

- **Cycles in project import graph**: 0 (clean DAG — unchanged)
- **Average ProjectReferences per component**: 20 / 9 = ~2.2 (cycle 2 = 12 / 8 = 1.5; growth is concentrated in test surface, not production)
- **Max in-degree**: Common (still highest at 6 — Api, TileDownloader, DataAccess, RegionProcessing, RouteManagement, Tests). TestSupport in-degree = 2 (Tests + IntegrationTests).
- **Max out-degree**: Tests (7 — adds the new TestSupport edge to its existing 6).
- **TestSupport position**: leaf-of-test-subgraph; no production-layer importers. Out-degree = 0 from a project-graph standpoint (it does not depend on any other satellite-provider project). NuGet-only dependencies.

## NuGet dependency hygiene (cycle 3)

| Package | Version | Status |
|---------|---------|--------|
| `Microsoft.AspNetCore.OpenApi` | 8.0.21 → **8.0.25** | RESOLVED D1 (AZ-496) |
| `Microsoft.AspNetCore.Authentication.JwtBearer` | 8.0.21 → **8.0.25** | RESOLVED D3 (AZ-496) |
| `Microsoft.IdentityModel.Tokens` (TestSupport) | 7.0.3 (NEW) | NEW D4 — CVE-2024-21319, test-only, bump to 7.1.2 tracked as future PBI |
| `System.IdentityModel.Tokens.Jwt` (TestSupport) | 7.0.3 (NEW) | NEW D4 — same advisory; same disposition |
| `SixLabors.ImageSharp` | 3.1.11 (unchanged) | clean |
| `Npgsql` | 9.0.2 (unchanged) | clean (now consumed by AZ-493's reset hook) |
| `Microsoft.Extensions.*` | 9.0.10 (unchanged across DataAccess, TileDownloader, Tests, RegionProcessing, RouteManagement) | consistent across all 5 sites |

## Architecture / contract surface (cycle 3 delta)

- No new public-API contracts under `_docs/02_document/contracts/` this cycle. AZ-494 strictly tightens token acceptance (rejects with 401 on iss/aud mismatch) without changing the public HTTP surface.
- The `Microsoft.AspNetCore.Authentication.JwtBearer` middleware now validates four claim-shape invariants instead of two (signing key + lifetime + **issuer** + **audience**).
- Cycle-2 contract `_docs/02_document/contracts/api/uav-tile-upload.md` v1.0.0 unchanged.

## Net Architecture delta vs cycle 2

- **Resolved**: F-AUTH-2 (iss/aud unvalidated), D1 (OpenApi 8.0.21 patch line), D3 (JwtBearer 8.0.21 patch line), PT-07/PT-08 cycle-2 leftover. **Total: 4 resolved.**
- **Newly introduced**: D4 (Low, test-only NU1902 pin), F-AUTH-3 (Informational, test-runner log), F-AUTH-4 (Informational, by-design DEV-ONLY placeholders), F-DBR-2 (Low, test-only TRUNCATE guard), F-PERF-1 (Low, operator-CLI token-on-stdout). F-DBR-1 explicit false-positive. **Total: 3 new Low + 2 Informational + 1 FP.**
- **Net Architecture delta**: -1 finding overall, -3 Medium-or-above (the 3 cycle-3 introductions are all Low or Informational; the 3 prior resolutions were 2 Medium + 1 Low).

First cycle with a net-negative architecture delta. Pattern: prior-retro Action items + targeted dependency bumps + spec hardening combined to retire more debt than they introduced.
