diff --git a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/NoOpSecretReplicator.cs b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/NoOpSecretReplicator.cs
new file mode 100644
index 0000000..04cfe87
--- /dev/null
+++ b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/NoOpSecretReplicator.cs
@@ -0,0 +1,14 @@
+using ZB.MOM.WW.Secrets.Abstractions;
+
+namespace ZB.MOM.WW.Secrets.DependencyInjection;
+
+///
+/// The single-node default : publishing a row is a no-op because
+/// there are no peers to broadcast to. The deferred ZB.MOM.WW.Secrets.Akka package replaces
+/// this registration with a real cluster replicator for clustered deployments.
+///
+public sealed class NoOpSecretReplicator : ISecretReplicator
+{
+ ///
+ public Task PublishAsync(StoredSecret row, CancellationToken ct) => Task.CompletedTask;
+}
diff --git a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/SecretsMigrationHostedService.cs b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/SecretsMigrationHostedService.cs
new file mode 100644
index 0000000..bf58beb
--- /dev/null
+++ b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/SecretsMigrationHostedService.cs
@@ -0,0 +1,27 @@
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Options;
+using ZB.MOM.WW.Secrets.Sqlite;
+
+namespace ZB.MOM.WW.Secrets.DependencyInjection;
+
+///
+/// Runs the secrets SQLite schema migration at application startup when
+/// is . The migration is
+/// idempotent, so repeated restarts are safe.
+///
+internal sealed class SecretsMigrationHostedService(
+ SqliteSecretsStoreMigrator migrator,
+ IOptions options) : IHostedService
+{
+ ///
+ public async Task StartAsync(CancellationToken cancellationToken)
+ {
+ if (options.Value.RunMigrationsOnStartup)
+ {
+ await migrator.MigrateAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+}
diff --git a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/SecretsServiceCollectionExtensions.cs b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/SecretsServiceCollectionExtensions.cs
new file mode 100644
index 0000000..4d73a5b
--- /dev/null
+++ b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/DependencyInjection/SecretsServiceCollectionExtensions.cs
@@ -0,0 +1,87 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+using ZB.MOM.WW.Audit;
+using ZB.MOM.WW.Secrets.Abstractions;
+using ZB.MOM.WW.Secrets.Crypto;
+using ZB.MOM.WW.Secrets.MasterKey;
+using ZB.MOM.WW.Secrets.Sqlite;
+
+namespace ZB.MOM.WW.Secrets.DependencyInjection;
+
+///
+/// Dependency-injection helpers that wire the ZB.MOM.WW secrets subsystem — master-key provider,
+/// envelope cipher, SQLite store, resolver, no-op replicator, and the startup migration hosted
+/// service — from configuration with a single call.
+///
+public static class SecretsServiceCollectionExtensions
+{
+ ///
+ /// Registers the secrets subsystem: binds from the configuration
+ /// section at and wires the master-key provider, envelope cipher,
+ /// SQLite-backed store, the caching resolver, the single-node no-op replicator, and a hosted
+ /// service that runs the schema migration on startup (when enabled).
+ ///
+ /// The service collection to add to.
+ /// The application configuration.
+ /// Path of the configuration section holding the secrets options.
+ /// The same instance, for chaining.
+ public static IServiceCollection AddZbSecrets(
+ this IServiceCollection services,
+ IConfiguration config,
+ string sectionPath)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(config);
+ ArgumentException.ThrowIfNullOrWhiteSpace(sectionPath);
+
+ services.Configure(config.GetSection(sectionPath));
+
+ // One connection factory targets the configured SQLite path. Singleton: it is stateless
+ // aside from the path and opens a fresh connection per operation.
+ services.TryAddSingleton(sp =>
+ new SecretsSqliteConnectionFactory(
+ sp.GetRequiredService>().Value.SqlitePath));
+
+ services.TryAddSingleton(sp =>
+ MasterKeyProviderFactory.Create(
+ sp.GetRequiredService>().Value.MasterKey));
+
+ services.TryAddSingleton(sp =>
+ new AesGcmEnvelopeCipher(sp.GetRequiredService()));
+
+ services.TryAddSingleton(sp =>
+ new SqliteSecretStore(sp.GetRequiredService()));
+
+ services.TryAddSingleton();
+
+ services.TryAddSingleton(sp =>
+ {
+ // Resolve the audit writer LAZILY so this picks up the app's AddZbAudit registration
+ // regardless of call order; fall back to a no-op writer when the app registers none.
+ IAuditWriter audit = sp.GetService() ?? NoOpAuditWriter.Instance;
+ ISecretActorAccessor? actorAccessor = sp.GetService();
+ TimeProvider? timeProvider = sp.GetService();
+ TimeSpan ttl = sp.GetRequiredService>().Value.ResolveCacheTtl;
+
+ return new DefaultSecretResolver(
+ sp.GetRequiredService(),
+ sp.GetRequiredService(),
+ audit,
+ ttl,
+ actorAccessor,
+ timeProvider);
+ });
+
+ // Migrator: singleton, constructed from the already-registered connection factory. Needed
+ // before any store operations so the schema exists.
+ services.TryAddSingleton(sp =>
+ new SqliteSecretsStoreMigrator(sp.GetRequiredService()));
+
+ // Hosted service that runs migrations on startup when SecretsOptions.RunMigrationsOnStartup.
+ services.AddHostedService();
+
+ return services;
+ }
+}
diff --git a/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/SecretsOptions.cs b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/SecretsOptions.cs
new file mode 100644
index 0000000..20c779c
--- /dev/null
+++ b/ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/SecretsOptions.cs
@@ -0,0 +1,37 @@
+using ZB.MOM.WW.Secrets.MasterKey;
+
+namespace ZB.MOM.WW.Secrets;
+
+///
+/// Application-level configuration for the ZB.MOM.WW secrets subsystem: where the SQLite store
+/// lives, how the master key (KEK) is resolved, whether the schema migration runs on startup,
+/// how long decrypted values are cached, and whether the UI reveal path is enabled. Bound from a
+/// configuration section by .
+///
+public sealed class SecretsOptions
+{
+ /// The path to the SQLite database backing the secret store. Defaults to secrets.db.
+ public string SqlitePath { get; set; } = "secrets.db";
+
+ /// The master-key (KEK) resolution configuration. Defaults to a fresh .
+ public MasterKeyOptions MasterKey { get; set; } = new();
+
+ ///
+ /// When (the default), the SQLite schema migration runs at application
+ /// startup via the hosted service. The migration is idempotent, so repeated restarts are safe.
+ ///
+ public bool RunMigrationsOnStartup { get; set; } = true;
+
+ ///
+ /// How long a decrypted secret value is cached in process memory by the resolver. Kept short by
+ /// default (30 seconds) because the cache holds plaintext secret material.
+ ///
+ public TimeSpan ResolveCacheTtl { get; set; } = TimeSpan.FromSeconds(30);
+
+ ///
+ /// Opt-in switch for the UI reveal path (consumed by the deferred ZB.MOM.WW.Secrets.Ui
+ /// package). Defaults to — secrets are write-only from the UI unless a
+ /// deployment explicitly opts in.
+ ///
+ public bool RevealEnabled { get; set; }
+}
diff --git a/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/DependencyInjection/AddZbSecretsTests.cs b/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/DependencyInjection/AddZbSecretsTests.cs
new file mode 100644
index 0000000..a9542ed
--- /dev/null
+++ b/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/DependencyInjection/AddZbSecretsTests.cs
@@ -0,0 +1,116 @@
+using System.Security.Cryptography;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using ZB.MOM.WW.Secrets.Abstractions;
+using ZB.MOM.WW.Secrets.DependencyInjection;
+using ZB.MOM.WW.Secrets.Sqlite;
+
+namespace ZB.MOM.WW.Secrets.Tests.DependencyInjection;
+
+///
+/// Verifies that wires the whole
+/// core: every seam resolves, a real secret round-trips through the composed graph, and a missing
+/// master key fails closed.
+///
+public sealed class AddZbSecretsTests
+{
+ private static string NewTempDbPath() =>
+ Path.Combine(Path.GetTempPath(), $"zb-secrets-di-{Guid.NewGuid():N}.db");
+
+ private static string Base64Key32() => Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
+
+ private static IConfiguration BuildConfig(string sqlitePath, string envVarName) =>
+ new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["Secrets:SqlitePath"] = sqlitePath,
+ ["Secrets:MasterKey:Source"] = "Environment",
+ ["Secrets:MasterKey:EnvVarName"] = envVarName,
+ })
+ .Build();
+
+ [Fact]
+ public void Registers_All_CoreServices()
+ {
+ string dbPath = NewTempDbPath();
+ string envVar = $"ZB_TEST_KEY_{Guid.NewGuid():N}";
+ Environment.SetEnvironmentVariable(envVar, Base64Key32());
+ try
+ {
+ var services = new ServiceCollection();
+ services.AddZbSecrets(BuildConfig(dbPath, envVar), "Secrets");
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ Assert.NotNull(provider.GetService());
+ Assert.NotNull(provider.GetService());
+ Assert.NotNull(provider.GetService());
+ Assert.NotNull(provider.GetService());
+
+ ISecretReplicator? replicator = provider.GetService();
+ Assert.NotNull(replicator);
+ Assert.IsType(replicator);
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(envVar, null);
+ File.Delete(dbPath);
+ }
+ }
+
+ [Fact]
+ public async Task Resolver_Works_EndToEnd_ThroughDi()
+ {
+ string dbPath = NewTempDbPath();
+ string envVar = $"ZB_TEST_KEY_{Guid.NewGuid():N}";
+ Environment.SetEnvironmentVariable(envVar, Base64Key32());
+ try
+ {
+ var services = new ServiceCollection();
+ services.AddZbSecrets(BuildConfig(dbPath, envVar), "Secrets");
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ // Run the migration first so the schema exists (as the hosted service would on startup).
+ await provider.GetRequiredService().MigrateAsync(CancellationToken.None);
+
+ var name = new SecretName("db/password");
+ const string plaintext = "super-secret-value";
+
+ ISecretCipher cipher = provider.GetRequiredService();
+ ISecretStore store = provider.GetRequiredService();
+ StoredSecret row = cipher.Encrypt(name, plaintext, SecretContentType.Text);
+ await store.UpsertAsync(row, CancellationToken.None);
+
+ ISecretResolver resolver = provider.GetRequiredService();
+ string? resolved = await resolver.GetAsync(name, CancellationToken.None);
+
+ Assert.Equal(plaintext, resolved);
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(envVar, null);
+ File.Delete(dbPath);
+ }
+ }
+
+ [Fact]
+ public void MasterKey_Unavailable_FailsClosed()
+ {
+ string dbPath = NewTempDbPath();
+ string envVar = $"ZB_TEST_KEY_{Guid.NewGuid():N}";
+ // Deliberately do NOT set the env var — the master key cannot be resolved.
+ try
+ {
+ var services = new ServiceCollection();
+ services.AddZbSecrets(BuildConfig(dbPath, envVar), "Secrets");
+ using ServiceProvider provider = services.BuildServiceProvider();
+
+ IMasterKeyProvider keyProvider = provider.GetRequiredService();
+
+ Assert.Throws(() => keyProvider.GetMasterKey());
+ }
+ finally
+ {
+ File.Delete(dbPath);
+ }
+ }
+}
diff --git a/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/ZB.MOM.WW.Secrets.Tests.csproj b/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/ZB.MOM.WW.Secrets.Tests.csproj
index 89c1e1b..e96d479 100644
--- a/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/ZB.MOM.WW.Secrets.Tests.csproj
+++ b/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/ZB.MOM.WW.Secrets.Tests.csproj
@@ -10,6 +10,8 @@
+
+