# Deploy Report — Cycle 4 (AZ-500)

**Date**: 2026-05-12
**Cycle**: 4
**Scope**: Single-task cycle — **AZ-500 .NET 8 LTS → .NET 10 migration** (cross-cutting infra: every csproj, both Dockerfiles, every CI script, two docs, plus the Microsoft.OpenApi 1.x → 2.x compat refactor in `Program.cs`).

## What is shipping

### Code changes (committed to `dev`, pushed to `origin/dev`)

| Commit | Subject |
|--------|---------|
| `c0f004d` | `[AZ-500] Cycle 4 Step 9: new-task .NET 10 migration` |
| `8131363` | `[AZ-500] .NET 8 -> .NET 10 migration` |
| `de609cf` | `[AZ-500] Cycle 4 implement-skill wrap-up reports` |
| `af4219f` | `[AZ-500] Cycle 4 Steps 12-15 sync (test-spec / docs / security / perf)` |

All 4 commits on `dev`, pushed to `origin/dev` as of this report.

### Database migration

**None this cycle.** AZ-500 is runtime/SDK only; the `tiles` table schema and all DbUp migrations are unchanged. Per AZ-500 Constraint: "Do not rename any database objects during this task."

### Configuration changes (operator must verify before promoting)

| Setting | Was | Now | Source |
|---------|-----|-----|--------|
| Host SDK on every dev/CI machine | .NET 8.0.x SDK installed (or rolling from .NET 10.0.x via global.json `latestMinor` — no, that one didn't roll because cycle-3 pin was `8.0.0/latestMinor`) | **.NET 10.0.x SDK installed** (pin is now `10.0.0/latestMinor`, so any 10.0.x patch SDK satisfies — `dotnet --list-sdks` should show ≥ 10.0.0) | AZ-500 AC-2 — `global.json`. Surfaced in cycle-3 perf-harness leftover (host had only 10.0.103, .NET 8 pin wouldn't roll). Now resolved at the project pin level. |
| Docker image tag (`api` service) | `mcr.microsoft.com/dotnet/aspnet:8.0` (floating, last resolved 8.0.25-bullseye-slim) | **`mcr.microsoft.com/dotnet/aspnet:10.0`** (floating; first build pulls latest 10.0.x patch from Microsoft) | AZ-500 AC-3 — `SatelliteProvider.Api/Dockerfile`. CI image (`mcr.microsoft.com/dotnet/sdk:10.0`) similarly bumped in `.woodpecker/01-test.yml` + `scripts/run-tests.sh`. |
| **No new env vars introduced.** | — | — | AZ-500 carries forward the cycle-3 env contract verbatim (`JWT_SECRET ≥ 32B`, `JWT_ISSUER`, `JWT_AUDIENCE`, `GOOGLE_MAPS_API_KEY`). |

### Container image

- **Source**: `SatelliteProvider.Api/Dockerfile` multi-stage build, base `mcr.microsoft.com/dotnet/aspnet:10.0` (was `:8.0`).
- **Verification on dev workstation (local)**: `docker compose up -d --build` → API healthy on `:18980`, `/swagger` returns 301, anonymous probe of `/api/satellite/region/<id>` returns 401 (expected — JWT enforcement). Verified at the start of Step 15 perf run.
- **Verification on CI**: `origin/dev` push at `af4219f` triggers Woodpecker `01-test` (now on `mcr.microsoft.com/dotnet/sdk:10.0`) → `02-build-push` → registry tag `dev-arm`. **Operator action**: confirm the next CI run on `dev` succeeds before promoting to staging.
- **Multi-arch**: `mcr.microsoft.com/dotnet/sdk:10.0` and `aspnet:10.0` are published as multi-arch (amd64 + arm64) by Microsoft — verified via `docker manifest inspect` in cycle 3 (no change in cycle 4); Risk #6 from AZ-500 spec is closed.

## Verification gates passed in this cycle

