feat(artifacts): central ships each site's referenced secrets (ciphertext) and fails closed on a missing one

ArtifactDeploymentService now resolves each site's secret: references (via
the new SiteSecretsCollector, wrapping ISecretStore) into DeployArtifactsCommand.Secrets,
never plaintext. A referenced secret that is absent or tombstoned fails
that site's deploy before anything is sent, with a named error, while the
rest of the batch still deploys; ComputeCurrentArtifactHashesAsync instead
folds a MissingSecrets marker into the hash so such a site reads Out of
date. Central logs what it ships (name@revision) and errors on what's
missing, never material.

Also updates the two other direct ArtifactDeploymentService construction
sites (PendingUpdatesEvaluatorTests, CentralUI SitesPageTests) for the new
constructor parameter.
This commit is contained in:
Joseph Doherty
2026-09-28 18:07:17 -04:00
parent a57c36f504
commit 75b2a7244c
8 changed files with 508 additions and 22 deletions
@@ -16,6 +16,7 @@ using ZB.MOM.WW.ScadaBridge.Communication;
using ZB.MOM.WW.ScadaBridge.DeploymentManager.PendingUpdates;
using ZB.MOM.WW.ScadaBridge.TemplateEngine.Flattening;
using ZB.MOM.WW.ScadaBridge.TemplateEngine.Validation;
using ZB.MOM.WW.Secrets.Abstractions;
namespace ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests;
@@ -66,7 +67,10 @@ public class PendingUpdatesEvaluatorTests
comms, _audit,
Options.Create(new DeploymentManagerOptions()),
NullLogger<ArtifactDeploymentService>.Instance,
_artifactHasher);
_artifactHasher,
// None of these tests reference secrets, so a store that never has anything is
// never exercised — CollectAsync only calls GetAsync for a scanned secret: reference.
new SiteSecretsCollector(Substitute.For<ISecretStore>()));
_deploymentService = new DeploymentService(
_deploymentRepo,