[AZ-500] Cycle 4 Steps 12-15 sync (test-spec / docs / security / perf)
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful

Step 12 (Test-Spec Sync) - cycle-update mode
  - traceability-matrix: 8 AZ-500 AC rows + .NET 10 runtime
    restriction supersession + Cycle-4 coverage shape note
    (no new tests; ACs verified by re-running existing 78-test
    suite + build pipeline + manifest grep)

Step 13 (Update Docs) - task mode
  - FINAL_report, 00_discovery, architecture, module-layout,
    api_program, tests_unit: .NET 8 -> .NET 10 / C# 12 -> 14 /
    Swashbuckle 6.6.2 -> 10.1.7 + Microsoft.OpenApi 2.x
    refactor note in api_program; Serilog.AspNetCore 8.0.3
    fallback documented inline per AZ-500 Risk #4
  - deployment/{containerization, ci_cd_pipeline}: Docker
    aspnet/sdk:8.0 -> :10.0
  - ripple_log_cycle4: empty import-graph ripple recorded
    (Program.cs is entry point; ParameterDescriptionFilter only
    consumed by Program.cs; csproj/global.json/Dockerfile have
    no import edges)

Step 14 (Security Audit) - resume mode
  - dependency_scan_cycle4: AZ-500 19-package delta scanned;
    cycle-3 D1+D3 (CVE-2026-26130) closed by major-version
    bump; cycle-3 D2 (Test.Sdk 17.8.0 NuGet.Frameworks flag)
    carried over - explicitly out of AZ-500 scope
  - security_report_cycle4: PASS_WITH_WARNINGS (only carry-over
    Medium open; AZ-500 introduced 0 new Critical/High); cycle-3
    static_analysis/owasp_review/infrastructure_review carried
    forward unchanged (AZ-500 made no source-level edits to
    those surfaces)

Step 15 (Performance Test) - perf mode, full default-param run
  - perf_2026-05-12_cycle4: 7 Pass + 1 Unverified (PT-08 hit
    pre-existing scripts/run-performance-tests.sh:417 grep-
    pipefail bug, NOT a .NET 10 regression)
  - PT-07 warm p95 = 301ms (7.7x improvement vs cycle-3 short
    variant - .NET 10 pipeline + N=20 dilution); cold p95 =
    2782ms (-14%); PT-06 90ms (-49%)
  - AZ-500 NFR (Performance) MET for 7/8 scenarios
  - Cycle-3 perf-harness leftover updated with replay #3
    results; STAYS OPEN per AZ-500 Constraint (deletes only on
    fully clean run)

Recommended follow-up PBIs (out of cycle-4 scope, surfaced for
the backlog):
  - 1 SP fix scripts/run-performance-tests.sh:416-417 grep-
    pipefail (replace grep -o ... | wc -l with grep -c ... ||
    true) - unblocks PT-08 + closes the cycle-3 perf leftover
  - 3 SP migrate WithOpenApi(...) callsites to ASP.NET Core 10
    minimal-API metadata extensions (clears 8 ASPDEPR002
    warnings; recorded in batch_01_cycle4_review.md)
  - 1 SP Microsoft.OpenApi 2.x nullable cleanup (CS8604 in
    ParameterDescriptionFilter.cs:25)
  - 1 SP bump Microsoft.NET.Test.Sdk 17.8.0 -> 17.13.0+
    (closes cycle-3 D2 NuGet.Frameworks transitive flag)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-12 06:05:29 +03:00
