Files
slp-modular-cms/aidlc-docs/features/master-cms-module/construction/master-backend/functional-design/domain-entities.md
T
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

6.9 KiB

Domain Entities — Unit 1: master-backend

Entity Overview

graph TD
    MasterDbCtx["MasterDbContext\n(per-module EF Core DbContext)"]
    CmsInst["CmsInstance\n(aggregate root)"]
    Status["CmsInstanceStatus\n(enum)"]
    Opts["MasterModuleOptions\n(config POCO)"]
    DP["IDataProtector\n(ApiKey encryption)"]
    DTO["CmsInstanceDto\n(API response shape)"]
    CreateReq["CreateCmsInstanceRequest\n(API input)"]
    UpdateReq["UpdateStatusRequest\n(API input)"]
    UpdateRes["UpdateStatusResult\n(API response for status update)"]

    MasterDbCtx -->|"owns"| CmsInst
    CmsInst -->|"has"| Status
    CmsInst -->|"ApiKey encrypted via"| DP
    CmsInst -->|"projected to"| DTO
    CreateReq -->|"creates"| CmsInst
    UpdateReq -->|"mutates status of"| CmsInst
    UpdateRes -->|"returned from UpdateStatusAsync"| CmsInst
    Opts -->|"IntegrityCheckIntervalMinutes"| MasterDbCtx
    Opts -->|"MasterUrl fallback"| MasterDbCtx

    classDef entity fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000
    classDef infra fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000
    classDef dto fill:#FFC107,stroke:#F57F17,stroke-width:1px,color:#000
    classDef config fill:#CE93D8,stroke:#6A1B9A,stroke-width:1px,color:#000
    class CmsInst entity
    class MasterDbCtx,DP infra
    class Status,DTO,CreateReq,UpdateReq,UpdateRes dto
    class Opts config

Text alternative: MasterDbContext owns CmsInstance; CmsInstance has a Status enum and its ApiKey is encrypted via IDataProtector; request/response shapes map to and from CmsInstance.


CmsInstance

Table: CmsInstances (owned by MasterDbContext, migrations in SlpModularCms.Modules.Master)

Field Type Nullable Notes
Id Guid No Primary key
Name string No Friendly display name; required
Url string No Base URL of slave CMS API; must start with http:// or https://
ApiKey string No Encrypted via ASP.NET Core Data Protection before storage; decrypted before HTTP calls
Status CmsInstanceStatus No Default: Available on creation
DisableMessage string? Yes Required when Status = NotAvailable; null otherwise
LastContactedAt DateTimeOffset? Yes Null = never successfully contacted; set on successful registration or integrity check
LastStatusPushedAt DateTimeOffset? Yes Null = status never successfully pushed; set after successful PushStatusAsync
LastIntegrityCheckFailedAt DateTimeOffset? Yes Null = no pending failure; set when integrity check cannot reach slave or re-registration fails; cleared on next successful contact

CmsInstanceStatus

public enum CmsInstanceStatus
{
    Available = 0,
    NotAvailable = 1,
    Inactive = 2,
}
Value Meaning Master Contacts Slave?
Available Slave is enabled; normal operation Yes (status push + integrity checks)
NotAvailable Slave is disabled; DisableMessage served to end-users Yes (status push + integrity checks)
Inactive Soft-removed; greyed out in UI On the transition into Inactive: one final push (Available, no message) to release the gate. Afterwards: No further contact — excluded from GetActiveAsync, so no more pushes/integrity checks/re-pushes

Updated 2026-07-04: previously Inactive meant no HTTP contact at all, including on the transition itself — this left slaves stuck on their last pushed status after being detached. See BR-CONTACT-02 in business-rules.md.


MasterModuleOptions

Config section: "MasterModule" in appsettings.json

Property Type Default Side Notes
IntegrityCheckIntervalMinutes int 60 Master Interval for IntegrityCheckBackgroundService
MasterUrl string? null Master Fallback public URL of this master CMS; used by background service when HttpContext is unavailable
CacheMinutes int 60 Slave Slave pull cache interval (used by Unit 2)
ApiKey string? null Slave Slave API key for validating incoming master requests (used by Unit 2)

CmsInstanceDto (API Response)

Rule: ApiKey is never included (NFR-MASTER-03).

Property Type Notes
Id Guid
Name string
Url string
Status string Serialized as string ("Available" / "NotAvailable" / "Inactive")
DisableMessage string?
LastContactedAt DateTimeOffset?
LastStatusPushedAt DateTimeOffset?
LastIntegrityCheckFailedAt DateTimeOffset? Visible in UI so owner knows which slaves have pending check failures

CreateCmsInstanceRequest (API Input)

Property Type Validation
Name string Required, non-empty
Url string Required; must start with http:// or https://
ApiKey string Required, non-empty

UpdateStatusRequest (API Input)

Property Type Validation
Status CmsInstanceStatus Required; must be valid enum value
DisableMessage string? Required and non-empty when Status = NotAvailable; ignored otherwise

UpdateStatusResult (Service Return / API Response)

Property Type Notes
Success bool Always true when status persisted to DB (DB is the authority)
SlaveContactSuccess bool true if the HTTP push to the slave succeeded; false if it failed (slave unreachable). Applies to Inactive transitions too — reflects whether the gate-release push succeeded, not a hardcoded true

Updated 2026-07-04: SlaveContactSuccess for Inactive used to always be hardcoded true (no push happened, so nothing could fail) — now reflects the real result of the release push.


SlaveStatusPollResponse (API Response) — Added 2026-07-04

Response shape for the new slave-pull endpoint (GET /api/v1/SlaveStatus, SlaveStatusController), used by MasterStatusPollingBackgroundService on the slave side (see slave-availability-extension/functional-design/domain-entities.md). This is the counterpart of the push-based flow above — added to close a gap versus the original inception requirements (FR-MASTER-06/07), which specified a slave-initiated pull in addition to the push that construction actually implemented.

Property Type Notes
IsAvailable bool true when CmsInstance.Status == Available
DisableMessage string? CmsInstance.DisableMessage

Identified by matching the caller's plain API key (header X-Master-Api-Key) against each active CmsInstance's decrypted key — there is no separate slave-identity field, the shared key doubles as the credential. No [Authorize]/JWT on this endpoint.