Files
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

188 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Dependencies
## Internal Dependencies
```mermaid
graph TD
api["SlpModularCms.Api<br/>Client"]
slave["SlpModularCms.Api.Slave<br/>Client"]
core["SlpModularCms.Core"]
mid["Modules.Identity"]
mav["Modules.Availability"]
mma["Modules.Master"]
tcore["Core.Tests"]
tid["Modules.Identity.Tests"]
tav["Modules.Availability.Tests"]
tma["Modules.Master.Tests"]
fe["frontend<br/>admin SPA"]
api --> core
api --> mid
api --> mav
api --> mma
slave --> core
slave --> mid
slave --> mav
mid --> core
mav --> core
mma --> core
tcore --> core
tid --> mid
tav --> mav
tma --> mma
fe -->|"REST at runtime"| api
api -->|"pnpm build at publish"| fe
classDef client fill:#90cdf4,stroke:#2b6cb0,stroke-width:1px,color:#000;
classDef corelayer fill:#fbd38d,stroke:#c05621,stroke-width:1px,color:#000;
classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:1px,color:#000;
classDef test fill:#e2e8f0,stroke:#4a5568,stroke-width:1px,color:#000;
classDef frontend fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000;
class api,slave client;
class core corelayer;
class mid,mav,mma module;
class tcore,tid,tav,tma test;
class fe frontend;
```
Text alternative: Both host projects reference Core and their modules; every module references only Core; each test project targets its own production project; the admin SPA calls the API at runtime and is itself built by the API project at publish time — a bidirectional coupling between backend and frontend.
### Dependency Details
#### `SlpModularCms.Api` → `SlpModularCms.Core`
- **Type**: Compile
- **Reason**: `AddCoreInfrastructure`, `AddCmsCors`, `AddCmsRateLimiting`, `ModuleOrchestrator`, `ApiPrefixConvention`.
#### `SlpModularCms.Api` → `Modules.Identity`, `Modules.Availability`, `Modules.Master`
- **Type**: Compile (present to force the module DLLs into the output directory)
- **Reason**: `ModuleOrchestrator` discovers modules by globbing `SlpModularCms.Modules.*.dll` in the app base directory, so a project reference is how a module ends up deployed. The code itself does not call into the modules directly. **Consequence for deployment: which modules an instance has is determined by which DLLs the published output contains.**
#### `SlpModularCms.Api.Slave` → `Core`, `Modules.Identity`, `Modules.Availability`
- **Type**: Compile
- **Reason**: Same as above, minus the Master module — the omission is the entire point of this host.
#### `Modules.Identity` → `Core`
- **Type**: Compile
- **Reason**: Identity entities, `IAuthService`, `IInvitationService`, `ISetupService`, request/response models, authorization policies.
#### `Modules.Availability` → `Core`
- **Type**: Compile
- **Reason**: `IAvailabilityService`, `AvailabilityStatus`, `AvailabilityOptions`, `MasterControlledAvailabilityException`, `IModule`.
#### `Modules.Master` → `Core`
- **Type**: Compile
- **Reason**: `IModule`, authorization policies, shared exception types.
#### Test projects → their production project (and transitively `Core`)
- **Type**: Test
- **Reason**: Each suite mirrors one production project, per the solution-folder rules in `CLAUDE.md`.
#### `frontend` → `SlpModularCms.Api`
- **Type**: Runtime (HTTP/REST)
- **Reason**: All data comes from `/api/v1/**` at `VITE_API_BASE_URL`, with credentials so the refresh cookie travels.
#### `SlpModularCms.Api` → `frontend` (build-time, reverse direction)
- **Type**: Build
- **Reason**: The `BuildAndCopyAdminFrontend` MSBuild target runs `pnpm install --frozen-lockfile` and `pnpm build` in `frontend/` before publish and copies `dist/**` into `wwwroot/admin/`. **This makes Node and pnpm hard prerequisites of `dotnet publish` on any build agent.**
### Cross-instance runtime dependencies
Not project references, but real coupling between deployed instances:
- A **Master** instance calls each registered slave's `POST /api/v1/master/register` and `POST /api/v1/master/status`, authenticated with `X-Master-Api-Key`, through a resilience pipeline (2 retries, exponential backoff with jitter, configurable timeout).
- Each **slave** calls its Master's `GET /api/v1/SlaveStatus` on a timer (`MasterPolling:PollIntervalSeconds`), and fails open to Available after `MasterPolling:FailOpenAfterMinutes` of unreachability.
- Both directions require the two instances to be reachable over HTTP from one another, which is a deployment/network consideration rather than a code one.
## External Dependencies
### Backend — `SlpModularCms.Core`
| Package | Version | Purpose | License |
|---|---|---|---|
| `Microsoft.EntityFrameworkCore.SqlServer` | 10.0.9 | SQL Server persistence | MIT |
| `Microsoft.AspNetCore.Identity.EntityFrameworkCore` | 10.0.9 | Users, roles, password hashing | MIT |
| `Microsoft.AspNetCore.Authentication.JwtBearer` | 10.0.9 | Bearer token validation | MIT |
| `Microsoft.AspNetCore.OpenApi` | 10.0.9 | OpenAPI document generation | MIT |
| `Asp.Versioning.Mvc` | 10.0.0 | API version reporting | MIT |
| `Microsoft.Extensions.DependencyInjection.Abstractions` | 10.0.9 | DI abstractions | MIT |
| `Microsoft.Extensions.Hosting.Abstractions` | 10.0.9 | Hosting abstractions | MIT |
| `Microsoft.Extensions.Logging.Abstractions` | 10.0.9 | Logging abstractions | MIT |
| `FrameworkReference: Microsoft.AspNetCore.App` | net10.0 | Lets a class library use ASP.NET Core types, incl. Data Protection | MIT |
### Backend — `SlpModularCms.Api`
| Package | Version | Purpose | License |
|---|---|---|---|
| `Asp.Versioning.Mvc` | 10.0.0 | API versioning | MIT |
| `Microsoft.AspNetCore.Authentication.JwtBearer` | 10.0.9 | Bearer auth | MIT |
| `Microsoft.AspNetCore.OpenApi` | 10.0.9 | OpenAPI | MIT |
| `Microsoft.EntityFrameworkCore.Design` | 10.0.9 (PrivateAssets) | `dotnet ef` tooling | MIT |
| `Scalar.AspNetCore` | 2.16.3 | `/scalar` API reference, Development only | MIT |
`SlpModularCms.Api.Slave` carries `Microsoft.EntityFrameworkCore.Design` and `Scalar.AspNetCore` at the same versions.
### Backend — modules
| Project | Package | Version | Purpose | License |
|---|---|---|---|---|
| `Modules.Master` | `Microsoft.Extensions.Http.Resilience` | 9.6.0 | Retry/timeout for master→slave calls (pulls in Polly) | MIT |
| `Modules.Availability` | — | — | No external packages beyond Core's transitives | — |
| `Modules.Identity` | — | — | No external packages beyond Core's transitives | — |
**Version note**: `Microsoft.Extensions.Http.Resilience` 9.6.0 is a 9.x package on `net10.0` targets. It works, but it is the one dependency out of step with the otherwise uniform 10.0.x line — worth pinning deliberately rather than by accident in any CI setup.
### Backend — test projects (all four, identical set)
| Package | Version | Purpose | License |
|---|---|---|---|
| `Microsoft.NET.Test.Sdk` | 17.14.1 | Test host | MIT |
| `xunit` | 2.9.3 | Test framework | Apache-2.0 |
| `xunit.runner.visualstudio` | 3.1.4 | Test adapter | Apache-2.0 |
| `FluentAssertions` | 8.10.0 | Assertions | Dual: free for non-commercial / paid commercial from v8 — **worth verifying against how this project is used** |
| `NSubstitute` | 5.3.0 | Mocking | BSD-3-Clause |
| `AutoFixture` | 4.18.1 | Test data generation | MIT |
| `Microsoft.EntityFrameworkCore.InMemory` | 10.0.9 | In-memory provider for tests | MIT |
| `coverlet.collector` | 6.0.4 | Coverage collection | MIT |
### Frontend — runtime (`frontend/package.json` dependencies)
| Package | Version | Purpose | License |
|---|---|---|---|
| `react`, `react-dom` | ^19.2.6 | UI framework | MIT |
| `@tanstack/react-router` | ^1.170.16 | Routing (honours `BASE_URL`, so `/admin` works) | MIT |
| `@tanstack/react-query` | ^5.101.0 | Server state | MIT |
| `@radix-ui/react-{dialog,dropdown-menu,label,select,slot}` | 1.x2.x | Accessible primitives | MIT |
| `react-hook-form` | ^7.79.0 | Forms | MIT |
| `@hookform/resolvers` | ^5.4.0 | Validation bridge | MIT |
| `zod` | ^4.4.3 | Schema validation (also validates app config) | MIT |
| `i18next`, `react-i18next`, `i18next-browser-languagedetector` | 26 / 17 / 8 | NL/EN localisation | MIT |
| `lucide-react` | ^1.21.0 | Icons | ISC |
| `sonner` | ^2.0.7 | Toasts | MIT |
| `class-variance-authority`, `clsx`, `tailwind-merge` | 0.7 / 2.1 / 3.6 | Class composition | MIT |
### Frontend — build and test (devDependencies)
| Package | Version | Purpose | License |
|---|---|---|---|
| `vite` | ^8.0.12 | Build tool / dev server | MIT |
| `@vitejs/plugin-react` | ^6.0.1 | React support | MIT |
| `typescript` | ~6.0.2 | Type checking (`tsc -b` gates the build) | Apache-2.0 |
| `tailwindcss`, `@tailwindcss/vite` | ^4.3.1 | Styling | MIT |
| `vitest`, `@vitest/coverage-v8` | ^4.1.9 | Tests and coverage | MIT |
| `jsdom` | ^29.1.1 | DOM for tests | MIT |
| `@testing-library/{react,jest-dom,user-event}` | 16.3 / 6.9 / 14.6 | Component testing | MIT |
| `msw` | ^2.14.6 | Request mocking | MIT |
| `eslint`, `typescript-eslint`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`, `@eslint/js`, `globals` | 10 / 8.59 / 7.1 / 0.5 / 10 / 17.6 | Linting | MIT |
| `prettier` | ^3.8.4 | Formatting | MIT |
| `concurrently` | ^9.1.2 | Runs `dev:all` (master + slave dev servers) | MIT |
| `@types/{node,react,react-dom}` | 24 / 19.2 / 19.2 | Type definitions | MIT |
### Lockfiles and reproducibility
- `frontend/pnpm-lock.yaml` exists, and the publish target uses `--frozen-lockfile`, so frontend installs are reproducible.
- There is **no `packages.lock.json`** for any .NET project — NuGet restore is not locked, so `dotnet restore` can resolve differently over time on floating transitive versions. Relevant if reproducible CI builds matter.
### Dependencies that are absent but expected by this feature
No package currently provides uptime, analytics, error tracking or structured logging. Adding **Sentry** (a `Sentry.AspNetCore` package on the backend and `@sentry/react` on the frontend) and **Umami** (a script tag, no package) would be new dependencies; **UptimeRobot** is external and needs only an HTTP endpoint to probe.