# Phase 1: Central Platform Foundations

**Date**: 2026-03-16
**Status**: Draft

---

## 1. Scope

**Goal**: Central node can authenticate users, persist data, and host a web shell. Site-to-central trust model is established.

**Components in scope**:
- **Configuration Database** — EF Core DbContext, Fluent API entity mappings, repository implementations, IAuditService, migrations, seed data, optimistic concurrency on deployment status records.
- **Security & Auth** — LDAP bind authentication, JWT issuance and lifecycle, role extraction from LDAP groups, authorization policies with site scoping, shared Data Protection keys.
- **Host** — Startup validation (REQ-HOST-4), readiness gating (REQ-HOST-4a), Windows Service support (REQ-HOST-5), Akka.NET bootstrap (REQ-HOST-6), ASP.NET web endpoints (REQ-HOST-7), structured logging (REQ-HOST-8), dead letter monitoring (REQ-HOST-8a), graceful shutdown (REQ-HOST-9).
- **Central UI** — Blazor Server shell with SignalR, login/logout flow, role-aware navigation and route guards, failover behavior.

**HighLevelReqs sections covered**: 9.1, 9.2, 9.3, 9.4, 10.1, 10.2, 10.3, 10.4.

**Implicitly supporting**: Section 2.1 (Central Databases) is realized by the Configuration Database schema and DbContext work in this phase.

**Testable Outcome**: User logs in via LDAP, receives JWT with correct role claims, sees an empty dashboard. Admin can manage LDAP group mappings. Audit entries persist. Central runs behind load balancer. Akka.NET actor system boots with cluster configuration.

---

## 2. Prerequisites

- **Phase 0 complete**: Solution structure, all 17 projects compiling, Commons type system (enums, Result<T>, UTC convention), Commons entity POCOs, Commons repository interfaces (including ISecurityRepository, ICentralUiRepository), IAuditService interface, Commons message contracts, Host skeleton (REQ-HOST-1 single binary, REQ-HOST-2 role detection, REQ-HOST-10 extension method convention, REQ-HOST-3 config binding), per-component options classes.
- **Test infrastructure running**: MS SQL (Docker), GLAuth LDAP (Docker) per `infra/docker-compose.yml`.
- **Commons POCOs exist**: AuditLogEntry, LdapGroupMapping, SiteScopingRule, and all security-related entities.
- **Commons interfaces exist**: ISecurityRepository, ICentralUiRepository, IAuditService.

---

## 3. Requirements Checklist

### Section 9.1 — Authentication

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [9.1-1] | UI users authenticate via username/password validated directly against LDAP/Active Directory. | WP-6 |
| [9.1-2] | Sessions maintained via JWT tokens. | WP-7 |
| [9.1-3] | External system API callers authenticate via API key (see Section 7). | N/A — Phase 7 (Inbound API runtime). Noted for split-section tracking. |

### Section 9.2 — Authorization

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [9.2-1] | Authorization is role-based, with roles assigned by LDAP group membership. | WP-8 |
| [9.2-2] | Roles are independent — they can be mixed and matched per user (via group membership). No implied hierarchy. | WP-8, WP-9 |
| [9.2-3] | A user may hold multiple roles simultaneously by being a member of corresponding LDAP groups. | WP-8 |
| [9.2-4] | Inbound API authorization is per-method, based on approved API key lists (see Section 7.4). | N/A — Phase 7. Noted for split-section tracking. |

### Section 9.3 — Roles

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [9.3-1] | Admin: System-wide permission to manage sites, data connections, LDAP group-to-role mappings, API keys, and system-level configuration. | WP-9 |
| [9.3-2] | Design: System-wide permission to author and edit templates, scripts, shared scripts, external system definitions, notification lists, and inbound API method definitions. | WP-9 |
| [9.3-3] | Deployment: Permission to manage instances and deploy configurations to sites. Also triggers system-wide artifact deployment. Can be scoped per site. | WP-9 |

### Section 9.4 — Role Scoping

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [9.4-1] | Admin is always system-wide. | WP-9 |
| [9.4-2] | Design is always system-wide. | WP-9 |
| [9.4-3] | Deployment can be system-wide or site-scoped, controlled by LDAP group membership. | WP-9 |

### Section 10.1 — Audit Storage

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [10.1-1] | Audit logs stored in the configuration MS SQL database. | WP-1, WP-3 |
| [10.1-2] | Entries are append-only — never modified or deleted. | WP-3 |
| [10.1-3] | No retention policy — retained indefinitely. | WP-3 |

### Section 10.2 — Audit Scope

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [10.2-1] | All system-modifying actions are logged: template changes, script changes, alarm changes, instance changes, deployments, system-wide artifact deployments, external system definition changes, database connection changes, notification list changes, inbound API changes, area changes, site & data connection changes, security/admin changes. | WP-3 |

**Note**: In Phase 1, the only auditable actions are security/admin changes (LDAP group mapping management). The IAuditService infrastructure is built here; other components will use it in their respective phases.

### Section 10.3 — Audit Detail Level

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [10.3-1] | Each entry records the state of the entity after the change, serialized as JSON. Only after-state stored. | WP-3 |
| [10.3-2] | Each entry includes: who (authenticated user), what (action, entity type, entity ID, entity name), when (timestamp), and state (JSON after-state, null for deletes). | WP-3 |
| [10.3-3] | One entry per save operation. | WP-3 |

### Section 10.4 — Audit Transactional Guarantee

| ID | Requirement | Work Package |
|----|-------------|--------------|
| [10.4-1] | Audit entries written synchronously within the same database transaction as the change (unit-of-work pattern). If change succeeds, audit entry guaranteed recorded. If change rolls back, audit entry rolls back too. | WP-3 |

---

## 4. Design Constraints Checklist

### From CLAUDE.md Key Design Decisions

| ID | Constraint | Work Package |
|----|-----------|--------------|
| KDD-sec-1 | Authentication: direct LDAP bind, no Kerberos/NTLM. LDAPS/StartTLS required. | WP-6 |
| KDD-sec-2 | JWT: HMAC-SHA256 shared symmetric key, 15-min expiry with sliding refresh, 30-min idle timeout. | WP-7 |
| KDD-sec-3 | LDAP failure: new logins fail; active sessions continue with current roles. | WP-6, WP-7 |
| KDD-sec-4 | Load balancer in front of central UI; JWT + shared Data Protection keys for failover. | WP-10, WP-21 |
| KDD-ui-1 | Central UI: Blazor Server (ASP.NET Core + SignalR). Bootstrap CSS, no third-party component frameworks. | WP-18 |
| KDD-code-1 | Entity classes are persistence-ignorant POCOs in Commons; EF mappings in Configuration Database. | WP-1 |
| KDD-code-2 | Repository interfaces in Commons; implementations in Configuration Database. | WP-2 |
| KDD-code-5 | Per-component configuration via appsettings.json sections bound to options classes (Options pattern). | WP-11 |
| KDD-code-7 | Host readiness gating: /health/ready endpoint, no traffic until operational. | WP-12 |
| KDD-code-8 | EF Core migrations: auto-apply in dev, manual SQL scripts for production. | WP-1 |

### From docs/requirements/Component-ConfigurationDatabase.md

