From 21849eb09753cccf0286105a4ff1e91ce30f533f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 19 Oct 2026 17:48:03 +0200 Subject: [PATCH] Cut c2b fix: a drain whose stored plan no longer parses is bounded, not unbounded deadline_for_current_activity's drain arm returned None when parsed_inputs failed (the running binary re-parses and re-admits the frozen binding), which left the drain with no deadline, unpaged, and disarmed every waiter behind it. The retire time is now the promotion plus the drain when the binding parses, and otherwise now; the frozen committing duration from it bounds the drain. Co-Authored-By: Claude Opus 5.5 --- src/control_plane.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/control_plane.rs b/src/control_plane.rs index 3169542..0f4385b 100644 --- a/src/control_plane.rs +++ b/src/control_plane.rs @@ -1775,17 +1775,20 @@ impl DeploymentTransaction { return None; } let plan = self.plan.as_ref()?; - let from = match self + // The retire time needs the binding's drain, which the running + // binary re-parses and re-admits: a plan frozen by an older binary + // may not parse now. That must not leave a drain with no deadline, + // so a retire time that cannot be derived is now, and the + // committing duration from it bounds the drain. + let from = self .routing .as_ref() .and_then(RoutingEvidence::promoted_at_unix_millis) - { - Some(promoted_at) => { - route_drain_deadline(promoted_at, plan.parsed_inputs().ok()?.1.rollout.drain_seconds) - .ok()? - } - None => now, - }; + .and_then(|promoted_at| { + let (_, binding) = plan.parsed_inputs().ok()?; + route_drain_deadline(promoted_at, binding.rollout.drain_seconds).ok() + }) + .unwrap_or(now); return Some(PhaseDeadline { phase: self.phase, entered_at_unix_millis: now,