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:
@@ -1,118 +1,187 @@
|
||||
# Dependencies
|
||||
# Dependencies
|
||||
|
||||
## Internal Dependencies
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Api["SlpModularCms.Api"]
|
||||
Core["SlpModularCms.Core"]
|
||||
ModIdentity["SlpModularCms.Modules.Identity"]
|
||||
ModAvail["SlpModularCms.Modules.Availability"]
|
||||
CoreTests["SlpModularCms.Core.Tests"]
|
||||
AvailTests["SlpModularCms.Modules.Availability.Tests"]
|
||||
Frontend["SlpModularCms.Frontend\n(to be built)"]
|
||||
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 -->|compile| Core
|
||||
Api -->|compile| ModIdentity
|
||||
Api -->|compile| ModAvail
|
||||
ModIdentity -->|compile| Core
|
||||
ModAvail -->|compile| Core
|
||||
CoreTests -->|test| Core
|
||||
AvailTests -->|test| ModAvail
|
||||
AvailTests -->|test| Core
|
||||
Frontend -->|runtime REST| Api
|
||||
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
|
||||
|
||||
style Api fill:#4CAF50,stroke:#2E7D32,color:#fff
|
||||
style Core fill:#FFC107,stroke:#F57F17,color:#000
|
||||
style ModIdentity fill:#4CAF50,stroke:#2E7D32,color:#fff
|
||||
style ModAvail fill:#4CAF50,stroke:#2E7D32,color:#fff
|
||||
style CoreTests fill:#9E9E9E,stroke:#424242,color:#fff
|
||||
style AvailTests fill:#9E9E9E,stroke:#424242,color:#fff
|
||||
style Frontend fill:#2196F3,stroke:#0D47A1,color:#fff
|
||||
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 depends on SlpModularCms.Core
|
||||
#### `SlpModularCms.Api` → `SlpModularCms.Core`
|
||||
- **Type**: Compile
|
||||
- **Reason**: Needs ApplicationDbContext, entities, DI extensions, module registration
|
||||
- **Reason**: `AddCoreInfrastructure`, `AddCmsCors`, `AddCmsRateLimiting`, `ModuleOrchestrator`, `ApiPrefixConvention`.
|
||||
|
||||
#### SlpModularCms.Api depends on SlpModularCms.Modules.Identity
|
||||
#### `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**: Registers Identity module and its HTTP controllers
|
||||
- **Reason**: Same as above, minus the Master module — the omission is the entire point of this host.
|
||||
|
||||
#### SlpModularCms.Api depends on SlpModularCms.Modules.Availability
|
||||
#### `Modules.Identity` → `Core`
|
||||
- **Type**: Compile
|
||||
- **Reason**: Registers Availability module and its HTTP controllers
|
||||
- **Reason**: Identity entities, `IAuthService`, `IInvitationService`, `ISetupService`, request/response models, authorization policies.
|
||||
|
||||
#### SlpModularCms.Modules.Identity depends on SlpModularCms.Core
|
||||
#### `Modules.Availability` → `Core`
|
||||
- **Type**: Compile
|
||||
- **Reason**: Uses domain entities (ApplicationUser, Invitation), services (IAuthService), and DbContext
|
||||
- **Reason**: `IAvailabilityService`, `AvailabilityStatus`, `AvailabilityOptions`, `MasterControlledAvailabilityException`, `IModule`.
|
||||
|
||||
#### SlpModularCms.Modules.Availability depends on SlpModularCms.Core
|
||||
#### `Modules.Master` → `Core`
|
||||
- **Type**: Compile
|
||||
- **Reason**: Uses GlobalAvailabilityState, AvailabilityStatus, AvailabilityOptions
|
||||
- **Reason**: `IModule`, authorization policies, shared exception types.
|
||||
|
||||
## External Dependencies (Backend)
|
||||
#### 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`.
|
||||
|
||||
### Microsoft.AspNetCore.Identity
|
||||
- **Version**: .NET 10 built-in
|
||||
- **Purpose**: User and role management, password hashing
|
||||
- **License**: MIT
|
||||
#### `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.
|
||||
|
||||
### Microsoft.EntityFrameworkCore + SqlServer provider
|
||||
- **Version**: .NET 10 built-in
|
||||
- **Purpose**: Data persistence
|
||||
- **License**: MIT
|
||||
#### `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.**
|
||||
|
||||
### Microsoft.AspNetCore.Authentication.JwtBearer
|
||||
- **Version**: .NET 10 built-in
|
||||
- **Purpose**: JWT authentication middleware
|
||||
- **License**: MIT
|
||||
### Cross-instance runtime dependencies
|
||||
|
||||
### Microsoft.IdentityModel.Tokens
|
||||
- **Version**: .NET 10 built-in
|
||||
- **Purpose**: JWT token creation and validation
|
||||
- **License**: MIT
|
||||
Not project references, but real coupling between deployed instances:
|
||||
|
||||
## External Dependencies (Frontend — from package.json)
|
||||
- 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.
|
||||
|
||||
### react + react-dom
|
||||
- **Version**: 18.3.1
|
||||
- **Purpose**: Core UI framework
|
||||
- **License**: MIT
|
||||
## External Dependencies
|
||||
|
||||
### react-router
|
||||
- **Version**: 7.13.0
|
||||
- **Purpose**: Client-side routing
|
||||
- **License**: MIT
|
||||
### Backend — `SlpModularCms.Core`
|
||||
|
||||
### @radix-ui/* (multiple packages)
|
||||
- **Version**: Various (1.x–2.x)
|
||||
- **Purpose**: shadcn/ui component primitives
|
||||
- **License**: MIT
|
||||
| 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 |
|
||||
|
||||
### tailwindcss
|
||||
- **Version**: 4.1.12
|
||||
- **Purpose**: Utility-first CSS framework
|
||||
- **License**: MIT
|
||||
### Backend — `SlpModularCms.Api`
|
||||
|
||||
### lucide-react
|
||||
- **Version**: 0.487.0
|
||||
- **Purpose**: Icon library
|
||||
- **License**: ISC
|
||||
| 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 |
|
||||
|
||||
### recharts
|
||||
- **Version**: 2.15.2
|
||||
- **Purpose**: Charts and data visualization
|
||||
- **License**: MIT
|
||||
`SlpModularCms.Api.Slave` carries `Microsoft.EntityFrameworkCore.Design` and `Scalar.AspNetCore` at the same versions.
|
||||
|
||||
### react-hook-form
|
||||
- **Version**: 7.55.0
|
||||
- **Purpose**: Form state management
|
||||
- **License**: MIT
|
||||
### Backend — modules
|
||||
|
||||
### sonner
|
||||
- **Version**: 2.0.3
|
||||
- **Purpose**: Toast notifications
|
||||
- **License**: MIT
|
||||
| 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.x–2.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.
|
||||
|
||||
Reference in New Issue
Block a user