| ID | Constraint | Work Package |
|----|-----------|--------------|
| CD-ConfigDB-1 | Single ScadaLinkDbContext with Fluent API only — no data annotations on entity classes. | WP-1 |
| CD-ConfigDB-2 | Scoped DbContext registration in DI container. | WP-1 |
| CD-ConfigDB-3 | Optimistic concurrency via rowversion on deployment status records and instance lifecycle state. NOT on templates (last-write-wins). | WP-4 |
| CD-ConfigDB-4 | IAuditService.LogAsync adds AuditLogEntry to current DbContext, committed in same SaveChangesAsync call. | WP-3 |
| CD-ConfigDB-5 | Audit entry schema: Id, Timestamp (UTC), User, Action, EntityType, EntityId, EntityName, State (nvarchar(max) JSON). | WP-3 |
| CD-ConfigDB-6 | Audit entry State serialized as JSON using standard .NET JSON serializer. Null for deletes. | WP-3 |
| CD-ConfigDB-7 | Audit log entries indexed on Timestamp, User, EntityType, EntityId, Action for efficient filtering. | WP-1, WP-3 |
| CD-ConfigDB-8 | Seed data via HasData() in entity configurations or dedicated seed migrations. | WP-5 |
| CD-ConfigDB-9 | Connection strings from Host's DatabaseOptions (bound from appsettings.json). | WP-1 |
| CD-ConfigDB-10 | Production startup validates database schema version matches expected migration level; fail fast if not. | WP-1, WP-11 |

### From docs/requirements/Component-Security.md

| ID | Constraint | Work Package |
|----|-----------|--------------|
| CD-Security-1 | Login form → LDAP bind with provided credentials → query group memberships. | WP-6 |
| CD-Security-2 | Transport: LDAPS (port 636) or StartTLS required. Unencrypted LDAP (port 389) not permitted. | WP-6 |
| CD-Security-3 | No local user store. No credentials cached locally. | WP-6 |
| CD-Security-4 | No Windows Integrated Authentication. | WP-6 |
| CD-Security-5 | JWT claims: user display name, username, roles list, site-scoped site IDs. All auth decisions from claims. | WP-7 |
| CD-Security-6 | Token lifecycle: 15-min expiry, sliding refresh re-queries LDAP for current group memberships. | WP-7 |
| CD-Security-7 | Idle timeout: 30 minutes. Tracked via last-activity timestamp in token. | WP-7 |
| CD-Security-8 | Roles are never more than 15 minutes stale (re-queried on refresh). | WP-7 |
| CD-Security-9 | LDAP failure: new logins fail. Active sessions continue with current roles. Token refresh skipped until LDAP available. Recovery: next refresh re-queries. | WP-6, WP-7 |
| CD-Security-10 | Load balancer compatible — no server-side session state. | WP-7, WP-21 |
| CD-Security-11 | Multi-role support. Roles are independent, no hierarchy. | WP-8 |
| CD-Security-12 | Permission enforcement on every endpoint. | WP-9, WP-20 |
| CD-Security-13 | Unauthorized actions return appropriate error and are not logged as audit events. | WP-9 |
| CD-Security-14 | LDAP group mappings stored in configuration database, managed via Central UI (Admin role). | WP-2, WP-18 |

### From docs/requirements/Component-CentralUI.md

| ID | Constraint | Work Package |
|----|-----------|--------------|
| CD-CentralUI-1 | Blazor Server with Bootstrap CSS, no third-party component frameworks. | WP-18 |
| CD-CentralUI-2 | SignalR built-in for real-time push. | WP-18 |
| CD-CentralUI-3 | Failover: load balancer routes to active node. SignalR reconnect on circuit break. JWT survives failover. No re-login required. | WP-21 |
| CD-CentralUI-4 | Both nodes share ASP.NET Data Protection keys (config DB or shared config). | WP-10, WP-21 |
| CD-CentralUI-5 | Active debug view streams and in-progress subscriptions are lost on failover; user must re-open. | WP-21 |

### From docs/requirements/Component-Host.md

| ID | Constraint | Work Package |
|----|-----------|--------------|
| CD-Host-1 | REQ-HOST-4 validation rules: valid NodeRole, non-empty NodeHostname, valid RemotingPort, site needs SiteId, central needs ConfigurationDb + MachineDataDb, at least two seed nodes. | WP-11 |
| CD-Host-2 | REQ-HOST-4a readiness checks: Akka cluster membership, DB connectivity verified, required singletons running. Returns 503 until ready. | WP-12 |
| CD-Host-3 | REQ-HOST-5: UseWindowsService(); runs as console app in dev. No code changes needed. | WP-17 |
| CD-Host-4 | REQ-HOST-6: Akka.Hosting with Remoting (hostname/port), Clustering (seed nodes, role), Persistence (SQL for central), SBR (keep-oldest, stable-after from config). | WP-13 |
| CD-Host-5 | REQ-HOST-7: Central uses WebApplication.CreateBuilder; site uses Host.CreateDefaultBuilder (no Kestrel). | WP-18 |
| CD-Host-6 | REQ-HOST-8: Serilog with SiteId, NodeHostname, NodeRole enrichment. Console + file sinks minimum. Structured output. | WP-14 |
| CD-Host-7 | REQ-HOST-8a: Subscribe to Akka.NET DeadLetter event stream. Log at Warning level. Count reported as health metric. | WP-15 |
| CD-Host-8 | REQ-HOST-9: CoordinatedShutdown on stop signal. No Environment.Exit() or forcible termination. | WP-16 |

---

## 5. Work Packages

### WP-1: Configuration Database — EF Core DbContext, Fluent API Entity Mappings, Initial Migration

**Description**: Implement `ScadaLinkDbContext` with Fluent API mappings for all entity types defined in Commons. Create the initial EF Core migration. Configure scoped registration. Implement environment-aware migration behavior (auto-apply dev, validate-only production).

**Acceptance Criteria**:
1. Single `ScadaLinkDbContext` class maps all Commons POCO entities using Fluent API only — no data annotations on entity classes.
2. Fluent API configurations define relationships, indexes, constraints, and value conversions for all entity types (templates, attributes, alarms, scripts, compositions, instances, overrides, connection bindings, areas, shared scripts, sites, data connections, external systems, external system methods, database connections, notification lists, notification recipients, SMTP config, API keys, API methods, LDAP group mappings, site scoping rules, deployment records, system-wide artifact deployment records, audit log entries).
3. Audit log entries table has indexes on Timestamp, User, EntityType, EntityId, and Action.
4. DbContext registered as scoped service in DI container.
5. Initial migration creates complete schema.
6. `dotnet ef migrations script --idempotent` produces valid SQL.
7. In development: `dbContext.Database.MigrateAsync()` auto-applies on startup.
8. In production: startup validates schema version matches expected migration level and fails fast with clear error if not.
9. Connection strings sourced from `DatabaseOptions` bound from `appsettings.json`.

**Complexity**: L

**Requirements Traced**: [10.1-1], KDD-code-1, KDD-code-8, CD-ConfigDB-1, CD-ConfigDB-2, CD-ConfigDB-7, CD-ConfigDB-9, CD-ConfigDB-10

---

### WP-2: Configuration Database — Repository Implementations (ISecurityRepository, ICentralUiRepository)

**Description**: Implement the EF Core-backed repository classes for `ISecurityRepository` and `ICentralUiRepository` (interfaces defined in Commons in Phase 0). These are the repositories actively used in Phase 1. Other repository implementations will be added in their respective phases.

