Files
slp-modular-cms/aidlc-docs/features/gitea-deployment-workflow/inception/application-design/application-design.md
T
SluijsensandClaude Opus 5 8568ca43c6 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
2026-07-27 23:59:30 +02:00

11 KiB

Application Design — Gitea Deployment Workflow

Feature: gitea-deployment-workflow Date: 2026-07-27 Consolidates: components.md, component-methods.md, services.md, component-dependency.md


1. Design Summary

This feature adds cross-cutting infrastructure to an existing modular monolith, plus the CI/CD pipeline that deploys it. It introduces no new business capability and no new domain entity beyond a Data Protection keys table.

The design is shaped by four decisions taken during this stage:

Decision Choice Consequence
Q1 = A — placement All new cross-cutting concerns live in SlpModularCms.Core as extension methods Both hosts get identical behaviour; nothing is implemented twice
Q2 = A — Slave scope The Slave host receives everything except static mounts The Slave is a reference instance showing what a customer-facing API looks like, not a stripped-down dev tool. Its behaviour must match production
Q9 = A — composition Each host keeps its own Program.cs What remains duplicated is a readable list of calls, not logic
Q8 = A — failure mode Fail fast on migration failure Makes the liveness-only health check meaningful: a process that cannot migrate never starts, so /health goes silent and monitoring goes red

14 code components (9 new, 5 modified) and 2 workflow components, across Core, Api, Modules.Availability, frontend and .gitea/workflows/.


2. Components

See components.md for full definitions. Summary:

New in SlpModularCms.Core

  • C-01 SecurityHeadersMiddleware — applies headers that would normally come from nginx or IIS, which NFR-01 forbids relying on
  • C-02 SecurityHeadersOptions — binds the SecurityHeaders section
  • C-03 CspPolicyBuilder — composes CSP strings from code-defined policies plus configured origins
  • C-04 Health-check registrationGET /health, liveness only
  • C-05 CmsDataProtection — database-backed key ring
  • C-07 Startup migration runner — Core migrations, fail fast
  • C-08 CmsLogging — structured logging, independent of Sentry
  • C-09 CmsSentry — optional; absent DSN is a supported state

Modified

  • C-06 ApplicationDbContext — implements IDataProtectionKeyContext; one new Core migration
  • C-13 AvailabilityMiddleware/health bypass, plus the FR-24 token-validation fix
  • C-14/C-15 frontend — same-origin config, Sentry, Umami
  • C-16 host composition — both Program.cs files

Host-specific and workflow

  • C-10 Static mountsApi only: wwwroot/web/ at /, wwwroot/admin/ at /admin
  • C-11 deploy-scp.yaml, C-12 continuous_integration.yaml

3. Key Interfaces

See component-methods.md for full signatures. The contracts that carry design weight:

AddCmsHealthChecks() takes no options. Deliberate — "just add a database check" then becomes a visible code change rather than configuration drift, keeping D-21 (liveness only) enforced by shape rather than by discipline.

MigrateCoreDatabase() has no try/catch. Exceptions propagate by design (Q8 = A).

AddCmsSentry() treats an absent DSN as normal. Local development and any Sentry-less deployment run unchanged.

SecurityHeadersOptions configures assignment, not definition. Policy content is in code; path assignment and environment-specific origins are configuration (FU2 = A). A misconfiguration can misroute a path but cannot invent a broken policy.

Both deploy workflows share one input interface (Q11 = B), so switching transport changes only a uses: line.


4. Orchestration

See services.md for the full pipeline. The two orderings that matter most:

Security headers precede static files. Static files short-circuit the pipeline; anything registered after them never reaches the public website — the very surface the CSP is for. Headers are applied at response start via OnStarting, because the content type is unknown earlier and per-header scoping (FU1 = A) depends on it.

/health is an endpoint, so it runs after the availability gate. That is exactly why /health must be on the bypass list (D-22) — otherwise a deliberately disabled instance would report itself as unhealthy, re-creating the conflation this feature exists to avoid.

Startup order: logging and Sentry first (so later failures are captured) → module discovery → services including Data Protection → build → Core migration (fail fast) → module migrations → serve.


5. Two Conflicts Found During Design

Both were discovered by tracing the composition order, and both would have produced code that looks correct while doing nothing useful.

5.1 Duplicate AddDataProtection() would silently defeat FR-12

AvailabilityModule.RegisterServices and MasterModule.RegisterServices each call services.AddDataProtection() today. Module registration runs after the host's registration, so the modules' bare calls would override the persistent key store configured by AddCmsDataProtection().

The result: FR-12 appears implemented, tests that check registration pass, and the key ring is still ephemeral — so the first atomic release switch silently breaks master↔slave trust in a way that presents as a network fault. Exactly the failure FR-12 exists to prevent.

Resolution: remove AddDataProtection() from both modules; the host configures Data Protection once. Assigned to Unit 2, with a test asserting the persistent store survives module registration.

Second-order requirement: the Data Protection application discriminator must be set explicitly. By default it derives from the content root path, which changes on every atomic release-directory switch (FR-06) — which would defeat FR-12 by a different route.

