feat(secrets): G-2a secret: arm on GalaxySecretRef via ISecretResolver (Task 7)

Add a secret:NAME arm to GalaxySecretRef.ResolveApiKey that resolves the Galaxy
gateway API key through the shared ISecretResolver — fail-closed if the secret is
absent (never falls through to the cleartext literal arm), retiring the dev:/literal
in-DB path for production. Because GetAsync is async the method becomes
ResolveApiKeyAsync; the await cascade threads ISecretResolver by ctor injection into
GalaxyDriver + GalaxyDriverBrowser and (since GalaxyDriver is built by a static
factory closure, not DI) through GalaxyDriverFactoryExtensions + DriverFactoryBootstrap
(which pulls the real resolver from the service provider — registered unconditionally
in Slice 1). A NullSecretResolver null-object backs the parse-only/test paths only;
the runtime path always gets the real resolver (verified end-to-end).

TDD: 3 new secret:-arm tests (resolve / fail-closed-on-absent / no-literal-warning)
RED without the arm, GREEN with it; 338 Galaxy tests pass; no sync-over-async.
This commit is contained in:
Joseph Doherty
2026-07-16 17:57:04 -04:00
parent ce383df39a
commit 1424a21419
13 changed files with 289 additions and 92 deletions
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
using ZB.MOM.WW.OtOpcUa.Core.Resilience;
using ZB.MOM.WW.Secrets.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Host.Drivers;
@@ -48,7 +49,10 @@ public static class DriverFactoryBootstrap
// The calc driver needs the root script logger so a script failure fans out onto the
// script-logs topic; resolve it here (null on nodes without the script pipeline wired).
var scriptRoot = sp.GetService<ZB.MOM.WW.OtOpcUa.Core.Scripting.ScriptRootLogger>();
Register(registry, loggerFactory, scriptRoot);
// The Galaxy driver resolves its Gateway.ApiKeySecretRef secret: arm through the
// shared ISecretResolver (registered unconditionally by AddZbSecrets on every node).
var secretResolver = sp.GetRequiredService<ISecretResolver>();
Register(registry, loggerFactory, secretResolver, scriptRoot);
return registry;
});
services.AddSingleton<IDriverFactory>(sp =>
@@ -131,13 +135,14 @@ public static class DriverFactoryBootstrap
private static void Register(
DriverFactoryRegistry registry,
ILoggerFactory? loggerFactory,
ISecretResolver secretResolver,
ZB.MOM.WW.OtOpcUa.Core.Scripting.ScriptRootLogger? scriptRoot = null)
{
Driver.AbCip.AbCipDriverFactoryExtensions.Register(registry);
Driver.AbLegacy.AbLegacyDriverFactoryExtensions.Register(registry, loggerFactory);
Driver.Calculation.CalculationDriverFactoryExtensions.Register(registry, loggerFactory, scriptRoot);
Driver.FOCAS.FocasDriverFactoryExtensions.Register(registry);
Driver.Galaxy.GalaxyDriverFactoryExtensions.Register(registry, loggerFactory);
Driver.Galaxy.GalaxyDriverFactoryExtensions.Register(registry, secretResolver, loggerFactory);
Driver.Modbus.ModbusDriverFactoryExtensions.Register(registry, loggerFactory);
Driver.OpcUaClient.OpcUaClientDriverFactoryExtensions.Register(registry, loggerFactory);
Driver.S7.S7DriverFactoryExtensions.Register(registry);