docs(mesh-phase4): add Task 1b (driver-only LDAP mapper) discovered during Task 1

Gating ConfigDb on hasAdmin exposed a 6th driver-side ConfigDb consumer the
§6.1 audit missed: OtOpcUaGroupRoleMapper's ILdapGroupRoleMappingService dep.
Task 7 gains a fused-node ordering pin (Task 1b review gap).

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
Joseph Doherty
2026-07-23 11:48:45 -04:00
parent 833e45db9c
commit 5c40908a55
2 changed files with 157 additions and 13 deletions
@@ -129,6 +129,49 @@ node: "driver-only node — no ConfigDb registered; config via ConfigSource:Mode
---
## Task 1b: driver-only `ILdapGroupRoleMappingService` — empty (appsettings-only group→role) — DISCOVERED during Task 1
**Classification:** small
**Estimated implement time:** ~4 min
**Parallelizable with:** Task 5
**Files:**
- Create: `src/Server/ZB.MOM.WW.OtOpcUa.Security/Ldap/NullLdapGroupRoleMappingService.cs` (or a Host-side
driver-only impl — place it beside `OtOpcUaGroupRoleMapper`)
- Modify: `src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs:317-327` (the `hasDriver` auth block — register the
empty service with `TryAddScoped` so a fused node keeps the real EF one; fix the now-false comment at
322-323)
- Test: `tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/` (or wherever `OtOpcUaGroupRoleMapper` is tested)
**Context — a 6th ConfigDb consumer the §6.1 audit missed.** `OtOpcUaGroupRoleMapper` (the OPC UA
data-path group→role mapper, `IGroupRoleMapper<string>`, registered at `Program.cs:327`) merges the
appsettings `Security:Ldap:GroupToRole` baseline (always available) with **system-wide DB grants** from
`ILdapGroupRoleMappingService` — which is registered **only** inside `AddOtOpcUaConfigDb`. Task 1 gated
that on `hasAdmin`, so a driver-only node now cannot resolve `IGroupRoleMapper<string>` at OPC UA login
(lazy scoped resolve — `Build()` does not surface it; the auth path does). `LdapGroupRoleMapping` rows are
**not** in the deployment artifact (`ConfigComposer` never composes them), so driver nodes never received
DB grants via config in the first place. The honest fix: a driver-only `ILdapGroupRoleMappingService` whose
`GetByGroupsAsync` returns empty (CRUD methods throw `NotSupportedException` — never called with no AdminUI),
registered via `TryAddScoped` in the `hasDriver` block so the fused node's real EF impl still wins.
**Behavior reduction to document (Task 8):** DB-backed LDAP group→role grants (the AdminUI RoleGrants page)
apply only where ConfigDb is present (admin/fused nodes); driver-only site nodes map groups→roles from
`Security:Ldap:GroupToRole` appsettings only.
**Step 1: Write the failing test** — resolve `IGroupRoleMapper<string>` in a driver-only service graph and
call `MapAsync(["some-group"])` → returns the appsettings baseline with no throw (today it throws:
`ILdapGroupRoleMappingService` unresolvable).
**Step 2:** Run — fails.
**Step 3: Implement** the empty service + `TryAddScoped` registration in the `hasDriver` block; correct the
`Program.cs:322-323` comment to state the driver-only node uses the empty impl and why.
**Step 4:** Run tests + build.
**Step 5: Commit**`fix(mesh-phase4): driver-only nodes map LDAP groups from appsettings (no ConfigDb grants)`.
---
## Task 2: `DbHealthProbeActor` — do not spawn on driver-only; `DbReachable=true` constant
**Classification:** high-risk
@@ -346,7 +389,12 @@ that the driver-only node never serves login (it hosts no AdminUI — see the to
**Step 1: Write the failing test** — a "driver-only DI graph resolves nothing ConfigDb-bound" test:
build the `hasDriver && !hasAdmin` service provider and assert
`GetService<IDbContextFactory<OtOpcUaConfigDbContext>>()` is null AND that spawning the runtime actors
does not throw.
does not throw. **Also pin the Task-1b fused-node ordering** (Task 1b review gap): build the
`hasAdmin && hasDriver` graph through the same `AddOtOpcUaConfigDb` + `hasDriver` TryAdd sequence and assert
`GetService<ILdapGroupRoleMappingService>()` resolves the **real** `LdapGroupRoleMappingService`, not the
`Null…` — so a later reorder of the role blocks (or switching `AddOtOpcUaConfigDb` to a TryAdd) fails a
red test instead of silently stripping DB-backed role grants from central. Also resolve
`IGroupRoleMapper<string>` in a driver-only scope to catch the lazy auth-path break Task 1b fixed.
**Step 2:** Run — fix whatever it catches.