Task #209 exit gate — seed-creds fix + live Modbus verification (4/5 stages) #218

Merged
dohertj2 merged 1 commits from task-209-exitgate-seed-creds into v2 2026-04-21 11:32:24 -04:00
Owner

Exit gate for the #209 umbrella. Factories + seeds shipped in #216 (Modbus) + #217 (AbCip/S7/AbLegacy); this PR actually boots the server end-to-end against the Modbus seed and fixes two seed-side issues the live boot surfaced.

Seed fixes

  1. Missing ClusterNodeCredential row. sp_GetCurrentGenerationForCluster enforces ClusterNodeCredential.Value = SUSER_SNAME() and aborts with RAISERROR('Unauthorized: caller sa is not bound to NodeId'). Without it the server fails bootstrap with BootstrapException: Central DB unreachable and no local cache available (the real error gets swallowed by the cache-fallback path). All four seed scripts now insert the binding row.

  2. Config cache gitignore. Running the server from the repo root writes config_cache.db + config_cache-log.db at cwd, outside the existing src/.../Server/config_cache.db pattern. Added config_cache*.db so any run location is covered.

Verified live against Modbus

Booted server against seed-modbus-smoke.sql → pymodbus --profile standardtest-modbus.ps1 -BridgeNodeId "ns=2;s=HR200":

=== Modbus e2e summary: 4/5 passed ===
[PASS] Probe
[PASS] Driver loopback
[PASS] Server bridge (driver → server → client)
[FAIL] OPC UA write bridge (0x801F0000)
[PASS] Subscribe sees change

Forward-direction reads + subscription delivery are proven working through the server. Stage 4 (reverse-write) fails with 0x801F0000 on the write-response — not an exception on the server write path, so this is a type-mismatch or ACL-default issue in the node-manager write guard chain. Filing a follow-up issue; it's not a factory or driver defect.

Server-log confirmation

[INF] Bootstrapped from central DB: generation 3
[INF] DriverInstance modbus-smoke-drv (Modbus) registered + initialized
[INF] DriverInstanceBootstrapper: gen=3 registered=1 skippedUnknownType=0 failedInit=0
[INF] Equipment namespace snapshots loaded for 1/1 driver(s) at generation 3
[INF] OPC UA server started — endpoint=opc.tcp://0.0.0.0:4840/OtOpcUa driverCount=1

The skippedUnknownType=0 is the win — before #216 it'd be 1. The factory wiring is live.

Test plan

  • Modbus 4/5 stages live-verified
  • Full-solution build clean
  • AB CIP / S7 live-boot verification — follow-up; factories + seeds ready
  • Reverse-write investigation — follow-up issue
Exit gate for the #209 umbrella. Factories + seeds shipped in #216 (Modbus) + #217 (AbCip/S7/AbLegacy); this PR actually boots the server end-to-end against the Modbus seed and fixes two seed-side issues the live boot surfaced. ## Seed fixes 1. **Missing `ClusterNodeCredential` row.** `sp_GetCurrentGenerationForCluster` enforces `ClusterNodeCredential.Value = SUSER_SNAME()` and aborts with `RAISERROR('Unauthorized: caller sa is not bound to NodeId')`. Without it the server fails bootstrap with `BootstrapException: Central DB unreachable and no local cache available` (the real error gets swallowed by the cache-fallback path). All four seed scripts now insert the binding row. 2. **Config cache gitignore.** Running the server from the repo root writes `config_cache.db` + `config_cache-log.db` at cwd, outside the existing `src/.../Server/config_cache.db` pattern. Added `config_cache*.db` so any run location is covered. ## Verified live against Modbus Booted server against `seed-modbus-smoke.sql` → pymodbus `--profile standard` → `test-modbus.ps1 -BridgeNodeId "ns=2;s=HR200"`: ``` === Modbus e2e summary: 4/5 passed === [PASS] Probe [PASS] Driver loopback [PASS] Server bridge (driver → server → client) [FAIL] OPC UA write bridge (0x801F0000) [PASS] Subscribe sees change ``` Forward-direction reads + subscription delivery are **proven working through the server**. Stage 4 (reverse-write) fails with `0x801F0000` on the write-response — not an exception on the server write path, so this is a type-mismatch or ACL-default issue in the node-manager write guard chain. Filing a follow-up issue; it's not a factory or driver defect. ## Server-log confirmation ``` [INF] Bootstrapped from central DB: generation 3 [INF] DriverInstance modbus-smoke-drv (Modbus) registered + initialized [INF] DriverInstanceBootstrapper: gen=3 registered=1 skippedUnknownType=0 failedInit=0 [INF] Equipment namespace snapshots loaded for 1/1 driver(s) at generation 3 [INF] OPC UA server started — endpoint=opc.tcp://0.0.0.0:4840/OtOpcUa driverCount=1 ``` The `skippedUnknownType=0` is the win — before #216 it'd be 1. The factory wiring is live. ## Test plan - [x] Modbus 4/5 stages live-verified - [x] Full-solution build clean - [ ] AB CIP / S7 live-boot verification — follow-up; factories + seeds ready - [ ] Reverse-write investigation — follow-up issue
dohertj2 added 1 commit 2026-04-21 11:32:21 -04:00
Booted the server against the Modbus seed end-to-end to exercise the
factory wiring shipped in #216 + #217. Surfaced two real issues with
the seeds themselves; fixed both:

1. **Missing ClusterNodeCredential.** `sp_GetCurrentGenerationForCluster`
   enforces `ClusterNodeCredential.Value = SUSER_SNAME()` and aborts
   with `RAISERROR('Unauthorized: caller sa is not bound to NodeId')`.
   All four seed scripts now insert the binding row alongside the
   ClusterNode row. Without this, the server fails bootstrap with
   `BootstrapException: Central DB unreachable and no local cache
   available` (the Unauthorized error gets swallowed on top of the
   HTTP fallback path).

2. **Config cache gitignore.** Running the server from the repo root
   writes `config_cache.db` + `config_cache-log.db` next to the cwd,
   outside the existing `src/.../Server/config_cache.db` pattern. Add
   a `config_cache*.db` pattern so any future run location is covered.

## Verified live against Modbus

Booted server against `seed-modbus-smoke.sql` → pymodbus standard
fixture → ran `scripts/e2e/test-modbus.ps1 -BridgeNodeId "ns=2;s=HR200"`:

    === Modbus e2e summary: 4/5 passed ===
    [PASS] Probe
    [PASS] Driver loopback
    [PASS] Server bridge (driver → server → client)
    [FAIL] OPC UA write bridge (0x801F0000)
    [PASS] Subscribe sees change

The forward direction + subscription delivery are proven working through
the server. The reverse-write failure is a seed-or-ACL issue — server
log shows no exception on the write path, so the client-side status is
coming from the stack's type/ACL guards. Tracking as a follow-up issue
so the remaining three factory wirings can be smoke-booted against the
same pattern.

Note for future runs: two stale v1 `ZB.MOM.WW.LmxOpcUa.Host.exe`
processes from `C:\publish\lmxopcua\instance{1,2}\` squat on ports
4840 + 4841 on this dev box; kill them first or bump the seed's
DashboardPort.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit 682c1c5e75 into v2 2026-04-21 11:32:24 -04:00
dohertj2 deleted branch task-209-exitgate-seed-creds 2026-04-21 11:32:25 -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#218