5.2 AvailabilityMiddleware runs before authentication

FR-24 requires the admin bypass to stop trusting an unvalidated token. But AvailabilityMiddleware is installed by orchestrator.UseModules(app) at step 8, while UseAuthentication() runs at step 9 — so HttpContext.User is not yet populated when the bypass is evaluated.

Two options, decided in Functional Design for Unit 2:

Option Trade-off
(a) Validate the token inside the middleware with the same TokenValidationParameters as the bearer scheme Contained, but duplicates validation parameters — which must then be shared from one source rather than copied
(b) Move UseAuthentication() before the module middleware Smaller code change, but alters the pipeline for every module including future ones — a wider blast radius than this feature warrants

Neither is obviously correct, which is why it is recorded rather than decided here.


6. Dependencies

See component-dependency.md for the matrix and data flows. The coupling that deserves attention:

The wwwroot/web/ filesystem convention is the feature's weakest link. It is enforced by convention, not by the type system, and getting it wrong destroys a customer's website (NFR-02). Two design obligations follow:

  1. 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
  2. Deployment must link the persistent wwwroot/web/ into each new release directory (ASM-01)

Core reaches the Slave. Every change lands in a host with no test project, verified at Build and Test by starting it.

Build-time frontend configuration forces two artifacts (D-15), and the publish target makes Node and pnpm prerequisites of dotnet publish.


7. Requirements Coverage

Requirement Covered by
FR-01, FR-03, FR-04, FR-05 C-12
FR-02 C-11, S-06
FR-06, FR-20 S-05
FR-07, FR-08 C-10, S-01
FR-09 Unit 7 documentation (Infrastructure Design input)
FR-10 C-04, C-13, S-01
FR-11 C-07, S-03
FR-12 C-05, C-06 — and § 5.1
FR-13 C-14
FR-14 C-09
FR-15, FR-16 C-15
FR-17 Operations phase
FR-18 C-01, C-02, C-03, S-02
FR-19 C-09 + NFR Design, Unit 4
FR-21, FR-22 Unit 1 — no design needed
FR-23 Operations phase
FR-24 C-13 — and § 5.2

All 24 functional requirements are accounted for: 18 by a designed component, 3 by the Operations phase, 2 needing no design, and 1 (FR-09) depending on Infrastructure Design output.


8. Security Compliance (Security Baseline extension — enabled, blocking)

Assessed against the design.

Rule Status Notes
SECURITY-01 Addressed TLS enforced in connection strings; HSTS on all responses (FU1 = A)
SECURITY-02 N/A No load balancer, API gateway or CDN in this architecture
SECURITY-03 Addressed C-08 structured logging with correlation ID; no secrets or PII. Mechanism decided in NFR Design (OPEN-01)
SECURITY-04 Addressed C-01/C-02/C-03, with per-header scoping so nosniff covers assets — the specific gap caught in follow-up round 2
SECURITY-05 Unchanged /health is the only new endpoint and accepts no input
SECURITY-06 Addressed Deploy credentials scoped to the target; Gitea secrets repository-scoped
SECURITY-07 Partially N/A No cloud networking; applicable parts are documented host setup
SECURITY-08 Improved C-13 closes the forged-token bypass (FR-24) — a pre-existing finding this feature now fixes rather than inherits
SECURITY-09 Addressed Directory browsing stays disabled; Scalar remains Development-only; production errors stay generic; no default credentials
SECURITY-10 Addressed Blocking vulnerability gate; pinned tool versions; --frozen-lockfile. DEV-02 records the missing packages.lock.json and SBOM
SECURITY-11 Addressed Rate limiting pre-exists; security logic stays in Core/Identity; the misuse cases explicitly designed against are website destruction (NFR-02) and silent key-ring loss (§ 5.1)
SECURITY-12 Unchanged Pre-existing; DEV-03 records absent MFA and breached-password checking
SECURITY-13 Addressed SRI for external scripts where supported; CSP constrains them; pipeline definitions are version-controlled and reviewable
SECURITY-14 Addressed with DEV-01 Alerting via C-09; retention deviation accepted
SECURITY-15 Unchanged Global exception handler pre-exists. New fail-closed decision: an unknown CSP policy name fails at startup rather than degrading silently. The master gate's deliberate fail-open remains an intentional, business-driven exception

No blocking security findings. SECURITY-08 improves from "pre-existing, unchanged" to "improved" because Q12 = A folded the fix into this feature.


9. Carried Forward to Construction

Item Resolved at
§ 5.1 — remove duplicate AddDataProtection(); set an explicit application discriminator Functional Design + Code Generation, Unit 2
§ 5.2 — FR-24 implementation approach (validate in middleware versus move authentication) Functional Design, Unit 2
OPEN-01 — correlation-ID mechanism NFR Design, Unit 4
OPEN-03 — patched versions for the two vulnerable packages Code Generation, Unit 1
ASM-01 — wwwroot/web/ outside the swapped release directory Infrastructure Design, Unit 6
Definition of an alertable security event NFR Design, Unit 4
C-10 tolerating a missing wwwroot/web/ at startup Functional Design, Unit 2