Phase 6.2 Stream A - LdapGroupRoleMapping entity + migration + CRUD service #84

Merged
dohertj2 merged 1 commits from phase-6-2-stream-a-ldap-role-mapping into v2 2026-04-19 09:20:07 -04:00
Owner

Stream A.1-A.2 per phase-6-2-authorization-runtime.md. Seed migration (A.3) deferred until production LDAP DNs are finalised.

Summary

  • New AdminRole enum {ConfigViewer, ConfigEditor, FleetAdmin}.
  • New LdapGroupRoleMapping entity: Id, LdapGroup (512), Role, ClusterId (nullable FK to ServerCluster), IsSystemWide, CreatedAtUtc, Notes. Unique (LdapGroup, ClusterId) + hot-path IX on LdapGroup.
  • EF migration generated via dotnet ef.
  • ILdapGroupRoleMappingService + EF impl. Control-plane-only surface per decision #150; the OPC UA data-path evaluator MUST NOT depend on it. GetByGroupsAsync is the hot-path sign-in lookup.
  • Write-time invariant: exactly one of (ClusterId populated, IsSystemWide=true). Violations throw InvalidLdapGroupRoleMappingException.

Test plan

  • 9 new unit tests: Create sets Id+CreatedAtUtc; Create rejects empty group / systemwide+clusterid / non-systemwide without clusterid; GetByGroups filters correctly; empty input returns empty; ListAll orders group/cluster; Delete removes; Delete of unknown id is no-op.
  • SchemaComplianceTests updated to expect the new table.
  • Full solution dotnet test: 1051 passing (was 1042, +9).

🤖 Generated with Claude Code

Stream A.1-A.2 per phase-6-2-authorization-runtime.md. Seed migration (A.3) deferred until production LDAP DNs are finalised. ## Summary - New `AdminRole` enum {ConfigViewer, ConfigEditor, FleetAdmin}. - New `LdapGroupRoleMapping` entity: Id, LdapGroup (512), Role, ClusterId (nullable FK to ServerCluster), IsSystemWide, CreatedAtUtc, Notes. Unique (LdapGroup, ClusterId) + hot-path IX on LdapGroup. - EF migration generated via `dotnet ef`. - `ILdapGroupRoleMappingService` + EF impl. Control-plane-only surface per decision #150; the OPC UA data-path evaluator MUST NOT depend on it. GetByGroupsAsync is the hot-path sign-in lookup. - Write-time invariant: exactly one of (ClusterId populated, IsSystemWide=true). Violations throw `InvalidLdapGroupRoleMappingException`. ## Test plan - [x] 9 new unit tests: Create sets Id+CreatedAtUtc; Create rejects empty group / systemwide+clusterid / non-systemwide without clusterid; GetByGroups filters correctly; empty input returns empty; ListAll orders group/cluster; Delete removes; Delete of unknown id is no-op. - [x] SchemaComplianceTests updated to expect the new table. - [x] Full solution `dotnet test`: 1051 passing (was 1042, +9). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dohertj2 added 1 commit 2026-04-19 09:19:55 -04:00
Stream A.1-A.2 per docs/v2/implementation/phase-6-2-authorization-runtime.md.
Seed-data migration (A.3) is a separate follow-up once production LDAP group
DNs are finalised; until then CRUD via the Admin UI handles the fleet set up.

Configuration:
- New AdminRole enum {ConfigViewer, ConfigEditor, FleetAdmin} — string-stored.
- New LdapGroupRoleMapping entity with Id (surrogate PK), LdapGroup (512 chars),
  Role (AdminRole enum), ClusterId (nullable, FK to ServerCluster), IsSystemWide,
  CreatedAtUtc, Notes.
- EF config: UX_LdapGroupRoleMapping_Group_Cluster unique index on
  (LdapGroup, ClusterId) + IX_LdapGroupRoleMapping_Group hot-path index on
  LdapGroup for sign-in lookups. Cluster FK cascades on cluster delete.
- Migration 20260419_..._AddLdapGroupRoleMapping generated via `dotnet ef`.

Configuration.Services:
- ILdapGroupRoleMappingService — CRUD surface. Declared as control-plane only
  per decision #150; the OPC UA data-path evaluator must NOT depend on this
  interface (Phase 6.2 compliance check on control/data-plane separation).
  GetByGroupsAsync is the hot-path sign-in lookup.
- LdapGroupRoleMappingService (EF Core impl) enforces the write-time invariant
  "exactly one of (ClusterId populated, IsSystemWide=true)" and surfaces
  InvalidLdapGroupRoleMappingException on violation. Create auto-populates Id
  + CreatedAtUtc when omitted.

Tests (9 new, all pass) in Configuration.Tests:
- Create sets Id + CreatedAtUtc.
- Create rejects empty LdapGroup.
- Create rejects IsSystemWide=true with populated ClusterId.
- Create rejects IsSystemWide=false with null ClusterId.
- GetByGroupsAsync returns matching rows only.
- GetByGroupsAsync with empty input returns empty (no full-table scan).
- ListAllAsync orders by group then cluster.
- Delete removes the target row.
- Delete of unknown id is a no-op.

Microsoft.EntityFrameworkCore.InMemory 10.0.0 added to Configuration.Tests for
the service-level tests (schema-compliance tests still use the live SQL
fixture).

SchemaComplianceTests updated to expect the new LdapGroupRoleMapping table.

Full solution dotnet test: 1051 passing (baseline 906, Phase 6.1 shipped at
1042, Phase 6.2 Stream A adds 9 = 1051). Pre-existing Client.CLI Subscribe
flake unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit 13a231b7ad into v2 2026-04-19 09:20:07 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#84