8.8 KiB
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 0–9 DONE (10 commits). Tasks 10–16 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 1–17 map to plan tasks 0–16) 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, namespaceRuntime.DeploymentCache.src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Deployment/IDeploymentArtifactCache.cs—StoreAsync/GetCurrentAsync/GetCurrentUnkeyedAsync(D-1 addition) +CachedDeploymentArtifactrecord.src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Deployment/LocalDbDeploymentArtifactCache.cs— chunked impl. NOTE the prune guardAND 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.cs—OnReady(DDL→RegisterReplicated). PUBLIC (Host has no InternalsVisibleTo).src/Server/ZB.MOM.WW.OtOpcUa.Host/Configuration/LocalDbRegistration.cs—AddOtOpcUaLocalDb,SyncListenPort(config). RegistersIDeploymentArtifactCache → LocalDbDeploymentArtifactCachesingleton.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 inhasDriverbranch (~L176 after AddAlarmHistorian:AddOtOpcUaLocalDb+AddGrpc(interceptor)); Kestrel re-bind block beforebuilder.Build();MapZbLocalDbSync()gatedhasDriver && syncListenPort>0beforeMapOtOpcUaHealth().src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json—LocalDbsection (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);PushDesiredSubscriptionsFromArtifactsplit out;_isRunningFromCachefield;SingleClusterCacheKey="__single";ReconcileDriversnow returnsbyte[]?.src/Server/ZB.MOM.WW.OtOpcUa.Runtime/ServiceCollectionExtensions.cs— resolvesIDeploymentArtifactCache(optional, nullable) and passes into DriverHostActor.Props.src/Core/ZB.MOM.WW.OtOpcUa.Commons/Interfaces/NodeDiagnosticsSnapshot.cs— addedbool 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
Deploymentchild namespace underRuntimeORRuntime.Testsshadows theDeploymentEF 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 passTestContext.Current.CancellationToken(xunit.v3 Host.IntegrationTests) — butRuntime.Testsis xunit v2 (noTestContext.Current). - Test project reality (D-5): NO
Host.Testsproject; NOWebApplicationFactory<Program>(deliberately —TwoNodeClusterHarness.cs:48explains why). Host unit tests live inHost.IntegrationTests(xunit.v3). Actor tests inRuntime.Tests(xunit v2 + Akka.TestKit.Xunit2). - Task 10 (D-7):
AddOtOpcUaHealth()takes NO args, called unconditionally atProgram.cs:365. DON'T change its signature — register the LocalDb check unconditionally and returnHealthywhen LocalDb absent (ActiveNodeHealthCheck precedent). Health check classes: there are ZERO in-repo today;Host/Health/has onlyHealthEndpoints.cs. Meter allowlist is REAL and REQUIRED:Host/Observability/ObservabilityExtensions.cs:30o.Meters = [OtOpcUaTelemetry.MeterName]— addLocalDbMetrics.MeterName(fromZB.MOM.WW.LocalDb.Replication). Health check readsISyncStatus+IOptions<ReplicationOptions>. - Task 11: copy the
TwoNodeClusterHarnessdirect-host-build pattern, NOT WebApplicationFactory. Pins:ILocalDbsingleton +ReplicatedTables==["deployment_artifacts","deployment_pointer"];IDeploymentArtifactCache→concrete;ISyncStatusdefault-OFF (Connected==false); admin-only graph has NOILocalDb+ doesn't demandLocalDb:Path; health check registered. SetOTOPCUA_ROLESper 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 PRODUCTIONLocalDbSetup.OnReady. Real loopback Kestrel h2c through the REAL interceptor. DBs as pre-constructed singletons. Positive control: comment out bothRegisterReplicated, confirm scenarios 1–3 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.ymlservices:central-1/2(admin,driver, MAIN),site-a-1/2(driver, SITE-A),site-b-1/2(driver, SITE-B). Env styleSection__Key.9001free 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. Validatedocker compose config; DON'T bring rig up. - Task 15: baseline pre-existing test failures FIRST (
git stash→ run fulldotnet test→ unstash), report deltas only. KNOWN intermittent: oneRuntime.Testsfailure 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_sqlite3at 2.1.12 (CVE-fixed). Confirmed.
Follow-ups logged (NOT in scope, don't action without asking)
Polly.CoreinConfiguration.csprojnow orphaned (ResilientConfigReader was its only real consumer). Left in place — recorded in recon §7.3b.- Pre-existing:
TryRecoverFromStalemarks Applied w/o reconciling drivers (recon §3.5);Install-Services.ps1gives driver-only nodes noASPNETCORE_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".