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

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:
2026-08-02 01:28:39 +02:00
co-authored by Claude Sonnet 5
parent dcc82cdf62
commit fa389e42ee
51 changed files with 3119 additions and 127 deletions
@@ -0,0 +1,76 @@
# Application Design Plan — SlpSoftware Production API
Dit plan beschrijft hoe de high-level applicatie-ontwerp-artefacten voor deze feature worden opgesteld. Beantwoord eerst de vragen hieronder; na jouw goedkeuring wordt het plan uitgevoerd.
Context die ik al heb geverifieerd in de code (niet aangenomen):
- `ModuleOrchestrator` (`SlpModularCms.Core/Hosting/ModuleOrchestrator.cs`) ontdekt modules **dynamisch** door `.dll`-bestanden op schijf te scannen — er is nergens een expliciete "welke modules host ik"-lijst in `Program.cs`. Dat betekent: welke modules een Client-project host, wordt volledig bepaald door welke Module-projecten dat `.csproj` referenceert, niet door code in `Program.cs` zelf.
- `SlpModularCms.Api/Program.cs` bevat, op de bootstrap-regels na (`WebApplication.CreateBuilder`, `appsettings.local.json`), **geen enkele project-specifieke branch** — alles is generiek/config-gedreven. Dat maakt een verregaande extractie (FR-3) haalbaar.
- `SlpModularCms.Modules.Master` volgt het patroon: `Controllers/``Services/` (`I{X}Service`/`{X}Service`) → `Repositories/` (`I{X}Repository`/`{X}Repository`) → `Data/{X}DbContext.cs`, plus `Models/` voor DTO's/requests en `{Module}Module.cs` (`IModule`-implementatie).
## Uitvoeringschecklist
- [x] Stap A — `components.md`: componenten identificeren (CmsHost-extractie, Offerings-module met sub-componenten) met verantwoordelijkheden
- [x] Stap B — `component-methods.md`: methode-signaturen per component (geen gedetailleerde business rules — dat komt in Functional Design)
- [x] Stap C — `services.md`: servicedefinities en orkestratiepatronen (o.a. featured-exclusiviteit, reorder-logica uit de user stories)
- [x] Stap D — `component-dependency.md`: afhankelijkheidsmatrix + datastroom (Core ↔ Api ↔ Api.SlpSoftware ↔ Offerings)
- [x] Stap E — `application-design.md`: consolidatie van bovenstaande in één document
- [x] Stap F — Consistentiecontrole: komt het ontwerp overeen met requirements.md (FR-1..FR-9) en stories.md (US-01..US-12)?
---
## Vragen
### Vraag 1 — Vorm van de `CmsHost`-extractie (FR-3)
`Api/Program.cs` bevat, buiten de bootstrap-regels, geen project-specifieke logica. Dat maakt twee uitersten mogelijk voor de extractie.
Hoe ver moet de extractie naar `SlpModularCms.Core` gaan?
A) **Eén volledig entrypoint**`CmsHost.RunAsync(string[] args)` bevat de hele samenstelling (services + pipeline + `app.Run()`); beide `Program.cs`-bestanden worden dan letterlijk een paar regels (`return CmsHost.RunAsync(args);` + evt. bootstrap-overrides). Minimaliseert duplicatie/drift maximaal, maar geeft een individueel project weinig ruimte om ooit af te wijken zonder de gedeelde methode te wijzigen.
B) **Twee gedeelde methodes**`CmsHost.ConfigureServices(WebApplicationBuilder)` en `CmsHost.ConfigurePipeline(WebApplication)`, die elk project vanuit zijn eigen dunne `Program.cs` aanroept (zoals vandaag al met `AddCoreInfrastructure` etc. gebeurt, maar dan als één samengestelde aanroep per fase). Iets meer code per project, maar elk project behoudt een zichtbaar `Program.cs` waarin het makkelijk is om ooit één stap toe te voegen/over te slaan zonder `Core` te wijzigen.
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: B
### Vraag 2 — Architectuurpatroon voor de Offerings-module
`Modules.Master` gebruikt een Repository+Service-laag (`ICmsInstanceRepository``ICmsInstanceService` → Controller). Bij Requirements Analysis heb je de Property-Based Testing-extensie overgeslagen met als reden dat dit een eenvoudige CRUD-achtige module is zonder significante bedrijfslogica.
Moet de Offerings-module hetzelfde Repository+Service-patroon volgen (consistent met Master), of is dat voor deze module onnodige indirectie?
A) Repository+Service (consistent met Master) — `IOfferingRepository` + `IOfferingsService`, ook al is de module zelf simpel
B) Alleen Service, geen Repository — `IOfferingsService` praat direct met `OfferingsDbContext` (minder indirectie voor een module die je zelf als eenvoudige CRUD hebt gekarakteriseerd)
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: A
### Vraag 3 — Controllersplitsing publiek vs. admin
Requirements.md scheidt al duidelijk het publieke `GET /api/v1/offerings` (FR-6, anoniem) van de admin-CRUD (FR-7, `AdminOnly`).
Moet dit ook twee aparte controllers worden, of één controller met gemengde autorisatie per actie?
A) Twee controllers — `OfferingsController` (publiek, alleen `GET`) en `OfferingsAdminController` (CRUD + reorder, `AdminOnly`) — duidelijke scheiding, moeilijker om per ongeluk een admin-actie anoniem te laten
B) Eén controller — `OfferingsController` met `[AllowAnonymous]` op de publieke `GET` en `[Authorize(Policy = "AdminOnly")]` op de rest
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: B
### Vraag 4 — Id-strategie voor nieuw aangemaakte offerings
De drie bestaande referentiewaarden (FR-8) gebruiken leesbare slugs (`pakket_01`, `pakket_02`, `pakket_03`). Het publieke contract (FR-6) verwacht een `string`-veld `id`, dus zowel een GUID als een handmatige slug is technisch mogelijk.
Hoe moet de `Id` van een **nieuw** aangemaakte offering tot stand komen?
A) Automatisch gegenereerd (GUID as string) — simpel, geen validatie op uniekheid/formaat nodig, consistent met andere entiteiten in dit systeem (bijv. `CmsInstance.Id`)
B) Door de CMS Administrator zelf opgegeven als leesbare slug — consistent met de bestaande `pakket_XX`-stijl, vereist wel validatie (uniek, toegestane tekens)
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: A
### Vraag 5 — Verwijdersemantiek
Requirements.md signaleert een nog open punt bij SECURITY-13 (audit-trail op content-mutaties). US-06/US-07 beschrijven "verwijderen" zonder te specificeren of dat een echte database-delete is of een soft-delete.
Hoe moet "een offering verwijderen" op databaseniveau werken?
A) Hard delete — de rij wordt echt verwijderd uit `OfferingsDbContext`; simpelst, maar draagt niet bij aan het SECURITY-13-openpunt
B) Soft delete — een `IsDeleted`/`DeletedAt`-veld, verwijderde offerings worden uit alle queries gefilterd maar blijven in de database staan; simpele, gedeeltelijke invulling van het SECURITY-13-openpunt (geen volledige audit trail, maar wel behoud van de laatste staat vóór verwijdering)
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: B
@@ -0,0 +1,214 @@
# Execution Plan — SlpSoftware Production API
## Detailed Analysis Summary
### Transformation Scope (Brownfield)
- **Transformation Type**: Architectural addition, not a rewrite — a new deployable Client project is added alongside the existing one, a piece of existing hosting logic is extracted into a shared location, and one new module is added. No existing deployment model changes (still a single self-hosted ASP.NET Core process per environment).
- **Primary Changes**: (1) Extract `SlpModularCms.Api/Program.cs`'s composed hosting pipeline into `SlpModularCms.Core` (`CmsHost.Configure(...)`); (2) new `SlpModularCms.Api.SlpSoftware` Client project consuming that shared method; (3) new `SlpModularCms.Modules.Offerings` module (entity, `DbContext`, public endpoint, admin CRUD); (4) Operations-phase CI/CD cutover of the existing pipeline from `Api` to `Api.SlpSoftware`.
- **Related Components**: `SlpModularCms.Core` (extraction target + hosts the `Offering`-adjacent shared conventions), `SlpModularCms.Api` (must keep working identically after the extraction — it is not itself changing behavior), the existing Gitea Actions pipeline (`gitea-deployment-workflow` feature's artifacts).
### Change Impact Assessment
- **User-facing changes**: Yes — new admin CRUD screens for the CMS Administrator persona, and new dynamic (CMS-managed) content on the live website for the Site Visitor persona (requirements.md FR-6, FR-7; stories.md US-01..US-12).
- **Structural changes**: Yes — first-ever project in the `Clients` solution folder; new shared hosting-composition method in `Core`; new module following the existing `IModule` pattern.
- **Data model changes**: Yes — new `Offering` entity + `OfferingsDbContext` (FR-5), isolated per the existing per-module migration pattern.
- **API changes**: Yes — new public `GET /api/v1/offerings` (FR-6) and new authenticated admin endpoints (FR-7).
- **NFR impact**: Yes — Security Baseline extension is enabled and blocking (D-11); the hosting-pipeline extraction (FR-3) must preserve `Api`'s existing security headers/rate limiting/Sentry/Data Protection behavior exactly, so it doesn't regress the already-hardened dev host while building the new one on the same foundation.
### Component Relationships (Brownfield)
```mermaid
graph TD
core["SlpModularCms.Core<br/>(hosting composition, Identity, Availability entities)"]
api["SlpModularCms.Api<br/>(existing dev host)"]
apiSlp["SlpModularCms.Api.SlpSoftware<br/>(new Client, eventual prod host)"]
offerings["SlpModularCms.Modules.Offerings<br/>(new module)"]
identity["SlpModularCms.Modules.Identity"]
availability["SlpModularCms.Modules.Availability"]
master["SlpModularCms.Modules.Master"]
pipeline["Gitea Actions Pipeline<br/>(owned by gitea-deployment-workflow)"]
core -->|"CmsHost.Configure(...)<br/>consumed by both"| api
core -->|"CmsHost.Configure(...)"| apiSlp
apiSlp -->|"hosts"| identity
apiSlp -->|"hosts"| availability
apiSlp -->|"hosts"| master
apiSlp -->|"hosts"| offerings
api -->|"hosts (unchanged)"| identity
api -->|"hosts (unchanged)"| availability
api -->|"hosts (unchanged)"| master
pipeline -.->|"retargeted (D-15 cutover)<br/>Operations phase"| apiSlp
classDef core fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000;
classDef existing fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef new fill:#f6e05e,stroke:#c05621,stroke-width:1px,color:#000;
classDef external fill:#d6bcfa,stroke:#553c9a,stroke-width:1px,color:#000;
class core core;
class api,identity,availability,master existing;
class apiSlp,offerings new;
class pipeline external;
```
Text alternative: `Core` provides the shared `CmsHost.Configure` method to both the existing `Api` (unchanged behavior) and the new `Api.SlpSoftware` (blue = shared foundation, green = existing/unchanged components, yellow = new components, purple = the externally-owned CI/CD pipeline that gets retargeted in the Operations phase).
- **Primary Component**: `SlpModularCms.Modules.Offerings` (new) and `SlpModularCms.Api.SlpSoftware` (new)
- **Infrastructure Components**: `.gitea/workflows/continuous_integration.yaml`, `.gitea/workflows/deploy-scp.yaml`, `operations/deployment/deployment-instructions.md` (all owned by `gitea-deployment-workflow`; extended, not duplicated, per D-7/D-15)
- **Shared Components**: `SlpModularCms.Core` (new `CmsHost.Configure(...)`), `SlpModularCms.Modules.Identity`, `SlpModularCms.Modules.Availability`, `SlpModularCms.Modules.Master` (all hosted, unchanged)
- **Dependent Components**: `SlpModularCms.Api` — does not change behavior, but depends on the FR-3 extraction being behavior-preserving
- **Supporting Components**: Existing Sentry-based logging/alerting, existing `HierarchicalRoleHandler`/`AdminOnly` policy
| Related Component | Change Type | Change Reason | Change Priority |
|---|---|---|---|
| `SlpModularCms.Core` | Minor (additive extraction) | FR-3 shared hosting composition | Critical (blocks both Client projects) |
| `SlpModularCms.Api` | Configuration-only (calls the new shared method instead of inline code) | FR-3 | Critical (regression risk if behavior changes) |
| `SlpModularCms.Api.SlpSoftware` | Major (new project) | FR-1, FR-2 | Critical |
| `SlpModularCms.Modules.Offerings` (+ Tests) | Major (new module) | FR-4, FR-5 | Critical |
| Gitea Actions pipeline | Minor (retarget existing jobs) | FR-9, D-15 | Important (Operations phase only, not blocking Construction) |
### Risk Assessment
- **Risk Level**: **Medium** — multiple components change, but each is independently testable (the `Core` extraction can be verified against `Api`'s existing test suite before `Api.SlpSoftware` is even built on top of it), and the highest-risk step (the CI/CD cutover, D-15) is isolated to the Operations phase, coordinated with the feature that already owns that pipeline rather than a fresh, unreviewed change.
- **Rollback Complexity**: Moderate — the `Core` extraction is a straightforward revert if `Api`'s behavior regresses (git revert, `Api.SlpSoftware` didn't exist to depend on it yet at that point in the sequence). The pipeline cutover (Operations) is a config change to Gitea Actions YAML, revertible the same way.
- **Testing Complexity**: Moderate — needs before/after regression coverage on `Api` for the extraction (NFR impact on security headers/rate limiting/Sentry/Data Protection continuity), plus new unit/integration tests for the `Offerings` module.
---
## Module Update Strategy
- **Update Approach**: Sequential where dependencies require it, then parallel-capable.
1. **Foundation first**: Extract `CmsHost.Configure(...)` into `Core` and repoint `SlpModularCms.Api/Program.cs` at it, **verifying `Api`'s existing behavior and test suite are unaffected** before building anything new on top of the shared method.
2. **Then, in parallel**: create `SlpModularCms.Api.SlpSoftware` (consuming the now-shared method + existing modules) and build out `SlpModularCms.Modules.Offerings` — these two do not depend on each other's internals, only on the foundation from step 1 and on `Api.SlpSoftware` existing as *a* host by the time `Offerings` needs to be wired in.
3. **Operations last**: CI/CD cutover (D-15) only after Construction (Code Generation + Build and Test) has proven both the extraction and the new module.
- **Critical Path**: The `Core` extraction (step 1) — both the new Client project and the continued correctness of the existing dev host depend on it.
- **Coordination Points**: The shared `CmsHost.Configure(...)` signature (must accommodate `Api`'s and `Api.SlpSoftware`'s differing module lists); the CI/CD pipeline hand-off with `gitea-deployment-workflow` (extend existing jobs, don't fork them).
- **Testing Checkpoints**: (a) After the `Core` extraction — full existing `Api` test suite + a manual/automated smoke check that `Api` still serves `/admin`, static content, health checks, and security headers identically. (b) After `Offerings` module code generation — its own unit/integration tests (NFR-2). (c) After both units — full Build and Test phase covering `Api.SlpSoftware` end-to-end. (d) Before the Operations cutover — confirm `Api.SlpSoftware` has been running successfully (e.g. against `test.slpsoftware.nl`) prior to repointing production.
**Exact unit boundaries and naming are finalized in the Units Generation stage** (next after Application Design); this section states the intended dependency order that Units Generation should respect, not the final unit list.
---
## Workflow Visualization
```mermaid
flowchart TD
Start(["User Request"])
subgraph INCEPTION["🔵 INCEPTION PHASE"]
WD["Workspace Detection<br/><b>COMPLETED</b>"]
RE["Reverse Engineering<br/><b>COMPLETED (reused)</b>"]
RA["Requirements Analysis<br/><b>COMPLETED</b>"]
US["User Stories<br/><b>COMPLETED</b>"]
WP["Workflow Planning<br/><b>COMPLETED</b>"]
AD["Application Design<br/><b>EXECUTE</b>"]
UP["Units Planning<br/><b>EXECUTE</b>"]
UG["Units Generation<br/><b>EXECUTE</b>"]
end
subgraph CONSTRUCTION["🟢 CONSTRUCTION PHASE"]
FD["Functional Design (per unit)<br/><b>EXECUTE</b>"]
NFRA["NFR Requirements (per unit)<br/><b>EXECUTE</b>"]
NFRD["NFR Design (per unit)<br/><b>EXECUTE</b>"]
ID["Infrastructure Design (per unit)<br/><b>EXECUTE</b>"]
CG["Code Generation<br/>(Planning + Generation)<br/><b>EXECUTE</b>"]
BT["Build and Test<br/><b>EXECUTE</b>"]
end
subgraph OPERATIONS["🟡 OPERATIONS PHASE"]
DS["Deployment Setup<br/><b>EXECUTE</b>"]
MS["Monitoring Setup<br/><b>EXECUTE</b>"]
PRV["Production Readiness Validation<br/><b>EXECUTE</b>"]
end
Start --> WD --> RE --> RA --> US --> WP --> AD --> UP --> UG --> FD --> NFRA --> NFRD --> ID --> CG --> BT --> DS --> MS --> PRV --> End(["Complete"])
style WD fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style RE fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style RA fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style US fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style WP fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style AD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style UP fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style UG fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style FD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style NFRA fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style NFRD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style ID fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style CG fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style BT fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style DS fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style MS fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
style PRV fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
style Start fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000
style End fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000
style INCEPTION fill:#BBDEFB,color:#000
style CONSTRUCTION fill:#C8E6C9,color:#000
style OPERATIONS fill:#FFF59D,color:#000
linkStyle default stroke:#333,stroke-width:2px
```
Text alternative: all Inception stages up to and including Workflow Planning are completed (solid green). Application Design, Units Planning, Units Generation, and all four per-unit Construction design stages (Functional Design, NFR Requirements, NFR Design, Infrastructure Design) are planned to execute (dashed orange). Code Generation and Build and Test always execute (solid green). In Operations, Deployment Setup and Monitoring Setup are planned to execute (dashed orange, each asks its own inclusion question when reached), and Production Readiness Validation always runs once the phase is reached (solid green).
---
## Phases to Execute
### 🔵 INCEPTION PHASE
- [x] Workspace Detection (COMPLETED)
- [x] Reverse Engineering (COMPLETED — reused existing `_shared/reverse-engineering/` artifacts, no rerun)
- [x] Requirements Analysis (COMPLETED)
- [x] User Stories (COMPLETED)
- [x] Workflow Planning / Execution Plan (COMPLETED — this document)
- [ ] Application Design — **EXECUTE**
- **Rationale**: New components are introduced (`Offering` entity, `OfferingsDbContext`, admin CRUD service layer, the shared `CmsHost.Configure(...)` method) whose methods, business rules (featured exclusivity, reorder persistence), and dependencies need definition before units can be planned.
- [ ] Units Planning — **EXECUTE**
- **Rationale**: Multiple modules/projects are involved (Core extraction, new Client project, new module) with a real dependency order (Module Update Strategy above) — this needs explicit unit boundaries, not an implicit single unit.
- [ ] Units Generation — **EXECUTE**
- **Rationale**: Same as Units Planning — this is a multi-unit change, not a single simple unit.
### 🟢 CONSTRUCTION PHASE
*(Assessed per unit once Units Generation defines them; overall expectation below.)*
- [ ] Functional Design — **EXECUTE** (primarily for the Offerings unit: new data model + business rules; likely minimal/skippable for a pure hosting-extraction unit — confirmed per-unit)
- **Rationale**: New data model (`Offering`) and non-trivial business rules (exactly-one-featured, reorder semantics) need detailed design.
- [ ] NFR Requirements — **EXECUTE**
- **Rationale**: Security Baseline extension is enabled and blocking (D-11); the hosting-extraction unit specifically carries NFR risk (must not regress `Api`'s existing security headers/rate limiting/Sentry/Data Protection).
- [ ] NFR Design — **EXECUTE**
- **Rationale**: Follows directly from NFR Requirements being executed.
- [ ] Infrastructure Design — **EXECUTE** (primarily for the Client/hosting unit: `Api.SlpSoftware` is a new deployment target; likely skippable for the Offerings unit, which reuses the existing MariaDB/module-migration infrastructure with nothing new to map)
- **Rationale**: `Api.SlpSoftware` becoming a deployment target is new for this specific unit, even though the underlying hosting infrastructure (Pi, Gitea) already exists — per the "when in doubt, execute" rule for infra that's new to *this* unit.
- [ ] Code Generation — **EXECUTE (ALWAYS)**
- **Rationale**: Implementation planning and code generation needed for every unit.
- [ ] Build and Test — **EXECUTE (ALWAYS)**
- **Rationale**: Full build across units together, plus integration testing between the new module, the new Client project, and the unchanged `Api`.
### 🟡 OPERATIONS PHASE
- [ ] Deployment Setup — **EXECUTE** (asks its own inclusion question when reached, per the workflow's standard pattern)
- **Rationale**: D-7/D-15 — the CI/CD pipeline cutover from `Api` to `Api.SlpSoftware` is explicitly in scope for this feature's Operations phase.
- [ ] Monitoring Setup — **EXECUTE** (asks its own inclusion question when reached)
- **Rationale**: The new public endpoint and admin CRUD are new surfaces on what will become the production API; worth confirming the existing Sentry-based monitoring (inherited via FR-3) covers them, or whether anything additional is needed.
- [ ] Production Readiness Validation — **EXECUTE (ALWAYS, once Operations phase is reached)**
- **Rationale**: Standard wrap-up gate, including the Security Baseline final check and (per this repo's convention) the `dotnet-appsettings` compliance check for the new Client project.
---
## Package Change Sequence (Brownfield)
1. **`SlpModularCms.Core`** — add `CmsHost.Configure(...)` (or equivalent), extracted from `SlpModularCms.Api/Program.cs`. *Must land first; blocks everything else.*
2. **`SlpModularCms.Api`** — repoint `Program.cs` at the new shared method. *No behavior change; verify via existing tests before proceeding.*
3. **`SlpModularCms.Api.SlpSoftware`** (new) and **`SlpModularCms.Modules.Offerings`** (+ `.Tests`, new) — can proceed once steps 1-2 are verified; independent of each other internally, both needed before Build and Test can exercise the full stack.
4. **Gitea Actions pipeline** (`continuous_integration.yaml`, `deploy-scp.yaml`, `deployment-instructions.md`) — retargeted in the Operations phase only, after Construction has proven steps 1-3.
*(Final unit grouping is confirmed in Units Generation — this is the dependency-respecting order that stage should produce.)*
---
## Estimated Timeline
- **Total Phases**: 3 (Inception remainder, Construction, Operations)
- **Estimated Duration**: Not tracked in calendar time for this workflow — driven by stage-by-stage approval, not a schedule.
## Success Criteria
- **Primary Goal**: `SlpModularCms.Api.SlpSoftware` exists, hosts Core/Identity/Availability/Master/Offerings, serves the public `GET /api/v1/offerings` and authenticated admin CRUD, without regressing `SlpModularCms.Api`.
- **Key Deliverables**: Shared `CmsHost.Configure(...)` in `Core`; `SlpModularCms.Api.SlpSoftware` project in `Clients`; `SlpModularCms.Modules.Offerings` (+ `.Tests`) in `Application/Modules` / `Tests/Modules`; documented reference content (FR-8); retargeted CI/CD pipeline (Operations).
- **Quality Gates**: Full Security Baseline compliance (per requirements.md); `Api`'s existing test suite green after the extraction; new module's own test coverage (NFR-2); Build and Test phase integration checks.
- **Integration Testing**: `Api.SlpSoftware` serving all five modules together, same-origin site + `/admin` + `/api/v1`, matches `Api`'s existing behavior for the four pre-existing modules.
- **Operational Readiness**: CI/CD pipeline successfully building/deploying `Api.SlpSoftware`; monitoring/alerting confirmed to cover the new surfaces.
@@ -0,0 +1,80 @@
# Story Generation Plan — SlpSoftware Production API
Dit plan beschrijft hoe de user stories en persona's voor deze feature worden opgesteld. Beantwoord eerst de vragen hieronder; na jouw goedkeuring wordt dit plan stap voor stap uitgevoerd.
## Uitvoeringschecklist
- [x] Stap A — Persona's definiëren (`personas.md`): Site Visitor (anonieme bezoeker marketingsite) en CMS Administrator (Administrator-rol, beheert offerings via `/admin`)
- [x] Stap B — Stories voor de Site Visitor-persona (consumptie van `GET /api/v1/offerings`, incl. leeg-resultaat-scenario)
- [x] Stap C — Stories voor de CMS Administrator-persona (aanmaken, bewerken, verwijderen, herordenen van offerings, incl. de "featured"-regel)
- [x] Stap D — Acceptatiecriteria per story toevoegen (Given/When/Then, zie Vraag 2)
- [x] Stap E — Persona's koppelen aan bijbehorende stories
- [x] Stap F — Zelfcontrole: elke story voldoet aan INVEST (Independent, Negotiable, Valuable, Estimable, Small, Testable)
- [x] Stap G — `stories.md` en `personas.md` opslaan onder `aidlc-docs/features/slpsoftware-api/inception/user-stories/`
## Aanpak-opties voor storyopbouw
- **Persona-based** (aanbevolen): stories gegroepeerd per persona (Site Visitor / CMS Administrator) — sluit direct aan op de twee duidelijk verschillende gebruikersrollen uit requirements.md.
- **Feature-based**: stories gegroepeerd per capability (lezen, aanmaken, bewerken, verwijderen, herordenen) ongeacht wie de actor is.
- **Hybride**: epics per persona, met feature-based sub-stories eronder.
---
## Vragen
### Vraag 1 — Storyopbouw
Welke aanpak voor het groeperen van de stories heeft je voorkeur?
A) Persona-based (aanbevolen) — twee groepen: Site Visitor en CMS Administrator
B) Feature-based — gegroepeerd per capability (lezen/aanmaken/bewerken/verwijderen/herordenen)
C) Hybride — epics per persona met feature-based sub-stories
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: a
### Vraag 2 — Detailniveau acceptatiecriteria
Welk format voor acceptatiecriteria per story?
A) Given/When/Then (aanbevolen — direct bruikbaar als testscenario in latere fases)
B) Simpele bullet-checklist per story (sneller te lezen, minder gestructureerd)
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: A
### Vraag 3 — "Exactly one featured" regel
De externe hand-off-doc noemt: "Exactly one package in the list should have `featured: true`" — maar de frontend handhaaft dit niet zelf. Moet de admin-CRUD (bij het aanmaken/bewerken) dit afdwingen?
A) Ja — bij het instellen van `featured` op een offering wordt automatisch de vorige featured-offering ontfeatured (systeem garandeert altijd precies 0 of 1 featured item)
B) Nee — geen afdwinging; de admin is zelf verantwoordelijk, het systeem staat 0, 1 of meerdere featured offerings toe
C) Waarschuwen, niet blokkeren — het systeem staat meerdere featured offerings toe maar toont een duidelijke waarschuwing in de admin-UI
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: A
### Vraag 4 — Lege lijst op de publieke endpoint
Wat moet er gebeuren als de admin alle offerings verwijdert, zodat `GET /api/v1/offerings` een lege array `[]` teruggeeft?
A) Toestaan — een lege array is een geldige response; de marketingsite toont dan geen pakket-cards (frontend-verantwoordelijkheid, niet iets wat de API moet voorkomen)
B) Voorkomen — de admin kan de laatste overgebleven offering niet verwijderen (systeem blokkeert dit met een duidelijke foutmelding)
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]:A
### Vraag 5 — Herordenen (reorder)
Hoe moet de admin de volgorde van offerings (het `DisplayOrder`-veld uit FR-5) kunnen aanpassen?
A) Drag-and-drop in de lijst-view van de admin-UI
B) Expliciete "omhoog"/"omlaag"-knoppen per rij
C) Een numeriek volgorde-veld dat de admin direct invult bij het aanmaken/bewerken
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: A en B
### Vraag 6 — Persona-naam voor de beheerder
Welke naam/omschrijving past het best bij de admin-persona, gezien de `AdminOnly`-policy (Administrator-rol) uit requirements.md?
A) "CMS Administrator" — generieke, rol-neutrale naam
B) "Site Owner" — benadrukt dat het (voorlopig) waarschijnlijk de eigenaar zelf is die dit gebruikt
X) Anders (geef zelf een naam op na de [Answer]:-tag)
[Answer]: CMS Beheerder, als we het nederlands willen houden
@@ -0,0 +1,52 @@
# Unit of Work Plan — SlpSoftware Production API
Dit plan beschrijft hoe het systeem wordt opgedeeld in units of work voor de Construction-fase. Beantwoord eerst de vragen; na goedkeuring wordt het plan uitgevoerd.
**Al besliste punten, hier niet opnieuw bevraagd** (met onderbouwing waarom een vraag overbodig zou zijn):
- **Volgorde tussen units**: al vastgelegd in `inception/plans/execution-plan.md` (Module Update Strategy) — de Foundation-unit (Core-extractie + `Api.SlpSoftware`-skelet) moet eerst landen en geverifieerd worden tegen `Api`'s bestaande gedrag, vóórdat de Offerings-unit erbovenop gebouwd wordt. Geen nieuwe ambiguïteit sinds die analyse.
- **Wie voegt de project-reference naar `Modules.Offerings` toe aan `Api.SlpSoftware.csproj`**: dit moet de Offerings-unit zelf doen (niet de Foundation-unit), simpelweg omdat die referentie niet kan compileren vóórdat het Offerings-project bestaat. Geen keuzevraag, een logische noodzaak.
- **Teamafstemming (Team Alignment-categorie)**: N/A — dit is een solo-project (jij bent de enige ontwikkelaar/reviewer), er zijn geen team-ownership-grenzen te bepalen.
- **Code-organisatiestrategie (greenfield-only categorie)**: N/A — dit is een brownfield-feature; de mapstructuur ligt al vast via `CLAUDE.md`/`AGENTS.md` (Application/Modules, Tests/Modules, Clients).
## Uitvoeringschecklist
- [x] Stap A — `unit-of-work.md`: unit-definities en verantwoordelijkheden
- [x] Stap B — `unit-of-work-dependency.md`: afhankelijkheidsmatrix tussen units
- [x] Stap C — `unit-of-work-story-map.md`: koppeling van elke user story (US-01..US-12) en relevante FR's aan een unit
- [x] Stap D — Valideren: zijn alle stories toegewezen, kloppen de grenzen met application-design.md?
---
## Vragen
### Vraag 1 — Unit-indeling
Op basis van requirements.md en application-design.md stel ik twee units voor: **Foundation** (`CmsHost`-extractie in Core + het `Api.SlpSoftware`-projectskelet, FR-1/FR-2/FR-3) en **Offerings** (de volledige nieuwe module, FR-4 t/m FR-8, alle 12 user stories). Dit sluit aan bij de Module Update Strategy uit Workflow Planning: Foundation moet eerst en heeft het meeste regressierisico op de bestaande `Api`; Offerings is de nieuwe, op zichzelf staande module.
Welke indeling heeft je voorkeur?
A) Twee units (aanbevolen) — Foundation en Offerings, zoals hierboven beschreven
B) Eén gecombineerde unit — alles in één keer (Core-extractie, nieuw project, nieuwe module) als één ontwerp/codegeneratie-traject
C) Drie units — Foundation opsplitsen in "Core-extractie" en "Api.SlpSoftware-projectskelet" als aparte units
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]: A
### Vraag 2 — Naamgeving van de units
Bij optie A of C hierboven, welke namen passen het best?
A) "Client Hosting Foundation" en "Offerings Module" (technisch, beschrijft wat de unit doet)
B) "SlpSoftware Client Setup" en "Offerings" (korter, gekoppeld aan het eindresultaat)
X) Anders (geef zelf namen op na de [Answer]:-tag)
[Answer]: B
### Vraag 3 — Unit zonder eigen user stories
De Foundation-unit host geen enkele van de 12 user stories rechtstreeks (die horen allemaal bij de Offerings-functionaliteit) — Foundation bestaat puur om FR-1/FR-2/FR-3 (nieuw project + gedeelde hosting-extractie) te realiseren, zonder zichtbaar persona-voordeel op zich.
Is het acceptabel dat een unit in `unit-of-work-story-map.md` geen enkele story toegewezen krijgt (wel FR's), of geef je de voorkeur aan een andere aanpak?
A) Ja, prima — Foundation krijgt FR-1/FR-2/FR-3 toegewezen in de story-map, geen user stories; dat is een geldige, verwachte situatie voor een puur technische enabling-unit
B) Nee — voeg Foundation samen met Offerings tot één unit, zodat elke unit minstens één user story heeft (impliceert antwoord B bij Vraag 1)
X) Anders (beschrijf hieronder na de [Answer]:-tag)
[Answer]:A
@@ -0,0 +1,24 @@
# User Stories Assessment
## Request Analysis
- **Original Request**: New `SlpModularCms.Api.SlpSoftware` client hosting a new `SlpModularCms.Modules.Offerings` module: a public unauthenticated `GET /api/v1/offerings` endpoint plus admin CRUD (create/edit/delete/reorder) for offering content.
- **User Impact**: Direct — two distinct user types interact with this feature: anonymous website visitors (consumers of the public endpoint, indirectly via the external frontend) and CMS administrators (direct users of the new admin CRUD screens).
- **Complexity Level**: Complex (per requirements.md Intent Analysis)
- **Stakeholders**: The user (product owner + sole admin operator today), plus the external `SlpSoftware` frontend as a technical consumer of the public contract.
## Assessment Criteria Met
- [x] High Priority: **New User Features** — the admin CRUD screens are entirely new functionality (requirements FR-7).
- [x] High Priority: **Customer-Facing APIs**`GET /api/v1/offerings` is consumed by an external system (requirements FR-6).
- [x] High Priority: **Multi-Persona Systems** — anonymous site visitor vs. authenticated CMS administrator have different needs and acceptance criteria.
- [x] Medium Priority / Complexity Assessment: **Ambiguity** — requirements intentionally left some admin-UX details open (e.g. how "featured" exclusivity and reordering are enforced), which acceptance criteria can resolve concretely.
- [x] Benefits: Clear acceptance criteria for the "exactly one featured" business rule (hand-off doc) and for delete/reorder edge cases, which are exactly the kind of detail that's easy to get wrong without a story-level decision.
## Decision
**Execute User Stories**: Yes
**Reasoning**: Meets multiple High Priority criteria outright (new user-facing admin feature, customer-facing API, multi-persona), and there are genuine open UX/business-rule questions (featured-flag exclusivity, empty-state handling, reorder UX) that are better resolved as acceptance criteria now than left ambiguous into Application Design or Code Generation.
## Expected Outcomes
- A concrete, testable acceptance-criteria decision for the "exactly one featured offering" rule (currently only a soft expectation in the external hand-off doc).
- A concrete decision for what happens to the public endpoint when zero offerings exist.
- A concrete decision for the reorder interaction/persistence model, feeding directly into FR-5's `DisplayOrder` field and FR-7's admin CRUD design.
- Two clear personas (Site Visitor, CMS Administrator) that later design/code-generation stages can reference instead of re-deriving "who is this for" each time.