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
7.5 KiB
Unit of Work Dependencies
Dependency Diagram
graph TD
u1["U1 Hosting and Serving"]
u2["U2 Data Durability"]
u3["U3 Security Headers and CSP"]
u4["U4 Observability"]
u5["U5 CI Workflow and Gates"]
u6["U6 Deploy Workflow"]
u7["U7 Documentation"]
ops["Operations Phase"]
u1 -->|"path layout for CSP scoping"| u3
u4 -->|"Umami and Sentry origins"| u3
u4 -->|"env-specific Vite variables"| u5
u1 --> u6
u2 -->|"durability must precede first deploy"| u6
u3 --> u5
u5 -->|"invokes"| u6
u6 -->|"settled host layout"| u7
u6 --> ops
u7 --> ops
classDef r1 fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef r2 fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
classDef r3 fill:#fbd38d,stroke:#c05621,stroke-width:1px,color:#000;
classDef r4 fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000;
classDef opsphase fill:#f6e05e,stroke:#c05621,stroke-width:1px,color:#000;
class u1,u2 r1;
class u3,u4 r2;
class u5,u6 r3;
class u7 r4;
class ops opsphase;
Text alternative: U1 and U2 are independent and run first; U3 needs U1's path layout and U4's origins; U5 and U6 need the application work complete; U7 needs U6's settled host layout; the Operations phase follows.
Dependency Matrix
| Unit | Depends on | Depended on by | Nature of dependency |
|---|---|---|---|
| U1 Hosting & Serving | — | U3, U6 | Establishes the path layout that U3's CSP scopes against and U6 deploys into |
| U2 Data Durability | — | U6 | Must land before any automated deploy, or the first atomic switch destroys the key ring |
| U3 Security Headers | U1, U4 | U5 | Needs U1's final paths and U4's external origins |
| U4 Observability | — | U3, U5 | Introduces the origins U3 must permit and the build variables U5 must supply |
| U5 CI Workflow | U3, U4 | U6 | Gates must pass against the finished application; the two builds need U4's variables |
| U6 Deploy Workflow | U1, U2, U5 | U7, Operations | Deploys what the application units produced, invoked by U5 |
| U7 Documentation | U6 | Operations | Documents the layout U6 settles |
Ordering Constraints That Are Load-Bearing
These are not preferences. Reordering any of them produces a broken or dangerous result.
1. U2 before U6 — otherwise the first deploy is the dangerous one
The atomic release switch (U6) changes the content root path on every deploy. Until U2 configures a database-backed key ring with an explicit application discriminator, that switch discards the Data Protection keys and makes every stored slave API key undecryptable. The symptom presents as a network fault between master and slave, so it would be misdiagnosed.
Deploying first and hardening afterwards means the very first production deploy carries the failure.
2. U4 before or with U3 — otherwise the CSP is written blind
U3's Strict policy must permit the Umami script origin and the Sentry ingest origin. Those origins are introduced by U4. Writing U3 first means either guessing them or shipping a CSP that blocks the observability U4 then adds — a failure that appears only in a real browser.
This is why R2 groups them rather than running them in sequence.
3. U1 before U3 — otherwise path scoping is provisional
U3 assigns policies by path prefix. Until U1 settles which paths exist and where they are served from, that assignment is written against a layout still in flux.
4. U3 and U4 before U5 — otherwise the gates fail on incomplete work
U5's six gates run against the whole application. Switching them on before the application units are complete produces failures that reflect unfinished work rather than defects.
5. U5 with U6 — one interface, two files
U6 is a reusable workflow invoked by U5 with a fixed input set. Designing them apart risks an interface mismatch that only surfaces on the first real run.
6. U6 before U7 — documentation cannot precede the layout it documents
U7's website contract states target paths, reserved paths and the persistent-directory arrangement. U6 settles those in its Infrastructure Design.
What Is Not Dependent
Worth stating explicitly, because it justifies the grouping:
- U1 and U2 do not touch each other. U1 changes serving and middleware; U2 changes persistence and startup. They share
Program.csas a file, but not as logic — U1 adds pipeline and endpoint registrations, U2 adds service registration and a startup call. A merge conflict is possible; a behavioural conflict is not. - U4 does not depend on U1, U2 or U3. Observability can be added to the application as it stands today.
- U7 does not depend on U3, U4 or U5 beyond describing their results.
Shared Resources and Coordination Points
| Resource | Touched by | Coordination needed |
|---|---|---|
SlpModularCms.Api/Program.cs |
U1, U2, U3, U4 | Four units modify the same file in different places. Registration and pipeline order is specified in services.md § S-01, so each unit inserts at a defined position rather than appending |
SlpModularCms.Api.Slave/Program.cs |
U1 (health only), U2, U3, U4 | Same, minus the static mounts. The Slave is a reference instance (Q2 of Application Design = A) and must keep working; it has no test project, so it is verified by starting it |
SlpModularCms.Core |
U1, U2, U3, U4 | Each unit adds its own subfolder under Hosting/ — Health/, Security/, Observability/ — so files do not collide |
AvailabilityModule.cs / MasterModule.cs |
U2 | Removing AddDataProtection() from both. No other unit touches them |
appsettings.json |
U2, U3, U4 | Three new sections. Additive, no overlap |
frontend/ |
U4 (features), U5 (lint fixes) | U5's lint fixes touch AddCmsInstanceDialog.tsx, InviteUserDialog.tsx, SettingsPage.tsx and SetStatusDialog.tsx; U4 touches main.tsx, config.ts and adds a Umami component. No overlapping files |
.gitea/workflows/ |
U5, U6 | Separate files sharing one input contract |
Consequence of Merging the Quality Gates into U5
Q2 = B moved the lint fixes and package pins from a standalone first unit into U5. This is coherent — gates and their prerequisites land in one commit, so the pipeline is never red on arrival — but it has one side effect worth managing:
pnpm run lint stays failing through U3 and U4, and U4 changes frontend files. New violations introduced during U4 would therefore hide among the five pre-existing ones.
Mitigation: run lint on the changed files during U4 rather than the whole tree. The blocking gate still arrives with U5, but nothing new accumulates in the meantime.
The overlap is limited: U5's fixes and U4's changes touch disjoint files, so there is no merge risk — only a detection gap.
Rollback Between Units
Every unit is a self-contained commit on feature/gitea-deployment-workflow, with a single pull request at the end (Q6 = A).
| Unit | Revertible independently? | Notes |
|---|---|---|
| U1 | Yes | Pipeline and endpoint registrations |
| U2 | Yes, with care | The Core migration adds a table; reverting the code leaves the table in place, which is harmless |
| U3 | Yes | Additive middleware plus one config section |
| U4 | Yes | Additive |
| U5 | Yes | New file plus lint and package changes |
| U6 | Yes | New file only |
| U7 | Yes | Documentation only |
Nothing is deployed to any environment until U6 is complete and explicitly triggered, so a mid-sequence failure cannot affect a running environment.