**Acceptance Criteria**:
1. `ISecurityRepository` implementation supports CRUD operations for LDAP group mappings and site scoping rules.
2. `ICentralUiRepository` implementation supports read-oriented queries across domain areas for display purposes, including audit log queries with filtering by user, entity type, action type, time range, and specific entity ID/name. Results returned in reverse chronological order with pagination.
3. Both implementations use `ScadaLinkDbContext` internally and work with Commons POCO entities.
4. Consuming components depend only on Commons interfaces — never reference the Configuration Database project directly.
5. DI registration via `AddConfigurationDatabase()` extension method wires implementations to interfaces.

**Complexity**: M

**Requirements Traced**: KDD-code-2, CD-Security-14

---

### WP-3: Configuration Database — IAuditService with Transactional Guarantee

**Description**: Implement `IAuditService` in the Configuration Database component. The implementation adds `AuditLogEntry` entities to the current `DbContext` so they commit in the same `SaveChangesAsync()` transaction as the change being audited.

**Acceptance Criteria**:
1. `IAuditService.LogAsync(user, action, entityType, entityId, entityName, afterState)` adds an `AuditLogEntry` to the current DbContext change tracker.
2. Audit entry schema matches: Id, Timestamp (UTC), User, Action, EntityType, EntityId, EntityName, State (nvarchar(max) JSON).
3. Entity state serialized as JSON using standard .NET JSON serializer. State is null for deletes.
4. One audit entry per save operation — when multiple changes are saved together, the caller creates one entry per logical entity change.
5. Audit entries are append-only: no update or delete operations exposed.
6. When `SaveChangesAsync()` succeeds, audit entry is committed in the same transaction.
7. When `SaveChangesAsync()` fails and rolls back, audit entry also rolls back.
8. Integration test: change + audit in same transaction succeeds atomically; rollback proves both are rolled back.
9. Audit entries never modified or deleted (no retention policy).

**Complexity**: M

**Requirements Traced**: [10.1-1], [10.1-2], [10.1-3], [10.2-1], [10.3-1], [10.3-2], [10.3-3], [10.4-1], CD-ConfigDB-4, CD-ConfigDB-5, CD-ConfigDB-6, CD-ConfigDB-7

---

### WP-4: Configuration Database — Optimistic Concurrency on Deployment Status Records

**Description**: Configure EF Core optimistic concurrency via `rowversion` concurrency tokens on deployment status and instance lifecycle state entities. Verify that template entities intentionally do NOT have concurrency tokens (last-write-wins).

**Acceptance Criteria**:
1. Deployment status records have a `rowversion` / concurrency token configured in Fluent API.
2. Instance lifecycle state (enabled/disabled) has a concurrency token.
3. `SaveChangesAsync()` throws `DbUpdateConcurrencyException` when a stale deployment status record is updated.
4. Template entities do NOT have concurrency tokens — last-write-wins behavior verified.
5. Unit test: concurrent update to deployment status fails for the stale writer.
6. Unit test: concurrent template update succeeds (last write wins).

**Complexity**: S

**Requirements Traced**: CD-ConfigDB-3

---

### WP-5: Configuration Database — Seed Data

**Description**: Implement seed data for initial system setup, including a default LDAP group mapping for the Admin role so a fresh installation is usable.

**Acceptance Criteria**:
1. Seed data defined using EF Core `HasData()` in entity configurations.
2. Default LDAP group-to-Admin-role mapping seeded (e.g., `SCADA-Admins` -> Admin).
3. Seed data included in generated SQL migration scripts (applies in both dev and production).
4. System is usable after fresh install — admin can log in and manage further mappings.

**Complexity**: S

**Requirements Traced**: CD-ConfigDB-8

---

### WP-6: Security & Auth — LDAP Bind Service

**Description**: Implement the LDAP authentication service that validates user credentials via direct LDAP bind and queries group memberships. Enforce transport security (LDAPS/StartTLS).

**Acceptance Criteria**:
1. `LdapAuthService` accepts username/password, performs LDAP bind against configured server.
2. LDAPS (port 636) or StartTLS connections enforced. Unencrypted LDAP (port 389) rejected at configuration validation.
3. On successful bind, queries user's LDAP group memberships and user display name.
4. Returns authentication result with display name, username, and group list.
5. No local user store — all identity from AD.
6. No Kerberos/NTLM — direct LDAP bind only.
7. No credentials cached locally.
8. LDAP connection failure: returns authentication failure for new login attempts. Does NOT throw unhandled exceptions.
9. LDAP server address and port sourced from `SecurityOptions` (bound from `appsettings.json`).
10. Integration test with GLAuth: successful bind, group query, failed bind (wrong password), LDAP unavailable.

**Complexity**: M

**Requirements Traced**: [9.1-1], KDD-sec-1, KDD-sec-3, CD-Security-1, CD-Security-2, CD-Security-3, CD-Security-4

---

### WP-7: Security & Auth — JWT Issuance, Sliding Refresh, Idle Timeout

**Description**: Implement JWT token issuance on successful authentication, with HMAC-SHA256 signing, 15-minute expiry, sliding refresh that re-queries LDAP, and 30-minute idle timeout.

**Acceptance Criteria**:
1. JWT signed with HMAC-SHA256 using shared symmetric key from `SecurityOptions.JwtSigningKey`.
2. JWT claims include: user display name, username, roles list (Admin, Design, Deployment), and for site-scoped Deployment, list of permitted site IDs.
3. JWT expiry set to 15 minutes.
4. Sliding refresh: on each authenticated request, if token is near expiry, re-queries LDAP for current group memberships and issues fresh token with updated claims.
5. Roles never more than 15 minutes stale.
6. Idle timeout: 30 minutes (configurable via `SecurityOptions.IdleTimeoutMinutes`). Last-activity timestamp tracked in token. If no request within idle window, token not refreshed — user must re-login.
7. Active users stay logged in indefinitely (sliding refresh continues as long as requests within idle window).
8. LDAP failure during refresh: token refresh skipped, user continues with current roles until token expires or LDAP recovers.
9. When LDAP recovers, next refresh cycle re-queries and updates claims.
10. JWT is self-contained — no server-side session state. Load-balancer compatible.
11. Both central cluster nodes use same signing key, so either can issue and validate tokens.
12. Unit test: token issuance with correct claims.
13. Unit test: token near expiry triggers refresh.
14. Unit test: idle timeout exceeded prevents refresh.
15. Unit test: LDAP unavailable during refresh — token continues with existing claims.

**Complexity**: L

**Requirements Traced**: [9.1-2], KDD-sec-2, KDD-sec-3, CD-Security-5, CD-Security-6, CD-Security-7, CD-Security-8, CD-Security-9, CD-Security-10

---

### WP-8: Security & Auth — Role Claim Extraction from LDAP Groups

**Description**: Implement the mapping logic that converts LDAP group memberships to system roles (Admin, Design, Deployment) using the LDAP group mappings stored in the configuration database.

**Acceptance Criteria**:
1. LDAP group names queried from user's memberships are matched against `LdapGroupMapping` records from `ISecurityRepository`.
2. Multiple group memberships produce multiple roles — a user in both `SCADA-Designers` and `SCADA-Deploy-All` gets both Design and Deployment roles.
3. Roles are independent — no implied hierarchy. Having Admin does not imply Design or Deployment.
4. Deployment role extracts site scope from the mapping: system-wide (`Deploy-All` pattern) or site-scoped (per-site group).
5. A user with multiple site-scoped Deployment groups accumulates all permitted site IDs.
6. Unrecognized LDAP groups (not in mapping table) are ignored — no error.
7. User with no matching groups gets no roles — can authenticate but has no permissions.
8. Unit test: multi-role extraction from multiple groups.
9. Unit test: site-scoped Deployment role accumulation.
10. Unit test: unrecognized group ignored.

