# Code Structure
## Build System
- **Type**: .NET SDK (MSBuild / `dotnet` CLI) for the backend; pnpm + Vite for the admin SPA.
- **Solution**: `SlpModularCms.sln` — 10 projects, organised into three top-level Solution Folders (see `CLAUDE.md` / `AGENTS.md`):
- **Application** — `SlpModularCms.Core` plus a nested **Modules** folder (`Modules.Master`, `Modules.Identity`, `Modules.Availability`)
- **Tests** — mirrors Application, with its own nested **Modules** folder
- **Clients** — the deployable hosts: `SlpModularCms.Api`, `SlpModularCms.Api.Slave`
- **Target framework**: `net10.0` for every project. SDK in use: 10.0.301.
- **Key build settings**: `Nullable` and `ImplicitUsings` enabled everywhere. `SlpModularCms.Core` uses `` so a class library can depend on ASP.NET Core types.
- **Coverage**: `coverlet.runsettings` at the repository root excludes migrations, `obj/`, generated OpenAPI interceptors, and anything marked `[ExcludeFromCodeCoverage]`.
- **Frontend build**: `frontend/package.json` — `build` runs `tsc -b && vite build`; `vite.config.ts` sets `base: '/admin/'` for `command === 'build'` only, so the dev server still serves from `/`.
- **Publish coupling**: `SlpModularCms.Api.csproj` defines the `BuildAndCopyAdminFrontend` target with `BeforeTargets="Publish"`, which runs `pnpm install --frozen-lockfile` and `pnpm build` in `frontend/` and copies `frontend/dist/**` into `wwwroot/admin/`. **`dotnet publish` therefore requires Node and pnpm on the build machine.** `wwwroot/` is gitignored.
## Project Structure
```mermaid
graph TD
root["SlpModularCms (repo root)"]
sln["SlpModularCms.sln"]
src["src/"]
fe["frontend/ (admin SPA)"]
docs["aidlc-docs/"]
api["SlpModularCms.Api
Client / host"]
slave["SlpModularCms.Api.Slave
Client / host"]
core["SlpModularCms.Core
shared library"]
mid["Modules.Identity"]
mav["Modules.Availability"]
mma["Modules.Master"]
tests["4 test projects
Core, Identity, Availability, Master"]
root --> sln
root --> src
root --> fe
root --> docs
src --> api
src --> slave
src --> core
src --> mid
src --> mav
src --> mma
src --> tests
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 meta fill:#d6bcfa,stroke:#6b46c1,stroke-width:1px,color:#000;
class api,slave,fe client;
class core corelayer;
class mid,mav,mma module;
class tests test;
class root,sln,src,docs meta;
```
Text alternative: The repository root holds the solution file, a `src/` folder with two host projects, Core, three modules and four test projects, plus a separate `frontend/` admin SPA and the `aidlc-docs/` documentation tree.
## Key Classes/Modules
```mermaid
classDiagram
class IModule {
+string Name
+string Version
+RegisterServices(IServiceCollection)
+UseModule(IApplicationBuilder)
}
class ModuleOrchestrator {
+IReadOnlyList~string~ ModuleNames
+DiscoverModules()
+RegisterModuleServices(IServiceCollection)
+UseModules(IApplicationBuilder)
}
class IdentityModule
class AvailabilityModule
class MasterModule
class IAvailabilityService {
+IsAvailableAsync()
+UpdateStatusAsync()
}
class PersistentAvailabilityService
class IMasterAvailabilityService {
+GetMasterStatus()
}
class MasterAvailabilityService
IModule <|.. IdentityModule
IModule <|.. AvailabilityModule
IModule <|.. MasterModule
ModuleOrchestrator --> IModule
IAvailabilityService <|.. PersistentAvailabilityService
IMasterAvailabilityService <|.. MasterAvailabilityService
```
Text alternative: `ModuleOrchestrator` works against the `IModule` contract implemented by the three modules; the Availability module supplies the concrete availability and master-gate services behind Core's interfaces.
### Existing Files Inventory
#### Clients (deployable hosts)
- `src/SlpModularCms.Api/Program.cs` — Host composition: local settings overlay, module discovery, core infrastructure/CORS/rate limiting, `/api/v1` convention, enum-as-string JSON, exception handler, HTTPS redirect, static files, CORS, module middleware, auth, controllers, and the two SPA fallbacks (`/admin/{*path:nonfile}` → `admin/index.html`, `{*path:nonfile}` → `index.html`).
- `src/SlpModularCms.Api/Program.Coverage.cs` — Coverage-support partial.
- `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — Package/project references plus the `BuildAndCopyAdminFrontend` publish target.
- `src/SlpModularCms.Api/appsettings.json` — Production baseline with placeholder secrets; `Logging` default `Warning`; `AllowedHosts: "*"`; empty `Cors:AllowedOrigins`; `JwtSettings`, `Availability`, `MasterModule`, `MasterPolling`, `RateLimiting` sections.
- `src/SlpModularCms.Api/appsettings.Development.json` — LocalDB connection, dev JWT secret, `CookieSameSite: None`, CORS for `localhost:5173`, relaxed rate limits, `MasterUrl: https://localhost:7221`.
- `src/SlpModularCms.Api/appsettings.local.json` — Gitignored developer overrides.
- `src/SlpModularCms.Api/Properties/launchSettings.json` — `http` (5284) and `https` (7221) profiles, both `Development`, launching `/scalar`.
- `src/SlpModularCms.Api.Slave/Program.cs`, `…/appsettings*.json`, `…/Properties/launchSettings.json` — Second host on 7222; no Master module reference; expects its own database.
#### Core
- `src/SlpModularCms.Core/Hosting/ModuleOrchestrator.cs` — Globs `SlpModularCms.Modules.*.dll` from `AppDomain.CurrentDomain.BaseDirectory`, loads assemblies, instantiates every non-abstract `IModule`, and exposes `ModuleNames`. Failures are logged, not thrown.
- `src/SlpModularCms.Core/Hosting/ServiceCollectionExtensions.cs` — `AddCoreInfrastructure` (DbContext, Identity with password policy, JWT bearer with `ClockSkew.Zero`, the three hierarchical policies, exception handler + ProblemDetails, API versioning, OpenAPI), `AddCmsCors`, `AddCmsRateLimiting` (fixed-window `login`, sliding-window `refresh`).
- `src/SlpModularCms.Core/Hosting/ApiPrefixConvention.cs` — Applies the single `api/v1` prefix to every controller.
- `src/SlpModularCms.Core/Hosting/SystemController.cs` — `GET /api/v1/System/capabilities`, returns loaded module names.
- `src/SlpModularCms.Core/Data/ApplicationDbContext.cs` — Identity + `RefreshToken`, `Invitation`, `ModulePermission`, `GlobalAvailabilityState`.
- `src/SlpModularCms.Core/Identity/Entities/*.cs` — `ApplicationUser`, `ApplicationRole`, `RefreshToken`, `Invitation`, `ModulePermission`, `GlobalAvailabilityState`.
- `src/SlpModularCms.Core/Identity/Models/*.cs` — `IdentityRequests`, `JwtSettings`, `TokenResponse`.
- `src/SlpModularCms.Core/Identity/Services/{AuthService,IAuthService,InvitationService,IInvitationService,SetupService}.cs` — Login/refresh/revoke with token rotation, invitation lifecycle, first-Owner bootstrap.
- `src/SlpModularCms.Core/Identity/Authorization/{HierarchicalRoleHandler,HierarchicalRoleRequirement}.cs` — Owner > Administrator > User satisfaction.
- `src/SlpModularCms.Core/Availability/{AvailabilityOptions,AvailabilityStatus,AvailabilityStatusDetails,IAvailabilityService,MasterControlledAvailabilityException}.cs` — Availability contract; the exception is what turns a local override attempt into `409 Conflict` while master-controlled.
- `src/SlpModularCms.Core/Exceptions/{GlobalExceptionHandler,ValidationException,UnauthorizedException,InvitationOrUserAlreadyExistsException}.cs` — RFC 9457 mapping.
- `src/SlpModularCms.Core/Modules/{IModule,ModuleInfo}.cs` — Module contract.
- `src/SlpModularCms.Core/Migrations/` — 5 files; `ApplicationDbContext` migrations, applied **manually only**.
#### Modules.Identity
- `Controllers/AuthController.cs` — `login` (rate-limited `login`), `refresh` (rate-limited `refresh`), `revoke`, `change-password`.
- `Controllers/SetupController.cs` — `status`, `owner`.
- `Controllers/InvitationController.cs` — `validate`, `complete`; anonymous.
- `Controllers/UsersController.cs` — list, `me` update, `invite`, role/active updates, delete; `AdminOnly` by default.
- `IdentityModule.cs` — Module registration.
#### Modules.Availability
- `AvailabilityModule.cs` — Registers services, `AvailabilityDbContext`, Data Protection, polling `HttpClient` and hosted service; on `UseModule` runs `Database.Migrate()` and installs `AvailabilityMiddleware`.
- `Middleware/AvailabilityMiddleware.cs` — Bypass prefixes, admin-token bypass, master gate then local status, 503 `ProblemDetails` otherwise.
- `Services/PersistentAvailabilityService.cs` — Persisted local status with caching/circuit breaker; throws `MasterControlledAvailabilityException` on local override while master-controlled.
- `Services/{MasterAvailabilityService,MasterGateStatus,MasterStatusPollClient,MasterApiKeyProtector,…}.cs` — Master gate state, poll client, encrypted key handling, DI dependency bundle.
- `BackgroundServices/MasterStatusPollingBackgroundService.cs` — Periodic pull with fail-open.
- `Controllers/{AvailabilityController,MasterController}.cs` — Public status + Owner-only update; inbound master register/status/registered-url.
- `Data/AvailabilityDbContext.cs`, `Data/Entities/MasterRegistration.cs`, `Config/MasterPollingOptions.cs`, `Repositories/*`, `Models/MasterModels.cs`; `Migrations/` — 5 files, auto-applied.
#### Modules.Master
- `MasterModule.cs` — Data Protection, `MasterModuleOptions`, `MasterDbContext`, repositories/services, `SlaveApiClient` with a `slave-resilience` handler (2 retries, exponential backoff with jitter, configurable timeout), `IntegrityCheckBackgroundService`, `HttpContextAccessor`; migrates on `UseModule`.
- `Controllers/CmsInstanceController.cs` — Owner-only list/create/update-status.
- `Controllers/SlaveStatusController.cs` — Anonymous pull endpoint for slaves.
- `Services/{CmsInstanceService,SlaveApiClient,ApiKeyProtector,MasterServiceDependencies,…}.cs`
- `BackgroundServices/IntegrityCheckBackgroundService.cs` — Periodic reconciliation and status re-push.
- `Data/MasterDbContext.cs`, `Data/Entities/{CmsInstance,CmsInstanceStatus}.cs`, `Models/*`, `Options/MasterModuleOptions.cs`, `Repositories/*`; `Migrations/` — 3 files, auto-applied.
#### Tests
- `src/SlpModularCms.Core.Tests/` — 7 files (Exceptions, Hosting, Identity).
- `src/SlpModularCms.Modules.Identity.Tests/` — 4 files (Controllers).
- `src/SlpModularCms.Modules.Availability.Tests/` — 10 files (Controllers, Services, Repositories, BackgroundServices).
- `src/SlpModularCms.Modules.Master.Tests/` — 7 files (Controllers, Services, Repositories, BackgroundServices).
#### frontend (admin SPA)
- `frontend/vite.config.ts` — `base: '/admin/'` on build, `@` alias, dev port 5173, Vitest config with v8 coverage.
- `frontend/package.json` — Scripts `dev`, `dev:slave` (mode `slave`, port 5174), `dev:all` (concurrently), `build`, `lint`, `format`, `format:check`, `test`, `test:watch`, `test:coverage`, `preview`.
- `frontend/.env.example` — `VITE_API_BASE_URL`, `VITE_APP_TITLE`, plus notes for the slave setup. `.env.local` / `.env.slave.local` are local-only.
- `frontend/src/lib/config.ts` — Reads `VITE_API_BASE_URL` and `VITE_APP_TITLE`; Zod-validates `apiBaseUrl` as a URL, warning only in dev. **Makes the production bundle environment-specific.**
- `frontend/src/lib/api-client.ts` — `fetch` wrapper: credentials always sent, in-memory access token, 401 refresh-and-retry interceptor, `ProblemDetailsError` and `NetworkError`.
- `frontend/src/router.tsx` — TanStack Router with `basepath: import.meta.env.BASE_URL`, so routing follows the `/admin/` base automatically.
- `frontend/src/main.tsx` — Sets document title from config, React Query client, optional MSW via `VITE_ENABLE_MSW`, renders only after the initial silent refresh settles.
- `frontend/src/api/use*.ts` — Typed hooks per resource (availability, users, profile, invitation, setup, CMS instances, system capabilities), each with a colocated test.
- `frontend/src/pages/` — 10 pages, each with a test.
- `frontend/src/components/{auth,cms,layout,shared,ui,users}/` — Guards (`ModuleGuard`, `RoleGuard`), CMS instance dialogs/list, layout shell, shadcn-style primitives.
- `frontend/src/{contexts,hooks,i18n,mocks,test}/` — Auth provider, hooks, NL/EN translations, MSW handlers per domain, test setup.
## Design Patterns
### Module / plugin pattern
- **Location**: `Core/Modules/IModule.cs`, `Core/Hosting/ModuleOrchestrator.cs`, each `*Module.cs`.
- **Purpose**: Let one codebase deploy with different capability sets.
- **Implementation**: Reflection-based discovery of `SlpModularCms.Modules.*.dll` in the app base directory; each module registers services and middleware itself. Deployment content, not configuration, decides capability.
### Repository pattern
- **Location**: `Modules.Master/Repositories/`, `Modules.Availability/Repositories/`.
- **Purpose**: Keep EF Core access behind an interface so services stay unit-testable.
- **Implementation**: Interface plus EF-backed implementation per aggregate. Core's identity services use `ApplicationDbContext`/Identity managers directly rather than repositories — an intentional inconsistency between old and new code.
### Options pattern
- **Location**: `JwtSettings`, `AvailabilityOptions`, `MasterModuleOptions`, `MasterPollingOptions`.
- **Purpose**: Bind configuration sections to typed objects.
- **Implementation**: `services.Configure` / `AddOptions().BindConfiguration(...)`.
### Dependency-bundle (parameter object)
- **Location**: `MasterServiceDependencies`, `MasterAvailabilityServiceDependencies`.
- **Purpose**: Keep constructors manageable where a service needs many collaborators.
### Middleware gate
- **Location**: `AvailabilityMiddleware`.
- **Purpose**: Enforce availability centrally rather than per controller, with explicit bypasses.
### Background reconciliation (push + pull)
- **Location**: `IntegrityCheckBackgroundService` (master push), `MasterStatusPollingBackgroundService` (slave pull, fail-open).
- **Purpose**: Make distributed status self-healing without a message broker.
### JWT with refresh-token rotation
- **Location**: `AuthService`, `AuthController`, `frontend/src/lib/api-client.ts`.
- **Purpose**: Short-lived access tokens held in memory; rotating refresh token in an httpOnly cookie scoped to `/api/v1/auth`.
### Global exception handling to RFC 9457
- **Location**: `GlobalExceptionHandler` + typed exceptions.
- **Purpose**: One error contract for all clients.
### Resilience pipeline
- **Location**: `MasterModule` `slave-resilience` handler.
- **Purpose**: Tolerate slow or briefly unreachable slaves without failing the Owner's action outright.
## Critical Dependencies
### Microsoft.EntityFrameworkCore.SqlServer — 10.0.9
- **Usage**: All three `DbContext` types, one shared connection string.
- **Purpose**: Persistence. Module contexts self-migrate; the Core context does not.
### Microsoft.AspNetCore.Identity.EntityFrameworkCore — 10.0.9
- **Usage**: `ApplicationUser`/`ApplicationRole` stores, password hashing, policy.
- **Purpose**: Account primitives.
### Microsoft.AspNetCore.Authentication.JwtBearer — 10.0.9
- **Usage**: Token validation in `AddCoreInfrastructure` with `ClockSkew.Zero`.
- **Purpose**: Stateless authentication. Requires `JwtSettings:Secret` to be present or startup throws.
### ASP.NET Core Data Protection (shared framework)
- **Usage**: `ApiKeyProtector`, `MasterApiKeyProtector`.
- **Purpose**: Encrypt slave API keys at rest. **Default file-system key ring with no persistent store configured** — a redeploy that loses the key folder makes stored keys unreadable.
### Microsoft.Extensions.Http.Resilience — 9.6.0
- **Usage**: `SlaveApiClient`.
- **Purpose**: Retry and timeout for master→slave calls. Note: 9.x package on a `net10.0` target.
### Asp.Versioning.Mvc — 10.0.0
- **Usage**: `AddApiVersioning` with `ReportApiVersions`.
- **Purpose**: Version reporting alongside the static `/api/v1` prefix convention.
### Scalar.AspNetCore — 2.16.3
- **Usage**: `MapScalarApiReference()`, Development only.
- **Purpose**: API reference UI at `/scalar`. Not exposed in production.
### Vite 8 + React 19 + TanStack Router/Query (frontend)
- **Usage**: Admin SPA build and runtime.
- **Purpose**: `base: '/admin/'` and `basepath: import.meta.env.BASE_URL` are what make the `/admin` mount work.
### pnpm (build-time, backend publish)
- **Usage**: Invoked from `SlpModularCms.Api.csproj` during publish.
- **Purpose**: Builds the admin SPA. Makes Node + pnpm a hard prerequisite of `dotnet publish`.