Node records Applied (and advances _currentRevision) for a deployment whose artifact it could not read #486
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found during the #485 live gate (
docs/plans/2026-07-21-issue-485-live-gate.md). Pre-existing — not introduced by the #485 fixes, though those fixes make the skip path more reachable.What happens
DriverHostActor.ApplyAndAckadvances the revision and records success before it knows whether anything was applied:ReconcileDriversreturnsnullon both the (long-standing) throw path and the empty-artifact path added for #485. Observed live on central-2:NodeDeploymentStatefor that deployment readsStatus = Appliedfor central-2.Why it matters
The node claims a revision whose configuration it never applied. Because
HandleDispatchFromSteadyshort-circuits on a revision match (dispatch matches current rev; immediate ACK), re-dispatching that same revision is a no-op — the node cannot self-heal. Only a later deployment carrying a different revision recovers it.The fleet view is also wrong: the coordinator and the AdminUI both believe the node is converged when it is not.
Scope
Reporting/convergence only, not data plane. Thanks to the #485 fixes the node keeps serving its last known-good address space, drivers and subscriptions throughout — verified live (values kept advancing across the event). So this is a lying status, not an outage.
Suggested fix
When
ReconcileDriversreturnsnull, treat the apply as failed: leave_currentRevisionalone, writeNodeDeploymentStatus.Failedwith the reason, andSendAck(..., ApplyAckOutcome.Failed, ...). The normal deploy retry path then heals the node, and the short-circuit stops hiding the gap.Kept out of the #485 fixes deliberately — it changes deploy ACK semantics fleet-wide (a node that today reports Applied would start reporting Failed), so it wants its own change and its own live gate.
Fixed and live-gated on
master(7808faa7). Evidence appended todocs/plans/2026-07-21-issue-485-live-gate.md.A null blob from
ReconcileDriversnow fails the apply: the revision is left where it was,NodeDeploymentStaterecords Failed with the reason, and the coordinator gets a Failed ACK. Leaving the revision alone is the part that matters — it is what makes a retry actually land instead of being waved through by the revision short-circuit.Live results on docker-dev, same induction as #485:
Status = Failedon central-2 with the reason, while the five nodes that read the real artifact record Applied — the fleet view now distinguishes them. Pre-fix all six claimed Applied.apply of a855dd9e... FAILED — ... Staying on revision 5bc0d9f3...— the previous revision, not the dispatched583122b7....keeping the 5 running driver(s)) and the Modbus tag read 59603 with a current timestamp after the Failed apply. This reports the truth; it does not tear anything down.Tests were RED-first (both failed with "should be Failed but was Applied"). The one that carries the weight is the retry test: after a failed apply, re-dispatching the same revision now genuinely applies — its recovered artifact adds a second driver specifically so a short-circuited ACK, which has no side effects, cannot satisfy it.
Four existing tests changed, both changes deliberate:
DriverHostActorTests.SeedDeploymentnever setArtifactBlobat 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 exactly what "bytes we could not read" is not.EmptyArtifact_IsNotCachedasserted "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.