chore(docker-dev): site-b-2 is Cluster:Roles-only, site-a-2 pins a miscased Cluster:Roles entry (archreview 01/S-12, 01/C-9)

T11: site-b-2 drops OTOPCUA_ROLES entirely — Cluster:Roles is its sole role source, the rig's
only node that can exhibit the two-role-authority split (01/S-12). Expected to fail every
deploy on a pre-R3-04 build.

T12: site-a-2 pins Cluster__Roles__0="Driver" (capital D) as the standing 01/C-9 case-mismatch
pin, keeping cluster-SITE-A verbatim. Pre-fix this un-elects the SITE-A Primary.

Combined into one commit (deviation from the plan's two separate T11/T12 commits): the header
comment block documenting all six nodes' role sources interleaves both nodes on adjacent lines,
so a clean split was not practical. Recorded in the plan's Execution notes.

Compose edits only — the rig is NOT brought up as part of this change.
This commit is contained in:
Joseph Doherty
2026-08-20 10:32:18 -04:00
parent b8d2d50006
commit 985de0b6ad
3 changed files with 40 additions and 8 deletions
+21 -2
View File
@@ -27,17 +27,36 @@ Authentication uses the **shared GLAuth** on the Linux Docker host at `10.100.0.
| Service | Roles | Ports |
|---|---|---|
| `site-a-1` | `OTOPCUA_ROLES=driver`, joins the single mesh | host `4842` → container `4840` |
| `site-a-2` | `OTOPCUA_ROLES=driver`, joins the single mesh | host `4843` → container `4840` |
| `site-a-2` | `OTOPCUA_ROLES=driver` **+** a deliberately miscased `Cluster__Roles__0=Driver` (see below) | host `4843` → container `4840` |
### Site B nodes — driver-only (SITE-B cluster)
| Service | Roles | Ports |
|---|---|---|
| `site-b-1` | `OTOPCUA_ROLES=driver`, joins the single mesh | host `4844` → container `4840` |
| `site-b-2` | `OTOPCUA_ROLES=driver`, joins the single mesh | host `4845` → container `4840` |
| `site-b-2` | **`Cluster:Roles`-ONLY** — no `OTOPCUA_ROLES` at all (see below) | host `4845` → container `4840` |
Site nodes serve no UI and authenticate no users. The central cluster manages and deploys to them over the shared Akka mesh. All six nodes bind Akka remoting to port `4053` inside their own network namespace; `PublicHostname` for each matches its Compose service name.
### Role-authority standing pins (archreview R3-04)
Every node above sets **both** `OTOPCUA_ROLES` and the matching `Cluster__Roles__*` env vars,
consistently — which is exactly why neither R3-04 finding (01/S-12, 01/C-9) could reproduce on this
rig as originally configured. Two nodes deliberately deviate, as standing pins:
- **`site-b-2` is `Cluster:Roles`-only.** No `OTOPCUA_ROLES` is set at all; `Cluster__Roles__0=driver`
/ `Cluster__Roles__1=cluster-SITE-B` are its sole role source. **Expected to FAIL every deploy on a
pre-R3-04 build** (`Program.cs` read `OTOPCUA_ROLES` only, so this node's host-side conditional
wiring mounted no driver stack at all — no LocalDb, no OPC UA endpoint, no ConfigDb, the deny-all
`NullOpcUaUserAuthenticator` — while its Akka gossip still looked healthy) — the exact outage
01/S-12 describes. Post-fix it behaves identically to `site-b-1`.
- **`site-a-2` pins a miscased `Cluster__Roles__0=Driver`** (capital D; `cluster-SITE-A` keeps its
own spelling verbatim, by design). Post-fix `EffectiveRoles` normalizes it silently and this node
behaves identically to `site-a-1`. Pre-fix, all four role-scoped validators (`SplitTopologyTransportValidator`
and three siblings) compared roles case-sensitively, so three of the four silently skipped every
driver-scoped boot-time rule for this node, and `site-a` elected **no Primary at all**
(`RedundancyStateActor.SelectDriverPrimary` matches roles ordinally against gossip).
## Multi-tenancy
All six host nodes write to the same `OtOpcUa` ConfigDb. The `ServerCluster` table differentiates the three logical clusters: each maps to one row, and each `ClusterNode` row's `ClusterId` ties the runtime node back to its owning cluster scope.