fix(deploy): derive the apply-outcome timeout from central's apply deadline (105 s) and name a timeout reason distinctly (#601 review)

60 s labelled a slow-but-clean large rebuild degraded. The deadline moves to a
shared Commons DeployTimings constant that ConfigPublishCoordinator reads, and
the node's timeout is deadline - 15 s margin, so the held ack still lands in
time. A timed-out ack's reason now says the outcome was not reported in time,
distinct from a real sink failure.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 17:54:09 -04:00
parent af02fce3c4
commit 0a821417ea
6 changed files with 67 additions and 13 deletions
@@ -134,7 +134,9 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase
ack.Outcome.ShouldBe(ApplyAckOutcome.Applied);
ack.Degraded.ShouldBeTrue();
ack.DegradedReason.ShouldNotBeNull();
ack.DegradedReason.ShouldContain("did not report an outcome");
// Distinct from a real sink failure: it says the outcome was not reported in time.
ack.DegradedReason.ShouldStartWith(DriverHostActor.OutcomeNotReportedReasonPrefix);
ack.DegradedReason.ShouldNotContain("failedNodes=");
// A late outcome after the timeout must not produce a second ack.
var rebuild = publish.FishForMessage<OpcUaPublishActor.RebuildAddressSpace>(_ => true, PresenceBudget);
@@ -199,6 +201,18 @@ public sealed class DriverHostActorDegradedAckTests : RuntimeActorTestBase
}, PresenceBudget);
}
[Fact]
public void The_default_outcome_timeout_is_derived_from_centrals_apply_deadline_minus_a_margin()
{
// A slow-but-clean rebuild must not be labelled degraded at 60 s, yet the held ack must still
// reach central before its apply deadline. Both sides read ONE shared constant.
DeployTimings.ApplyDeadline.ShouldBe(TimeSpan.FromMinutes(2));
DriverHostActor.DefaultApplyOutcomeTimeout.ShouldBe(DeployTimings.ApplyDeadline - DeployTimings.ApplyOutcomeSafetyMargin);
DeployTimings.ApplyOutcomeSafetyMargin.ShouldBe(TimeSpan.FromSeconds(15));
DriverHostActor.DefaultApplyOutcomeTimeout.ShouldBeGreaterThan(TimeSpan.FromSeconds(60));
DriverHostActor.DefaultApplyOutcomeTimeout.ShouldBeLessThan(DeployTimings.ApplyDeadline);
}
private (IDbContextFactory<OtOpcUaConfigDbContext> Db, DeploymentId Id) SeedOneTagDeployment()
{
var db = NewInMemoryDbFactory();