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>
Parent: #209. Follow-up to #210 (Modbus). Registers the remaining three
non-Galaxy driver factories so a Config DB `DriverType` in
{`AbCip`, `S7`, `AbLegacy`} actually boots a live driver instead of
being silently skipped by DriverInstanceBootstrapper.
Each factory follows the same shape as ModbusDriverFactoryExtensions +
the existing Galaxy + FOCAS patterns:
- Static `Register(DriverFactoryRegistry)` entry point.
- Internal `CreateInstance(driverInstanceId, driverConfigJson)` —
deserialises a DTO, strict-parses enum fields (fail-fast with an
explicit "expected one of" list), composes the driver's options object,
returns a new driver.
- DriverType keys: `"AbCip"`, `"S7"`, `"AbLegacy"` (case-insensitive at
the registry layer).
DTO surfaces cover every option the respective driver's Options class
exposes — devices, tags, probe, timeouts, per-driver quirks
(AbCip `EnableControllerBrowse` / `EnableAlarmProjection`, S7 Rack/Slot/
CpuType, AbLegacy PlcFamily).
Seed SQL (mirrors `seed-modbus-smoke.sql` shape):
- `seed-abcip-smoke.sql` — `abcip-smoke` cluster + ControlLogix device +
`TestDINT:DInt` tag, pointing at the ab_server compose fixture
(`ab://127.0.0.1:44818/1,0`).
- `seed-s7-smoke.sql` — `s7-smoke` cluster + S71500 CPU + `DB1.DBW0:Int16`
tag at the python-snap7 fixture (`127.0.0.1:1102`, non-priv port).
- `seed-ablegacy-smoke.sql` — `ablegacy-smoke` cluster + SLC 500 + `N7:5`
tag. Hardware-gated per #222; placeholder gateway to be replaced with
real SLC/MicroLogix/PLC-5/RSEmulate before running.
Build plumbing:
- Each driver project now ProjectReferences `Core` (was
`Core.Abstractions`-only). `DriverFactoryRegistry` lives in `Core.Hosting`
so the factory extensions can't compile without it. Matches the FOCAS +
Galaxy.Proxy reference shape.
- `Server.csproj` adds the three new driver ProjectReferences so Program.cs
resolves the symbols at compile-time + ships the assemblies at runtime.
Full-solution build: 0 errors, 334 pre-existing xUnit1051 warnings only.
Live boot verification of all four (Modbus + these three) happens in the
exit-gate PR — factories + seeds are pre-conditions and are being
shipped first so the exit-gate PR can scope to "does the server publish
the expected NodeIds + does the e2e script pass."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>