| Gate | Result | Evidence |
|------|--------|----------|
| Step 11 — Functional test suite | **PASS** | 271 unit + integration tests green; `_docs/03_implementation/implementation_report_dotnet10_migration_cycle4.md` |
| Step 12 — Test-Spec Sync | **PASS** | `_docs/02_document/tests/traceability-matrix.md` updated with 8 AZ-500 AC rows + .NET 10 runtime restriction supersession + Cycle-4 coverage shape note |
| Step 13 — Update Docs | **PASS** | 8 doc files synced + `_docs/02_document/ripple_log_cycle4.md` (empty import-graph ripple recorded with rationale) |
| Step 14 — Security Audit | **PASS_WITH_WARNINGS** | `_docs/05_security/dependency_scan_cycle4.md` + `security_report_cycle4.md`; 0 new Critical/High; cycle-3 D2 (`Microsoft.NET.Test.Sdk` carry-over) still open per AZ-500 scope |
| Step 15 — Performance Test | **PASS_WITH_UNVERIFIED** | `_docs/06_metrics/perf_2026-05-12_cycle4.md`; PT-01..PT-07 PASS (7.7x improvement on PT-07 warm p95); PT-08 unmeasurable (pre-existing script bug, not a .NET 10 regression) |

## Outstanding leftovers (NOT closed by cycle 4)

1. **`_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md`** — STAYS OPEN. Replay #3 (this cycle's Step 15 full run) appended; PT-08 still hits the same pre-existing `scripts/run-performance-tests.sh:417` grep-pipefail bug. Per AZ-500 Constraint: "leftover file is deleted ONLY when the full perf script runs cleanly." Closure path is the script-fix follow-up PBI below.

## Recommended follow-up PBIs (out of cycle-4 scope, surfaced for backlog)

| ID | Estimate | Title | Why |
|----|----------|-------|-----|
| (TBD) | 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-harness leftover. Trivial mechanical fix. |
| (TBD) | 3 SP | Migrate `WithOpenApi(...)` callsites to ASP.NET Core 10 minimal-API metadata extensions | Clears 8 `ASPDEPR002` deprecation warnings in `Program.cs`. Recorded in `_docs/03_implementation/reviews/batch_01_cycle4_review.md`. API still fully functional in .NET 10 (deprecated, not removed). |
| (TBD) | 1 SP | Microsoft.OpenApi 2.x nullable cleanup | `CS8604` warning in `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs:25` exposed by the major bump. |
| (TBD) | 1 SP | Bump `Microsoft.NET.Test.Sdk` 17.8.0 → 17.13.0+ | Closes cycle-3 D2 (transitive `NuGet.Frameworks` flag). Test-runtime exposure only; safe to land independently. |
| (TBD) | 1 SP (recheck per cycle) | `Serilog.AspNetCore` 8.0.3 → 10.x | Currently retains 8.0.3 fallback per AZ-500 Risk #4 (no 10.x line published as of cycle 4). Re-check at every cycle start; bump as soon as a 10.x line ships. Removes the AGENTS.md/00_discovery/api_program inline-fallback notes. |

## Operator runbook for promoting to staging / production

1. Wait for CI to confirm `dev` build at `af4219f` is green and the registry has the new `dev-arm` (and any new amd64) tag built on `aspnet:10.0`.
2. Verify on a dev environment that `docker pull` of the new tag + `docker compose up -d` brings the API up healthy on the configured port; smoke-test `/swagger` (expect 200/301) and `/api/satellite/region/<random>` (expect 401, no 500).
3. If staging/production hosts have not yet provisioned the .NET 10 SDK on the *host* (only relevant for non-containerised perf-harness invocations), update host provisioning to install .NET 10.0.x SDK. The deployed container itself does NOT need a host SDK — only `scripts/run-performance-tests.sh` does (and that script normally only runs from a developer or CI machine).
4. No DB migration to apply. No env-var change to coordinate.
5. Roll forward; if a regression appears, roll back to the prior `dev-arm` tag (the one built from commit `de609cf` or earlier) — same compose contract, same env vars, same DB schema.