**Complexity**: M

**Requirements Traced**: [9.2-1], [9.2-2], [9.2-3], CD-Security-11

---

### WP-9: Security & Auth — Authorization Policies with Site-Scoped Deployment Checks

**Description**: Implement ASP.NET Core authorization policies for Admin, Design, and Deployment roles. Deployment policy includes site-scope validation — when a Deployment action targets a specific site, verify the user's permitted site IDs include that site.

**Acceptance Criteria**:
1. ASP.NET Core authorization policies defined for: `RequireAdmin`, `RequireDesign`, `RequireDeployment`.
2. Admin policy: requires Admin role claim. Always system-wide.
3. Design policy: requires Design role claim. Always system-wide.
4. Deployment policy: requires Deployment role claim. Additionally checks site scope when a target site is specified.
5. Site-scoped Deployment: if user has system-wide Deployment, all sites allowed. If site-scoped, only permitted site IDs.
6. Permission enforcement on every API endpoint and UI action.
7. Unauthorized actions return 403 Forbidden.
8. Unauthorized actions are NOT logged as audit events (only successful changes audited).
9. Admin role permission set: manage sites, data connections, LDAP group-to-role mappings, API keys, system-level configuration.
10. Design role permission set: author templates, scripts, shared scripts, external system definitions, notification lists, inbound API method definitions.
11. Deployment role permission set: manage instances, deploy configurations, trigger system-wide artifact deployment. Site-scoped variant restricts to permitted sites.
12. Unit test: Admin authorized for admin actions, denied for design-only actions.
13. Unit test: site-scoped Deployment user authorized for permitted site, denied for other site.
14. Unit test: user with no roles denied all actions.

**Complexity**: M

**Requirements Traced**: [9.3-1], [9.3-2], [9.3-3], [9.4-1], [9.4-2], [9.4-3], CD-Security-12, CD-Security-13

---

### WP-10: Security & Auth — Shared Data Protection Keys

**Description**: Configure ASP.NET Core Data Protection to use shared keys accessible by both central cluster nodes, ensuring anti-forgery tokens and any other Data Protection-dependent artifacts remain valid across failover.

**Acceptance Criteria**:
1. Data Protection keys stored in a shared location (configuration database or shared filesystem path).
2. Both central nodes configured to use the same key ring.
3. Anti-forgery tokens generated by one node are valid on the other.
4. JWT signing key is separate (in SecurityOptions) but conceptually aligned — both nodes use the same value.
5. Integration test: token/artifact generated on node A validates on node B (simulated via two DI containers with same config).

**Complexity**: S

**Requirements Traced**: KDD-sec-4, CD-CentralUI-4

---

### WP-11: Host — Full Startup Validation (REQ-HOST-4)

**Description**: Implement comprehensive configuration validation that runs before the Akka.NET actor system is created. Fail fast with clear error messages for any missing or invalid configuration.

**Acceptance Criteria**:
1. `NodeConfiguration.Role` validated as a valid `NodeRole` value.
2. `NodeConfiguration.NodeHostname` validated as non-null and non-empty.
3. `NodeConfiguration.RemotingPort` validated in range 1-65535.
4. Site nodes validated to have non-empty `SiteId`.
5. Central nodes validated to have non-empty `ConfigurationDb` and `MachineDataDb` connection strings.
6. Site nodes validated to have non-empty SQLite path values.
7. At least two seed nodes must be configured.
8. All per-component options validated for required fields (e.g., `SecurityOptions.LdapServer`, `SecurityOptions.JwtSigningKey` for central).
9. Validation runs before any actor system creation — no partial startup on validation failure.
10. Clear, actionable error messages indicating which configuration value is missing or invalid.
11. Unit test: each validation rule triggers expected failure with correct error message.

**Complexity**: M

**Requirements Traced**: REQ-HOST-4, KDD-code-5, CD-Host-1

---

### WP-12: Host — Readiness Gating with /health/ready Endpoint (REQ-HOST-4a)

**Description**: Implement an ASP.NET Core health check endpoint that reports readiness status. Central web endpoints must not accept traffic until the node is fully operational.

**Acceptance Criteria**:
1. `/health/ready` endpoint implemented using ASP.NET Core health checks.
2. Returns 200 OK when node is ready: Akka.NET cluster membership established, database connectivity verified, required singletons running.
3. Returns 503 Service Unavailable during startup or when not ready.
4. Load balancer can use this endpoint to determine routing.
5. Central UI and Inbound API requests are blocked (503) until readiness achieved.
6. Integration test: startup sequence returns 503 before ready, 200 after ready.

**Complexity**: M

**Requirements Traced**: REQ-HOST-4a, KDD-code-7, CD-Host-2

---

### WP-13: Host — Akka.NET Bootstrap (REQ-HOST-6)

**Description**: Configure the Akka.NET actor system using Akka.Hosting with remoting, clustering, persistence, and split-brain resolution for the central node role.

**Acceptance Criteria**:
1. Akka.NET actor system configured via Akka.Hosting `AddAkka()`.
2. Remoting configured with hostname and port from `NodeOptions`.
3. Clustering configured with seed nodes and cluster role from configuration.
4. Persistence configured with SQL Server journal and snapshot store for central.
5. Split-brain resolver configured: keep-oldest strategy with `down-if-alone = on`, `stable-after` from `ClusterOptions`.
6. Component actors registered via `AddXxxActors()` extension methods, conditional on central role.
7. Actor system does NOT start if startup validation (WP-11) fails.
8. Integration test: actor system boots, joins cluster, persistence provider available.

**Complexity**: M

**Requirements Traced**: REQ-HOST-6, CD-Host-4

---

### WP-14: Host — Serilog Structured Logging (REQ-HOST-8)

**Description**: Configure Serilog as the logging provider with configuration-driven sinks and environment-specific enrichment properties.

**Acceptance Criteria**:
1. Serilog configured as the logging provider for the Host.
2. Console and file sinks configured at minimum, driven by `LoggingOptions`.
3. Every log entry automatically enriched with `SiteId`, `NodeHostname`, and `NodeRole` from `NodeOptions`.
4. Structured (machine-parseable) output format.
5. Log level overrides configurable per namespace via `LoggingOptions`.
6. Integration test: log output contains enriched properties.

**Complexity**: S

**Requirements Traced**: REQ-HOST-8, CD-Host-6

---

### WP-15: Host — Dead Letter Monitoring Subscription (REQ-HOST-8a)

**Description**: Subscribe to the Akka.NET `DeadLetter` event stream and log dead letters at Warning level. Maintain a dead letter count for health metric reporting.

**Acceptance Criteria**:
1. Akka.NET `EventStream` subscription for `DeadLetter` events registered at actor system startup.
2. Each dead letter logged at Warning level with message type, sender, and intended recipient.
3. Dead letter count maintained (incrementing counter) for health metric reporting.
4. Unit test: sending a message to a terminated actor produces a dead letter log entry and increments the counter.

**Complexity**: S

**Requirements Traced**: REQ-HOST-8a, CD-Host-7

---

### WP-16: Host — CoordinatedShutdown Wiring (REQ-HOST-9)

