Files
SluijsensandClaude Sonnet 5 0447993181 Completes local-dev-master-slave-setup: dual-instance frontend tooling, module-capability gating, and master/slave protocol self-healing fixes
Frontend (Unit 2 completion): dual dev-server tooling (pnpm dev:slave,
pnpm dev:all), per-instance browser tab titles, and a backend
capability check (SystemController + useSystemCapabilities +
ModuleGuard) so a Master-only page is hidden on a slave instance
instead of assuming every backend has every module.

Master/slave protocol fixes surfaced by actually running master and
slave side by side locally:
- Deactivating a CMS instance (Inactive) now releases the slave's
  master gate instead of leaving it stuck on its last pushed status.
- The periodic integrity check now also re-pushes status to every
  reachable slave (previously URL-verification only) and runs once
  immediately on startup.
- Added the originally-specified (but never implemented) slave-pull
  path: a slave now periodically polls its own status from the master
  (GET /api/v1/SlaveStatus) and fails open to Available if the master
  is unreachable for too long, complementing the existing push.
- The slave's own Settings page can no longer "successfully" change
  local availability while the master controls it; it's now locked
  with an explanatory banner and the backend rejects the write with
  409 instead of silently no-op'ing it.
- CMS instance status badges now match the dashboard's color/icon
  styling instead of a plain grey badge.

Also corrected the master-cms-module design docs to match this
as-built behavior, and flagged (without a full rewrite) a larger,
pre-existing divergence between its inception-stage application
design and what construction actually built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 19:53:52 +02:00

8.5 KiB

Application Design — Master CMS Module

Design Decisions Summary

Question Decision
Q1 — HTTP client (Master → Slave) A) Typed clientISlaveApiClient / SlaveApiClient via AddHttpClient<>
Q2 — Two-phase gate middleware B) Extend AvailabilityMiddleware — Master gate added at top of InvokeAsync
Q3 — Slave-side status caching A) Static field + timestamp — consistent with existing circuit breaker pattern
Q4 — Service responsibility split B) CmsInstanceRepository + CmsInstanceService — data and orchestration separated
Q5 — Master controller granularity A) Single CmsInstanceController — all actions in one controller
Q6 — Slave internal endpoint placement B) Extended AvailabilityController — registration endpoint added to existing controller
Q7 — Frontend hooks organization B) Separate hook files — one file per hook: useCmsInstances.ts, useAddCmsInstance.ts, useUpdateCmsInstanceStatus.ts

Architecture Overview

graph TD
    subgraph MasterCms["Master CMS Instance"]
        MasterModule["MasterModule\n(IModule)"]
        CmsCtrl["CmsInstanceController\n/api/v1/CmsInstances"]
        CmsService["CmsInstanceService\n(orchestration)"]
        CmsRepo["CmsInstanceRepository\n(data access)"]
        MasterDb["MasterDbContext\nCmsInstances table"]
        SlaveClient["SlaveApiClient\n(typed HTTP client)"]
        BgService["IntegrityCheckBackgroundService\n(PeriodicTimer)"]
        MasterOpts["MasterModuleOptions"]
    end

    subgraph SlaveCms["Slave CMS Instance"]
        ExtAvailMw["AvailabilityMiddleware\n(EXTENDED — two-phase gate)"]
        MasterAvailSvc["MasterAvailabilityService\n(pull + cache + fallback)"]
        LocalAvailSvc["IAvailabilityService\n(existing local gate)"]
        AvailDb["AvailabilityDbContext\nMasterRegistrations table"]
        ExtAvailCtrl["AvailabilityController\n(EXTENDED + RegisterMaster)"]
        SlaveOpts["MasterModuleOptions\n(ApiKey, CacheMinutes)"]
    end

    subgraph FrontendApp["Frontend (Master UI)"]
        CmsPage["CmsPage\n(/cms route)"]
        Hooks["TanStack Query Hooks\n(useCmsInstances, useAddCmsInstance,\nuseUpdateCmsInstanceStatus)"]
        Components["Components\n(CmsInstanceList,\nAddCmsInstanceDialog,\nSetStatusDialog)"]
    end

    FrontendApp -->|"REST /api/v1/CmsInstances"| MasterCms
    MasterCms -->|"HTTP slave API"| SlaveCms
    SlaveCms -->|"HTTP pull status"| MasterCms

    CmsPage --> Components
    Components --> Hooks
    Hooks -->|"GET/POST/PUT"| CmsCtrl
    CmsCtrl --> CmsService
    CmsService --> CmsRepo
    CmsService --> SlaveClient
    CmsRepo --> MasterDb
    BgService --> CmsService
    CmsService --> MasterOpts
    BgService --> MasterOpts
    ExtAvailMw --> MasterAvailSvc
    ExtAvailMw --> LocalAvailSvc
    MasterAvailSvc --> AvailDb
    MasterAvailSvc --> SlaveOpts
    ExtAvailCtrl --> AvailDb
    ExtAvailCtrl --> SlaveOpts

    classDef module fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000
    classDef controller fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000
    classDef service fill:#FFC107,stroke:#F57F17,stroke-width:1px,color:#000
    classDef data fill:#CE93D8,stroke:#6A1B9A,stroke-width:1px,color:#000
    classDef frontend fill:#FC8181,stroke:#C53030,stroke-width:1px,color:#000
    classDef config fill:#B0BEC5,stroke:#546E7A,stroke-width:1px,color:#000
    class MasterModule module
    class CmsCtrl,ExtAvailCtrl controller
    class CmsService,MasterAvailSvc,SlaveClient,LocalAvailSvc,BgService service
    class CmsRepo,MasterDb,AvailDb data
    class CmsPage,Hooks,Components frontend
    class MasterOpts,SlaveOpts config

