4.7 KiB
Business Rules — Unit 2: slave-availability-extension
Rule Set 1: API Key Validation
graph TD
A{Registration\nexists in DB?} -->|No| B{Is this a\nregister call?}
B -->|Yes| C[Accept and create\nnew registration]
B -->|No| D[Return 401\nUnauthorized]
A -->|Yes| E{X-Master-Api-Key\nmatches stored key?}
E -->|Yes| F[Proceed with\nbusiness logic]
E -->|No| G[Return 401\nUnauthorized]
classDef decision fill:#FFC107,stroke:#F57F17,color:#000
classDef pass fill:#9ae6b4,stroke:#2f855a,color:#000
classDef fail fill:#FC8181,stroke:#C53030,color:#000
class A,B,E decision
class C,F pass
class D,G fail
Text alternative: If no registration exists and this is a register call, create it. If no registration and not a register call, 401. If registration exists, validate key; match = proceed, mismatch = 401.
Validation rules:
| # | Rule | Applies to |
|---|---|---|
| BR-SLAVE-01 | First POST /register with no existing registration: accept unconditionally, store ApiKey from X-Master-Api-Key header |
Register endpoint |
| BR-SLAVE-02 | Subsequent POST /register: validate header against stored ApiKey. Match → update MasterUrl + LastContactedAt. Mismatch → 401. |
Register endpoint |
| BR-SLAVE-03 | POST /status without existing registration → 401 |
Status push endpoint |
| BR-SLAVE-04 | POST /status with key mismatch → 401 |
Status push endpoint |
| BR-SLAVE-05 | GET /registered-url without existing registration → 401 |
Get-URL endpoint |
| BR-SLAVE-06 | GET /registered-url with key mismatch → 401 |
Get-URL endpoint |
| BR-SLAVE-07 | Missing or empty X-Master-Api-Key header → 401 on all endpoints |
All master endpoints |
Rule Set 2: Master Gate Bypass
graph TD
A{Path starts with\nbypass prefix?} -->|Yes| B[Pass through\nunconditionally]
A -->|No| C{Valid admin\nJWT bearer?}
C -->|Yes| B
C -->|No| D{Master gate\nenabled?}
D -->|_masterIsAvailable = true\nor default| E[Proceed to\nlocal gate]
D -->|_masterIsAvailable = false| F[Return 503\nwith master message]
E --> G{Local availability\ncheck}
G -->|Available| H[Pass to next\nmiddleware]
G -->|Unavailable| I[Return 503\nwith local message]
classDef decision fill:#FFC107,stroke:#F57F17,color:#000
classDef pass fill:#9ae6b4,stroke:#2f855a,color:#000
classDef fail fill:#FC8181,stroke:#C53030,color:#000
class A,C,D,G decision
class B,E,H pass
class F,I fail
Text alternative: Bypass path check first. Admin JWT next (bypasses both gates). Then master gate (static field). If master blocks: 503. If master passes: local gate. If local blocks: 503. Otherwise pass through.
Bypass prefix list (extended from existing):
| Path prefix | Reason |
|---|---|
/api/v1/Availability/status |
Already bypassed — public status endpoint |
/api/v1/Auth/ |
Already bypassed — login must always work |
/api/v1/Setup/status |
Already bypassed — frontend init check |
/api/v1/master/ |
NEW — master management endpoints must bypass gate so master can always push status or re-register |
Cache behavior rules:
| # | Rule |
|---|---|
| BR-SLAVE-08 | _masterIsAvailable defaults to true (fail-open) on process startup |
| BR-SLAVE-09 | _masterDisableMessage defaults to null on process startup |
| BR-SLAVE-10 | Cache has no expiry (Q4=A); only updated on POST /status with valid API key |
| BR-SLAVE-11 | 503 response from master gate includes _masterDisableMessage in ProblemDetails.Detail |
Rule Set 3: MasterRegistration Singleton
| # | Rule |
|---|---|
| BR-SLAVE-12 | MasterRegistration is a singleton: Id is always Guid.Parse("00000000-0000-0000-0000-000000000001") |
| BR-SLAVE-13 | On RegisterAsync: if row with that Id exists → update. If not → insert. Never delete. |
| BR-SLAVE-14 | RegisteredAt is set once at creation and never updated |
| BR-SLAVE-15 | LastContactedAt is updated on every successful master call (register, status push, get-url) |
Rule Set 4: Controller Routing
| Endpoint | Method | Route | Auth |
|---|---|---|---|
| Register master | POST |
/api/v1/master/register |
None (API key in header) |
| Receive status push | POST |
/api/v1/master/status |
None (API key in header) |
| Get registered URL | GET |
/api/v1/master/registered-url |
None (API key in header) |
All three endpoints are unauthenticated from ASP.NET Core's perspective — they use the custom X-Master-Api-Key header validation implemented in MasterAvailabilityService. They are also in the middleware bypass list so the gate cannot block master management calls.