From 9633ab35a69ed5b2f4c4596dc67b54a2b63e90ad Mon Sep 17 00:00:00 2001 From: Timmy Date: Fri, 10 Apr 2026 10:52:42 +0100 Subject: [PATCH] fix: validate_story_dirs reads filesystem shadows instead of global CRDT singleton (bug 525) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post-520 migration changed validate_story_dirs to read from pipeline_state::read_all_typed() (the process-global CRDT singleton), ignoring its root: &Path argument. This broke test isolation — tests creating a tempdir saw dozens of results from ambient CRDT state, causing non-deterministic failures that blocked every mergemaster gate. Remove the CRDT singleton block and rely on the filesystem shadow scan that already uses the root argument correctly. 1845/1845 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) --- server/src/http/workflow/mod.rs | 50 +++------------------------------ 1 file changed, 4 insertions(+), 46 deletions(-) diff --git a/server/src/http/workflow/mod.rs b/server/src/http/workflow/mod.rs index 7d71d452..083d03fb 100644 --- a/server/src/http/workflow/mod.rs +++ b/server/src/http/workflow/mod.rs @@ -344,47 +344,10 @@ pub fn validate_story_dirs( ) -> Result, String> { let mut results = Vec::new(); - // Validate from typed projection + content store. - { - let typed_items = crate::pipeline_state::read_all_typed(); - for item in typed_items { - use crate::pipeline_state::Stage; - if !matches!(item.stage, Stage::Backlog | Stage::Coding) { - continue; - } - let sid = item.story_id.0.clone(); - if let Some(content) = crate::db::read_content(&sid) { - match parse_front_matter(&content) { - Ok(meta) => { - let mut errors = Vec::new(); - if meta.name.is_none() { - errors.push("Missing 'name' field".to_string()); - } - if errors.is_empty() { - results.push(StoryValidationResult { - story_id: sid, - valid: true, - error: None, - }); - } else { - results.push(StoryValidationResult { - story_id: sid, - valid: false, - error: Some(errors.join("; ")), - }); - } - } - Err(e) => results.push(StoryValidationResult { - story_id: sid, - valid: false, - error: Some(e.to_string()), - }), - } - } - } - } - - // Filesystem fallback: also check work/ directories. + // Validate from filesystem shadows under the given root. + // NOTE: We intentionally read the filesystem here (not the global CRDT + // singleton) so that tests can pass an isolated tempdir and get + // deterministic results. See bug 525. let dirs_to_validate = vec![ root.join(".huskies").join("work").join("2_current"), root.join(".huskies").join("work").join("1_backlog"), @@ -409,11 +372,6 @@ pub fn validate_story_dirs( .unwrap_or_default() .to_string(); - // Skip if already validated from CRDT. - if results.iter().any(|r| r.story_id == story_id) { - continue; - } - let contents = std::fs::read_to_string(&path) .map_err(|e| format!("Failed to read {}: {e}", path.display()))?; match parse_front_matter(&contents) {