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
This commit is contained in:
+249
@@ -0,0 +1,249 @@
|
||||
# Component Dependencies
|
||||
|
||||
Dependency matrix, communication patterns and data flow.
|
||||
|
||||
---
|
||||
|
||||
## Dependency Diagram
|
||||
|
||||
```mermaid
|
||||
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:
|
||||
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. 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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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. |
|
||||
Reference in New Issue
Block a user