# Code Generation Plan — U1 Hosting & Serving **This plan is the single source of truth for Code Generation of U1.** Generation executes exactly these steps in order; no step is added or skipped during execution. --- ## Unit Context | Aspect | Detail | |---|---| | **Unit** | U1 Hosting & Serving | | **Round** | R1 (with U2 Data Durability) | | **Workspace root** | `K:\Development\Projects\SlpModularCms` | | **Project type** | Brownfield — existing structure retained, files modified in place | | **Requirements** | FR-07, FR-10, FR-24 | | **Components** | C-04 health checks, C-10 static mounts, C-13 availability middleware, U1 portion of C-16 | | **Business rules** | BR-U1-01 … BR-U1-22 | | **Depends on** | Nothing. U1 and U2 are mutually independent | | **Depended on by** | U3 (path layout for CSP scoping), U6 (deploys into this layout) | | **New database entities** | **None** — U1 adds no table, migration or configuration section | ### Requirement traceability | Requirement | Implemented by steps | |---|---| | FR-07 — serve `/` from `wwwroot/web/`, `/admin` from `wwwroot/admin/` | 4, 5, 6 | | FR-10 — `/health` liveness endpoint on the availability bypass list | 2, 6, 7 | | FR-24 — validate the token in the availability gate's admin bypass | 3, 7, 8 | --- ## Generation Steps ### Step 1: Shared JWT validation parameters (Core) - [x] Create `src/SlpModularCms.Core/Hosting/JwtTokenValidation.cs` — a factory producing `TokenValidationParameters` from `JwtSettings`, with `ClockSkew.Zero`, matching the current inline configuration exactly - [x] Modify `src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` so `AddJwtBearer` consumes the factory instead of building parameters inline - [x] Register the produced `TokenValidationParameters` as a singleton so the availability gate resolves the **same instance** *Implements the BR-U1-11 single-source constraint: two copies could drift, and a gate more permissive than the bearer scheme would silently re-open the hole FR-24 closes.* ### Step 2: Health check registration (Core) - [x] Create `src/SlpModularCms.Core/Hosting/Health/HealthCheckExtensions.cs` with `AddCmsHealthChecks()` and `MapCmsHealthChecks()` - [x] Create the `HealthReport` response model — `status`, `timestamp`, `version`, `modules` - [x] Compose the report from in-process state only: no database call, no dependency probe (BR-U1-15) - [x] Read module names from the existing `ModuleOrchestrator` - [x] Read the version from the assembly's informational version - [x] Deliberately expose **no** options parameter, so adding a database check later is a visible code change rather than configuration drift ### Step 3: Admin token validator (Core) - [x] Create `src/SlpModularCms.Core/Hosting/Security/IAdminTokenValidator.cs` and `AdminTokenValidator.cs` - [x] Validate the bearer token against the shared `TokenValidationParameters` from Step 1 — signature, issuer, audience and lifetime (BR-U1-11) - [x] Return true only when validation succeeds **and** the principal carries role `Owner` or `Administrator` (BR-U1-13) - [x] Return false — never throw — for absent, malformed, forged or expired tokens (BR-U1-12, BR-U1-14) - [x] Register in `ServiceCollectionExtensions.AddCoreInfrastructure` ### Step 4: Static content composition (Api host) - [x] Create `src/SlpModularCms.Api/Extensions/StaticContentExtensions.cs` with `UseCmsStaticContent()` - [x] Register the `/admin` mount **first**, then the root mount, each with its own `PhysicalFileProvider` (BR-U1-01) - [x] Enable default-file handling per mount; leave directory browsing disabled (BR-U1-07) - [x] Tolerate a missing physical directory at startup for both mounts (BR-U1-20, BR-U1-22) - [x] Log a warning naming the absolute expected path when a directory is absent (BR-U1-21) - [x] Redirect the exact path `/admin` to `/admin/`, leaving deeper paths untouched (BR-U1-03) ### Step 5: Placeholder page (Api host) - [x] Create `src/SlpModularCms.Api/Extensions/WebsitePlaceholder.html` — states no website is deployed, names the expected target path, links to `/admin` - [x] Contain no version, environment name, module list or configuration (domain-entities.md) - [x] Modify `src/SlpModularCms.Api/SlpModularCms.Api.csproj` to embed it as an `EmbeddedResource` - [x] Serve it with status `200` when the website fallback is needed and `wwwroot/web/index.html` is absent (BR-U1-06) *Embedded rather than placed in `wwwroot/web/`, because that directory is owned and overwritten by a website workspace — a file there would be deleted by the first real deployment or mistaken for part of the customer's site.* ### Step 6: Api host composition - [x] Modify `src/SlpModularCms.Api/Program.cs`: - [x] Replace `UseDefaultFiles()` + `UseStaticFiles()` with `UseCmsStaticContent()` - [x] Register `AddCmsHealthChecks()` alongside the existing service registrations - [x] Map `MapCmsHealthChecks()` after `MapControllers()` - [x] Retarget both `MapFallbackToFile` registrations to the two mounts, preserving the `nonfile` constraint (BR-U1-04, BR-U1-05) ### Step 7: Slave host composition - [x] Modify `src/SlpModularCms.Api.Slave/Program.cs`: - [x] Register `AddCmsHealthChecks()` and map `MapCmsHealthChecks()` - [x] Add **no** static mounts — the Slave serves no static content (Q2 of Application Design = A) ### Step 8: Availability middleware (Modules.Availability) - [x] Modify `src/SlpModularCms.Modules.Availability/Middleware/AvailabilityMiddleware.cs`: - [x] Add `/health` to `_bypassPrefixes` (BR-U1-09), leaving the existing entries unchanged (BR-U1-10) - [x] Replace the `JwtSecurityTokenHandler.ReadJwtToken` call in `IsAdminBypass` with the injected `IAdminTokenValidator` - [x] Remove the now-unused `System.IdentityModel.Tokens.Jwt` usage ### Step 9: Core unit tests - [x] Create `src/SlpModularCms.Core.Tests/Hosting/AdminTokenValidatorTests.cs` — valid Owner token accepted; valid Administrator accepted; valid User rejected; forged unsigned token rejected; expired token rejected; malformed header rejected; absent header rejected - [x] Create `src/SlpModularCms.Core.Tests/Hosting/HealthReportTests.cs` — report composition; module names sourced from the orchestrator; no configuration or path values present ### Step 10: Availability module unit tests - [x] Modify `src/SlpModularCms.Modules.Availability.Tests/AvailabilityMiddlewareTests.cs` for the constructor change, and add: `/health` bypasses while the instance is disabled; a forged Owner token grants **no** bypass; a valid Owner token still bypasses - [x] Modify `src/SlpModularCms.Modules.Availability.Tests/AvailabilityMiddlewareMasterGateTests.cs` for the constructor change ### Step 11: Static content unit tests — **DEVIATED, see generation-summary.md** - [~] `StaticContentTests.cs` **not created**: the plan placed it in `SlpModularCms.Core.Tests`, but `StaticContentExtensions` lives in `SlpModularCms.Api`, which `Core.Tests` does not reference. `SlpModularCms.Api` has no test project by the same convention that gives `SlpModularCms.Api.Slave` none - [x] Behaviour requiring a composed pipeline recorded in the unit summary as carried to the phase-level Build and Test stage ### Step 12: Documentation - [x] Create `aidlc-docs/features/gitea-deployment-workflow/construction/u1-hosting-serving/code/generation-summary.md` — files created and modified, decisions taken, and any deviation from this plan ### Step 13: Build and test verification (automatic) - [x] `dotnet build SlpModularCms.sln -c Release` - [x] `dotnet test` for `SlpModularCms.Core.Tests` and `SlpModularCms.Modules.Availability.Tests` - [x] Fix any failure directly and re-run until green - [x] Record the outcome for the completion message --- ## Files Touched ### Created | Path | Purpose | |---|---| | `src/SlpModularCms.Core/Hosting/JwtTokenValidation.cs` | Shared validation parameters | | `src/SlpModularCms.Core/Hosting/Health/HealthCheckExtensions.cs` | Health registration and endpoint | | `src/SlpModularCms.Core/Hosting/Security/IAdminTokenValidator.cs` | Contract | | `src/SlpModularCms.Core/Hosting/Security/AdminTokenValidator.cs` | Implementation | | `src/SlpModularCms.Api/Extensions/StaticContentExtensions.cs` | Two-mount composition | | `src/SlpModularCms.Api/Extensions/WebsitePlaceholder.html` | Embedded placeholder | | `src/SlpModularCms.Core.Tests/Hosting/AdminTokenValidatorTests.cs` | Tests | | `src/SlpModularCms.Core.Tests/Hosting/HealthReportTests.cs` | Tests | | `src/SlpModularCms.Core.Tests/Hosting/StaticContentTests.cs` | Tests | ### Modified | Path | Change | |---|---| | `src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` | Use the shared factory; register the validator | | `src/SlpModularCms.Api/Program.cs` | Static content, health checks, retargeted fallbacks | | `src/SlpModularCms.Api/SlpModularCms.Api.csproj` | Embed the placeholder | | `src/SlpModularCms.Api.Slave/Program.cs` | Health checks only | | `src/SlpModularCms.Modules.Availability/Middleware/AvailabilityMiddleware.cs` | `/health` bypass; validated admin bypass | | `src/SlpModularCms.Modules.Availability.Tests/AvailabilityMiddlewareTests.cs` | Constructor change plus new cases | | `src/SlpModularCms.Modules.Availability.Tests/AvailabilityMiddlewareMasterGateTests.cs` | Constructor change | **Brownfield rule**: every file above that exists is modified in place. No `*_new`, `*_modified` or parallel copies. --- ## Out of Scope for U1 - Security headers — U3 - Sentry, Umami, same-origin frontend config — U4 - Data Protection and automatic migrations — U2 - Anything under `.gitea/` — U5 and U6 - README and website contract — U7