Files
lmxopcua/docs/plans/2026-07-20-localdb-phase1-progress.md
T

8.8 KiB
Raw Blame History

LocalDb Phase 1 — Execution Progress (resume state)

Branch: feat/localdb-phase1 (off master). Working tree CLEAN, all work committed, nothing pushed. Plan: docs/plans/2026-07-20-localdb-adoption-phase1.md Recon: docs/plans/2026-07-20-localdb-phase1-recon.md (READ THIS — it has the 8 deviations D-1..D-8 that override the plan text). Skill in use: superpowers-extended-cc:executing-plans — batch, verify, commit per task, report between batches. Do NOT merge to master — plan stops at Task 15 (DoD) and reports; Task 16 is a rig live-gate needing explicit user go-ahead.

Status: Tasks 09 DONE (10 commits). Tasks 1016 REMAIN.

# Task State Commit
0 Preflight + recon 42f85507
1 Package refs (LocalDb 0.1.1, Grpc.AspNetCore 2.76.0) 44255fcb
2 Schema + LocalDbSetup.OnReady + tests 3b65c24b
3 Fail-closed sync auth interceptor + tests 3d29be83
4 LocalDbRegistration + Program.cs DI + appsettings 6aff9a83
5 h2c sync listener + MapZbLocalDbSync (Kestrel) b9ddf20e
(fix) rename Runtime.Deployment → Runtime.DeploymentCache e771a11a
6 IDeploymentArtifactCache + chunked impl + tests a38a52b8
9 Delete LiteDB LocalCache 2bae1b4c
7 Cache write path in DriverHostActor 1becf591
8 Boot-from-cache + RunningFromCache signal a27eff32
10 Health check + telemetry meter allowlist TODO
11 DI-pin integration tests over real host graph TODO
12 2-node convergence harness + scenarios TODO
13 docker-dev rig config TODO
14 Documentation TODO
15 DoD sweep (offline) — STOP + report TODO
16 Live gate on docker-dev rig — NEEDS USER GO-AHEAD TODO

Native task list (TaskCreate ids 117 map to plan tasks 016) tracks the same; tasks.json in plan dir mirrors it.

What exists now (key files)

  • src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Deployment/DeploymentCacheSchema.cs — DDL, namespace Runtime.DeploymentCache.
  • src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Deployment/IDeploymentArtifactCache.csStoreAsync / GetCurrentAsync / GetCurrentUnkeyedAsync (D-1 addition) + CachedDeploymentArtifact record.
  • src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Deployment/LocalDbDeploymentArtifactCache.cs — chunked impl. NOTE the prune guard AND deployment_id <> @DeploymentId (bug I found: pointer-target could be pruned under same-tick GUIDs → permanent silent miss).
  • src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LocalDbSetup.csOnReady (DDL→RegisterReplicated). PUBLIC (Host has no InternalsVisibleTo).
  • src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LocalDbRegistration.csAddOtOpcUaLocalDb, SyncListenPort(config). Registers IDeploymentArtifactCache → LocalDbDeploymentArtifactCache singleton.
  • src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LocalDbSyncAuthInterceptor.cs — fail-closed bearer.
  • src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/KestrelHttpBinding.cs — URL parse/re-bind helper (D-6).
  • src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs — LocalDb wired in hasDriver branch (~L176 after AddAlarmHistorian: AddOtOpcUaLocalDb + AddGrpc(interceptor)); Kestrel re-bind block before builder.Build(); MapZbLocalDbSync() gated hasDriver && syncListenPort>0 before MapOtOpcUaHealth().
  • src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.jsonLocalDb section (Path, SyncListenPort:0, Replication{}), between AlarmHistorian and Deployment.
  • src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs — cache dep threaded LAST in Props/ctor/forwarding; CacheAppliedArtifact (write, after PushDesiredSubscriptions, empty-blob skip, own try/catch); TryBootFromCache + ApplyCachedArtifact (in Bootstrap catch); PushDesiredSubscriptionsFromArtifact split out; _isRunningFromCache field; SingleClusterCacheKey="__single"; ReconcileDrivers now returns byte[]?.
  • src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs — resolves IDeploymentArtifactCache (optional, nullable) and passes into DriverHostActor.Props.
  • src/Core/ZB.MOM.WW.OtOpcUa.Commons/Interfaces/NodeDiagnosticsSnapshot.cs — added bool RunningFromCache = false.
  • Tests (all green): Host.IntegrationTests/LocalDbSetupTests.cs, LocalDbSyncAuthInterceptorTests.cs, LocalDbSyncListenerTests.cs; Runtime.Tests/Deployment/LocalDbDeploymentArtifactCacheTests.cs, Runtime.Tests/Drivers/DriverHostActorArtifactCacheTests.cs, DriverHostActorBootFromCacheTests.cs.