**Description**: Wire up Akka.NET CoordinatedShutdown to trigger on process stop signals (Windows Service stop, Ctrl+C, SIGTERM). Ensure graceful actor drain before process exit.

**Acceptance Criteria**:
1. CoordinatedShutdown triggered on Windows Service stop signal.
2. CoordinatedShutdown triggered on Ctrl+C (console mode).
3. CoordinatedShutdown triggered on SIGTERM.
4. No `Environment.Exit()` or forcible actor system termination — all shutdown goes through CoordinatedShutdown.
5. Actors have opportunity to drain in-flight work during shutdown.
6. Integration test: Ctrl+C triggers coordinated shutdown sequence; process exits cleanly.

**Complexity**: S

**Requirements Traced**: REQ-HOST-9, CD-Host-8

---

### WP-17: Host — Windows Service Support (REQ-HOST-5)

**Description**: Enable the Host to run as a Windows Service in production and as a console app in development, using `UseWindowsService()`.

**Acceptance Criteria**:
1. `UseWindowsService()` called in Host startup.
2. When running outside a Windows Service context, runs as standard console application.
3. No code changes or conditional compilation required to switch modes.
4. Windows Service stop triggers CoordinatedShutdown (verified by WP-16).

**Complexity**: S

**Requirements Traced**: REQ-HOST-5, CD-Host-3

---

### WP-18: Central UI — Blazor Server Shell with SignalR

**Description**: Implement the Blazor Server application shell with Bootstrap CSS, hosted via ASP.NET Core on central nodes. This is the UI framework setup — individual workflow pages are added in later phases.

**Acceptance Criteria**:
1. Blazor Server application bootstrapped via `WebApplication.CreateBuilder` on central nodes.
2. Central uses `MapCentralUI()` extension method to register Blazor endpoints.
3. Bootstrap CSS for styling — no third-party component frameworks (no Telerik, Syncfusion, etc.).
4. SignalR circuit established for real-time server push.
5. Layout includes navigation sidebar/header (content is role-dependent — see WP-20).
6. Landing page (empty dashboard placeholder) accessible after login.
7. LDAP group mapping management page (Admin role) — CRUD for group-to-role mappings and site scoping rules.
8. Site nodes use `Host.CreateDefaultBuilder` — no Kestrel, no web endpoints (verified by REQ-HOST-7).

**Complexity**: M

**Requirements Traced**: KDD-ui-1, CD-CentralUI-1, CD-CentralUI-2, CD-Host-5, REQ-HOST-7, CD-Security-14

---

### WP-19: Central UI — Login/Logout Flow with JWT

**Description**: Implement the login page, authentication flow (calling LDAP bind service), JWT cookie management, and logout.

**Acceptance Criteria**:
1. Login page with username/password form.
2. On submit, calls LDAP authentication service (WP-6).
3. On success, JWT issued (WP-7) and stored as HTTP-only cookie.
4. On failure, login page displays error message.
5. Logout clears the JWT cookie and redirects to login page.
6. Unauthenticated requests redirected to login page.
7. Integration test: full login → authenticated page → logout → redirect to login.

**Complexity**: M

**Requirements Traced**: [9.1-1], [9.1-2], CD-Security-1

---

### WP-20: Central UI — Role-Aware Navigation and Route Guards

**Description**: Implement role-based navigation visibility and route-level authorization guards. Navigation items are shown/hidden based on the user's roles. Unauthorized route access is blocked.

**Acceptance Criteria**:
1. Navigation items shown/hidden based on user's roles from JWT claims.
2. Admin-only pages (LDAP mapping, site management placeholders) visible only to Admin role.
3. Design-only page placeholders visible only to Design role.
4. Deployment-only page placeholders visible only to Deployment role.
5. Route guards: direct URL navigation to an unauthorized page returns 403 or redirects.
6. Multi-role users see all navigation items for their combined roles.
7. User with no roles sees minimal UI (no action items).
8. Permission enforcement on every page load and action.

**Complexity**: M

**Requirements Traced**: [9.2-1], [9.3-1], [9.3-2], [9.3-3], CD-Security-12

---

### WP-21: Central UI — Failover Behavior

**Description**: Ensure the Central UI gracefully handles central node failover: SignalR reconnection, JWT survival, shared Data Protection keys.

**Acceptance Criteria**:
1. When SignalR circuit breaks (node failover), Blazor Server's built-in reconnection logic attempts to re-establish the connection.
2. User's JWT survives failover — no re-login required if token is still valid.
3. Both nodes share ASP.NET Data Protection keys (WP-10) so anti-forgery tokens remain valid.
4. Active debug view streams and real-time subscriptions are lost on failover (acceptable — user must re-open).
5. After reconnection, UI state is restored (page reload acceptable).
6. Integration test (simulated): disconnect SignalR, verify reconnection attempt; verify JWT from node A validates on node B.

**Complexity**: M

**Requirements Traced**: KDD-sec-4, CD-CentralUI-3, CD-CentralUI-4, CD-CentralUI-5, CD-Security-10

---

### WP-22: Integration Tests — Auth Flow, Audit Logging, Startup Validation, Readiness Gating

**Description**: End-to-end integration tests that verify the Phase 1 testable outcome: full auth flow, audit persistence, startup behavior, and readiness gating.

**Acceptance Criteria**:
1. **Auth flow test**: User authenticates against GLAuth → receives JWT with correct role claims → accesses protected page → sees dashboard.
2. **Multi-role test**: User in multiple LDAP groups gets combined roles in JWT.
3. **Site-scoped Deployment test**: User with site-scoped Deployment group gets correct site IDs in token; can access permitted site actions; denied for other sites.
4. **LDAP failure test**: With LDAP down, new login fails; existing session with valid JWT continues; on LDAP recovery, next refresh updates claims.
5. **Audit test**: Admin manages LDAP group mapping → audit entry written in same transaction → entry has correct schema (who, what, when, state JSON).
6. **Audit rollback test**: Simulate failure during SaveChangesAsync → both change and audit entry are rolled back.
7. **Startup validation test**: Missing required config → Host fails fast with clear error before actor system creation.
8. **Readiness gating test**: During startup, `/health/ready` returns 503; after full initialization, returns 200.
9. **Idle timeout test**: After 30 minutes of inactivity, token is not refreshed and user must re-login.
10. **Load balancer simulation test**: JWT issued by one app instance validates on another (same signing key + Data Protection keys).

**Complexity**: L

**Requirements Traced**: [9.1-1], [9.1-2], [9.2-1], [9.2-3], [9.4-3], [10.4-1], KDD-sec-3, REQ-HOST-4, REQ-HOST-4a

---

## 6. Test Strategy

### Unit Tests

| Area | Scope |
|------|-------|
| Configuration Database | DbContext mapping correctness, Fluent API constraints, audit entry serialization, optimistic concurrency behavior, seed data presence. |
| Security & Auth | JWT issuance and claim structure, token refresh logic, idle timeout enforcement, role extraction from LDAP groups (mock LDAP responses), authorization policy evaluation, site-scoped Deployment checks. |
| Host | Each startup validation rule (missing config → clear error), dead letter counter increment. |

### Integration Tests

