Files
slp-modular-cms/aidlc-docs/features/gitea-deployment-workflow/inception/application-design/components.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

10 KiB

Components

Component definitions and high-level responsibilities. Detailed business logic is designed per unit in Functional Design.

Placement rule (Q1 = A): every new cross-cutting concern lives in SlpModularCms.Core and is exposed as an extension method. Both hosts therefore get identical behaviour with no duplicated implementation. Q2 = A: SlpModularCms.Api.Slave receives everything except the static-file mounts, because it serves as a reference for what a customer-facing API instance looks like — not merely as a local dev tool.


New Components

C-01 SecurityHeadersMiddleware

  • Project: SlpModularCms.Core (Hosting/Security/)
  • Purpose: Apply HTTP security headers that would normally come from nginx or IIS configuration, which NFR-01 forbids relying on.
  • Responsibilities:
    • Attach headers at response start, so the response content type is known before deciding what applies
    • Apply per-header scoping (FU1 = A): X-Content-Type-Options and Strict-Transport-Security on all responses; Content-Security-Policy, X-Frame-Options and Referrer-Policy on HTML responses only
    • Select the CSP policy for the request path
    • Never overwrite a header another component has already set
  • Interfaces: standard middleware — InvokeAsync(HttpContext). Registered via UseCmsSecurityHeaders().
  • Requirements: FR-18, SECURITY-04

C-02 SecurityHeadersOptions

  • Project: SlpModularCms.Core (Hosting/Security/)
  • Purpose: Bind the SecurityHeaders configuration section (Options pattern, consistent with JwtSettings, MasterModule, MasterPolling).
  • Responsibilities: Carry the path-to-policy assignment, the default policy, and the environment-specific allowed origins.
  • Design rule (Q5 = B + Q6 = B, confirmed FU2 = A): policy definitions live in code; path assignment and origins live in configuration. Adding a path later needs no code change; inventing a new policy does.
  • Requirements: FR-18

C-03 CspPolicyBuilder

  • Project: SlpModularCms.Core (Hosting/Security/)
  • Purpose: Compose a Content-Security-Policy string from a named policy plus the configured origins.
  • Responsibilities:
    • Define the two named policies in code: Strict (for /admin and /api/v1) and Relaxed (for the public website)
    • Inject the configured Umami script origin and Sentry ingest origin into the relevant directives
    • Build each policy once at startup rather than per request
  • Rationale for existing separately from C-01: keeps policy composition unit-testable without a request pipeline, and keeps the middleware free of string building.
  • Requirements: FR-18, D-31

C-04 Health-check registration

  • Project: SlpModularCms.Core (Hosting/Health/)
  • Purpose: Expose infrastructure liveness, strictly separate from CMS domain state.
  • Responsibilities:
    • Register the framework health-check services (no package required)
    • Map GET /health returning 200/Healthy or 503/Unhealthy
    • Liveness only — no database call, no dependency probing (D-21)
  • Explicit non-responsibility: this component says nothing about availability or capabilities. Those are CMS domain functionality and are never to be used for monitoring.
  • Requirements: FR-10

C-05 CmsDataProtection registration

  • Project: SlpModularCms.Core (Hosting/)
  • Purpose: Persist the Data Protection key ring in the database so redeploys and atomic release switches cannot render stored slave API keys unreadable.
  • Responsibilities: Configure PersistKeysToDbContext<ApplicationDbContext> and set a stable application discriminator so both hosts and all replicas derive the same keys.
  • Requirements: FR-12, D-17

C-06 ApplicationDbContext extension — IDataProtectionKeyContext

  • Project: SlpModularCms.Core (Data/) — modification of an existing component
  • Purpose: Host the Data Protection keys table (Q7 = A).
  • Responsibilities: Add DbSet<DataProtectionKey> DataProtectionKeys and implement IDataProtectionKeyContext. Requires one new Core migration.
  • Why here rather than a fourth context: keys are application-wide infrastructure, not module-owned, and ApplicationDbContext now migrates automatically (FR-11) so the table is created without manual steps.
  • Requirements: FR-12

C-07 Startup migration runner

  • Project: SlpModularCms.Core (Hosting/)
  • Purpose: Apply ApplicationDbContext migrations at startup, removing the need for CLI access on the host.
  • Responsibilities:
    • Run Database.Migrate() for ApplicationDbContext during startup, before the request pipeline accepts traffic
    • Fail fast (Q8 = A): on failure, let the exception propagate so the process does not start
  • Design interaction worth stating: fail-fast is what makes the liveness-only health check meaningful. A process that cannot migrate never starts, /health stops answering, and UptimeRobot goes red. Had this logged-and-continued, the app would look healthy while being unusable.
  • Requirements: FR-11, D-13