Load-bearing facts for the remaining tasks

  • Namespace trap (recurs!): any Deployment child namespace under Runtime OR Runtime.Tests shadows the Deployment EF entity → CS0118 across the assembly. Use ...DeploymentCache. Only a FULL-SOLUTION build catches it (per-project builds pass).
  • xUnit1051 is an error in these test projects: every ILocalDb/DB call in a test must pass TestContext.Current.CancellationToken (xunit.v3 Host.IntegrationTests) — but Runtime.Tests is xunit v2 (no TestContext.Current).
  • Test project reality (D-5): NO Host.Tests project; NO WebApplicationFactory<Program> (deliberately — TwoNodeClusterHarness.cs:48 explains why). Host unit tests live in Host.IntegrationTests (xunit.v3). Actor tests in Runtime.Tests (xunit v2 + Akka.TestKit.Xunit2).
  • Task 10 (D-7): AddOtOpcUaHealth() takes NO args, called unconditionally at Program.cs:365. DON'T change its signature — register the LocalDb check unconditionally and return Healthy when LocalDb absent (ActiveNodeHealthCheck precedent). Health check classes: there are ZERO in-repo today; Host/Health/ has only HealthEndpoints.cs. Meter allowlist is REAL and REQUIRED: Host/Observability/ObservabilityExtensions.cs:30 o.Meters = [OtOpcUaTelemetry.MeterName] — add LocalDbMetrics.MeterName (from ZB.MOM.WW.LocalDb.Replication). Health check reads ISyncStatus + IOptions<ReplicationOptions>.
  • Task 11: copy the TwoNodeClusterHarness direct-host-build pattern, NOT WebApplicationFactory. Pins: ILocalDb singleton + ReplicatedTables == ["deployment_artifacts","deployment_pointer"]; IDeploymentArtifactCache→concrete; ISyncStatus default-OFF (Connected==false); admin-only graph has NO ILocalDb + doesn't demand LocalDb:Path; health check registered. Set OTOPCUA_ROLES per case; LocalDb:Path→temp file, ClearAllPools+delete triplet in cleanup.
  • Task 12: harness copied from ~/Desktop/ScadaBridge/tests/.../LocalDbSitePairHarness.cs + ~/Desktop/scadaproj/ZB.MOM.WW.LocalDb/tests/.../Convergence/ConvergenceFixture.cs. Both nodes init via PRODUCTION LocalDbSetup.OnReady. Real loopback Kestrel h2c through the REAL interceptor. DBs as pre-constructed singletons. Positive control: comment out both RegisterReplicated, confirm scenarios 13 red, restore (don't commit red).
  • Task 13 (D-8): NO host node has a writable volume today — must ADD named volumes. docker-dev/docker-compose.yml services: central-1/2 (admin,driver, MAIN), site-a-1/2 (driver, SITE-A), site-b-1/2 (driver, SITE-B). Env style Section__Key. 9001 free container-internal. Enable replication on site-a pair only (a-1 initiator w/ PeerAddress, a-2 passive, byte-identical ApiKey, MaxBatchSize=16). site-b stays OFF = default-OFF pin. Validate docker compose config; DON'T bring rig up.
  • Task 15: baseline pre-existing test failures FIRST (git stash → run full dotnet test → unstash), report deltas only. KNOWN intermittent: one Runtime.Tests failure seen ~3× across runs (mine + subagent's), never reproduced under trx logger, NOT one of the new tests — investigate/baseline here.
  • Warnings-as-errors across src + most test projects. Pre-existing CS0618/xUnit1051 warnings exist ONLY in Client.Shared* (which don't set TreatWarningsAsErrors) — not mine, ignore.
  • SQLitePCLRaw: no pin needed — LocalDb 0.1.1 nuspec floors lib.e_sqlite3 at 2.1.12 (CVE-fixed). Confirmed.

Follow-ups logged (NOT in scope, don't action without asking)

  • Polly.Core in Configuration.csproj now orphaned (ResilientConfigReader was its only real consumer). Left in place — recorded in recon §7.3b.
  • Pre-existing: TryRecoverFromStale marks Applied w/o reconciling drivers (recon §3.5); Install-Services.ps1 gives driver-only nodes no ASPNETCORE_URLS (recon §6.4, worked around by D-6).

Verify-anything-quick

Full build: dotnet build ZB.MOM.WW.OtOpcUa.slnx (expect 0 errors). LocalDb tests: dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests --filter "FullyQualifiedName~LocalDb" and dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests --filter "FullyQualifiedName~ArtifactCache|FullyQualifiedName~BootFromCache".