diff --git a/aidlc-docs/features/gitea-deployment-workflow/aidlc-state.md b/aidlc-docs/features/gitea-deployment-workflow/aidlc-state.md new file mode 100644 index 0000000..68aff9f --- /dev/null +++ b/aidlc-docs/features/gitea-deployment-workflow/aidlc-state.md @@ -0,0 +1,97 @@ +# AI-DLC State Tracking + +## Project Information +- **Feature Name**: Gitea Deployment Workflow +- **Feature Slug**: gitea-deployment-workflow +- **Project Type**: Brownfield +- **Start Date**: 2026-07-27T00:00:00Z +- **Current Stage**: CONSTRUCTION - Code Generation, Round 1 (U1 + U2 complete, awaiting approval) +- **Branch**: feature/gitea-deployment-workflow + +## Workspace State +- **Existing Code**: Yes +- **Reverse Engineering Needed**: Completed — full rerun on 2026-07-27 (user chose Q3 = B) +- **Workspace Root**: K:\Development\Projects\SlpModularCms + +## Reverse Engineering Status +- [x] Reverse Engineering — Completed on 2026-07-27 +- **Artifacts Location**: aidlc-docs/_shared/reverse-engineering/ (all 8 artifacts regenerated + timestamp) +- **Verified by execution**: Release build 0 errors / 50 warnings; 219 backend tests pass; 213 frontend tests pass; `pnpm run lint` **fails** (5 errors, 1 warning); 2 high-severity transitive package advisories + +## Code Location Rules +- **Application Code**: Workspace root (NEVER in aidlc-docs/) +- **Feature Documentation**: aidlc-docs/features/gitea-deployment-workflow/ only +- **Shared Artifacts**: aidlc-docs/_shared/ +- **Structure patterns**: See code-generation.md Critical Rules + +## Language Configuration +- **Documentation Language**: English +- **Conversation Language**: User Language (Dutch) + +## Extension Configuration +| Extension | Enabled | Decided At | +|---|---|---| +| Security Baseline | Yes (blocking) | Requirements Analysis | +| Property-Based Testing | No | Requirements Analysis | + +## Operations Configuration +- **Include Operations Phase**: Yes +- **Decided At**: Requirements Analysis + +## Scope Decisions (from feature-selection.md) +- **Public website**: documentation/instructions only — where the website build lands in `wwwroot/`, how it coexists with `wwwroot/admin/`, and what a per-website workspace must deliver. The website's own build/deploy workflow stays out of scope (Q4 = A). +- **Environments**: local, test, production only. +- **Observability stack**: UptimeRobot (uptime), Umami (analytics), console logging + Sentry (logging/errors). +- **Deployment constraint**: upload as a published .NET application; no server configuration may be required. +- **Reference**: existing working Gitea Actions setup at `K:\Development\SlpSoftware\Projects\SlpSoftware` (React/Vite) is the starting point. +- **Health check endpoint**: IN SCOPE (decided 2026-07-27). **Liveness only** — `AddHealthChecks()` + `MapHealthChecks("/health")`, no package needed and **no database check** (Q17 = A / D-21, superseding the earlier note that `AddDbContextCheck` might be included). `/health` must be added to `AvailabilityMiddleware._bypassPrefixes` so the availability gate cannot return 503 for it. Health = infrastructure liveness; Availability/capabilities = CMS domain state — these stay strictly separate. + +> **Note**: this section records the earliest scope decisions. The authoritative and complete decision set is `inception/requirements/requirements.md` § 3 (D-01…D-32) — in particular, Q4 = C changed the public website from living directly in `wwwroot/` to `wwwroot/web/`. + +## Stage Progress + +### INCEPTION +- [x] Workspace Detection — Complete +- [x] Reverse Engineering — Complete, approved 2026-07-27 (full rerun of all 8 `_shared/` artifacts) +- [x] Requirements Analysis — Complete, approved 2026-07-27. 24 FRs (FR-24 added at Application Design), 10 NFRs, 32 decisions, 7 assumptions, 4 open items, 4 documented security deviations. Two question rounds: `requirement-verification-questions.md` (25 Q) and `requirement-clarification-questions.md` (5 Q). +- [x] User Stories — **SKIP** (infrastructure/operations work; no new end-user functionality or persona. Offered at Requirements Analysis approval, not requested.) +- [x] Workflow Planning — Complete, approved 2026-07-27. Artifact: `inception/plans/execution-plan.md` +- [x] Application Design — Complete, approved 2026-07-27. 14 code components (9 new, 5 modified) + 2 workflow components. Artifacts in `inception/application-design/`. Two composition conflicts found and carried to Unit 2. Added FR-24, closed OPEN-02. +- [x] Units Generation — Complete (awaiting approval). 7 units in 4 execution rounds. Artifacts: `unit-of-work.md`, `unit-of-work-dependency.md`, `unit-of-work-story-map.md` + +### CONSTRUCTION +Units finalised at Units Generation (see `inception/application-design/unit-of-work.md`): +U1 Hosting & Serving · U2 Data Durability · U3 Security Headers & CSP · U4 Observability · U5 CI Workflow & Gates · U6 Deploy Workflow · U7 Documentation + +Execution rounds (Q4 = B): **R1** = U1 + U2 · **R2** = U3 + U4 · **R3** = U5 + U6 · **R4** = U7. One commit per unit; single PR at the end (Q6 = A). + +- [~] Functional Design — **EXECUTE for U1, U2, U3, U4**; SKIP for U5, U6, U7. **U1 ✅ U2 ✅** approved 2026-07-27 +- [ ] NFR Requirements — **SKIP (all units)** — already comprehensively captured in `requirements.md` § 5 and § 6 +- [ ] NFR Design — **EXECUTE for U3, U4**; SKIP for the rest. *Deliberate deviation from the default NFR-Requirements/NFR-Design coupling — rationale in the execution plan.* +- [ ] Infrastructure Design — **EXECUTE for U6, U7**; SKIP for the rest +- [~] Code Generation — **EXECUTE** (all 7 units, each built and tested before its completion message). **U1 ✅ U2 ✅** generated and verified 2026-07-27 — build 0 errors, 253 backend tests pass (was 219) +- [ ] Build and Test — **EXECUTE** + +### OPERATIONS +- [ ] Deployment Setup — **EXECUTE** +- [ ] Monitoring Setup — **EXECUTE** +- [ ] Production Readiness Validation — **EXECUTE** (includes the `dotnet-appsettings` compliance gate) + +## Execution Plan Summary +- **Risk Level**: High — three destructive-and-silent failure modes (customer website loss, Data Protection key-ring loss, automatic migration against production) +- **Stages to Execute**: Functional Design (×4: U1–U4), NFR Design (×2: U3, U4), Infrastructure Design (×2: U6, U7), Code Generation (×7), Build and Test, Deployment Setup, Monitoring Setup, Production Readiness Validation +- **Stages to Skip**: User Stories (no end-user functionality), NFR Requirements (already captured), plus per-unit skips as listed above + +## Current Status +- **Lifecycle Phase**: CONSTRUCTION +- **Current Stage**: Code Generation complete for Round 1 (U1 Hosting & Serving, U2 Data Durability) +- **Next Stage**: Round 2 — U3 Security Headers & CSP + U4 Observability, starting with Functional Design +- **Status**: Awaiting Round 1 code approval + +## Round 1 Verification Record (2026-07-27) +- `dotnet build SlpModularCms.sln -c Release` — 0 errors +- Backend tests — **253 passed, 0 failed** (Core 83, Availability 82, Identity 37, Master 51); baseline was 219 +- New EF migration `20260727203036_AddDataProtectionKeys` — verified purely additive +- Embedded placeholder resource name verified against the compiled assembly manifest +- **Carried to phase-level Build and Test**: composed-startup behaviour that needs a running host and a real database — `/admin` trailing-slash redirect, 404-vs-HTML for missing assets, SPA fallback and placeholder resolution, `/health` while availability-disabled, `MigrateCoreDatabase` against SQL Server, and both hosts starting +- **Deviation**: U1 plan Step 11 (`StaticContentTests`) not implemented — the code lives in `SlpModularCms.Api`, which has no test project by convention; behaviour carried to Build and Test instead. Recorded in the unit's `generation-summary.md` diff --git a/aidlc-docs/features/gitea-deployment-workflow/audit.md b/aidlc-docs/features/gitea-deployment-workflow/audit.md new file mode 100644 index 0000000..6693801 --- /dev/null +++ b/aidlc-docs/features/gitea-deployment-workflow/audit.md @@ -0,0 +1,802 @@ +# Audit Log — Gitea Deployment Workflow + +## Initial Request + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Ik wil een Gitea workflow gaan opzetten om de CMS te kunnen deployen. In de instructies moet ook behandeld worden waar de frontend van de website moet gaan komen, maar de workflow voor de website zelf zal hierin niet worden gebouwd, datwordt per werbsite in hun eigen workspace gedaan. Gebruik de aidlc workflow om alles op te zetten. Voor Uptime gebruik ik UptimeRobot, voor analytics gebruik ik Umami en voor logging console logs samen met Sentry. Ik heb in de workspace `K:\Development\SlpSoftware\Projects\SlpSoftware` al een werkende workflow. Dit is voor een react vite front-end, maar is een goed startpunt voor deze workspace. Op dit moment zijn er alleen een lokale omgeving, test en productie. Het doel van deze workflow en eventuele deployment-opzet is dat we boel moeten kunnen uploaden als een .NET-applicatie en dat we geen server-configuratie hoeven te doen omdat dat voor andere websites waarschijnlijk niet kan. Vandaar dat ik een opzet wilde met de api en de 2 frontends als 1 website." + +**AI Response**: "Started AI-DLC workflow. Loaded common rules and extension opt-in files. Performed workspace scan and reference-workspace analysis. Presented feature resolution, language preference, reverse-engineering staleness handling, and public-website scope questions in `aidlc-docs/feature-selection.md`." + +**Context**: INCEPTION — Workspace Detection. Multi-feature workspace detected (`active-features.md` exists, 5 existing features). Request does not match any existing feature; proposed new feature `gitea-deployment-workflow`. + +--- + +## Workspace Detection — Findings + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated analysis step)" + +**AI Response**: "Recorded workspace state and reference-workflow analysis." + +**Context**: INCEPTION — Workspace Detection + +### Workspace State +- **Existing Code**: Yes +- **Programming Languages**: C# (.NET 10), TypeScript/React +- **Build System**: MSBuild / `dotnet` (solution `SlpModularCms.sln`); pnpm + Vite for `frontend/` +- **Project Structure**: Modular monolith — `src/SlpModularCms.Api` (host), `src/SlpModularCms.Api.Slave`, `src/SlpModularCms.Core`, `src/SlpModularCms.Modules.{Master,Identity,Availability}` plus mirrored test projects; `frontend/` admin SPA outside `src/` +- **Workspace Root**: `K:\Development\Projects\SlpModularCms` +- **Project Type**: Brownfield +- **Git Branch**: `master` +- **CI/CD present**: No `.gitea/` directory exists in this repo yet + +### Reverse Engineering Artifact Status +- Artifacts exist in `aidlc-docs/_shared/reverse-engineering/` (8 files), dated **2026-06-16T20:30:00Z** +- Assessed as **stale**: commits after that date changed deployment-relevant structure — `master-cms-module`, `local-dev-master-slave-setup`, solution reorganization (`754bd97`), and single-host serving (`3885703`) +- Staleness handling deferred to the user via Question 3 in `feature-selection.md` + +### Reference Workflow Analysis (`K:\Development\SlpSoftware\Projects\SlpSoftware`) +Existing, working Gitea Actions setup for a React/Vite frontend — to be used as the starting point for this feature: + +- `.gitea/workflows/continuous_integration.yaml` — triggers on `pull_request`, `push` to `master`, and `workflow_dispatch` (with a `deploy_production` boolean input). Jobs: `config` (re-exports `env:` values as job outputs so they are usable in a reusable workflow's `with:` block), `prepare`, `build` (test env), `build-production` (separate build because `VITE_APP_ENV` is build-time), `test` (lint + unit tests), `deploy-test`, `deploy-production`. +- `.gitea/workflows/deploy.yaml` — reusable `workflow_call` workflow taking `artifact_name`, `environment`, `deploy_path`; downloads the artifact and uploads it over SCP using `sshpass` in a plain shell step (the `appleboy/scp-action` container action fails on the Podman-backed runner with a 409 attach error). +- Configuration split: non-secret values (Umami script URL / website IDs per environment, Sentry DSN) as Gitea Actions **variables**; SSH credentials as **secrets**. +- Operations artifacts under `aidlc-docs/features/react-frontend/operations/` include `deployment-instructions.md`, `deployment-plan.md`, `rollback-plan.md`, nginx example configs, Umami podman-compose setup, `monitoring-plan.md`, `monitoring-setup.md`, `umami-setup.md`, and a `production-readiness-checklist.md`. + +### Key Difference From The Reference +The reference deploys a **static `dist/` folder** to an nginx webroot on self-hosted Raspberry Pis. This feature must instead deploy a **published .NET application** to shared hosting with **no server configuration possible**, which is why the API serves the public website (`/`), the admin SPA (`/admin`), and the API (`/api/v1`) from a single host process. + +--- + +## Workspace Detection — Feature Resolution Answers + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "klaar, antwoorden staan in het bestand" — answers recorded in `aidlc-docs/feature-selection.md`: Q1 = "A, maak ook een nieuwe feature-branch aan voor deze feature.", Q2 = "A", Q3 = "B", Q4 = "A" + +**AI Response**: "Validated answers — no contradictions or ambiguities detected. Created git branch `feature/gitea-deployment-workflow`, created `aidlc-state.md` for the feature, and registered the feature in `aidlc-docs/active-features.md` with status 🔵 Inception. Proceeding to Reverse Engineering (full rerun)." + +**Context**: INCEPTION — Workspace Detection complete. Decisions: (1) new feature `gitea-deployment-workflow` on its own feature branch; (2) Documentation Language = English, Conversation Language = Dutch; (3) full reverse engineering rerun of all 8 `_shared/` artifacts; (4) public website handled as documentation/instructions only — its build/deploy workflow stays out of scope. + +--- + +## Conflict Detection + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated analysis step)" + +**AI Response**: "Compared affected components against other non-complete features." + +**Context**: INCEPTION — Workspace Detection. One other active feature: `tech-debt-backlog` (🔵 Inception), affecting `Modules.Master` and `Frontend`. Overlap with this feature is limited to `Frontend` (this feature only touches its build/deploy configuration, not its source). Informational only — does not block progress; developer handles at merge time. + +--- + +## Reverse Engineering — Start + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — stage entered automatically after Workspace Detection)" + +**AI Response**: "Loaded `inception/reverse-engineering.md`. Started full rerun of all 8 shared artifacts per the user's Q3 = B decision." + +**Context**: INCEPTION — Reverse Engineering. Full rerun, not a targeted refresh. + +--- + +## Reverse Engineering — Complete + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated analysis and generation)" + +**AI Response**: "Regenerated all 8 artifacts plus the timestamp file in `aidlc-docs/_shared/reverse-engineering/`. Updated the feature's `aidlc-state.md` with Reverse Engineering Status. Presented the completion message and awaiting explicit approval." + +**Context**: INCEPTION — Reverse Engineering complete. + +### Scope of analysis +122 C# files (excluding `bin`/`obj`) and 111 TypeScript/TSX files under `frontend/src`, plus solution, project, configuration and lock files. + +### Verification performed by execution (not inferred) +- `dotnet build SlpModularCms.sln -c Release` — 0 errors, 50 warnings +- `dotnet test SlpModularCms.sln -c Release` — 219 tests: Core 54, Identity 37, Availability 78, Master 50; all passed +- `cd frontend && pnpm test` — 34 files, 213 tests, all passed +- `cd frontend && pnpm run lint` — **FAILS**: 5 errors, 1 warning +- `dotnet list package --vulnerable --include-transitive` — `Microsoft.OpenApi` 2.0.0 (High), `System.Security.Cryptography.Xml` 10.0.9 (High) + +### Key deployment-relevant findings +1. No CI/CD exists — the repository has no `.gitea/workflows/`. +2. `dotnet publish` on `SlpModularCms.Api` requires Node and pnpm: the `BuildAndCopyAdminFrontend` target runs `pnpm install --frozen-lockfile` and `pnpm build` before publish. +3. The admin SPA bundle is environment-specific — `frontend/src/lib/config.ts` requires `VITE_API_BASE_URL` as an absolute URL, so test and production cannot share one artifact unless a same-origin default is introduced. +4. No health-check endpoint exists for uptime monitoring; the three anonymous candidates (`/api/v1/Availability/status`, `/api/v1/System/capabilities`, `/`) each prove something different, and only `capabilities` goes red when an instance is deliberately disabled. +5. The public website at `/` is not behind the availability gate — static files short-circuit before `AvailabilityMiddleware` is installed. +6. `ModuleOrchestrator` discovers modules by globbing `SlpModularCms.Modules.*.dll` in the app base directory, so deployment content determines capability; load failures are logged, not fatal. +7. Data Protection uses the default file-system key ring with no persistent store — a redeploy that discards it makes stored slave API keys unreadable. +8. No forwarded-headers middleware while `UseHttpsRedirection()` runs early — relevant behind a hosting provider's TLS-terminating proxy. +9. `ApplicationDbContext` migrations are never applied automatically, while both module contexts self-migrate at startup. +10. No `Test` environment configuration exists (`appsettings.Test.json` absent, no `ASPNETCORE_ENVIRONMENT` value defined for test). +11. Observability is entirely greenfield — no Sentry, Umami, structured logging, metrics or tracing anywhere. Production log level is `Warning`. +12. No `packages.lock.json` for any .NET project, so NuGet restore is not reproducible (the frontend is locked via `pnpm-lock.yaml`). +13. `AvailabilityMiddleware.IsAdminBypass` reads the JWT without validating its signature, so the availability gate is bypassable by an unauthenticated caller (not a privilege escalation — protected endpoints still authenticate). + +--- + +## Reverse Engineering — Change Requested + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "De availability is geen health check. Die zou dan nog apart moeten worden ingeregeld. De availabillity en capabilities is functionaliteit van de CMS zelf en/of tussen slave en master." + +**AI Response**: "Correction accepted and applied. The earlier framing of `/api/v1/Availability/status` and `/api/v1/System/capabilities` as candidate monitoring endpoints was wrong. Both are CMS domain functionality — availability is the product's own on/off state (local switch plus master gate) and capabilities reports loaded modules; both also serve the master↔slave protocol. Neither reflects application health and neither may be repurposed for monitoring. Updated `api-documentation.md` (§ Observability endpoints rewritten), `architecture.md` (deployment-relevant observation 4) and `code-quality-assessment.md` (technical-debt item 4) to state that a dedicated health check is a separate concern still to be designed: its own endpoint, outside `/api/v1` domain routing and outside the availability gate, reporting infrastructure liveness (process up, database reachable, migrations applied) rather than product state." + +**Context**: INCEPTION — Reverse Engineering. Supersedes deployment-relevant finding 4 as originally logged in the "Reverse Engineering — Complete" entry above. Carried forward as a requirement for the Operations phase: a health-check endpoint must be built for UptimeRobot rather than reusing domain endpoints. + +--- + +## Reverse Engineering — Scope Addition: Health Check Endpoint + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Is er in .NET niet iets simpels om health checks toe te toevegen? Iets van AddHealthChecks()? Als het zo simpel is mag het erbij omdat het wel kan helpen bij deze feature. Anders wordt dat los opgepakt" + +**AI Response**: "Confirmed it is that simple, and verified package availability. `builder.Services.AddHealthChecks()` and `app.MapHealthChecks(\"/health\")` require NO package — both are in the ASP.NET Core shared framework; default response is plain text `Healthy`/200 or `Unhealthy`/503, which is directly consumable by UptimeRobot. A database probe costs one package, `Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore` 10.0.9 (verified available on nuget.org and in line with the existing 10.0.x dependencies), plus `.AddDbContextCheck()`, which does `CanConnectAsync` by default and can optionally report pending migrations — extra valuable here because `ApplicationDbContext` never migrates automatically. Flagged one codebase-specific requirement: `MapHealthChecks` registers an endpoint and middleware runs first, so `AvailabilityMiddleware` would return 503 for `/health` on a switched-off instance — reproducing exactly the conflation the user corrected. `/health` must therefore be added to `AvailabilityMiddleware._bypassPrefixes`. Recorded as in scope in `aidlc-state.md` and updated `api-documentation.md` and `code-quality-assessment.md` accordingly." + +**Context**: INCEPTION — Reverse Engineering. Scope decision: a dedicated health-check endpoint is IN SCOPE for this feature (rather than being picked up separately). Separation of concerns is explicit: `/health` reports infrastructure liveness; Availability and capabilities remain CMS domain functionality and are never used for monitoring. + +--- + +## Reverse Engineering — Approved + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Continue to Next Stage" + +**AI Response**: "Reverse Engineering approved. Marked complete in `aidlc-state.md` and proceeded to Requirements Analysis." + +**Context**: INCEPTION — Reverse Engineering approved, including the health-check scope addition and the availability/capabilities correction. + +--- + +## Requirements Analysis — Intent Analysis + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated analysis step)" + +**AI Response**: "Loaded architecture.md, component-inventory.md and technology-stack.md from `_shared/reverse-engineering/`, plus the reference project's operations artifacts (deployment-plan.md, monitoring-plan.md) at `K:\Development\SlpSoftware\Projects\SlpSoftware\aidlc-docs\features\react-frontend\operations\`. Performed intent analysis and generated 25 clarifying questions." + +**Context**: INCEPTION — Requirements Analysis, Steps 1–6. + +### Intent Analysis +- **Request Clarity**: Clear on goals and tooling choices, incomplete on hosting/transport specifics — the user named the target stack (Gitea Actions, UptimeRobot, Umami, Sentry + console logging), the environments (local/test/production), the reference implementation, and the core constraint (upload as a .NET application, no server configuration possible). What is not yet specified is how the shared host is reached, how migrations and configuration are delivered, and how the public website's `wwwroot` content is protected from the CMS deploy. +- **Request Type**: New Feature (CI/CD and operational tooling), with supporting Enhancement work in the application itself (health-check endpoint, and candidate changes for frontend config, Data Protection key ring, and migration strategy). +- **Initial Scope Estimate**: Multiple Components — new `.gitea/workflows/` definitions, changes to `SlpModularCms.Api` (health checks, bypass list, possibly key ring and migrations), possibly `frontend/src/lib/config.ts`, new `appsettings.Test.json`, plus documentation and operations artifacts. +- **Initial Complexity Estimate**: Moderate-to-Complex. Not algorithmically hard, but the constraint set is unusually tight: one host process serving three surfaces, a customer-owned `wwwroot` that must survive deploys, shared hosting with no server configuration, self-migrating module contexts alongside a manually-migrated Core context, and an environment-specific frontend bundle. +- **Requirements Depth**: **Comprehensive** — justified by the number of interacting constraints, the deployment risk (a wrong `wwwroot` sync destroys a customer's website; a lost Data Protection key ring silently breaks master↔slave), and the fact that several findings require explicit product decisions rather than defaults. + +### Notable reference-project context loaded +- Deploy transport in the reference is `scp` + `sshpass` in a plain shell step, because container-based actions fail on the Podman-backed self-hosted runner with a 409 attach error. Shared hosting is unlikely to offer SSH, so transport is an open question here. +- The reference deliberately splits test (automatic on `master`) from production (manual `workflow_dispatch` with a `deploy_production` checkbox), and builds twice because `VITE_APP_ENV` is build-time. +- The reference uses one Sentry project with environment tags, a self-hosted Umami on `analytics.slpsoftware.nl` with per-environment website IDs, and UptimeRobot for uptime. + +### Questions generated +25 questions in `inception/requirements/requirement-verification-questions.md`, grouped: A hosting/transport (3), B public website in `wwwroot` (2), C workflow setup and gates (4), D database and configuration (4), E monitoring and observability (7), F rollback and reliability (2), G extensions and phasing (3 — security opt-in, PBT opt-in, Operations phase inclusion). + +--- + +## Requirements Analysis — Answers Received, Clarification Round Opened + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "klaar, antwoorden staan in het bestand" — answers recorded in `inception/requirements/requirement-verification-questions.md`: Q1 = "X, Op de pi kan ik met FileZilla ook verbinden met SFTP. Is dat voldoende om FTP als uitgangspunt te nemen? Anders beginnen met opzetten zoals de referentie met SSH/SCP en later aanpassen naar FTPS als dat nodig is.", Q2 = B, Q3 = B, Q4 = "C, noem de map \"web\" in plaats van \"site\", Dus dan krijgen we uiteindelijk 3 mappen in wwwroot: admin, web en eventueel een map voor de API (indien nodig).", Q5 = B, Q6 = A, Q7 = A, Q8 = A, Q9 = "A, B, C, D, E", Q10 = C, Q11 = "X, Het mag naar same-origin, maar de frontend kan ook een aparte test-build hebben net als de referentie website. Ook is het zo dat nu lokaal wel een url moet worden opgegeven. Als dat verder ook blijft werken hoeft de url variabele niet blokkerend te zijn voor een enkele bundel", Q12 = A, Q13 = A, Q14 = A, Q15 = A, Q16 = C, Q17 = A, Q18 = C, Q19 = A, Q20 = B, Q21 = C, Q22 = A, Security extension = A, PBT extension = C, Operations phase = A. + +**AI Response**: "Loaded the security-baseline extension rules (opted in). Validated all answers and detected one direct question from the user plus three genuine conflicts; opened a clarification round in `inception/requirements/requirement-clarification-questions.md` with 5 questions." + +**Context**: INCEPTION — Requirements Analysis, Step 6 contradiction/ambiguity analysis. + +### Extension configuration decided +- Security Baseline: **Enabled** (blocking) — full rules file loaded. +- Property-Based Testing: **Disabled** — rules file deliberately not loaded. +- Operations Phase: **Yes**, decided at Requirements Analysis. + +### Contradictions and ambiguities detected +1. **Q1 answered about the wrong environment, plus a protocol misconception.** The user asked whether FileZilla connecting over SFTP means FTP can be the baseline. It does not: SFTP is file transfer inside an SSH session (proving SSH/SCP works, exactly the reference approach), whereas FTPS is a separate protocol needing its own server. More importantly, Q1 asked about **production** but the answer describes the Pi, which Q2 assigns to **test**. Commit `3885703` cites shared hosting (mijnhostingpartner.nl) as the reason for single-host serving, so where production actually runs is unresolved. +2. **Q11 (single bundle) contradicts Q14/Q15/Q20 (build-time environment tagging).** Same-origin API URL is implementable, but Sentry's `environment` tag and Umami's per-environment website ID are Vite build-time values, so one `dist/` cannot serve both test and production — the very reason the reference project has a separate `build-production` job. Either two builds are accepted or the environment config must become runtime-delivered. +3. **Q22 (`app_offline.htm`) is inapplicable to Q2's chosen test environment.** `app_offline.htm` is an IIS ASP.NET Core Module feature; on the Pi the app runs as a Kestrel process (nginx + systemd), where the file is ignored and an upload would overwrite DLLs of a live process. Also noted: any clean stop takes the public website in `wwwroot/web/` offline too, even though it is unrelated to the CMS deploy. +4. **Blocking security rules expand scope beyond the workflow** (raised for an explicit decision rather than silently expanding or skipping): SECURITY-04 requires HTTP security headers, of which the app currently sets none — and because server configuration is explicitly unavailable, they must be app middleware; a CSP additionally constrains the externally-owned public website and needs explicit exceptions for Umami and Sentry. SECURITY-14 requires alerting plus 90-day log retention, which conflicts with Sentry's free-plan 30-day event retention chosen in Q16. + +### Answers accepted without conflict (noted for the requirements document) +- Q4 = C with the folder named **`web`**, not `site` — final `wwwroot` layout: `admin/`, `web/`, and optionally an API folder if needed. This resolves the `wwwroot` destruction risk structurally rather than by exclusion lists. +- Q10 = C (auto-migrate `ApplicationDbContext` at startup) sits consistently with Q17 = A (liveness-only health check): if the database is unreachable, startup migration fails, so liveness implicitly covers database availability at boot. +- Q3 = B means the toolchain is installed explicitly in the workflow (`actions/setup-dotnet`, `pnpm/action-setup`) rather than assumed present on the runner — which also satisfies SECURITY-10's pinned-tool-version requirement. + +--- + +## Requirements Analysis — Clarification Answers Received + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "klaar, antwoorden staan in het bestand" — answers recorded in `inception/requirements/requirement-clarification-questions.md`: CQ1 = C, CQ2 = C, CQ3 = C, CQ4 = A, CQ5 = B. + +**AI Response**: "All clarifications unambiguous; no further contradictions detected. Generated `inception/requirements/requirements.md` at comprehensive depth. Updated `aidlc-state.md` with Extension Configuration and Operations Configuration." + +**Context**: INCEPTION — Requirements Analysis, Steps 7–8. + +### Clarifications resolved +- **CQ1 = C**: Production runs on the Pi for now, with shared hosting as the eventual target. Transport must be structured so FTPS can be added alongside SSH/SCP without restructuring the workflow. Resolves the Q1 environment mix-up; SSH/SCP is correct for both environments today. +- **CQ2 = C**: Two builds now (reference pattern), with runtime-delivered environment configuration recorded as a possible later improvement. Same-origin API base URL is still adopted for its own sake. +- **CQ3 = C**: Atomic release-directory switch instead of `app_offline.htm`. Supersedes Q22 = A, which was inapplicable to the Pi. +- **CQ4 = A**: Both SECURITY-04 (security headers middleware) and SECURITY-14 (Sentry alert rules) are addressed within this feature, with log retention documented as a deliberate deviation. +- **CQ5 = B**: CSP strict for `/admin` and `/api/v1`, more permissive for the public website. + +### Derived requirement stated rather than asked (ASM-01) +Combining Q4 = C (`wwwroot/web/`) with CQ3 = C (atomic release switch) creates a failure mode neither answer covers on its own: switching the release directory would discard the customer's website along with the old release. `wwwroot/web/` must therefore live outside the swapped release directory and be linked into it. This follows necessarily from the two decisions, so it is recorded as assumption ASM-01 with its rationale and consequence rather than opening a third question round — the user can reject it at approval. + +### Artifact produced +`inception/requirements/requirements.md` — 23 functional requirements, 10 non-functional requirements, 32 traced decisions (D-01…D-32), 7 assumptions, 4 open items, 4 documented security deviations, and a full SECURITY-01…15 compliance assessment. + +### Security Compliance summary (Security Baseline extension, enabled and blocking) +Addressed: SECURITY-01, 03, 04, 06, 09, 10, 11, 13, 14 (with deviation DEV-01). Pre-existing and unchanged: SECURITY-05, 08, 12, 15. N/A: SECURITY-02 (no load balancer, API gateway or CDN in this architecture). Partially N/A: SECURITY-07 (no cloud networking; applicable parts documented as host setup). + +**No blocking security findings** — every rule is either addressed by a requirement, pre-existing and unchanged, marked N/A with rationale, or recorded as an explicitly accepted deviation (DEV-01…DEV-04, all pre-existing or cost-driven, none introduced by this feature). + +Two security-relevant findings are logged as open items rather than silently dropped: OPEN-01 (correlation/request ID required by SECURITY-03 does not exist yet) and OPEN-02 (`AvailabilityMiddleware.IsAdminBypass` reads the JWT without validating its signature — pre-existing, needs an explicit owner). + +--- + +## Requirements Analysis — Approved + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Continue to Next Stage" + +**AI Response**: "Requirements approved (User Stories option offered and not taken). Marked complete in `aidlc-state.md` and proceeded to Workflow Planning." + +**Context**: INCEPTION — Requirements Analysis approved, including assumption ASM-01 and the four documented security deviations. + +--- + +## Workflow Planning — Complete + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated planning step)" + +**AI Response**: "Loaded all prior context (4 reverse-engineering artifacts, requirements.md, both question files with answers). Performed transformation-scope, change-impact, component-relationship and risk analysis. Created `inception/plans/execution-plan.md` with a validated Mermaid workflow diagram. Updated `aidlc-state.md` with the full stage matrix, execution plan summary and current status." + +**Context**: INCEPTION — Workflow Planning, Steps 1–8. + +### Risk assessment: HIGH +Rated High rather than Medium because three failure modes are destructive **and** silent: +1. An atomic release switch carrying `wwwroot/web/` inside the swapped directory would destroy the customer's public website on every deploy (ASM-01) — data loss, not a bug. +2. Losing the Data Protection key ring makes stored slave API keys undecryptable; the symptom resembles a network fault and would be misdiagnosed. +3. Automatic Core migrations at startup (FR-11) make deployment self-contained and simultaneously remove the human gate before a migration runs against production — which is why FR-20 (pre-deploy backup) and forward-compatible migrations are load-bearing rather than optional. +Also: the deploy path cannot be fully tested in CI (needs the real Pi, SSH credentials and a database), and CSP failures only manifest in a real browser on pages this repository does not own. + +Rollback complexity: Moderate. Testing complexity: Complex. + +### Stage decisions +- **User Stories — SKIP**: infrastructure/operations work with no new end-user functionality or persona; the website-builder "persona" is properly served by FR-09's documented contract. Offered at Requirements Analysis approval and not requested. +- **Application Design — EXECUTE**: genuine component-boundary decisions — whether cross-cutting registrations (health checks, security headers, Data Protection) belong in `Core` (inherited by both hosts including the Slave) or in `Api`; redesigning static-file serving for two mounts and two fallbacks; a CSP path-scoping mechanism that does not exist yet; and a deploy-transport abstraction admitting FTPS later (NFR-09). +- **Units Generation — EXECUTE**: 7 units with a load-bearing ordering constraint — durability changes must precede the first automated deploy, and quality-gate fixes must precede switching blocking gates on. +- **Functional Design — EXECUTE units 2, 3, 4 only**: those contain real behavioural logic; units 1, 5, 6, 7 are lint/package changes, declarative YAML and documentation. +- **NFR Requirements — SKIP all units**: already captured comprehensively with traceability in `requirements.md` § 5 (NFR-01…10) and § 6 (full SECURITY-01…15 assessment); the tech stack is fixed. +- **NFR Design — EXECUTE units 3, 4 only**, recorded as a **deliberate deviation** from the workflow's default coupling (NFR Design skipped when NFR Requirements is skipped). Rationale: for these two units the NFR *is* the deliverable — CSP composition and path-scoping plus HSTS-behind-proxy behaviour (SECURITY-04), and structured-logging shape, correlation ID (OPEN-01), alertable-event definition and PII exclusion (SECURITY-03, SECURITY-14). Those are pattern decisions, not requirement decisions, so skipping requirements while designing patterns is the correct split rather than an oversight. +- **Infrastructure Design — EXECUTE units 6, 7 only**: the host layout (release-directory scheme, where `wwwroot/web/` lives so ASM-01 holds, symlink/mount strategy, process restart, backup placement, transport abstraction) and the website contract that depends on it. Produces documented procedure rather than IaC, because NFR-01 forbids server configuration. +- **Operations — all three stages EXECUTE**: Operations Configuration = Yes, and for this feature it is the centre of gravity (FR-17, FR-19, FR-20, FR-23), including the `dotnet-appsettings` compliance gate at Production Readiness Validation. + +### Proposed unit sequence (finalised at Units Generation) +1. Quality Gate Prerequisites (FR-21, FR-22) — first, so blocking gates are not red on arrival +2. Hosting Layout & Data Durability (FR-07, FR-08, FR-10, FR-11, FR-12) — before any deploy exists +3. HTTP Security Headers & CSP (FR-18) +4. Observability Integration (FR-13, FR-14, FR-15, FR-16) +5. CI Workflow (FR-01, FR-05) +6. Deploy Workflow (FR-02, FR-03, FR-04, FR-06, FR-20) +7. Repository Documentation (FR-09 + README/`.env.example` updates) + +### Coordination note +`SlpModularCms.Api.Slave` inherits every `Core` change and has no test project by design, so it is verified at Build and Test by actually starting it. The `/health` bypass in `Modules.Availability` must land in the same unit as the health endpoint, or `/health` returns 503 on a disabled instance — the exact conflation the user corrected during Reverse Engineering. + +### Security Compliance (Security Baseline extension — enabled, blocking) +No new blocking findings at this stage. Workflow Planning produces a plan, not code or configuration; the SECURITY-01…15 assessment recorded in `requirements.md` § 6 remains current and is carried forward unchanged. SECURITY-10 (pinned tool versions, no `latest` tags) and SECURITY-13 (access-controlled, auditable pipeline definitions) are explicitly reflected in the plan's Unit 5 and Unit 6 scope. + +--- + +## Workflow Planning — Approved + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Continue to Next Stage" + +**AI Response**: "Execution plan approved, including the User Stories and NFR Requirements skips and the deliberate NFR Design coupling deviation. Proceeded to Application Design." + +**Context**: INCEPTION — Workflow Planning approved. Next stage: Application Design. + +--- + +## Application Design — Plan Created + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated planning step)" + +**AI Response**: "Analyzed context and inspected both host `Program.cs` files to establish the composition baseline. Created `inception/plans/application-design-plan.md` with 8 design steps and 12 embedded design questions." + +**Context**: INCEPTION — Application Design, Steps 1–6. + +### Baseline established by inspection +`SlpModularCms.Api/Program.cs` and `SlpModularCms.Api.Slave/Program.cs` are near-identical, differing only in the static-file and SPA-fallback section. This feature adds the same new registrations to both, which would grow that duplication — raised as Question 9 rather than silently choosing. + +### Question areas +1. Home project for the four new cross-cutting concerns (`Core` versus host) — consequential because `Core` is inherited by the Slave +2. What the Slave host should and should not receive +3. Static-file serving model for the two mounts +4. Security-headers placement in the pipeline — noting that static files short-circuit, so headers registered after them never reach the public website +5. Configurability surface for the security headers +6. CSP path-scoping mechanism +7. Which `DbContext` hosts the Data Protection keys +8. Migration-at-startup failure behaviour, and its interaction with the liveness health check +9. Whether to extract the duplicated host composition +10. Sentry registration shape +11. Deploy transport abstraction (workflow layer, not C#) +12. OPEN-02 ownership — fold into this feature, defer to `tech-debt-backlog`, or accept as documented risk + +--- + +## Application Design — Answers Received, Follow-up Round Opened + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "klaar, antwoorden staan in het bestand" — answers in `inception/plans/application-design-plan.md`: Q1 = A, Q2 = "A, Want de Slave is wel een API die laat zien hoe een klant-API eruit kan komen te zien.", Q3 = A, Q4 = C, Q5 = B, Q6 = B, Q7 = A, Q8 = A, Q9 = A, Q10 = B, Q11 = B, Q12 = A. + +**AI Response**: "Analyzed all 12 answers per Step 8. Ten are unambiguous and mutually consistent. Two required follow-up, added to the plan as Part 3." + +**Context**: INCEPTION — Application Design, Steps 7–9. + +### Decisions accepted +- **Q1 = A**: all four new cross-cutting concerns live in `Core` as extension methods — both hosts get identical behaviour, no duplication. +- **Q2 = A**: the Slave receives everything except the static-file mounts. User rationale recorded: the Slave demonstrates what a customer-facing API instance looks like, so it should behave like production rather than like a stripped-down test harness. This elevates the Slave from "local dev tool" to "reference instance", which is a meaningful reframing. +- **Q3 = A**: two explicit `UseStaticFiles` registrations, each with its own `PhysicalFileProvider` and `RequestPath`. +- **Q7 = A**: Data Protection keys live in `ApplicationDbContext`, which will implement `IDataProtectionKeyContext`. Requires a new Core migration — acceptable because FR-11 now migrates that context automatically. +- **Q8 = A**: fail fast on migration failure. Consistent with the liveness-only health check (D-21): a process that cannot migrate does not start, so `/health` stops answering and UptimeRobot goes red — the monitoring signal is meaningful precisely because of this choice. +- **Q9 = A**: the two `Program.cs` files stay separate. With Q1 = A the shared logic is in `Core` extension methods, so what remains duplicated is the explicit list of calls — which is intentional readability, not accidental duplication. +- **Q10 = B**: Sentry and logging configuration registered separately, so structured logging works without Sentry. +- **Q11 = B**: one reusable workflow per transport (`deploy-scp.yaml`, later `deploy-ftps.yaml`) with an identical input interface. +- **Q12 = A**: **OPEN-02 is folded into this feature.** `AvailabilityMiddleware.IsAdminBypass` will validate the token properly instead of reading it unvalidated. Lands in the same unit as the `/health` bypass, since both touch the same middleware. This closes a SECURITY-08 finding and requires a new functional requirement — to be added as FR-24 and OPEN-02 marked resolved. + +### Follow-ups raised +1. **Q4 = C would weaken security if implemented literally.** The user chose "headers only on SPA/HTML responses, not assets". That is correct for `Content-Security-Policy`, `X-Frame-Options` and `Referrer-Policy`, but wrong for two headers: `X-Content-Type-Options: nosniff` exists specifically to stop MIME-sniffing of **non-HTML** resources, so restricting it to HTML removes it exactly where it does its job; and `Strict-Transport-Security` is a host-level transport directive that a visitor whose first request is an asset would never receive. Follow-up offers per-header scoping as the recommended reading of the same intent. +2. **Q5 = B and Q6 = B appear to conflict** (minimal configuration versus a configurable path-to-policy rule list). One reading makes them consistent: policy *definitions* in code (Q5), path-to-policy *assignment* and environment-specific origins in configuration (Q6). Presented with a concrete `appsettings` shape for confirmation rather than assumed. + +--- + +## Application Design — Follow-up Answers and Artifacts Generated + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "klaar, antwoorden staan in het bestand" — follow-up answers in `inception/plans/application-design-plan.md` Part 3: FU1 = A, FU2 = A. + +**AI Response**: "Both follow-ups resolved. Updated `requirements.md` (added FR-24, closed OPEN-02, upgraded SECURITY-08 to Improved, count now 24 FRs). Generated all five Application Design artifacts and marked every plan step complete." + +**Context**: INCEPTION — Application Design, Steps 10–12. + +### Follow-up resolutions +- **FU1 = A**: per-header scoping. `X-Content-Type-Options` and `Strict-Transport-Security` apply to **all** responses; `Content-Security-Policy`, `X-Frame-Options` and `Referrer-Policy` to HTML responses only. This preserves the user's intent from Q4 = C while closing the gap that a literal reading would have created — `nosniff` exists precisely to protect non-HTML resources, so restricting it to HTML would have removed it where it does its job. +- **FU2 = A**: confirmed the synthesis of Q5 = B and Q6 = B — policy *definitions* in code, path *assignment* and environment-specific *origins* in configuration. A misconfiguration can misroute a path but cannot invent a broken policy. + +### Requirements updated +- **FR-24 added**: validate the token in the availability gate's admin bypass (Q12 = A), replacing the unvalidated `ReadJwtToken` call. +- **OPEN-02 closed**: folded into this feature, landing in the same unit as the `/health` bypass. +- **SECURITY-08 upgraded** from "Pre-existing, unchanged" to "**Improved**". +- Totals now: 24 FRs, 10 NFRs, 32 decisions, 7 assumptions, 3 remaining open items, 4 documented deviations. + +### Artifacts generated +`inception/application-design/`: `components.md`, `component-methods.md`, `services.md`, `component-dependency.md`, `application-design.md`. + +**14 code components** (9 new, 5 modified) plus 2 workflow components. All in `Core` per Q1 = A except the static mounts, which are `Api`-only per Q2 = A. + +### TWO CONFLICTS DISCOVERED DURING DESIGN +Both found by tracing composition order; both would have produced code that looks correct while achieving nothing. + +1. **Duplicate `AddDataProtection()` would silently defeat FR-12.** `AvailabilityModule.RegisterServices` and `MasterModule.RegisterServices` each call `services.AddDataProtection()`. Module registration runs *after* the host's, so those bare calls would override the persistent key store from `AddCmsDataProtection()`. FR-12 would appear implemented, registration tests would pass, and the key ring would still be ephemeral — so the first atomic release switch would silently break master↔slave trust in a way presenting as a network fault. Resolution: remove both module calls; host configures once. Assigned to Unit 2 with a test asserting the persistent store survives module registration. + **Second-order finding**: the Data Protection **application discriminator must be set explicitly** — it defaults to deriving from the content root path, which changes on every atomic release-directory switch (FR-06), defeating FR-12 by a different route. +2. **`AvailabilityMiddleware` runs before `UseAuthentication()`**, so `HttpContext.User` is unpopulated when the admin bypass is evaluated. FR-24 therefore has two viable implementations — validate the token in the middleware with shared `TokenValidationParameters`, or move authentication earlier (smaller change, wider blast radius across all modules). Recorded rather than decided; resolved in Functional Design for Unit 2. + +### Additional design obligations recorded +- `C-10` must tolerate a **missing `wwwroot/web/` at startup** — a fresh deployment has none until a website workspace deploys into it, and the CMS must still start and serve `/admin` and `/api/v1`. +- An unknown CSP policy name must fail at **startup**, not degrade per request (SECURITY-15, fail closed). + +### Security Compliance (Security Baseline extension — enabled, blocking) +Addressed: SECURITY-01, 03, 04, 06, 09, 10, 11, 13, 14 (with DEV-01). **Improved: SECURITY-08** (FR-24 fixes a pre-existing finding). Unchanged: SECURITY-05, 12, 15. N/A: SECURITY-02. Partially N/A: SECURITY-07. + +**No blocking security findings.** + +--- + +## Application Design — Approved + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Continue to Next Stage" + +**AI Response**: "Application Design approved, including FR-24, the two composition conflicts carried to Unit 2, and the additional design obligations. Proceeded to Units Generation Part 1 (Planning)." + +**Context**: INCEPTION — Application Design approved. Next stage: Units Generation. + +--- + +## Units Generation — Plan Created (Part 1) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated planning step)" + +**AI Response**: "Created `inception/plans/unit-of-work-plan.md` with 6 decomposition steps and 8 questions covering unit boundaries, sequencing, completion criteria, version control and artifact mapping." + +**Context**: INCEPTION — Units Generation, Part 1 Steps 1–5. + +### Question areas +1. Whether the proposed 7-unit split holds +2. Whether Unit 1 (lint fixes, package pins) warrants its own unit +3. Whether Unit 2 is too large — it bundles serving, health/gate and data-durability concerns whose only commonality is that all must land before the first deploy +4. Sequencing: strictly serial versus grouping independent units into one approval round +5. Per-unit definition of done, noting that Q9 of the requirements deliberately excluded a coverage gate +6. Commit and review granularity on `feature/gitea-deployment-workflow` — nothing has been committed yet; all aidlc documentation is currently uncommitted working-tree changes +7. What the mandatory `unit-of-work-story-map.md` maps to, given User Stories was skipped — proposed mapping the 24 FRs as the equivalent +8. Whether documentation belongs in Construction Unit 7 or the Operations phase, given Operations already produces deployment, rollback, monitoring and readiness documents + +--- + +## Units Generation — Answers Received and Artifacts Generated (Part 2) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "ik heb de vragen beantwoord" — answers in `inception/plans/unit-of-work-plan.md`: Q1 = C, Q2 = B, Q3 = B, Q4 = B, Q5 = B, Q6 = A, Q7 = A, Q8 = A. + +**AI Response**: "Analyzed all answers per Step 7 — no contradictions or ambiguities found, so no follow-up round was needed. Executed the plan and generated all three unit artifacts. Marked every plan step complete and updated `aidlc-state.md`." + +**Context**: INCEPTION — Units Generation, Part 1 Steps 6–11 and Part 2 Steps 12–15. + +### Decomposition outcome +Two changes to the proposed split that cancel out numerically — **still 7 units**, but with boundaries drawn along the work rather than along the deadline: +- **Q3 = B**: the oversized Unit 2 split into **U1 Hosting & Serving** and **U2 Data Durability**. It had bundled serving, middleware and persistence work whose only commonality was "must land before the first deploy". +- **Q2 = B**: the quality-gate prerequisites (5 lint fixes, 2 package pins) merged into the CI unit, so gates and their prerequisites land in one commit and the pipeline is never red on arrival. + +Final units: U1 Hosting & Serving · U2 Data Durability · U3 Security Headers & CSP · U4 Observability · U5 CI Workflow & Gates · U6 Deploy Workflow · U7 Documentation. + +### Execution rounds (Q4 = B) +R1 = U1 + U2 (mutually independent) · R2 = U3 + U4 (tightly coupled — U3's CSP needs U4's origins) · R3 = U5 + U6 (one shared input interface) · R4 = U7. One commit per unit; single PR at the end (Q6 = A). A round is an approval boundary, not a commit boundary. + +### Consequence of Q2 = B, recorded and mitigated +Merging the lint fixes into U5 means `pnpm run lint` stays failing through U3 and U4, and U4 changes frontend files — so new violations would hide among the five pre-existing ones. Mitigation recorded in `unit-of-work-dependency.md`: run lint on **changed files** during U4. The file sets are disjoint (U5 fixes four component files; U4 touches `main.tsx`, `config.ts` and a new Umami component), so there is no merge risk — only a detection gap. + +### Change to the execution plan's per-unit stage assignment +The plan assigned Functional Design to "units 2, 3, 4" under the pre-split numbering. After the split, **both halves need it**: U1 for the FR-24 pipeline-ordering decision and the missing-`wwwroot/web/` startup behaviour, U2 for the registration-order conflict and migration failure semantics. Functional Design is therefore **×4 (U1–U4)** rather than ×3. Recorded in `unit-of-work-story-map.md`, which is authoritative over the execution plan's pre-split table, and reflected in `aidlc-state.md`. + +### Coverage verification +All 24 functional requirements assigned, none orphaned or duplicated. All 16 design components assigned. Two requirements deliberately split across a boundary: FR-08 (serving precondition in U1, deployment behaviour in U6) and FR-19 (event emission in U4, alert rules in Operations). Every carried-forward item — the two Application Design conflicts, OPEN-01, OPEN-03, ASM-01, and the two additional design obligations — has an owning unit and a resolving stage. + +### Artifacts generated +`inception/application-design/`: `unit-of-work.md`, `unit-of-work-dependency.md`, `unit-of-work-story-map.md`. + +### Security Compliance (Security Baseline extension — enabled, blocking) +No new blocking findings. This stage produces a decomposition, not code. Relevant to the assessment: U2's ordering constraint directly protects SECURITY-01 and SECURITY-13 (key-ring integrity across deploys), U5 carries SECURITY-10 (vulnerability gate, pinned tool versions), and U1 carries the SECURITY-08 improvement (FR-24). The `requirements.md` § 6 assessment remains current. + +--- + +## Units Generation — Approved + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Continue to Next Stage" + +**AI Response**: "Units approved. Entered the CONSTRUCTION PHASE at Round 1 (U1 Hosting & Serving + U2 Data Durability), starting with Functional Design for both units." + +**Context**: INCEPTION complete. Transition to CONSTRUCTION PHASE. + +--- + +## Functional Design — Plans Created for U1 and U2 (Round 1) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated planning step)" + +**AI Response**: "Created functional design plans and dedicated question files for both Round 1 units, per the rule that plan files contain only checklists and questions live in separate files." + +**Context**: CONSTRUCTION — Functional Design, Steps 1–5, for U1 and U2 together (Round 1 grouping per Q4 = B). + +### Artifacts created +- `construction/plans/u1-hosting-serving-functional-design-plan.md` — 6 steps +- `construction/plans/u1-hosting-serving-fd-questions.md` — 5 questions +- `construction/plans/u2-data-durability-functional-design-plan.md` — 6 steps +- `construction/plans/u2-data-durability-fd-questions.md` — 5 questions + +### U1 question areas +1. **FR-24 implementation approach** — the § 5.2 conflict: validate the token in the middleware with shared `TokenValidationParameters`, versus moving `UseAuthentication()` before the module middleware +2. Behaviour when `wwwroot/web/` is absent — 404, built-in placeholder, or redirect to `/admin` +3. Whether a missing `wwwroot/web/` should be logged at startup, noting it is normal on a fresh install but would mean the customer's website has vanished on a running production environment +4. `/health` response contract — framework default plain text, or JSON with more detail +5. Whether `/admin` should redirect to `/admin/`, since the SPA is built with `base: '/admin/'` and relative references resolve one level too high without the trailing slash + +### U2 question areas +1. Application-discriminator source — the value that must stay stable across atomic release switches, since the default derives from the content root path +2. **Key encryption at rest** — `PersistKeysToDbContext` stores keys unencrypted by default; DPAPI is unavailable on Linux, and X.509 encryption relocates the loss problem to the certificate. SECURITY-01 relevance flagged explicitly +3. Startup behaviour when the database is temporarily unreachable, distinguishing "not up yet" from "migration is broken" — relevant because the app and SQL Server may start together on the Pi +4. Concurrent migration by two starting instances — currently impossible (one instance per database) but worth an explicit assumption +5. Key lifetime and rotation, including whether to document that the key table must never be pruned + +--- + +## Functional Design — Answers Received and Artifacts Generated (U1 and U2, Round 1) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "klaar, antwoorden staan in het bestand" — U1: Q1 = A, Q2 = B, Q3 = A, Q4 = C, Q5 = A. U2: Q1 = A, Q2 = C, Q3 = C, Q4 = C, Q5 = C. + +**AI Response**: "Analyzed all ten answers per Step 6. No contradictions or vague responses; no clarification round needed. Generated six functional design artifacts (three per unit). Recorded DEV-05 in `requirements.md` and documented one accepted disclosure trade-off. Marked all plan steps complete." + +**Context**: CONSTRUCTION — Functional Design, Steps 6–8, for U1 and U2. + +### U1 decisions +- **Q1 = A — FR-24 approach resolved (§ 5.2 conflict closed)**: validate the token inside `AvailabilityMiddleware` using the same `TokenValidationParameters` as the JWT bearer scheme, resolved from a **single shared source** rather than copied. Chosen over moving `UseAuthentication()` earlier, which would have changed the pipeline for every module including future ones. A drift in which the gate is more permissive than the scheme would silently re-open the hole FR-24 closes, which is why the single-source constraint is a rule (BR-U1-11) rather than a note. +- **Q2 = B**: a built-in placeholder page is served when `wwwroot/web/index.html` is absent. Stored as an **embedded resource**, not a file in `wwwroot/web/` — a file there would sit inside the directory a website workspace owns and overwrites, so it would either be deleted by the first real deployment or mistaken for part of the customer's site. +- **Q3 = A**: a missing `wwwroot/web/` logs a warning at startup with the expected path. Normal on a fresh install, but on a running production instance it means the customer's website has vanished — a warning is visible in Sentry without blocking startup. +- **Q4 = C**: `/health` returns JSON with status, timestamp, version and loaded module names. Directly serves NFR-06: `ModuleOrchestrator` logs rather than throws on module load failure, so an instance can start with reduced capability, and this is the only way to detect that after a deploy without host access. +- **Q5 = A**: `/admin` redirects to `/admin/`, since the SPA is built with `base: '/admin/'` and relative references otherwise resolve one level too high. + +### U2 decisions +- **Q1 = A**: the application discriminator is a **fixed constant in code**, not configuration. The default derives from the content root path, which changes on every atomic release switch — persisting keys in the database while letting the discriminator move would produce keys that are stored but underivable. A constant cannot be forgotten during a host migration or accidentally differ between instances. +- **Q2 = C**: keys stored unencrypted at rest for now, with certificate encryption as a separate follow-up. **Recorded as DEV-05** in `requirements.md` — the one deviation this feature introduces, accepted because it removes a far larger risk than it adds. +- **Q3 = C**: failures are classified — connection failures retry with backoff, migration failures fail immediately. On the Pi the app and SQL Server may start together after a reboot, so a brief unavailability window is normal operation rather than a fault; a broken migration is a fault, and retrying it only delays the inevitable. +- **Q4 = C**: concurrent migration handled by documentation rather than a distributed lock. One instance per database holds by design today; a lock would add failure modes without removing any. Recorded with the condition that automatic startup migration must be revisited **before** any move to multiple instances sharing a database. +- **Q5 = C**: framework default 90-day rotation, plus an explicit documented rule that the keys table must **never** be pruned — the single most destructive maintenance action available against this system, because it looks like harmless housekeeping and permanently breaks every Master↔slave relationship. + +### Accepted trade-off recorded rather than escalated +U1 Q4 = C places the application **version** on an anonymous endpoint, which touches SECURITY-09 hardening. Judged acceptable and documented in `business-rules.md` under BR-U1-17 rather than opened as another question round, on two grounds: module names are **already** publicly exposed by the anonymous `/api/v1/System/capabilities`, so that field adds no new disclosure; and the version field is the primary reason the endpoint exists — confirming which build is live without host access — while an authenticated health endpoint would not work with UptimeRobot. Flagged to the user in the completion message so it can be reversed if unwanted. + +### Artifacts generated +- `construction/u1-hosting-serving/functional-design/`: `business-logic-model.md`, `business-rules.md`, `domain-entities.md` — 22 business rules (BR-U1-01…22) +- `construction/u2-data-durability/functional-design/`: `business-logic-model.md`, `business-rules.md`, `domain-entities.md` — 18 business rules (BR-U2-01…18) + +U1 adds no persisted entity, no migration and no configuration section. U2 adds one entity (the Data Protection keys table), one Core migration, and no configuration section. + +### Security Compliance (Security Baseline extension — enabled, blocking) +- **U1** — SECURITY-05 compliant, **SECURITY-08 improved** (forged-token bypass closed), SECURITY-09 compliant with the documented version disclosure, SECURITY-15 compliant (gate fails closed). +- **U2** — SECURITY-01 **partially compliant, DEV-05** (in-transit enforced, at-rest deferred with compensating controls), SECURITY-03 compliant, SECURITY-09 compliant, **SECURITY-13 improved** (key-ring survival across redeploys is a software-integrity property), SECURITY-15 compliant. + +**No blocking security findings.** DEV-05 is a newly introduced deviation, explicitly accepted with rationale and compensating controls. + +--- + +## Functional Design — Approved (U1 and U2) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Continue to next stage" + +**AI Response**: "Functional Design approved for both Round 1 units, including DEV-05 and the accepted version-disclosure trade-off on `/health`. Proceeded to Code Generation Part 1 (Planning). NFR Requirements, NFR Design and Infrastructure Design are skipped for U1 and U2 per the execution plan." + +**Context**: CONSTRUCTION — Functional Design approved for U1 and U2. + +--- + +## Code Generation — Plans Created for U1 and U2 (Part 1) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated planning step)" + +**AI Response**: "Inspected the actual files to be modified (`ApplicationDbContext`, existing test conventions, both `Program.cs` files) and verified the required package version on nuget.org before writing the plans. Created `u1-hosting-serving-code-generation-plan.md` (13 steps) and `u2-data-durability-code-generation-plan.md` (11 steps)." + +**Context**: CONSTRUCTION — Code Generation, Part 1 Steps 1–6, for U1 and U2. + +### Approval prompt logged before asking +"Code generation plans ready for U1 Hosting & Serving and U2 Data Durability. Review the plans at `construction/plans/u1-hosting-serving-code-generation-plan.md` and `construction/plans/u2-data-durability-code-generation-plan.md`. Approve the plans and proceed to Code Generation (Part 2)?" + +### U1 plan — 13 steps +9 files created, 7 modified. Key design decisions recorded in the plan: +- **Step 1 extracts the JWT validation parameters into a shared factory** consumed by both `AddJwtBearer` and the new admin token validator. This implements the BR-U1-11 single-source constraint — two copies could drift, and a gate more permissive than the bearer scheme would silently re-open the hole FR-24 closes. +- **Step 2 deliberately exposes no options parameter** on `AddCmsHealthChecks()`, so adding a database check later is a visible code change rather than configuration drift — keeping D-21 (liveness only) enforced by shape. +- **Step 5 embeds the placeholder page as an assembly resource** rather than placing it in `wwwroot/web/`, because that directory is owned and overwritten by a website workspace. +- **Step 10 modifies two existing test files** for the middleware constructor change, and adds cases asserting that a forged Owner token grants no bypass while a valid one still does. + +### U2 plan — 11 steps +5 files created, 6 modified, 1 EF migration. Verified `Microsoft.AspNetCore.DataProtection.EntityFrameworkCore` **10.0.9** exists on nuget.org and matches the existing 10.0.x line. +- **Step 5 removes `AddDataProtection()` from both modules** — the § 5.1 conflict. Recorded in the plan with its rationale, because the removal looks like a regression to a reviewer who does not know the ordering issue. +- **Step 8's first assertion is that the persistent key store survives module registration**, explicitly flagged as the highest-value test in the unit: a test that merely asserts "Data Protection is registered" passes in both the broken and fixed cases. +- A **Risk Notes for the Executor** section lists five ways this unit could be implemented in a way that looks correct while remaining ineffective, each paired with the step that prevents it. + +--- + +## Code Generation — Plans Approved (U1 and U2) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Approve Plan" + +**AI Response**: "Both code generation plans approved. Proceeded to Part 2 (Generation)." + +**Context**: CONSTRUCTION — Code Generation Part 1 approved for U1 and U2. + +--- + +## Code Generation — Complete (U1 and U2, Round 1) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — plan execution)" + +**AI Response**: "Executed both plans in order. 16 files created, 13 modified, 1 EF migration generated. Build clean and all tests green on the first run; nothing needed fixing and retrying. Recorded one plan deviation." + +**Context**: CONSTRUCTION — Code Generation, Part 2 Steps 10–14, for U1 and U2. + +### Verification (Step 13.5) +- `dotnet build SlpModularCms.sln -c Release` — **0 errors**, 70 warnings (all pre-existing NU1903/NU1510; the two vulnerable packages are U5's scope) +- `dotnet test SlpModularCms.sln -c Release` — **253 passed, 0 failed, 0 skipped**. Baseline was 219, so 34 tests added. Per project: Core 83 (was 54), Availability 82 (was 78), Identity 37 (unchanged), Master 51 (was 50) +- `dotnet ef migrations add AddDataProtectionKeys` — generated `20260727203036_AddDataProtectionKeys`; inspected and confirmed **purely additive** (creates one table, drops nothing), so rollback by redeploying an earlier release stays safe per BR-U2-16 +- **Embedded resource name verified against the compiled assembly manifest** — `SlpModularCms.Api.Extensions.WebsitePlaceholder.html`. A wrong name would have failed *silently*, falling back to a minimal inline HTML string, so this was checked rather than assumed + +No build or test failure occurred during generation. + +### Implementation decisions worth recording +- **The forged-token fix is proven against the real validator, not only a substitute.** The middleware's own tests substitute `IAdminTokenValidator`, which is correct unit-testing practice — but a substitute keeps passing even if the middleware were later rewired back to unvalidated token parsing. A nested `WithRealValidator` test class therefore wires the middleware to the actual `AdminTokenValidator` and asserts both halves: a forged unsigned Owner token is rejected, **and** a genuine Owner token still bypasses (an administrator must always be able to reach a disabled instance). +- **U2's tests assert resulting configuration, not registration.** A test asserting "Data Protection is registered" passes in both the broken and fixed cases because `IDataProtector` resolves either way. The tests instead inspect `KeyManagementOptions.XmlRepository` (must be the EF repository, not the filesystem default) and `DataProtectionOptions.ApplicationDiscriminator` (must be the fixed constant), plus a round-trip proving a value encrypted before a deploy is readable after one. +- **The module-level "does not override" tests live in each module's own test project**, because `Core.Tests` does not reference the modules. Each registers the host's Data Protection first and the module second — the real ordering. +- **`SqlException` is produced genuinely rather than faked.** It has no public constructor, so the test provokes a real one against an unreachable host with a one-second timeout, exercising the classifier against the exact type it will meet in production. +- **Both `AddDataProtection()` removal sites carry an explanatory comment**, because deleting them looks like a regression to anyone unaware of the ordering issue. + +### DEVIATION FROM PLAN +**U1 plan Step 11 (`StaticContentTests`) was not implemented as written.** The plan placed it in `SlpModularCms.Core.Tests`, but `StaticContentExtensions` lives in `SlpModularCms.Api`, which `Core.Tests` does not reference and must not. `SlpModularCms.Api` has no test project, by the same deliberate convention that gives `SlpModularCms.Api.Slave` none — the Clients solution folder holds deployables, not tested libraries. Creating one would have been a structural change outside this unit's scope. + +The behaviour the step was meant to cover is largely ASP.NET Core's own static-file behaviour rather than this project's logic. The genuinely project-specific parts are **carried to the phase-level Build and Test stage**, where both hosts are started: `/admin` trailing-slash redirect, 404-vs-HTML for missing assets under either mount, SPA fallback resolution for both mounts, placeholder substitution when the website index is absent, and `/health` answering while the instance is availability-disabled. + +Recorded in the plan file (Step 11 marked `[~]` with the reason) and in `construction/u1-hosting-serving/code/generation-summary.md`. + +### Also carried to phase-level Build and Test +`MigrateCoreDatabase()` against a real SQL Server, and both hosts actually starting. Neither is verifiable without a database. + +### Behaviour note recorded for Operations +Static mounts are resolved **at startup**: a directory created after the process started is not served until the next restart. Correct for the intended deployment model — the atomic release switch links `wwwroot/web/` into place before the process starts — but it means dropping a website into a running instance requires a restart. + +### Security Compliance (Security Baseline extension — enabled, blocking) +- **SECURITY-08 — improved and now verified by test.** The forged-token bypass is closed; `AdminTokenValidatorTests` covers forged-unsigned, wrong-key, expired, wrong-issuer, non-admin, malformed and absent tokens. +- **SECURITY-13 — improved.** The key ring now survives redeploys, asserted by a round-trip test. +- **SECURITY-03 — compliant.** Migration failure logging carries diagnostic context but no connection string or credentials. +- **SECURITY-15 — compliant.** The gate fails closed; startup fails closed. +- **SECURITY-01 — partially compliant, DEV-05** (keys unencrypted at rest, compensating controls documented). + +**No blocking security findings.** + +--- + +## Code Generation — Approved (U1 and U2, Round 1) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "Continue to Next Stage" + +**AI Response**: "Round 1 code approved, including the U1 Step 11 deviation and the items carried to the phase-level Build and Test stage. Performed the mandatory phase transition check: updated this feature's status in `aidlc-docs/active-features.md` from 🔵 Inception to 🟢 Construction, and broadened its Affected Components to include Core and Modules. Proceeded to Round 2 (U3 Security Headers & CSP + U4 Observability), starting with Functional Design for both units." + +**Context**: CONSTRUCTION — Code Generation approved for U1 and U2. Phase transition applied. + +--- + +## Functional Design — Plans Created for U3 and U4 (Round 2) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "(no user input — automated planning step)" + +**AI Response**: "Created functional design plans and dedicated question files for both Round 2 units. Each plan states an explicit scope boundary against the NFR Design stage that follows for these two units, so behaviour questions and pattern questions are not duplicated." + +**Context**: CONSTRUCTION — Functional Design, Steps 1–5, for U3 and U4 together (Round 2 grouping per Q4 = B). + +### Artifacts created +- `construction/plans/u3-security-headers-functional-design-plan.md` — 6 steps +- `construction/plans/u3-security-headers-fd-questions.md` — 5 questions +- `construction/plans/u4-observability-functional-design-plan.md` — 7 steps +- `construction/plans/u4-observability-fd-questions.md` — 5 questions + +### U3 question areas +1. **A genuinely strict CSP would break the admin SPA.** Radix UI components position dialogs and dropdowns using inline `style` attributes, which `style-src 'self'` blocks without `'unsafe-inline'`. The failure is silent in the console sense — misplaced or invisible menus, visible only in a browser and only where the user clicks. Options offered: documented `'unsafe-inline'` for styles while keeping `script-src` strict, nonces/hashes (impractical because Radix generates styles per interaction), or report-only first. +2. What "relaxed" concretely permits for the public website, given the same process also serves the admin UI and API. +3. Whether headers apply in Development — flagged that HSTS on `localhost` is remembered per host by browsers and can interfere with unrelated local projects, while an active CSP locally is genuinely useful. +4. `X-Frame-Options: DENY` as specified in FR-18 also applies to the public website, so a customer embedding their own page in an iframe would break. Offered per-path scoping. +5. Behaviour when the Umami/Sentry origin lists are empty — including the case where a DSN or website ID is configured but the matching CSP origin is not, which would silently block monitoring traffic. + +### U4 question areas +1. **Sentry transport: tunnel versus direct.** The reference project tunnels through nginx because ad blockers return `ERR_BLOCKED_BY_CLIENT` for Sentry domains — meaning errors are lost precisely for ad-blocker users. Here nginx is unavailable (NFR-01), so a tunnel would have to be an app endpoint. Noted the side benefit: a tunnel keeps traffic same-origin, so U3's CSP needs no external Sentry origin. +2. **`SendDefaultPii`** — flagged that enabling it sends request headers **including cookies**, and this application carries the `refreshToken` in a cookie. SECURITY-03 forbids logging secrets and PII. +3. Production log level — noted that the current `Warning` baseline means a normal production run logs almost nothing, including startup, module discovery and migration outcome, which are the three most useful things after a deploy. +4. Which security events to emit for alerting (multi-select), including the availability-gate rejection that only became meaningful after the FR-24 fix. +5. Umami on the admin SPA — always, or respecting `Do Not Track`. + +--- + +## Functional Design — U3 Answers Received, U4 Answers Missing (Round 2) + +**Timestamp**: 2026-07-27T00:00:00Z + +**User Input**: "klaar, antwoorden staan in het bestand" — U3 answers: Q1 = B, Q2 = B, Q3 = A, Q4 = A, Q5 = C. **U4: all five `[Answer]:` tags are empty.** + +**AI Response**: "Cannot proceed to artifact generation. Two blockers reported: (1) `u4-observability-fd-questions.md` has no answers filled in at all; (2) U3 Q1 = B is technically unworkable and I described that option too mildly in the first place — correction and a follow-up question added to the U3 file, along with a confirmation question about the SECURITY-04 consequence of Q2 = B." + +**Context**: CONSTRUCTION — Functional Design, Step 6 (answer analysis) for U3 and U4. Gate not passed. + +### Blocker 1 — U4 unanswered +All five questions in `construction/plans/u4-observability-fd-questions.md` still have empty `[Answer]:` tags. Per the workflow's answer-collection rule, generation cannot start until they are completed. + +### Blocker 2 — U3 Q1 = B is not implementable, and my option description was at fault +The user chose "nonces or hashes for styles". I had described this as "theoretically neater but impractical because Radix generates styles per interaction", which understated the problem: **CSP nonces apply only to `