fix(deploy): hold the Applied ack until the address-space apply outcome is known (#601)

DriverHostActor no longer acks Applied the moment the revision advances. It
asks OpcUaPublishActor for the outcome (RebuildAddressSpace.ReportOutcome) and
acks Applied+Degraded(reason) when the apply was degraded; a missing outcome
times out (60 s, inside the coordinator's 2 min deadline) to a degraded ack,
never a clean one. Re-dispatching a revision whose apply was degraded or is
still unconfirmed re-applies instead of taking the clean idempotent
short-circuit - that re-apply is also what repairs the degraded nodes.
Tests with a bare publish probe use a clean-outcome auto-pilot probe.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 17:29:45 -04:00
parent 521e1eb89b
commit 90d7f6dfca
13 changed files with 421 additions and 24 deletions
@@ -42,7 +42,7 @@ public sealed class DriverHostActorFetchAndCacheUnreadableTests : RuntimeActorTe
[RevA.Value] = Artifact(),
[RevB.Value] = null,
});
var publishProbe = CreateTestProbe();
var publishProbe = this.CreateCleanOutcomePublishProbe();
var coordinator = CreateTestProbe();
var actor = Sys.ActorOf(DriverHostActor.Props(
@@ -72,7 +72,7 @@ public sealed class DriverHostActorFetchAndCacheUnreadableTests : RuntimeActorTe
// empty blob handed to it directly: ReconcileDriversFromBlob's #485 guard fails the apply.
var db = NewInMemoryDbFactory();
var fetcher = new MapFetcher(new Dictionary<string, byte[]?> { [RevA.Value] = Array.Empty<byte>() });
var publishProbe = CreateTestProbe();
var publishProbe = this.CreateCleanOutcomePublishProbe();
var coordinator = CreateTestProbe();
var actor = Sys.ActorOf(DriverHostActor.Props(
@@ -95,7 +95,7 @@ public sealed class DriverHostActorFetchAndCacheUnreadableTests : RuntimeActorTe
// A truncated/corrupt cached artifact surfaces as GetCurrentUnkeyedAsync == null (the cache's
// integrity check is deliberately indistinguishable from a miss). Boot must land
// Steady-no-revision and apply NOTHING — never a partial address space.
var publishProbe = CreateTestProbe();
var publishProbe = this.CreateCleanOutcomePublishProbe();
var actor = Sys.ActorOf(DriverHostActor.Props(
new ThrowingDbFactory(), TestNode, ackRouter: null,