diff --git a/.woodpecker/01-test.yml b/.woodpecker/01-test.yml index c210f77..56cb259 100644 --- a/.woodpecker/01-test.yml +++ b/.woodpecker/01-test.yml @@ -7,7 +7,7 @@ labels: steps: - name: unit-tests - image: mcr.microsoft.com/dotnet/sdk:8.0 + image: mcr.microsoft.com/dotnet/sdk:10.0 commands: - dotnet restore SatelliteProvider.sln - dotnet test SatelliteProvider.Tests/SatelliteProvider.Tests.csproj --no-restore --configuration Release --logger "console;verbosity=normal" --logger "trx;LogFileName=test-results.trx" --results-directory /app/test-results diff --git a/AGENTS.md b/AGENTS.md index 1fa9906..42e54cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,11 +2,11 @@ ## System Overview -This is a .NET 8.0 ASP.NET Web API service that downloads, stores, and manages satellite imagery tiles from Google Maps. The service supports region-based tile requests, route planning with intermediate points, and geofencing capabilities. +This is a .NET 10 ASP.NET Web API service that downloads, stores, and manages satellite imagery tiles from Google Maps. The service supports region-based tile requests, route planning with intermediate points, and geofencing capabilities. ## Tech Stack -- **.NET 8.0** with ASP.NET Core Web API +- **.NET 10** with ASP.NET Core Web API - **PostgreSQL** database (via Docker) - **Dapper** for data access (ORM) - **DbUp** for database migrations @@ -236,10 +236,12 @@ Development defaults: ## Dependencies and Versions -Key packages (all .NET 8.0): -- Microsoft.AspNetCore.OpenApi 8.0.21 +Key packages (all .NET 10): +- Microsoft.AspNetCore.OpenApi 10.0.7 +- Microsoft.AspNetCore.Authentication.JwtBearer 10.0.7 +- Microsoft.Extensions.* 10.0.7 - Swashbuckle.AspNetCore 6.6.2 -- Serilog.AspNetCore 8.0.3 +- Serilog.AspNetCore 8.0.3 (intentional — 10.0.0 requires Serilog.Sinks.File ≥ 7.0.0; bumping Serilog.Sinks.File is out of AZ-500 scope per "no unrelated package bumps") - SixLabors.ImageSharp 3.1.11 - Newtonsoft.Json 13.0.4 - Dapper (check DataAccess csproj) diff --git a/SatelliteProvider.Api/Dockerfile b/SatelliteProvider.Api/Dockerfile index 209e03d..af9394d 100644 --- a/SatelliteProvider.Api/Dockerfile +++ b/SatelliteProvider.Api/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base WORKDIR /app EXPOSE 8080 EXPOSE 8081 -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY ["SatelliteProvider.Api/SatelliteProvider.Api.csproj", "SatelliteProvider.Api/"] COPY ["SatelliteProvider.Common/SatelliteProvider.Common.csproj", "SatelliteProvider.Common/"] diff --git a/SatelliteProvider.Api/Program.cs b/SatelliteProvider.Api/Program.cs index 79ffd35..6fd0766 100644 --- a/SatelliteProvider.Api/Program.cs +++ b/SatelliteProvider.Api/Program.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; using SatelliteProvider.Api; using SatelliteProvider.Api.Authentication; @@ -108,36 +108,29 @@ builder.Services.AddSwaggerGen(c => Description = "JWT Authorization header using the Bearer scheme. Example: 'Bearer {token}'" }); - c.AddSecurityRequirement(new OpenApiSecurityRequirement + c.AddSecurityRequirement(_ => new OpenApiSecurityRequirement { { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - Array.Empty() + new OpenApiSecuritySchemeReference("Bearer"), + new List() } }); c.MapType(() => new OpenApiSchema { - Type = "object", - Properties = new Dictionary + Type = JsonSchemaType.Object, + Properties = new Dictionary { - ["metadata"] = new() + ["metadata"] = new OpenApiSchema { - Type = "string", + Type = JsonSchemaType.String, Description = "JSON document `{ \"items\": [ { \"latitude\", \"longitude\", \"tileZoom\", \"tileSizeMeters\", \"capturedAt\" } ] }` where item ordinal index aligns with the matching file in `files`." }, - ["files"] = new() + ["files"] = new OpenApiSchema { - Type = "array", + Type = JsonSchemaType.Array, Description = "UAV tile JPEG files in the same order as `metadata.items`.", - Items = new OpenApiSchema { Type = "string", Format = "binary" } + Items = new OpenApiSchema { Type = JsonSchemaType.String, Format = "binary" } } }, Required = new HashSet { "metadata", "files" } diff --git a/SatelliteProvider.Api/SatelliteProvider.Api.csproj b/SatelliteProvider.Api/SatelliteProvider.Api.csproj index c14ee05..d8240ae 100644 --- a/SatelliteProvider.Api/SatelliteProvider.Api.csproj +++ b/SatelliteProvider.Api/SatelliteProvider.Api.csproj @@ -1,19 +1,19 @@ - net8.0 + net10.0 enable enable - - + + - + diff --git a/SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs b/SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs index 2392483..736a8a7 100644 --- a/SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs +++ b/SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace SatelliteProvider.Api.Swagger; diff --git a/SatelliteProvider.Common/SatelliteProvider.Common.csproj b/SatelliteProvider.Common/SatelliteProvider.Common.csproj index ed767a0..f185c57 100644 --- a/SatelliteProvider.Common/SatelliteProvider.Common.csproj +++ b/SatelliteProvider.Common/SatelliteProvider.Common.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable diff --git a/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj b/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj index e30dce4..a15026d 100644 --- a/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj +++ b/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable enable @@ -10,8 +10,8 @@ - - + + diff --git a/SatelliteProvider.IntegrationTests/Dockerfile b/SatelliteProvider.IntegrationTests/Dockerfile index 22c8291..a2669d8 100644 --- a/SatelliteProvider.IntegrationTests/Dockerfile +++ b/SatelliteProvider.IntegrationTests/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY ["SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj", "SatelliteProvider.IntegrationTests/"] COPY ["SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj", "SatelliteProvider.TestSupport/"] @@ -10,7 +10,7 @@ RUN dotnet build "SatelliteProvider.IntegrationTests.csproj" -c Release -o /app/ FROM build AS publish RUN dotnet publish "SatelliteProvider.IntegrationTests.csproj" -c Release -o /app/publish /p:UseAppHost=false -FROM mcr.microsoft.com/dotnet/runtime:8.0 AS final +FROM mcr.microsoft.com/dotnet/runtime:10.0 AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "SatelliteProvider.IntegrationTests.dll"] diff --git a/SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj b/SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj index e642543..1dfbe22 100644 --- a/SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj +++ b/SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable enable diff --git a/SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj b/SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj index 5911051..6fbf32a 100644 --- a/SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj +++ b/SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj @@ -1,16 +1,16 @@ - net8.0 + net10.0 enable enable - - - - + + + + diff --git a/SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj b/SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj index 5911051..6fbf32a 100644 --- a/SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj +++ b/SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj @@ -1,16 +1,16 @@ - net8.0 + net10.0 enable enable - - - - + + + + diff --git a/SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj b/SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj index f2e9bd3..67cffa0 100644 --- a/SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj +++ b/SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj @@ -1,16 +1,16 @@ - net8.0 + net10.0 enable enable - - - - + + + + diff --git a/SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj b/SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj index 14a97eb..a59997d 100644 --- a/SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj +++ b/SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable false diff --git a/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj b/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj index ca6d690..addbcb6 100644 --- a/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj +++ b/SatelliteProvider.Tests/SatelliteProvider.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 enable enable @@ -12,13 +12,13 @@ - - - - - - - + + + + + + + diff --git a/_docs/02_document/architecture.md b/_docs/02_document/architecture.md index 4456a24..3e685c3 100644 --- a/_docs/02_document/architecture.md +++ b/_docs/02_document/architecture.md @@ -2,7 +2,7 @@ ## Architecture Vision -Satellite Provider is a self-hosted .NET 8.0 backend service that pre-downloads, caches, and composites Google Maps satellite imagery for offline use. It runs as a single containerized monolith with PostgreSQL, processing requests asynchronously via in-process queues. The dominant pattern is a layered architecture (API → Services → DataAccess → PostgreSQL) with background hosted services for long-running work. +Satellite Provider is a self-hosted .NET 10 backend service that pre-downloads, caches, and composites Google Maps satellite imagery for offline use. It runs as a single containerized monolith with PostgreSQL, processing requests asynchronously via in-process queues. The dominant pattern is a layered architecture (API → Services → DataAccess → PostgreSQL) with background hosted services for long-running work. **Components & responsibilities** (each owns its own `.csproj` since AZ-309): - **Common** (`SatelliteProvider.Common`) — Shared contracts: DTOs, service interfaces, common exceptions, configuration models, geospatial math @@ -64,7 +64,7 @@ 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) | 8.0 | Lightweight HTTP hosting | +| Framework | ASP.NET Core (Minimal API) | 10.0 | 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 | diff --git a/_docs/02_tasks/_dependencies_table.md b/_docs/02_tasks/_dependencies_table.md index cacf3af..eeb95d7 100644 --- a/_docs/02_tasks/_dependencies_table.md +++ b/_docs/02_tasks/_dependencies_table.md @@ -90,7 +90,7 @@ Source: cycle-3 perf-harness leftover replay surfaced the host SDK / project SDK | Task | Title | Depends On | Points | Status | |------|-------|-----------|--------|--------| -| AZ-500 | .NET 8 → .NET 10 migration (TFM + SDK pin + Docker images + CI images + Microsoft.AspNetCore.* + Microsoft.Extensions.* + Serilog.AspNetCore) | — | 5 | To Do | +| AZ-500 | .NET 8 → .NET 10 migration (TFM + SDK pin + Docker images + CI images + Microsoft.AspNetCore.* + Microsoft.Extensions.* + Serilog.AspNetCore) | — | 5 | Done (In Testing) | ## Execution Order diff --git a/_docs/02_tasks/todo/AZ-500_dotnet10_migration.md b/_docs/02_tasks/done/AZ-500_dotnet10_migration.md similarity index 100% rename from _docs/02_tasks/todo/AZ-500_dotnet10_migration.md rename to _docs/02_tasks/done/AZ-500_dotnet10_migration.md diff --git a/_docs/03_implementation/batch_01_cycle4_report.md b/_docs/03_implementation/batch_01_cycle4_report.md new file mode 100644 index 0000000..acf2ce7 --- /dev/null +++ b/_docs/03_implementation/batch_01_cycle4_report.md @@ -0,0 +1,95 @@ +# Batch Report + +**Batch**: 1 (cycle 4) +**Tasks**: AZ-500_dotnet10_migration +**Date**: 2026-05-12 + +## Task Results + +| Task | Status | Files Modified | Tests | AC Coverage | Issues | +|------|--------|---------------|-------|-------------|--------| +| AZ-500_dotnet10_migration | Done | 18 source/config + 2 doc + 1 leftover | 271/271 unit + full integration suite PASS | 8/8 ACs covered | 2 Medium (deferred, scope-protected) + 1 Low scope-doc | + +## Files Modified (21 total) + +**Build / project files (12)**: +- `global.json` +- `SatelliteProvider.Api/SatelliteProvider.Api.csproj` +- `SatelliteProvider.Common/SatelliteProvider.Common.csproj` +- `SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj` +- `SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj` +- `SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj` +- `SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj` +- `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj` +- `SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj` +- `SatelliteProvider.Tests/SatelliteProvider.Tests.csproj` +- `SatelliteProvider.Api/Dockerfile` +- `SatelliteProvider.IntegrationTests/Dockerfile` + +**Source code (2)**: +- `SatelliteProvider.Api/Program.cs` — Microsoft.OpenApi 2.x refactor (namespace + `OpenApiSecuritySchemeReference` + `JsonSchemaType` + `IOpenApiSchema` properties) +- `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs` — namespace update + +**Scripts / CI (3)**: +- `scripts/run-tests.sh` — 3× `mcr.microsoft.com/dotnet/sdk:8.0` → `:10.0` +- `scripts/run-performance-tests.sh` — `bin/Release/net8.0/` → `bin/Release/net10.0/` (necessary for AC-5) +- `.woodpecker/01-test.yml` — `mcr.microsoft.com/dotnet/sdk:8.0` → `:10.0` + +**Documentation (2)**: +- `_docs/02_document/architecture.md` — Tech Stack table + Architecture Vision prose updated to .NET 10 +- `AGENTS.md` — Tech Stack section + Key packages list updated; Serilog.AspNetCore 8.0.3 fallback documented + +**Process (2)**: +- `_docs/_autodev_state.md` — phase tracking +- `_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md` — replay #2 result added (bootstrap clean; PT-08 grep-pipefail bug surfaced as new follow-up) + +## Per-Task Notes + +### AZ-500: .NET 8 → .NET 10 migration + +**Implementation order** (single batch, sequential): + +1. Inventory current state: 9 csproj files on `net8.0`; `global.json sdk.version=8.0.0`; 4 Docker base image refs; 19 `Microsoft.AspNetCore.*` / `Microsoft.Extensions.*` package refs. +2. Bump `global.json` SDK pin to `10.0.0` / `latestMinor`. +3. Bump every `` from `net8.0` to `net10.0`. +4. Bump all `Microsoft.AspNetCore.*` (8.0.25) and `Microsoft.Extensions.*` (9.0.10) packages to `10.0.7` (highest 10.0 patch on NuGet at run time). +5. Initial attempt with `Serilog.AspNetCore 10.0.0` failed `dotnet restore` with NU1605 (transitive dep `Serilog.Sinks.File >= 7.0.0` conflicts with the project's pinned `6.0.0`). Per Risk #4 mitigation + "no unrelated package bumps" constraint, reverted Serilog.AspNetCore to `8.0.3` and documented in AGENTS.md. +6. Bump Docker base images in 2 Dockerfiles (`aspnet:10.0`, `sdk:10.0`, `runtime:10.0`). +7. Bump CI/script images: `scripts/run-tests.sh` (3 refs) + `.woodpecker/01-test.yml` (1 ref). +8. Initial `Microsoft.AspNetCore.OpenApi 10.0.7` upgrade pulled `Microsoft.OpenApi 2.x` which removed the `Microsoft.OpenApi.Models` namespace. Compilation failed (CS0234, CS0246, CS7069) in `Program.cs` and `ParameterDescriptionFilter.cs`. User chose: bump Swashbuckle to 10.1.7 (which targets `Microsoft.OpenApi 2.4.1`) + refactor source to the 2.x API surface (`OpenApiSecuritySchemeReference`, `JsonSchemaType`, `IOpenApiSchema` dictionary properties, `using Microsoft.OpenApi;`). +9. After build clean, found one remaining compile error: `OpenApiSecurityRequirement` collection initializer expected `List` and Swashbuckle's `AddSecurityRequirement` expected `Func`. Fixed by wrapping in lambda + `new List()`. +10. `./scripts/run-tests.sh --full` → exit 0 (271/271 unit + full integration suite green). +11. `docker compose up -d --build` → API healthy on `:18980` (anonymous request → 401, swagger → 200). +12. Found `scripts/run-performance-tests.sh:49` had a hardcoded `bin/Release/net8.0/` path that would fail post-migration; bumped to `net10.0/`. +13. AC-5 perf-bootstrap smoke (`PERF_REPEAT_COUNT=2 PERF_UAV_BATCH_SIZE=2`): build succeeded (no exit 3 — bootstrap clean), JWT mint OK, PT-01..PT-07 PASS. PT-08 crashed at `rejected=$(grep -o ... | wc -l | tr -d ' ')` because `grep -o` exits 1 on zero matches and `set -o pipefail` propagates. Pre-existing script bug, surfaced now because cycle 3 never reached PT-08. Documented in the perf-leftover file as a new follow-up; AC-5's bootstrap-only gate is satisfied. +14. AC-8 docs verified via `grep` over `architecture.md` and `AGENTS.md` — both reflect `.NET 10` / `ASP.NET Core 10`. + +**Unresolved questions**: none. + +**Blockers**: none. + +## AC Test Coverage + +All 8 ACs covered (full trace in `reviews/batch_01_cycle4_review.md` → "AC Verification Trace"). Verification mix matches the task spec's Blackbox Tests table: AC-1/2/3/4/8 by `grep`, AC-6 by `./scripts/run-tests.sh --full`, AC-7 by `docker compose build`, AC-5 by manual smoke run. + +## Code Review Verdict + +**PASS_WITH_WARNINGS** — see `_docs/03_implementation/reviews/batch_01_cycle4_review.md`. + +Findings (all deferred per "scope discipline"; see review report Findings section + Recommended Follow-Up PBIs): + +1. F1 Medium — `WithOpenApi(...)` deprecation (ASPDEPR002) at 8 callsites in `Program.cs`. Migration intentionally preserved API surface per Risk #2. +2. F2 Medium — CS8604 nullable warning in `ParameterDescriptionFilter.cs:25` from Microsoft.OpenApi 2.x stricter annotations. +3. F3 Low — `scripts/run-performance-tests.sh:49` path fix was implicit in AC-5 but not in `Scope/Included`. Necessary, accepted. + +## Auto-Fix Attempts + +0 (no FAIL findings; PASS_WITH_WARNINGS proceeds without auto-fix). + +## Stuck Agents + +None. + +## Next Batch + +All AZ-500 work complete. Next: implement-skill Step 13 (archive task to `_docs/02_tasks/done/`) → Step 15 (Product Implementation Completeness Gate) → Step 16 (full test run is already green; record handoff to test-run skill in autodev Step 11) → autodev Step 11 (Run Tests) → Step 12 (Test-Spec Sync) → ... diff --git a/_docs/03_implementation/reviews/batch_01_cycle4_review.md b/_docs/03_implementation/reviews/batch_01_cycle4_review.md new file mode 100644 index 0000000..b27ee1d --- /dev/null +++ b/_docs/03_implementation/reviews/batch_01_cycle4_review.md @@ -0,0 +1,84 @@ +# Code Review Report + +**Batch**: 1 (cycle 4) — `AZ-500_dotnet10_migration` +**Date**: 2026-05-12 +**Verdict**: PASS_WITH_WARNINGS + +## Findings + +| # | Severity | Category | File:Line | Title | +|---|----------|----------|-----------|-------| +| 1 | Medium | Maintainability | SatelliteProvider.Api/Program.cs:174–219 | `WithOpenApi(...)` extension is deprecated in ASP.NET Core 10 (ASPDEPR002) — slated for removal | +| 2 | Medium | Maintainability | SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs:25 | CS8604 — `parameter.Name` is nullable in Microsoft.OpenApi 2.x; missing null guard | +| 3 | Low | Scope | scripts/run-performance-tests.sh:49 | Fix to `bin/Release/netX.0/` path was implicit in AC-5 but not explicitly listed in task spec's `Scope/Included` | + +### Finding Details + +**F1: `WithOpenApi(...)` extension is deprecated in ASP.NET Core 10 (ASPDEPR002)** (Medium / Maintainability) +- Location: `SatelliteProvider.Api/Program.cs:174,178,182,187,199,207,211,219` (8 endpoint mappings) +- Description: `OpenApiEndpointConventionBuilderExtensions.WithOpenApi(TBuilder, Func)` is marked obsolete with diagnostic ID `ASPDEPR002` ("WithOpenApi is deprecated and will be removed in a future release"). The build emits 8 deprecation warnings — one per endpoint that calls `.WithOpenApi(op => new(op) { Summary = ... })`. The migration intentionally kept the existing surface to minimise behavioural risk (Risk #2 in AZ-500 task spec), but this is a known follow-up. See https://aka.ms/aspnet/deprecate/002 for the recommended replacement (`.WithSummary(...)` / `.WithDescription(...)` / `.WithName(...)` minimal-API metadata extensions). +- Suggestion: Open a follow-up PBI to migrate the 8 callsites to the new minimal-API metadata extensions (`WithSummary`, `WithDescription`, `WithName`, `WithTags`). Out of AZ-500 scope per `coderule.mdc` "scope discipline" — AZ-500's contract is "preserve behaviour during runtime/SDK migration, do not adopt new APIs." +- Task: AZ-500 + +**F2: CS8604 — `parameter.Name` is nullable in Microsoft.OpenApi 2.x** (Medium / Maintainability) +- Location: `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs:25` + ```csharp + if (parameterDescriptions.TryGetValue(parameter.Name, out var description)) + ``` +- Description: Microsoft.OpenApi 2.x added stricter nullable annotations — `OpenApiParameter.Name` is now declared `string?`, while `Dictionary.TryGetValue(string key, ...)` requires non-null. The build emits one CS8604 warning. At runtime any parameter without a `Name` would NRE here, although in practice every Swashbuckle-emitted parameter has a non-null `Name`. Trivial defensive fix. +- Suggestion (one-line, can land in this batch if user prefers): + ```csharp + if (parameter.Name is { } name && parameterDescriptions.TryGetValue(name, out var description)) + ``` + Otherwise defer to a follow-up nullable-cleanup PBI. +- Task: AZ-500 + +**F3: `scripts/run-performance-tests.sh:49` path fix was implicit, not explicitly listed in task scope** (Low / Scope) +- Location: `scripts/run-performance-tests.sh:49` — `PERF_DLL=".../bin/Release/net8.0/..."` → `.../bin/Release/net10.0/...` +- Description: AZ-500's `Scope/Included` lists `scripts/run-tests.sh` and `.woodpecker/01-test.yml` for image bumps but does NOT explicitly mention `scripts/run-performance-tests.sh`. However, AC-5 requires the perf-bootstrap to succeed; without this path fix, `dotnet "$PERF_DLL"` would always fail (file not found at the old path). This is necessary scope creep, not opportunistic — AC-5 cannot pass without it. Recorded as Low/Scope rather than Spec-Gap because the fix is required by AC-5's letter. +- Suggestion: Accept as in-scope. No code change needed — this finding is documentary. +- Task: AZ-500 + +## Phase Summary + +| Phase | Result | Notes | +|-------|--------|-------| +| 1. Context Loading | OK | Task spec read; 21 changed files mapped to AZ-500 | +| 2. Spec Compliance | PASS | All 8 ACs satisfied (verified by grep + build + test run + manual smoke) | +| 3. Code Quality | PASS_WITH_WARNINGS | F1 + F2 flagged; otherwise pure version-bump diff | +| 4. Security Quick-Scan | PASS | No new code paths; JWT validation contract preserved (AC-6 covers AZ-487/AZ-494 regression) | +| 5. Performance Scan | PASS | No new logic; .NET 10 ASP.NET pipeline expected to improve, not regress (gated at Step 15) | +| 6. Cross-Task Consistency | N/A | Single-task batch | +| 7. Architecture Compliance | PASS | No layer-direction violations; no new cross-component imports; `using Microsoft.OpenApi;` is external | + +## AC Verification Trace + +| AC | Verification | Result | +|----|-------------|--------| +| AC-1 (every csproj net10.0) | `grep -r "" --include="*.csproj"` → 9/9 net10.0, 0 net8.0 | PASS | +| AC-2 (global.json sdk.version=10.0.0) | `cat global.json` → `"version": "10.0.0", "rollForward": "latestMinor"` | PASS | +| AC-3 (all docker images :10.0) | `grep -rE "mcr.microsoft.com/dotnet/" --include="*Dockerfile" --include="*.yml" --include="*.sh"` → 7/7 references on `:10.0` | PASS | +| AC-4 (M.AspNetCore.* / M.Extensions.* on 10.0.7) | `grep` over csprojs → 19 references on `10.0.7`; Serilog.AspNetCore at `8.0.3` (documented per Risk #4 in AGENTS.md:244) | PASS | +| AC-5 (perf-bootstrap succeeds, leftover updated) | `PERF_REPEAT_COUNT=2 PERF_UAV_BATCH_SIZE=2 ./scripts/run-performance-tests.sh` → exit code 1 (NOT 3 — bootstrap succeeded, build OK, JWT mint OK, PT-01..PT-07 PASS, PT-08 crashed on pre-existing grep-pipefail bug). Leftover file updated with non-SDK failure detail. Per AZ-500 Constraints last bullet, leftover stays in place — closed by Step 15 of cycle 4 after the script grep fix lands. | PASS | +| AC-6 (`./scripts/run-tests.sh --full` green) | Step 0 dotnet format → PASS; Step 1 unit tests → 271/271 passed in 4.2s; Step 2 integration tests → docker-compose with `--abort-on-container-exit --exit-code-from integration-tests` exited 0; visible PASSED markers cover JWT/UAV/Tile/Region/Route + 4 complex/extended route scenarios | PASS | +| AC-7 (`docker-compose build` succeeds, no downgrade warnings) | Both `run-tests.sh` Step 2 build AND a separate `docker compose up -d --build` succeeded; only warnings emitted are F1 (ASPDEPR002) + F2 (CS8604) — neither is "package downgrade", "framework conflict", or "missing base image" | PASS | +| AC-8 (docs reflect .NET 10) | `_docs/02_document/architecture.md:5` ".NET 10", line 67 Tech Stack table "10.0"; `AGENTS.md:9` ".NET 10", lines 240–244 packages list updated with Serilog fallback note | PASS | + +## Auto-Fix Eligibility + +Per `implement/SKILL.md` Step 10 matrix: + +- F1 (Medium / Maintainability) → eligible, but **deferred** — fix would expand scope beyond AZ-500's "preserve behaviour" contract; needs a follow-up PBI to migrate 8 endpoints to the new metadata API. Surfaced for user decision. +- F2 (Medium / Maintainability) → eligible auto-fix. Trivial 1-line null guard. User can opt to fold into this batch or defer to a nullable-cleanup PBI. +- F3 (Low / Scope) → no fix needed, documentary only. + +## Verdict Logic + +- 0 Critical, 0 High → no FAIL gate triggered. +- 2 Medium + 1 Low → **PASS_WITH_WARNINGS**. + +## Recommended Follow-Up PBIs (out of AZ-500 scope) + +1. **`scripts/run-performance-tests.sh:416-417` grep-pipefail fix** (1 point) — replace `grep -o ... | wc -l` with `grep -c ... || true`; unblocks Step 15 perf full-run + closes the perf-cycle3 leftover. See `_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md` for the trace. +2. **Migrate `WithOpenApi(...)` callsites to ASP.NET Core 10 minimal-API metadata extensions** (3 points) — 8 callsites in `Program.cs`; covers F1. +3. **Microsoft.OpenApi 2.x nullable cleanup** (1 point) — covers F2 plus any other nullable annotations exposed by the migration (none others detected in this batch, but a project-wide grep is recommended). diff --git a/_docs/_autodev_state.md b/_docs/_autodev_state.md index 26cb726..6f0abdf 100644 --- a/_docs/_autodev_state.md +++ b/_docs/_autodev_state.md @@ -6,8 +6,8 @@ step: 10 name: Implement status: in_progress sub_step: - phase: 1 - name: prerequisite-checks + phase: 15 + name: product-completeness-gate detail: "" retry_count: 0 cycle: 4 diff --git a/_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md b/_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md index eccfcd4..6a148b2 100644 --- a/_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md +++ b/_docs/_process_leftovers/2026-05-12_perf-cycle3-harness-execution.md @@ -1,48 +1,101 @@ # Leftover — Cycle 3 perf harness execution -**Timestamp**: 2026-05-12T01:11:00Z (last replay attempt; original deferral at 2026-05-12T00:00:00Z) +**Timestamp**: 2026-05-12T02:25:00Z (replay #2 — post AZ-500 .NET 10 migration; original deferral 2026-05-12T00:00:00Z) **Reason for deferral**: User skipped the Step 15 (Performance Test) gate of cycle 3. Per `meta-rule.mdc`, performance tests require explicit approval; a skipped question is not approval. Defaulted to skip + record-as-leftover to avoid blocking cycle-3 progress through Steps 16-17. -## Replay attempt — 2026-05-12 (cycle 4 /autodev start) +## Replay attempt #1 — 2026-05-12T01:11:00Z (cycle 4 /autodev start, pre-migration) -User picked A (run perf harness now). Stack came up cleanly via `docker-compose up -d --build`. Perf script `scripts/run-performance-tests.sh` failed at the bootstrap step (`dotnet build SatelliteProvider.IntegrationTests` for the `--mint-only` JWT subcommand) because the host has only .NET 10.0.103 SDK installed and `global.json` pins `sdk.version=8.0.0` with `rollForward=latestMinor` (only rolls within 8.0.x). Exit code 3. +User picked A (run perf harness now). Stack came up cleanly via `docker-compose up -d --build`. Perf script `scripts/run-performance-tests.sh` failed at the bootstrap step (`dotnet build SatelliteProvider.IntegrationTests` for the `--mint-only` JWT subcommand) because the host had only .NET 10.0.103 SDK installed and `global.json` pinned `sdk.version=8.0.0` with `rollForward=latestMinor` (only rolls within 8.0.x). Exit code 3. Sibling script `scripts/run-tests.sh` does NOT have this problem because it shells out to `docker run --rm ... mcr.microsoft.com/dotnet/sdk:8.0` for every dotnet invocation. The perf script was written without that pattern. Per cycle-3 lesson "scenarios accumulate as Unverified across cycles" — this is a real script bug, not just a host quirk. -## Resolution path +## Replay attempt #2 — 2026-05-12T02:21:00Z (cycle 4, AC-5 of AZ-500 short bootstrap-smoke) -Cycle 4 will run a full **".NET 8 → .NET 10 migration"** task (see Step 9 of cycle 4). Once the project targets net10.0 and global.json no longer pins to 8.x, the host SDK becomes the project SDK and the perf script's bootstrap will succeed without modification (it shells out to `dotnet build` against the same project, which will then resolve against `mcr.microsoft.com/dotnet/sdk:10.0` for any Docker-side calls and against the host 10.x SDK for host-side calls). Cycle 4's deploy gate (Step 15) will then re-run this perf harness against the migrated build. +After AZ-500 landed (.NET 10 migration: TFM, global.json `sdk.version=10.0.0`, all Docker images, all `Microsoft.AspNetCore.*` / `Microsoft.Extensions.*` packages, `scripts/run-performance-tests.sh:49` `bin/Release/net8.0/` → `bin/Release/net10.0/`), re-ran the AC-5 short variant: -## Pre-requisites for replay (after .NET 10 migration lands) +``` +PERF_REPEAT_COUNT=2 PERF_UAV_BATCH_SIZE=2 ./scripts/run-performance-tests.sh +``` -Same as before — env vars must be present: +against `docker-compose up -d --build` (api healthy on `:18980`, swagger 200, anonymous request 401). Trace summary: -- `JWT_SECRET` — ≥ 32 bytes; already in `.env` -- `JWT_ISSUER` — already in `.env` as DEV-ONLY (AZ-494) -- `JWT_AUDIENCE` — already in `.env` as DEV-ONLY (AZ-494) -- `GOOGLE_MAPS_API_KEY` — already in `.env` +| Step | Result | +|------|--------| +| Build `SatelliteProvider.IntegrationTests` (Release) | **OK** (build succeeded, 11 NU1902/CA2227 warnings, 0 errors, 41.5s) | +| `--mint-only` JWT subcommand | **OK** (341-byte token, 4h lifetime) | +| PT-01 cold tile download | **PASS** (2538ms / 30000ms threshold) | +| PT-02 cached tile retrieval | **PASS** (195ms / 500ms) | +| PT-03 region 200m / z18 | **PASS** (384ms / 60000ms) | +| PT-04 region 500m / z18 + stitch | **PASS** (2202ms / 120000ms) | +| PT-05 5 concurrent regions | **PASS** (3258ms / 300000ms) | +| PT-06 route creation (2 points) | **PASS** (178ms / 5000ms) | +| PT-07 cold/warm region request | **PASS** (warm p95 2340ms < cold p95 3241ms) | +| PT-08 UAV batch upload | **CRASHED** at first batch summarisation — see below | + +**Bootstrap step DID NOT exit with code 3** — host SDK / global.json mismatch is gone. AC-5 met. + +## Replay attempt #2 — root cause of PT-08 crash (NOT an SDK / .NET 10 issue) + +`bash -x` trace shows the script silently exits right after `rejected=0` and the cleanup trap fires. The script bug is at `scripts/run-performance-tests.sh:417`: + +```bash +rejected=$(grep -o '"status":"rejected"' "$PERF_TMP_DIR/pt08_resp.json" | wc -l | tr -d ' ') +``` + +When the upload response has zero rejected items (the happy-path case), `grep -o` exits 1 (no matches). With `set -o pipefail` (line 16) the pipeline returns 1; with `set -e` the assignment kills the script. The sibling line at 416 for `accepted` only worked in this trace because the response had 2 accepted items so `grep` exited 0. + +This bug pre-existed AZ-500. It was previously masked because the perf script never reached PT-08 — it failed at bootstrap (replay #1) due to the SDK mismatch. The .NET 10 migration unmasked it by clearing the bootstrap blocker. PT-01..PT-07 are unaffected (no `grep -c`/`grep -o` counts on potentially-empty matches). + +The actual perf-relevant data PT-08 captured before crashing (one batch run completed): HTTP 200, batch latency 99ms (well under the AZ-488 2000ms p95 threshold), accepted=2, rejected=0. So the underlying perf is healthy; only the script's failure-counting harness is buggy. + +## Resolution path (forward) + +Two follow-up fixes are needed; **both are out of AZ-500 scope** per `coderule.mdc` "scope discipline": + +1. **`scripts/run-performance-tests.sh:416-417`** — defensive grep-counting. Replace + ```bash + accepted=$(grep -o '"status":"accepted"' "$PERF_TMP_DIR/pt08_resp.json" | wc -l | tr -d ' ') + rejected=$(grep -o '"status":"rejected"' "$PERF_TMP_DIR/pt08_resp.json" | wc -l | tr -d ' ') + ``` + with a pipefail-tolerant variant such as + ```bash + accepted=$(grep -c '"status":"accepted"' "$PERF_TMP_DIR/pt08_resp.json" || true) + rejected=$(grep -c '"status":"rejected"' "$PERF_TMP_DIR/pt08_resp.json" || true) + ``` + (`grep -c` already counts; `|| true` neutralises the exit-1-on-no-match case when summed with `set -o pipefail`/`set -e`). + +2. **Step 15 (Performance Test) of cycle 4** — re-run the *full* harness (default `PERF_REPEAT_COUNT=20 PERF_UAV_BATCH_SIZE=10`) after the script fix lands. Only then can the leftover be deleted (per `Constraints` last bullet of AZ-500: "leftover file is deleted ONLY when the full perf script runs cleanly"). + +## Pre-requisites for full replay + +Same as before — env vars must be present (already in `.env`): + +- `JWT_SECRET` — ≥ 32 bytes +- `JWT_ISSUER` — DEV-ONLY (AZ-494) +- `JWT_AUDIENCE` — DEV-ONLY (AZ-494) +- `GOOGLE_MAPS_API_KEY` Optionally: - `PERF_REPEAT_COUNT` (default 20) - `PERF_UAV_BATCH_SIZE` (default 10) -## How to replay (after .NET 10 migration) +## How to replay (after the script fix lands) ```bash docker-compose up -d --build # bring up API on :18980 -./scripts/run-performance-tests.sh # ~3-5 minutes +./scripts/run-performance-tests.sh # ~3-5 minutes; full PT-01..PT-08 docker-compose down --remove-orphans ``` ## Why this is NOT a hard blocker +- AC-5 of AZ-500 only gates the bootstrap step ("does NOT exit with code 3"). That is met. - The cycle-3 implementation report and code review verdicts already note that the perf harness was statically verified (script grep + integration-test compile + AZ-492 AC-1/AC-4/AC-5/AC-6 covered). -- The AZ-488 batch-p95 threshold was set in cycle 2 and existing integration tests do NOT regress at the cycle-3 build (Step 11 full suite all-green). -- No cycle-3 change altered the production hot paths beyond JWT validation (AZ-494 adds two string comparisons per request — sub-microsecond). -- The cycle-2 deploy also skipped this gate (Option B) without negative consequences. +- The AZ-488 batch-p95 threshold was set in cycle 2; the one PT-08 batch we did capture (99ms) is far below the 2000ms threshold. +- No cycle-3/cycle-4 change altered production hot paths beyond JWT validation (AZ-494 adds two string comparisons per request — sub-microsecond). ## Replay obligation -Defer until cycle 4's .NET 10 migration is deployed (Step 16). At that point, replay this leftover as part of cycle 4's Step 15 (Performance Test) gate. If perf passes — delete this file. If it fails — keep with failure detail. +Open a new follow-up PBI for the `scripts/run-performance-tests.sh:416-417` grep fix. Once that lands and a full perf run is green, delete this file. Until then, this leftover stays. diff --git a/global.json b/global.json index 2ddda36..cb40a97 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "8.0.0", + "version": "10.0.0", "rollForward": "latestMinor", "allowPrerelease": false } -} \ No newline at end of file +} diff --git a/scripts/run-performance-tests.sh b/scripts/run-performance-tests.sh index 7d451fe..4eb04ed 100755 --- a/scripts/run-performance-tests.sh +++ b/scripts/run-performance-tests.sh @@ -46,7 +46,7 @@ if { [[ -z "${JWT_SECRET:-}" ]] || [[ -z "${JWT_ISSUER:-}" ]] || [[ -z "${JWT_AU fi PERF_PROJECT="$PROJECT_ROOT/SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj" -PERF_DLL="$PROJECT_ROOT/SatelliteProvider.IntegrationTests/bin/Release/net8.0/SatelliteProvider.IntegrationTests.dll" +PERF_DLL="$PROJECT_ROOT/SatelliteProvider.IntegrationTests/bin/Release/net10.0/SatelliteProvider.IntegrationTests.dll" # Pre-build IntegrationTests once so the --mint-only / --gen-uav-fixture # subcommands produce clean stdout (no interleaved Restore/Build chatter). diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index bd83bd9..842328e 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -59,7 +59,7 @@ if [[ "$skip_format" == "true" ]]; then echo "Step 0: Skipping dotnet format check (--skip-format)" else echo "Step 0: dotnet format whitespace --verify-no-changes" - if ! docker run --rm -v "$PROJECT_ROOT:/src" -w /src mcr.microsoft.com/dotnet/sdk:8.0 \ + if ! docker run --rm -v "$PROJECT_ROOT:/src" -w /src mcr.microsoft.com/dotnet/sdk:10.0 \ dotnet format whitespace SatelliteProvider.sln --verify-no-changes; then echo "" echo "ERROR: Whitespace violations detected. Run 'dotnet format whitespace SatelliteProvider.sln' to fix." @@ -70,7 +70,7 @@ fi if [[ "$mode" == "unit" ]]; then echo "Running unit tests only..." - docker run --rm -v "$PROJECT_ROOT:/src" -w /src mcr.microsoft.com/dotnet/sdk:8.0 \ + docker run --rm -v "$PROJECT_ROOT:/src" -w /src mcr.microsoft.com/dotnet/sdk:10.0 \ sh -c "dotnet restore SatelliteProvider.sln && dotnet test SatelliteProvider.Tests/SatelliteProvider.Tests.csproj --no-restore --configuration Release --collect:'XPlat Code Coverage' --results-directory /src/TestResults --logger 'console;verbosity=normal'" echo "" echo "=== Unit tests complete (coverage written to ./TestResults/) ===" @@ -113,7 +113,7 @@ export JWT_ISSUER export JWT_AUDIENCE echo "Step 1: Unit tests" -docker run --rm -v "$PROJECT_ROOT:/src" -w /src mcr.microsoft.com/dotnet/sdk:8.0 \ +docker run --rm -v "$PROJECT_ROOT:/src" -w /src mcr.microsoft.com/dotnet/sdk:10.0 \ sh -c "dotnet restore SatelliteProvider.sln && dotnet test SatelliteProvider.Tests/SatelliteProvider.Tests.csproj --no-restore --configuration Release --collect:'XPlat Code Coverage' --results-directory /src/TestResults --logger 'console;verbosity=normal'" echo ""