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
10 KiB
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-OptionsandStrict-Transport-Securityon all responses;Content-Security-Policy,X-Frame-OptionsandReferrer-Policyon 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 viaUseCmsSecurityHeaders(). - Requirements: FR-18, SECURITY-04
C-02 SecurityHeadersOptions
- Project:
SlpModularCms.Core(Hosting/Security/) - Purpose: Bind the
SecurityHeadersconfiguration section (Options pattern, consistent withJwtSettings,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/adminand/api/v1) andRelaxed(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
- Define the two named policies in code:
- 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 /healthreturning200/Healthyor503/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> DataProtectionKeysand implementIDataProtectionKeyContext. Requires one new Core migration. - Why here rather than a fourth context: keys are application-wide infrastructure, not module-owned, and
ApplicationDbContextnow 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
ApplicationDbContextmigrations at startup, removing the need for CLI access on the host. - Responsibilities:
- Run
Database.Migrate()forApplicationDbContextduring startup, before the request pipeline accepts traffic - Fail fast (Q8 = A): on failure, let the exception propagate so the process does not start
- Run
- Design interaction worth stating: fail-fast is what makes the liveness-only health check meaningful. A process that cannot migrate never starts,
/healthstops 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
TraceIdentifierversus W3Ctraceparent) 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.AspNetCorewhen 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)
- Initialise
- 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.Apionly — host-specific, not inCore(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 ownPhysicalFileProvider - Mount
wwwroot/admin/at/adminwith its ownPhysicalFileProvider - Provide default-file handling per mount
- Map two SPA fallbacks, preserving the
nonfileroute constraint so missing assets still return404
- Mount
- 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.yamlis implemented now;deploy-ftps.yamlcan 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:
- Add
/healthto_bypassPrefixesso the availability gate cannot mask infrastructure liveness (FR-10, D-22) - Fix
IsAdminBypassto stop trusting an unvalidated token (FR-24, Q12 = A) — currentlyReadJwtTokenparses without signature verification, so an unauthenticated caller can forge anOwnerclaim and bypass the gate
- Add
- 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_URLas same-origin while still accepting an explicit absolute URL for local development againsthttps://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/reactwith 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
Coreextension methods in the correct order. Q9 = A: the two files stay separate — with the implementation inCore, 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.