Merge b640-601: honest deploy acks — Applied only after the address-space outcome, degraded flag + reason (#601)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe # Conflicts: # src/Server/ZB.MOM.WW.OtOpcUa.Runtime/OpcUa/OpcUaPublishActor.cs
This commit is contained in:
@@ -7,9 +7,32 @@ public enum ApplyAckOutcome { Applied, Failed }
|
||||
/// <summary>
|
||||
/// Per-node acknowledgment returned by <c>DriverHostActor</c> to the dispatching coordinator.
|
||||
/// </summary>
|
||||
/// <param name="DeploymentId">The deployment being acknowledged.</param>
|
||||
/// <param name="NodeId">The acknowledging node.</param>
|
||||
/// <param name="Outcome">Whether the node applied the deployment.</param>
|
||||
/// <param name="FailureReason">Why the apply failed; null on <see cref="ApplyAckOutcome.Applied"/>.</param>
|
||||
/// <param name="CorrelationId">The dispatch's correlation id.</param>
|
||||
/// <param name="Degraded">
|
||||
/// Gitea #601: <see langword="true"/> when the node applied the deployment (the revision advanced) but
|
||||
/// its OPC UA address-space apply was DEGRADED — swallowed per-node sink faults, a failed rebuild, or no
|
||||
/// outcome reported in time — so the served address space may be partial. Only meaningful with
|
||||
/// <see cref="ApplyAckOutcome.Applied"/>. Trailing and defaulted so an ack from a node built before
|
||||
/// this field existed (DPS JSON or gRPC without field 6) decodes exactly as it always did: clean.
|
||||
/// </param>
|
||||
/// <param name="DegradedReason">A short, operator-readable description of the degradation, or null.</param>
|
||||
public sealed record ApplyAck(
|
||||
DeploymentId DeploymentId,
|
||||
NodeId NodeId,
|
||||
ApplyAckOutcome Outcome,
|
||||
string? FailureReason,
|
||||
CorrelationId CorrelationId);
|
||||
CorrelationId CorrelationId,
|
||||
bool Degraded = false,
|
||||
string? DegradedReason = null)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gitea #601 — prefix of <c>NodeDeploymentState.FailureReason</c> on an <b>Applied</b> row whose
|
||||
/// address-space apply was degraded. Shared by central (which writes it from the ack) and a node with
|
||||
/// a ConfigDb (which writes its own row), so both spell the row identically.
|
||||
/// </summary>
|
||||
public const string DegradedReasonPrefix = "Applied degraded: ";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
||||
|
||||
/// <summary>
|
||||
/// The deploy-path timing contract shared by central (<c>ConfigPublishCoordinator</c>) and the node
|
||||
/// (<c>DriverHostActor</c>). One place, so the node's ack-hold timeout can never drift past the deadline
|
||||
/// central times the deployment out on.
|
||||
/// </summary>
|
||||
public static class DeployTimings
|
||||
{
|
||||
/// <summary>How long central waits for every expected node's ack before sealing the deployment TimedOut
|
||||
/// (<c>ConfigPublishCoordinator.DefaultApplyDeadline</c>).</summary>
|
||||
public static readonly TimeSpan ApplyDeadline = TimeSpan.FromMinutes(2);
|
||||
|
||||
/// <summary>Gitea #601 — how far inside <see cref="ApplyDeadline"/> a node's held Applied ack is released
|
||||
/// when the address-space outcome has not arrived, leaving room for transport latency.</summary>
|
||||
public static readonly TimeSpan ApplyOutcomeSafetyMargin = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>Gitea #601 — the node's default apply-outcome timeout: <see cref="ApplyDeadline"/> −
|
||||
/// <see cref="ApplyOutcomeSafetyMargin"/> (105 s). Long enough that a slow-but-clean rebuild of a large
|
||||
/// address space is not labelled degraded, short enough to ack before central gives up.</summary>
|
||||
/// <remarks>The timer is armed after the artifact read and driver reconcile, so if those alone took
|
||||
/// longer than the margin the ack can still land after the deadline — accepted: they are seconds in
|
||||
/// practice, and lengthening the margin would label more slow-but-clean rebuilds degraded.</remarks>
|
||||
public static TimeSpan ApplyOutcomeTimeout => ApplyDeadline - ApplyOutcomeSafetyMargin;
|
||||
}
|
||||
@@ -216,6 +216,11 @@ public static class MeshTransportDtoMapper
|
||||
if (message.FailureReason is not null)
|
||||
proto.FailureReason = message.FailureReason;
|
||||
|
||||
// #601 — additive fields; a clean ack leaves both unset, byte-identical to a pre-#601 encode.
|
||||
proto.Degraded = message.Degraded;
|
||||
if (message.DegradedReason is not null)
|
||||
proto.DegradedReason = message.DegradedReason;
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
@@ -249,7 +254,10 @@ public static class MeshTransportDtoMapper
|
||||
NodeId.Parse(proto.NodeId),
|
||||
outcome,
|
||||
proto.HasFailureReason ? proto.FailureReason : null,
|
||||
CorrelationId.Parse(proto.CorrelationId));
|
||||
CorrelationId.Parse(proto.CorrelationId),
|
||||
// #601 — absent on an old node's ack, which decodes as false/null: today's clean ack.
|
||||
Degraded: proto.Degraded,
|
||||
DegradedReason: proto.HasDegradedReason ? proto.DegradedReason : null);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Envelope
|
||||
|
||||
@@ -105,6 +105,11 @@ message ApplyAckMessage {
|
||||
ApplyOutcome outcome = 3;
|
||||
optional string failure_reason = 4; // nullable in the record — presence distinguishes null from ""
|
||||
string correlation_id = 5; // CorrelationId (Guid) in its "N" hex form
|
||||
// Gitea #601 — ADDITIVE. True when the node applied the deployment but its address-space apply was
|
||||
// degraded (sink faults, failed rebuild, or no outcome in time). An old node never sets it, so it decodes
|
||||
// as false: exactly today's clean ack. Only meaningful with APPLY_OUTCOME_APPLIED.
|
||||
bool degraded = 6;
|
||||
optional string degraded_reason = 7; // short operator-readable reason; presence = set
|
||||
}
|
||||
|
||||
// Mirrors ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy.ApplyAckOutcome, shifted by one so 0 stays
|
||||
|
||||
Reference in New Issue
Block a user