parent de609cffa1
commit af4219fce6
15 changed files with 331 additions and 22 deletions
+5 -5
View File
@@ -105,22 +105,22 @@ satellite-provider/
| Category | Technology | Version |
|----------|-----------|---------|
| Language | C# | 12 (.NET 8.0) |
| Framework | ASP.NET Core (Minimal API) | 8.0 |
| Language | C# | 14 (.NET 10) — was C# 12 / .NET 8.0 through cycle 3 (AZ-500) |
| Framework | ASP.NET Core (Minimal API) | 10.0 — was 8.0 through cycle 3 (AZ-500) |
| Database | PostgreSQL | 16 (Docker image) |
| ORM/Data Access | Dapper | 2.1.35 |
| DB Migrations | DbUp (PostgreSQL) | 6.0.3 |
| Logging | Serilog (Console + File) | 8.0.3 |
| Logging | Serilog (Console + File) | 8.0.3 (Serilog.AspNetCore — fallback retained on .NET 10 per AZ-500 Risk #4: no 10.x line published; restores cleanly via netstandard 2.0) |
| Image Processing | SixLabors.ImageSharp | 3.1.11 |
| JSON Serialization | Newtonsoft.Json + System.Text.Json | 13.0.4 |
| API Docs | Swagger / Swashbuckle | 6.6.2 |
| API Docs | Swagger / Swashbuckle | 10.1.7 (was 6.6.2; bumped by AZ-500 to land Microsoft.OpenApi 2.x compat — required by ASP.NET Core 10) |
| HTTP Client | IHttpClientFactory | built-in |
| Containerization | Docker (multi-stage) | - |
| Orchestration | Docker Compose | - |
| CI/CD | Woodpecker CI | - |
| Unit Testing | xUnit + Moq + FluentAssertions | 2.5.3 / 4.20.72 / 8.8.0 |
| Integration Testing | Console app (custom harness) | - |
| SDK | .NET 8.0 (latestMinor rollForward) | 8.0.0+ |
| SDK | .NET 10 (latestMinor rollForward) | 10.0.0+ — was .NET 8.0 / 8.0.0+ through cycle 3 (AZ-500) |
## Dependency Graph
+2 -2
View File
@@ -2,7 +2,7 @@
## Executive Summary
Full bottom-up documentation of the Satellite Provider service — a .NET 8.0 backend that pre-downloads, caches, and composites satellite imagery for a GPS-denied UAV navigation system. The analysis identified 5 logical components across 16 modules, documented 6 system flows, and produced a complete data model, deployment guide, and architecture reference.
Full bottom-up documentation of the Satellite Provider service — a .NET 10 backend (migrated from .NET 8 LTS by AZ-500 in cycle 4) that pre-downloads, caches, and composites satellite imagery for a GPS-denied UAV navigation system. The analysis identified 5 logical components across 16 modules, documented 6 system flows, and produced a complete data model, deployment guide, and architecture reference.
## Problem Statement
@@ -12,7 +12,7 @@ UAVs operating without GPS need pre-cached satellite imagery for visual position
Single-instance containerized monolith with layered architecture (API → Services → DataAccess → PostgreSQL) and asynchronous background processing via in-process queues. No authentication (internal/trusted network service).
**Technology stack**: C# 12 / .NET 8.0, ASP.NET Core Minimal API, PostgreSQL 16, Dapper, Docker, Woodpecker CI
**Technology stack**: C# 14 / .NET 10 (cycle 4 — was C# 12 / .NET 8.0 through cycle 3), ASP.NET Core Minimal API, PostgreSQL 16, Dapper, Docker, Woodpecker CI
**Deployment**: Docker Compose (API + PostgreSQL), ARM64 primary, self-hosted registry
+3 -3
View File
@@ -25,7 +25,7 @@ The three Layer-3 service components are compile-time siblings: each only refere
- JWT-validated callers only — every HTTP endpoint requires a valid HS256-signed Bearer token, validated locally against a shared `JWT_SECRET` per the suite-level auth contract (`suite/_docs/10_auth.md`). Issuer/audience are intentionally not validated yet; signature + lifetime + ≥32-byte key are. Per-endpoint permission claims (e.g. `permissions: ["GPS"]` on the UAV upload) layer on top of this baseline.
**Authentication & Authorization** (AZ-487):
- Validation library: `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.25 (matches `Microsoft.AspNetCore.OpenApi` 8.0.25; AZ-496 bumped both packages from 8.0.21 to close the cycle-1 D1 + cycle-2 D3 supply-chain findings).
- Validation library: `Microsoft.AspNetCore.Authentication.JwtBearer` 10.0.7 (matches `Microsoft.AspNetCore.OpenApi` 10.0.7; AZ-496 bumped both packages from 8.0.21 → 8.0.25 in cycle 3 to close the cycle-1 D1 + cycle-2 D3 supply-chain findings, then AZ-500 bumped both 8.0.25 → 10.0.7 in cycle 4 as part of the .NET 8 → .NET 10 migration). The `TokenValidationParameters` shape is unchanged across the JwtBearer 8 → 10 jump — AZ-487/AZ-494 integration tests are the gate and all pass on .NET 10.
- Signing key: read from the `JWT_SECRET` environment variable (preferred) or the `Jwt:Secret` configuration key. Startup fails fast if the resolved secret is unset, empty, or shorter than 32 bytes (HMAC-SHA256 minimum per RFC 2104 §3).
- Token contract: `ValidateIssuerSigningKey = true`, `ValidateLifetime = true`, `RequireSignedTokens = true`, `RequireExpirationTime = true`, `ValidateIssuer = true` + `ValidIssuer = $JWT_ISSUER`, `ValidateAudience = true` + `ValidAudience = $JWT_AUDIENCE` (AZ-494), `ClockSkew = 30s`. The 5-minute JwtBearer default is intentionally tightened.
- Authorization model: every endpoint registered in `Program.cs` is decorated with `.RequireAuthorization()`. AZ-488 adds `permissions`-claim policies on top of this baseline (UAV upload requires `GPS`).
@@ -63,8 +63,8 @@ The N-source storage contract is authoritative in `_docs/02_document/contracts/d
| Layer | Technology | Version | Rationale |
|-------|-----------|---------|-----------|
| Language | C# | 12.0 | .NET ecosystem, strong typing |
| Framework | ASP.NET Core (Minimal API) | 10.0 | Lightweight HTTP hosting |
| Language | C# | 14.0 (was 12.0 through cycle 3 — AZ-500) | .NET ecosystem, strong typing |
| Framework | ASP.NET Core (Minimal API) | 10.0 (was 8.0 through cycle 3 — AZ-500) | Lightweight HTTP hosting |
| Database | PostgreSQL | 15+ | Reliable RDBMS, spatial-friendly |
| ORM | Dapper | latest | Micro-ORM, raw SQL control |
| Migrations | DbUp | latest | Simple SQL-file-based schema migrations |
@@ -19,7 +19,7 @@ flowchart LR
|----------|-------|
| Trigger | push, pull_request, manual |
| Branches | dev, stage, main |
| Image | mcr.microsoft.com/dotnet/sdk:8.0 |
| Image | mcr.microsoft.com/dotnet/sdk:10.0 (was `:8.0` through cycle 3 — bumped by AZ-500) |
| Steps | `dotnet restore``dotnet test` (Release config) |
| Output | TRX test results |
@@ -2,8 +2,8 @@
## Docker Image
**Base image**: `mcr.microsoft.com/dotnet/aspnet:8.0`
**Build image**: `mcr.microsoft.com/dotnet/sdk:8.0`
**Base image**: `mcr.microsoft.com/dotnet/aspnet:10.0` (was `:8.0` through cycle 3 — bumped by AZ-500)
**Build image**: `mcr.microsoft.com/dotnet/sdk:10.0` (was `:8.0` through cycle 3 — bumped by AZ-500)
**Build strategy**: Multi-stage (restore → build → publish → runtime)
**Exposed ports**: 8080 (HTTP), 8081 (management/metrics)
+1 -1
View File
@@ -127,7 +127,7 @@ The cycle-1 (AZ-487) and cycle-2 (AZ-488) code reviews each surfaced an F1 (Low
- `SatelliteProvider.Api/DTOs/UavTileBatchUploadRequest.cs` (added by AZ-488; multipart form binding envelope — kept in WebApi because it depends on `IFormFileCollection` + `[FromForm]`, both API-layer types)
- **Internal**: (none)
- **Owns**: `SatelliteProvider.Api/**`
- **PackageReferences (added by AZ-487, bumped by AZ-496)**: `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.25 (pinned to the same minor patch as `Microsoft.AspNetCore.OpenApi` 8.0.25; AZ-496 bumped both packages from 8.0.21 → 8.0.25 to close cycle-1 D1 + cycle-2 D3 supply-chain findings).
- **PackageReferences (added by AZ-487, bumped by AZ-496, then by AZ-500)**: `Microsoft.AspNetCore.Authentication.JwtBearer` 10.0.7 (pinned to the same minor patch as `Microsoft.AspNetCore.OpenApi` 10.0.7; AZ-496 bumped both packages from 8.0.21 → 8.0.25 in cycle 3 to close cycle-1 D1 + cycle-2 D3 supply-chain findings, then AZ-500 bumped both 8.0.25 → 10.0.7 in cycle 4 as part of the .NET 8 → .NET 10 migration; AZ-500 also bumped `Swashbuckle.AspNetCore` 6.6.2 → 10.1.7 here to land Microsoft.OpenApi 2.x compat required by ASP.NET Core 10).
- **Imports from**: Common (incl. AZ-488 UAV DTOs + `UavQualityConfig`), DataAccess, TileDownloader (incl. AZ-488 `IUavTileUploadHandler`), RegionProcessing, RouteManagement
- **Consumed by**: (none — top-level entry point)
+3 -1
View File
@@ -69,7 +69,9 @@ Buffers each `IFormFile` into memory, packages them as `UavUploadFile` records (
## Dependencies
All project references: Common, DataAccess, Services.
NuGet: `Serilog.AspNetCore`, `Swashbuckle.AspNetCore`, `Microsoft.AspNetCore.OpenApi` (8.0.25, bumped from 8.0.21 by AZ-496), `Microsoft.AspNetCore.Authentication.JwtBearer` (8.0.25, added at 8.0.21 by AZ-487, bumped by AZ-496), `SixLabors.ImageSharp`, `Newtonsoft.Json`.
NuGet: `Serilog.AspNetCore` (8.0.3 — fallback retained on .NET 10 per AZ-500 Risk #4: no 10.x line published as of cycle 4; documented in `AGENTS.md`), `Swashbuckle.AspNetCore` (10.1.7 — bumped from 6.6.2 by AZ-500 to land Microsoft.OpenApi 2.x compat required by ASP.NET Core 10), `Microsoft.AspNetCore.OpenApi` (10.0.7 — bumped from 8.0.25 by AZ-500), `Microsoft.AspNetCore.Authentication.JwtBearer` (10.0.7 — added at 8.0.21 by AZ-487, bumped to 8.0.25 by AZ-496, bumped to 10.0.7 by AZ-500), `SixLabors.ImageSharp`, `Newtonsoft.Json`.
**Microsoft.OpenApi 2.x refactor note (AZ-500)**: the major bump (1.x → 2.x) drove three internal Swashbuckle-setup edits in this file — `using Microsoft.OpenApi.Models;``using Microsoft.OpenApi;`; `AddSecurityRequirement(...)` rewritten to take a `Func<OpenApiDocument, OpenApiSecurityRequirement>` and use `OpenApiSecuritySchemeReference("Bearer")` instead of the removed `OpenApiSecurityScheme.Reference` shape; `MapType<UavTileBatchUploadRequest>` rewritten to use the new `JsonSchemaType` enum and `IDictionary<string, IOpenApiSchema>` properties bag. The Swagger document shape (paths, operations, the Bearer Authorize button, the multipart-batch upload schema) is preserved exactly — `SwaggerDocument_AdvertisesBearerSecurityScheme` and the AZ-353 swagger-ready integration assertions still pass. Eight `ASPDEPR002` deprecation warnings (`WithOpenApi(...)`) remain — they're recorded in `_docs/03_implementation/reviews/batch_01_cycle4_review.md` as a follow-up PBI; the API is still fully functional in .NET 10 (deprecated, not removed).
## Consumers
- HTTP clients (external)
+1 -1
View File
@@ -26,7 +26,7 @@ Existing baseline (pre-cycle-2) test classes cover `TileService`, `RegionService
## Dependencies
- Project references: `SatelliteProvider.Services.TileDownloader`, `SatelliteProvider.Services.RegionProcessing`, `SatelliteProvider.Services.RouteManagement`, `SatelliteProvider.Common`, `SatelliteProvider.DataAccess`, `SatelliteProvider.Api` (for the Authentication tests — added in AZ-487), `SatelliteProvider.TestSupport` (added by AZ-491; provides the canonical `JwtTokenFactory` consumed by both this project and `SatelliteProvider.IntegrationTests`).
- NuGet: xUnit (2.5.3), Moq (4.20.72), FluentAssertions (8.8.0), coverlet.collector (6.0.0), Microsoft.NET.Test.Sdk (17.8.0), Microsoft.Extensions.* (Caching.Memory, Configuration, DI, Logging, Options, Http), `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.25 (consumed transitively via the `ProjectReference` to `SatelliteProvider.Api`; AZ-487 added the dependency at 8.0.21, AZ-496 bumped it to 8.0.25), `SixLabors.ImageSharp` 3.1.11 (added by AZ-488 for the gate tests).
- NuGet: xUnit (2.5.3), Moq (4.20.72), FluentAssertions (8.8.0), coverlet.collector (6.0.0), Microsoft.NET.Test.Sdk (17.8.0), Microsoft.Extensions.* (Caching.Memory, Configuration, DI, Logging, Options, Http — all bumped from 9.0.10 → 10.0.7 by AZ-500 as a coordinated cycle-4 move), `Microsoft.AspNetCore.Authentication.JwtBearer` 10.0.7 (consumed transitively via the `ProjectReference` to `SatelliteProvider.Api`; AZ-487 added the dependency at 8.0.21, AZ-496 bumped it to 8.0.25, AZ-500 bumped it to 10.0.7), `SixLabors.ImageSharp` 3.1.11 (added by AZ-488 for the gate tests).
- `appsettings.json` copied to output (used by Authentication tests for the `Jwt` section binding scenario).
## Consumers
+35
View File
@@ -0,0 +1,35 @@
# Ripple Log — Cycle 4
**Cycle**: 4 (AZ-500 .NET 8 LTS → .NET 10 migration)
## Direct doc updates (Task Step 13)
| File | Reason |
|------|--------|
| `_docs/02_document/FINAL_report.md` (lines 5, 15) | Executive summary + technology stack now state `.NET 10` / `C# 14`; cycle-3 → cycle-4 supersession noted inline |
| `_docs/02_document/00_discovery.md` (Tech Stack table, rows: Language / Framework / Logging / API Docs / SDK) | Tech-stack table now states .NET 10 / ASP.NET Core 10 / Swashbuckle 10.1.7 / SDK 10.0.0+; Serilog.AspNetCore 8.0.3 fallback documented inline per AZ-500 Risk #4 |
| `_docs/02_document/architecture.md` (Authentication & Authorization paragraph + §2 Tech Stack table) | JwtBearer + OpenApi versions bumped 8.0.25 → 10.0.7; explicit note that the `TokenValidationParameters` shape is unchanged across the major bump (AZ-487/AZ-494 integration tests are the gate) |
| `_docs/02_document/module-layout.md` (SatelliteProvider.Api PackageReferences row) | JwtBearer + OpenApi versions bumped 8.0.25 → 10.0.7; Swashbuckle 6.6.2 → 10.1.7 noted inline as the Microsoft.OpenApi 2.x compat path |
| `_docs/02_document/modules/api_program.md` (Dependencies + new "Microsoft.OpenApi 2.x refactor note") | The major Swashbuckle/OpenApi bump drove three internal setup edits in `Program.cs` (using-directive, `AddSecurityRequirement``Func<OpenApiDocument, OpenApiSecurityRequirement>` + `OpenApiSecuritySchemeReference("Bearer")`, `MapType``JsonSchemaType` + `IDictionary<string, IOpenApiSchema>`); Swagger document shape (paths, Bearer Authorize button, multipart upload schema) is preserved exactly; 8 `ASPDEPR002` `WithOpenApi(...)` deprecations recorded as a follow-up PBI |
| `_docs/02_document/modules/tests_unit.md` (Dependencies row) | JwtBearer 8.0.25 → 10.0.7; Microsoft.Extensions.* coordinated bump 9.0.10 → 10.0.7 noted inline |
| `_docs/02_document/deployment/containerization.md` (Docker base + build images) | `mcr.microsoft.com/dotnet/aspnet:8.0` + `sdk:8.0``:10.0` |
| `_docs/02_document/deployment/ci_cd_pipeline.md` (01-test image row) | `mcr.microsoft.com/dotnet/sdk:8.0``:10.0` |
| `_docs/02_document/tests/traceability-matrix.md` (AC-mapping + Restrictions + Coverage shape notes) | AZ-500 AC-1..AC-8 rows appended; ".NET 8.0 runtime" restriction rewritten to ".NET 10 runtime"; cycle 4 coverage shape note added explaining why no new tests were generated (AZ-500 ACs are infrastructure-level, verified by re-running the existing 78-test suite + build pipeline + manifest grep) |
## Import-graph ripple (Task Step 0.5)
**Source-level edits in this cycle**: `SatelliteProvider.Api/Program.cs` (Microsoft.OpenApi 2.x setup refactor — internal to the Swashbuckle DI registration) and `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs` (single `using` directive change).
**Reverse-dependency search** (`rg "^using SatelliteProvider\.Api\.Swagger"` + project-reference scan):
- Nothing imports `Program.cs` — it's the application entry point.
- Nothing outside `SatelliteProvider.Api` imports `SatelliteProvider.Api.Swagger.ParameterDescriptionFilter` — it's consumed only by `Program.cs` (via Swashbuckle's `c.OperationFilter<ParameterDescriptionFilter>()` registration).
**Ripple set: EMPTY.** No downstream module/component docs are stale because of AZ-500's source-level edits — the public C# surface of `SatelliteProvider.Api` is unchanged; only the internal Swashbuckle wiring moved.
**csproj / global.json / Dockerfile / script edits** are infrastructure manifests with no code-import edges, so they don't contribute to the ripple either.
## Coverage shape notes
- AZ-500 is a runtime/SDK/package migration, not a feature change. Architecture, system flows, data model, contracts, and problem-level ACs are untouched (see Task Step 3/4 conditions in `.cursor/skills/document/workflows/task.md`).
- `_docs/02_document/ripple_log_cycle3.md` is intentionally left as-is — historical record of cycle 3's bumps.
- `_docs/02_document/architecture_compliance_baseline.md` is intentionally left as-is for this cycle. AZ-500 NFR (Compatibility) requires that the cycle-3 baseline still holds post-migration; that gate is the Step 11 full test suite (which passed: 271 unit + integration green) plus the Step 14 Security Audit (next step).
+15 -1
View File
@@ -78,12 +78,20 @@
| AZ-495 AC-1..AC-N | Doc folder convention formalized | doc-state AC — `.cursor/skills/new-task/SKILL.md` updated in batch 01; `_docs/02_document/module-layout.md` carries the convention | ✓ |
| AZ-496 AC-1 | `Microsoft.AspNetCore.Authentication.JwtBearer` bumped 8.0.21 → 8.0.25 in `SatelliteProvider.Api.csproj` | Structural: csproj diff visible in batch 01 commit; transitive update propagates to `Tests.csproj` via `ProjectReference` | ✓ |
| AZ-496 AC-2..AC-N | Suite still green at the new version | Full unit + integration suite at Step 11 — all green; SEC-05..SEC-11 + AZ-494 AC-1/AC-2 (which depend on `JwtBearer`) all PASS | ✓ |
| AZ-500 AC-1 | Every csproj targets `net10.0` | Structural: `grep -r "<TargetFramework>" --include="*.csproj"` returns 9/9 `net10.0`, 0 `net8.0` (verified at cycle 4 Step 11) | ✓ |
| AZ-500 AC-2 | `global.json` `sdk.version=10.0.0`, `rollForward=latestMinor` | Structural: file contents asserted; SDK roll-forward exercised by host running .NET 10.0.103 | ✓ |
| AZ-500 AC-3 | All Docker base images + CI images on `:10.0` | Structural: `grep -rE "mcr.microsoft.com/dotnet/" --include="*Dockerfile" --include="*.yml" --include="*.sh"` → 7/7 on `:10.0` | ✓ |
| AZ-500 AC-4 | `Microsoft.AspNetCore.*` + `Microsoft.Extensions.*` on `10.0.7`; `Serilog.AspNetCore` documented fallback `8.0.3` | Structural: csproj diff (19 references on `10.0.7`); Serilog.AspNetCore fallback rationale recorded in `AGENTS.md:244` per Risk #4 | ✓ |
| AZ-500 AC-5 | Perf-script bootstrap step succeeds (no exit 3) — closes cycle-3 SDK-mismatch leftover | `PERF_REPEAT_COUNT=2 PERF_UAV_BATCH_SIZE=2 ./scripts/run-performance-tests.sh` exit 1 (NOT 3 — bootstrap clean, build OK, JWT mint OK, PT-01..PT-07 PASS); leftover `_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md` updated with new (non-SDK) PT-08 grep-pipefail finding; full perf gate runs at Step 15 of cycle 4 | ✓ |
| AZ-500 AC-6 | All unit + integration tests pass on the migrated build | Full `./scripts/run-tests.sh --full` at cycle 4 Step 11 — 271/271 unit + integration suite green | ✓ |
| AZ-500 AC-7 | `docker-compose build` succeeds with no downgrade / framework / missing-image warnings | `run-tests.sh` Step 2 build path + `docker compose up -d --build` both succeeded; only warnings emitted are CS8604 nullable + ASPDEPR002 deprecation (neither category gated) | ✓ |
| AZ-500 AC-8 | Documentation reflects .NET 10 | `_docs/02_document/architecture.md` lines 5 + 67 (Tech Stack table) updated; `AGENTS.md` lines 9 + 240244 updated incl. Serilog fallback note | ✓ |
## Restrictions → Test Mapping
| Restriction | Tests | Coverage |
|-------------|-------|----------|
| .NET 8.0 runtime | All (via Docker image) | ✓ |
| .NET 10 runtime (cycle 4 — was .NET 8.0 LTS through cycle 3) | All (via Docker image `mcr.microsoft.com/dotnet/aspnet:10.0`); cycle 4 Step 11 full suite green | ✓ |
| PostgreSQL 16 | All (via docker-compose) | ✓ |
| Single instance | PT-05 (concurrent regions on one instance) | ✓ |
| Max 4 concurrent downloads | RS-05, RL-03 | ✓ |
@@ -124,3 +132,9 @@
**Coverage shape notes (Cycle 2):**
- AZ-487 AC-7 (Swagger UI Authorize) is verified programmatically (`SwaggerDocument_AdvertisesBearerSecurityScheme`) rather than via a real UI flow; marked `◐ doc-verified`. The end-to-end browser-UI Authorize-button check remains a manual smoke before deploy.
- AZ-487 perf NFR (< 1 ms JWT overhead) remains `◐ recorded`; not separately gated. AZ-488 perf NFR (PT-08) moved from `◐ recorded (Deferred)` to `✓` for batch p95 — see PT-08 row above. AZ-484 perf NFR (PT-07) moved from `◐ recorded` to `✓` — see PT-07 row above. The harness work landed in AZ-492 (cycle 3) along with the `Authorization: Bearer …` attach that AZ-487 silently broke for the perf script.
**Coverage shape notes (Cycle 4 — AZ-500 .NET 8 → .NET 10 migration):**
- All 8 AZ-500 ACs are infrastructure-level (TFM/SDK pin/Docker base/package version/build/test-suite/doc) and are verified by re-running the **existing** test suite, the build pipeline, and `grep` over manifests. **No new test cases were added** — the contract being tested is "the previous 78 tests still pass on the new toolchain", which Step 11 confirmed (271 unit + integration green). Total counts above are unchanged.
- AZ-500 AC-5 (perf-script bootstrap) demoted the cycle-3 SDK-mismatch leftover to a script-bug leftover (PT-08 grep-pipefail at `scripts/run-performance-tests.sh:417`). The full PT-01..PT-08 perf gate moves to cycle 4 Step 15 (Performance Test). The PT-07 / PT-08 coverage rows above remain `✓` because they reflect the harness's *measurement capability*, not the per-cycle measurement run.
- AZ-500 NFRs (Compatibility / Performance / Reliability / Security) propagate to existing rows rather than introducing new gates: Compatibility ⇒ cycle-3 architecture-compliance baseline (verified by Step 11 suite); Performance ⇒ Step 15 perf gate (PT-07/PT-08); Reliability ⇒ no `dotnet restore` failures in the migrated state (Step 11 build path); Security ⇒ Step 14 dependency-scan re-run.
- Restriction "**.NET 8.0 runtime**" was rewritten to "**.NET 10 runtime**" — this is a supersession (toolchain bump) not a new gate, so no Choose was needed per cycle-update rule 3.