| Area | Scope |
|------|-------|
| LDAP Integration | Authenticate against GLAuth (Docker). Successful bind, failed bind, group query, LDAP unavailable. |
| Database Integration | Verify EF Core migration applies to SQL Server (Docker). CRUD operations via repositories. Audit entry transactional guarantee (commit + rollback scenarios). Optimistic concurrency on deployment records. |
| Auth Flow | Full login → JWT → protected page → logout. Multi-role. Site-scoped Deployment. LDAP failure mid-session. Idle timeout. |
| Startup | Validation failure prevents actor system start. Readiness gating (503 → 200 sequence). |
| Failover Simulation | JWT validates on second instance. Data Protection keys shared. SignalR reconnection behavior. |

### Negative Tests (Prohibition Verification)

| Requirement | Test |
|-------------|------|
| CD-Security-2: Unencrypted LDAP not permitted | Configuration with LDAP port 389 and no TLS is rejected at startup validation. |
| CD-Security-3: No local user store | No fallback authentication when LDAP is down — login fails. |
| CD-Security-4: No Kerberos/NTLM | Only direct LDAP bind is used — no Negotiate/NTLM headers accepted. |
| CD-Security-13: Unauthorized actions not audited | Attempt unauthorized action → verify no audit entry created. |
| [10.1-2]: Audit entries never modified or deleted | Verify no UPDATE or DELETE operations exist on AuditLogEntry repository. |
| CD-ConfigDB-3 (negative): No concurrency on templates | Concurrent template update succeeds (last write wins). |

---

## 7. Verification Gate

Phase 1 is complete when ALL of the following pass:

1. **Configuration Database**: `ScadaLinkDbContext` creates full schema on SQL Server. All Fluent API mappings correct. Initial migration generates valid idempotent SQL. Seed data present.
2. **Repositories**: `ISecurityRepository` and `ICentralUiRepository` implementations pass CRUD integration tests against SQL Server.
3. **Audit Service**: `IAuditService` implementation commits audit entries in same transaction as changes. Rollback test passes. Append-only constraint verified.
4. **Optimistic Concurrency**: Stale deployment status update throws `DbUpdateConcurrencyException`. Template last-write-wins verified.
5. **LDAP Authentication**: User authenticates against GLAuth. Transport security enforced. Failed bind handled correctly.
6. **JWT Lifecycle**: Token issued with correct claims. 15-minute refresh re-queries LDAP. 30-minute idle timeout enforced. LDAP-down scenario handled.
7. **Role Extraction**: Multi-role, site-scoped Deployment, unrecognized groups — all correct.
8. **Authorization Policies**: Admin/Design/Deployment policies enforce correct permissions. Site-scoped Deployment checked. Unauthorized → 403, no audit.
9. **Shared Data Protection Keys**: Token/artifact from node A validates on node B.
10. **Startup Validation**: Every validation rule tested. Missing config → fail fast before actor system.
11. **Readiness Gating**: `/health/ready` returns 503 during startup, 200 when ready.
12. **Akka.NET Bootstrap**: Actor system starts with cluster, remoting, persistence. Cluster membership established.
13. **Logging**: Serilog outputs structured logs with SiteId, NodeHostname, NodeRole enrichment.
14. **Dead Letters**: Dead letter events logged at Warning level and counted.
15. **Graceful Shutdown**: CoordinatedShutdown triggers on stop signal. Clean exit.
16. **Windows Service**: `UseWindowsService()` configured. Console mode works in dev.
17. **Blazor Server Shell**: Login page, authenticated dashboard, LDAP group mapping management page, Bootstrap CSS, SignalR circuit active.
18. **Role-Aware Navigation**: Correct items visible per role. Route guards block unauthorized access.
19. **Failover**: SignalR reconnection, JWT survives, Data Protection keys shared.
20. **End-to-end testable outcome**: User logs in via LDAP → JWT with correct roles → sees empty dashboard → admin manages LDAP mappings → audit entries persist → central behind load balancer → Akka.NET boots.

---

## 8. Open Questions

| # | Question | Context | Impact | Status |
|---|----------|---------|--------|--------|
| Q-P1-1 | Should Data Protection keys be stored in the configuration database (via EF Core Data Protection key store) or on a shared filesystem path? | WP-10 requires both nodes share keys. DB storage is more portable; filesystem requires shared mount. | Implementation detail for WP-10. Either approach works. | Open — decide during implementation. Default to DB storage. |
| Q-P1-2 | Should the audit log viewer be included in Phase 1 UI, or deferred to Phase 6 (Deployment Operations UI)? | Phase 1 builds the IAuditService infrastructure. The viewer is an Admin workflow. Phase 6 explicitly lists "Audit log viewer" as a sub-task. | If deferred, admin can still query audit logs via SQL. UI viewer in Phase 6. | Resolved — Phase 1 builds the infrastructure only. Viewer UI is Phase 6. |
| Q-P1-3 | Should the production schema validation (CD-ConfigDB-10) be a hard fail or a warning? | Hard fail prevents running against a stale schema; warning allows operation at risk. | Decision affects production deployment workflow. | Resolved — Hard fail. The requirement explicitly says "fail fast with clear error." |

**Note**: Q-P1-1 logged to `questions.md`.

---

## 9. Split-Section Tracking

### Section 9.1 — Authentication (shared with Phase 7)

| Bullet | Phase 1 | Phase 7 |
|--------|---------|---------|
| [9.1-1] UI users authenticate via username/password against LDAP/AD. Sessions via JWT. | Covered (WP-6, WP-7) | — |
| [9.1-2] Sessions maintained via JWT tokens. | Covered (WP-7) | — |
| [9.1-3] External system API callers authenticate via API key (Section 7). | — | Covered (Inbound API) |

**Union**: Complete. No gaps.

### Section 9.2 — Authorization (shared with Phase 7)

| Bullet | Phase 1 | Phase 7 |
|--------|---------|---------|
| [9.2-1] Role-based, assigned by LDAP group membership. | Covered (WP-8) | — |
| [9.2-2] Roles independent, no hierarchy. | Covered (WP-8, WP-9) | — |
| [9.2-3] Multiple roles via multiple LDAP groups. | Covered (WP-8) | — |
| [9.2-4] Inbound API authorization per-method, approved API key lists. | — | Covered (Inbound API) |

**Union**: Complete. No gaps.

---

## 10. Post-Generation Verification (Orphan Check)

### Forward Check (Requirements → Work Packages)

