# 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/status` locally (existing `AvailabilityController` endpoint, 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%): - `MasterController` - `MasterAvailabilityService` (all 3 public methods) - `MasterApiKeyProtector` - `AvailabilityMiddleware` (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` |