fix(site-secrets): ship an empty Secrets list, not null, when a site references no secrets

The site applies DeployArtifactsCommand.Secrets as the full set (rows absent from it are
soft-removed and DeployedSecretsMirror deletes them on both nodes), while null means
'central shipped no secrets' and touches nothing. The command builder shipped null for a
site with no references, so dropping the LAST secret: reference never removed the secret
from either site node. Found by the Task 13 live gate (check 5: site-a-a and site-a-b
both still held sb-gate-mxgw r1, deployed_secrets.is_removed=0 after the removal deploy).

Central now always ships a list. Old sites ignore proto field 9; the artifact hash is
unchanged (empty and null hash identically); SecretsDeployed stays null and no
DeploySiteSecrets audit row is written when nothing shipped. Regression test
NoSecretReferences_ShipsEmptySecretsList_SoTheSiteRemovesPreviouslyDeployedOnes (red
before, green after). Spec notes in Component-DeploymentManager/SiteRuntime + the design
doc's as-built section.
This commit is contained in:
Joseph Doherty
2026-09-28 20:43:36 -04:00
parent 1444f85919
commit ad19d4b158
5 changed files with 63 additions and 2 deletions
@@ -220,6 +220,12 @@ accurate as the spec:
side effect that needs explicit user consent, and it would be a dead pin here regardless —
hub retirement (above) removes ScadaBridge's only dependency on the hub client entirely. It
remains available for the library's other hub consumers once published.
- **Empty, not null, when a site references no secrets (Task 13 live-gate fix).** The command
builder first shipped `Secrets = null` for a site with no references ("null-when-empty", borrowed
from the hash convention). But the site treats `null` as "central shipped no secrets" and touches
nothing, so dropping a site's LAST `secret:` reference never soft-removed the row and the secret
stayed live on both nodes. Central now always ships a list (empty when there are no references);
the hash is unchanged because an empty and a null list hash identically.
- **Rig posture.** `docker/docker-compose.yml`'s central nodes now carry
`Secrets__Store__Provider=SqlServer` plus a shared KEK; site-a shares that KEK (it is the
secrets-via-artifact-deploy site); site-b/site-c deliberately carry a **mismatched** KEK so the