Adds 2 units and docs for unit 3. nfr-requirements plan

This commit is contained in:
2026-06-29 22:18:37 +02:00
parent 0e01ca1e1c
commit c156107cb1
126 changed files with 15204 additions and 80199 deletions
@@ -0,0 +1,60 @@
# Functional Design Questions — Unit 3: frontend-cms-page
Please answer each question by filling in the letter after the `[Answer]:` tag.
If none of the options match, choose the last option (Other) and describe your preference.
---
## Question 1
Which columns should the `CmsInstanceList` table display?
A) Name, URL, Status
B) Name, URL, Status, Last Contact (`lastContactedAt`)
C) Name, URL, Status, DisableMessage
D) Other (please describe after [Answer]: tag below)
[Answer]: B + C
---
## Question 2
"Inactive rows greyed out" — what exactly?
A) The entire `<TableRow>` gets `opacity-50` (everything fades)
B) All `<TableCell>` text gets `text-muted-foreground` class (subtler dimming)
C) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Question 3
When the status is updated, the backend pushes to the slave and reports whether it was reachable (`UpdateStatusResult`). What does the frontend show?
A) Always a generic success toast on HTTP 200, regardless of slave details
B) Toast differs: "Status updated — slave confirmed" vs "Status saved, slave unreachable"
C) Other (please describe after [Answer]: tag below)
[Answer]: B, but don't call it slave. I'd rather see the term "cliënt"
---
## Question 4
The backend returns HTTP 400 for invalid data in `AddCmsInstanceDialog`. What does the UI show?
A) Banner error at the top of the dialog (`FormBannerError` pattern, as in LoginPage)
B) Toast on failed submit
C) Other (please describe after [Answer]: tag below)
[Answer]: A
---
## Question 5
When there are no CMS instances yet (empty state):
A) Centered placeholder with icon + "Add your first CMS" message and an Add button
B) Empty table (the Add button in the page header is sufficient)
C) Other (please describe after [Answer]: tag below)
[Answer]: A
@@ -0,0 +1,52 @@
# Functional Design Plan — Unit 3: frontend-cms-page
## Scope
**Modify**: `frontend/src/` (existing React SPA, extended)
**Questions file**: `frontend-cms-page-fd-questions.md`
---
## Steps
### Part A — TypeScript Types
- [ ] **Step 1**`src/api/types.ts` — add `CmsInstance` interface and `CmsInstanceStatus` type
### Part B — TanStack Query Hooks
- [ ] **Step 2**`src/api/useCmsInstances.ts``GET /api/v1/CmsInstances`
- [ ] **Step 3**`src/api/useAddCmsInstance.ts``POST /api/v1/CmsInstances`
- [ ] **Step 4**`src/api/useUpdateCmsInstanceStatus.ts``PUT /api/v1/CmsInstances/{id}/status`
### Part C — Components
- [ ] **Step 5**`src/components/cms/CmsInstanceList.tsx` — table with status badges; Inactive rows styled per Q2
- [ ] **Step 6**`src/components/cms/AddCmsInstanceDialog.tsx` — modal form: Name, URL, ApiKey; error handling per Q4
- [ ] **Step 7**`src/components/cms/SetStatusDialog.tsx` — status dropdown + conditional DisableMessage (mandatory when NotAvailable)
### Part D — Page
- [ ] **Step 8**`src/pages/CmsPage.tsx` — replace placeholder; header + Add button; empty state per Q5; columns per Q1; UpdateStatusResult per Q3
### Part E — i18n
- [ ] **Step 9**`src/i18n/locales/nl/translation.json` — add `cms.*` keys
- [ ] **Step 10**`src/i18n/locales/en/translation.json` — add `cms.*` keys
### Part F — MSW Mocks
- [ ] **Step 11**`src/mocks/cms/handlers.ts` — GET, POST, PUT handlers with in-memory state
- [ ] **Step 12**`src/mocks/browser.ts` + `src/mocks/server.ts` — register cms handlers
### Part G — Tests
- [ ] **Step 13**`src/api/useCmsInstances.test.ts` — GET hook
- [ ] **Step 14**`src/api/useAddCmsInstance.test.ts` — POST mutation + cache invalidation
- [ ] **Step 15**`src/api/useUpdateCmsInstanceStatus.test.ts` — PUT mutation + cache invalidation
- [ ] **Step 16**`src/pages/CmsPage.test.tsx` — renders list, Add button, empty state, Owner-only guard
---
*Answers to design questions: see `frontend-cms-page-fd-questions.md`*
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/frontend-cms-page-functional-design-plan.md`
@@ -0,0 +1,51 @@
# NFR Requirements Questions — Unit 3: frontend-cms-page
Please answer each question by filling in the letter after the `[Answer]:` tag.
If none of the options match, choose the last option (Other) and describe your preference.
---
## Question 1
How strict should the URL validation be for the `url` field in `AddCmsInstanceDialog`?
Context: `z.string().url()` (Zod strict) rejects bare IPs without protocol (`192.168.1.5:8080`) and rejects `localhost:5000`. CMS slave instances may live on a local network or development machine.
A) **Strict**`z.string().url()`. Enforces a valid HTTP/HTTPS URL. User must enter `http://192.168.1.5:8080`. Backend already validates anyway; this prevents obvious typos.
B) **Lenient**`z.string().min(1)`. Any non-empty string is accepted. Backend is the authoritative validator; client only ensures the field is not empty.
C) **Custom** — Must start with `http://` or `https://`, but the rest is not validated (`z.string().regex(/^https?:\/\//)`). Prevents protocol-less entries without being as strict as full URL parsing.
D) Other (please describe after [Answer]: tag below)
[Answer]:
---
## Question 2
What test scope applies to the new components and hooks?
A) **Page integration only**`CmsPage.test.tsx` tests the main flows end-to-end (load list, open Add dialog, set status). Individual components are not tested separately. Keeps the test suite lean.
B) **Hooks + page integration** — Dedicated tests for `useCmsInstances`, `useAddCmsInstance`, `useUpdateCmsInstanceStatus` using `renderHook` + MSW. Page integration test covers the UI flows. Consistent with existing `useUsers` pattern.
C) **Hooks + page + component tests** — In addition to B, dedicated tests for `AddCmsInstanceDialog` and `SetStatusDialog` (error state, DisableMessage conditional, etc.). Matches the `InviteUserDialog.test.tsx` precedent in this project.
D) Other (please describe after [Answer]: tag below)
[Answer]:
---
## Question 3
How should the `ApiKey` input field behave in `AddCmsInstanceDialog`?
The user copies the API key from the slave CMS admin panel and pastes it here. It is an infrastructure credential, not a user password.
A) **`type="password"`** — Hidden by default. Safe against shoulder surfing. The existing `PasswordField` component with show/hide toggle can be reused.
B) **`type="text"`** — Visible. Easier to verify the pasted value is correct. Acceptable since this is a one-time setup action performed by an Owner in a secure context.
C) Other (please describe after [Answer]: tag below)
[Answer]:
@@ -0,0 +1,22 @@
# NFR Requirements Plan — Unit 3: frontend-cms-page
## Unit Context
**Unit**: `frontend-cms-page`
**Inputs**: domain-entities.md, business-logic-model.md, business-rules.md, frontend-components.md
**Key NFR concerns**: form validation strategy, test scope, URL validation
---
## Execution Steps
- [ ] **Step 1** — Analyze answers; flag ambiguities
- [ ] **Step 2** — Generate `nfr-requirements.md`
- [ ] **Step 3** — Generate `tech-stack-decisions.md`
- [ ] **Step 4** — Update `aidlc-state.md`
- [ ] **Step 5** — Present completion message for approval
---
*Questions file*: `frontend-cms-page-nfr-questions.md`
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/frontend-cms-page-nfr-requirements-plan.md`
@@ -0,0 +1,207 @@
# Code Generation Plan — Unit 1: master-backend
## Unit Context
**Unit**: `master-backend`
**Workspace root**: `K:\Development\Projects\SlpModularCms`
**Project type**: Brownfield — new project added to existing solution
**New projects**:
- `src/SlpModularCms.Modules.Master/` (application code)
- `src/SlpModularCms.Modules.Master.Tests/` (test code)
**Modified files**:
- `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — add project reference to Master module
- `SlpModularCms.sln` — add both new projects
**Test stack** (matching existing projects): xUnit, NSubstitute, FluentAssertions, EF Core InMemory
**New NuGet dependency**: `Microsoft.Extensions.Http.Resilience` (Polly integration)
**Requirements covered**: FR-MASTER-01/02/03/04/05/10/12/13/14; NFR-MASTER-03/04/05/06
---
## Generation Steps
### STEP 1 — Project files & solution wiring
- [x] 1a. Create `src/SlpModularCms.Modules.Master/SlpModularCms.Modules.Master.csproj`
- Target: `net10.0`; Nullable + ImplicitUsings enabled
- ProjectReference: `SlpModularCms.Core`
- PackageReference: `Microsoft.Extensions.Http.Resilience` (latest stable)
- [x] 1b. Create `src/SlpModularCms.Modules.Master.Tests/SlpModularCms.Modules.Master.Tests.csproj`
- Target: `net10.0`; `IsPackable = false`
- PackageReference: `xunit`, `xunit.runner.visualstudio`, `Microsoft.NET.Test.Sdk`, `coverlet.collector`, `NSubstitute`, `FluentAssertions`, `Microsoft.EntityFrameworkCore.InMemory`, `Microsoft.Extensions.Logging.Abstractions`
- ProjectReference: `SlpModularCms.Modules.Master`
- Global using: `Xunit`
- [x] 1c. Modify `src/SlpModularCms.Api/SlpModularCms.Api.csproj` — add `<ProjectReference Include="..\SlpModularCms.Modules.Master\SlpModularCms.Modules.Master.csproj" />`
- [x] 1d. Modify `SlpModularCms.sln` — add both new projects with correct GUIDs and folder placement
### STEP 2 — Configuration options
- [x] 2a. Create `src/SlpModularCms.Modules.Master/Options/MasterModuleOptions.cs`
- Properties: `IntegrityCheckIntervalMinutes` (int, 60), `HttpTimeoutSeconds` (int, 10), `MasterUrl` (string?), `CacheMinutes` (int, 60), `ApiKey` (string?)
### STEP 3 — Domain entities
- [x] 3a. Create `src/SlpModularCms.Modules.Master/Data/Entities/CmsInstanceStatus.cs`
- Enum: `Available = 0`, `NotAvailable = 1`, `Inactive = 2`
- [x] 3b. Create `src/SlpModularCms.Modules.Master/Data/Entities/CmsInstance.cs`
- All fields per domain-entities.md (including `LastIntegrityCheckFailedAt`)
- `[ExcludeFromCodeCoverage]` NOT applied — entity has no logic; covered by service tests
### STEP 4 — DTOs and request/response models
- [x] 4a. Create `src/SlpModularCms.Modules.Master/Models/CmsInstanceDto.cs``[ExcludeFromCodeCoverage]`
- [x] 4b. Create `src/SlpModularCms.Modules.Master/Models/CreateCmsInstanceRequest.cs``[ExcludeFromCodeCoverage]`
- [x] 4c. Create `src/SlpModularCms.Modules.Master/Models/UpdateStatusRequest.cs``[ExcludeFromCodeCoverage]`
- [x] 4d. Create `src/SlpModularCms.Modules.Master/Models/UpdateStatusResult.cs``[ExcludeFromCodeCoverage]`
### STEP 5 — EF Core DbContext
- [x] 5a. Create `src/SlpModularCms.Modules.Master/Data/MasterDbContext.cs`
- `DbSet<CmsInstance> CmsInstances`
- `OnModelCreating`: configure entity (table name, required fields, max lengths)
- Constructor: accepts `DbContextOptions<MasterDbContext>`
### STEP 6 — Repository
- [x] 6a. Create `src/SlpModularCms.Modules.Master/Repositories/ICmsInstanceRepository.cs`
- `GetAllAsync`, `GetActiveAsync`, `GetByIdAsync`, `AddAsync`, `UpdateAsync`, `SaveChangesAsync`
- [x] 6b. Create `src/SlpModularCms.Modules.Master/Repositories/CmsInstanceRepository.cs`
- Inject `MasterDbContext`; implement all methods
- `GetActiveAsync` filters `Status != CmsInstanceStatus.Inactive`
### STEP 7 — Security: ApiKeyProtector
- [x] 7a. Create `src/SlpModularCms.Modules.Master/Services/IApiKeyProtector.cs`
- `string Protect(string plainApiKey)`
- `string Unprotect(string encryptedApiKey)`
- [x] 7b. Create `src/SlpModularCms.Modules.Master/Services/ApiKeyProtector.cs`
- Inject `IDataProtectionProvider`; purpose string `"SlpModularCms.Master.ApiKey"`
- `[ExcludeFromCodeCoverage]` NOT applied — testable via unit test with real `EphemeralDataProtectionProvider`
### STEP 8 — HTTP client: SlaveApiClient
- [x] 8a. Create `src/SlpModularCms.Modules.Master/Services/ISlaveApiClient.cs`
- `RegisterMasterAsync`, `PushStatusAsync`, `GetRegisteredMasterUrlAsync`
- [x] 8b. Create `src/SlpModularCms.Modules.Master/Services/SlaveApiClient.cs`
- Inject `HttpClient` (typed client)
- Each method: build request with `X-Master-Api-Key` header; handle non-success responses; return `false` / `null` on failure
- JSON serialization: `System.Text.Json` (consistent with project)
### STEP 9 — Service dependencies record
- [x] 9a. Create `src/SlpModularCms.Modules.Master/Services/MasterServiceDependencies.cs`
- Record with 6 properties: `ICmsInstanceRepository`, `ISlaveApiClient`, `IApiKeyProtector`, `IOptions<MasterModuleOptions>`, `IHttpContextAccessor`, `ILogger<CmsInstanceService>`
- `[ExcludeFromCodeCoverage]`
### STEP 10 — CmsInstanceService
- [x] 10a. Create `src/SlpModularCms.Modules.Master/Services/ICmsInstanceService.cs`
- `GetAllAsync`, `AddAsync`, `UpdateStatusAsync`, `VerifyIntegrityAsync`
- [x] 10b. Create `src/SlpModularCms.Modules.Master/Services/CmsInstanceService.cs`
- Inject `MasterServiceDependencies`
- `GetAllAsync`: return all as `CmsInstanceDto` (no ApiKey)
- `AddAsync`: validate → encrypt → persist → determine masterUrl (HttpContext → config fallback) → register → update `LastContactedAt` if success
- `UpdateStatusAsync`: validate → load → validate DisableMessage → persist → for non-Inactive: decrypt → push → update `LastStatusPushedAt` if success → return `UpdateStatusResult`
- `VerifyIntegrityAsync`: get active → per slave: decrypt → get registered URL → compare → re-register if mismatch → update `LastIntegrityCheckFailedAt` / `LastContactedAt`
- Logging per business-rules.md (Error/Warning/Information)
### STEP 11 — Background service
- [x] 11a. Create `src/SlpModularCms.Modules.Master/BackgroundServices/IntegrityCheckBackgroundService.cs`
- Inject `IServiceScopeFactory`, `IOptions<MasterModuleOptions>`, `ILogger<IntegrityCheckBackgroundService>`
- `PeriodicTimer` with `IntegrityCheckIntervalMinutes`
- `CreateAsyncScope()` per tick; resolve `ICmsInstanceService`; call `VerifyIntegrityAsync()`
- Outer `try/catch` logs `Error` and continues
- `[ExcludeFromCodeCoverage]` NOT applied — test with mocked `IServiceScopeFactory`
### STEP 12 — Controller
- [x] 12a. Create `src/SlpModularCms.Modules.Master/Controllers/CmsInstanceController.cs`
- `[ApiController]`, `[Route("[controller]")]`, `[Authorize(Policy = "OwnerOnly")]`
- `GetAll()`: GET → `ICmsInstanceService.GetAllAsync()` → 200 OK
- `Add([FromBody] CreateCmsInstanceRequest)`: POST → `ICmsInstanceService.AddAsync()` → 201 Created
- `UpdateStatus(Guid id, [FromBody] UpdateStatusRequest)`: PUT `/{id}/status``ICmsInstanceService.UpdateStatusAsync()` → 200 OK with `UpdateStatusResult`
- Error handling: catch `KeyNotFoundException` → 404; catch `ArgumentException` (validation) → 400
### STEP 13 — Module registration
- [x] 13a. Create `src/SlpModularCms.Modules.Master/MasterModule.cs``[ExcludeFromCodeCoverage]`
- `Name = "Master"`, `Version = "1.0.0"`
- `RegisterServices`: AddDataProtection, AddSingleton IApiKeyProtector, Configure MasterModuleOptions, AddDbContext MasterDbContext, AddScoped repo + deps + service, AddHttpClient ISlaveApiClient + AddResilienceHandler, AddHostedService IntegrityCheckBackgroundService, AddHttpContextAccessor
- `UseModule`: migrate `MasterDbContext` at startup
### STEP 14 — EF Core migration
- [x] 14a. Create `src/SlpModularCms.Modules.Master/Migrations/` directory placeholder
- Document CLI command to generate initial migration:
```
dotnet ef migrations add InitialCreate --project src/SlpModularCms.Modules.Master --startup-project src/SlpModularCms.Api
```
- Note: Migration is created via CLI after Step 13 compile-succeeds; not hand-generated
### STEP 15 — Unit tests: Repository
- [x] 15a. Create `src/SlpModularCms.Modules.Master.Tests/Repositories/CmsInstanceRepositoryTests.cs`
- Use EF Core InMemory for `MasterDbContext`
- Test: `GetAllAsync`, `GetActiveAsync` (excludes Inactive), `GetByIdAsync` (found/not found), `AddAsync` + `SaveChangesAsync`, `UpdateAsync`
### STEP 16 — Unit tests: ApiKeyProtector
- [x] 16a. Create `src/SlpModularCms.Modules.Master.Tests/Services/ApiKeyProtectorTests.cs`
- Use `EphemeralDataProtectionProvider` (real provider, no mocks)
- Test: Protect returns non-plaintext; Unprotect(Protect(x)) == x; Unprotect with wrong key throws
### STEP 17 — Unit tests: SlaveApiClient
- [x] 17a. Create `src/SlpModularCms.Modules.Master.Tests/Services/SlaveApiClientTests.cs`
- Use `NSubstitute` `HttpMessageHandler` substitute or `MockHttpMessageHandler`
- Test: `RegisterMasterAsync` returns true on 200, false on 4xx/5xx/exception
- Test: `PushStatusAsync` returns true on 200, false on failure
- Test: `GetRegisteredMasterUrlAsync` returns URL from response body, null on failure
- Test: `X-Master-Api-Key` header is set on each request
### STEP 18 — Unit tests: CmsInstanceService
- [x] 18a. Create `src/SlpModularCms.Modules.Master.Tests/Services/CmsInstanceServiceTests.cs`
- Substitute all 6 dependencies via NSubstitute
- `AddAsync` tests: success path, registration failure (record still persisted), URL from HttpContext, URL from config fallback
- `UpdateStatusAsync` tests: not found → throws, missing DisableMessage → throws, Inactive → no push, Available → persists + pushes, push fails → SlaveContactSuccess=false
- `VerifyIntegrityAsync` tests: skip Inactive, URL match → clear flag, mismatch → re-register, unreachable → set LastIntegrityCheckFailedAt
- `GetAllAsync`: ApiKey not in DTO
### STEP 19 — Unit tests: IntegrityCheckBackgroundService
- [x] 19a. Create `src/SlpModularCms.Modules.Master.Tests/BackgroundServices/IntegrityCheckBackgroundServiceTests.cs`
- Test: service calls `VerifyIntegrityAsync` on tick
- Test: exceptions in `VerifyIntegrityAsync` are caught and logged (service does not crash)
- Use NSubstitute `IServiceScopeFactory` + `IServiceScope`
### STEP 20 — Unit tests: Controller
- [x] 20a. Create `src/SlpModularCms.Modules.Master.Tests/Controllers/CmsInstanceControllerTests.cs`
- Substitute `ICmsInstanceService` via NSubstitute
- `GetAll`: 200 with list
- `Add`: 201 Created with dto; 400 on validation exception; 400 on argument exception
- `UpdateStatus`: 200 with result; 404 on KeyNotFoundException; 400 on ArgumentException
### STEP 21 — Code documentation summary
- [x] 21a. Create `aidlc-docs/features/master-cms-module/construction/master-backend/code/code-summary.md`
- List all created/modified files with paths
- Note the EF Core migration CLI command
- Note NuGet package added
---
## File Summary
| File | Action | ExcludeFromCoverage |
|------|--------|-------------------|
| `SlpModularCms.Modules.Master.csproj` | Create | N/A |
| `SlpModularCms.Modules.Master.Tests.csproj` | Create | N/A |
| `SlpModularCms.Api.csproj` | Modify | N/A |
| `SlpModularCms.sln` | Modify | N/A |
| `Options/MasterModuleOptions.cs` | Create | No |
| `Data/Entities/CmsInstanceStatus.cs` | Create | No |
| `Data/Entities/CmsInstance.cs` | Create | No |
| `Models/CmsInstanceDto.cs` | Create | Yes |
| `Models/CreateCmsInstanceRequest.cs` | Create | Yes |
| `Models/UpdateStatusRequest.cs` | Create | Yes |
| `Models/UpdateStatusResult.cs` | Create | Yes |
| `Data/MasterDbContext.cs` | Create | No |
| `Repositories/ICmsInstanceRepository.cs` | Create | No |
| `Repositories/CmsInstanceRepository.cs` | Create | No |
| `Services/IApiKeyProtector.cs` | Create | No |
| `Services/ApiKeyProtector.cs` | Create | No |
| `Services/ISlaveApiClient.cs` | Create | No |
| `Services/SlaveApiClient.cs` | Create | No |
| `Services/MasterServiceDependencies.cs` | Create | Yes |
| `Services/ICmsInstanceService.cs` | Create | No |
| `Services/CmsInstanceService.cs` | Create | No |
| `BackgroundServices/IntegrityCheckBackgroundService.cs` | Create | No |
| `Controllers/CmsInstanceController.cs` | Create | No |
| `MasterModule.cs` | Create | Yes |
| Tests (6 files) | Create | N/A |
**Total**: 25 application files (24 new, 2 modified) + 6 test files + 1 code summary
@@ -0,0 +1,122 @@
# Functional Design Plan — Unit 1: master-backend
## Unit Context
**Unit**: `master-backend`
**Project**: `SlpModularCms.Modules.Master` (new)
**Test Project**: `SlpModularCms.Modules.Master.Tests` (new)
**Requirements**: FR-MASTER-01 t/m FR-MASTER-05, FR-MASTER-10, FR-MASTER-12/13/14; NFR-MASTER-03/04/05/06
---
## Functional Design Questions
Answer each question by filling in your choice after the `[Answer]:` tag.
---
### Q1 — Auto-Registration Failure Behavior
When the Master calls `RegisterMasterAsync` on a new slave during `AddAsync`, and the slave is unreachable or returns an error, what should happen?
A) **Persist anyway, LastContactedAt = null** — save the `CmsInstance` record regardless; `LastContactedAt` stays null to signal no successful contact; the owner can see the slave is uncontacted and resolve manually.
B) **Rollback — do not persist** — if registration fails, the entire `AddAsync` operation fails; return an error to the owner; no partial record is created.
C) **Persist with Status = Inactive** — save the record but set status to `Inactive` automatically; the owner must manually reactivate once the slave is reachable.
D) Other
[Answer]: A
---
### Q2 — Status Push When Setting to Inactive
When the owner sets a slave's status to `Inactive`, should the Master attempt to push this status change to the slave via HTTP?
A) **No push for Inactive**`Inactive` means the Master stops contacting the slave entirely; no status push is sent; the slave keeps its last received state until the owner reactivates it.
B) **Push Inactive status** — send a final status update to the slave before going silent, so the slave is aware it has been deactivated.
C) Other
[Answer]: A
---
### Q3 — Integrity Check: Unreachable Slave
During `VerifyIntegrityAsync`, if a slave is unreachable (HTTP timeout or error), what should happen?
A) **Log and skip** — log the failure at Warning level, skip this slave, continue with the remaining slaves in the batch; retry on the next scheduled cycle.
B) **Mark as needs-check** — update a `LastIntegrityCheckFailedAt` timestamp on the entity; retry more aggressively on next cycle for flagged slaves.
C) Other
[Answer]: B
---
### Q4 — Master URL Discovery
When the Master calls `RegisterMasterAsync`, it needs to send its own public base URL to the slave. How should the Master know its own URL?
A) **Configured in `appsettings.json`** — owner sets `MasterModule:MasterUrl` (e.g., `https://master.myapp.com`). Explicit, reliable in all environments; simple to implement; follows existing `appsettings` pattern.
B) **Derived from `HttpContext`** — inject `IHttpContextAccessor` and derive the base URL from the current request in the controller; pass it down to the service. No config needed; but only works when called from an HTTP request (not from background service integrity checks).
C) **Two-value approach** — use `IHttpContextAccessor` when available (in controller context), fall back to `MasterModule:MasterUrl` config (for background service context).
D) Other
[Answer]: C
---
### Q5 — ApiKey Storage
The `ApiKey` in `CmsInstance` is the secret the Master sends to the slave in `X-Master-Api-Key`. How should it be stored in the `MasterDbContext`?
A) **Plaintext** — stored as-is in the database. The master must read the actual value to send in HTTP calls, so it must be stored in recoverable form. Access to the database is already protected by the deployment environment.
B) **Encrypted via ASP.NET Core Data Protection** — encrypt at write, decrypt at read using `IDataProtector`. More secure at rest; adds complexity; requires Data Protection key management.
C) Other
[Answer]: B
---
### Q6 — CmsInstanceController: Return 503 or 200 on Failed Slave Push
When `UpdateStatusAsync` successfully persists the new status but the subsequent HTTP push to the slave fails, what should the controller return to the frontend?
A) **200 OK with a warning field** — the status change is persisted (source of truth is the master DB); return 200 with an additional `slaveContactSuccess: false` field so the UI can display a warning.
B) **200 OK, no warning** — the master DB is the authority; whether the slave received the push is an implementation detail; the background integrity check ensures eventual consistency.
C) **207 Multi-Status** — partial success response indicating the DB write succeeded but the slave push failed.
D) Other
[Answer]: A
---
## Execution Steps
After all questions above are answered, the following artifacts will be generated:
- [x] **Step 1** — Analyze all answers; flag any ambiguities
- [x] **Step 2** — Generate `domain-entities.md``CmsInstance` entity, `MasterModuleOptions`, enums, DTO shapes
- [x] **Step 3** — Generate `business-logic-model.md` — process flows: AddAsync, UpdateStatusAsync, VerifyIntegrityAsync
- [x] **Step 4** — Generate `business-rules.md` — validation rules, constraints, decision logic
- [x] **Step 5** — Validate all Mermaid diagrams (no hyphens in IDs, classDef colors, text alternatives)
- [x] **Step 6** — Update `aidlc-state.md`
- [x] **Step 7** — Present completion message for user approval
---
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/master-backend-functional-design-plan.md`
@@ -0,0 +1,72 @@
# NFR Design Plan — Unit 1: master-backend
## Unit Context
**Unit**: `master-backend`
**Input**: nfr-requirements.md, tech-stack-decisions.md, functional-design artifacts
**Patterns to design**: Polly resilience pipeline, Data Protection wiring, background service isolation
---
## NFR Design Questions
Answer each question by filling in your choice after the `[Answer]:` tag.
---
### Q1 — Data Protection: Injection Pattern
`CmsInstanceService` needs to encrypt/decrypt `ApiKey` via `IDataProtector`. How should it be injected?
A) **Direct `IDataProtectionProvider` injection** — inject `IDataProtectionProvider` in `CmsInstanceService`; call `.CreateProtector("SlpModularCms.Master.ApiKey")` in the constructor. Simple; no extra type; easy to test by mocking `IDataProtectionProvider`.
B) **Thin `IApiKeyProtector` wrapper** — define a small `IApiKeyProtector` interface with `Protect(string)` and `Unprotect(string)` methods; inject the interface into `CmsInstanceService`. Purpose is explicit; makes mocking in tests trivially simple (no need to mock ASP.NET Core Data Protection internals).
C) Other
[Answer]: B
---
### Q2 — Polly Pipeline: Registration Scope
The Polly resilience pipeline (exponential backoff + timeout) applies to all `SlaveApiClient` calls. How should it be registered?
A) **Single pipeline on `AddResilienceHandler`** — configure one pipeline on the `IHttpClientBuilder` for `SlaveApiClient`; applies automatically to all HTTP calls made by this client. Zero per-call setup; consistent behavior across all slave endpoints.
B) **Inline `ResiliencePipeline` per method** — build and execute a `ResiliencePipeline` explicitly inside each `SlaveApiClient` method. More control per call (e.g., different timeout for integrity check vs. status push); more boilerplate.
C) Other
[Answer]: A
---
### Q3 — `CmsInstanceService` Constructor Complexity
`CmsInstanceService` will inject: `ICmsInstanceRepository`, `ISlaveApiClient`, `IDataProtectionProvider` (or `IApiKeyProtector`), `IOptions<MasterModuleOptions>`, `IHttpContextAccessor`, `ILogger<CmsInstanceService>` — 6 dependencies. Is this acceptable?
A) **Accept 6 dependencies** — follows existing project pattern (`PersistentAvailabilityService` has similar dependencies); no need to introduce aggregation objects.
B) **Introduce `MasterServiceDependencies` context record** — wrap the 6 dependencies in a single record type to simplify the constructor signature. Cleaner constructor; slightly more indirection.
C) Other
[Answer]: B
---
## Execution Steps
After all questions above are answered, the following artifacts will be generated:
- [x] **Step 1** — Analyze all answers; flag any ambiguities
- [x] **Step 2** — Generate `nfr-design-patterns.md` — Polly pipeline design, Data Protection pattern, logging pattern, test isolation pattern
- [x] **Step 3** — Generate `logical-components.md` — infrastructure wiring diagram, component interaction for NFR patterns
- [x] **Step 4** — Validate all Mermaid diagrams
- [x] **Step 5** — Update `aidlc-state.md`
- [x] **Step 6** — Present completion message for user approval
---
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/master-backend-nfr-design-plan.md`
@@ -0,0 +1,110 @@
# NFR Requirements Plan — Unit 1: master-backend
## Unit Context
**Unit**: `master-backend`
**Applicable NFRs**: NFR-MASTER-01 (fail-open), NFR-MASTER-03 (API key security), NFR-MASTER-04 (background service interval), NFR-MASTER-05 (≥80% test coverage), NFR-MASTER-06 (per-module migrations)
---
## NFR Questions
Answer each question by filling in your choice after the `[Answer]:` tag.
---
### Q1 — HTTP Timeout for Slave API Calls
The `SlaveApiClient` calls slave endpoints for registration, status push, and integrity checks. What HTTP timeout should be configured?
A) **5 seconds** — short timeout; registration and status push are synchronous user-triggered actions; a hanging slave should not block the owner long.
B) **15 seconds** — moderate timeout; balances responsiveness with tolerance for temporarily slow slaves.
C) **30 seconds** — generous timeout; maximizes chance of successful contact before giving up.
D) **Configurable via `MasterModuleOptions.HttpTimeoutSeconds` (default 10)** — owner can tune per-environment; sensible default.
E) Other
[Answer]: D
---
### Q2 — HTTP Retry Policy for Slave API Calls
Should `SlaveApiClient` retry failed HTTP calls automatically, or fail immediately and rely on the next integrity check cycle for recovery?
A) **No retry — fail immediately** — if a slave is unreachable, return `false` at once; the background integrity check handles eventual re-registration; keeps user-visible latency predictable.
B) **1 retry with short delay (e.g., 2s)** — single retry on transient failures (network blip); still bounded latency; reduces false negatives on flaky connections.
C) **Exponential backoff (3 attempts)** — standard resilience pattern; handles transient errors well; may add up to ~7s latency in worst case.
D) Other
[Answer]: C
---
### Q3 — ASP.NET Core Data Protection Key Storage
The `ApiKey` values are encrypted with `IDataProtector`. Where should Data Protection keys be stored?
A) **Default file system** (`%APPDATA%\Microsoft\UserSecrets` / platform default) — zero configuration; keys are machine-bound; acceptable for single-instance deployments.
B) **SQL Server via EF Core** (`services.AddDataProtection().PersistKeysToDbContext<MasterDbContext>()`) — keys survive container restarts and work across deployable instances; requires a `DataProtectionKeys` table in `MasterDbContext`.
C) **Default for now, documented as a production concern** — use the default in code; add a note in documentation that production deployments should configure persistent key storage.
D) Other
[Answer]: A
---
### Q4 — Test Coverage: Exclusions
NFR-MASTER-05 requires ≥80% coverage on new backend code. Which classes should be excluded from coverage measurement for `SlpModularCms.Modules.Master`?
A) **Module registration + migrations only** — exclude `MasterModule.cs` (module registration boilerplate) and EF Core migration files; cover everything else including controllers, services, and repository.
B) **Module registration, migrations, and DTOs/records** — additionally exclude plain record/DTO classes (no logic to test); cover all classes with business logic.
C) **No exclusions** — aim for ≥80% including all files; let natural coverage determine what's tested.
D) Other
[Answer]: B
---
### Q5 — Logging for Slave Contact Failures
When a slave is unreachable during a status push or integrity check, at what log level should the failure be recorded?
A) **Warning** — slave unreachability is expected during network issues; `Warning` signals the issue without triggering on-call alerts; appropriate for a fail-open system.
B) **Error** — slave contact failures represent a degraded state; `Error` ensures visibility in monitoring dashboards and may trigger alerts.
C) **Warning for integrity checks, Error for status push failures** — integrity checks are background maintenance; status push failures have direct owner impact.
D) Other
[Answer]: C
---
## Execution Steps
After all questions above are answered, the following artifacts will be generated:
- [x] **Step 1** — Analyze all answers; flag any ambiguities
- [x] **Step 2** — Generate `nfr-requirements.md` — performance, security, reliability, testability, maintainability requirements
- [x] **Step 3** — Generate `tech-stack-decisions.md` — HTTP client config, Data Protection, retry policy, logging
- [x] **Step 4** — Update `aidlc-state.md`
- [x] **Step 5** — Present completion message for user approval
---
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/master-backend-nfr-requirements-plan.md`
@@ -0,0 +1,62 @@
# Code Generation Plan — Unit 2: slave-availability-extension
## Scope
**Modify**: `src/SlpModularCms.Modules.Availability/` (existing project)
**Create**: `src/SlpModularCms.Modules.Availability.Master.Tests/` (new test project)
**Update**: `SlpModularCms.sln` (add new test project)
**Update**: existing `AvailabilityMiddlewareTests.cs` in `Availability.Tests` (new 3-param signature)
---
## Steps
### Part A — Production Code (Availability module)
- [x] **Step 1**`SlpModularCms.Modules.Availability.csproj` — add `InternalsVisibleTo` for new test project
- [x] **Step 2**`Data/Entities/MasterRegistration.cs` — entity with singleton Id constant
- [x] **Step 3**`Data/AvailabilityDbContext.cs` — DbContext with MasterRegistrations DbSet + OnModelCreating
- [x] **Step 4**`Repositories/IMasterRegistrationRepository.cs``GetAsync`, `AddAsync`, `Update`, `SaveChangesAsync`
- [x] **Step 5**`Repositories/MasterRegistrationRepository.cs` — EF Core implementation
- [x] **Step 6**`Services/IMasterApiKeyProtector.cs``Protect` + `Unprotect` (null on crypto failure)
- [x] **Step 7**`Services/MasterApiKeyProtector.cs` — wraps `IDataProtectionProvider`; purpose `"SlpModularCms.Availability.MasterApiKey"`
- [x] **Step 8**`Services/MasterGateStatus.cs``record MasterGateStatus(bool IsAvailable, string? DisableMessage)`
- [x] **Step 9**`Services/MasterAvailabilityServiceDependencies.cs` — record with repo, protector, logger
- [x] **Step 10**`Services/IMasterAvailabilityService.cs``RegisterAsync(bool)`, `PushStatusAsync(bool)`, `GetRegisteredUrlAsync(string?)`, `GetMasterStatus()`
- [x] **Step 11**`Services/MasterAvailabilityService.cs` — volatile static fields + implementation of all 4 methods
- [x] **Step 12**`Models/RegisterMasterRequest.cs` + `Models/PushStatusRequest.cs``[ExcludeFromCodeCoverage]` records
- [x] **Step 13**`Controllers/MasterController.cs` — 3 endpoints; header extraction; delegate to service; Unauthorized() on false/null
- [x] **Step 14**`Middleware/AvailabilityMiddleware.cs` — extend: add `/api/v1/master/` bypass; move admin check before local gate; add master gate between admin check and local gate
- [x] **Step 15**`AvailabilityModule.cs` — add DbContext, protector, repo, deps, service registrations; add `MigrateAsync` in UseModule
### Part B — Test Project (new)
- [x] **Step 16**`SlpModularCms.Modules.Availability.Master.Tests.csproj` — new project; copy packages from Master.Tests; add `Microsoft.IdentityModel.Tokens` for JWT tests
- [x] **Step 17**`Repositories/MasterRegistrationRepositoryTests.cs` — EF InMemory; GetAsync (null + found); AddAsync; Update; SaveChanges
- [x] **Step 18**`Services/MasterApiKeyProtectorTests.cs``EphemeralDataProtectionProvider`; round-trip; invalid ciphertext returns null
- [x] **Step 19**`Services/MasterAvailabilityServiceTests.cs` — NSubstitute; all 4 public methods; key mismatch; first registration; re-registration; static cache state
- [x] **Step 20**`Controllers/MasterControllerTests.cs` — NSubstitute; missing header → 401; success → 200; service returns false/null → 401
- [x] **Step 21**`Middleware/AvailabilityMiddlewareMasterGateTests.cs` — NSubstitute; master gate blocks → 503; master gate passes → local gate evaluated; `/api/v1/master/` bypasses; master gate + admin JWT → pass through
### Part C — Cross-cutting
- [x] **Step 22**`SlpModularCms.sln` — add `Availability.Master.Tests` project with new GUID
- [x] **Step 23**`src/SlpModularCms.Modules.Availability.Tests/AvailabilityMiddlewareTests.cs` — update all `InvokeAsync(context, _service)` calls to `InvokeAsync(context, _service, masterSvc)` (masterSvc stub returning `IsAvailable=true`)
---
## Key Design Notes
| Concern | Decision |
|---------|----------|
| Singleton Id | `public static readonly Guid SingletonId = new("00000000-0000-0000-0000-000000000001")` |
| IMasterAvailabilityService.RegisterAsync | Returns `bool` (true=ok, false=key mismatch) |
| IMasterAvailabilityService.PushStatusAsync | Returns `bool` (true=ok, false=key mismatch) |
| IMasterAvailabilityService.GetRegisteredUrlAsync | Returns `string?` (null=unauthorized) |
| Unprotect null guard | `storedKey is null \|\| storedKey != incoming` → treat as mismatch |
| Middleware InvokeAsync order | bypass paths → admin JWT → master gate → local gate |
| Existing AvailabilityMiddlewareTests | Add stub `IMasterAvailabilityService` returning `IsAvailable=true` as 3rd param |
---
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/slave-availability-extension-code-generation-plan.md`
@@ -0,0 +1,119 @@
# Functional Design Plan — Unit 2: slave-availability-extension
## Unit Context
**Unit**: `slave-availability-extension`
**Project**: `src/SlpModularCms.Modules.Availability/` (EXTENDED — existing project)
**Test Project**: `src/SlpModularCms.Modules.Availability.Master.Tests/` (NEW)
**Construction Cycle**: Functional Design → NFR Requirements → NFR Design → Code Generation
**What this unit does**: Extends the slave CMS so it can:
1. Accept registrations from a master CMS (store master URL)
2. Receive status pushes from master (cache available/unavailable)
3. Return the registered master URL (for integrity checks from master)
4. Block requests via a two-phase middleware gate (master gate outer + local gate inner)
**Endpoint contract (fixed by Unit 1 SlaveApiClient)**:
- `POST /api/v1/master/register` — receive master registration
- `POST /api/v1/master/status` — receive status push from master
- `GET /api/v1/master/registered-url` — return currently registered master URL
---
## Questions
Answer each question by filling in your choice after the `[Answer]:` tag.
---
### Q1 — API Key Validation: How does the slave authenticate incoming master requests?
The master sends an `X-Master-Api-Key` header on every call. The slave must validate this. Where does the slave get the expected key to compare against?
A) **Configuration-based** — slave admin sets the expected key in `appsettings.json` under `Availability:MasterApiKey`. The key is shared out-of-band when setting up the master/slave relationship. Simple and explicit.
B) **Stored at first registration** — slave accepts the first registration request unconditionally and stores the API key from the header. Subsequent calls (status push, registered-url) validate against this stored key. No pre-configuration needed.
C) **No validation** — slave trusts all requests to master endpoints (relies on network security). Simpler but less secure.
D) Other
[Answer]: B
---
### Q2 — No Master Registered: What happens when a slave has never been registered with a master?
If no registration exists in the `MasterRegistrations` table, what should the master gate do?
A) **Fail-open** — no registration means master gate passes (slave is Available from master's perspective). This is safe: a fresh slave not yet connected to a master is fully accessible. Consistent with the fail-open philosophy from the requirements.
B) **Fail-closed** — no registration means master gate blocks (slave is unavailable until a master registers it). More secure but breaks fresh deployments.
C) Other
[Answer]: A
---
### Q3 — Master Gate: Which paths bypass the master gate?
The master gate blocks incoming requests when master says slave is unavailable. Which paths should always bypass it?
A) **Same as local gate + master endpoints** — bypass the same paths already in `_bypassPrefixes` (Auth, Setup, Availability/status) AND add `/api/v1/master/` so master can always push status or re-register even when gate is closed.
B) **All internal API paths** — bypass everything under `/api/v1/master/` and `/api/v1/Availability/` (broader bypass for any "system" paths).
C) **Master endpoints only** — only `/api/v1/master/` bypasses the master gate; keep Auth/Setup/Availability bypass only in the local gate where it already lives.
D) Other
[Answer]: A
---
### Q4 — Master Status Cache: When does the cached master status expire?
When the master pushes a status to the slave, the slave stores it in a static field. How long is it valid?
A) **No expiry** — cache never expires; only updated when master pushes again. If master goes offline permanently, last known status is used forever. Simplest implementation; consistent with fail-open (default = Available).
B) **Configurable expiry** — add `MasterCacheMinutes` to `AvailabilityOptions`; after expiry, status reverts to Available (fail-open). Allows fresh slaves to auto-recover if master disappears.
C) **Timestamp-based expiry (same as existing circuit breaker)** — static field with `_lastMasterUpdateTime`; if older than `CacheMinutes`, revert to Available.
D) Other
[Answer]: A
---
### Q5 — MasterRegistration Entity: What data does it store?
The `MasterRegistrations` table on the slave stores data about the registered master. What fields are needed?
A) **Minimal**`Id` (Guid PK), `MasterUrl` (string). Just the URL needed for integrity check response. The API key (Q1) is stored in config, not DB.
B) **Extended**`Id` (Guid PK), `MasterUrl` (string), `RegisteredAt` (DateTimeOffset), `LastContactedAt` (DateTimeOffset?). More diagnostic info; useful for monitoring.
C) Other
[Answer]: B
---
## Execution Steps
After all questions above are answered, the following artifacts will be generated:
- [x] **Step 1** — Analyze answers; flag ambiguities
- [x] **Step 2** — Generate `domain-entities.md``MasterRegistration` entity + relationship to `AvailabilityDbContext`
- [x] **Step 3** — Generate `business-logic-model.md` — sequence diagrams for: register, status push, get-registered-url, middleware gate evaluation
- [x] **Step 4** — Generate `business-rules.md` — validation rules, gate bypass logic, cache behavior, API key validation
- [x] **Step 5** — Validate all Mermaid diagrams
- [ ] **Step 6** — Update `aidlc-state.md`
- [ ] **Step 7** — Present completion message for approval
---
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/slave-availability-extension-functional-design-plan.md`
@@ -0,0 +1,72 @@
# NFR Design Plan — Unit 2: slave-availability-extension
## Unit Context
**Unit**: `slave-availability-extension`
**Inputs**: nfr-requirements.md, tech-stack-decisions.md (Unit 2)
**Already decided**: `IMasterApiKeyProtector` wrapper (mirrors Unit 1 `IApiKeyProtector`); `volatile` static cache fields; structured logging table; `AvailabilityDbContext` + `IMasterRegistrationRepository`
**Key design decisions remaining**: middleware extension pattern, `IMasterAvailabilityService` interface shape, service constructor
---
## Questions
Answer each question by filling in your choice after the `[Answer]:` tag.
---
### Q1 — Middleware: How does `AvailabilityMiddleware` read the master gate status?
The master gate needs to read `_masterIsAvailable` (volatile bool) on every request. Two approaches:
A) **InvokeAsync injection** — add `IMasterAvailabilityService` as a third parameter to `InvokeAsync`. The service exposes a synchronous `GetMasterStatus()` method that returns the volatile fields. Pattern:
```csharp
public async Task InvokeAsync(HttpContext context,
IAvailabilityService localSvc,
IMasterAvailabilityService masterSvc)
{
if (!masterSvc.GetMasterStatus().IsAvailable) { /* 503 */ }
...
}
```
Fully testable via NSubstitute substitute on `IMasterAvailabilityService`. Consistent with how `IAvailabilityService` is already injected.
B) **Direct static read**`AvailabilityMiddleware` reads `MasterAvailabilityService._masterIsAvailable` directly via `internal static volatile` fields (with `InternalsVisibleTo` for the test project). No extra injection; no interface method for status read; slightly faster on hot path.
C) Other
[Answer]: A
---
### Q2 — `MasterAvailabilityService` constructor: Direct injection or deps record?
`MasterAvailabilityService` needs 3 dependencies: `IMasterRegistrationRepository`, `IMasterApiKeyProtector`, `ILogger<MasterAvailabilityService>`.
A) **Direct injection** — pass all 3 as constructor parameters. Simple; idiomatic for a small number of deps. No wrapper record needed.
```csharp
public MasterAvailabilityService(
IMasterRegistrationRepository repository,
IMasterApiKeyProtector keyProtector,
ILogger<MasterAvailabilityService> logger)
```
B) **Dependencies record** — wrap in `MasterAvailabilityServiceDependencies` record (consistent with Unit 1's `MasterServiceDependencies`). Useful if deps may grow or for visual consistency.
C) Other
[Answer]: B
---
## Execution Steps
- [x] **Step 1** — Analyze answers; flag ambiguities
- [x] **Step 2** — Generate `nfr-design-patterns.md`
- [x] **Step 3** — Generate `logical-components.md`
- [ ] **Step 4** — Update `aidlc-state.md`
- [ ] **Step 5** — Present completion message for approval
---
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/slave-availability-extension-nfr-design-plan.md`
@@ -0,0 +1,88 @@
# NFR Requirements Plan — Unit 2: slave-availability-extension
## Unit Context
**Unit**: `slave-availability-extension`
**Inputs**: domain-entities.md, business-logic-model.md, business-rules.md (Unit 2 FD)
**Key NFR concerns**: ApiKey storage security, static cache thread safety, test coverage, logging
---
## Questions
Answer each question by filling in your choice after the `[Answer]:` tag.
---
### Q1 — ApiKey Storage: How should the slave store the master's ApiKey?
The `MasterRegistration.ApiKey` is used to validate subsequent master calls (BR-SLAVE-02/04/06). How should it be stored in the DB?
A) **Plain text** — store as-is from the `X-Master-Api-Key` header. Simple; compare directly on each request. Acceptable given the key is an infrastructure credential (not user password) and the DB should be secured.
B) **SHA-256 hash** — store `SHA256(apiKey)` and compare `SHA256(incoming)` on each request. No plain-text at rest; constant-time comparison prevents timing attacks.
C) **ASP.NET Core Data Protection** — encrypt using `IDataProtectionProvider` (same pattern as `ApiKeyProtector` in Unit 1). Reversible; consistent with master-side pattern.
D) Other
[Answer]: C
---
### Q2 — Static Cache Thread Safety: How should `_masterIsAvailable` and `_masterDisableMessage` be protected?
These static fields are read on every request (high frequency) and written only when the master pushes status (rare). What thread safety approach is appropriate?
A) **`volatile` fields** — `private static volatile bool _masterIsAvailable = true` and `private static volatile string? _masterDisableMessage`. Sufficient for atomic single-field reads/writes in .NET; no lock overhead per request. Consistent with `PersistentAvailabilityService`'s existing circuit breaker pattern.
B) **`lock` statement** — lock a `static readonly object _lock` around both reads and writes. Explicit and safe; slight overhead on every request read.
C) Other
[Answer]: A
---
### Q3 — Test Coverage: What should be excluded from coverage in this unit?
Which components in Unit 2 should receive `[ExcludeFromCodeCoverage]`?
A) **Module/wiring only** — only the `AvailabilityModule` changes (service registrations, migration call) and EF migration files. All service, controller, and middleware logic is covered.
B) **Module + DTOs**`AvailabilityModule` changes + DTO/request/response records + EF migration files. Consistent with Unit 1 pattern.
C) Other
[Answer]: B
---
### Q4 — Logging: What log levels apply to the new slave-side logic?
A) **Minimal** — only Error for unexpected exceptions. Keep logs quiet since master calls are frequent background operations.
B) **Structured per scenario** — follow the same table approach as Unit 1:
- `Warning` — API key mismatch on any endpoint
- `Warning` — master gate blocked a request (log path + disable message)
- `Information` — master registered successfully (first registration)
- `Information` — status update received (isAvailable value)
- `Debug` — get-registered-url called
C) Other
[Answer]: B
---
## Execution Steps
- [x] **Step 1** — Analyze answers; flag ambiguities
- [x] **Step 2** — Generate `nfr-requirements.md`
- [x] **Step 3** — Generate `tech-stack-decisions.md`
- [ ] **Step 4** — Update `aidlc-state.md`
- [ ] **Step 5** — Present completion message for approval
---
*Artifact path*: `aidlc-docs/features/master-cms-module/construction/plans/slave-availability-extension-nfr-requirements-plan.md`