From 83f7e41932531fb7704cc6ee2882142131814a65 Mon Sep 17 00:00:00 2001 From: dave Date: Tue, 28 Apr 2026 10:12:33 +0000 Subject: [PATCH] huskies: merge 780 --- server/src/db/mod.rs | 54 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/server/src/db/mod.rs b/server/src/db/mod.rs index cd46a08b..5815f921 100644 --- a/server/src/db/mod.rs +++ b/server/src/db/mod.rs @@ -305,7 +305,7 @@ pub fn move_item_stage( _ => None, }; - let (name, agent, retry_count, blocked, depends_on) = content + let (name, agent, _ignored_retry_count, blocked, depends_on) = content .as_deref() .or(current_content.as_deref()) .and_then(|c| parse_front_matter(c).ok()) @@ -322,6 +322,14 @@ pub fn move_item_stage( }) .unwrap_or((None, None, None, None, None)); + // Bug 780: stage transitions reset retry_count to 0. retry_count tracks + // attempts at THIS stage's work (coding, merging, qa); a fresh attempt at + // a new stage is conceptually distinct from prior attempts at a different + // stage. `blocked` is preserved — that's a human-set signal that survives + // transitions. Note: passing None to write_item is a no-op on the CRDT + // register (the old value is kept), so we must pass Some(0) to force reset. + let retry_count: Option = Some(0); + // CRDT stage transition. let merged_at_ts = if crate::pipeline_state::Stage::from_dir(new_stage) .is_some_and(|s| matches!(s, crate::pipeline_state::Stage::Done { .. })) @@ -706,4 +714,48 @@ mod tests { row.map(|r| r.0) ); } + + /// Bug 780: stage transitions must reset retry_count to 0 in the CRDT. + /// Carryover from prior-stage retries was tripping the auto-assigner's + /// deterministic-merge skip logic. + #[test] + fn move_item_stage_resets_retry_count_to_zero() { + ensure_content_store(); + let story_id = "9870_story_780_retry_reset"; + + // Seed the story in 2_current with retry_count = 3 (a coder that + // burned all its retries). + crate::crdt_state::write_item( + story_id, + "2_current", + Some("Retry reset test"), + None, + Some(3), + None, + None, + None, + None, + None, + ); + write_content( + story_id, + "---\nname: Retry reset test\nretry_count: 3\n---\n", + ); + let typed = crate::pipeline_state::read_typed(story_id) + .expect("read should succeed") + .expect("story exists in CRDT"); + assert_eq!(typed.retry_count, 3); + + // Promote to 4_merge. retry_count must reset. + move_item_stage(story_id, "4_merge", None); + + let typed_after = crate::pipeline_state::read_typed(story_id) + .expect("read should succeed") + .expect("story exists in CRDT"); + assert_eq!(typed_after.stage.dir_name(), "4_merge"); + assert_eq!( + typed_after.retry_count, 0, + "retry_count must reset to 0 on stage transition" + ); + } }