4.7 KiB
NFR Requirements — Unit 2: slave-availability-extension
Security
SEC-01: ApiKey encrypted at rest (Q1=C)
The MasterRegistration.ApiKey field is not stored in plain text. The slave encrypts the key using ASP.NET Core Data Protection before writing to the DB, and decrypts it before comparison on each incoming request.
| Aspect | Decision |
|---|---|
| Mechanism | ASP.NET Core Data Protection (IDataProtectionProvider) |
| Wrapper type | IMasterApiKeyProtector / MasterApiKeyProtector |
| Purpose string | "SlpModularCms.Availability.MasterApiKey" |
| Comparison | Decrypt stored key → compare with incoming header value (plain-text equality) |
| On decryption failure | Return null (key is unreadable) → treat as mismatch → 401 |
Rationale: Consistent with Unit 1's IApiKeyProtector pattern. Protects the key if the database is accessed directly (e.g., backup file, DB dump). The purpose string isolates it from Unit 1's protector scope.
SEC-02: Missing or empty header → 401
An absent or empty X-Master-Api-Key header on any master endpoint (register, status, registered-url) immediately returns 401 without touching the database. No information leaked about whether a registration exists.
SEC-03: Unauthenticated controller, API key validated in service
The three master endpoints carry no [Authorize] attribute — authentication is done via the custom header in MasterAvailabilityService. The endpoints are in the /api/v1/master/ bypass prefix so the availability gate cannot block them.
Performance
PERF-01: Master gate check is synchronous
The AvailabilityMiddleware reads _masterIsAvailable from a static volatile field — zero async overhead, zero DB call per request. Only status pushes touch the DB.
PERF-02: No DB call on gate evaluation
The master gate evaluates solely from volatile static fields. The local gate still calls IAvailabilityService.IsAvailableAsync() (one DB read with 1s cache per AvailabilityOptions.StatusCacheSeconds — unchanged from existing behaviour).
Reliability
REL-01: Fail-open on process startup (Q2=A, Q4=A from FD)
_masterIsAvailable defaults to true at process startup. A slave that restarts before the master pushes status again is immediately accessible. The master's IntegrityCheckBackgroundService will push status again within IntegrityCheckIntervalMinutes.
REL-02: No expiry on cached status (Q4=A from FD)
The static cache has no TTL. If the master goes offline permanently, the last pushed status is used indefinitely. For a slave last told "unavailable", it remains unavailable until either:
- The master recovers and pushes "available" again, or
- An operator manually calls
POST /api/v1/Availability/statuslocally (existingAvailabilityControllerendpoint, Owner-only).
REL-03: Static field thread safety via volatile (Q2=A)
private static volatile bool _masterIsAvailable and private static volatile string? _masterDisableMessage. Writes to reference types and bools are atomic in .NET; volatile ensures cross-thread visibility without a lock on every request. Consistent with PersistentAvailabilityService's _lastErrorTime pattern.
Maintainability
MAINT-01: Test coverage ≥ 80% (Q3=B)
Excluded from coverage:
AvailabilityModule(service registration changes +Database.MigrateAsync()call)- EF Core migration files (auto-generated)
- DTO / request / response record classes (
[ExcludeFromCodeCoverage])
Included (must reach ≥ 80%):
MasterControllerMasterAvailabilityService(all 3 public methods)MasterApiKeyProtectorAvailabilityMiddleware(extended paths — master gate logic)IMasterRegistrationRepository/MasterRegistrationRepository
MAINT-02: Structured logging (Q4=B)
| Scenario | Level | Structured Fields |
|---|---|---|
| First master registration | Information |
masterUrl |
| Re-registration (existing key match) | Information |
masterUrl |
| API key mismatch on any endpoint | Warning |
endpoint (do NOT log the key itself) |
| Missing X-Master-Api-Key header | Warning |
endpoint |
| Status update received | Information |
isAvailable, disableMessage |
| Master gate blocked a request | Warning |
path, disableMessage |
| Get-registered-url called | Debug |
— |
| Decryption failure on stored key | Error |
(no key value) |
Test Framework (unchanged from Unit 1)
| Aspect | Decision |
|---|---|
| Framework | xUnit |
| Mocking | NSubstitute 5.x |
| Assertions | FluentAssertions 8.x |
| EF Core testing | EF Core InMemory provider |
| Data Protection testing | EphemeralDataProtectionProvider |
| Project name | SlpModularCms.Modules.Availability.Master.Tests |