Plans the Gitea deployment feature and refreshes the codebase analysis
Adds the AI-DLC inception record for deploying the CMS as a single .NET application on hosting where no server configuration is possible. The reverse-engineering artifacts were regenerated: the previous set predated the Master module, the Slave host, the solution reorganisation and single-host serving, all of which matter for deployment. Findings were verified by running the build, both test suites and the linter rather than inferred, which surfaced two facts the plan depends on: the frontend lint gate currently fails (5 errors), and two transitive packages carry high-severity advisories. Records 24 functional requirements, 32 traced decisions and a seven-unit decomposition whose ordering is load-bearing: durability work must land before the first automated deploy, or the very first deploy is the one that silently breaks master/slave trust. Two conflicts found while designing and carried into the units: - Both modules call AddDataProtection(), which runs after the host and would override a persistent key store while still passing any registration test. - The availability gate runs before authentication, so its admin bypass cannot read HttpContext.User. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHoJpxYXzHACSQguHrC5fw
This commit is contained in:
+289
@@ -0,0 +1,289 @@
|
||||
# Application Design Plan — Gitea Deployment Workflow
|
||||
|
||||
**Stage**: INCEPTION — Application Design
|
||||
**Scope**: high-level component identification, responsibilities, interfaces and service-layer orchestration. Detailed business logic follows per unit in Functional Design.
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — Design Steps
|
||||
|
||||
### Step 1: Context analysis
|
||||
- [x] Read `requirements.md` (23 FRs, 10 NFRs, 32 decisions, ASM-01, OPEN-01…04)
|
||||
- [x] Read `execution-plan.md` (7 units, risk level High)
|
||||
- [x] Read the shared reverse-engineering artifacts
|
||||
- [x] Inspect `Program.cs` of both host projects to establish the current composition baseline
|
||||
|
||||
### Step 2: Component identification
|
||||
- [x] Identify new components introduced by this feature
|
||||
- [x] Decide the home project for each (`Core` versus each host) — see Question 1
|
||||
- [x] Establish which components the Slave host inherits and which it must not — see Question 2
|
||||
- [x] Define component boundaries and responsibilities
|
||||
|
||||
### Step 3: Static-file serving redesign
|
||||
- [x] Design the two-mount model (`wwwroot/web/` at `/`, `wwwroot/admin/` at `/admin`) — see Question 3
|
||||
- [x] Define fallback precedence and the `nonfile` constraint behaviour
|
||||
- [x] Establish middleware ordering relative to security headers and the availability gate — see Question 4
|
||||
|
||||
### Step 4: Cross-cutting component interfaces
|
||||
- [x] Define the security-headers component and its configuration surface — see Question 5
|
||||
- [x] Define the CSP path-scoping mechanism — see Question 6
|
||||
- [x] Define health-check registration and its endpoint
|
||||
- [x] Define Data Protection key-ring placement — see Question 7
|
||||
- [x] Define migration-at-startup placement and failure behaviour — see Question 8
|
||||
|
||||
### Step 5: Service layer and orchestration
|
||||
- [x] Define registration extension methods and their composition order
|
||||
- [x] Decide whether shared host composition is extracted — see Question 9
|
||||
- [x] Define the observability registration surface (Sentry, logging) — see Question 10
|
||||
|
||||
### Step 6: Deployment transport abstraction
|
||||
- [x] Design the transport seam that admits FTPS later without restructuring (NFR-09, D-02) — see Question 11
|
||||
|
||||
### Step 7: Scope confirmation
|
||||
- [x] Resolve OPEN-02 ownership — see Question 12
|
||||
|
||||
### Step 8: Mandatory design artifacts
|
||||
- [x] Generate `components.md` — component definitions and high-level responsibilities
|
||||
- [x] Generate `component-methods.md` — method signatures and input/output types
|
||||
- [x] Generate `services.md` — service definitions and orchestration patterns
|
||||
- [x] Generate `component-dependency.md` — dependency matrix, communication patterns, data flow
|
||||
- [x] Generate `application-design.md` — consolidated design document
|
||||
- [x] Validate design completeness and consistency against all 23 FRs
|
||||
- [x] Verify Security Baseline compliance for the design
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — Design Questions
|
||||
|
||||
Vul je keuze in achter elke `[Answer]:`-tag. Kies de laatste optie (`Anders`) als niets past.
|
||||
|
||||
---
|
||||
|
||||
### Question 1 — Waar horen de nieuwe cross-cutting componenten?
|
||||
|
||||
**Context**: `SlpModularCms.Core` wordt door **beide** hosts gebruikt (`Api` en `Api.Slave`). Alles wat je in `Core` registreert, krijgt de Slave er automatisch bij. `Core` heeft al `FrameworkReference: Microsoft.AspNetCore.App`, dus middleware in `Core` kan technisch prima.
|
||||
|
||||
Het gaat om vier nieuwe zaken: health checks, securityheaders-middleware, Data Protection key ring, en Sentry/logging.
|
||||
|
||||
Waar komen die te staan?
|
||||
|
||||
A) Alles in `Core`, aangeboden als extension methods (`AddCmsHealthChecks()`, `AddCmsSecurityHeaders()`, …) — beide hosts krijgen identiek gedrag, geen duplicatie
|
||||
B) Alles in het `Api`-host-project — de Slave is puur een lokaal ontwikkelhulpmiddel en heeft dit niet nodig
|
||||
C) Gesplitst: infrastructuur die beide hosts nodig hebben (Data Protection, health checks, logging) in `Core`; wat alleen met het publieke serveren te maken heeft (securityheaders) in `Api`
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 2 — Wat krijgt de Slave-host wél en niet?
|
||||
|
||||
**Context**: `SlpModularCms.Api.Slave` draait alleen lokaal, heeft geen testproject, en wordt níet gedeployed. Maar hij deelt wel de master↔slave-protocolcode, en juist daar speelt de Data Protection key ring een rol.
|
||||
|
||||
Welke van de nieuwe voorzieningen moet de Slave krijgen?
|
||||
|
||||
A) Alles behalve de statics — dus wél health check, securityheaders, key ring, Sentry; geen `wwwroot/web` of `/admin`. Maximale gelijkenis met productiegedrag
|
||||
B) Alleen wat functioneel nodig is voor het master/slave-protocol: de Data Protection key ring. Geen health check, securityheaders of Sentry — die voegen lokaal niets toe
|
||||
C) Alles wat `Core` biedt (volgt automatisch uit Question 1 = A), en verder niets host-specifieks
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A, Want de Slave is wel een API die laat zien hoe een klant-API eruit kan komen te zien.
|
||||
|
||||
---
|
||||
|
||||
### Question 3 — Hoe worden de twee statics-mappen bediend?
|
||||
|
||||
**Context**: nu doet `Program.cs` `UseDefaultFiles()` + `UseStaticFiles()` op `wwwroot/`, met twee `MapFallbackToFile`-regels. Met de nieuwe indeling moet `/` uit `wwwroot/web/` komen en `/admin` uit `wwwroot/admin/`.
|
||||
|
||||
A) Twee expliciete `UseStaticFiles`-registraties met elk een eigen `PhysicalFileProvider` en `RequestPath` — expliciet en goed leesbaar, elk pad heeft zijn eigen configuratie (en kan later eigen headers krijgen)
|
||||
B) `WebRootPath` verleggen naar `wwwroot/web` en `/admin` als losse extra mount toevoegen — kleinste wijziging, maar `wwwroot` betekent dan iets anders dan de mapnaam suggereert
|
||||
C) Eén statics-registratie op `wwwroot/` houden en het onderscheid puur via fallback-routes regelen — minste code, maar dan is `wwwroot/web/index.html` ook direct op `/web/index.html` bereikbaar
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
|
||||
---
|
||||
|
||||
### Question 4 — Waar in de pipeline komen de securityheaders?
|
||||
|
||||
**Context**: de huidige volgorde is exception handler → rate limiter → (dev: OpenAPI/Scalar) → HTTPS redirect → statics → CORS → availability-gate → auth → endpoints.
|
||||
|
||||
Statics *short-circuiten*: een bestaand bestand wordt direct geserveerd en alles daarna draait niet meer. Securityheaders die ná de statics staan, komen dus nooit op de publieke website terecht.
|
||||
|
||||
A) Direct vóór de statics — dan krijgen álle responses de headers, inclusief statische bestanden en de publieke website
|
||||
B) Direct ná de exception handler, helemaal vooraan — dan krijgen ook foutresponses de headers
|
||||
C) Alleen op de SPA/HTML-responses, niet op assets — minder overhead op afbeeldingen en scripts
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: C
|
||||
|
||||
---
|
||||
|
||||
### Question 5 — Hoe configureerbaar moeten de securityheaders zijn?
|
||||
|
||||
**Context**: de Umami-script-origin en de Sentry-ingest-origin moeten in de CSP toegelaten worden, en die verschillen per omgeving. Configuratie hoort volgens de bestaande stijl in `appsettings` via het Options-patroon.
|
||||
|
||||
A) Volledig via een nieuwe `SecurityHeaders`-sectie in `appsettings.json`, met een typed options-class — consistent met `JwtSettings`, `MasterModule` en de rest
|
||||
B) Vaste, in code ingebakken headers met alleen de CSP-uitzonderingen (Umami/Sentry-origins) configureerbaar — minder knoppen om verkeerd te zetten
|
||||
C) Volledig in code, met de origins afgeleid uit de bestaande Sentry- en Umami-configuratie — geen aparte sectie nodig
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 6 — Hoe wordt de CSP per pad gescopet?
|
||||
|
||||
**Context**: je koos strikt voor `/admin` en `/api/v1`, ruimer voor de publieke website (D-31/CQ5 = B). Dat vraagt een mechanisme dat per request beslist welke CSP geldt.
|
||||
|
||||
A) Padprefix-vergelijking in de middleware: begint het pad met `/admin` of `/api/v1` → strikte policy, anders de ruime — eenvoudig en direct leesbaar
|
||||
B) Een configureerbare lijst van pad-naar-policy-regels in `appsettings`, zodat je later paden kunt toevoegen zonder code te wijzigen
|
||||
C) Twee losse middleware-registraties met `UseWhen()` op padprefix — elk met zijn eigen policy, geen if-logica binnen één component
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 7 — Welke DbContext huisvest de Data Protection keys?
|
||||
|
||||
**Context**: `PersistKeysToDbContext<T>` vereist een `DbContext` die `IDataProtectionKeyContext` implementeert. Er zijn er drie: `ApplicationDbContext` (Core/Identity, migreert straks automatisch), `AvailabilityDbContext` en `MasterDbContext` (beide migreren al automatisch). Ze delen één connection string.
|
||||
|
||||
A) `ApplicationDbContext` — de sleutels zijn infrastructuur van de hele applicatie, niet van één module. Vereist een nieuwe Core-migratie
|
||||
B) Een eigen, nieuwe `DataProtectionDbContext` — maximale scheiding, maar een vierde context en een vierde migratieset
|
||||
C) `AvailabilityDbContext` — die zit het dichtst bij de master/slave-functionaliteit waarvoor de sleutels gebruikt worden
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
|
||||
---
|
||||
|
||||
### Question 8 — Wat gebeurt er als de migratie bij het opstarten faalt?
|
||||
|
||||
**Context**: `ApplicationDbContext` gaat automatisch migreren bij startup (FR-11). De twee modulecontexts doen dat al. De vraag is wat er moet gebeuren als dat misgaat — bijvoorbeeld doordat de database niet bereikbaar is of een migratie stukloopt.
|
||||
|
||||
Dit raakt de health check direct: bij "fail fast" start het proces niet, waardoor `/health` niets teruggeeft en UptimeRobot dus rood wordt — precies wat je wilt weten.
|
||||
|
||||
A) Fail fast — gooi de fout door, het proces start niet. Een half-werkende applicatie is erger dan een zichtbaar dode
|
||||
B) Loggen en toch doorstarten — de applicatie draait, en fouten worden zichtbaar zodra iemand de database aanraakt
|
||||
C) Fail fast in productie, loggen-en-doorstarten in Development — lokaal niet geblokkeerd worden door een migratieprobleem
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 9 — De twee `Program.cs`-bestanden zijn bijna identiek
|
||||
|
||||
**Context**: `SlpModularCms.Api/Program.cs` en `SlpModularCms.Api.Slave/Program.cs` verschillen alleen in de statics/SPA-fallbacks. Deze feature voegt aan beide dezelfde nieuwe registraties toe, waardoor de duplicatie groeit en het risico ontstaat dat ze uit elkaar gaan lopen.
|
||||
|
||||
A) Laat de duplicatie staan — twee losse hosts die expliciet zijn, is duidelijker dan een gedeelde abstractie. Deze feature blijft klein
|
||||
B) Extraheer de gedeelde compositie naar één extension method in `Core` (bijv. `AddCmsHost()` / `UseCmsPipeline()`); elke host voegt alleen zijn eigen specifieke stukken toe
|
||||
C) Extraheer alleen de nieuwe registraties uit deze feature naar gedeelde extension methods, en laat de bestaande duplicatie ongemoeid — kleinste risico, geen regressie in bestaand gedrag
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
|
||||
---
|
||||
|
||||
### Question 10 — Hoe wordt Sentry geregistreerd?
|
||||
|
||||
**Context**: `Sentry.AspNetCore` haakt normaal in op de host-builder. Sentry moet optioneel blijven: zonder DSN geen initialisatie, alleen console-logging (FR-14).
|
||||
|
||||
A) Eén extension method die alles doet (Sentry + logging-configuratie), die zichzelf overslaat als er geen DSN is — één plek om naar te kijken
|
||||
B) Sentry en de logging-configuratie apart registreren, zodat je structured logging ook zonder Sentry kunt aanzetten
|
||||
C) Sentry alleen in het `Api`-host-project, logging-configuratie in `Core`
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 11 — Hoe ziet de transport-abstractie eruit?
|
||||
|
||||
**Context**: nu SSH/SCP, later mogelijk FTPS erbij, zonder de workflow te herstructureren (D-02, NFR-09). Dit is een ontwerpkeuze in de workflow-laag, niet in C#.
|
||||
|
||||
A) `deploy.yaml` krijgt een `transport`-input (`scp` nu, `ftps` later) en kiest intern de juiste stap met een `if`-conditie — één bestand, één interface, transport is een parameter
|
||||
B) Eén reusable workflow per transport (`deploy-scp.yaml`, later `deploy-ftps.yaml`), met een identieke input-interface; de aanroeper kiest welke — schoner gescheiden, iets meer bestanden
|
||||
C) Eén `deploy.yaml` met alleen SCP nu, en de FTPS-variant later toevoegen wanneer die daadwerkelijk nodig is — geen abstractie bouwen voor iets wat er nog niet is
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 12 — Wat doen we met OPEN-02?
|
||||
|
||||
**Context**: `AvailabilityMiddleware.IsAdminBypass` leest de JWT met `ReadJwtToken` **zonder de handtekening te valideren**. Iemand kan dus een zelfgemaakt, ongetekend token met een `Owner`-rol meesturen en daarmee de availability-gate omzeilen. Beveiligde endpoints weigeren daarna alsnog (echte authenticatie gebeurt wel goed), dus dit is geen rechten-escalatie — maar de gate is er wel mee te omzeilen door een niet-geauthenticeerde beller.
|
||||
|
||||
Dit is pre-existent en niet door deze feature veroorzaakt. Onder SECURITY-08 is het wel een echte bevinding.
|
||||
|
||||
A) Meenemen in deze feature — het is een kleine fix (valideer het token, of gebruik het al geauthenticeerde `HttpContext.User`) en het raakt precies de middleware die we toch aanpassen voor de `/health`-bypass
|
||||
B) Doorschuiven naar `tech-debt-backlog` als aparte feature — deze feature blijft over deployment gaan
|
||||
C) Alleen documenteren als geaccepteerd risico met onderbouwing, en verder niets
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — Follow-up Questions (Ronde 2)
|
||||
|
||||
Twaalf antwoorden waren eenduidig. Eén punt moet ik terugleggen omdat het letterlijk uitvoeren van je antwoord de beveiliging zou verzwakken, en één punt wil ik bevestigen omdat ik twee antwoorden heb samengevoegd.
|
||||
|
||||
---
|
||||
|
||||
### Follow-up Question 1 — `nosniff` hoort juist wél op assets
|
||||
|
||||
**Context**: bij Question 4 koos je C: securityheaders alleen op SPA/HTML-responses, niet op assets. Voor de meeste headers klopt dat — een CSP of `X-Frame-Options` op een PNG doet niets.
|
||||
|
||||
**Maar voor twee headers werkt dat averechts:**
|
||||
|
||||
- **`X-Content-Type-Options: nosniff`** bestaat er juist om te voorkomen dat de browser het type van een **niet-HTML-bestand** verkeerd raadt. Een geüpload `.txt`- of `.svg`-bestand dat de browser als HTML of JavaScript interpreteert, is precies het aanvalsscenario. Alleen op HTML zetten haalt de maatregel dus weg waar hij nodig is.
|
||||
- **`Strict-Transport-Security`** is een transportregel voor het hele domein, geen paginaregel. Browsers onthouden hem per host; hem alleen op HTML sturen werkt in de praktijk wel, maar is onnodig fragiel — een bezoeker die als eerste een asset ophaalt, krijgt hem dan niet.
|
||||
|
||||
`Content-Security-Policy`, `X-Frame-Options` en `Referrer-Policy` zijn wél zinvol HTML-only.
|
||||
|
||||
Hoe wil je het?
|
||||
|
||||
A) Per header het passende bereik: `X-Content-Type-Options` en `Strict-Transport-Security` op **alle** responses; `Content-Security-Policy`, `X-Frame-Options` en `Referrer-Policy` alleen op HTML-responses (aanbevolen — dit is wat je met C bedoelde, maar zonder het gat)
|
||||
B) Toch strikt alle vijf de headers alleen op HTML-responses, zoals letterlijk geantwoord
|
||||
C) Alle vijf op alle responses — eenvoudigst, iets meer bytes per asset
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Follow-up Question 2 — Bevestiging van de configuratie-indeling
|
||||
|
||||
**Context**: Question 5 = B ("vaste headers in code, alleen de CSP-uitzonderingen configureerbaar — minder knoppen om verkeerd te zetten") en Question 6 = B ("een configureerbare lijst van pad-naar-policy-regels, zodat je later paden kunt toevoegen zonder code te wijzigen") lijken elkaar tegen te spreken: de een wil weinig configuratie, de ander voegt configuratie toe.
|
||||
|
||||
Ik lees ze als samen consistent, op één manier:
|
||||
|
||||
- De **policy-definities zelf** (wat "strikt" en wat "ruim" betekent) staan **in code** — dat is Question 5 = B
|
||||
- De **toewijzing van pad aan policy** staat **in configuratie**, zodat je later een pad kunt toevoegen zonder code te wijzigen — dat is Question 6 = B
|
||||
- De **uitzonderingsorigins** (Umami-script, Sentry-ingest) staan in configuratie, want ze verschillen per omgeving
|
||||
|
||||
Concreet zou `appsettings` er dan ongeveer zo uitzien:
|
||||
|
||||
```json
|
||||
"SecurityHeaders": {
|
||||
"PathPolicies": [
|
||||
{ "PathPrefix": "/admin", "Policy": "Strict" },
|
||||
{ "PathPrefix": "/api/v1", "Policy": "Strict" }
|
||||
],
|
||||
"DefaultPolicy": "Relaxed",
|
||||
"AllowedScriptOrigins": [ "https://analytics.slpsoftware.nl" ],
|
||||
"AllowedConnectOrigins": [ "https://<sentry-ingest-host>" ]
|
||||
}
|
||||
```
|
||||
|
||||
Klopt die lezing?
|
||||
|
||||
A) Ja, precies zo — policies in code, padtoewijzing en origins in configuratie
|
||||
B) Nee, ik wil de policy-inhoud zelf ook configureerbaar (volledige CSP-strings in `appsettings`)
|
||||
C) Nee, ik wil juist minder: ook de padtoewijzing in code, alleen de origins configureerbaar
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
@@ -0,0 +1,284 @@
|
||||
# Execution Plan — Gitea Deployment Workflow
|
||||
|
||||
**Feature**: `gitea-deployment-workflow`
|
||||
**Branch**: `feature/gitea-deployment-workflow`
|
||||
**Date**: 2026-07-27
|
||||
|
||||
---
|
||||
|
||||
## 1. Detailed Analysis Summary
|
||||
|
||||
### Transformation Scope
|
||||
|
||||
- **Transformation Type**: **Infrastructure and operations transformation with supporting application changes.** This is not a refactor of business logic — it introduces a deployment capability that does not exist today (no `.gitea/` directory) and changes how the application is hosted, configured and observed.
|
||||
- **Primary Changes**:
|
||||
1. New CI/CD pipeline (two workflow files) where none exists.
|
||||
2. A change to the hosting contract: the public website moves from `wwwroot/` to `wwwroot/web/`, altering how `Program.cs` serves static content.
|
||||
3. New cross-cutting application concerns: health checks, HTTP security headers, structured error/log reporting.
|
||||
4. Durability changes: automatic Core migrations and a database-backed Data Protection key ring.
|
||||
5. Deployment-model change: from manual publish to an atomic release-directory switch with a retained previous release.
|
||||
- **Related Components**: `SlpModularCms.Api` (`Program.cs`, `.csproj`), `SlpModularCms.Core` (Data Protection, health checks, security headers, logging), `SlpModularCms.Modules.Availability` (bypass list), `frontend/` (config, Sentry, Umami, lint), plus new `.gitea/workflows/` and documentation.
|
||||
- **No infrastructure-as-code exists** (no CDK, Terraform, CloudFormation, Docker). Host setup is documented procedure, not code — a direct consequence of NFR-01 (no server configuration).
|
||||
|
||||
### Change Impact Assessment
|
||||
|
||||
| Area | Impact | Description |
|
||||
|---|---|---|
|
||||
| **User-facing changes** | **Indirect, yes** | No new features for CMS users. But the public website's URL structure is unchanged while its *storage location* changes (`wwwroot/` → `wwwroot/web/`), and a CSP begins applying to pages that previously had none. A too-strict CSP would visibly break a customer website. |
|
||||
| **Structural changes** | **Yes** | Static-file serving is remounted; new middleware enters the pipeline; a new endpoint (`/health`) is added outside `/api/v1`; the availability bypass list grows. |
|
||||
| **Data model changes** | **Yes, additive** | Data Protection key storage moves into the database, requiring a keys table and a `DbContext` implementing `IDataProtectionKeyContext` — a new migration. No existing entity changes. |
|
||||
| **API changes** | **Minimal, additive** | One new non-versioned endpoint `/health`. No change to any `/api/v1` contract. `frontend`'s API base URL becomes same-origin by default, which changes request URLs the SPA emits but not the endpoints themselves. |
|
||||
| **NFR impact** | **Substantial** | Security (headers, CSP, alerting), reliability (atomic switch, rollback, backup), observability (Sentry, Umami, UptimeRobot), and deployment reproducibility are all new or materially changed. This is where most of the feature's substance lives. |
|
||||
|
||||
### Component Relationships
|
||||
|
||||
- **Primary Component**: `SlpModularCms.Api` — the deployable host and the only place where all three surfaces meet.
|
||||
- **Shared Components**: `SlpModularCms.Core` — receives health-check registration, security-headers middleware, Data Protection configuration and logging setup, because both host projects must inherit them.
|
||||
- **Dependent Components**: `SlpModularCms.Api.Slave` — inherits every `Core` change automatically. **It must keep working**, and it is deliberately excluded from deployment (local-only, no test project).
|
||||
- **Modified Module**: `SlpModularCms.Modules.Availability` — `_bypassPrefixes` must include `/health`.
|
||||
- **Frontend Component**: `frontend/` — config, Sentry, Umami, lint fixes. Coupled to the backend at build time through the publish target.
|
||||
- **Supporting Components (new)**: `.gitea/workflows/`, documentation, and external services (Sentry, Umami, UptimeRobot).
|
||||
|
||||
| Component | Change Type | Change Reason | Priority |
|
||||
|---|---|---|---|
|
||||
| `SlpModularCms.Api` / `Program.cs` | Major | Static-file remount, health endpoint, middleware order, migrations | Critical |
|
||||
| `SlpModularCms.Core` | Minor (additive) | Health checks, security headers, Data Protection, logging | Critical |
|
||||
| `SlpModularCms.Modules.Availability` | Configuration-only | `/health` bypass | Critical |
|
||||
| `SlpModularCms.Api.Slave` | None (inherits) | Must not regress | Important |
|
||||
| `frontend/` | Minor | Same-origin config, Sentry, Umami, lint fixes | Critical |
|
||||
| `.gitea/workflows/` | New | The feature's core deliverable | Critical |
|
||||
| `*.csproj` (packages) | Minor | Pin vulnerable packages, add Sentry / health-check / Data Protection packages | Critical |
|
||||
| Documentation | New + updates | Website contract, README, operations artifacts | Important |
|
||||
|
||||
### Risk Assessment
|
||||
|
||||
- **Risk Level**: **High**
|
||||
- **Rollback Complexity**: **Moderate**
|
||||
- **Testing Complexity**: **Complex**
|
||||
|
||||
**Why High rather than Medium** — three failure modes are destructive and silent:
|
||||
|
||||
1. **Destroying a customer's public website.** An atomic release switch that carries `wwwroot/web/` inside the swapped directory discards the customer's site on every deploy (ASM-01). Data loss, not a bug.
|
||||
2. **Silently breaking master↔slave trust.** Losing the Data Protection key ring makes stored slave API keys undecryptable. The symptom looks like a network fault, so it would be misdiagnosed. FR-12 prevents it — but only if implemented before the first atomic switch.
|
||||
3. **Automatic migrations on startup against production.** FR-11 makes deployment self-contained, and consequently makes a bad migration run automatically with no human gate. This is why FR-20 (pre-deploy backup) and forward-compatible migrations are not optional extras.
|
||||
|
||||
Additional risk factors: the deploy path **cannot be fully tested in CI** — it needs the actual Pi, SSH credentials and a database; and a CSP is a class of change that breaks things only in a real browser, on pages this repository does not own.
|
||||
|
||||
**Mitigations built into the plan**: durability changes (Unit 2) land *before* any deploy workflow (Unit 6); the CSP starts from a known-origin allowlist with the public website deliberately more permissive (D-31); and quality-gate prerequisites (Unit 1) land first so the blocking gates are meaningful rather than immediately red.
|
||||
|
||||
---
|
||||
|
||||
## 2. 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</b>"]
|
||||
RA["Requirements Analysis<br/><b>COMPLETED</b>"]
|
||||
US["User Stories<br/><b>SKIP</b>"]
|
||||
WP["Workflow Planning<br/><b>IN PROGRESS</b>"]
|
||||
AD["Application Design<br/><b>EXECUTE</b>"]
|
||||
UG["Units Generation<br/><b>EXECUTE</b>"]
|
||||
end
|
||||
|
||||
subgraph CONSTRUCTION["🟢 CONSTRUCTION PHASE"]
|
||||
FD["Functional Design<br/><b>EXECUTE per unit</b>"]
|
||||
NFRA["NFR Requirements<br/><b>SKIP</b>"]
|
||||
NFRD["NFR Design<br/><b>EXECUTE per unit</b>"]
|
||||
ID["Infrastructure Design<br/><b>EXECUTE per unit</b>"]
|
||||
CG["Code Generation<br/>Planning plus 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
|
||||
WD --> RE
|
||||
RE --> RA
|
||||
RA --> WP
|
||||
WP --> AD
|
||||
AD --> UG
|
||||
UG --> FD
|
||||
FD --> NFRD
|
||||
NFRD --> ID
|
||||
ID --> CG
|
||||
CG --> BT
|
||||
BT --> DS
|
||||
DS --> MS
|
||||
MS --> PRV
|
||||
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 WP fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
|
||||
style CG fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
|
||||
style BT fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff
|
||||
style US fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000
|
||||
style NFRA fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray: 5 5,color:#000
|
||||
style AD 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 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 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:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray: 5 5,color:#000
|
||||
style INCEPTION fill:#BBDEFB,stroke:#1565C0,stroke-width:3px,color:#000
|
||||
style CONSTRUCTION fill:#C8E6C9,stroke:#2E7D32,stroke-width:3px,color:#000
|
||||
style OPERATIONS fill:#FFF59D,stroke:#F57F17,stroke-width:3px,color:#000
|
||||
style Start fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000
|
||||
style End fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000
|
||||
|
||||
linkStyle default stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
Text alternative: Inception is complete except Application Design and Units Generation which will execute; User Stories is skipped. Construction runs Functional Design, NFR Design and Infrastructure Design per unit (NFR Requirements skipped), then Code Generation and Build and Test. All three Operations stages execute.
|
||||
|
||||
---
|
||||
|
||||
## 3. Phases to Execute
|
||||
|
||||
### 🔵 INCEPTION PHASE
|
||||
|
||||
- [x] **Workspace Detection** — COMPLETED
|
||||
- [x] **Reverse Engineering** — COMPLETED (full rerun, all 8 shared artifacts, verified by executing build/test/lint)
|
||||
- [x] **Requirements Analysis** — COMPLETED (23 FRs, 10 NFRs, 32 decisions, 2 question rounds)
|
||||
- [x] **User Stories** — **SKIP**
|
||||
- **Rationale**: This is infrastructure and operations work. It introduces no new end-user functionality, no new persona, and no user journey. The only user-visible effects are a storage-location change and a CSP, both of which are already captured as requirements with acceptance-relevant detail (FR-07, FR-08, FR-18, FR-09). Personas here would be "the developer deploying" and "the website builder consuming the contract" — the latter is properly served by FR-09's documented contract, not by a story. Offered explicitly at Requirements Analysis approval and not requested.
|
||||
- [~] **Workflow Planning** — IN PROGRESS (this document)
|
||||
- [ ] **Application Design** — **EXECUTE**
|
||||
- **Rationale**: New cross-cutting components genuinely need placement decisions that are not obvious. Where do health checks, security headers and Data Protection registration live — `Core` (inherited by both hosts, including the Slave) or `Api` (host-specific)? Getting this wrong either breaks the Slave or duplicates code. Static-file serving must be redesigned for two mounts with two fallbacks and correct ordering relative to the availability gate. The CSP needs a path-scoping mechanism that does not exist yet. And the deploy transport needs an interface that admits FTPS later without restructuring (NFR-09). These are component-boundary and service-layer decisions — exactly this stage's purpose.
|
||||
- [ ] **Units Generation** — **EXECUTE**
|
||||
- **Rationale**: The work spans application code, frontend code, two workflow files, package changes and documentation, with a **mandatory ordering constraint**: durability changes must land before the first automated deploy, and quality-gate fixes must land before blocking gates are switched on. Sequencing this is load-bearing, not bookkeeping. Seven units are proposed in § 5.
|
||||
|
||||
### 🟢 CONSTRUCTION PHASE
|
||||
|
||||
- [ ] **Functional Design** — **EXECUTE for units 2, 3, 4; SKIP for units 1, 5, 6, 7**
|
||||
- **Rationale**: Units 2, 3 and 4 contain real behavioural logic that needs designing before coding — static-file resolution and fallback precedence, migration-at-startup failure behaviour, CSP composition per path, and what a "security-relevant event" is for alerting. Units 1 (lint fixes, package pins), 5 and 6 (declarative YAML) and 7 (documentation) have no business logic to design; a design document there would restate the requirement.
|
||||
- [ ] **NFR Requirements** — **SKIP (all units)**
|
||||
- **Rationale**: NFRs are already captured comprehensively and with traceability in `requirements.md` § 5 (NFR-01…10) and § 6 (full SECURITY-01…15 assessment with four documented deviations). The tech stack is fixed and unchanged. Re-deriving NFRs per unit would duplicate an artifact that already exists at higher quality than a per-unit restatement would produce.
|
||||
- [ ] **NFR Design** — **EXECUTE for units 3 and 4; SKIP for units 1, 2, 5, 6, 7**
|
||||
- **Rationale, including a deliberate deviation**: The workflow's default is that NFR Design is skipped when NFR Requirements is skipped. I am overriding that coupling for two units, because for them the NFR *is* the deliverable: Unit 3 implements SECURITY-04 (how a CSP is composed and path-scoped, how HSTS interacts with the hosting proxy) and Unit 4 implements SECURITY-03 and SECURITY-14 (structured-logging shape, correlation ID per OPEN-01, which events warrant alerting, PII exclusion). Those are pattern decisions, not requirement decisions — so skipping the requirements stage while designing the patterns is the correct split here, not an oversight. For all other units the existing NFRs need no new pattern work.
|
||||
- [ ] **Infrastructure Design** — **EXECUTE for units 6 and 7; SKIP for units 1, 2, 3, 4, 5**
|
||||
- **Rationale**: Unit 6 is where the host layout is decided — release-directory scheme, where `wwwroot/web/` lives so ASM-01 holds, the symlink or mount strategy, process restart, database backup placement, and the transport abstraction. Unit 7's website contract depends on that layout being settled. This is genuine infrastructure design even though it produces documented procedure rather than IaC (NFR-01 forbids server configuration, so there is nothing to codify). Skipped elsewhere: those units change application code and CI definitions, not infrastructure.
|
||||
- [ ] **Code Generation** — **EXECUTE (always, per unit)**
|
||||
- **Rationale**: Implementation planning and code generation are needed for all seven units. Each unit is built and its own tests run before its completion message.
|
||||
- [ ] **Build and Test** — **EXECUTE (always)**
|
||||
- **Rationale**: Full cross-unit build plus everything that only appears once units are combined — middleware ordering with the new security headers and health endpoint, the Slave host still starting correctly, the frontend building against the same-origin config, and the publish target producing the expected `wwwroot` layout.
|
||||
|
||||
### 🟡 OPERATIONS PHASE
|
||||
|
||||
- [ ] **Deployment Setup** — **EXECUTE**
|
||||
- [ ] **Monitoring Setup** — **EXECUTE**
|
||||
- [ ] **Production Readiness Validation** — **EXECUTE**
|
||||
- **Rationale**: `## Operations Configuration` = **Yes**, decided at Requirements Analysis. For this feature Operations is the centre of gravity, not an afterthought: FR-17 (UptimeRobot monitors), FR-19 (Sentry alert rules), FR-20 (pre-deploy database backup) and FR-23 (deployment, host-setup and rollback documentation, including the FTPS switch path) are all delivered here. Production Readiness Validation will also run the `dotnet-appsettings` compliance gate, which is directly relevant given D-16 (host environment variables) and the placeholder values in `appsettings.json`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Multi-Module Coordination
|
||||
|
||||
### Module Update Strategy
|
||||
|
||||
- **Update Approach**: **Sequential with two parallelisable pairs.**
|
||||
- **Critical Path**: `SlpModularCms.Core` → `SlpModularCms.Api` → `.gitea/workflows/`. Core carries the shared cross-cutting registrations; the Api host composes them and defines serving; the workflows can only deploy something that exists and is durable.
|
||||
- **Coordination Points**:
|
||||
- `Core` ↔ `Api.Slave`: every `Core` change is inherited by the Slave host. The Slave must still start and serve; it has no test project, so this is verified at Build and Test by starting it.
|
||||
- `Core` ↔ `Modules.Availability`: the `/health` bypass must land in the same unit as the health endpoint, or `/health` returns 503 on a disabled instance — the exact conflation the user corrected.
|
||||
- `Api` ↔ `frontend`: coupled bidirectionally — the SPA calls the API at runtime, and the API's publish target builds the SPA. A same-origin config change (Unit 4) and the `wwwroot` remount (Unit 2) must agree on where `/admin` is served from.
|
||||
- Package pinning (Unit 1) touches multiple `.csproj` files and must not conflict with the new packages added in Units 2, 3 and 4.
|
||||
- **Testing Checkpoints**: after each unit (automatic per-unit build + test), plus a full-solution checkpoint at Build and Test that additionally starts both hosts and verifies the publish output layout.
|
||||
- **Rollback Strategy (mid-sequence)**: each unit is a self-contained commit on `feature/gitea-deployment-workflow`. Units 1–4 are revertible independently. Units 5 and 6 add new files only (`.gitea/`) and are revertible by deletion. Nothing is deployed to any environment until Unit 6 is complete and explicitly triggered, so a mid-sequence failure cannot affect a running environment.
|
||||
|
||||
### Per-Module Detail
|
||||
|
||||
| Module | Priority | Depends on | Depended on by | Change Scope |
|
||||
|---|---|---|---|---|
|
||||
| `SlpModularCms.Core` | Must-update-first | — | Both hosts, all modules | Minor (additive) |
|
||||
| `SlpModularCms.Api` | Must-update-first | Core | Deployment | Major |
|
||||
| `SlpModularCms.Modules.Availability` | Must-update-with-Core | Core | Both hosts | Patch (bypass list) |
|
||||
| `SlpModularCms.Api.Slave` | Can-update-later (verify only) | Core | — | None (inherits) |
|
||||
| `frontend` | Must-update-before-CI | Api (same-origin contract) | Api publish target | Minor |
|
||||
| `.gitea/workflows/` | Update-last | Everything above | — | New |
|
||||
| Documentation | Update-last | Infrastructure Design | — | New + updates |
|
||||
|
||||
---
|
||||
|
||||
## 5. Proposed Unit Sequence
|
||||
|
||||
Final unit definitions are produced by Units Generation; this is the sequence the plan is built around, with the ordering constraints that make it non-arbitrary.
|
||||
|
||||
| # | Unit | Delivers | Why here |
|
||||
|---|---|---|---|
|
||||
| 1 | **Quality Gate Prerequisites** | Fix 5 lint errors (FR-21); pin `Microsoft.OpenApi` and `System.Security.Cryptography.Xml` (FR-22) | Blocking gates are switched on in Unit 5. If this does not land first, the pipeline is red on arrival and the gates get disabled "temporarily". Independent of everything else, so it costs nothing to do first. |
|
||||
| 2 | **Hosting Layout & Data Durability** | `wwwroot/web/` remount and dual SPA fallbacks (FR-07, FR-08); `/health` + bypass (FR-10); automatic Core migrations (FR-11); `PersistKeysToDbContext` (FR-12) | Must precede any automated deploy. The key ring and the `wwwroot` split are exactly what make an atomic switch non-destructive; deploying first and fixing after means the first deploy is the dangerous one. |
|
||||
| 3 | **HTTP Security Headers & CSP** | Security-headers middleware, path-scoped CSP (FR-18) | Needs Unit 2's final path layout to scope the CSP. Precedes Unit 4 so the CSP mechanism exists when Umami and Sentry origins need allowing. |
|
||||
| 4 | **Observability Integration** | Sentry backend + frontend (FR-14, FR-15); structured logging (D-20, OPEN-01); Umami (FR-16); same-origin SPA config (FR-13) | Adds the external origins that Unit 3's CSP must permit, and the environment-tagged build variables that Unit 5's two builds must supply. |
|
||||
| 5 | **CI Workflow** | `continuous_integration.yaml`: triggers, six blocking gates, two environment-specific builds, artifacts (FR-01, FR-05) | Gates are meaningful only after Unit 1; the two builds need Unit 4's variables. |
|
||||
| 6 | **Deploy Workflow** | `deploy.yaml`: reusable, transport-abstracted, atomic switch, restart, retained previous release, backup hook (FR-02, FR-03, FR-04, FR-06, FR-20) | Last executable piece; depends on everything above being durable and buildable. |
|
||||
| 7 | **Repository Documentation** | Website workspace contract (FR-09); README updates for the new `wwwroot` layout and health endpoint; `.env.example` updates | Depends on Unit 6's settled host layout. Operations-facing documents (deployment instructions, rollback plan, monitoring setup, readiness checklist) are produced in the Operations phase, not here. |
|
||||
|
||||
---
|
||||
|
||||
## 6. Timeline
|
||||
|
||||
- **Total stages to execute**: 12 (2 remaining Inception + 7 Construction stage-instances across units + Build and Test + 3 Operations)
|
||||
- **Per-unit Construction stage-instances**: Functional Design ×3, NFR Design ×2, Infrastructure Design ×2, Code Generation ×7
|
||||
- **Estimated duration**: not estimated in wall-clock time. Progress is gated on user approval at every stage boundary, and three items depend on external systems outside this workflow's control: Sentry project and alert-rule creation, Umami website entries, and UptimeRobot monitors. Those are host/service setup steps documented in Operations, not code.
|
||||
|
||||
---
|
||||
|
||||
## 7. Success Criteria
|
||||
|
||||
### Primary Goal
|
||||
A push to `master` builds, tests and deploys the CMS to the test environment without manual steps, and production can be deployed by one deliberate action — on hosting where no server configuration is possible, and without ever endangering the customer's public website, the database, or master↔slave trust.
|
||||
|
||||
### Key Deliverables
|
||||
1. `.gitea/workflows/continuous_integration.yaml` and `.gitea/workflows/deploy.yaml`
|
||||
2. `wwwroot/admin/` + `wwwroot/web/` serving, with `wwwroot/web/` outside the swapped release directory (ASM-01)
|
||||
3. `/health` liveness endpoint, on the availability bypass list, never conflated with domain endpoints
|
||||
4. Sentry on both sides, Umami on both frontends, UptimeRobot monitors for `/health`, `/` and `/admin`
|
||||
5. HTTP security-headers middleware with a path-scoped CSP
|
||||
6. Database-backed Data Protection key ring and automatic Core migrations
|
||||
7. Website workspace contract, deployment instructions, rollback plan, monitoring setup, production readiness checklist
|
||||
8. A green pipeline: lint clean, no vulnerable packages, all tests passing
|
||||
|
||||
### Quality Gates
|
||||
- `dotnet build -c Release`: 0 errors
|
||||
- `dotnet test`: all backend tests pass (219 at baseline, plus new tests)
|
||||
- `dotnet list package --vulnerable --include-transitive`: no advisories
|
||||
- `pnpm run build`: succeeds (includes `tsc -b`)
|
||||
- `pnpm test`: all frontend tests pass (213 at baseline, plus new tests)
|
||||
- `pnpm run lint` and `pnpm run format:check`: clean
|
||||
- No blocking Security Baseline findings at any stage
|
||||
|
||||
### Integration Readiness
|
||||
- Both hosts start successfully — `SlpModularCms.Api` **and** `SlpModularCms.Api.Slave`, the latter having no test project and therefore verified by starting it
|
||||
- `dotnet publish` produces the expected `wwwroot` layout with the admin SPA in place
|
||||
- Middleware ordering verified: exception handler → rate limiter → HTTPS redirect → security headers → static files → CORS → availability gate → auth → endpoints, with `/health` reachable while the instance is switched off
|
||||
- The availability gate still blocks `/api/v1` and `/admin` when disabled, and the public website still serves — the pre-existing behaviour recorded in `architecture.md`, deliberately unchanged
|
||||
|
||||
### Operational Readiness
|
||||
- A deploy can be rolled back by redeploying the retained previous release without a rebuild
|
||||
- A production deploy is preceded by a verifiable database backup
|
||||
- After a deploy it is determinable, without host access, that the app is alive, whether it is erroring, and whether all expected modules loaded — the last point mattering because `ModuleOrchestrator` logs rather than throws on module load failure
|
||||
|
||||
---
|
||||
|
||||
## 8. Carried-Forward Items
|
||||
|
||||
| ID | Item | Handled at |
|
||||
|---|---|---|
|
||||
| ASM-01 | `wwwroot/web/` must live outside the swapped release directory | Infrastructure Design, Unit 6 — **must be confirmed there** |
|
||||
| OPEN-01 | Correlation/request ID mechanism (SECURITY-03) | NFR Design, Unit 4 |
|
||||
| OPEN-02 | `IsAdminBypass` reads the JWT without validating its signature — pre-existing, needs an owner | **Recommend `tech-debt-backlog`**; decide at Application Design whether to fold in |
|
||||
| OPEN-03 | Exact patched versions for the two vulnerable packages | Code Generation, Unit 1 |
|
||||
| OPEN-04 | When FTPS is actually built | Deferred by design (D-02) |
|
||||
| DEV-01…04 | Four accepted security deviations | Re-confirmed at Production Readiness Validation |
|
||||
@@ -0,0 +1,170 @@
|
||||
# Unit of Work Plan — Gitea Deployment Workflow
|
||||
|
||||
**Stage**: INCEPTION — Units Generation (Part 1: Planning)
|
||||
|
||||
The execution plan proposed a 7-unit decomposition. This plan confirms or adjusts those boundaries before generating the unit artifacts.
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — Decomposition Steps
|
||||
|
||||
### Step 1: Context analysis
|
||||
- [x] Read `requirements.md` (24 FRs, 10 NFRs after FR-24 was added)
|
||||
- [x] Read `execution-plan.md` § 5 (proposed unit sequence)
|
||||
- [x] Read all five Application Design artifacts, including the two composition conflicts
|
||||
|
||||
### Step 2: Confirm unit boundaries
|
||||
- [x] Confirm or adjust the proposed 7-unit split — see Questions 1, 2, 3
|
||||
- [x] Assign every component (C-01…C-16) to exactly one unit
|
||||
- [x] Assign every functional requirement (FR-01…FR-24) to exactly one unit
|
||||
- [x] Verify no requirement or component is orphaned or duplicated
|
||||
|
||||
### Step 3: Establish dependencies and sequencing
|
||||
- [x] Build the inter-unit dependency matrix
|
||||
- [x] Confirm the ordering constraints that make the sequence non-arbitrary
|
||||
- [x] Identify any units that could run in parallel — see Question 4
|
||||
|
||||
### Step 4: Define per-unit completion criteria
|
||||
- [x] Define what "done" means per unit — see Question 5
|
||||
- [x] Assign the two Application Design conflicts to their units
|
||||
- [x] Assign the remaining open items (OPEN-01, OPEN-03, ASM-01) to their units
|
||||
|
||||
### Step 5: Version control strategy
|
||||
- [x] Establish commit and review granularity — see Question 6
|
||||
|
||||
### Step 6: Mandatory unit artifacts
|
||||
- [x] Generate `unit-of-work.md` — unit definitions and responsibilities
|
||||
- [x] Generate `unit-of-work-dependency.md` — dependency matrix
|
||||
- [x] Generate `unit-of-work-story-map.md` — requirement-to-unit mapping (see Question 7)
|
||||
- [x] Validate unit boundaries and dependencies
|
||||
- [x] Ensure all requirements are assigned to units
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — Decomposition Questions
|
||||
|
||||
Vul je keuze in achter elke `[Answer]:`-tag. Kies de laatste optie (`Anders`) als niets past.
|
||||
|
||||
---
|
||||
|
||||
### Question 1 — Klopt de opdeling in 7 units?
|
||||
|
||||
**Context**: dit is de voorgestelde indeling uit het uitvoeringsplan.
|
||||
|
||||
| # | Unit | Bevat |
|
||||
|---|---|---|
|
||||
| 1 | Quality Gate Prerequisites | 5 lint-fixes, 2 packages pinnen |
|
||||
| 2 | Hosting Layout & Data Durability | `wwwroot/web`, `/health`, auto-migratie, key ring, gate-fix |
|
||||
| 3 | HTTP Security Headers & CSP | middleware, policies, configuratie |
|
||||
| 4 | Observability Integration | Sentry backend + frontend, Umami, same-origin config |
|
||||
| 5 | CI Workflow | `continuous_integration.yaml` |
|
||||
| 6 | Deploy Workflow | `deploy-scp.yaml`, atomaire switch, backup |
|
||||
| 7 | Repository Documentation | website-contract, README, `.env.example` |
|
||||
|
||||
A) Ja, 7 units zoals voorgesteld
|
||||
B) Minder units — voeg samen wat bij elkaar hoort (zie ook vraag 2 en 3)
|
||||
C) Meer units — unit 2 is te groot en moet gesplitst (zie vraag 3)
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]: C
|
||||
|
||||
---
|
||||
|
||||
### Question 2 — Moet unit 1 een eigen unit zijn?
|
||||
|
||||
**Context**: unit 1 is klein — 5 lint-errors oplossen en 2 packages pinnen. Het staat los van al het andere. De reden om het apart en als eerste te doen: unit 5 zet blokkerende gates aan, en als deze fixes er dan nog niet zijn, is de pipeline meteen rood.
|
||||
|
||||
A) Ja, eigen unit en als eerste — de fixes zijn onafhankelijk, en een aparte commit maakt duidelijk wat pre-existente schuld was en wat nieuw werk is
|
||||
B) Voeg samen met unit 5 (CI Workflow) — de fixes bestaan alleen omdat de gates komen, dus hoort het bij elkaar
|
||||
C) Voeg samen met unit 2 — gewoon alle applicatiewijzigingen bij elkaar
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 3 — Is unit 2 te groot?
|
||||
|
||||
**Context**: unit 2 bevat vier losse dingen die alleen gemeen hebben dat ze vóór de eerste deploy klaar moeten zijn:
|
||||
|
||||
1. `wwwroot/web`-herindeling en SPA-fallbacks (FR-07, FR-08)
|
||||
2. `/health`-endpoint plus bypass (FR-10)
|
||||
3. Automatische Core-migratie (FR-11)
|
||||
4. Data Protection key ring (FR-12) — inclusief het conflict met de dubbele `AddDataProtection()`
|
||||
5. De gate-fix uit FR-24
|
||||
|
||||
Punt 1 gaat over serveren; punt 3 en 4 over dataduurzaamheid; punt 2 en 5 raken dezelfde middleware.
|
||||
|
||||
A) Laat unit 2 heel — alles moet toch vóór de eerste deploy klaar zijn, en opsplitsen levert units op die je nooit los oplevert
|
||||
B) Splits in twee: **2a Hosting & Serving** (`wwwroot/web`, SPA-fallbacks, `/health`, gate-fix) en **2b Data Durability** (auto-migratie, key ring, `AddDataProtection`-conflict, discriminator)
|
||||
C) Splits in drie: serveren, health/gate, dataduurzaamheid
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 4 — Volgorde: strikt serieel of waar mogelijk parallel?
|
||||
|
||||
**Context**: sommige units hebben een echte afhankelijkheid (unit 3's CSP heeft de origins uit unit 4 nodig; unit 6 heeft alles nodig). Andere niet: unit 1 en unit 7 staan vrijwel los.
|
||||
|
||||
Omdat elke unit een eigen goedkeuringsmoment heeft, is "parallel" hier vooral: mag ik in één ronde meerdere units afronden?
|
||||
|
||||
A) Strikt serieel — één unit per keer, elk met een eigen goedkeuring. Meeste controle, meeste rondes
|
||||
B) Serieel waar afhankelijk, gegroepeerd waar onafhankelijk — bijvoorbeeld unit 1 en 2 in één ronde, en 5 en 6 in één ronde
|
||||
C) Groepeer per laag: eerst alle applicatiewijzigingen (1–4), dan alle workflow-werk (5–6), dan documentatie (7) — drie rondes
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 5 — Wat betekent "klaar" per unit?
|
||||
|
||||
**Context**: de workflow bouwt en test elke unit automatisch vóór afronding. De vraag is hoe streng dat is.
|
||||
|
||||
A) Bouwt en alle bestaande tests slagen — nieuwe tests alleen waar de unit nieuw gedrag toevoegt dat te testen valt
|
||||
B) Zoals A, plus verplicht nieuwe tests voor elk nieuw gedrag, ook als dat een registratietest is
|
||||
C) Zoals B, plus een coverage-drempel per unit (let op: bij vraag 9 van de requirements koos je géén coverage-gate)
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:B
|
||||
|
||||
---
|
||||
|
||||
### Question 6 — Commit- en reviewstrategie
|
||||
|
||||
**Context**: we werken op `feature/gitea-deployment-workflow`. Er is nog niets gecommit — alle aidlc-documentatie tot nu toe staat als werkmap-wijziging klaar.
|
||||
|
||||
A) Eén commit per unit, alles op deze ene branch, één pull request aan het eind
|
||||
B) Eén commit per unit, en een pull request per unit — kleinere reviews, maar meer PR's
|
||||
C) Vrij committen tijdens het werk, één samengevoegde commit per unit aan het eind (squash)
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
|
||||
---
|
||||
|
||||
### Question 7 — Er zijn geen user stories; waar mapt de story map naar?
|
||||
|
||||
**Context**: de User Stories-fase is overgeslagen (infrastructuurwerk zonder eindgebruikersfunctionaliteit). Het artefact `unit-of-work-story-map.md` is verplicht, maar er zijn geen stories om te mappen.
|
||||
|
||||
A) Map de 24 functionele requirements (FR-01…FR-24) naar units — dat is hier het equivalent van stories en geeft volledige dekkingscontrole
|
||||
B) Map zowel de requirements als de 16 ontwerpcomponenten (C-01…C-16) naar units — dubbele controle op volledigheid
|
||||
C) Genereer het bestand met een notitie dat het niet van toepassing is
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
|
||||
---
|
||||
|
||||
### Question 8 — Hoort documentatie (unit 7) hier of in de Operations-fase?
|
||||
|
||||
**Context**: unit 7 bevat het website-workspace-contract (FR-09), README-updates en `.env.example`. De Operations-fase levert daarnaast al deployment-instructies, rollback-plan, monitoring-setup en de readiness-checklist op.
|
||||
|
||||
A) Unit 7 blijft in Construction voor repo-documentatie (README, `.env.example`, website-contract); Operations levert de operationele documenten — duidelijke scheiding tussen "wat in de repo hoort" en "hoe je het draait"
|
||||
B) Verplaats alles naar de Operations-fase — één plek voor alle documentatie
|
||||
C) Unit 7 vervalt; verdeel de documentatie over de units die de wijziging maken (README-stuk over `wwwroot` bij unit 2, enzovoort)
|
||||
X) Anders (beschrijf hieronder na de [Answer]:-tag)
|
||||
|
||||
[Answer]:A
|
||||
Reference in New Issue
Block a user