# Business Rules — Unit 1: master-backend ## BR-01 — Status Update Decision Logic ```mermaid graph TD Start(["UpdateStatusAsync called"]) CheckExists{"Entity exists\nfor given id?"} NotFound["Throw NotFoundException\n404 to caller"] CheckMsg{"newStatus = NotAvailable\nAND disableMessage\nis null or empty?"} ValidationErr["Throw ValidationException\nDisableMessage required"] CheckInactive{"newStatus\n= Inactive?"} SetInactive["Status = Inactive\nDisableMessage = null\nNo HTTP push\nSlaveContactSuccess = true"] PersistStatus["Persist Status + DisableMessage\nto MasterDbContext"] DecryptKey["Decrypt ApiKey\nvia IDataProtector"] PushSlave["PushStatusAsync\nto slave endpoint"] PushOk{"HTTP push\nsucceeded?"} UpdatePushed["LastStatusPushedAt = UtcNow\nSave"] ReturnOk["Return UpdateStatusResult\nSuccess=true\nSlaveContactSuccess=true"] ReturnWarn["Return UpdateStatusResult\nSuccess=true\nSlaveContactSuccess=false"] Done(["Return result to controller"]) Start --> CheckExists CheckExists -->|"no"| NotFound CheckExists -->|"yes"| CheckMsg CheckMsg -->|"yes — invalid"| ValidationErr CheckMsg -->|"no — valid"| CheckInactive CheckInactive -->|"yes"| SetInactive --> Done CheckInactive -->|"no"| PersistStatus --> DecryptKey --> PushSlave --> PushOk PushOk -->|"yes"| UpdatePushed --> ReturnOk --> Done PushOk -->|"no"| ReturnWarn --> Done classDef decision fill:#FFC107,stroke:#F57F17,stroke-width:2px,color:#000 classDef action fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000 classDef terminal fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000 classDef error fill:#FC8181,stroke:#C53030,stroke-width:2px,color:#000 class CheckExists,CheckMsg,CheckInactive,PushOk decision class PersistStatus,DecryptKey,PushSlave,UpdatePushed,SetInactive action class Start,Done terminal class NotFound,ValidationErr error ``` Text alternative: Load entity (404 if missing) → validate DisableMessage required for NotAvailable → for Inactive skip push → for others persist, decrypt key, push to slave, set SlaveContactSuccess based on push result. --- ## BR-02 — Integrity Check Decision Logic ```mermaid graph TD Start(["VerifyIntegrityAsync\nper instance"]) GetUrl["GetRegisteredMasterUrlAsync\n(slaveUrl, plainApiKey)"] Reachable{"Slave\nreachable?"} SetFailed["LastIntegrityCheckFailedAt = UtcNow\nSave — continue to next"] UrlMatch{"registeredMasterUrl\n= expected masterUrl?"} ClearOk["LastIntegrityCheckFailedAt = null\nLastContactedAt = UtcNow\nSave"] ReRegister["RegisterMasterAsync\n(slaveUrl, plainApiKey, masterUrl)"] RegOk{"Re-registration\nsucceeded?"} ClearAfterReg["LastIntegrityCheckFailedAt = null\nLastContactedAt = UtcNow\nSave"] SetFailedReg["LastIntegrityCheckFailedAt = UtcNow\nSave"] Next(["Next instance"]) Start --> GetUrl --> Reachable Reachable -->|"no"| SetFailed --> Next Reachable -->|"yes"| UrlMatch UrlMatch -->|"match"| ClearOk --> Next UrlMatch -->|"mismatch"| ReRegister --> RegOk RegOk -->|"yes"| ClearAfterReg --> Next RegOk -->|"no"| SetFailedReg --> Next classDef decision fill:#FFC107,stroke:#F57F17,stroke-width:2px,color:#000 classDef action fill:#63b3ed,stroke:#2b6cb0,stroke-width:1px,color:#000 classDef terminal fill:#9ae6b4,stroke:#2f855a,stroke-width:2px,color:#000 classDef error fill:#FC8181,stroke:#C53030,stroke-width:2px,color:#000 class Reachable,UrlMatch,RegOk decision class GetUrl,SetFailed,ClearOk,ReRegister,ClearAfterReg,SetFailedReg action class Start,Next terminal ``` Text alternative: For each active slave — attempt to get its registered master URL; if unreachable set failure flag; if reachable and URL matches clear flag; if mismatch re-register; clear flag on success, set flag on failure. --- ## Validation Rules | Rule | Field | Condition | Error | |------|-------|-----------|-------| | BR-VAL-01 | `Name` | Must not be null or whitespace | `Name is required` | | BR-VAL-02 | `Url` | Must not be null or whitespace | `Url is required` | | BR-VAL-03 | `Url` | Must start with `http://` or `https://` | `Url must be a valid absolute HTTP(S) URL` | | BR-VAL-04 | `ApiKey` | Must not be null or whitespace (on create) | `ApiKey is required` | | BR-VAL-05 | `DisableMessage` | Required (non-empty) when `Status = NotAvailable` | `DisableMessage is required when status is NotAvailable` | | BR-VAL-06 | `Status` | Must be a valid `CmsInstanceStatus` enum value | `Invalid status value` | | BR-VAL-07 | `id` (update) | CmsInstance with given id must exist | `CmsInstance not found` (404) | --- ## Status Transition Rules | From | To | DisableMessage | HTTP Push | Notes | |------|----|---------------|-----------|-------| | Any | `Available` | Clear to null | Yes | Slave re-enabled | | Any | `NotAvailable` | Required, non-empty | Yes | Slave disabled with message | | Any | `Inactive` | Clear to null | **No** | Master stops all contact | | `Inactive` | `Available` | Clear to null | Yes | Reactivation | | `Inactive` | `NotAvailable` | Required, non-empty | Yes | Reactivation with disable | --- ## ApiKey Encryption Rules | Rule | Description | |------|-------------| | BR-ENC-01 | `ApiKey` is encrypted via `IDataProtector` before writing to `MasterDbContext` | | BR-ENC-02 | `ApiKey` is decrypted via `IDataProtector` immediately before each HTTP call requiring it | | BR-ENC-03 | `ApiKey` is **never** included in `CmsInstanceDto` or any other API response | | BR-ENC-04 | `ApiKey` is accepted in `CreateCmsInstanceRequest` on creation only; no update endpoint for ApiKey | --- ## Master URL Resolution Rules | Context | Resolution Strategy | |---------|-------------------| | Controller-originated calls (Add) | Derive from `HttpContext.Request` scheme + host + (optional port) via `IHttpContextAccessor` | | Background service calls (Integrity Check) | Read `MasterModuleOptions.MasterUrl` from configuration | | `MasterModuleOptions.MasterUrl` is null in background context | Log a warning; skip registration/integrity for that cycle | --- ## HTTP Contact Exclusion Rules | Rule | Description | |------|-------------| | BR-CONTACT-01 | Instances with `Status = Inactive` are excluded from `GetActiveAsync` and never contacted via HTTP | | BR-CONTACT-02 | Status push is skipped when transitioning any status → `Inactive` | | BR-CONTACT-03 | Integrity check runs only against instances where `Status != Inactive` | --- ## Background Service Rules | Rule | Description | |------|-------------| | BR-BG-01 | `IntegrityCheckBackgroundService` resolves `ICmsInstanceService` via `IServiceScopeFactory` per tick (not injected directly, as service is Scoped) | | BR-BG-02 | Each tick creates and disposes its own `IServiceScope` | | BR-BG-03 | Exceptions within a single slave's integrity check are caught, logged, and do not abort processing for remaining slaves | | BR-BG-04 | If `MasterModuleOptions.MasterUrl` is null or empty, the background service logs a warning and skips the entire integrity check for that cycle |