namespace ZB.MOM.WW.OtOpcUa.Runtime.DeploymentCache; /// /// Node-local durable cache of the deployment artifact this node last applied. /// /// /// /// Exists so a driver node can boot into its last-known-good address space when the central /// configuration database is unreachable. Without it, a control-plane outage that outlives a /// node restart leaves that node with no address space at all — the plant loses visibility /// for a reason that has nothing to do with the plant. /// /// public interface IDeploymentArtifactCache { /// /// Persists as the cluster's current deployment. /// /// /// /// Idempotent per : re-storing the same deployment /// replaces its chunks rather than accumulating orphans. Retention is bounded to the two /// newest deployments per cluster, so a long-lived node cannot grow its local database /// without limit. /// /// Task StoreAsync(string clusterId, string deploymentId, string revisionHash, byte[] artifact, CancellationToken ct = default); /// /// Reads the cached artifact for , or /// when nothing is cached or the cached bytes fail their integrity check. /// /// /// /// A failed integrity check is deliberately indistinguishable from a miss. Booting a /// plant from a truncated address space is strictly worse than booting from none: the /// missing half looks like a deliberate configuration rather than an error. /// /// Task GetCurrentAsync(string clusterId, CancellationToken ct = default); /// /// Reads the single cached pointer without knowing the cluster id. /// /// /// /// The boot seam needs this. A node's cluster id is only derivable from an artifact it /// has already loaded, or from the central database — which is precisely what is /// unreachable in the scenario this cache exists to survive. So the cold path reads the /// newest pointer unkeyed. /// /// /// More than one pointer row means the node was re-homed between clusters. The newest /// wins, but the choice is logged as a warning naming both clusters — a node silently /// booting a neighbouring cluster's configuration is exactly the failure that must never /// be quiet. /// /// Task GetCurrentUnkeyedAsync(CancellationToken ct = default); } /// /// A deployment artifact recovered from the node-local cache, with the metadata needed to decide /// whether it is still current once the control plane is reachable again. /// public sealed record CachedDeploymentArtifact( string DeploymentId, string RevisionHash, byte[] Artifact, DateTimeOffset AppliedAtUtc);