docs: site secrets ride the artifact deploy; gRPC hub retired in ScadaBridge

This commit is contained in:
Joseph Doherty
2026-09-28 20:22:45 -04:00
parent bc29dc412b
commit 0ccf68951d
8 changed files with 259 additions and 6 deletions
@@ -281,6 +281,9 @@ Component-NotificationService.md). Notification delivery happens on the central
cluster, so no notification artifact or SMTP credential is ever distributed to
sites.
A site's **secrets** are a fifth, *derived* artifact class riding the same command —
see *Site secrets* below.
### Per-site artifact revision (2026-09-14)
`Site` carries two nullable columns — `ArtifactRevisionHash` (`string?`, max 100)
@@ -331,6 +334,59 @@ design**: a partial map would silently report the unqueried sites as up to date,
so the pending-updates cache instead discards the faulted flight and the UI shows
the aggregate as unavailable.
### Site secrets (2026-09-28)
Site secrets ride the artifact deploy instead of the (now retired) gRPC secrets
hub — design `docs/plans/2026-09-28-site-secrets-artifact-deploy-design.md`; see
that doc's *Implementation notes (as built)* section for the deviations called
out below.
- **Derived, not configured.** `SiteSecretsCollector` scans the site's data
connection artifacts (`SecretReferenceScanner` over `PrimaryConfigurationJson`
/ `BackupConfigurationJson`) for `secret:<name>` string values and resolves
each name against the central `ISecretStore`, producing the ciphertext-only
`SecretArtifact` rows the command carries (`DeployArtifactsCommand.Secrets`,
proto field 9) plus the set of unresolved references. A site with no `secret:`
references ships no secrets and is unaffected by any of this.
- **Fail closed, per site.** A site with one or more unresolved (absent or
tombstoned) references fails its deploy *before* `_communicationService` is
called — nothing is sent, the site keeps its previous stamp, and the error
names the secret and the referencing data connection(s): `Secret 'X'
referenced by data connection(s) A, B is not in the central secret store`
(semicolon-joined for more than one). Other sites in the same sweep are
unaffected.
- **Stamp gating.** A site is stamped `ArtifactRevisionHash`/`ArtifactsDeployedAt`
only when every shipped secret actually applied. Two failure shapes are
distinguished from an ordinary site failure and never stamped: an **old
site** (wire `Success = true`, secrets were shipped, but the response has no
`SecretsApplied` at all — a pre-feature site build) fails with `Site build
predates secret deployment — upgrade the site before deploying secrets`; a
**partial apply** (`SecretsApplied` less than the shipped count) fails with
`Site applied {n} of {m} secrets`. Both apply identically on the all-sites
sweep (`DeployCoreAsync`) and the per-site retry (`RetryForSiteAsync`).
- **Staleness hash.** `ArtifactRevisionHashService` folds secrets into the same
hash as every other artifact class (`HashableSecret`: a SHA-256 digest of the
ciphertext/nonce/tag/wrapped-DEK material, never plaintext, plus `KekId` and
`Revision`) so a KEK rewrap correctly flips a site to out of date. A
referenced-but-not-yet-created secret hashes as an explicit `Missing` marker
(`DeployArtifactsCommand.MissingSecrets`, central-only — never on the wire),
so that site reads *Out of date*, never *Current*, until the secret exists.
**A `null` secrets list and an empty one hash identically** (the existing
null-when-empty convention for every artifact class), so a site with no
secret references keeps its pre-existing hash across the upgrade that added
this feature.
- **Audit.** One best-effort `DeploySiteSecrets` / `SiteSecrets` audit row is
written per site deploy that carried secrets or a missing-secret failure
(never for a site with no secret references at all) — after-state is `{
deploymentId, secrets: [{name, revision}], missing: [name], success }`, which
structurally excludes ciphertext. Written on both `DeployCoreAsync` and
`RetryForSiteAsync`, including the fail-closed path (audited even though
nothing was sent).
- **Central store.** Central resolves secrets from `ISecretStore`, composed by
the Host per `Secrets:Store:Provider` — see Component-Host.md → *Secrets*.
- **Site apply and the per-node mirror** — see Component-SiteRuntime.md →
*Deployed Secrets*.
## Site-Side Apply Atomicity
Applying a deployment at the site is **all-or-nothing per instance**:
@@ -420,6 +476,7 @@ The Deployment Manager sends the following commands to sites via the Communicati
- **Configuration Database (MS SQL)**: Stores deployment status and deployed configuration snapshots.
- **Security & Auth**: Enforces Deployment role (with optional site scoping).
- **Configuration Database (via IAuditService)**: Logs all deployment actions, system-wide artifact deployments, and instance lifecycle changes.
- **`ZB.MOM.WW.Secrets.Abstractions`** (`ISecretStore`, `SecretName`): `SiteSecretsCollector` resolves a site's referenced secrets against the Host-composed central store — see Component-Host.md → *Secrets*.
## Interactions