From 0f710128edf7b27a6894ab9c7d23fe01d517999a Mon Sep 17 00:00:00 2001 From: iammukeshm Date: Tue, 3 Nov 2026 23:02:22 +0530 Subject: [PATCH 1/2] docs: document the backend stack upgrade (Aspire 13.6, .NET 10.0.14, Postgres 18) --- src/components/landing/TechStack.astro | 14 +++++++------- src/components/landing/Testing.astro | 2 +- src/content/docs/building-blocks/mailing.mdx | 4 ++-- src/content/docs/changelog/index.mdx | 1 + src/content/docs/compare/fsh-vs-abp.mdx | 4 ++-- src/content/docs/compare/fsh-vs-blazorplate.mdx | 4 ++-- .../docs/compare/fsh-vs-clean-architecture.mdx | 4 ++-- src/content/docs/deployment/aspire.mdx | 9 +++++---- src/content/docs/deployment/aws-terraform.mdx | 4 ++-- src/content/docs/deployment/ci-cd.mdx | 4 ++-- src/content/docs/getting-started/introduction.mdx | 6 +++--- src/content/docs/getting-started/prerequisites.mdx | 4 ++-- .../docs/testing/fixtures-and-seed-data.mdx | 4 ++-- src/content/docs/testing/integration-tests.mdx | 8 ++++---- src/layouts/BaseLayout.astro | 2 +- 15 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/components/landing/TechStack.astro b/src/components/landing/TechStack.astro index e34b580f..57fac7a1 100644 --- a/src/components/landing/TechStack.astro +++ b/src/components/landing/TechStack.astro @@ -4,16 +4,16 @@ import SectionEyebrow from './SectionEyebrow.astro'; const stack = [ { name: '.NET 10', version: 'C# latest', sub: 'net10.0 LTS', icon: 'lucide:hexagon' }, - { name: 'Aspire', version: '13.3', sub: 'orchestration', icon: 'lucide:layers' }, - { name: 'EF Core', version: '10.0.8', sub: 'PostgreSQL 17', icon: 'lucide:database' }, + { name: 'Aspire', version: '13.6', sub: 'orchestration', icon: 'lucide:layers' }, + { name: 'EF Core', version: '10.0.14', sub: 'PostgreSQL 18', icon: 'lucide:database' }, { name: 'Mediator', version: '3.0', sub: 'source generator', icon: 'lucide:radio' }, - { name: 'FluentValidation', version: '12.1', sub: 'request validation', icon: 'lucide:shield-check' }, + { name: 'FluentValidation', version: '12.2', sub: 'request validation', icon: 'lucide:shield-check' }, { name: 'Hangfire', version: '1.8', sub: 'background jobs', icon: 'lucide:zap' }, - { name: 'Valkey', version: '8', sub: 'distributed cache', icon: 'lucide:server' }, + { name: 'Valkey', version: '9', sub: 'distributed cache', icon: 'lucide:server' }, { name: 'Serilog', version: '4.x', sub: 'structured logs', icon: 'lucide:scroll-text' }, - { name: 'OpenTelemetry', version: '1.15', sub: 'OTLP exporter', icon: 'lucide:activity' }, - { name: 'Scalar', version: '2.14', sub: 'OpenAPI 3.1 browser',icon: 'lucide:book-open' }, - { name: 'Finbuckle', version: '10.0', sub: 'multitenancy', icon: 'lucide:building-2' }, + { name: 'OpenTelemetry', version: '1.20', sub: 'OTLP exporter', icon: 'lucide:activity' }, + { name: 'Scalar', version: '2.18', sub: 'OpenAPI 3.1 browser',icon: 'lucide:book-open' }, + { name: 'Finbuckle', version: '10.1', sub: 'multitenancy', icon: 'lucide:building-2' }, { name: 'React + Vite', version: 'TS 5.7', sub: 'admin · dashboard', icon: 'lucide:code' }, ]; --- diff --git a/src/components/landing/Testing.astro b/src/components/landing/Testing.astro index a3a15a9a..08a41eab 100644 --- a/src/components/landing/Testing.astro +++ b/src/components/landing/Testing.astro @@ -129,7 +129,7 @@ const pillars: Pillar[] = [
$ dotnet test
-  ⟳  Testcontainers - postgres:17 · valkey  ready
+  ⟳  Testcontainers - postgres:18 · valkey  ready
   ✓  unit ........................  774 passed
   ✓  integration (real Postgres) .  661 passed
   ✓  architecture (NetArchTest) .   49 passed
diff --git a/src/content/docs/building-blocks/mailing.mdx b/src/content/docs/building-blocks/mailing.mdx
index f60eecb9..ccbfeb2c 100644
--- a/src/content/docs/building-blocks/mailing.mdx
+++ b/src/content/docs/building-blocks/mailing.mdx
@@ -1,6 +1,6 @@
 ---
 title: Mailing building block
-lastUpdated: 2026-10-02
+lastUpdated: 2026-11-03
 description: SMTP or SendGrid email abstraction behind a single IMailService, with multi-recipient delivery and HTML body support.
 sidebar:
   label: Mailing
@@ -140,7 +140,7 @@ An unknown name (a typo such as `Plain`) makes the options fail to bind, so the
 
 ### Local mail catcher (Docker Compose)
 
-`deploy/docker/docker-compose.yml` reads the SMTP target from `.env` and ships a [Mailpit](https://mailpit.axllent.org) service (`axllent/mailpit:v1.31.3`) under the `mail-catcher` compose profile. The defaults in `.env.example` enable that profile and point the API at it, so confirmation, password-reset and welcome e-mails work without an SMTP account, and none of them is delivered:
+`deploy/docker/docker-compose.yml` reads the SMTP target from `.env` and ships a [Mailpit](https://mailpit.axllent.org) service (`axllent/mailpit:v1.33.0`) under the `mail-catcher` compose profile. The defaults in `.env.example` enable that profile and point the API at it, so confirmation, password-reset and welcome e-mails work without an SMTP account, and none of them is delivered:
 
 ```bash
 # .env
diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx
index ee7e43dd..94850afa 100644
--- a/src/content/docs/changelog/index.mdx
+++ b/src/content/docs/changelog/index.mdx
@@ -13,6 +13,7 @@ Notable changes to the kit, newest first.
 
 ## 2026-11-03
 
+- **Dependencies: the backend stack is on the latest stable versions (chore).** ASP.NET Core, EF Core and `Microsoft.Extensions.*` move to the **10.0.14** patch line (with the `System.Security.Cryptography.Xml` / `.Pkcs` pins), .NET **Aspire 13.6.4** (hosting packages and AppHost SDK), Finbuckle.MultiTenant 10.1.6, FluentValidation 12.2.0, OpenTelemetry 1.20.1 (EF Core and StackExchange.Redis instrumentation on 1.20.1-beta.1), StackExchange.Redis 3.4.31, Scalar.AspNetCore 2.18.7, MailKit/MimeKit 4.18.2, AWSSDK.S3 4.0.104.8, QuestPDF 2026.9.1, Testcontainers 4.16.0 and Spectre.Console.Cli 0.57.2. `global.json` now asks for SDK `10.0.400` or later (still `latestFeature`). Containers: Postgres is **18** everywhere (Docker Compose already was; the Testcontainers harness and the CI migrator smoke move from 17), Valkey 9.1.3, RustFS 1.0.4, Mailpit v1.33.0, `amazon/aws-cli` 2.39.3, and RedisInsight is pinned to 3.8.0 instead of `latest`. Aspire 13.6 still defaults to `postgres:18.3`, so an Aspire data volume from 13.4 or later keeps working; only a volume created before 13.4 needs the [wipe](/docs/getting-started/troubleshooting/). Terraform's AWS provider constraint moves to `~> 6.71` (Terraform 1.15.4 is still the floor). If you forked the CLI, Spectre.Console.Cli 0.57 made `ExecuteAsync` overrides `public`. Part of [#1453](https://github.com/fullstackhero/dotnet-starter-kit/issues/1453); see PR [#PRNUM](https://github.com/fullstackhero/dotnet-starter-kit/pull/PRNUM).
 - **Eventing: one failing integration-event handler no longer blocks the others (fix).** `InMemoryEventBus` ran an event's handlers in order and stopped at the first exception, so the rest waited for the outbox retry, and if the failing one kept failing until the row dead-lettered they never ran. For `FileStorageKeyChangedIntegrationEvent` that meant a store rejecting Files' superseded-object delete also skipped Catalog's product-image URL rewrite. The bus now runs every handler, then rethrows (the single exception, or an `AggregateException` when several fail) so the outbox still retries; the inbox already records each handler that succeeded, so a retry re-runs only the failed ones. See [Eventing](/docs/building-blocks/eventing/#event-bus-implementations) and [#1423](https://github.com/fullstackhero/dotnet-starter-kit/issues/1423).
 - **Files: finalize no longer answers `500` for a file it already committed (fix).** `FinalizeUpload` moves an upload presigned before visibility roots existed after the finalize commit; when that move failed the client got a `500` for a file that was already `Available`, and a retried finalize answered `409`. The move is now best-effort: finalize returns the file and logs a warning, the file stays at its legacy key, and `MigrateLegacyPublicFileKeysJob` moves a public one on its next run (a private one stays presigned-only). See [Files](/docs/modules/files/#visibility-and-storage-moves).
 - **Files: a storage move whose commit fails no longer leaves an orphaned, quota-charged copy (fix).** `FileStorageRelocator` copies the object before committing the row; if the commit then failed, the copy stayed in the bucket, charged to the tenant's `StorageBytes`, with nothing pointing at it. The copy is now deleted again (refunding its bytes), best-effort and logged.
diff --git a/src/content/docs/compare/fsh-vs-abp.mdx b/src/content/docs/compare/fsh-vs-abp.mdx
index cc2c5fe7..49d40629 100644
--- a/src/content/docs/compare/fsh-vs-abp.mdx
+++ b/src/content/docs/compare/fsh-vs-abp.mdx
@@ -1,6 +1,6 @@
 ---
 title: fullstackhero vs ABP Framework
-lastUpdated: 2026-06-11
+lastUpdated: 2026-11-03
 description: A fair comparison of fullstackhero and ABP Framework for building production .NET 10 applications. License, architecture, learning curve, and when to pick which.
 sidebar:
   label: vs ABP Framework
@@ -32,7 +32,7 @@ ABP Framework and fullstackhero both target the same problem - getting a product
 | ORM | EF Core 10 (PostgreSQL via Npgsql by default; SQL Server provider available) | EF Core with abstraction layer + LINQ via repository pattern |
 | Background jobs | Hangfire 1.8 | ABP's `IBackgroundJobManager` (multiple providers) |
 | Realtime | SignalR + Server-Sent Events | SignalR via ABP modules |
-| Observability | Serilog 4 + OpenTelemetry 1.19 (OTLP exporter), pre-wired | OpenTelemetry support; configuration via ABP modules |
+| Observability | Serilog 4 + OpenTelemetry 1.20 (OTLP exporter), pre-wired | OpenTelemetry support; configuration via ABP modules |
 | Frontend included | React + Vite admin and dashboard, in the repo | MVC + Razor / Blazor / Angular options - Lepton theme is paid in ABP Commercial |
 | CLI scaffolding | `fsh new ` | `abp new ` (Studio offers a GUI) |
 | Stars (May 2026) | 6.4k★ | 14.2k★ |
diff --git a/src/content/docs/compare/fsh-vs-blazorplate.mdx b/src/content/docs/compare/fsh-vs-blazorplate.mdx
index c3ab1793..f76951a0 100644
--- a/src/content/docs/compare/fsh-vs-blazorplate.mdx
+++ b/src/content/docs/compare/fsh-vs-blazorplate.mdx
@@ -1,6 +1,6 @@
 ---
 title: fullstackhero vs BlazorPlate
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
 description: How fullstackhero compares to BlazorPlate - the difference between a free, open-source .NET 10 starter kit and a paid closed-source SaaS template.
 sidebar:
   label: vs BlazorPlate
@@ -30,7 +30,7 @@ BlazorPlate is a paid commercial multi-tenant SaaS starter for .NET, sold as a o
 | Frontend | React 19 + Vite admin console + tenant dashboard (both in the repo) | Blazor Server + Blazor WebAssembly |
 | i18n | Not built-in | 20+ languages, RTL support |
 | Background jobs | Hangfire 1.8 | Hangfire |
-| Observability | Serilog 4 + OpenTelemetry 1.19 (OTLP) | Serilog |
+| Observability | Serilog 4 + OpenTelemetry 1.20 (OTLP) | Serilog |
 | Realtime | SignalR (Valkey backplane) + Server-Sent Events | SignalR |
 | File storage | Tenant-scoped S3 abstraction (RustFS locally) | File storage primitives |
 | Email | MailKit / SendGrid | Email service |
diff --git a/src/content/docs/compare/fsh-vs-clean-architecture.mdx b/src/content/docs/compare/fsh-vs-clean-architecture.mdx
index 599d545d..555d015e 100644
--- a/src/content/docs/compare/fsh-vs-clean-architecture.mdx
+++ b/src/content/docs/compare/fsh-vs-clean-architecture.mdx
@@ -1,6 +1,6 @@
 ---
 title: fullstackhero vs Clean Architecture templates
-lastUpdated: 2026-05-19
+lastUpdated: 2026-11-03
 description: How fullstackhero compares to the Jason Taylor and Ardalis Clean Architecture templates - the difference between shipping an architecture and shipping production modules on top of one.
 sidebar:
   label: vs Clean Architecture
@@ -27,7 +27,7 @@ The Clean Architecture templates by Jason Taylor and Steve Smith (Ardalis) are t
 | Multitenancy | Not included | Not included | Finbuckle 10 with EF Core global query filter, IGlobalEntity opt-out, tenant-aware cache + jobs + outbox |
 | Auditing | Not included | Not included | EF SaveChanges interceptor + per-entity before/after + queryable `/audits` |
 | Background jobs | Not included | Not included | Hangfire 1.8 with tenant context preserved |
-| Observability | Console logging | Serilog example | Serilog 4 + OpenTelemetry 1.19 (OTLP) pre-wired |
+| Observability | Console logging | Serilog example | Serilog 4 + OpenTelemetry 1.20 (OTLP) pre-wired |
 | Caching | Not included | Not included | HybridCache (in-memory + Valkey) with tenant-scoped invalidation |
 | API browser | Swagger | Swagger | Scalar (OpenAPI 3.1) on `/scalar/` |
 | Frontend | Angular 21 / React 19 (sample) | None (API-only) | React 19 + Vite admin + dashboard, in the repo |
diff --git a/src/content/docs/deployment/aspire.mdx b/src/content/docs/deployment/aspire.mdx
index 432de280..134edd22 100644
--- a/src/content/docs/deployment/aspire.mdx
+++ b/src/content/docs/deployment/aspire.mdx
@@ -1,6 +1,6 @@
 ---
 title: Local Orchestration with .NET Aspire
-lastUpdated: 2026-09-30
+lastUpdated: 2026-11-03
 description: What spins up when you run the AppHost, and the decisions behind the local topology - Postgres, Valkey, RustFS, the migrator, the API, and both React apps.
 sidebar:
   label: Local Orchestration (Aspire)
@@ -31,7 +31,7 @@ documents what it starts and **why** it's wired the way it is.
 | pgAdmin | *(sidecar)* | Web UI on **:5050**, auto-discovers every database on the server | Persistent |
 | Valkey | `redis` | Distributed cache (HybridCache L2) + SignalR backplane + Data Protection key ring (**Valkey** - a Redis-compatible, BSD-licensed Redis fork; resource name stays `redis`) | Persistent (data volume) |
 | RedisInsight | `redis-insight` | Key browser on **:5540**, pre-connected to the Valkey instance for inspecting cache keys in dev | Persistent |
-| RustFS | `rustfs` | S3-compatible object storage (`rustfs/rustfs:1.0.0`), **:9000** (S3 API) / **:9001** (web console) | Persistent (data volume) |
+| RustFS | `rustfs` | S3-compatible object storage (`rustfs/rustfs:1.0.4`), **:9000** (S3 API) / **:9001** (web console) | Persistent (data volume) |
 | RustFS init | `rustfs-init` | One-shot (`amazon/aws-cli`): creates the `fsh-uploads` bucket + an anonymous `GetObject` policy on `public/*` only (re-applied every run), then exits | Run-once |
 | DB migrator | `fsh-starter-db-migrator` | One-shot: applies migrations across the tenant catalog + every tenant's module DBs (`apply --seed`, so the root admin exists), then exits | Run-once |
 | Demo seeder | `fsh-starter-demo-seeder` | One-shot, **dev-only**: runs `seed-demo` after the migrator to provision the `acme`/`globex` demo tenants + users, then exits | Run-once |
@@ -93,7 +93,7 @@ two contexts.
 ### Valkey for caching and realtime, with a RedisInsight browser
 
 The cache engine is **Valkey** (a Redis-compatible, BSD-licensed Redis fork),
-pulled as `valkey/valkey:9.1.0`. It speaks the Redis protocol (RESP), so the
+pulled as `valkey/valkey:9.1.3`. It speaks the Redis protocol (RESP), so the
 .NET client (`StackExchange.Redis`) and every `CachingOptions:Redis` config key
 are unchanged - and the resource keeps the name `redis` in code. It backs the
 HybridCache L2, the SignalR backplane, and the Data Protection key ring
@@ -115,7 +115,8 @@ Keep it that way.
 
 
 `AddPostgres("postgres")` doesn't pin an image tag - it tracks whatever the
-Aspire hosting package defaults to (PostgreSQL 18 as of Aspire 13.4). When a
+Aspire hosting package defaults to (PostgreSQL 18 since Aspire 13.4; Aspire 13.6
+still ships `postgres:18.3`, so moving from 13.5 to 13.6 keeps your volume). When a
 package update jumps a Postgres **major** version, the persistent
 `*-postgres-data` volume written by the older major can't be read and the
 container exits. Recovery for local dev data: remove the volume and relaunch -
diff --git a/src/content/docs/deployment/aws-terraform.mdx b/src/content/docs/deployment/aws-terraform.mdx
index 81c2cfeb..b86bbe34 100644
--- a/src/content/docs/deployment/aws-terraform.mdx
+++ b/src/content/docs/deployment/aws-terraform.mdx
@@ -1,6 +1,6 @@
 ---
 title: Deploy to AWS with Terraform
-lastUpdated: 2026-09-30
+lastUpdated: 2026-11-03
 description: End-to-end AWS deployment - prerequisites, bootstrapping the state backend, configuring an environment, and the one-command deploy that ships the API and both React apps.
 sidebar:
   label: AWS (Terraform)
@@ -69,7 +69,7 @@ ACM cert (in `us-east-1` for CloudFront) and DNS when you're ready.
 
 
 The configuration pins `required_version >= 1.15.4` and the AWS provider to
-`~> 6.46`. An older CLI will be rejected at `terraform init`. Upgrade with e.g.
+`~> 6.71`. An older CLI will be rejected at `terraform init`. Upgrade with e.g.
 `choco upgrade terraform` (Windows), `brew upgrade terraform` (macOS), or your
 package manager. A committed `.terraform.lock.hcl` keeps provider versions
 identical across machines and CI.
diff --git a/src/content/docs/deployment/ci-cd.mdx b/src/content/docs/deployment/ci-cd.mdx
index 0a98c73e..125e2d59 100644
--- a/src/content/docs/deployment/ci-cd.mdx
+++ b/src/content/docs/deployment/ci-cd.mdx
@@ -1,6 +1,6 @@
 ---
 title: CI/CD Pipeline
-lastUpdated: 2026-06-11
+lastUpdated: 2026-11-03
 description: The GitHub Actions setup - path-scoped backend and frontend pipelines, a pinned SDK, deduplicated test + coverage, CodeQL, the template smoke guard, and how container images and NuGet packages are published.
 sidebar:
   label: CI/CD
@@ -39,7 +39,7 @@ Triggers on pushes to `main`, `v*` tags, and PRs to `main`, plus manual `workflo
 | **Detect changes** | - | always | A `dorny/paths-filter` step decides whether backend code changed. On any push/tag/dispatch it's always `true`; on a PR it's `true` only when `src/**`, `global.json`, `coverage.runsettings`, or the workflow itself changed. Every heavy job gates on this. |
 | **Unit Tests** | Detect changes | backend changed | `dotnet build -c Release -warnaserror` (the build gate), an audit that **fails on directly-referenced vulnerable packages** (transitive advisories are reported, not blocking), then the 12 unit/architecture test projects **with coverage collection**. Uploads the coverage as an artifact. |
 | **Integration Tests** | Detect changes | backend changed | `Integration.Tests` + `Integration.Middleware.Tests` via `WebApplicationFactory` + Testcontainers (Docker on the runner), **with coverage collection**. Uploads its coverage as an artifact. |
-| **DbMigrator Container Smoke** | Detect changes | backend changed | Publishes the `fsh-db-migrator` image (Dockerfile-less SDK publish) and runs `apply --catalog-only` against an ephemeral Postgres 17 - asserts it finishes successfully. Catches container-publish and DI-graph regressions. |
+| **DbMigrator Container Smoke** | Detect changes | backend changed | Publishes the `fsh-db-migrator` image (Dockerfile-less SDK publish) and runs `apply --catalog-only` against an ephemeral Postgres 18 - asserts it finishes successfully. Catches container-publish and DI-graph regressions. |
 | **Coverage Gate** | Unit + Integration | backend changed | **Merges** the coverage artifacts from the two test jobs with ReportGenerator and **fails if line coverage drops below 80%**. It does *not* re-run the tests - a ratchet; raise `MIN_LINE` as coverage grows. |
 | **Publish Dev Containers** | Unit + Integration | push to `main` | Publishes the API **and** DbMigrator images to GHCR tagged `dev-` and `dev-latest`. |
 | **Publish Release** | Unit + Integration | `v*` tag, or `workflow_dispatch` on `main` | Packs the `fsh` CLI + the `dotnet new` template, pushes both to NuGet.org, and publishes the API + DbMigrator images to GHCR tagged `` and `latest`. |
diff --git a/src/content/docs/getting-started/introduction.mdx b/src/content/docs/getting-started/introduction.mdx
index 25c7a209..4b366016 100644
--- a/src/content/docs/getting-started/introduction.mdx
+++ b/src/content/docs/getting-started/introduction.mdx
@@ -1,6 +1,6 @@
 ---
 title: Introduction
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
 description: What fullstackhero is, what it ships with, and how it's put together.
 sidebar:
   label: Introduction
@@ -145,10 +145,10 @@ The API never mutates data on startup. Demo data (acme/globex tenants, sample ca
 | Storage             | Local filesystem or S3-compatible (AWS SDK)                             |
 | Messaging           | RabbitMQ client (optional)                                              |
 | Logging             | Serilog 4 (structured)                                                  |
-| Tracing / metrics   | OpenTelemetry 1.19 (OTLP exporter)                                      |
+| Tracing / metrics   | OpenTelemetry 1.20 (OTLP exporter)                                      |
 | API docs            | OpenAPI 10 + Scalar UI                                                  |
 | Errors              | `ProblemDetails` (RFC 9457) via global exception handler                |
-| Orchestration       | .NET Aspire 13.5 (Postgres + Valkey + RustFS + API + both React apps)   |
+| Orchestration       | .NET Aspire 13.6 (Postgres + Valkey + RustFS + API + both React apps)   |
 
 ### Frontends
 
diff --git a/src/content/docs/getting-started/prerequisites.mdx b/src/content/docs/getting-started/prerequisites.mdx
index 2aaecb0b..44074081 100644
--- a/src/content/docs/getting-started/prerequisites.mdx
+++ b/src/content/docs/getting-started/prerequisites.mdx
@@ -1,6 +1,6 @@
 ---
 title: Prerequisites
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
 description: What you need installed locally before running a fullstackhero project.
 sidebar:
   order: 2
@@ -15,7 +15,7 @@ A short checklist. The rest of Getting Started assumes these are in place.
 
 ## Required
 
-- **.NET 10 SDK** - from [dotnet.microsoft.com/download](https://dotnet.microsoft.com/download). The repo's `global.json` pins SDK `10.0.100` (rolling forward to the latest feature band).
+- **.NET 10 SDK** - from [dotnet.microsoft.com/download](https://dotnet.microsoft.com/download). The repo's `global.json` pins SDK `10.0.400` as the floor (rolling forward to the latest feature band, `10.0.403` at the time of writing).
 - **Docker** - used for the Postgres, Valkey, and RustFS containers (Aspire and the integration tests rely on it). Docker Desktop on macOS / Windows, Docker Engine on Linux.
 - **Git** - for cloning the repo.
 
diff --git a/src/content/docs/testing/fixtures-and-seed-data.mdx b/src/content/docs/testing/fixtures-and-seed-data.mdx
index de5a7d6f..4c0a5e72 100644
--- a/src/content/docs/testing/fixtures-and-seed-data.mdx
+++ b/src/content/docs/testing/fixtures-and-seed-data.mdx
@@ -1,6 +1,6 @@
 ---
 title: Fixtures & seed data
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
 description: The shared test factory (containers, host, seeded root tenant + admin) used across the integration suite, and the service swaps that keep tests deterministic.
 sidebar:
   label: Fixtures & seed data
@@ -22,7 +22,7 @@ The whole suite shares one database across hundreds of tests. Give everything yo
 
 | Piece | Type | Role |
 |---|---|---|
-| `FshWebApplicationFactory` | `WebApplicationFactory, IAsyncLifetime` | Owns the `postgres:17-alpine` + `rustfs/rustfs:1.0.0` (generic `ContainerBuilder`) Testcontainers, builds the in-process host, migrates + seeds |
+| `FshWebApplicationFactory` | `WebApplicationFactory, IAsyncLifetime` | Owns the `postgres:18-alpine` + `rustfs/rustfs:1.0.4` (generic `ContainerBuilder`) Testcontainers, builds the in-process host, migrates + seeds |
 | `FshCollectionDefinition` | `ICollectionFixture` | Shares **one** factory instance across the whole suite (`[Collection(FshCollectionDefinition.Name)]`) |
 | `AuthHelper` | Plain class, wraps the factory | Issues real tokens via `POST /token/issue` and builds authenticated `HttpClient`s |
 | `TestConstants` | Static class | Root tenant id, root admin credentials, JWT settings, per-module base paths |
diff --git a/src/content/docs/testing/integration-tests.mdx b/src/content/docs/testing/integration-tests.mdx
index 1645ecc2..263f2400 100644
--- a/src/content/docs/testing/integration-tests.mdx
+++ b/src/content/docs/testing/integration-tests.mdx
@@ -1,6 +1,6 @@
 ---
 title: Integration tests
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
 description: WebApplicationFactory in-process API tests against Testcontainers Postgres + RustFS - one container set, all modules, real round trips.
 sidebar:
   label: Integration tests
@@ -12,7 +12,7 @@ seo:
   keywords: 'webapplicationfactory dotnet, testcontainers postgres .net 10, integration test asp.net core, in-process api test, testcontainers rustfs s3'
 ---
 
-Integration tests in fullstackhero spin up real infrastructure in containers - PostgreSQL 17 and RustFS (S3-compatible) - and exercise the API end-to-end through `Microsoft.AspNetCore.Mvc.Testing`'s `WebApplicationFactory`. One factory (and therefore one container set) serves the entire `Integration.Tests` project via an xUnit collection fixture, so the startup cost is paid once and the per-test cost is small.
+Integration tests in fullstackhero spin up real infrastructure in containers - PostgreSQL 18 and RustFS (S3-compatible) - and exercise the API end-to-end through `Microsoft.AspNetCore.Mvc.Testing`'s `WebApplicationFactory`. One factory (and therefore one container set) serves the entire `Integration.Tests` project via an xUnit collection fixture, so the startup cost is paid once and the per-test cost is small.
 
 
 Splitting integration tests into per-module projects would mean per-project container startup. The single project shares one factory + one container set across every test class, runs faster, and you don't have to think about container leakage across boundaries.
@@ -23,11 +23,11 @@ Splitting integration tests into per-module projects would mean per-project cont
 | Layer | Library | Purpose |
 |---|---|---|
 | Host | `Microsoft.AspNetCore.Mvc.Testing` | `WebApplicationFactory` for the in-process API |
-| Containers | `Testcontainers.PostgreSql` + generic `Testcontainers` (`ContainerBuilder`) 4.14 | `postgres:17-alpine` + `rustfs/rustfs:1.0.0` per test run |
+| Containers | `Testcontainers.PostgreSql` + generic `Testcontainers` (`ContainerBuilder`) 4.16 | `postgres:18-alpine` + `rustfs/rustfs:1.0.4` per test run |
 | HTTP | `HttpClient` from `factory.CreateClient()` | Real HTTP calls through the full middleware pipeline |
 | Assertions | Shouldly, NSubstitute, AutoFixture | Same as unit tests |
 
-There is **no Redis/Valkey container** in the shared harness - `CachingOptions:Redis` is set to an empty string, so HybridCache runs on its in-memory fallback and Hangfire runs on `Hangfire.InMemory`. The one place a real cache engine matters, `Tests/Caching/HybridCacheRedisTests.cs`, spins up its own `valkey/valkey:9.1.0-alpine` container to guard against in-memory-vs-distributed serialization divergence.
+There is **no Redis/Valkey container** in the shared harness - `CachingOptions:Redis` is set to an empty string, so HybridCache runs on its in-memory fallback and Hangfire runs on `Hangfire.InMemory`. The one place a real cache engine matters, `Tests/Caching/HybridCacheRedisTests.cs`, spins up its own `valkey/valkey:9.1.3-alpine` container to guard against in-memory-vs-distributed serialization divergence.
 
 ## The fixture pattern
 
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index b8b44185..2d767c60 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -121,7 +121,7 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? siteConfig.url).t
           applicationSubCategory: 'Web Application Framework',
           operatingSystem: 'Windows, Linux, macOS',
           softwareVersion: '10.0.0',
-          softwareRequirements: '.NET 10 SDK, PostgreSQL 17, Redis 7, Node.js 20+',
+          softwareRequirements: '.NET 10 SDK, PostgreSQL 18, Valkey 9 (or Redis), Node.js 20+',
           downloadUrl: siteConfig.repo,
           codeRepository: siteConfig.repo,
           programmingLanguage: ['C#', 'TypeScript'],

From c7d516b084b03cec988f54ccbc780885a6893bb0 Mon Sep 17 00:00:00 2001
From: iammukeshm 
Date: Tue, 3 Nov 2026 23:02:47 +0530
Subject: [PATCH 2/2] docs(changelog): link the backend upgrade PR

---
 src/content/docs/changelog/index.mdx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx
index 94850afa..68716711 100644
--- a/src/content/docs/changelog/index.mdx
+++ b/src/content/docs/changelog/index.mdx
@@ -13,7 +13,7 @@ Notable changes to the kit, newest first.
 
 ## 2026-11-03
 
-- **Dependencies: the backend stack is on the latest stable versions (chore).** ASP.NET Core, EF Core and `Microsoft.Extensions.*` move to the **10.0.14** patch line (with the `System.Security.Cryptography.Xml` / `.Pkcs` pins), .NET **Aspire 13.6.4** (hosting packages and AppHost SDK), Finbuckle.MultiTenant 10.1.6, FluentValidation 12.2.0, OpenTelemetry 1.20.1 (EF Core and StackExchange.Redis instrumentation on 1.20.1-beta.1), StackExchange.Redis 3.4.31, Scalar.AspNetCore 2.18.7, MailKit/MimeKit 4.18.2, AWSSDK.S3 4.0.104.8, QuestPDF 2026.9.1, Testcontainers 4.16.0 and Spectre.Console.Cli 0.57.2. `global.json` now asks for SDK `10.0.400` or later (still `latestFeature`). Containers: Postgres is **18** everywhere (Docker Compose already was; the Testcontainers harness and the CI migrator smoke move from 17), Valkey 9.1.3, RustFS 1.0.4, Mailpit v1.33.0, `amazon/aws-cli` 2.39.3, and RedisInsight is pinned to 3.8.0 instead of `latest`. Aspire 13.6 still defaults to `postgres:18.3`, so an Aspire data volume from 13.4 or later keeps working; only a volume created before 13.4 needs the [wipe](/docs/getting-started/troubleshooting/). Terraform's AWS provider constraint moves to `~> 6.71` (Terraform 1.15.4 is still the floor). If you forked the CLI, Spectre.Console.Cli 0.57 made `ExecuteAsync` overrides `public`. Part of [#1453](https://github.com/fullstackhero/dotnet-starter-kit/issues/1453); see PR [#PRNUM](https://github.com/fullstackhero/dotnet-starter-kit/pull/PRNUM).
+- **Dependencies: the backend stack is on the latest stable versions (chore).** ASP.NET Core, EF Core and `Microsoft.Extensions.*` move to the **10.0.14** patch line (with the `System.Security.Cryptography.Xml` / `.Pkcs` pins), .NET **Aspire 13.6.4** (hosting packages and AppHost SDK), Finbuckle.MultiTenant 10.1.6, FluentValidation 12.2.0, OpenTelemetry 1.20.1 (EF Core and StackExchange.Redis instrumentation on 1.20.1-beta.1), StackExchange.Redis 3.4.31, Scalar.AspNetCore 2.18.7, MailKit/MimeKit 4.18.2, AWSSDK.S3 4.0.104.8, QuestPDF 2026.9.1, Testcontainers 4.16.0 and Spectre.Console.Cli 0.57.2. `global.json` now asks for SDK `10.0.400` or later (still `latestFeature`). Containers: Postgres is **18** everywhere (Docker Compose already was; the Testcontainers harness and the CI migrator smoke move from 17), Valkey 9.1.3, RustFS 1.0.4, Mailpit v1.33.0, `amazon/aws-cli` 2.39.3, and RedisInsight is pinned to 3.8.0 instead of `latest`. Aspire 13.6 still defaults to `postgres:18.3`, so an Aspire data volume from 13.4 or later keeps working; only a volume created before 13.4 needs the [wipe](/docs/getting-started/troubleshooting/). Terraform's AWS provider constraint moves to `~> 6.71` (Terraform 1.15.4 is still the floor). If you forked the CLI, Spectre.Console.Cli 0.57 made `ExecuteAsync` overrides `public`. Part of [#1453](https://github.com/fullstackhero/dotnet-starter-kit/issues/1453); see PR [#1460](https://github.com/fullstackhero/dotnet-starter-kit/pull/1460).
 - **Eventing: one failing integration-event handler no longer blocks the others (fix).** `InMemoryEventBus` ran an event's handlers in order and stopped at the first exception, so the rest waited for the outbox retry, and if the failing one kept failing until the row dead-lettered they never ran. For `FileStorageKeyChangedIntegrationEvent` that meant a store rejecting Files' superseded-object delete also skipped Catalog's product-image URL rewrite. The bus now runs every handler, then rethrows (the single exception, or an `AggregateException` when several fail) so the outbox still retries; the inbox already records each handler that succeeded, so a retry re-runs only the failed ones. See [Eventing](/docs/building-blocks/eventing/#event-bus-implementations) and [#1423](https://github.com/fullstackhero/dotnet-starter-kit/issues/1423).
 - **Files: finalize no longer answers `500` for a file it already committed (fix).** `FinalizeUpload` moves an upload presigned before visibility roots existed after the finalize commit; when that move failed the client got a `500` for a file that was already `Available`, and a retried finalize answered `409`. The move is now best-effort: finalize returns the file and logs a warning, the file stays at its legacy key, and `MigrateLegacyPublicFileKeysJob` moves a public one on its next run (a private one stays presigned-only). See [Files](/docs/modules/files/#visibility-and-storage-moves).
 - **Files: a storage move whose commit fails no longer leaves an orphaned, quota-charged copy (fix).** `FileStorageRelocator` copies the object before committing the row; if the commit then failed, the copy stayed in the bucket, charged to the tenant's `StorageBytes`, with nothing pointing at it. The copy is now deleted again (refunding its bytes), best-effort and logged.