Text alternative: Master CMS has new module with controller, service, repository, typed HTTP client, and background service; Slave CMS has extended middleware with two-phase gate, new MasterAvailabilityService, new AvailabilityDbContext, and extended controller; Frontend has CmsPage with hooks and components calling Master REST API.


Component Inventory

Unit 1 — master-backend (SlpModularCms.Modules.Master)

Component Type New/Modified
MasterModule IModule New
MasterDbContext EF Core DbContext New
CmsInstance Entity New
CmsInstanceStatus Enum New
ICmsInstanceRepository / CmsInstanceRepository Repository New
ICmsInstanceService / CmsInstanceService Service New
ISlaveApiClient / SlaveApiClient Typed HTTP client New
CmsInstanceController Controller New
IntegrityCheckBackgroundService BackgroundService New
MasterModuleOptions Config POCO New
CmsInstanceDto DTO New
CreateCmsInstanceRequest Request model New
UpdateStatusRequest Request model New

Unit 2 — slave-availability-extension (SlpModularCms.Modules.Availability)

Component Type New/Modified
MasterRegistration Entity New
AvailabilityDbContext EF Core DbContext New
IMasterAvailabilityService / MasterAvailabilityService Service New
AvailabilityMiddleware Middleware Modified
AvailabilityController Controller Modified
MasterGateResult Result record New
RegisterMasterRequest Request model New

Unit 3 — frontend-cms-page (frontend/)

Component Type New/Modified
CmsPage React page New
CmsInstanceList React component New
AddCmsInstanceDialog React component New
SetStatusDialog React component New
useCmsInstances TanStack Query hook New
useAddCmsInstance TanStack Query hook New
useUpdateCmsInstanceStatus TanStack Query hook New
CmsInstance TypeScript type New
CmsInstanceStatus TypeScript enum New

Unit 4 — documentation

Artifact Type New/Modified
README.md — Migrations section Documentation Modified
README.md — Module guide section Documentation Modified
README.md — Production env vars Documentation Modified
frontend/README.md Documentation Modified

Key Design Constraints

Constraint Source Impact
ApiKey never returned in API responses NFR-MASTER-03 CmsInstanceDto excludes ApiKey; only accepted in CreateCmsInstanceRequest
Fail-open on Master unreachable NFR-MASTER-01 (Updated 2026-07-04) Originally only an in-memory startup default (_masterIsAvailable = true); now actively enforced by MasterStatusPollingBackgroundService.RecordPollFailureAsync, which forces the gate open if the master has been unreachable via poll for longer than MasterPolling:FailOpenAfterMinutes — this is the slave-pull half of FR-MASTER-06/07 that was originally specified but not implemented until 2026-07-04 (see slave-availability-extension/functional-design/business-rules.md Rule Set 5)
Per-module DbContext + migrations NFR-MASTER-06 New MasterDbContext in Modules.Master; new AvailabilityDbContext in Modules.Availability
Master exemption from own gate FR-MASTER-09 Handled naturally: no MasterRegistration record exists on Master instance → gate skipped
Disable message required for NotAvailable FR-MASTER-14 Validated in CmsInstanceService.UpdateStatusAsync before persistence
Inactive slaves: no further HTTP contact FR-MASTER-13 (Updated 2026-07-04) GetActiveAsync() filters out Inactive instances from ongoing integrity checks and status re-pushes, as before — but the transition into Inactive itself now always performs one final push (Available, no message) to release the master gate before the instance drops out; previously this transition performed no push at all, leaving the slave stuck on its last status
Owner role only FR-MASTER-10 [Authorize(Policy = "OwnerOnly")] on all CmsInstanceController actions

Artifact References

Artifact Path
Component definitions aidlc-docs/features/master-cms-module/inception/application-design/components.md
Method signatures aidlc-docs/features/master-cms-module/inception/application-design/component-methods.md
Service orchestration + flows aidlc-docs/features/master-cms-module/inception/application-design/services.md
Dependency diagrams + matrix aidlc-docs/features/master-cms-module/inception/application-design/component-dependency.md