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
11 KiB
Component Dependencies
Dependency matrix, communication patterns and data flow.
Dependency Diagram
graph TD
subgraph core["SlpModularCms.Core"]
sechdr["C-01 SecurityHeadersMiddleware"]
secopt["C-02 SecurityHeadersOptions"]
csp["C-03 CspPolicyBuilder"]
health["C-04 Health checks"]
dp["C-05 CmsDataProtection"]
appdb["C-06 ApplicationDbContext<br/>plus keys table"]
migrate["C-07 Migration runner"]
logging["C-08 CmsLogging"]
sentry["C-09 CmsSentry"]
end
subgraph apihost["SlpModularCms.Api"]
statics["C-10 Static mounts"]
prog["C-16 Host composition"]
end
subgraph slavehost["SlpModularCms.Api.Slave"]
progslave["C-16 Host composition"]
end
subgraph modules["Modules"]
avail["C-13 AvailabilityMiddleware"]
end
subgraph fe["frontend"]
cfg["C-14 Config"]
feobs["C-15 Sentry plus Umami"]
end
subgraph wf[".gitea/workflows"]
ci["C-12 CI workflow"]
scp["C-11 deploy-scp"]
end
sechdr --> secopt
sechdr --> csp
csp --> secopt
dp --> appdb
migrate --> appdb
sentry --> logging
prog --> sechdr
prog --> health
prog --> dp
prog --> migrate
prog --> logging
prog --> sentry
prog --> statics
progslave --> sechdr
progslave --> health
progslave --> dp
progslave --> migrate
progslave --> logging
progslave --> sentry
avail --> dp
cfg --> feobs
ci --> scp
ci --> fe
ci --> apihost
classDef corelayer fill:#fbd38d,stroke:#c05621,stroke-width:1px,color:#000;
classDef host fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef frontend fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000;
classDef workflow fill:#fbb6ce,stroke:#b83280,stroke-width:1px,color:#000;
class sechdr,secopt,csp,health,dp,appdb,migrate,logging,sentry corelayer;
class statics,prog,progslave host;
class avail module;
class cfg,feobs frontend;
class ci,scp workflow;
Text alternative: all new cross-cutting components live in Core and are consumed by both host projects; only the static mounts are exclusive to the Api host; the Availability middleware depends on Core's Data Protection; and the CI workflow orchestrates the frontend, the Api host and the deploy workflow.
Dependency Matrix
| Component | Depends on | Depended on by | Coupling |
|---|---|---|---|
C-01 SecurityHeadersMiddleware |
C-02, C-03 | C-16 (both hosts) | Compile |
C-02 SecurityHeadersOptions |
Configuration | C-01, C-03 | Configuration binding |
C-03 CspPolicyBuilder |
C-02 | C-01 | Compile |
| C-04 Health checks | — | C-16 (both hosts) | Compile |
C-05 CmsDataProtection |
C-06 | C-16, and indirectly C-13 | Compile |
C-06 ApplicationDbContext keys table |
EF Core, SQL Server | C-05, C-07 | Compile + schema |
| C-07 Migration runner | C-06 | C-16 (both hosts) | Compile |
C-08 CmsLogging |
— | C-09, C-16 | Compile |
C-09 CmsSentry |
C-08, configuration | C-16 | Compile |
| C-10 Static mounts | Filesystem layout | C-16 (Api only) |
Runtime (filesystem) |
C-11 deploy-scp.yaml |
Host over SSH | C-12 | Workflow call |
| C-12 CI workflow | C-11, both build outputs | — | Workflow |
C-13 AvailabilityMiddleware |
C-05 (key ring), validated principal | Both hosts, via module discovery | Compile + runtime |
| C-14 Frontend config | Vite build variables | C-15, ApiClient |
Build-time |
| C-15 Frontend observability | C-14, Vite build variables | — | Build-time |
| C-16 Host composition | C-01, C-04, C-05, C-07, C-08, C-09, C-10 | — | Compile |
Communication Patterns
In-process (the majority)
Everything in Core is consumed by the hosts through DI registration and middleware composition. There are no new service-to-service calls, no new queues and no new network hops inside the application. This is deliberate: the feature adds cross-cutting behaviour, not new interactions.
Configuration-driven
C-02 binds the SecurityHeaders section; C-09 reads a Sentry DSN; C-05 reads Data Protection settings. All follow the existing Options pattern. Configuration errors must surface at startup, not per request — an unknown CSP policy name fails the process rather than silently degrading, which given fail-fast startup (Q8 = A) means a misconfigured deployment goes visibly red instead of quietly serving without protection.
Filesystem-coupled (the fragile one)
C-10 depends on a directory layout that no code creates:
| Path | Owner | Created by |
|---|---|---|
wwwroot/admin/ |
This repository | dotnet publish (BuildAndCopyAdminFrontend target) |
wwwroot/web/ |
A separate website workspace | That workspace's own deployment; persists across releases (ASM-01) |
This is the feature's most fragile coupling, because it is enforced by convention rather than by the type system. Two consequences:
C-10must tolerate a missingwwwroot/web/at startup — a fresh deployment has none until a website workspace deploys into it, and the CMS must still start and serve/adminand/api/v1.- The deployment (S-05) must link the persistent
wwwroot/web/into each new release directory. Getting this wrong destroys the customer's website — the highest-severity risk in the feature (NFR-02).
Build-time coupling (frontend)
C-14 and C-15 read Vite variables baked in at build time, which is precisely why two artifacts are produced (D-15). The backend and frontend are additionally coupled in the reverse direction by the publish target, which runs pnpm install and pnpm build — making Node and pnpm prerequisites of dotnet publish and a required step in the CI workflow's toolchain setup.
Cross-instance (unchanged, but newly protected)
Master↔slave communication is untouched functionally. What changes is its durability: with C-05 and C-06, the encrypted API keys underpinning that trust survive a redeploy. Previously an atomic release switch would have discarded the file-based key ring and broken the protocol silently.
Data Flow
Request flow with the new components
sequenceDiagram
box rgba(246,224,94,0.4) Client
participant V as Visitor or admin
end
box rgba(144,205,244,0.4) Pipeline
participant E as Exception handler
participant S as Security headers
participant F as Static files
participant A as Availability gate
participant H as Health endpoint
end
V->>E: HTTP request
E->>S: continue
S->>S: resolve CSP policy for path
S->>F: continue with OnStarting callback
alt file exists in web or admin mount
F-->>V: file, headers applied at response start
else no matching file
F->>A: continue
alt path is /health or another bypass prefix
A->>H: continue
H-->>V: 200 Healthy or 503 Unhealthy
else instance disabled
A-->>V: 503 ProblemDetails
else instance available
A-->>V: routed to controllers or SPA fallback
end
end
Text alternative: security headers register a response-start callback before static files short-circuit, so both static and dynamic responses carry them; /health passes the availability gate via the bypass list, while other paths are blocked with a 503 when the instance is disabled.
Startup flow
graph TD
start["Process starts"]
log["Configure logging and Sentry"]
disc["Discover modules"]
svc["Register services<br/>including Data Protection"]
build["Build application"]
mig["Migrate ApplicationDbContext"]
modmig["Module contexts migrate<br/>during UseModules"]
serve["Accept traffic;<br/>/health answers"]
dead["Process does not start;<br/>/health silent, UptimeRobot red"]
start --> log
log --> disc
disc --> svc
svc --> build
build --> mig
mig -->|success| modmig
mig -->|failure| dead
modmig --> serve
classDef normal fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef decision fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
classDef bad fill:#fbb6ce,stroke:#b83280,stroke-width:1px,color:#000;
class start,log,disc,svc,build,modmig,serve normal;
class mig decision;
class dead bad;
Text alternative: logging and Sentry are configured first so any later startup failure is captured; Core migrations run before module migrations, and a migration failure stops the process entirely rather than serving a broken application.
Deployment data flow
graph LR
art["Build artifact"]
backup[("Database backup<br/>production only")]
newrel["New release directory"]
persist[("Persistent wwwroot/web<br/>customer website")]
active["Active release symlink"]
prev["Retained previous release"]
art --> newrel
backup -.->|before any change| newrel
persist -->|linked into| newrel
newrel --> active
active -.->|previous becomes| prev
prev -.->|rollback| active
classDef artifact fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef store fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000;
classDef link fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
class art,newrel artifact;
class backup,persist store;
class active,prev link;
Text alternative: the build artifact populates a new release directory into which the persistent customer website is linked; the active pointer then switches atomically, and the previous release is retained so a rollback is a pointer switch rather than a rebuild.
Coupling Concerns
| Concern | Assessment |
|---|---|
Core is inherited by both hosts |
Intended (Q1 = A, Q2 = A). Every Core change reaches SlpModularCms.Api.Slave, which has no test project — so it is verified at Build and Test by actually starting it. The Slave is a reference instance, not a throwaway. |
Duplicate AddDataProtection() in two modules |
A real conflict, documented in services.md S-01. Module registration runs after host registration, so the modules' calls would override the persistent key store and make FR-12 a no-op that looks implemented. Both must be removed. |
AvailabilityMiddleware runs before UseAuthentication() |
Constrains how FR-24 can be implemented — either validate the token in the middleware, or move authentication earlier. Resolved in Functional Design for Unit 2. |
Filesystem convention for wwwroot/web/ |
The weakest link: not enforceable in code, and getting it wrong is destructive. Mitigated by design (persistent path outside the release directory) and by documentation (FR-09), but it stays a convention. |
| Static files short-circuit the pipeline | Dictates that security headers precede them. Any future middleware that must see all responses faces the same constraint — worth remembering rather than rediscovering. |
| Build-time frontend configuration | Forces two build artifacts. Accepted (D-15), with runtime configuration recorded as a possible later improvement. |