diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs b/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs index 8eb7deff..e8ab8291 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Host/Program.cs @@ -64,6 +64,15 @@ var configuration = new ConfigurationBuilder() // EnsureCentralSharedStoreConnectionString check AddScadaBridgeSecrets runs — this is the // earliest code that would otherwise hand the bad value to SqlConnection, whose generic // "initialization string" format error would bury the designed message. +// +// Two consequences worth knowing. This block runs BEFORE Serilog exists and outside the +// try/catch below, so a SQL Server outage at central boot exits with a bare stderr stack +// trace, no structured log — honest and container-restart-retryable, just unenriched; the +// SQLite path had the same window but its only failure mode was a local file. And the +// role check is deliberately NOT folded into SecretsRegistration: that class refuses to +// read the node role from configuration (a config-read role could silently turn a site +// into a hub — see SecretsNodeRole), while Program.cs must read it to branch the whole +// composition anyway, so the config read stays here at the composition root. var expanderUsesSharedSqlStore = string.Equals( configuration["ScadaBridge:Node:Role"], "Central", StringComparison.OrdinalIgnoreCase) diff --git a/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs b/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs index 9ecb761a..cb18cec3 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Host/SecretsRegistration.cs @@ -199,6 +199,8 @@ public static class SecretsRegistration /// internal static string EnsureCentralSharedStoreConnectionString(IConfiguration config) { + ArgumentNullException.ThrowIfNull(config); + var hubConnectionString = config[HubConnectionStringKey]; if (string.IsNullOrWhiteSpace(hubConnectionString)) diff --git a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SecretsReplicationWiringTests.cs b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SecretsReplicationWiringTests.cs index 715e5f22..13592f99 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SecretsReplicationWiringTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.Host.Tests/SecretsReplicationWiringTests.cs @@ -79,9 +79,14 @@ public class SecretsReplicationWiringTests == "ZB.MOM.WW.Secrets.Replicator.SqlServer"; /// - /// Any descriptor contributed by the SQL-Server replicator package, whatever its shape. The - /// package's factory-lambda registrations (its store, migrator, connection factory) carry the - /// concrete type as the SERVICE type, so checking both sides catches every registration form. + /// Any descriptor whose service OR implementation type lives in the SQL-Server replicator + /// assembly. Honest bound: the package also registers ISecretStore/ISecretsStoreMigrator + /// through factory lambdas whose service type is the Abstractions interface and whose + /// ImplementationType is null — those two descriptors would evade this scan in isolation. The + /// site-purity pin still holds because the same extension unconditionally registers three + /// concrete types from the target assembly first, which this scan does catch — so the package + /// cannot enter the container without tripping it, even though not every individual descriptor + /// it adds is individually detectable. /// private static bool IsFromSqlServerReplicatorPackage(ServiceDescriptor descriptor) => descriptor.ServiceType.Assembly.GetName().Name == "ZB.MOM.WW.Secrets.Replicator.SqlServer"