| Item | Mapped To | Verified |
|------|-----------|----------|
| [9.1-1] | WP-6, WP-19 | Yes — LDAP bind + login page |
| [9.1-2] | WP-7, WP-19 | Yes — JWT issuance + cookie management |
| [9.1-3] | Phase 7 | Yes — documented in split-section tracking |
| [9.2-1] | WP-8 | Yes — role extraction from LDAP groups |
| [9.2-2] | WP-8, WP-9 | Yes — independent roles, no hierarchy |
| [9.2-3] | WP-8 | Yes — multi-group multi-role |
| [9.2-4] | Phase 7 | Yes — documented in split-section tracking |
| [9.3-1] | WP-9 | Yes — Admin policy and permission set |
| [9.3-2] | WP-9 | Yes — Design policy and permission set |
| [9.3-3] | WP-9 | Yes — Deployment policy and permission set |
| [9.4-1] | WP-9 | Yes — Admin always system-wide |
| [9.4-2] | WP-9 | Yes — Design always system-wide |
| [9.4-3] | WP-9 | Yes — Deployment system-wide or site-scoped |
| [10.1-1] | WP-1, WP-3 | Yes — stored in config DB |
| [10.1-2] | WP-3 | Yes — append-only, no modify/delete |
| [10.1-3] | WP-3 | Yes — no retention policy |
| [10.2-1] | WP-3 | Yes — IAuditService supports all action types |
| [10.3-1] | WP-3 | Yes — after-state JSON |
| [10.3-2] | WP-3 | Yes — who, what, when, state schema |
| [10.3-3] | WP-3 | Yes — one entry per save |
| [10.4-1] | WP-3 | Yes — same-transaction guarantee |
| KDD-sec-1 | WP-6 | Yes — direct LDAP bind, LDAPS/StartTLS |
| KDD-sec-2 | WP-7 | Yes — HMAC-SHA256, 15min, 30min idle |
| KDD-sec-3 | WP-6, WP-7 | Yes — LDAP failure handling |
| KDD-sec-4 | WP-10, WP-21 | Yes — LB + shared keys |
| KDD-ui-1 | WP-18 | Yes — Blazor Server + Bootstrap |
| KDD-code-1 | WP-1 | Yes — POCOs in Commons, EF in ConfigDB |
| KDD-code-2 | WP-2 | Yes — interfaces in Commons, impls in ConfigDB |
| KDD-code-5 | WP-11 | Yes — Options pattern validated at startup |
| KDD-code-7 | WP-12 | Yes — /health/ready endpoint |
| KDD-code-8 | WP-1 | Yes — auto-apply dev, manual SQL prod |
| CD-ConfigDB-1 | WP-1 | Yes — Fluent API only |
| CD-ConfigDB-2 | WP-1 | Yes — scoped registration |
| CD-ConfigDB-3 | WP-4 | Yes — rowversion on deployment status, not templates |
| CD-ConfigDB-4 | WP-3 | Yes — LogAsync adds to DbContext |
| CD-ConfigDB-5 | WP-3 | Yes — audit schema fields |
| CD-ConfigDB-6 | WP-3 | Yes — JSON serialization, null for deletes |
| CD-ConfigDB-7 | WP-1, WP-3 | Yes — indexes on audit table |
| CD-ConfigDB-8 | WP-5 | Yes — HasData() seed |
| CD-ConfigDB-9 | WP-1 | Yes — connection strings from DatabaseOptions |
| CD-ConfigDB-10 | WP-1, WP-11 | Yes — schema version validation in prod |
| CD-Security-1 | WP-6 | Yes — login form → LDAP bind → group query |
| CD-Security-2 | WP-6 | Yes — LDAPS/StartTLS enforced |
| CD-Security-3 | WP-6 | Yes — no local user store |
| CD-Security-4 | WP-6 | Yes — no Windows Integrated Auth |
| CD-Security-5 | WP-7 | Yes — JWT claims structure |
| CD-Security-6 | WP-7 | Yes — 15-min refresh re-queries LDAP |
| CD-Security-7 | WP-7 | Yes — 30-min idle via last-activity in token |
| CD-Security-8 | WP-7 | Yes — roles never > 15min stale |
| CD-Security-9 | WP-6, WP-7 | Yes — LDAP failure handling |
| CD-Security-10 | WP-7, WP-21 | Yes — no server-side session state |
| CD-Security-11 | WP-8 | Yes — multi-role, independent |
| CD-Security-12 | WP-9, WP-20 | Yes — permission enforcement on every endpoint |
| CD-Security-13 | WP-9 | Yes — unauthorized not audited |
| CD-Security-14 | WP-2, WP-18 | Yes — mappings in DB, managed via UI |
| CD-CentralUI-1 | WP-18 | Yes — Blazor Server + Bootstrap |
| CD-CentralUI-2 | WP-18 | Yes — SignalR built-in |
| CD-CentralUI-3 | WP-21 | Yes — SignalR reconnect, JWT survives |
| CD-CentralUI-4 | WP-10, WP-21 | Yes — shared Data Protection keys |
| CD-CentralUI-5 | WP-21 | Yes — debug streams lost on failover |
| CD-Host-1 | WP-11 | Yes — all validation rules |
| CD-Host-2 | WP-12 | Yes — readiness checks |
| CD-Host-3 | WP-17 | Yes — UseWindowsService() |
| CD-Host-4 | WP-13 | Yes — Akka.Hosting config |
| CD-Host-5 | WP-18 | Yes — WebApplication vs Host builder |
| CD-Host-6 | WP-14 | Yes — Serilog enrichment |
| CD-Host-7 | WP-15 | Yes — DeadLetter subscription + count |
| CD-Host-8 | WP-16 | Yes — CoordinatedShutdown |

**Forward check result**: All 68 checklist items map to at least one work package. **0 orphans.**

### Reverse Check (Work Packages → Requirements)

| Work Package | Traced Requirements | Verified |
|--------------|-------------------|----------|
| WP-1 | [10.1-1], KDD-code-1, KDD-code-8, CD-ConfigDB-1, CD-ConfigDB-2, CD-ConfigDB-7, CD-ConfigDB-9, CD-ConfigDB-10 | Yes |
| WP-2 | KDD-code-2, CD-Security-14 | Yes |
| WP-3 | [10.1-1], [10.1-2], [10.1-3], [10.2-1], [10.3-1], [10.3-2], [10.3-3], [10.4-1], CD-ConfigDB-4, CD-ConfigDB-5, CD-ConfigDB-6, CD-ConfigDB-7 | Yes |
| WP-4 | CD-ConfigDB-3 | Yes |
| WP-5 | CD-ConfigDB-8 | Yes |
| WP-6 | [9.1-1], KDD-sec-1, KDD-sec-3, CD-Security-1, CD-Security-2, CD-Security-3, CD-Security-4, CD-Security-9 | Yes |
| WP-7 | [9.1-2], KDD-sec-2, KDD-sec-3, CD-Security-5, CD-Security-6, CD-Security-7, CD-Security-8, CD-Security-9, CD-Security-10 | Yes |
| WP-8 | [9.2-1], [9.2-2], [9.2-3], CD-Security-11 | Yes |
| WP-9 | [9.3-1], [9.3-2], [9.3-3], [9.4-1], [9.4-2], [9.4-3], CD-Security-12, CD-Security-13 | Yes |
| WP-10 | KDD-sec-4, CD-CentralUI-4 | Yes |
| WP-11 | REQ-HOST-4, KDD-code-5, CD-Host-1 | Yes |
| WP-12 | REQ-HOST-4a, KDD-code-7, CD-Host-2 | Yes |
| WP-13 | REQ-HOST-6, CD-Host-4 | Yes |
| WP-14 | REQ-HOST-8, CD-Host-6 | Yes |
| WP-15 | REQ-HOST-8a, CD-Host-7 | Yes |
| WP-16 | REQ-HOST-9, CD-Host-8 | Yes |
| WP-17 | REQ-HOST-5, CD-Host-3 | Yes |
| WP-18 | KDD-ui-1, CD-CentralUI-1, CD-CentralUI-2, CD-Host-5, REQ-HOST-7, CD-Security-14 | Yes |
| WP-19 | [9.1-1], [9.1-2], CD-Security-1 | Yes |
| WP-20 | [9.2-1], [9.3-1], [9.3-2], [9.3-3], CD-Security-12 | Yes |
| WP-21 | KDD-sec-4, CD-CentralUI-3, CD-CentralUI-4, CD-CentralUI-5, CD-Security-10 | Yes |
| WP-22 | [9.1-1], [9.1-2], [9.2-1], [9.2-3], [9.4-3], [10.4-1], KDD-sec-3, REQ-HOST-4, REQ-HOST-4a | Yes |

