fix(drivers): fail the apply when the artifact could not be read (#486)
ApplyAndAck advanced _currentRevision and recorded NodeDeploymentState=Applied immediately after ReconcileDrivers, which returns null when the artifact could not be read. So a node that applied NOTHING reported success, claimed a revision whose configuration it never applied, and — because HandleDispatchFromSteady short-circuits on a revision match — could never be healed by re-dispatching that revision. Only a later, different revision recovered it. Now a null blob fails the apply: the revision is left where it was, NodeDeploymentState records Failed with the reason, and the coordinator gets a Failed ACK. Leaving the revision alone is what makes the retry actually land instead of being waved through. This is about telling the truth, not about tearing anything down — the node keeps serving its last-known-good address space, drivers and subscriptions throughout (#485). Tests, RED-first (both verified failing with "should be Failed but was Applied"): the ACK/revision assertion, plus the one that matters — after a failed apply, re-dispatching the SAME revision now genuinely applies. Its recovered artifact adds a second driver precisely so a short-circuited ACK (which has no side effects) cannot satisfy it. Four existing tests changed, and both changes are deliberate: - DriverHostActorTests.SeedDeployment never set ArtifactBlob at all. Its three consumers are about the apply/ACK/state machine, not the artifact, and now need a genuine apply — so the helper seeds a well-formed artifact declaring no drivers. That is what the composer emits for an empty configuration, and is precisely what "bytes we could not read" is NOT. - EmptyArtifact_IsNotCached asserted "the apply still succeeds — an empty artifact is a legitimate no-op deployment". That premise is the bug. Flipped to Failed; the test's actual subject (nothing is cached) is untouched and now holds for two independent reasons. Closes #486 Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -262,8 +262,25 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase
|
||||
Status = status,
|
||||
CreatedBy = "test",
|
||||
SealedAtUtc = status == DeploymentStatus.Sealed ? DateTime.UtcNow : null,
|
||||
// A REAL (if empty-of-content) artifact. These tests are about the apply/ACK/state machine,
|
||||
// not about the artifact, and used to omit the blob entirely — but since #486 an unreadable
|
||||
// artifact (which a missing blob is indistinguishable from) correctly FAILS the apply, so an
|
||||
// artifact-less row would no longer exercise the success path they are asserting on. A
|
||||
// configuration that declares no drivers is the genuine "nothing to run" deployment.
|
||||
ArtifactBlob = EmptyButRealArtifact,
|
||||
});
|
||||
ctx.SaveChanges();
|
||||
return id;
|
||||
}
|
||||
|
||||
/// <summary>A well-formed artifact declaring no drivers — what the composer emits for a configuration
|
||||
/// with nothing in it, as distinct from bytes that could not be read.</summary>
|
||||
private static readonly byte[] EmptyButRealArtifact = JsonSerializer.SerializeToUtf8Bytes(new
|
||||
{
|
||||
RawFolders = Array.Empty<object>(),
|
||||
DriverInstances = Array.Empty<object>(),
|
||||
Devices = Array.Empty<object>(),
|
||||
TagGroups = Array.Empty<object>(),
|
||||
Tags = Array.Empty<object>(),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user