Adds SlpModularCms.Api.SlpSoftware and extracts shared CmsHost composition
Continuous Integration / config (pull_request) Successful in 11s
Continuous Integration / changes (pull_request) Successful in 21s
Continuous Integration / backend-build (pull_request) Successful in 6m10s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m59s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m27s
Continuous Integration / backend-test (pull_request) Failing after 7m48s
Continuous Integration / frontend-build (pull_request) Successful in 2m5s
Continuous Integration / frontend-test (pull_request) Successful in 4m24s
Continuous Integration / frontend-lint (pull_request) Successful in 2m0s
Continuous Integration / publish-test (pull_request) Skipped
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-test (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Continuous Integration / config (pull_request) Successful in 11s
Continuous Integration / changes (pull_request) Successful in 21s
Continuous Integration / backend-build (pull_request) Successful in 6m10s
Continuous Integration / vulnerability-scan (pull_request) Successful in 4m59s
Continuous Integration / frontend-prepare (pull_request) Successful in 1m27s
Continuous Integration / backend-test (pull_request) Failing after 7m48s
Continuous Integration / frontend-build (pull_request) Successful in 2m5s
Continuous Integration / frontend-test (pull_request) Successful in 4m24s
Continuous Integration / frontend-lint (pull_request) Successful in 2m0s
Continuous Integration / publish-test (pull_request) Skipped
Continuous Integration / publish-production (pull_request) Skipped
Continuous Integration / deploy-test (pull_request) Skipped
Continuous Integration / deploy-production (pull_request) Skipped
Unit 1 of the slpsoftware-api feature (FR-1/FR-2/FR-3): a new Client project in the Clients solution folder, intended to eventually become the deployed API for test.slpsoftware.nl/slpsoftware.nl, hosting the same four modules as SlpModularCms.Api plus a future Offerings module. - Extracts SlpModularCms.Api/Program.cs's hosting-pipeline composition into SlpModularCms.Core.Hosting.CmsHost (ConfigureServices/ConfigurePipeline), shared by both Client projects so they cannot drift apart - Moves StaticContentExtensions.cs + WebsitePlaceholder.html from Api into Core, since CmsHost cannot live in Api but Core cannot depend on Api - Adds SlpModularCms.Api.SlpSoftware with its own isolated local dev database and dev ports (5286/7223, distinct from Api's and Api.Slave's) - Adds SlpModularCms.Api.Tests with WebApplicationFactory-based pipeline regression tests (security headers, health check, SPA fallback, rate limiting), scoped to Api per NFR Design - Adds a frontend dev:slpsoftware pnpm script mirroring dev:slave - Fixes GlobalExceptionHandler logging routine 401s (e.g. an expired/missing refresh token) as unhandled errors -- pre-existing, unrelated to this feature's own scope, found while testing the new instance Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FWyStNL2ZsjrS7FLd7xvvN
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
# Code Generation Plan — Unit: SlpSoftware Client Setup
|
||||
|
||||
## Unit Context
|
||||
|
||||
**Stories implemented**: None directly (unit-of-work-story-map.md — this unit is a purely technical enabling unit).
|
||||
**Functional requirements implemented**: FR-1, FR-2, FR-3.
|
||||
**Dependencies**: None on other units (this unit is the dependency Unit 2 "Offerings" needs).
|
||||
**Expected interfaces produced**: `SlpModularCms.Core.Hosting.CmsHost` (`ConfigureServices`/`ConfigurePipeline`), consumed by both Client projects' `Program.cs`.
|
||||
**Database entities owned**: None.
|
||||
**Workspace root**: `K:\Development\Projects\SlpModularCms` (brownfield — modify/move existing files where noted, never duplicate).
|
||||
|
||||
## Investigation Finding That Shapes This Plan
|
||||
|
||||
Reading `SlpModularCms.Api/Program.cs` and `SlpModularCms.Api/Extensions/StaticContentExtensions.cs` directly (not assumed) revealed that `UseCmsStaticContent()`/`MapCmsSpaFallbacks()` — needed by `CmsHost.ConfigurePipeline` — currently live in the **`Api` project itself**, not in `Core`. Since `Core` cannot depend on `Api` (wrong dependency direction — `Api` depends on `Core`, never the reverse), this file (and its embedded `WebsitePlaceholder.html` resource) must move into `Core` **before** `CmsHost` can call it. This is Step 1 below, not an afterthought.
|
||||
|
||||
---
|
||||
|
||||
## Steps
|
||||
|
||||
### Step 1 — Move Static Content Hosting into Core (prerequisite for CmsHost)
|
||||
- [x] Move `src/SlpModularCms.Api/Extensions/StaticContentExtensions.cs` → `src/SlpModularCms.Core/Hosting/StaticContentExtensions.cs`; change namespace `SlpModularCms.Api.Extensions` → `SlpModularCms.Core.Hosting`; update `PlaceholderResourceName` from `"SlpModularCms.Api.Extensions.WebsitePlaceholder.html"` to `"SlpModularCms.Core.Hosting.WebsitePlaceholder.html"`
|
||||
- [x] Move `src/SlpModularCms.Api/Extensions/WebsitePlaceholder.html` → `src/SlpModularCms.Core/Hosting/WebsitePlaceholder.html`
|
||||
- [x] `SlpModularCms.Core.csproj`: add `<EmbeddedResource Include="Hosting\WebsitePlaceholder.html" />`
|
||||
- [x] `SlpModularCms.Api.csproj`: remove the now-obsolete `<EmbeddedResource Include="Extensions\WebsitePlaceholder.html" />` item group and its explanatory comment (the file no longer lives there)
|
||||
- [x] Delete the now-empty `src/SlpModularCms.Api/Extensions/` directory if nothing else remains in it
|
||||
|
||||
### Step 2 — `CmsHostOptions` (Business Logic Generation — Core)
|
||||
- [x] Create `src/SlpModularCms.Core/Hosting/CmsHostOptions.cs`: an intentionally empty class (NFR Design Pattern 2 / Q2 = B) — a pure extension point, no properties yet
|
||||
|
||||
### Step 3 — `CmsHost` (Business Logic Generation — Core)
|
||||
- [x] Create `src/SlpModularCms.Core/Hosting/CmsHost.cs` with:
|
||||
- `public static ModuleOrchestrator ConfigureServices(WebApplicationBuilder builder, CmsHostOptions options)` — reproduces `Api/Program.cs` lines for: `appsettings.local.json` loading stays in each project's own `Program.cs` (NOT moved here — application-design.md component-methods.md is explicit that bootstrap lines stay per-project); logging (`AddCmsLogging`), Sentry (`UseCmsSentry`), `ModuleOrchestrator` construction + `DiscoverModules()`, `AddCoreInfrastructure`, `AddCmsCors`, `AddCmsRateLimiting`, `AddCmsHealthChecks`, `AddCmsSecurityHeaders`, `AddCmsObservability`, `AddCmsDataProtection` (before module services — order preserved exactly), `RegisterModuleServices`, `AddSingleton(orchestrator)`, `AddControllers` with `ApiPrefixConvention("api/v1")` + `JsonStringEnumConverter`. Returns the orchestrator.
|
||||
- `public static void ConfigurePipeline(WebApplication app, ModuleOrchestrator orchestrator, CmsHostOptions options)` — reproduces: `MigrateCoreDatabase`, `UseExceptionHandler`, `UseCmsSecurityHeaders`, `UseRateLimiter`, Development-only `MapOpenApi`/`MapScalarApiReference`, `UseHttpsRedirection`, `UseCmsStaticContent`, `UseCors`, `orchestrator.UseModules(app)`, `UseAuthentication`/`UseAuthorization`, `MapControllers`, `MapCmsHealthChecks`, `MapSentryTunnel`, `MapCmsSpaFallbacks` — same order as today's `Program.cs`, since that order encodes real constraints documented in its comments
|
||||
- [x] Both methods accept `CmsHostOptions` per NFR-CS-02, even though it currently has no properties to read
|
||||
|
||||
### Step 4 — Repoint `SlpModularCms.Api/Program.cs` (Modify In-Place)
|
||||
- [x] Rewrite `src/SlpModularCms.Api/Program.cs` to the thin form: create builder, load `appsettings.local.json`, `var orchestrator = CmsHost.ConfigureServices(builder, new CmsHostOptions());`, `var app = builder.Build();`, `CmsHost.ConfigurePipeline(app, orchestrator, new CmsHostOptions());`, `app.Run();`
|
||||
- [x] No behavior change — verified by Step 7's regression tests
|
||||
|
||||
### Step 5 — New Client Project: `SlpModularCms.Api.SlpSoftware` (Project Structure Setup)
|
||||
- [x] Create `src/SlpModularCms.Api.SlpSoftware/SlpModularCms.Api.SlpSoftware.csproj` — mirrors `SlpModularCms.Api.csproj` (SDK, `TargetFramework`, `Nullable`, `ImplicitUsings`, same package references: `Asp.Versioning.Mvc`, `Microsoft.AspNetCore.Authentication.JwtBearer`, `Microsoft.AspNetCore.OpenApi`, `Microsoft.EntityFrameworkCore.Design`, `Scalar.AspNetCore`), **without** the `WebsitePlaceholder.html` embedded resource (that now lives in `Core`, shared) and **without** a `Modules.Offerings` reference (added later by Unit 2, per unit-of-work.md)
|
||||
- `ProjectReference`: `SlpModularCms.Core`, `SlpModularCms.Modules.Availability`, `SlpModularCms.Modules.Identity`, `SlpModularCms.Modules.Master` (FR-2)
|
||||
- [x] Create `src/SlpModularCms.Api.SlpSoftware/Program.cs` — same thin shape as Step 4's rewritten `Api/Program.cs`
|
||||
- [x] Create `src/SlpModularCms.Api.SlpSoftware/appsettings.json` — mirrors `Api`'s structure/keys (connection string placeholder, JWT settings, Availability, MasterModule, MasterPolling, Cors, RateLimiting, SecurityHeaders, Observability sections)
|
||||
- [x] Create `src/SlpModularCms.Api.SlpSoftware/appsettings.Development.json` — mirrors `Api`'s Development file, but with its **own isolated local dev database name** (Infrastructure Design Q1 = B), e.g. `Database=SlpModularCmsSlpSoftwareDev`
|
||||
- [x] **Not creating** `appsettings.local.json` — it's git-ignored (verified: listed in `.gitignore`, not tracked in git) and personal per-developer; the developer creates their own copy locally if needed, same as for `Api`
|
||||
- [x] **Not creating** `Program.Coverage.cs` for this project in this unit — Infrastructure/NFR Design (Q1 = C) scoped the new pipeline regression tests to `Api` only, so there is no test target requiring `Program` to be a public partial class here yet; add it in a future unit/feature if `Api.SlpSoftware`-specific pipeline tests are ever introduced
|
||||
|
||||
### Step 6 — Solution File Updates (`SlpModularCms.sln`)
|
||||
- [x] Add `SlpModularCms.Api.SlpSoftware` project entry, nested under the existing (currently empty) `Clients` solution folder (`{D72703E6-B021-4360-B1EE-0E99999B5899}`)
|
||||
- [x] Add `SlpModularCms.Api.Tests` project entry (Step 7), nested directly under `Tests` (`{2F43D186-C7D5-4AB1-B821-4D595CA2ECB3}`) — mirroring how `SlpModularCms.Core.Tests` is nested directly under `Tests`, not under `Tests/Modules`
|
||||
- [x] Add both new projects' GUIDs to `ProjectConfigurationPlatforms` (Debug/Release × Any CPU/x64/x86, matching the existing pattern for every other project)
|
||||
- [x] Add both new projects' GUIDs to `NestedProjects`
|
||||
|
||||
### Step 7 — Pipeline Regression Tests (Business Logic Unit Testing, NFR-CS-01)
|
||||
- [x] Create `src/SlpModularCms.Api.Tests/SlpModularCms.Api.Tests.csproj` — same SDK-style shape as `SlpModularCms.Core.Tests.csproj` (xunit, FluentAssertions, `Microsoft.NET.Test.Sdk`, `coverlet.collector`), plus `Microsoft.AspNetCore.Mvc.Testing` (provides `WebApplicationFactory<TEntryPoint>`); `ProjectReference` to `SlpModularCms.Api.csproj` (its `Program.Coverage.cs` already makes `Program` a public partial class, so no further change needed there)
|
||||
- [x] Create `src/SlpModularCms.Api.Tests/PipelineTests.cs` using `WebApplicationFactory<Program>`, asserting (NFR-CS-01 / NFR Design Pattern 1):
|
||||
- Required security headers (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy) present on a representative response
|
||||
- `/health` returns a successful response
|
||||
- A non-file `/admin/{path}` route resolves to the admin SPA's `index.html` fallback (or, absent a built SPA in the test environment, at minimum does not 404 as a missing-file/static-asset request would)
|
||||
- A burst of requests against a rate-limited route eventually receives `429 Too Many Requests`
|
||||
|
||||
### Step 8 — Business Logic Summary
|
||||
- [x] Create `aidlc-docs/features/slpsoftware-api/construction/slpsoftware-client-setup/code/summary.md` (markdown only) summarizing: files moved (Step 1), `CmsHost`/`CmsHostOptions` added, `Api/Program.cs` rewritten, `Api.SlpSoftware` created, solution file changes, tests added
|
||||
|
||||
### Step 9 — API Layer / Repository Layer Generation
|
||||
- [x] **N/A** — this unit introduces no new HTTP endpoints or persisted entities (FR-1/FR-2/FR-3 are pure composition/project-scaffolding); these categories apply to Unit 2 "Offerings" instead
|
||||
|
||||
### Step 10 — Database Migration Scripts
|
||||
- [x] **N/A** — no new data model in this unit
|
||||
|
||||
### Step 11 — Documentation Generation
|
||||
- [x] Update root `README.md`: add `SlpModularCms.Api.SlpSoftware` to the project-structure description alongside `SlpModularCms.Api`/`SlpModularCms.Api.Slave`, and add a short note under the existing architecture/hosting section explaining `CmsHost` as the shared composition point both Client projects call, plus a one-line note that `Api.SlpSoftware` uses its own local dev database (Infrastructure Design Q1 = B)
|
||||
|
||||
### Step 12 — Deployment Artifacts Generation
|
||||
- [x] **N/A for this Construction stage** — per Infrastructure Design, no `.gitea/workflows/*.yaml` or Gitea Actions variable changes happen here; the CI/CD retarget is explicitly Operations-phase work (D-7/D-15)
|
||||
|
||||
### Step 13 — Build and Test Verification (automatic, Step 13.5 of the workflow)
|
||||
- [x] Build the full solution (or at minimum `Api`, `Api.SlpSoftware`, `Core`, `Api.Tests`) and confirm it compiles
|
||||
- [x] Run `SlpModularCms.Api.Tests` (new) and confirm all pipeline regression tests pass against `Api`
|
||||
- [x] Run `SlpModularCms.Core.Tests` (existing) and confirm nothing regressed from the Step 1 file move
|
||||
- [x] Fix and retry on any failure; only surface to the user if a fix requires a decision only they can make
|
||||
|
||||
---
|
||||
|
||||
**Scope reminder**: this plan implements Unit 1 only. Unit 2 "Offerings" (all 12 user stories) is a separate Code Generation pass, after this unit is approved and its own Build and Test step is green.
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Infrastructure Design Plan — Unit: SlpSoftware Client Setup
|
||||
|
||||
Voordat ik vragen stelde, heb ik `aidlc-docs/features/gitea-deployment-workflow/operations/deployment/deployment-instructions.md` (de bestaande, gedetailleerde deploy-documentatie van de feature die de huidige pipeline bezit) volledig gelezen. Dat geeft keihard bewijs voor bijna elke categorie hieronder — vandaar dat er maar één echte vraag overblijft.
|
||||
|
||||
## Al opgelost via bestaand bewijs (geen vraag nodig)
|
||||
|
||||
- **Deployment Environment / Compute**: één Raspberry Pi ("pi-main"), test en productie gescheiden per directory/systemd-unit/poort (5100/5101). Een aparte proxy-Pi regelt TLS-terminatie. Dit verandert niet door deze feature — D-15 is een **cutover**, geen nieuwe, aparte deploy-slot. Zodra de Operations-fase de pipeline omzet, draait `Api.SlpSoftware` **op precies dezelfde plek** als `Api` nu draait: zelfde Pi, zelfde systemd-unitnamen (`slpsoftware-test.service`/`slpsoftware-production.service`), zelfde poorten, zelfde domeinen. Het enige wat verandert is de ExecStart-regel (`SlpModularCms.Api.dll` → `SlpModularCms.Api.SlpSoftware.dll`) en het CI-publish-artefact — en dat is expliciet Operations-werk (D-7), niet iets wat deze Construction-stage of deze unit's Code Generation al hoeft aan te passen.
|
||||
- **Networking**: geen nginx-wijziging nodig (al vastgelegd als D-6/NFR-1) — de bestaande proxy-Pi-configuratie blijft ongewijzigd, hij proxied gewoon naar dezelfde poort, ongeacht welke `.dll` daar luistert.
|
||||
- **Storage (productie/test)**: zelfde MariaDB-instantie op pi-main, zelfde databasenamen (`SlpSoftwareTest`/`SlpSoftwareProduction`) — logisch gevolg van "cutover, geen nieuwe aparte app".
|
||||
- **Monitoring**: zelfde Sentry-project/DSN, onderscheiden via de bestaande `Observability__Environment`-tag — geen wijziging nodig.
|
||||
- **Shared Infrastructure/multi-tenancy**: N/A — single-tenant deployment, geen wijziging.
|
||||
- **Geen wijzigingen aan `.gitea/workflows/*.yaml` of Gitea Actions-variabelen in deze stage** — dat is expliciet Operations-fase-werk (D-7/D-15). Deze stage documenteert alleen de doelvorm, zodat Code Generation niets bouwt wat daar niet in past.
|
||||
|
||||
## Uitvoeringschecklist
|
||||
|
||||
- [x] Stap A — `infrastructure-design.md`: bovenstaande bevindingen + antwoord op Vraag 1 vastleggen
|
||||
- [x] Stap B — `deployment-architecture.md`: doelarchitectuur voor `Api.SlpSoftware` na de toekomstige cutover (referentie, geen wijziging nu)
|
||||
|
||||
---
|
||||
|
||||
## Vragen
|
||||
|
||||
### Vraag 1 — Lokale ontwikkeldatabase voor `Api.SlpSoftware`
|
||||
Voor productie/test is de databasekeuze al duidelijk (hierboven). Voor **lokale ontwikkeling** (jouw eigen machine) is dat nog niet vastgelegd: moet `Api.SlpSoftware` lokaal dezelfde database gebruiken als `Api` vandaag, of een eigen, aparte lokale database?
|
||||
|
||||
A) Dezelfde lokale database als `Api` — handig als je makkelijk wilt wisselen tussen beide projecten met dezelfde testdata; risico op onderlinge beïnvloeding tijdens ontwikkeling van de Offerings-module (Unit 2)
|
||||
B) Eigen, aparte lokale database voor `Api.SlpSoftware` — geïsoleerde ontwikkelomgeving, geen kans dat het testen van de Offerings-module `Api`'s lokale data raakt; wel een aparte lokale database aanmaken
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# NFR Design Plan — Unit: SlpSoftware Client Setup
|
||||
|
||||
**Categorieën die niet van toepassing zijn (met onderbouwing, niet zomaar overgeslagen)**:
|
||||
- **Scalability Patterns**: N/A — deze unit voegt geen belasting toe, ze hercomponeert bestaande middleware. Geen nieuwe schaal-grenzen.
|
||||
- **Performance Patterns**: N/A — zelfde reden; geen nieuwe latency/throughput-doelen, alleen reproductie van bestaand gedrag.
|
||||
- **Security Patterns**: al besloten in NFR Requirements (NFR-CS-03) — "identiek gedrag, geverifieerd door de nieuwe regressietests" is het patroon; er is geen los ontwerp nodig bovenop wat NFR-CS-01 al vastlegt.
|
||||
|
||||
## Uitvoeringschecklist
|
||||
|
||||
- [x] Stap A — `nfr-design-patterns.md`: testpatroon en observability-patroon vastleggen
|
||||
- [x] Stap B — `logical-components.md`: de nieuwe testproject-structuur en `CmsHostOptions` als logische componenten beschrijven
|
||||
|
||||
---
|
||||
|
||||
## Vragen
|
||||
|
||||
### Vraag 1 — Tegen welk(e) project(en) draaien de nieuwe pipeline-tests?
|
||||
NFR-CS-01 vereist nieuwe `WebApplicationFactory`-gebaseerde tests die het pipeline-gedrag verifiëren. Ze kunnen tegen `Api`, tegen `Api.SlpSoftware`, of tegen beide draaien.
|
||||
|
||||
A) Tegen beide Client-projecten — één gedeelde/geparametriseerde testsuite die tegen zowel `Api` als `Api.SlpSoftware` draait; sterkste garantie dat `CmsHost` zich op beide identiek gedraagt, iets meer testtijd
|
||||
B) Alleen tegen `Api.SlpSoftware` — het project waar het écht om gaat (toekomstige productie-host); `Api` blijft ongetest op pipeline-niveau maar heeft z'n bestaande (unit-niveau) testsuite nog
|
||||
C) Alleen tegen `Api` — bestaat al, sneller op te zetten; `Api.SlpSoftware` erft het vertrouwen via de gedeelde `CmsHost`-code
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: C
|
||||
|
||||
### Vraag 2 — Inhoud van `CmsHostOptions`
|
||||
Tech-stack-decisions.md liet de exacte vorm van `CmsHostOptions` open. Voor het NFR-ontwerp: moet de klasse nu al één concreet, direct nuttig veld krijgen, of blijft het een lege plaatshouder?
|
||||
|
||||
A) Eén concreet veld nu: bijv. `HostLabel`/`ApplicationName` (string) — gebruikt voor observability-tagging (logs/Sentry), zodat je straks in gedeelde logging kunt onderscheiden of een entry van `Api` of `Api.SlpSoftware` komt. Direct nuttig, geen giswerk over toekomstige velden.
|
||||
B) Volledig lege plaatshouderklasse — puur een uitbreidingspunt zonder velden, tot er een concrete behoefte is
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: B
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
# NFR Requirements Plan — Unit: SlpSoftware Client Setup
|
||||
|
||||
**Waarom Functional Design is overgeslagen voor deze unit**: geen nieuw datamodel, geen nieuwe business rules — dit is een pure hosting-compositie-extractie (FR-1/FR-2/FR-3), zonder domeinlogica om te ontwerpen. Rechtstreeks door naar NFR Requirements.
|
||||
|
||||
**Al vastgelegd, hier niet opnieuw bevraagd**:
|
||||
- **Security**: deze unit introduceert geen nieuw aanvalsoppervlak (geen nieuwe module, geen nieuwe business logic) — het enige vereiste is dat de bestaande Security Baseline-regels (SECURITY-03/04/09/10/14/15) na de extractie **exact** hetzelfde gedrag opleveren als vandaag. Zie Vraag 1 hieronder voor hoe dat geverifieerd wordt.
|
||||
- **Database-scheiding**: `Api` en `Api.SlpSoftware` gebruiken elk hun eigen `appsettings.json`/`appsettings.local.json` (bestaand `dotnet-appsettings`-patroon, ook al toegepast tussen `Api` en `Api.Slave`) — dus per omgeving een eigen connection string. Geen wijziging t.o.v. vandaag, geen vraag nodig.
|
||||
|
||||
## Uitvoeringschecklist
|
||||
|
||||
- [x] Stap A — `nfr-requirements.md`: NFR's voor deze unit vastleggen (reliability/testability, maintainability)
|
||||
- [x] Stap B — `tech-stack-decisions.md`: bevestigen dat geen nieuwe technologie nodig is; vastleggen of `CmsHost` parameterloos blijft
|
||||
|
||||
---
|
||||
|
||||
## Vragen
|
||||
|
||||
### Vraag 1 — Regressietest-strengheid voor de `CmsHost`-extractie
|
||||
`Api/Program.cs` bevat vandaag gedrag dat niet mag veranderen: security headers, rate limiting, health checks, static content + SPA-fallback, Sentry-tunnel. Hoe streng moet geverifieerd worden dat `CmsHost` dat gedrag exact reproduceert?
|
||||
|
||||
A) Vertrouwen op `Api`'s bestaande testsuite die ongewijzigd groen blijft — voldoende signaal, geen nieuwe tests specifiek voor deze extractie
|
||||
B) Nieuwe integratietests toevoegen die specifiek het pipeline-gedrag assert (headerwaarden aanwezig, health-endpoint bereikbaar, SPA-fallback lost op) — blijvende regressiebewaking voor beide Client-projecten, ook na deze feature
|
||||
C) Alleen handmatige smoke-test (beide apps lokaal draaien, responses vergelijken), geen nieuwe geautomatiseerde tests
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: A, als dit voldoende dekking geeft, anders B
|
||||
|
||||
### Vraag 2 — Uitbreidbaarheid van `CmsHost`
|
||||
`CmsHost.ConfigureServices`/`ConfigurePipeline` (application-design/component-methods.md) hebben vandaag geen parameters buiten `WebApplicationBuilder`/`WebApplication` — beide Client-projecten roepen ze identiek aan.
|
||||
|
||||
Moet er nu al ruimte komen voor toekomstige verschillen tussen projecten (bijv. een `CmsHostOptions`-object), of pas toevoegen zodra er een echte reden voor is?
|
||||
|
||||
A) Parameterloos houden voor nu (YAGNI) — pas een parameter toevoegen zodra `Api` en `Api.SlpSoftware` daadwerkelijk moeten verschillen
|
||||
B) Nu al een klein `CmsHostOptions`-object toevoegen, ook al geven beide aanroepen vandaag identieke waarden door
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: B
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# Code Generation Summary — Unit: SlpSoftware Client Setup
|
||||
|
||||
## Moved (brownfield relocation, Step 1)
|
||||
- `src/SlpModularCms.Api/Extensions/StaticContentExtensions.cs` → `src/SlpModularCms.Core/Hosting/StaticContentExtensions.cs` (namespace `SlpModularCms.Api.Extensions` → `SlpModularCms.Core.Hosting`; embedded-resource name updated to match)
|
||||
- `src/SlpModularCms.Api/Extensions/WebsitePlaceholder.html` → `src/SlpModularCms.Core/Hosting/WebsitePlaceholder.html`
|
||||
- Deleted the now-empty `src/SlpModularCms.Api/Extensions/` directory
|
||||
|
||||
## Created
|
||||
- `src/SlpModularCms.Core/Hosting/CmsHostOptions.cs` — empty extension-point class (NFR Design Pattern 2)
|
||||
- `src/SlpModularCms.Core/Hosting/CmsHost.cs` — `ConfigureServices`/`ConfigurePipeline`, the shared composition both Client projects call
|
||||
- `src/SlpModularCms.Api.SlpSoftware/` — new Client project: `.csproj`, `Program.cs`, `appsettings.json`, `appsettings.Development.json` (own isolated local dev database, Infrastructure Design Q1 = B), `Properties/launchSettings.json` (ports 5286/7222, distinct from `Api`'s 5284/7221 so both can run simultaneously)
|
||||
- `src/SlpModularCms.Api.Tests/` — new test project: `.csproj`, `PipelineTests.cs` (4 tests: `/health` reachable, required security headers present on an HTML response with HSTS's Development-only absence explicitly asserted, `/admin` fallback 404s correctly with no build present, the `sentry-tunnel` rate limiter engages past its configured permit limit)
|
||||
|
||||
## Modified
|
||||
- `src/SlpModularCms.Api/Program.cs` — rewritten to the thin form calling `CmsHost.ConfigureServices`/`ConfigurePipeline`; no behavior change
|
||||
- `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — removed the `WebsitePlaceholder.html` embedded-resource item (moved to `Core`)
|
||||
- `src/SlpModularCms.Core/SlpModularCms.Core.csproj` — added the `WebsitePlaceholder.html` embedded-resource item
|
||||
- `SlpModularCms.sln` — added `SlpModularCms.Api.SlpSoftware` (nested under `Clients`) and `SlpModularCms.Api.Tests` (nested directly under `Tests`, mirroring `Core.Tests`'s placement)
|
||||
- `README.md` — documented the new project, `CmsHost`, and the local dev database isolation
|
||||
|
||||
## Not Created (explicit N/A, per the plan)
|
||||
- No `Program.Coverage.cs` for `Api.SlpSoftware` — no pipeline tests target it in this unit (Q1 = C)
|
||||
- No `appsettings.local.json` for `Api.SlpSoftware` — git-ignored, personal-per-developer, created locally when needed
|
||||
- No API/Repository layer code, no database migrations — this unit has neither (Unit 2 "Offerings" does)
|
||||
- No `.gitea/workflows/*.yaml` or Gitea Actions variable changes — Operations-phase scope (D-7/D-15)
|
||||
|
||||
## Post-Completion Fix: Dev Port Collision + Frontend Dev Script
|
||||
|
||||
Prompted by a user question about a frontend `pnpm dev:slpsoftware` script, investigation surfaced a real bug: `Api.SlpSoftware`'s `launchSettings.json` had been assigned HTTPS port **7222**, which is already `SlpModularCms.Api.Slave`'s port (confirmed via `frontend/.env.example` and `Api.Slave/Properties/launchSettings.json`). Fixed:
|
||||
- `src/SlpModularCms.Api.SlpSoftware/Properties/launchSettings.json` — HTTPS port 7222 → **7223** (HTTP 5286 unchanged, already free)
|
||||
- `src/SlpModularCms.Api.SlpSoftware/appsettings.Development.json` — `MasterModule:MasterUrl` updated to match (`https://localhost:7223`)
|
||||
- `README.md` — corrected port note, with an explicit call-out of the 7222 collision reason
|
||||
|
||||
Added (frontend, not part of the original per-unit plan, but a direct, symmetrical follow-up to the port fix):
|
||||
- `frontend/package.json` — `dev:slpsoftware` script (`vite --mode slpsoftware --port 5175`), mirroring `dev:slave`
|
||||
- `frontend/.env.example` — documented the `.env.slpsoftware.local` pattern (`VITE_API_BASE_URL=https://localhost:7223`, `VITE_APP_TITLE=SlpModularCms (SlpSoftware)`)
|
||||
|
||||
## Post-Completion Fix: CORS Origin Mismatch Blocked the Setup Page
|
||||
|
||||
`Api.SlpSoftware/appsettings.Development.json`'s `Cors:AllowedOrigins` had been copied from `Api`'s own file verbatim, listing `localhost:5173` — but `pnpm dev:slpsoftware` serves the frontend on port **5175**. The mismatch caused the frontend's `/api/v1/Setup/status` check to fail on CORS, and `router.tsx`'s `InitGuard` fails open toward the login page (not setup) on any fetch error — so the setup page never appeared, even against a genuinely empty database (verified directly via the local MariaDB, not assumed). Fixed: `Cors:AllowedOrigins` now lists `localhost:5175`.
|
||||
|
||||
## Post-Completion Fix: GlobalExceptionHandler Logged Expected 401s as Errors
|
||||
|
||||
Pre-existing bug, unrelated to this feature's own scope (not introduced by the `CmsHost` extraction) but surfaced by testing the new instance: `src/SlpModularCms.Core/Exceptions/GlobalExceptionHandler.cs` logged every exception at `LogError` before mapping it to a status code, so a routine `UnauthorizedException` (e.g. an invalid/missing refresh token — the normal state for a fresh, never-logged-in session) was indistinguishable from a genuine unhandled 500 in the console and in Sentry. Fixed: exceptions that map to a status below 500 now log at `LogWarning`; only genuinely unmapped exceptions (500) log at `LogError`. Affects every Client project equally (`Api`, `Api.Slave`, `Api.SlpSoftware`), not just this feature. No existing test asserted log level; full suite reran green (196 + 4).
|
||||
|
||||
## Build and Test Verification (Step 13.5)
|
||||
|
||||
Two real build fixes were needed and applied during this step (not deviations from the plan — the plan didn't anticipate these, since they only surface once the code actually compiles):
|
||||
1. `SlpModularCms.Core.csproj` was missing a `Scalar.AspNetCore` package reference — `CmsHost.ConfigurePipeline` calls `MapScalarApiReference()`, which moved into `Core` along with the rest of the pipeline composition but the package reference hadn't followed it. Added.
|
||||
2. `CmsHost.cs` and the moved `StaticContentExtensions.cs` were missing explicit `using Microsoft.Extensions.Hosting;` / `using Microsoft.Extensions.DependencyInjection;` — `Core` is an `Microsoft.NET.Sdk` project (not `Sdk.Web`), so it does not get ASP.NET Core's implicit usings the way `Api` (`Sdk.Web`) did. Added both.
|
||||
|
||||
After both fixes:
|
||||
- **Build**: ✅ Success (full solution, all 13 projects)
|
||||
- **`SlpModularCms.Core.Tests`**: ✅ 196 passed, 0 failed — no regression from the Step 1 file move
|
||||
- **`SlpModularCms.Api.Tests`** (new): ✅ 4 passed, 0 failed — `/health` reachable, required security headers present on an HTML response (with HSTS's Development-only absence correctly asserted, not overlooked), `/admin` fallback 404s correctly with no build present, the `sentry-tunnel` rate limiter engages past its configured permit limit
|
||||
|
||||
**Local environment note**: running the new tests required a local MariaDB (the same one `Api` itself needs to run, per the README). It was stopped; started it via `podman machine start` + `podman start mariadb` for this verification, and left both running afterward for continued local development (including Unit 2 "Offerings").
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# Deployment Architecture — Unit: SlpSoftware Client Setup
|
||||
|
||||
**Status note**: this diagram documents the **target state after** the Operations-phase cutover (D-15) — it is reference/planning context for Code Generation, not something this unit deploys itself. No infrastructure changes happen as part of this Construction stage.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
visitor["Site Visitor / CMS Administrator browser"]
|
||||
proxy["Proxy Pi<br/>nginx + TLS (certbot)"]
|
||||
pimain["pi-main<br/>systemd --user"]
|
||||
svc_test["slpsoftware-test.service<br/>port 5100"]
|
||||
svc_prod["slpsoftware-production.service<br/>port 5101"]
|
||||
dll["SlpModularCms.Api.SlpSoftware.dll<br/>(replaces Api.dll at cutover)"]
|
||||
db["MariaDB<br/>SlpSoftwareTest / SlpSoftwareProduction"]
|
||||
sentry["Sentry (shared project)"]
|
||||
|
||||
visitor -->|"HTTPS"| proxy
|
||||
proxy -->|"proxy_pass, plain HTTP over LAN"| pimain
|
||||
pimain --> svc_test
|
||||
pimain --> svc_prod
|
||||
svc_test --> dll
|
||||
svc_prod --> dll
|
||||
dll -->|"EF Core / MariaDB provider"| db
|
||||
dll -->|"error/event reporting"| sentry
|
||||
|
||||
classDef external fill:#e9d8fd,stroke:#553c9a,stroke-width:2px,color:#000000,font-weight:bold;
|
||||
classDef proxy fill:#bee3f8,stroke:#2b6cb0,stroke-width:2px,color:#000000,font-weight:bold;
|
||||
classDef compute fill:#fefcbf,stroke:#b7791f,stroke-width:2px,color:#000000,font-weight:bold;
|
||||
classDef data fill:#c6f6d5,stroke:#2f855a,stroke-width:2px,color:#000000,font-weight:bold;
|
||||
|
||||
class visitor external;
|
||||
class proxy proxy;
|
||||
class pimain,svc_test,svc_prod,dll compute;
|
||||
class db,sentry data;
|
||||
|
||||
linkStyle default stroke:#666666,stroke-width:2px;
|
||||
```
|
||||
|
||||
Text alternative: a visitor's browser reaches the proxy Pi over HTTPS, which forwards plain HTTP over the LAN to pi-main; pi-main runs two systemd-managed instances (test on port 5100, production on port 5101), both eventually running `SlpModularCms.Api.SlpSoftware.dll` after the cutover, each talking to its own MariaDB database and the shared Sentry project (purple = external actor, blue = the TLS-terminating proxy, yellow = compute/hosting, green = data/observability backends).
|
||||
|
||||
## Local Development (Current Scope of This Unit)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
dev["Developer machine"]
|
||||
api["SlpModularCms.Api<br/>(existing dev host)"]
|
||||
apislp["SlpModularCms.Api.SlpSoftware<br/>(new, this unit)"]
|
||||
dbA["Local DB: Api"]
|
||||
dbB["Local DB: Api.SlpSoftware (isolated)"]
|
||||
|
||||
dev --> api
|
||||
dev --> apislp
|
||||
api --> dbA
|
||||
apislp --> dbB
|
||||
|
||||
classDef dev fill:#fefcbf,stroke:#b7791f,stroke-width:2px,color:#000000,font-weight:bold;
|
||||
classDef data fill:#c6f6d5,stroke:#2f855a,stroke-width:2px,color:#000000,font-weight:bold;
|
||||
|
||||
class dev,api,apislp dev;
|
||||
class dbA,dbB data;
|
||||
|
||||
linkStyle default stroke:#666666,stroke-width:2px;
|
||||
```
|
||||
|
||||
Text alternative: locally, `Api` and the new `Api.SlpSoftware` each connect to their own separate, isolated database (decision Q1 = B) — no shared local dev data between the two.
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
# Infrastructure Design — Unit: SlpSoftware Client Setup
|
||||
|
||||
## Production / Test Infrastructure — No Change Now, Documented Target for the Future Cutover
|
||||
|
||||
Sourced directly from `aidlc-docs/features/gitea-deployment-workflow/operations/deployment/deployment-instructions.md` (the authoritative, existing deploy documentation, owned by that feature):
|
||||
|
||||
| Aspect | Current (`Api`) | Target after D-15 cutover (`Api.SlpSoftware`) |
|
||||
|---|---|---|
|
||||
| Host | Single Raspberry Pi ("pi-main") | **Same Pi** — no new host |
|
||||
| TLS termination | Separate "proxy Pi", reverse-proxies to pi-main | **Unchanged** |
|
||||
| Process manager | `systemd --user`, units `slpsoftware-test.service` / `slpsoftware-production.service` | **Same unit names** — only the `ExecStart` line's `.dll` filename changes |
|
||||
| Ports | 5100 (test), 5101 (production) | **Unchanged** |
|
||||
| Database | MariaDB on pi-main, `SlpSoftwareTest` / `SlpSoftwareProduction` | **Unchanged** — same instance, same database names (this is a cutover of which binary runs, not a new environment) |
|
||||
| Monitoring | Shared Sentry project, distinguished by `Observability__Environment` | **Unchanged** |
|
||||
| Reverse proxy config | nginx on the proxy Pi, `proxy_pass` to pi-main's port | **Unchanged** — nginx doesn't know or care which `.dll` is listening (D-6/NFR-1) |
|
||||
|
||||
**What actually changes at cutover time** (Operations phase, D-7/D-15 — explicitly **not** this Construction stage or this unit's Code Generation):
|
||||
- The systemd unit's `ExecStart` line: `SlpModularCms.Api.dll` → `SlpModularCms.Api.SlpSoftware.dll`
|
||||
- The CI/CD pipeline's publish/artifact step, to build and ship `Api.SlpSoftware` instead of `Api`
|
||||
|
||||
**Why this matters for Code Generation (this unit, now)**: `SlpModularCms.Api.SlpSoftware` must be structurally deployable into this exact same shape without modification — same `wwwroot/web` symlink convention, same `/health` endpoint contract, same `ASPNETCORE_URLS=http://0.0.0.0:<port>` binding pattern, same environment-file-based configuration approach. This is already satisfied by design, since `Api.SlpSoftware` consumes the same `CmsHost` composition as `Api` (Unit 1's own FR-3) — there is nothing extra to build here, only something to avoid accidentally breaking.
|
||||
|
||||
## No Changes to CI/CD or Gitea Actions Configuration in This Stage
|
||||
|
||||
`.gitea/workflows/continuous_integration.yaml`, `deploy-scp.yaml`, and the Gitea Actions variables/secrets listed in deployment-instructions.md § 1.9 are **not** touched by this unit or this Construction phase. That retarget is explicitly Operations-phase scope (D-7, D-15) and belongs to a later stage of this feature, coordinated with the `gitea-deployment-workflow` feature that owns those files.
|
||||
|
||||
## Local Development Database (Decision Q1 = B)
|
||||
|
||||
`Api.SlpSoftware` uses its **own, isolated local development database**, separate from `Api`'s local dev database. Rationale: developing and testing the Offerings module (Unit 2) against `Api.SlpSoftware` should not risk touching or corrupting whatever local data `Api` already has, and vice versa.
|
||||
|
||||
**Implication for Code Generation**: `Api.SlpSoftware`'s `appsettings.Development.json` gets its own `ConnectionStrings:DefaultConnection` pointing at a distinct local database name (e.g. a `SlpModularCmsSlpSoftwareDev`-style name — exact naming is a Code Generation Planning detail, not decided here), following the same `dotnet-appsettings` pattern already used by `Api`.
|
||||
|
||||
## Shared Infrastructure
|
||||
|
||||
None applicable — single-tenant deployment (per deployment-instructions.md's explicit note that a future second customer would get an entirely separate instance, not shared infrastructure within this one).
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Logical Components — Unit: SlpSoftware Client Setup
|
||||
|
||||
## Component: Pipeline Regression Test Suite
|
||||
|
||||
**Type**: Test project / test fixture (new logical component, no runtime footprint in production).
|
||||
|
||||
**Scope**: Targets `SlpModularCms.Api` only (Q1 = C). Exact host project (new `SlpModularCms.Api.Tests` vs. extending `SlpModularCms.Core.Tests`) is a Code Generation Planning decision — this stage fixes *what* it tests and *against which project*, not its exact file location.
|
||||
|
||||
**Integration pattern**: `Microsoft.AspNetCore.Mvc.Testing`'s `WebApplicationFactory<TEntryPoint>`, using `Api`'s own `Program.cs` as the entry point (requires `Api`'s `Program.cs` to be accessible to the test project via the standard `InternalsVisibleTo`/top-level-statement partial-class pattern, if not already the case for other reasons).
|
||||
|
||||
## Component: `CmsHostOptions`
|
||||
|
||||
**Type**: Plain options object (new), living in `SlpModularCms.Core.Hosting` alongside `CmsHost`.
|
||||
|
||||
**Shape at this stage**: empty (Q2 = B) — see nfr-design-patterns.md Pattern 2.
|
||||
|
||||
**Consumers**: `SlpModularCms.Api/Program.cs` and `SlpModularCms.Api.SlpSoftware/Program.cs`, both constructing a default instance.
|
||||
|
||||
## No Other New Logical Components
|
||||
|
||||
This unit does not introduce queues, caches, circuit breakers, or any other infrastructure component — it is a composition/extraction of existing pieces plus the one new options object above.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# NFR Design Patterns — Unit: SlpSoftware Client Setup
|
||||
|
||||
## Pattern 1: Pipeline Regression Test, Scoped to `Api` (NFR-CS-01)
|
||||
|
||||
**Decision** (Q1 = C): the new `WebApplicationFactory`-based integration tests target **`SlpModularCms.Api` only**. `SlpModularCms.Api.SlpSoftware` is not separately pipeline-tested in this unit — it inherits confidence transitively through the shared `CmsHost.ConfigureServices`/`ConfigurePipeline` code path that both projects call identically.
|
||||
|
||||
**Pattern**: a single `WebApplicationFactory<TEntryPoint>`-based test fixture, pointed at `Api`, asserting on real HTTP responses:
|
||||
- Security headers present (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy) — CSP presence and required directives only, not the exact Umami/Sentry exceptions (those are `Api`-specific configuration, unrelated to `CmsHost` correctness).
|
||||
- `/health` returns success.
|
||||
- A non-file `/admin/*` route resolves to the admin SPA's `index.html`.
|
||||
- A burst of requests against a rate-limited route eventually receives a `429`.
|
||||
|
||||
**Accepted trade-off**: if a future change to `CmsHost` behaves differently under `Api.SlpSoftware`'s specific module composition (Offerings module present) than under `Api`'s, this test suite alone would not catch it. This is an accepted gap per the user's decision, not an oversight — full end-to-end coverage of `Api.SlpSoftware` itself is covered later by the feature-wide Build and Test phase once Unit 2 exists.
|
||||
|
||||
## Pattern 2: `CmsHostOptions` as an Empty Extension Point (NFR-CS-02)
|
||||
|
||||
**Decision** (Q2 = B): `CmsHostOptions` is introduced as a genuinely empty class (or, in C# terms, a class with zero properties, or `internal sealed record CmsHostOptions;` — exact syntax decided at Code Generation) — a placeholder in the method signature, not a placeholder-with-a-guess-field.
|
||||
|
||||
**Pattern**: standard **Options Object** pattern, sized for its current job (nothing) rather than a speculative future job. Both `Api` and `Api.SlpSoftware` construct `new CmsHostOptions()` and pass it to `CmsHost.ConfigureServices(builder, options)` / `ConfigurePipeline(app, orchestrator, options)`. When a real per-project difference appears later, a property is added to this one class rather than changing either method's signature again.
|
||||
|
||||
**Constraint carried forward** (from NFR-CS-02 / tech-stack-decisions.md): whatever is eventually added to `CmsHostOptions`, it must never affect `DataProtectionExtensions.ApplicationDiscriminator` — that stays the hardcoded `"SlpModularCms"` constant regardless.
|
||||
|
||||
## Pattern 3: Security — Verification, Not New Design (NFR-CS-03)
|
||||
|
||||
No new security pattern is introduced by this unit. The applicable pattern is "prove equivalence," fully covered by Pattern 1's regression tests. No additional logical components (WAF, extra middleware, etc.) are needed.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# NFR Requirements — Unit: SlpSoftware Client Setup
|
||||
|
||||
## NFR-CS-01 — Pipeline-Level Regression Test Coverage (Reliability / Testability)
|
||||
|
||||
**Requirement**: The `CmsHost` extraction MUST be verified by new automated integration tests that exercise the actual HTTP pipeline, not unit tests of individual option classes.
|
||||
|
||||
**Rationale**: Investigated the existing test suite before deciding (per the user's conditional answer to Q1 — "A if sufficient coverage exists, otherwise B"). Found:
|
||||
- Zero usages of `WebApplicationFactory` anywhere in the solution.
|
||||
- `SlpModularCms.Core.Tests/Hosting/*` tests (`SecurityHeadersOptionsValidationTests`, `PathPolicyResolverTests`, `DeployedConfigurationTests`, `SecurityEventsTests`) are unit-level — they test option/handler classes in isolation, not the composed pipeline.
|
||||
- No dedicated `Api.Tests` project exists.
|
||||
|
||||
Existing coverage does **not** verify that security headers are actually present on a real response, that `/health` is reachable, that the SPA fallback resolves `/admin/*` routes, or that the rate limiter engages — all pipeline-composition behavior that `CmsHost` must reproduce exactly from today's `Api/Program.cs`. Per the decision rule in the user's own answer, this resolves to **Option B**.
|
||||
|
||||
**Scope for Code Generation**: New `WebApplicationFactory`-based integration tests asserting, at minimum:
|
||||
- Required security headers (CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy) are present on a representative response.
|
||||
- `/health` returns a successful liveness response.
|
||||
- A non-file `/admin/*` route resolves to the admin SPA's `index.html` (SPA fallback).
|
||||
- The rate limiter is active (a burst of requests eventually receives a rate-limited response).
|
||||
|
||||
These tests become a **permanent regression guard** for both `Api` and `Api.SlpSoftware`, not a one-time check discarded after this feature.
|
||||
|
||||
## NFR-CS-02 — `CmsHost` Extensibility (Maintainability)
|
||||
|
||||
**Requirement**: `CmsHost.ConfigureServices`/`ConfigurePipeline` accept a `CmsHostOptions` parameter, even though both current call sites (`Api`, `Api.SlpSoftware`) will pass equivalent values today.
|
||||
|
||||
**Rationale**: Decision Q2 = B — proactive extensibility preferred over strict YAGNI for this specific extraction point, since it's the one place both current and future Client projects funnel through.
|
||||
|
||||
**Constraint found during investigation**: `SlpModularCms.Core.Hosting.DataProtectionExtensions.ApplicationDiscriminator` is a deliberate hardcoded constant (`"SlpModularCms"`), with an explicit existing code comment stating it must never become configurable — a mismatched discriminator between instances silently breaks stored Data Protection values (see NFR-CS-02 note in `tech-stack-decisions.md`). **`CmsHostOptions` MUST NOT expose anything that changes the Data Protection application name.** This constraint carries forward into Code Generation Planning for this unit.
|
||||
|
||||
## NFR-CS-03 — No New Security Surface (Security Baseline continuity)
|
||||
|
||||
**Requirement**: This unit introduces no new attack surface — no new module, no new business logic, no new endpoints. Its only Security Baseline obligation is that NFR-CS-01's regression tests actually prove the existing security posture (headers, rate limiting, Data Protection, Sentry-based logging) is unchanged after the extraction.
|
||||
|
||||
**Rationale**: Confirmed against requirements.md's Security Compliance table, where every rule applicable to the hosting/pipeline layer (SECURITY-01, 03, 04, 09, 10, 14, 15) is marked "Pre-existing, unchanged" — this unit's job is to keep that true, not to add anything new.
|
||||
|
||||
## Out of Scope for This Unit
|
||||
|
||||
- Database technology/connection changes — none; `Api.SlpSoftware` uses its own environment-specific connection string via the existing `dotnet-appsettings` pattern, same as `Api`/`Api.Slave` today. No new decision needed.
|
||||
- Scalability/performance targets — unchanged from today's `Api`; this unit doesn't add load, it re-composes existing middleware.
|
||||
- Availability/disaster-recovery — unchanged; no new infrastructure introduced by this unit (Infrastructure Design for this unit, next stage, covers whether `Api.SlpSoftware` as a *deployment target* needs anything new).
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# Tech Stack Decisions — Unit: SlpSoftware Client Setup
|
||||
|
||||
## No New Technology Introduced
|
||||
|
||||
This unit re-composes existing infrastructure (ASP.NET Core hosting, the existing `Core.Hosting.*` extension methods, the existing `ModuleOrchestrator`). No new package, framework, or external service is introduced.
|
||||
|
||||
## Decision: `CmsHostOptions` (new, minimal)
|
||||
|
||||
Per NFR-CS-02 (Q2 = B), `CmsHost.ConfigureServices`/`ConfigurePipeline` will accept a `CmsHostOptions` parameter.
|
||||
|
||||
**What it must NOT contain**: anything affecting the Data Protection application discriminator (`DataProtectionExtensions.ApplicationDiscriminator`, hardcoded `"SlpModularCms"`). That constant is deliberately not configurable — an existing code comment explains that a mismatched discriminator between instances silently breaks previously-encrypted values with no visible error. `CmsHostOptions` must not reopen that risk.
|
||||
|
||||
**Exact shape**: left open for Code Generation Planning for this unit — both current call sites (`Api`, `Api.SlpSoftware`) pass equivalent values today, so there is no concrete divergence yet to model. Introducing the parameter now (rather than later) is purely about not having to touch both `Program.cs` call sites' method signatures again the first time a real difference appears.
|
||||
|
||||
## Decision: New Integration Test Project/Location for Pipeline Tests (NFR-CS-01)
|
||||
|
||||
No dedicated `Api.Tests` project exists today (only module/`Core` test projects). For the new `WebApplicationFactory`-based pipeline tests:
|
||||
- Exact project placement (a new shared test project vs. extending `SlpModularCms.Core.Tests`) is a Code Generation Planning decision, not decided here — this is implementation planning, not a technology choice. Either way, `Microsoft.AspNetCore.Mvc.Testing` (the standard `WebApplicationFactory` package) is the technology being introduced; it is Microsoft's own first-party integration-testing package for ASP.NET Core, already implied by the framework version this solution targets — no new external dependency risk.
|
||||
|
||||
## Database
|
||||
|
||||
No change. Both `Api` and `Api.SlpSoftware` connect to MariaDB via `UseMySQL`, using their own environment-specific connection string per the existing `dotnet-appsettings` pattern (already the case for `Api`/`Api.Slave`). No new decision required for this unit.
|
||||
Reference in New Issue
Block a user