**Reverse check result**: All 22 work packages trace to at least one requirement or design constraint. **0 untraceable work packages.**

### Split-Section Check

- **Section 9.1**: Bullets [9.1-1] and [9.1-2] covered in Phase 1. Bullet [9.1-3] (API key auth) owned by Phase 7. **Complete.**
- **Section 9.2**: Bullets [9.2-1], [9.2-2], [9.2-3] covered in Phase 1. Bullet [9.2-4] (Inbound API per-method auth) owned by Phase 7. **Complete.**
- **Sections 9.3, 9.4, 10.1, 10.2, 10.3, 10.4**: Entirely owned by Phase 1. Not split. **Complete.**

### Negative Requirement Check

| Negative Requirement | Acceptance Criterion | Sufficient? |
|---------------------|---------------------|-------------|
| CD-Security-2: Unencrypted LDAP not permitted | WP-6 AC#2: configuration validation rejects port 389 without TLS. WP-11: startup validation. Test strategy: negative test. | Yes — tests both config validation and runtime rejection. |
| CD-Security-3: No local user store | WP-6 AC#5: no local store. Test strategy: LDAP down → login fails, no fallback. | Yes — proves no fallback exists. |
| CD-Security-4: No Windows Integrated Auth | WP-6 AC#6: no Kerberos/NTLM. Test strategy: no Negotiate headers accepted. | Yes. |
| CD-Security-13: Unauthorized actions not audited | WP-9 AC#8: unauthorized → no audit entry. Test strategy: explicit negative test. | Yes — verifies absence of audit entry on unauthorized action. |
| [10.1-2]: Audit entries never modified or deleted | WP-3 AC#5: no update/delete operations. Test strategy: verify no mutation APIs on audit entries. | Yes — structural verification. |
| CD-ConfigDB-3 (negative): No concurrency on templates | WP-4 AC#4 + AC#6: template concurrent update succeeds (last write wins). | Yes — proves the absence of concurrency control on templates. |

**Negative requirement check result**: All 6 negative requirements have acceptance criteria that would catch violations. **Pass.**

### Verification Result

**Orphan check: PASS** — 0 orphaned requirements, 0 untraceable work packages, 0 split-section gaps, all negative requirements verified.

---

## Codex MCP Review

**Model**: gpt-5.4
**Result**: Pass with dismissed findings.

The Codex review was conducted against a summary of the plan (not the full text). Codex identified 26 findings across three categories. Analysis of each:

### Findings Dismissed as False Positives (reviewed against full plan text)

Most findings arose because the Codex review operated on a condensed summary that omitted the detailed acceptance criteria present in the full plan. Specifically:

1. **"No WP covers Admin/Design/Deployment management capabilities"** — Dismissed. WP-9 AC#9-11 explicitly define the permission sets for each role. Phase 1 establishes the authorization infrastructure; the actual workflows (template authoring, instance management, etc.) are built in their respective phases. The role definitions and policies are fully covered.

2. **"No WP ties audit coverage to enumerated action families"** — Dismissed. WP-3 builds the generic IAuditService infrastructure. [10.2-1] is correctly mapped to WP-3. The note in the Requirements Checklist explicitly states: "In Phase 1, the only auditable actions are security/admin changes. Other components will use IAuditService in their respective phases." Audit scope for each action family is enforced when the calling component is built.

3. **"No local user store / No credentials cached locally not covered"** — Dismissed. WP-6 AC#5 ("No local user store — all identity from AD") and AC#7 ("No credentials cached locally") cover this. Negative test in Test Strategy confirms.

4. **"No Windows Integrated Auth not covered"** — Dismissed. WP-6 AC#6 ("No Kerberos/NTLM — direct LDAP bind only") covers this. Negative test confirms.

5. **"JWT claims: display name, username not covered"** — Dismissed. WP-7 AC#2 explicitly lists "user display name, username, roles list, site-scoped site IDs" in JWT claims.

6. **"LDAP failure/recovery not covered"** — Dismissed. WP-6 AC#8 and WP-7 AC#8-9 cover LDAP failure handling. WP-22 AC#4 is an integration test for this scenario.

7. **"No server-side session state not covered"** — Dismissed. WP-7 AC#10 ("JWT is self-contained — no server-side session state. Load-balancer compatible") covers this.

8. **"Permission enforcement on every endpoint not covered"** — Dismissed. WP-9 AC#6 ("Permission enforcement on every API endpoint and UI action") and WP-20 AC#8 cover this.

9. **"Unauthorized actions not audited not covered"** — Dismissed. WP-9 AC#8 covers this. Negative test in Test Strategy confirms.

10. **"Group mappings in config DB, managed via Central UI not covered"** — Dismissed. WP-2 (ISecurityRepository for LDAP group mappings) and WP-18 AC#7 ("LDAP group mapping management page") cover this.

11. **"Scoped DbContext registration not covered"** — Dismissed. WP-1 AC#4 ("DbContext registered as scoped service in DI container") covers this.

12. **"Instance lifecycle missing from WP-4"** — Dismissed. WP-4 AC#2 ("Instance lifecycle state (enabled/disabled) has a concurrency token") covers this.

13. **"Audit schema columns not covered"** — Dismissed. WP-3 AC#2 lists the exact schema.

14. **"Indexes not covered"** — Dismissed. WP-1 AC#3 ("Audit log entries table has indexes on Timestamp, User, EntityType, EntityId, and Action") covers this.

15. **"Connection strings from DatabaseOptions not covered"** — Dismissed. WP-1 AC#9 covers this.

16. **"Production schema validation not covered"** — Dismissed. WP-1 AC#8 and WP-11 (startup validation) cover this.

17. **"REQ-HOST-7 not in work packages"** — Dismissed. WP-18 traces REQ-HOST-7 and AC#8 verifies site nodes use generic Host.

18. **"REQ-HOST-8 enrichment not covered"** — Dismissed. WP-14 AC#3 lists SiteId, NodeHostname, NodeRole enrichment.

19. **"Bootstrap CSS / no third-party frameworks not covered"** — Dismissed. WP-18 AC#3 ("Bootstrap CSS for styling — no third-party component frameworks") covers this.

20. **"Debug streams lost on failover not covered"** — Dismissed. WP-21 AC#4 covers this explicitly.

21. **"HMAC-SHA256 not covered"** — Dismissed. WP-7 AC#1 ("JWT signed with HMAC-SHA256") covers this.

22. **"POCOs in Commons constraint not covered"** — Dismissed. WP-1 traces KDD-code-1 and AC#1 references "Commons POCO entities."

23. **"Options pattern not covered"** — Dismissed. WP-11 traces KDD-code-5 and validates all options.

24. **"EF migration strategy not covered"** — Dismissed. WP-1 AC#7-8 cover dev auto-apply and prod validation.

25. **"Section 9.3 claimed as covered but permissions not verifiable"** — Dismissed. Phase 1 defines the authorization policies (WP-9). The actual permission checks are exercised when each component's workflows are built. The policies define the permission boundaries; enforcement is cross-cutting.

26. **"WP-4 conflicts with docs/requirements/Component-ConfigurationDatabase (instance lifecycle missing)"** — Dismissed. WP-4 AC#2 includes instance lifecycle.

### Conclusion

All 26 Codex findings were reviewed against the full plan text. All were dismissed as false positives caused by the review operating against a condensed summary rather than the complete acceptance criteria. No plan changes required.

**Codex review outcome: Pass (all findings dismissed with rationale).**
