- deferment.md: node ACLs no longer authored-but-inert (#520 shipped through stage 7, opt-in Security:Acl:Enabled); failure-visibility trio mostly closed (01/S-1 + 03/S4 fixed, 06/S-1 partial); bulkhead + WriteIdempotentAttribute closed by R2-02; Sql Postgres+Oracle dialects shipped (d54f3c83); akka-hosting partials verified superseded - archreview/00-OVERALL.md: staleness banner on the carried-open block (04/C-1→R2-05, 03/S2+S3→R2-08, 03/P1→R2-07, driver fleet→R2-09/R2-11/#457, hygiene→R2-12, trio per above; what genuinely remains from theme 4) - archreview/plans/STATUS.md: carved out the true exceptions to "every Round-2 live gate GREEN" (R2-03 T11/T12 deferred-live, R2-10 T10 breaker-OPEN never forced); #459 closed by mesh Phase 7 drill D4 (c50ebcf7) - archreview/plans/INTEGRATION-SWEEP-STATUS.md: "follow-ups unstarted" → all 7 shipped as PRs #446–#452 - archreview/06-gateway-integrations.md: 06/S-1 STILL OPEN → PARTIAL (fail-closed advise + unconfirmed meter; residual cross-repo) - 2026-07-31-apikey-acl-enforcement-design.md: status → implemented through stage 7, #520 CLOSED 2026-08-01 - 2026-07-21-per-cluster-mesh-design.md: "Phases 1–7 not started" → PROGRAM COMPLETE 2026-07-24 (Phase 7c50ebcf7) - 2026-07-20-localdb-phase2-live-gate.md: merged as 06b5d81b; open design question shipped by mesh Phase 6 (checks 3/4 still never re-run on the three-mesh topology) - 2026-07-20-localdb-phase1-progress.md: Tasks 10–16 REMAIN → Phase 1 complete, live-gated 8/8, mergedc957db52- 2026-08-06-clusterclient-to-grpc-tracking.md: defect 4 OPEN → fixed byc0475f2d+e54883a8, merged @b360239e- 2026-08-06-clusterclient-to-grpc.md.tasks.json: Task 15 pending → completed (Phase 4 gate PASS 8/8 @0b194003, recordd0ac6fc5) - 2026-07-22-mesh-phase2-live-gate.md: DriverStatusPanel orphan → wired into /hosts by #546 (9420f8ef) - 2026-07-21-issue-485-live-gate.md: pointer from the "NOT fixed here" finding to the #486 fix (DriverHostActor.cs:1696-1717 + FetchAndCache mirror) - 2026-08-05-sql-postgres-oracle.md: chrome/AdminUI gate not deferred — PASSED 2026-08-05, found + fixed the Test-Connect provider gate (4e461951) - v2-ga-lab-gates-plan.md / v2-release-readiness.md / live-hardware-validation-runbooks.md: FOCAS live-CNC smoke #54 RESOLVED on a real 31i-B 2026-06-25 (cnc_rdparam caveat preserved) - Mqtt-Test-Fixture.md: STATE/DCMD "not implemented" → shipped + live-gated (#5082a7d1167, #511857a10b4) - 2026-08-01-continuous-historization-live-gate.md: poison-tag FIFO pin "filed, not fixed" → #538 fixed same day (e7fbe977, gate 4bdce910; Capacity default now 500k) - 2026-05-26-akka-hosting-alignment-plan.md.tasks.json: F8/F9/F10/F13/F14 partial → completed, superseded by the v3 address-space rework (Phase7Composer/ScriptedAlarmActor deleted; evaluators/SDK sinks/security policies production-wired) Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
12 KiB
Sql Postgres + Oracle providers Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (opus implementers per user instruction).
Goal: Make SqlProvider.Postgres and SqlProvider.Oracle working backends via managed
ADO.NET providers (Npgsql, Oracle.ManagedDataAccess.Core).
Architecture: See docs/plans/2026-08-05-sql-postgres-oracle-design.md (this branch). Two new
dialects in the existing .Dialect project; dialect-owned parameter markers/bind-names +
ConfigureCommand (Oracle BindByName); provider gates widened; AdminUI dropdown; env-gated docker
integration suites. Chrome/AdminUI live gate is DEFERRED to the operator. Update 2026-08-07
(truth sweep): the chrome/AdminUI gate RAN and PASSED 2026-08-05 — and found a real defect (Test
Connect was SqlServer-gated for both new providers), fixed in merge 4e461951; see
2026-08-05-sql-postgres-oracle-design.md §4.3 record.
Tech Stack: .NET 10, central package management (Directory.Packages.props), xUnit v3 +
Shouldly, SQLite falsifiability control.
Worktree: /Users/dohertj2/Desktop/OtOpcUa/.claude/worktrees/sql-multi-provider
(branch feature/sql-multi-provider). All paths below relative to it.
Task 0: Package pins
Classification: trivial Estimated implement time: ~3 min Parallelizable with: Task 1, Task 5
Files:
- Modify:
Directory.Packages.props - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect.csproj
Add PackageVersion entries for Npgsql (alphabetical slot after MQTTnet) and
Oracle.ManagedDataAccess.Core (after the OPCFoundation.* block) — latest stable on the feed
(dotnet package search <id> --exact-match to confirm; Npgsql 9.x line, ODP.NET Core 23.x line;
both must target net8.0+). Add both PackageReferences (version-less) to the .Dialect csproj
under the existing Microsoft.Data.SqlClient reference, extending that ItemGroup's
rationale comment. dotnet restore ZB.MOM.WW.OtOpcUa.slnx must come back clean WITH the audit
active — if NU1903 fires on a transitive dep, apply the surgical direct-pin pattern documented at
Directory.Packages.props:118-145, never a suppress. Commit
build(sql): pin Npgsql + Oracle.ManagedDataAccess.Core (managed providers only).
Task 1: Dialect-owned parameter naming + ConfigureCommand
Classification: high-risk Estimated implement time: ~5 min Parallelizable with: Task 0, Task 5
Files:
- Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/ISqlDialect.cs - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/SqlServerDialect.cs - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlGroupPlanner.cs(:21-25 consts, :143, :151, :188) - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlQueryPlan.cs(bind names alongside markers if the plan record needs both) - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlPollReader.cs(:379-405 — ConfigureCommand call + bind-name copy) - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlCatalogLoader.cs(:97-113,Bind:181-188) - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverProbe.cs(ConfigureCommand) - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser/SqlBrowseSession.cs(ConfigureCommand) - Modify:
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqliteDialect.cs(implement new members; keep linked copy in Browser.Tests compiling) - Test:
SqlGroupPlannerTests.cs,SqlPollReaderTests.cs,SqlCatalogGateDriverTests.cs(+ a colon-marker fake dialect in the test tree)
Per design §2 bullet 2. ISqlDialect gains ParameterMarker(string baseName),
ParameterBindName(string baseName), ConfigureCommand(DbCommand); SqlServerDialect returns
"@"+name / "@"+name / no-op — behaviour today must be byte-identical (existing planner
SQL-text assertions must pass untouched; if any needs editing, that is a regression, stop).
Planner takes markers from its dialect; catalog SQL text and loader bind names route through the
dialect so the two can never disagree. TDD: the colon-marker fake proves markers flow (observed
red against the @k consts first). Every CreateCommand site calls ConfigureCommand
immediately after creation. Commit
refactor(sql): dialect-owned parameter markers, bind names and command configuration.
Task 2: PostgresDialect
Classification: standard Estimated implement time: ~5 min Parallelizable with: Task 3, Task 5
Files:
- Create:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/PostgresDialect.cs - Test:
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/PostgresDialectTests.cs(new)
Per design §2 Postgres bullet. Factory => NpgsqlFactory.Instance; Provider => SqlProvider.Postgres; @ markers/bind names; "-quoting ""-escaped, MaxIdentifierLength 63,
same control-char/Unicode-Format rejection discipline as SqlServerDialect.QuoteIdentifier
(:88-114); LivenessSql "SELECT 1"; limit ""/" LIMIT 1"; catalog SQL over
information_schema.schemata/tables/columns excluding pg_catalog + information_schema,
DefaultSchemaSql "SELECT current_schema()" (parameter markers in catalog SQL via the Task 1
members); MapColumnType per design (unknown → the same fallback SqlServerDialect uses — read it
first). Tests mirror SqlServerDialectTests structure: quoting theory, SQL text, type-map theory,
marker members. Commit feat(sql): PostgresDialect (Npgsql).
Task 3: OracleDialect
Classification: standard Estimated implement time: ~5 min Parallelizable with: Task 2, Task 5
Files:
- Create:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Dialect/OracleDialect.cs - Test:
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/OracleDialectTests.cs(new)
Per design §2 Oracle bullet. OracleClientFactory.Instance; : markers, bare bind names;
ConfigureCommand casts to OracleCommand and sets BindByName = true (the test constructs a
real OracleCommand and asserts the flag — this is the load-bearing test of the whole branch;
also assert a foreign DbCommand passes through untouched). Quoting "/"", max 128;
LivenessSql "SELECT 1 FROM DUAL"; limit ""/" FETCH FIRST 1 ROWS ONLY"; catalog SQL:
ALL_USERS schemas, ALL_TABLES ∪ ALL_VIEWS tables, ALL_TAB_COLUMNS columns,
sys_context('userenv','current_schema') default schema — markers via :schema/:table through
the Task 1 members; MapColumnType: NUMBER→Float64 (doc the DeclaredType escape hatch in the
XML doc comment), BINARY_DOUBLE/BINARY_FLOAT, VARCHAR2/NVARCHAR2/CHAR/NCHAR/CLOB,
DATE/TIMESTAMP*, RAW, plus FLOAT/INTEGER synonyms. Commit
feat(sql): OracleDialect (ODP.NET managed, BindByName).
Task 4: Widen the provider gates
Classification: small Estimated implement time: ~4 min Parallelizable with: none
Files:
- Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs(:208-215) - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser/SqlDriverBrowser.cs(:126-127, :146-151 comment) - Modify:
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts/SqlProvider.cs(doc comments) - Test:
SqlDriverFactoryTests.cs(:133-135 → MySql as rejected control; new: Postgres/Oracle construct with their dialects),SqlDriverBrowserTests.cs(:188-196 same flip)
TDD: red first — author {"provider":"Postgres"} and assert construction reaches the
connection-string resolver (or dialect type assertion via the binding) instead of the
not-available throw. Rejection message updated to name the three supported providers. Commit
feat(sql): construct Postgres and Oracle providers; MySql/Odbc stay reserved.
Task 5: AdminUI dropdown + docs
Classification: small Estimated implement time: ~4 min Parallelizable with: Task 1, Task 2, Task 3
Files:
- Modify:
src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/Forms/SqlDriverForm.razor(:18-25) - Modify:
docs/drivers/Sql.md(:15-18, :96 provider row, browse section) - Test:
tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/— extend the existing Sql driver-form model test file (findSqlDriverFormModelTestsor nearest equivalent) with round-trip ofprovider: Postgres/Oracle
Dropdown gains Postgres + Oracle options; helper text → "SQL Server, PostgreSQL and Oracle
are supported; MySql/Odbc are reserved." Sql.md rewritten per design §5 including the Oracle
NUMBER→Float64 note and the 12.2+ floor. Commit
feat(sql-adminui): Postgres + Oracle in the provider dropdown; docs.
Task 6: Integration fixtures + env-gated suites
Classification: standard Estimated implement time: ~5 min Parallelizable with: none
Files:
- Create:
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/Docker/postgres/docker-compose.ymlseed-pg.sql
- Create:
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/Docker/oracle/docker-compose.ymlseed-oracle.sql
- Create:
tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.IntegrationTests/PostgresReadTests.cs,OracleReadTests.cs
Per design §4.2. Compose shapes mirror the existing mssql fixture (Docker/docker-compose.yml:
project label project: lmxopcua, one-shot seed service, healthcheck): postgres:17-alpine
port 15432:5432 env POSTGRES_PASSWORD=${SQL_PG_PASSWORD}; gvenzl/oracle-free:23-slim port
11521:1521 env ORACLE_PASSWORD=${SQL_ORACLE_PASSWORD} (gvenzl images take APP_USER/
APP_USER_PASSWORD for a non-SYS user — use otopcua/the password env). Seeds create one
KeyValue table + one WideRow table with a timestamp column, plus (Oracle) a deliberately
UPPER-CASE table authored lower-case in the test to pin catalog case-folding. Test classes follow
SqlServerReadTests/SqlBlackholeTimeoutTests conventions exactly: env vars
SQL_PG_ENDPOINT/SQL_PG_USER/SQL_PG_PASSWORD, SQL_ORACLE_ENDPOINT/SQL_ORACLE_USER/
SQL_ORACLE_PASSWORD; Assert.SkipWhen absent; connection strings via each provider's builder
obtained FROM THE FACTORY (dialect.Factory.CreateConnectionStringBuilder()) so the csproj keeps
its no-direct-provider-reference discipline (.IntegrationTests.csproj:29-33). Legs per suite:
initialize (liveness + catalog), KeyValue read, WideRow topByTimestamp read, browse-session
walk, case-folding leg. Commit test(sql): env-gated Postgres + Oracle integration suites.
Task 7: Full-suite verification + bookkeeping
Classification: small Estimated implement time: ~5 min Parallelizable with: none
dotnet build ZB.MOM.WW.OtOpcUa.slnx (0 errors), full Sql unit + Browser.Tests + AdminUI.Tests +
Configuration.Tests (the SqlCredentialGuard suites must stay green), integration suites compile +
skip clean offline. Update docs/plans/2026-07-24-driver-expansion-tracking.md Sql row/deferral
note. Commit.
Task 8: Fixture deploy + live integration run (main session)
Classification: standard Estimated implement time: ~15 min (main session, after merge)
Main session, not a subagent: rsync the two fixture dirs to dohertj2@10.100.0.35:/opt/otopcua-sql-pg/
and /opt/otopcua-sql-oracle/, docker compose up -d each over ssh, then run
dotnet test --filter "FullyQualifiedName~PostgresRead|FullyQualifiedName~OracleRead" with the
env vars set. (oracle-free first pull is ~1.5 GB — allow time; if the host can't take it, record
the suite as authored-but-not-yet-run.) Record results in the design doc §Outcome.
Chrome/AdminUI live gate — DEFERRED to the operator. After merge the operator runs, in a
chrome-capable session:
/superpowers-extended-cc:executing-plans docs/plans/2026-08-05-sql-postgres-oracle.md — gate
section: provider dropdown authoring, Test-connect, /raw browse + browse-commit against both
fixtures at http://localhost:9200.