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
@@ -13,6 +13,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Communication;
using ZB.MOM.WW.ScadaBridge.DeploymentManager;
using ZB.MOM.WW.ScadaBridge.DeploymentManager.PendingUpdates;
using ZB.MOM.WW.Secrets.Abstractions;
using SitesPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Admin.Sites;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Admin;
@@ -54,7 +55,10 @@ public class SitesPageTests : BunitContext
Substitute.For<IAuditService>(),
Options.Create(new DeploymentManagerOptions()),
NullLogger<ArtifactDeploymentService>.Instance,
new ArtifactRevisionHashService());
new ArtifactRevisionHashService(),
// These tests don't exercise secret references, so a store that never has
// anything is never called — CollectAsync only queries a scanned secret: reference.
new SiteSecretsCollector(Substitute.For<ISecretStore>()));
Services.AddSingleton(artifactSvc);
Services.AddSingleton<IDialogService>(Substitute.For<IDialogService>());