diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/LocalDbWiringTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/LocalDbWiringTests.cs
new file mode 100644
index 00000000..77060548
--- /dev/null
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests/LocalDbWiringTests.cs
@@ -0,0 +1,172 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Data.Sqlite;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Options;
+using Shouldly;
+using Xunit;
+using ZB.MOM.WW.LocalDb;
+using ZB.MOM.WW.LocalDb.Replication;
+using ZB.MOM.WW.OtOpcUa.Host.Configuration;
+using ZB.MOM.WW.OtOpcUa.Host.Health;
+using ZB.MOM.WW.OtOpcUa.Host.Observability;
+using ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache;
+
+namespace ZB.MOM.WW.OtOpcUa.Host.IntegrationTests;
+
+///
+/// LocalDb Phase 1 (Task 11) — DI pins over the real composition methods.
+///
+///
+///
+/// DI extension methods have shipped inert three times in this family (Secrets 0.2.0 / 0.2.2,
+/// ScadaBridge #22): a registration that compiles, deploys, and does nothing because the seam
+/// was never resolved from a built container. These tests build a real container from the
+/// same AddOtOpcUa* methods Program.cs calls and resolve through it.
+///
+///
+/// They deliberately do NOT boot the full host. WebApplicationFactory<Program>
+/// would start hosted services — Akka, the OPC UA server, ValidateOnStart — which need
+/// SQL Server and an Akka mesh (see TwoNodeClusterHarness on why the real host is not
+/// driven here). What is under test is DI resolution, so the container is built and resolved
+/// without ever starting it.
+///
+///
+public sealed class LocalDbWiringTests : IDisposable
+{
+ private readonly List _dbPaths = [];
+ private readonly List _apps = [];
+
+ ///
+ /// Builds a container the way Program.cs's driver branch does for LocalDb: the real
+ /// , health, and observability methods,
+ /// over a temp-file LocalDb:Path.
+ ///
+ private IServiceProvider BuildDriverGraph()
+ {
+ var dbPath = Path.Combine(Path.GetTempPath(), $"otopcua-wiring-{Guid.NewGuid():N}.db");
+ _dbPaths.Add(dbPath);
+
+ var builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = [] });
+ builder.Configuration.AddInMemoryCollection(new Dictionary
+ {
+ ["LocalDb:Path"] = dbPath,
+ });
+
+ // The exact LocalDb composition Program.cs runs under hasDriver.
+ builder.Services.AddOtOpcUaLocalDb(builder.Configuration);
+ builder.Services.AddGrpc(o => o.Interceptors.Add());
+ builder.Services.AddOtOpcUaHealth();
+ builder.Services.AddOtOpcUaObservability(builder.Configuration);
+
+ var app = builder.Build();
+ _apps.Add(app);
+ return app.Services;
+ }
+
+ ///
+ /// Builds an admin-only container: the shared registrations that run on every node, but NOT
+ /// — exactly as Program.cs gates it under
+ /// hasDriver.
+ ///
+ private IServiceProvider BuildAdminOnlyGraph()
+ {
+ var builder = WebApplication.CreateBuilder(new WebApplicationOptions { Args = [] });
+
+ // No LocalDb:Path configured — an admin-only node has no reason to set it.
+ builder.Services.AddOtOpcUaHealth();
+ builder.Services.AddOtOpcUaObservability(builder.Configuration);
+
+ var app = builder.Build();
+ _apps.Add(app);
+ return app.Services;
+ }
+
+ [Fact]
+ public void DriverGraph_LocalDbResolvesAsASingletonWithBothCacheTablesRegistered()
+ {
+ var sp = BuildDriverGraph();
+
+ var first = sp.GetService();
+ first.ShouldNotBeNull();
+ sp.GetService().ShouldBeSameAs(first); // singleton
+
+ // Exact set, both directions — a dropped registration replicates nothing; an extra one costs
+ // three triggers and oplog volume on every write.
+ first.ReplicatedTables.Keys.OrderBy(k => k, StringComparer.Ordinal)
+ .ShouldBe(["deployment_artifacts", "deployment_pointer"]);
+ }
+
+ [Fact]
+ public void DriverGraph_ArtifactCacheResolvesToTheLocalDbBackedImplementation()
+ {
+ var sp = BuildDriverGraph();
+
+ sp.GetService()
+ .ShouldBeOfType();
+ }
+
+ [Fact]
+ public void DriverGraph_DefaultOff_SyncStatusReportsNotConnectedAndNoPeer()
+ {
+ // The default-OFF posture pin: registering the replication engine must not connect it.
+ var sp = BuildDriverGraph();
+
+ var status = sp.GetService();
+ status.ShouldNotBeNull();
+ status.Connected.ShouldBeFalse();
+ status.PeerNodeId.ShouldBeNull();
+ }
+
+ [Fact]
+ public void DriverGraph_LocalDbReplicationHealthCheckIsRegistered()
+ {
+ var sp = BuildDriverGraph();
+
+ var registrations = sp.GetRequiredService>()
+ .Value.Registrations
+ .Select(r => r.Name)
+ .ToArray();
+
+ registrations.ShouldContain("localdb-replication");
+ }
+
+ [Fact]
+ public void AdminOnlyGraph_HasNoLocalDb_AndDoesNotDemandLocalDbPath()
+ {
+ // Boot must not require LocalDb:Path on a node that never registers LocalDb. Building the
+ // container without a configured path and resolving the shared services proves nothing in
+ // the always-on registrations reaches for ILocalDb or its options.
+ var sp = BuildAdminOnlyGraph();
+
+ sp.GetService().ShouldBeNull();
+ sp.GetService().ShouldBeNull();
+
+ // The unconditionally-registered health check must still resolve and construct — it is meant
+ // to no-op to Healthy when LocalDb is absent, not to fail because ISyncStatus is missing.
+ var registrations = sp.GetRequiredService>()
+ .Value.Registrations
+ .Where(r => r.Name == "localdb-replication")
+ .ToArray();
+ var localDbCheck = registrations.ShouldHaveSingleItem();
+ Should.NotThrow(() => localDbCheck.Factory(sp));
+ }
+
+ public void Dispose()
+ {
+ foreach (var app in _apps)
+ ((IDisposable)app).Dispose();
+
+ SqliteConnection.ClearAllPools();
+
+ foreach (var basePath in _dbPaths)
+ {
+ foreach (var path in new[] { basePath, $"{basePath}-wal", $"{basePath}-shm" })
+ {
+ if (File.Exists(path))
+ File.Delete(path);
+ }
+ }
+ }
+}