diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 8dfab7f..5fc7834 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "9.0.12", + "version": "10.0.8", "commands": [ "dotnet-ef" ] diff --git a/.github/workflows/cd-localcluster.yml b/.github/workflows/cd-localcluster.yml index f27cff5..b744d16 100644 --- a/.github/workflows/cd-localcluster.yml +++ b/.github/workflows/cd-localcluster.yml @@ -36,7 +36,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Load deployment settings run: | @@ -67,7 +67,7 @@ jobs: echo "Using successful CI run: ${CI_RUN_ID}" >> "$GITHUB_STEP_SUMMARY" - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e66407..9e39a83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Deployment audit run: bash Deployment/LocalCluster/Scripts/audit-deployment.sh @@ -31,9 +31,9 @@ jobs: yamllint .github Deployment/LocalCluster - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Restore run: dotnet restore @@ -66,7 +66,7 @@ jobs: - name: Upload migration bundle if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ env.APP_NAME }}-migrate-linux-x64 path: artifacts/migrations/${{ env.MIGRATION_BUNDLE_NAME }} @@ -80,7 +80,7 @@ jobs: - name: Login to GHCR if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/BlazorAutoApp.Client/BlazorAutoApp.Client.csproj b/BlazorAutoApp.Client/BlazorAutoApp.Client.csproj index 8689b78..3af648e 100644 --- a/BlazorAutoApp.Client/BlazorAutoApp.Client.csproj +++ b/BlazorAutoApp.Client/BlazorAutoApp.Client.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable true @@ -9,7 +9,7 @@ - + diff --git a/BlazorAutoApp.Core/BlazorAutoApp.Core.csproj b/BlazorAutoApp.Core/BlazorAutoApp.Core.csproj index 17b910f..237d661 100644 --- a/BlazorAutoApp.Core/BlazorAutoApp.Core.csproj +++ b/BlazorAutoApp.Core/BlazorAutoApp.Core.csproj @@ -1,7 +1,7 @@  - net9.0 + net10.0 enable enable diff --git a/BlazorAutoApp.Test/BlazorAutoApp.Test.csproj b/BlazorAutoApp.Test/BlazorAutoApp.Test.csproj index 4219d75..7b7d14a 100644 --- a/BlazorAutoApp.Test/BlazorAutoApp.Test.csproj +++ b/BlazorAutoApp.Test/BlazorAutoApp.Test.csproj @@ -1,26 +1,27 @@ - net9.0 + net10.0 false enable + $(NoWarn);xUnit1051 - - - - - - - - + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + diff --git a/BlazorAutoApp.Test/Features/Inspections/HullImages/CreateHullImageTests.cs b/BlazorAutoApp.Test/Features/Inspections/HullImages/CreateHullImageTests.cs index 51e5a8e..dce8d31 100644 --- a/BlazorAutoApp.Test/Features/Inspections/HullImages/CreateHullImageTests.cs +++ b/BlazorAutoApp.Test/Features/Inspections/HullImages/CreateHullImageTests.cs @@ -10,6 +10,7 @@ using Microsoft.EntityFrameworkCore; using BlazorAutoApp.Test.TestingSetup; using Microsoft.Extensions.DependencyInjection; +using SixLabors.ImageSharp; using Xunit; namespace BlazorAutoApp.Test.Features.Inspections.HullImages; @@ -71,6 +72,19 @@ public async Task Tus_Upload_And_Download_Works() var bytes = await _client.GetByteArrayAsync($"/api/hull-images/{created.Id}/original"); Assert.Equal(original, bytes); + // Generate and verify thumbnail + var thumbRes = await _client.GetAsync($"/api/hull-images/{created.Id}/thumbnail/64"); + Assert.Equal(HttpStatusCode.OK, thumbRes.StatusCode); + Assert.Equal("image/jpeg", thumbRes.Content.Headers.ContentType?.MediaType); + var thumbBytes = await thumbRes.Content.ReadAsByteArrayAsync(); + using (var thumbnail = Image.Load(thumbBytes)) + { + Assert.True(thumbnail.Width > 0); + Assert.True(thumbnail.Height > 0); + Assert.True(thumbnail.Width <= 64); + Assert.True(thumbnail.Height <= 64); + } + // Range request var rangeReq = new HttpRequestMessage(HttpMethod.Get, $"/api/hull-images/{created.Id}/original"); rangeReq.Headers.Range = new RangeHeaderValue(0, 9); @@ -87,8 +101,8 @@ public async Task Tus_Upload_And_Download_Works() Assert.Equal(HttpStatusCode.NotFound, gone.StatusCode); } - public Task InitializeAsync() => Task.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public ValueTask InitializeAsync() => ValueTask.CompletedTask; + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() { // factory is container-owned; no disposal required here diff --git a/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImageTests.cs b/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImageTests.cs index 55852fd..fd9fc4d 100644 --- a/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImageTests.cs +++ b/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImageTests.cs @@ -22,7 +22,7 @@ public async Task GetById_Returns_404_When_NotFound() Assert.Equal(HttpStatusCode.NotFound, res.StatusCode); } - public Task InitializeAsync() => Task.CompletedTask; - public Task DisposeAsync() => _reset(); + public ValueTask InitializeAsync() => ValueTask.CompletedTask; + public async ValueTask DisposeAsync() => await _reset(); } diff --git a/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImagesTests.cs b/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImagesTests.cs index dc94a71..435b9f4 100644 --- a/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImagesTests.cs +++ b/BlazorAutoApp.Test/Features/Inspections/HullImages/GetHullImagesTests.cs @@ -64,8 +64,8 @@ public async Task List_Returns_Items_After_Tus_Upload() Assert.Contains(list.Items, i => i.OriginalFileName == "test-image.PNG"); } - public Task InitializeAsync() => Task.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public ValueTask InitializeAsync() => ValueTask.CompletedTask; + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() => GC.SuppressFinalize(this); } diff --git a/BlazorAutoApp.Test/Features/Inspections/HullImages/TusUploadTests.cs b/BlazorAutoApp.Test/Features/Inspections/HullImages/TusUploadTests.cs index 19c899b..56252a0 100644 --- a/BlazorAutoApp.Test/Features/Inspections/HullImages/TusUploadTests.cs +++ b/BlazorAutoApp.Test/Features/Inspections/HullImages/TusUploadTests.cs @@ -77,7 +77,7 @@ public async Task Tus_Upload_And_Download_Matches() Assert.Equal(all, bytes); } - public Task InitializeAsync() => Task.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public ValueTask InitializeAsync() => ValueTask.CompletedTask; + public async ValueTask DisposeAsync() => await _resetDatabase(); } diff --git a/BlazorAutoApp.Test/Features/Movies/CreateMovieTests.cs b/BlazorAutoApp.Test/Features/Movies/CreateMovieTests.cs index c3363c4..35e3347 100644 --- a/BlazorAutoApp.Test/Features/Movies/CreateMovieTests.cs +++ b/BlazorAutoApp.Test/Features/Movies/CreateMovieTests.cs @@ -73,9 +73,9 @@ public async Task Create_InvalidRating_ReturnsBadRequest() Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } - public Task InitializeAsync() => Task.CompletedTask; + public ValueTask InitializeAsync() => ValueTask.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() => GC.SuppressFinalize(this); } diff --git a/BlazorAutoApp.Test/Features/Movies/DeleteMovieTests.cs b/BlazorAutoApp.Test/Features/Movies/DeleteMovieTests.cs index 22b8769..ada6728 100644 --- a/BlazorAutoApp.Test/Features/Movies/DeleteMovieTests.cs +++ b/BlazorAutoApp.Test/Features/Movies/DeleteMovieTests.cs @@ -52,9 +52,9 @@ public async Task Delete_NotFound_Returns404() Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - public Task InitializeAsync() => Task.CompletedTask; + public ValueTask InitializeAsync() => ValueTask.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() => GC.SuppressFinalize(this); } diff --git a/BlazorAutoApp.Test/Features/Movies/GetMovieTests.cs b/BlazorAutoApp.Test/Features/Movies/GetMovieTests.cs index 420474e..e62374a 100644 --- a/BlazorAutoApp.Test/Features/Movies/GetMovieTests.cs +++ b/BlazorAutoApp.Test/Features/Movies/GetMovieTests.cs @@ -53,9 +53,9 @@ public async Task GetById_NotFound_Returns404() Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - public Task InitializeAsync() => Task.CompletedTask; + public ValueTask InitializeAsync() => ValueTask.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() => GC.SuppressFinalize(this); } diff --git a/BlazorAutoApp.Test/Features/Movies/GetMoviesTests.cs b/BlazorAutoApp.Test/Features/Movies/GetMoviesTests.cs index e2b5cff..13f551d 100644 --- a/BlazorAutoApp.Test/Features/Movies/GetMoviesTests.cs +++ b/BlazorAutoApp.Test/Features/Movies/GetMoviesTests.cs @@ -54,9 +54,9 @@ public async Task GetAll_WithData_ReturnsOkWithAllItems() Assert.Equal(10, payload!.Movies.Count); } - public Task InitializeAsync() => Task.CompletedTask; + public ValueTask InitializeAsync() => ValueTask.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() => GC.SuppressFinalize(this); } diff --git a/BlazorAutoApp.Test/Features/Movies/MoviesCachingTests.cs b/BlazorAutoApp.Test/Features/Movies/MoviesCachingTests.cs index 912c2fe..c8779a8 100644 --- a/BlazorAutoApp.Test/Features/Movies/MoviesCachingTests.cs +++ b/BlazorAutoApp.Test/Features/Movies/MoviesCachingTests.cs @@ -29,9 +29,9 @@ public MoviesCachingTests(WebAppFactory factory) _dbFactory = scope.ServiceProvider.GetRequiredService>(); } - public Task InitializeAsync() => Task.CompletedTask; + public ValueTask InitializeAsync() => ValueTask.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() => GC.SuppressFinalize(this); diff --git a/BlazorAutoApp.Test/Features/Movies/UpdateMovieTests.cs b/BlazorAutoApp.Test/Features/Movies/UpdateMovieTests.cs index 69b6087..b708e16 100644 --- a/BlazorAutoApp.Test/Features/Movies/UpdateMovieTests.cs +++ b/BlazorAutoApp.Test/Features/Movies/UpdateMovieTests.cs @@ -117,9 +117,9 @@ public async Task Update_InvalidBody_ReturnsBadRequest() Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } - public Task InitializeAsync() => Task.CompletedTask; + public ValueTask InitializeAsync() => ValueTask.CompletedTask; - public Task DisposeAsync() => _resetDatabase(); + public async ValueTask DisposeAsync() => await _resetDatabase(); public void Dispose() => GC.SuppressFinalize(this); } diff --git a/BlazorAutoApp.Test/TestingSetup/WebAppFactory.cs b/BlazorAutoApp.Test/TestingSetup/WebAppFactory.cs index b3a5a5d..465f434 100644 --- a/BlazorAutoApp.Test/TestingSetup/WebAppFactory.cs +++ b/BlazorAutoApp.Test/TestingSetup/WebAppFactory.cs @@ -19,9 +19,18 @@ namespace BlazorAutoApp.Test.TestingSetup; public class WebAppFactory : WebApplicationFactory, IAsyncLifetime { private const int MaxWaitTimeMinutes = 5; + private const string RyukImageEnvironmentVariable = "TESTCONTAINERS_RYUK_CONTAINER_IMAGE"; + private const string RyukImage = "testcontainers/ryuk:0.12.0"; - private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder() - .WithImage("postgres:16-alpine") + static WebAppFactory() + { + if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(RyukImageEnvironmentVariable))) + { + Environment.SetEnvironmentVariable(RyukImageEnvironmentVariable, RyukImage); + } + } + + private readonly PostgreSqlContainer _dbContainer = new PostgreSqlBuilder("postgres:16-alpine") .Build(); //Default! cause we are not initializing it here, but in the InitializeAsync method @@ -92,7 +101,7 @@ public async Task ResetDatabaseAsync() } } - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { await _dbContainer.StartAsync(); @@ -128,8 +137,8 @@ private async Task InitializeRespawner() } //"New": to tell compiler that this is a new DisposeAsync method - public new Task DisposeAsync() + public new async ValueTask DisposeAsync() { - return _dbContainer.StopAsync(); + await _dbContainer.StopAsync(); } } diff --git a/BlazorAutoApp/BlazorAutoApp.csproj b/BlazorAutoApp/BlazorAutoApp.csproj index 89e612b..83f7e57 100644 --- a/BlazorAutoApp/BlazorAutoApp.csproj +++ b/BlazorAutoApp/BlazorAutoApp.csproj @@ -1,48 +1,55 @@ - net9.0 + net10.0 + linux-x64 enable enable - - - - - - - - - + + + + + all + + + all + + + all + + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + PreserveNewest PreserveNewest - - + + - - + + - - + + diff --git a/BlazorAutoApp/Data/DesignTimeDbContextFactory.cs b/BlazorAutoApp/Data/DesignTimeDbContextFactory.cs index cf9e525..2f6c574 100644 --- a/BlazorAutoApp/Data/DesignTimeDbContextFactory.cs +++ b/BlazorAutoApp/Data/DesignTimeDbContextFactory.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using Npgsql; namespace BlazorAutoApp.Data; @@ -10,11 +11,22 @@ public AppDbContext CreateDbContext(string[] args) var optionsBuilder = new DbContextOptionsBuilder(); // Prefer environment variable first, then default. - var conn = Environment.GetEnvironmentVariable("DefaultConnection") - ?? "Host=localhost;Port=5432;Database=app;Username=postgres;Password=postgres"; + var conn = ConfigurePostgresConnectionString( + Environment.GetEnvironmentVariable("DefaultConnection") + ?? "Host=localhost;Port=5432;Database=app;Username=postgres;Password=postgres"); optionsBuilder.UseNpgsql(conn); return new AppDbContext(optionsBuilder.Options); } -} + private static string ConfigurePostgresConnectionString(string connectionString) + { + var connectionBuilder = new NpgsqlConnectionStringBuilder(connectionString); + if (!connectionBuilder.ContainsKey("GSS Encryption Mode")) + { + connectionBuilder.GssEncryptionMode = GssEncryptionMode.Disable; + } + + return connectionBuilder.ConnectionString; + } +} diff --git a/BlazorAutoApp/Dockerfile b/BlazorAutoApp/Dockerfile index 4c3d503..47f4bae 100644 --- a/BlazorAutoApp/Dockerfile +++ b/BlazorAutoApp/Dockerfile @@ -1,11 +1,11 @@ # syntax=docker/dockerfile:1.7-labs -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base WORKDIR /app EXPOSE 8080 EXPOSE 7186 -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src # Copy project files first for better layer caching diff --git a/BlazorAutoApp/Program.cs b/BlazorAutoApp/Program.cs index b92e997..f1d91e9 100644 --- a/BlazorAutoApp/Program.cs +++ b/BlazorAutoApp/Program.cs @@ -13,6 +13,7 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Npgsql; using StackExchange.Redis; using System.Security.Cryptography.X509Certificates; @@ -133,15 +134,26 @@ string GetEnvVar(string key) ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; - options.KnownNetworks.Clear(); + options.KnownIPNetworks.Clear(); options.KnownProxies.Clear(); }); // EF Core with PostgreSQL: prefer ConnectionStrings:DefaultConnection; fallback to Database__* env vars. var explicitConn = builder.Configuration.GetConnectionString("DefaultConnection"); -var connString = !string.IsNullOrWhiteSpace(explicitConn) +var connString = ConfigurePostgresConnectionString(!string.IsNullOrWhiteSpace(explicitConn) ? explicitConn - : $"Host={GetEnvVar("Database__Host")};Port={GetEnvVar("Database__Port")};Database={GetEnvVar("Database__Name")};Username={GetEnvVar("Database__Username")};Password={GetEnvVar("Database__Password")}"; + : $"Host={GetEnvVar("Database__Host")};Port={GetEnvVar("Database__Port")};Database={GetEnvVar("Database__Name")};Username={GetEnvVar("Database__Username")};Password={GetEnvVar("Database__Password")}"); + +string ConfigurePostgresConnectionString(string connectionString) +{ + var connectionBuilder = new NpgsqlConnectionStringBuilder(connectionString); + if (!connectionBuilder.ContainsKey("GSS Encryption Mode")) + { + connectionBuilder.GssEncryptionMode = GssEncryptionMode.Disable; + } + + return connectionBuilder.ConnectionString; +} void ConfigureDbContext(DbContextOptionsBuilder options) { diff --git a/BlazorAutoApp/appsettings.Docker.json b/BlazorAutoApp/appsettings.Docker.json index 75cfb52..4749d30 100644 --- a/BlazorAutoApp/appsettings.Docker.json +++ b/BlazorAutoApp/appsettings.Docker.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Host=postgres;Port=5432;Database=app;Username=postgres;Password=postgres" + "DefaultConnection": "Host=postgres;Port=5432;Database=app;Username=postgres;Password=postgres;GSS Encryption Mode=Disable" }, "Serilog": { "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Seq" ], diff --git a/BlazorAutoApp/appsettings.json b/BlazorAutoApp/appsettings.json index fa922a7..99281f0 100644 --- a/BlazorAutoApp/appsettings.json +++ b/BlazorAutoApp/appsettings.json @@ -7,7 +7,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Host=localhost;Port=5432;Database=app;Username=postgres;Password=postgres" + "DefaultConnection": "Host=localhost;Port=5432;Database=app;Username=postgres;Password=postgres;GSS Encryption Mode=Disable" }, "Serilog": { "Using": [ diff --git a/UpdatePlan.md b/UpdatePlan.md index 296d90f..8817d45 100644 --- a/UpdatePlan.md +++ b/UpdatePlan.md @@ -4,13 +4,37 @@ Research snapshot: 2026-05-24. Goal: move this repo from the current .NET 9 stack to the newest production-stable .NET stack, update direct NuGet packages and deployment/runtime dependencies, and prove the result through local tests, container build, CI, and LocalCluster CD acceptance. +## Execution Notes - 2026-05-24 + +Executed on branch `update-dotnet-10`. + +Completed locally: + +- Confirmed the official .NET release metadata target for this pass remains .NET 10 LTS with SDK `10.0.300` and runtime `10.0.8`. +- Retargeted the app, client, core, and test projects to `net10.0`. +- Fixed `global.json` to pin SDK `10.0.300`, disable prerelease SDK selection, and prevent latest-major roll-forward. +- Updated Microsoft, ASP.NET Core, EF Core, Npgsql, Serilog, tusdotnet, test infrastructure, CI setup-dotnet, `dotnet-ef`, and Docker app images to the .NET 10 package/tooling band. +- Removed unused direct EF SQL Server and SQLite provider references after code search found no `UseSqlServer` or `UseSqlite` usage. +- Added direct private NuGet package overrides for `NuGet.Packaging` and `NuGet.Protocol` to eliminate the transitive low-severity advisory introduced by code generation tooling. +- Migrated tests from legacy `xunit` 2.x to `xunit.v3` `3.2.2`; kept `xunit.runner.visualstudio` `3.1.5`. +- Built the EF migration bundle for `linux-x64`. +- Rebuilt and started the local Docker Compose stack, verified `/health/ready`, verified the login page returns `200 OK`, and confirmed clean web logs. +- Ran deployment audit, rendered template validation, deployment summary, local status, package vulnerability scan, generated artifact tracking check, and secret/audit scan. + +Deferred or externally gated: + +- `SixLabors.ImageSharp` `4.0.0` was tested and deferred because the app build fails without the new Six Labors license configuration required by ImageSharp 4. The app and tests are pinned to current 3.x `3.1.12`. +- PostgreSQL and Redis major image upgrades remain deferred as planned; production-like data containers were not major-upgraded as part of the .NET migration. +- Ansible was later installed in a side-by-side Ubuntu 24.04 WSL 2 distro on this Windows 10 developer machine, and local `ansible-inventory` parsing passed. This machine is not the LocalCluster control PC from `HowToDeployLocalCluster.md`, so production SSH/vault/node checks were intentionally not treated as required local upgrade gates. +- CI, CD, `preflight.sh deploy`, and `acceptance-check.sh` still need to run in their intended environments. `preflight.sh deploy` and `acceptance-check.sh` are control-machine/CD-runner checks because they decrypt the vault, use the deploy key, SSH to the nodes, inspect ports, and verify live services. + ## Recommendation Use .NET 10 LTS as the upgrade target. Do not target .NET 11 preview for this app unless the explicit goal is preview testing. Official .NET metadata currently lists .NET 11 as preview, while .NET 10 is active LTS with latest runtime `10.0.8`, latest SDK `10.0.300`, and end of support `2028-11-14`. -Current repo facts: +Baseline repo facts before execution: | Area | Current state | Target / action | | --- | --- | --- | @@ -29,67 +53,67 @@ Current repo facts: ## Source Of Truth Checks -- [ ] Confirm official .NET release metadata still says .NET 10 is the latest stable/LTS channel before executing. -- [ ] Confirm `https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json` still lists latest .NET 10 SDK/runtime targets. -- [ ] Run `dotnet list BlazorAutoApp.sln package --outdated` again immediately before edits. -- [ ] Run `dotnet --info` and confirm the active SDK is valid and compatible. -- [ ] Decide whether this pass includes only app/.NET dependencies or also database/Redis major upgrades. +- [x] Confirm official .NET release metadata still says .NET 10 is the latest stable/LTS channel before executing. +- [x] Confirm `https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json` still lists latest .NET 10 SDK/runtime targets. +- [x] Run `dotnet list BlazorAutoApp.sln package --outdated` again immediately before edits. +- [x] Run `dotnet --info` and confirm the active SDK is valid and compatible. +- [x] Decide whether this pass includes only app/.NET dependencies or also database/Redis major upgrades. Recommended scope for first execution: -- [ ] Update .NET SDK, target frameworks, Microsoft packages, EF provider/tool, test packages, Docker app images, and CI. -- [ ] Do not upgrade PostgreSQL major, Redis major, or local-only observability images in the same pass unless the .NET upgrade is already green. +- [x] Update .NET SDK, target frameworks, Microsoft packages, EF provider/tool, test packages, Docker app images, and CI. +- [x] Do not upgrade PostgreSQL major, Redis major, or local-only observability images in the same pass unless the .NET upgrade is already green. ## Phase 1 - Baseline Before Changing Anything -- [ ] Create a branch named `update-dotnet-10`. -- [ ] Record `git status --short --untracked-files=all`. -- [ ] Run `dotnet --info`. -- [ ] Run `dotnet restore`. -- [ ] Run `dotnet build --configuration Release --no-restore`. -- [ ] Run `dotnet test --configuration Release --no-build`. -- [ ] Run `dotnet tool restore`. -- [ ] Run `dotnet ef migrations list --project BlazorAutoApp --startup-project BlazorAutoApp`. -- [ ] Run `docker build -f BlazorAutoApp/Dockerfile -t blazorautoapp-update-baseline .`. -- [ ] Run `bash Deployment/LocalCluster/Scripts/audit-deployment.sh`. -- [ ] Run `bash Deployment/LocalCluster/Scripts/validate-rendered-templates.sh`. -- [ ] Save any current failures separately; do not mix existing failures with upgrade regressions. +- [x] Create a branch named `update-dotnet-10`. +- [x] Record `git status --short --untracked-files=all`. +- [x] Run `dotnet --info`. +- [x] Run `dotnet restore`. +- [x] Run `dotnet build --configuration Release --no-restore`. +- [x] Run `dotnet test --configuration Release --no-build`. +- [x] Run `dotnet tool restore`. +- [x] Run `dotnet ef migrations list --project BlazorAutoApp --startup-project BlazorAutoApp`. +- [x] Run `docker build -f BlazorAutoApp/Dockerfile -t blazorautoapp-update-baseline .`. +- [x] Run `bash Deployment/LocalCluster/Scripts/audit-deployment.sh`. +- [x] Run `bash Deployment/LocalCluster/Scripts/validate-rendered-templates.sh`. +- [x] Save any current failures separately; do not mix existing failures with upgrade regressions. ## Phase 2 - Fix SDK Pinning First Reason: `global.json` currently has an invalid SDK version and allows prerelease/latest-major roll-forward. That is risky because a machine with .NET 11 preview installed could silently build the app with preview tooling. -- [ ] Change `global.json` SDK version from `10.0.0` to `10.0.300`. -- [ ] Set `allowPrerelease` to `false`. -- [ ] Change `rollForward` from `latestMajor` to either `latestFeature` or `latestPatch`. -- [ ] Preferred default: `latestFeature`, because it permits future .NET 10 feature-band SDKs while blocking .NET 11. -- [ ] Re-run `dotnet --info` and confirm `global.json` is valid. -- [ ] If local SDK `10.0.300` is missing, install it or temporarily use CI for final proof. +- [x] Change `global.json` SDK version from `10.0.0` to `10.0.300`. +- [x] Set `allowPrerelease` to `false`. +- [x] Change `rollForward` from `latestMajor` to either `latestFeature` or `latestPatch`. +- [x] Preferred default: `latestFeature`, because it permits future .NET 10 feature-band SDKs while blocking .NET 11. +- [x] Re-run `dotnet --info` and confirm `global.json` is valid. +- [x] If local SDK `10.0.300` is missing, install it or temporarily use CI for final proof. Acceptance gate: -- [ ] `dotnet --info` no longer reports invalid `global.json`. -- [ ] `dotnet restore` succeeds. +- [x] `dotnet --info` no longer reports invalid `global.json`. +- [x] `dotnet restore` succeeds. ## Phase 3 - Retarget Projects To .NET 10 Files: -- [ ] `BlazorAutoApp/BlazorAutoApp.csproj` -- [ ] `BlazorAutoApp.Client/BlazorAutoApp.Client.csproj` -- [ ] `BlazorAutoApp.Core/BlazorAutoApp.Core.csproj` -- [ ] `BlazorAutoApp.Test/BlazorAutoApp.Test.csproj` +- [x] `BlazorAutoApp/BlazorAutoApp.csproj` +- [x] `BlazorAutoApp.Client/BlazorAutoApp.Client.csproj` +- [x] `BlazorAutoApp.Core/BlazorAutoApp.Core.csproj` +- [x] `BlazorAutoApp.Test/BlazorAutoApp.Test.csproj` Changes: -- [ ] Replace every `net9.0` with `net10.0`. -- [ ] Check for implicit language-version changes and warnings. -- [ ] Keep nullable and implicit usings as-is. +- [x] Replace every `net9.0` with `net10.0`. +- [x] Check for implicit language-version changes and warnings. +- [x] Keep nullable and implicit usings as-is. Acceptance gate: -- [ ] `rg -n "net9.0|dotnet-version: 9.0|aspnet:9.0|sdk:9.0" .` only finds intentional historical notes, or finds nothing. -- [ ] `dotnet restore` succeeds. +- [x] `rg -n "net9.0|dotnet-version: 9.0|aspnet:9.0|sdk:9.0" .` only finds intentional historical notes, or finds nothing. +- [x] `dotnet restore` succeeds. ## Phase 4 - Update Microsoft, ASP.NET, EF, And Tooling Packages @@ -120,27 +144,27 @@ Direct package targets from `dotnet list package --outdated`: Steps: -- [ ] Update package references in the app, client, and test projects. -- [ ] Update `.config/dotnet-tools.json` to `dotnet-ef` `10.0.8`. -- [ ] Run `dotnet tool restore`. -- [ ] Run `dotnet restore`. -- [ ] Run `dotnet ef migrations list --project BlazorAutoApp --startup-project BlazorAutoApp`. -- [ ] Build the EF migration bundle locally: +- [x] Update package references in the app, client, and test projects. +- [x] Update `.config/dotnet-tools.json` to `dotnet-ef` `10.0.8`. +- [x] Run `dotnet tool restore`. +- [x] Run `dotnet restore`. +- [x] Run `dotnet ef migrations list --project BlazorAutoApp --startup-project BlazorAutoApp`. +- [x] Build the EF migration bundle locally: `dotnet ef migrations bundle --project BlazorAutoApp/BlazorAutoApp.csproj --startup-project BlazorAutoApp/BlazorAutoApp.csproj --configuration Release --self-contained --runtime linux-x64 --output artifacts/migrations/test-migrate` Risks to inspect: -- [ ] EF Core 10 model snapshot and SQL generation changes. -- [ ] Npgsql provider behavior changes for PostgreSQL. -- [ ] Identity UI and authentication package behavior changes. -- [ ] Hybrid cache API changes. -- [ ] WebAssembly static asset behavior changes. +- [x] EF Core 10 model snapshot and SQL generation changes. +- [x] Npgsql provider behavior changes for PostgreSQL. +- [x] Identity UI and authentication package behavior changes. +- [x] Hybrid cache API changes. +- [x] WebAssembly static asset behavior changes. Acceptance gate: -- [ ] No mixed Microsoft `9.x` packages remain in direct references. -- [ ] EF migrations list succeeds. -- [ ] Migration bundle builds. +- [x] No mixed Microsoft `9.x` packages remain in direct references. +- [x] EF migrations list succeeds. +- [x] Migration bundle builds. ## Phase 5 - Update Third-Party Packages Deliberately @@ -159,148 +183,148 @@ Direct package targets from current NuGet data: | `Respawn` | `6.2.1` | `7.0.0` | Medium/high, major test reset upgrade | | `Testcontainers` | `4.7.0` | `4.12.0` | Medium | | `Testcontainers.PostgreSql` | `4.7.0` | `4.12.0` | Medium | -| `xunit` | `2.9.2` | `2.9.3` | Low | +| `xunit` | `2.9.2` | `xunit.v3` `3.2.2` | Medium, package migration | | `xunit.runner.visualstudio` | `2.8.2` | `3.1.5` | Medium/high, runner major upgrade | Recommended order: -- [ ] First update low-risk patch/minor packages. -- [ ] Then update test infrastructure packages and run all tests. -- [ ] Then update ImageSharp `4.0.0` and run image upload/processing tests carefully. -- [ ] Keep any package that breaks tests temporarily pinned and document why. +- [x] First update low-risk patch/minor packages. +- [x] Then update test infrastructure packages and run all tests. +- [x] Attempt ImageSharp `4.0.0` and run image upload/processing tests carefully; deferred 4.0.0 because it requires Six Labors license configuration, kept `3.1.12`, and added thumbnail endpoint coverage. +- [x] Keep any package that breaks tests temporarily pinned and document why. Acceptance gate: -- [ ] `dotnet list BlazorAutoApp.sln package --outdated` has no direct updates left, except intentionally deferred packages with notes. -- [ ] No vulnerable package warnings appear during restore/build. +- [x] `dotnet list BlazorAutoApp.sln package --outdated` has no direct updates left, except intentionally deferred packages with notes. +- [x] No vulnerable package warnings appear during restore/build. ## Phase 6 - Consider Package Management Cleanup The repo currently repeats package versions across project files. This can drift during a large upgrade. -- [ ] Decide whether to introduce `Directory.Packages.props`. -- [ ] If introduced, move direct package versions into one central file. -- [ ] Keep `` and `` metadata in project files where needed. -- [ ] Do this only after the .NET 10 upgrade is green, or in a separate commit, to keep regressions easy to isolate. +- [x] Decide whether to introduce `Directory.Packages.props`. +- [x] If introduced, move direct package versions into one central file. Not introduced in this pass. +- [x] Keep `` and `` metadata in project files where needed. +- [x] Do this only after the .NET 10 upgrade is green, or in a separate commit, to keep regressions easy to isolate. Recommended default: -- [ ] Do not introduce central package management in the same commit as the .NET 10 migration unless package drift becomes confusing during implementation. +- [x] Do not introduce central package management in the same commit as the .NET 10 migration unless package drift becomes confusing during implementation. ## Phase 7 - Update Docker App Build And Runtime Files: -- [ ] `BlazorAutoApp/Dockerfile` -- [ ] `docker-compose.yml` -- [ ] `.dockerignore` if build context warnings appear +- [x] `BlazorAutoApp/Dockerfile` +- [x] `docker-compose.yml` +- [x] `.dockerignore` if build context warnings appear Changes: -- [ ] Change `FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base` to `aspnet:10.0`. -- [ ] Change `FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build` to `sdk:10.0`. -- [ ] Verify Docker build still restores the web app graph only. -- [ ] Confirm `ASPNETCORE_URLS` and exposed ports remain unchanged. +- [x] Change `FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base` to `aspnet:10.0`. +- [x] Change `FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build` to `sdk:10.0`. +- [x] Verify Docker build still restores the web app graph only. +- [x] Confirm `ASPNETCORE_URLS` and exposed ports remain unchanged. Acceptance gate: -- [ ] `docker build -f BlazorAutoApp/Dockerfile -t blazorautoapp-dotnet10 .` -- [ ] Local compose starts with `docker compose up -d --build`. -- [ ] `curl -k https://localhost:7186/health/ready` succeeds for local Docker, if local cert setup is present. -- [ ] App storage volume behavior still works. +- [x] `docker build -f BlazorAutoApp/Dockerfile -t blazorautoapp-dotnet10 .` +- [x] Local compose starts with `docker compose up -d --build`. +- [x] `curl -k https://localhost:7186/health/ready` succeeds for local Docker, if local cert setup is present. +- [x] App storage volume behavior still works. ## Phase 8 - Update CI/CD Workflows Files: -- [ ] `.github/workflows/ci.yml` -- [ ] `.github/workflows/cd-localcluster.yml` +- [x] `.github/workflows/ci.yml` +- [x] `.github/workflows/cd-localcluster.yml` CI changes: -- [ ] Update `actions/setup-dotnet@v4` to `actions/setup-dotnet@v5`. -- [ ] Update `dotnet-version: 9.0.x` to `10.0.x`. -- [ ] Keep restore/build/test order unchanged at first. -- [ ] Keep migration bundle build in CI and verify it uses .NET 10. -- [ ] Keep Docker image build and GHCR push unchanged except for the Dockerfile base image change. +- [x] Update `actions/setup-dotnet@v4` to `actions/setup-dotnet@v5`. +- [x] Update `dotnet-version: 9.0.x` to `10.0.x`. +- [x] Keep restore/build/test order unchanged at first. +- [x] Keep migration bundle build in CI and verify it uses .NET 10. +- [x] Keep Docker image build and GHCR push unchanged except for the Dockerfile base image change. CD changes: -- [ ] Usually no CD workflow change is needed beyond artifact compatibility. -- [ ] Verify the self-hosted runner can run the updated repo and Docker image. -- [ ] Ensure CD downloads the migration bundle created by the .NET 10 CI run. +- [x] Usually no CD workflow change is needed beyond artifact compatibility. +- [ ] Verify the self-hosted runner can run the updated repo and Docker image. External: real self-hosted runner/control environment. +- [x] Ensure CD downloads the migration bundle created by the .NET 10 CI run. Workflow contract reviewed; runtime proof remains in CD. Acceptance gate: -- [ ] CI passes on pull request. -- [ ] CI passes on main. -- [ ] Docker image is pushed to GHCR with the commit SHA. -- [ ] Migration bundle artifact exists and is executable after download. +- [ ] CI passes on pull request. External: requires GitHub Actions run. +- [ ] CI passes on main. External: requires GitHub Actions run after merge. +- [ ] Docker image is pushed to GHCR with the commit SHA. External: requires CI on `main`. +- [ ] Migration bundle artifact exists and is executable after download. External: requires CI artifact and CD download. ## Phase 9 - Audit App Code For .NET 10 Breaking Changes Search and inspect: -- [ ] `Program.cs` startup, auth, Identity, cookies, antiforgery, health checks. -- [ ] EF Core `DbContext`, migrations, model snapshot, query warnings. -- [ ] Redis cache and Data Protection key persistence. -- [ ] Blazor WebAssembly client package/static asset behavior. -- [ ] File upload and TUS endpoints. -- [ ] Image processing paths using ImageSharp. -- [ ] Integration test host setup in `BlazorAutoApp.Test/TestingSetup/WebAppFactory.cs`. +- [x] `Program.cs` startup, auth, Identity, cookies, antiforgery, health checks. +- [x] EF Core `DbContext`, migrations, model snapshot, query warnings. +- [x] Redis cache and Data Protection key persistence. +- [x] Blazor WebAssembly client package/static asset behavior. +- [x] File upload and TUS endpoints. +- [x] Image processing paths using ImageSharp. +- [x] Integration test host setup in `BlazorAutoApp.Test/TestingSetup/WebAppFactory.cs`. Commands: -- [ ] `dotnet build --configuration Release` -- [ ] Treat new compiler/analyzer warnings as upgrade findings, not noise. -- [ ] Fix real warnings before moving to deployment. +- [x] `dotnet build --configuration Release` +- [x] Treat new compiler/analyzer warnings as upgrade findings, not noise. +- [x] Fix real warnings before moving to deployment. ## Phase 10 - Test Matrix Local fast checks: -- [ ] `dotnet restore` -- [ ] `dotnet build --configuration Release --no-restore` -- [ ] `dotnet test --configuration Release --no-build` -- [ ] `dotnet tool restore` -- [ ] `dotnet ef migrations list --project BlazorAutoApp --startup-project BlazorAutoApp` +- [x] `dotnet restore` +- [x] `dotnet build --configuration Release --no-restore` +- [x] `dotnet test --configuration Release --no-build` +- [x] `dotnet tool restore` +- [x] `dotnet ef migrations list --project BlazorAutoApp --startup-project BlazorAutoApp` Local integration checks: -- [ ] Start Docker Desktop. -- [ ] Run `docker compose up -d --build`. -- [ ] Run `python ./docker/local-status.py`. -- [ ] Run app health endpoint checks. -- [ ] Exercise login page and Google auth configuration does not crash startup. -- [ ] Exercise image upload/thumbnail flow. -- [ ] Exercise TUS upload flow. -- [ ] Confirm Redis cache and Data Protection do not throw at startup. +- [x] Start Docker Desktop. +- [x] Run `docker compose up -d --build`. +- [x] Run `python ./docker/local-status.py`. +- [x] Run app health endpoint checks. +- [x] Exercise login page and Google auth configuration does not crash startup. +- [x] Exercise image upload/thumbnail flow. +- [x] Exercise TUS upload flow. +- [x] Confirm Redis cache and Data Protection do not throw at startup. Deployment checks: -- [ ] `bash Deployment/LocalCluster/Scripts/audit-deployment.sh` -- [ ] `bash Deployment/LocalCluster/Scripts/validate-rendered-templates.sh` -- [ ] `bash Deployment/LocalCluster/Scripts/summary.sh` -- [ ] `bash Deployment/LocalCluster/Scripts/preflight.sh deploy` on control machine. -- [ ] CI workflow green. -- [ ] CD workflow with migrations enabled. -- [ ] `bash Deployment/LocalCluster/Scripts/acceptance-check.sh` +- [x] `bash Deployment/LocalCluster/Scripts/audit-deployment.sh` +- [x] `bash Deployment/LocalCluster/Scripts/validate-rendered-templates.sh` +- [x] `bash Deployment/LocalCluster/Scripts/summary.sh` +- [ ] `bash Deployment/LocalCluster/Scripts/preflight.sh deploy` on control machine. External: do not run target-node checks from this Windows developer PC. +- [ ] CI workflow green. External: requires GitHub Actions. +- [ ] CD workflow with migrations enabled. External: requires real self-hosted/control runner. +- [ ] `bash Deployment/LocalCluster/Scripts/acceptance-check.sh`. External: live-node/public-host acceptance check. ## Phase 11 - Database And Migration Safety Before deploying: -- [ ] Confirm CI produces the .NET 10 migration bundle. -- [ ] Confirm CD runs pre-migration backup before the bundle. -- [ ] Confirm no new EF migration is generated unless model changes are intentional. -- [ ] If EF reports model changes, inspect them before deployment. +- [ ] Confirm CI produces the .NET 10 migration bundle. External: local bundle and workflow command verified; actual CI artifact requires CI. +- [x] Confirm CD runs pre-migration backup before the bundle. Playbook inspection confirms backup precedes migration bundle execution. +- [x] Confirm no new EF migration is generated unless model changes are intentional. +- [x] If EF reports model changes, inspect them before deployment. Deployment: -- [ ] Deploy with migrations enabled for the first .NET 10 deployment. -- [ ] Verify `/health/ready` after app start. -- [ ] Verify backup file exists on `node-db`. -- [ ] If migration fails, stop and restore from backup rather than retrying blindly. +- [ ] Deploy with migrations enabled for the first .NET 10 deployment. External: run from CD/control environment after merge. +- [ ] Verify `/health/ready` after app start. External for production deployment; local Docker health passed. +- [ ] Verify backup file exists on `node-db`. External: requires deployed migration run on node-db. +- [x] If migration fails, stop and restore from backup rather than retrying blindly. Rollback rule remains documented; no migration failure occurred locally. ## Phase 12 - Local Runtime Images After .NET Upgrade @@ -308,88 +332,88 @@ Handle these after the app is green on .NET 10. Local-only Docker Compose: -- [ ] Consider `postgres:17-alpine` for local dev. -- [ ] Consider `redis:8-alpine` for local dev. -- [ ] Replace `datalust/seq:latest` with a pinned Seq tag. -- [ ] Replace `redis/redisinsight:latest` with a pinned RedisInsight tag. +- [x] Consider `postgres:17-alpine` for local dev. Considered and deferred; database major upgrades stay separate. +- [x] Consider `redis:8-alpine` for local dev. Considered and deferred; Redis major upgrades stay separate. +- [x] Replace `datalust/seq:latest` with a pinned Seq tag. Considered and deferred as local-only observability image cleanup. +- [x] Replace `redis/redisinsight:latest` with a pinned RedisInsight tag. Considered and deferred as local-only tooling cleanup. LocalCluster production-like compose: -- [ ] Keep `postgres:16.14-alpine3.23` during the .NET upgrade. -- [ ] Keep `redis:7.4.9-alpine3.21` during the .NET upgrade. -- [ ] Plan PostgreSQL major upgrade as a separate maintenance task with dump/restore or `pg_upgrade` testing. -- [ ] Plan Redis major upgrade separately with persistence compatibility checks. +- [x] Keep `postgres:16.14-alpine3.23` during the .NET upgrade. +- [x] Keep `redis:7.4.9-alpine3.21` during the .NET upgrade. +- [x] Plan PostgreSQL major upgrade as a separate maintenance task with dump/restore or `pg_upgrade` testing. +- [x] Plan Redis major upgrade separately with persistence compatibility checks. Acceptance gate: -- [ ] Local dev compose works after optional local image changes. -- [ ] Production LocalCluster data containers are not major-upgraded as a side effect of app dependency updates. +- [x] Local dev compose works after optional local image changes. +- [x] Production LocalCluster data containers are not major-upgraded as a side effect of app dependency updates. ## Phase 13 - Deployment Dependency Review -- [ ] Confirm `cloudflared_version` remains current. Latest observed GitHub release was `2026.5.0`, matching `all.yml`. -- [ ] Confirm GitHub runner installer still resolves latest `actions/runner`. -- [ ] Do not pin GitHub runner in this pass unless reproducibility becomes more important than auto-updating. -- [ ] Confirm Caddy install still uses the stable apt repository. -- [ ] Confirm Docker apt repository role still uses supported Linux Mint/Ubuntu codename handling. -- [ ] Run LocalCluster audit after any deployment script change. +- [x] Confirm `cloudflared_version` remains current. Latest observed GitHub release was `2026.5.0`, matching `all.yml`. +- [x] Confirm GitHub runner installer still resolves latest `actions/runner`. +- [x] Do not pin GitHub runner in this pass unless reproducibility becomes more important than auto-updating. +- [x] Confirm Caddy install still uses the stable apt repository. +- [x] Confirm Docker apt repository role still uses supported Linux Mint/Ubuntu codename handling. +- [x] Run LocalCluster audit after any deployment script change. ## Phase 14 - Security And Public Repo Review -- [ ] Run `rg -n "password|secret|token|eyJ|BEGIN .*PRIVATE|ANSIBLE_VAULT" .` -- [ ] Confirm `Deployment/LocalCluster/inventory/prod/vault.yml` is encrypted and safe to commit only if it contains Ansible Vault ciphertext. -- [ ] Confirm no local `.env` files are tracked. -- [ ] Confirm no Docker build output, migration bundle, or backup file is tracked. -- [ ] Confirm package updates do not introduce deprecated/vulnerable package warnings. +- [x] Run `rg -n "password|secret|token|eyJ|BEGIN .*PRIVATE|ANSIBLE_VAULT" .` +- [x] Confirm `Deployment/LocalCluster/inventory/prod/vault.yml` is encrypted and safe to commit only if it contains Ansible Vault ciphertext. +- [x] Confirm no local `.env` files are tracked. +- [x] Confirm no Docker build output, migration bundle, or backup file is tracked. +- [x] Confirm package updates do not introduce deprecated/vulnerable package warnings. ## Phase 15 - Rollback Plan If local build/test fails: -- [ ] Revert the smallest package group that caused the failure. -- [ ] Keep `global.json` fixed if possible, because it is currently invalid. -- [ ] Document deferred packages in this file. +- [x] Revert the smallest package group that caused the failure. Not needed after final green build/test; ImageSharp 4 was the only failed package group and was kept pinned. +- [x] Keep `global.json` fixed if possible, because it is currently invalid. +- [x] Document deferred packages in this file. If CI fails: -- [ ] Compare local SDK version and CI SDK version. -- [ ] Check whether `global.json` and `setup-dotnet` disagree. -- [ ] Check migration bundle build first, then Docker build. +- [x] Compare local SDK version and CI SDK version. `global.json` pins `10.0.300`; CI installs `10.0.x`. +- [x] Check whether `global.json` and `setup-dotnet` disagree. They agree on .NET 10. +- [x] Check migration bundle build first, then Docker build. If CD fails: -- [ ] Do not rerun repeatedly without reading the failing stage. -- [ ] If migration failed, use the pre-migration backup path. -- [ ] If app health failed, inspect app logs on both app nodes. -- [ ] If Caddy/public health failed, run `acceptance-check.sh` manually and inspect its diagnostics. +- [x] Do not rerun repeatedly without reading the failing stage. +- [x] If migration failed, use the pre-migration backup path. Rollback rule remains documented; no deployment migration was run here. +- [x] If app health failed, inspect app logs on both app nodes. Rollback rule remains documented; no production app health failure was created here. +- [x] If Caddy/public health failed, run `acceptance-check.sh` manually and inspect its diagnostics. Rollback rule remains documented; no production ingress check was run here. If production behavior is wrong after deployment: -- [ ] Roll back by dispatching CD from the previous known-good commit/image. -- [ ] Restore database only if migrations made incompatible data changes. -- [ ] Keep Cloudflare tunnel and runner unchanged unless the failure clearly involves ingress or runner setup. +- [x] Roll back by dispatching CD from the previous known-good commit/image. Rollback rule documented; not executed because no production deployment was run here. +- [x] Restore database only if migrations made incompatible data changes. Rollback rule documented; not executed because no production migration was run here. +- [x] Keep Cloudflare tunnel and runner unchanged unless the failure clearly involves ingress or runner setup. ## Phase 16 - Final Completion Criteria The update is not done until all of these are true: -- [ ] Every project targets `net10.0`. -- [ ] `global.json` is valid and blocks preview/latest-major surprise upgrades. -- [ ] CI uses .NET 10. -- [ ] Docker app image uses .NET 10 SDK/runtime. -- [ ] `dotnet-ef` is on the EF 10 line. -- [ ] Direct NuGet package updates are either fully current or explicitly deferred with a reason. -- [ ] `dotnet restore` passes. -- [ ] `dotnet build --configuration Release` passes. -- [ ] `dotnet test --configuration Release` passes. -- [ ] EF migration bundle builds. -- [ ] Docker image builds. -- [ ] Deployment audit passes. -- [ ] Rendered deployment template validation passes. -- [ ] CI passes. -- [ ] CD passes. -- [ ] `acceptance-check.sh` passes against `shipinspection.jacobgrum.com`. -- [ ] No secrets or generated artifacts are accidentally included. +- [x] Every project targets `net10.0`. +- [x] `global.json` is valid and blocks preview/latest-major surprise upgrades. +- [x] CI uses .NET 10. +- [x] Docker app image uses .NET 10 SDK/runtime. +- [x] `dotnet-ef` is on the EF 10 line. +- [x] Direct NuGet package updates are either fully current or explicitly deferred with a reason. +- [x] `dotnet restore` passes. +- [x] `dotnet build --configuration Release` passes. +- [x] `dotnet test --configuration Release` passes. +- [x] EF migration bundle builds. +- [x] Docker image builds. +- [x] Deployment audit passes. +- [x] Rendered deployment template validation passes. +- [ ] CI passes. External: requires GitHub Actions. +- [ ] CD passes. External: requires real self-hosted/control runner. +- [ ] `acceptance-check.sh` passes against `shipinspection.jacobgrum.com`. External: live production acceptance gate. +- [x] No secrets or generated artifacts are accidentally included. ## Useful Commands diff --git a/global.json b/global.json index a11f48e..17d728e 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "10.0.0", - "rollForward": "latestMajor", - "allowPrerelease": true + "version": "10.0.300", + "rollForward": "latestFeature", + "allowPrerelease": false } -} \ No newline at end of file +}