From 28b82d76ad0cf010be9c3fedfe1f27f409212406 Mon Sep 17 00:00:00 2001 From: Val Alexander <68980965+BunsDev@users.noreply.github.com> Date: Tue, 21 Jul 2026 21:53:22 -0500 Subject: [PATCH] fix: make veto-deadline recovery test deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit threads_scheduler_recovers_veto_claimed_before_deadline staged a proposal with a real 2-second veto window and raced the wall clock twice: the interrupted reject had to durably record claimed_at within 2s of staging (slow Windows runners exceed this, so recovery correctly reported the veto window closed and the test failed), and a fixed 2.1s sleep had to outlast the deadline. Drive both conditions through durable state instead, following the sibling scheduler tests' idioms: stage with a 300s window backdated 10 minutes so the deadline has already elapsed (deleting the sleep), and pin the durable decisionRequest.claimed_at inside the window to model a timely claim. Recovery judges veto timeliness by the durable claimed_at alone, so the covered scenario — timely claim, post-deadline replay — is unchanged; it just no longer races the scheduler's clock. Test-only change; the test now runs in ~60ms instead of 2.1s+. Closes #455 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Val Alexander <68980965+BunsDev@users.noreply.github.com> --- crates/coven-cli/src/api.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/crates/coven-cli/src/api.rs b/crates/coven-cli/src/api.rs index 0f4642f9..cdaef3fd 100644 --- a/crates/coven-cli/src/api.rs +++ b/crates/coven-cli/src/api.rs @@ -9620,13 +9620,16 @@ tier = 0 let temp = tempfile::tempdir()?; let home = temp.path(); let veto = coven_threads_core::VetoWindow::new( - std::time::Duration::from_secs(2), + std::time::Duration::from_secs(300), std::time::Duration::ZERO, ); - let (_, proposal_id) = stage_scheduled_reviewed_edit( + // Stage in the past so the veto deadline (staged_at + 300s) has + // already elapsed by the time recovery runs — no wall-clock sleep. + let staged_at = time::OffsetDateTime::now_utc() - time::Duration::minutes(10); + let (pending_path, proposal_id) = stage_scheduled_reviewed_edit( home, coven_threads_core::ApprovalPath::FamiliarCoherence { veto }, - time::OffsetDateTime::now_utc(), + staged_at, )?; set_proposal_decision_failpoint(Some(( ProposalDecisionFailpoint::ClaimBeforeValidation, @@ -9641,7 +9644,26 @@ tier = 0 Some(&decision_body), ); assert!(interrupted.is_err()); - std::thread::sleep(std::time::Duration::from_millis(2_100)); + + // The interrupted claim durably recorded claimed_at = wall-clock now, + // which is after the backdated deadline. Recovery judges veto + // timeliness by the durable claimed_at alone, so pin it inside the + // window to model a claim that landed before the deadline. This keeps + // the scenario — timely claim, post-deadline replay — deterministic + // instead of racing a real clock (flaky on slow CI runners, #455). + let raw = std::fs::read_to_string(&pending_path)?; + let mut value: Value = serde_json::from_str(&raw)?; + let mut request = proposal_decision_request(&value)? + .context("interrupted reject left a durable decision request")?; + request.claimed_at = staged_at + time::Duration::seconds(1); + value + .as_object_mut() + .context("pending proposal is a JSON object")? + .insert( + "decisionRequest".to_string(), + serde_json::to_value(&request)?, + ); + std::fs::write(&pending_path, serde_json::to_vec_pretty(&value)?)?; let recovered = process_due_threads_proposals(home)?; assert_eq!(recovered, 1);