C-08 CmsLogging registration

  • Project: SlpModularCms.Core (Hosting/Observability/)
  • Purpose: Configure structured logging, independent of whether Sentry is enabled (Q10 = B).
  • Responsibilities: Console logging with structured output and a correlation/request identifier on every entry; exclude secrets and PII.
  • Open item: OPEN-01 — the correlation-ID mechanism (ASP.NET Core TraceIdentifier versus W3C traceparent) is decided in NFR Design for Unit 4.
  • Requirements: D-20, SECURITY-03

C-09 CmsSentry registration

  • Project: SlpModularCms.Core (Hosting/Observability/)
  • Purpose: Report errors and structured logs to Sentry, tagged by environment.
  • Responsibilities:
    • Initialise Sentry.AspNetCore when a DSN is configured, and skip silently when it is not, leaving console logging active
    • Tag events with environment and release
    • Emit security-relevant events for alerting (FR-19)
  • Separate from C-08 (Q10 = B): structured logging must work without Sentry.
  • Requirements: FR-14, FR-19, D-19

C-10 Static-file mount composition

  • Project: SlpModularCms.Api only — host-specific, not in Core (Q2 = A: the Slave has no static content)
  • Purpose: Serve two independent front-ends from one process.
  • Responsibilities (Q3 = A):
    • Mount wwwroot/web/ at / with its own PhysicalFileProvider
    • Mount wwwroot/admin/ at /admin with its own PhysicalFileProvider
    • Provide default-file handling per mount
    • Map two SPA fallbacks, preserving the nonfile route constraint so missing assets still return 404
  • Design consequence: two explicit registrations rather than one, so each mount can later carry its own headers or caching without disturbing the other.
  • Requirements: FR-07, FR-08, D-06

C-11 Deploy transport workflows

  • Project: .gitea/workflows/ — not C#
  • Purpose: Transfer a published release to a target host.
  • Responsibilities (Q11 = B): one reusable workflow per transport, sharing an identical input interface. deploy-scp.yaml is implemented now; deploy-ftps.yaml can be added later without changing callers.
  • Requirements: FR-02, D-02, NFR-09

C-12 CI workflow

  • Project: .gitea/workflows/continuous_integration.yaml
  • Purpose: Validate every change and drive deployment.
  • Responsibilities: Six blocking gates, two environment-specific builds, artifact publication, and the calls into C-11 for test and production.
  • Requirements: FR-01, FR-03, FR-04, FR-05

Modified Existing Components

C-13 AvailabilityMiddleware

  • Project: SlpModularCms.Modules.Availability (Middleware/)
  • Changes:
    1. Add /health to _bypassPrefixes so the availability gate cannot mask infrastructure liveness (FR-10, D-22)
    2. Fix IsAdminBypass to stop trusting an unvalidated token (FR-24, Q12 = A) — currently ReadJwtToken parses without signature verification, so an unauthenticated caller can forge an Owner claim and bypass the gate
  • Behaviour that must be preserved: an Owner or Administrator with a valid token still passes, so administrators can always reach a disabled instance to switch it back on.
  • Requirements: FR-10, FR-24

C-14 Frontend application configuration (frontend/src/lib/config.ts)

  • Changes: treat an absent or empty VITE_API_BASE_URL as same-origin while still accepting an explicit absolute URL for local development against https://localhost:7221 / :7222. Zod validation relaxed accordingly, without silently accepting malformed values.
  • Requirements: FR-13, D-14

C-15 Frontend observability

  • Project: frontend/src/
  • Changes: initialise @sentry/react with environment and release tags, skipping gracefully without a DSN; add the Umami tracking script with a per-environment website ID, absent during local development.
  • Requirements: FR-15, FR-16

C-16 Host composition (Program.cs, both hosts)

  • Changes: call the new Core extension methods in the correct order. Q9 = A: the two files stay separate — with the implementation in Core, what remains duplicated is an explicit list of calls, which is intentional readability rather than accidental duplication.
  • Requirements: FR-07, FR-10, FR-11, FR-12, FR-14, FR-18

Component Summary

ID Component Project Type Slave gets it?
C-01 SecurityHeadersMiddleware Core New Yes
C-02 SecurityHeadersOptions Core New Yes
C-03 CspPolicyBuilder Core New Yes
C-04 Health-check registration Core New Yes
C-05 CmsDataProtection registration Core New Yes
C-06 ApplicationDbContext keys table Core Modified Yes
C-07 Startup migration runner Core New Yes
C-08 CmsLogging registration Core New Yes
C-09 CmsSentry registration Core New Yes
C-10 Static-file mount composition Api New No
C-11 Deploy transport workflows .gitea/ New n/a
C-12 CI workflow .gitea/ New n/a
C-13 AvailabilityMiddleware Modules.Availability Modified Yes
C-14 Frontend config frontend Modified n/a
C-15 Frontend observability frontend New n/a
C-16 Host composition Both hosts Modified Yes

14 code components (9 new, 5 modified) plus 2 workflow components.