From 7b6454eb27dc28d1aba6314ffbadcf2032a70e56 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Tue, 3 Nov 2026 01:07:26 -0800 Subject: [PATCH] fix(retention): retry failed graph staging releases Preserve an earlier staging-release failure when the independent graph replay release page reports Complete, Retained, or MoreWork. That keeps the existing short retry cadence instead of parking failed staging cleanup behind the full maintenance interval. Refs #4616 --- .../src/store_maintenance/graph_replay.rs | 53 +++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/crates/tracedecay-maintenance/src/store_maintenance/graph_replay.rs b/crates/tracedecay-maintenance/src/store_maintenance/graph_replay.rs index eb098b8877..e4f22a9bc0 100644 --- a/crates/tracedecay-maintenance/src/store_maintenance/graph_replay.rs +++ b/crates/tracedecay-maintenance/src/store_maintenance/graph_replay.rs @@ -7,6 +7,7 @@ use tracedecay_code_index_retention::code_index_generations::{ }; use tracedecay_runtime_core::logging::log_daemon_event; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ReconcileOutcome { /// The page from the cursor held no queued release event. Complete, @@ -26,6 +27,20 @@ pub enum ReconcileOutcome { Failed, } +/// Keep a failed staging release visible even if an independent replay-release +/// page succeeds. Otherwise the maintenance owner treats the tick as complete +/// and can park staging cleanup behind the full maintenance interval. +fn with_staging_release_failure( + page_outcome: ReconcileOutcome, + staging_failed: bool, +) -> ReconcileOutcome { + if staging_failed { + ReconcileOutcome::Failed + } else { + page_outcome + } +} + /// Every code-generation degradation is a genuine anomaly: it keeps the tick /// summary loud and logs on every attempt. pub fn log_code_generation_retention_degraded( @@ -166,9 +181,10 @@ pub async fn reconcile_graph_replay_releases( staging_cursor, ) .await; - match staging_release { + let staging_failed = match staging_release { Ok(continuation) => { observations.record_graph_staging_release_cursor(project_root, continuation); + false } Err(error) => { log_code_generation_retention_degraded_with_error( @@ -176,8 +192,9 @@ pub async fn reconcile_graph_replay_releases( "graph_staging_release_failed", &error, ); + true } - } + }; // One bounded page per pass, resumed from the durable-queue cursor of the // previous attempt. Retained releases fall behind the cursor instead of // blocking every later page, and a backlog drains across short-cadence @@ -197,7 +214,7 @@ pub async fn reconcile_graph_replay_releases( }; if page.releases.is_empty() { observations.record_graph_replay_release_served(project_root, page.continuation); - return ReconcileOutcome::Complete; + return with_staging_release_failure(ReconcileOutcome::Complete, staging_failed); } let mut retained = false; let mut released = false; @@ -242,11 +259,39 @@ pub async fn reconcile_graph_replay_releases( } let more_work = page.continuation.is_some(); observations.record_graph_replay_release_served(project_root, page.continuation); - if more_work || released { + let page_outcome = if more_work || released { ReconcileOutcome::MoreWork } else if retained { ReconcileOutcome::Retained } else { ReconcileOutcome::Complete + }; + with_staging_release_failure(page_outcome, staging_failed) +} + +#[cfg(test)] +mod tests { + use super::{ReconcileOutcome, with_staging_release_failure}; + + #[test] + fn staging_release_failure_cannot_be_hidden_by_release_page_result() { + for page_outcome in [ + ReconcileOutcome::Complete, + ReconcileOutcome::MoreWork, + ReconcileOutcome::Retained, + ReconcileOutcome::Deferred, + ReconcileOutcome::Failed, + ] { + assert_eq!( + with_staging_release_failure(page_outcome, true), + ReconcileOutcome::Failed, + "failed staging release must keep the maintenance retry cadence", + ); + assert_eq!( + with_staging_release_failure(page_outcome, false), + page_outcome, + "successful staging release must preserve the independent page result", + ); + } } }