huskies: merge 1052
This commit is contained in:
@@ -55,8 +55,8 @@ pub use types::{
|
||||
pub use write::{
|
||||
bump_retry_count, migrate_legacy_stage_strings, migrate_merge_job, migrate_names_from_slugs,
|
||||
migrate_node_claims_to_agent_claims, migrate_story_ids_to_numeric, name_from_story_id,
|
||||
set_agent, set_depends_on, set_epic, set_item_type, set_name, set_plan_state, set_qa_mode,
|
||||
set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
||||
purge_done_stage_merge_jobs, set_agent, set_depends_on, set_epic, set_item_type, set_name,
|
||||
set_plan_state, set_qa_mode, set_resume_to, set_resume_to_raw, set_retry_count, write_item,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -650,6 +650,61 @@ pub fn migrate_merge_job(db_path: &std::path::Path) {
|
||||
slog!("[crdt] Migrated {count} MergeJob entries to typed MergeResult format");
|
||||
}
|
||||
|
||||
/// Delete MergeJob CRDT entries for stories that have reached a terminal stage
|
||||
/// (Done, Archived, Abandoned, Superseded, Rejected).
|
||||
///
|
||||
/// Pre-1036 code left stale MergeJob entries after a story recovered from a
|
||||
/// merge failure and transitioned to Done. Those entries caused the gateway
|
||||
/// UI to mislabel recovered stories as "FAILED" because `load_pipeline_state`
|
||||
/// unconditionally read the MergeJob error field for every story. This
|
||||
/// migration removes the orphaned entries so the state is clean on the next
|
||||
/// server start.
|
||||
///
|
||||
/// Running this migration repeatedly is safe — tombstoned entries are filtered
|
||||
/// out by the read path, so subsequent calls are no-ops.
|
||||
pub fn purge_done_stage_merge_jobs() {
|
||||
use crate::pipeline_state::Stage;
|
||||
|
||||
let Some(jobs) = crate::crdt_state::read_all_merge_jobs() else {
|
||||
return;
|
||||
};
|
||||
if jobs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect IDs of every story currently in a terminal stage.
|
||||
let terminal_ids: std::collections::HashSet<String> = crate::pipeline_state::read_all_typed()
|
||||
.into_iter()
|
||||
.filter(|item| {
|
||||
matches!(
|
||||
item.stage,
|
||||
Stage::Done { .. }
|
||||
| Stage::Archived { .. }
|
||||
| Stage::Abandoned { .. }
|
||||
| Stage::Superseded { .. }
|
||||
| Stage::Rejected { .. }
|
||||
)
|
||||
})
|
||||
.map(|item| item.story_id.0.clone())
|
||||
.collect();
|
||||
|
||||
let to_delete: Vec<String> = jobs
|
||||
.into_iter()
|
||||
.filter(|j| terminal_ids.contains(&j.story_id))
|
||||
.map(|j| j.story_id)
|
||||
.collect();
|
||||
|
||||
if to_delete.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let count = to_delete.len();
|
||||
for story_id in &to_delete {
|
||||
crate::crdt_state::delete_merge_job(story_id);
|
||||
}
|
||||
slog!("[crdt] Purged {count} stale MergeJob entries for terminal-stage stories");
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod merge_job_migration_tests {
|
||||
use super::super::super::state::init_for_test;
|
||||
|
||||
@@ -19,4 +19,5 @@ pub use item::write_item_str;
|
||||
pub use migrations::{
|
||||
migrate_legacy_stage_strings, migrate_merge_job, migrate_names_from_slugs,
|
||||
migrate_node_claims_to_agent_claims, migrate_story_ids_to_numeric, name_from_story_id,
|
||||
purge_done_stage_merge_jobs,
|
||||
};
|
||||
|
||||
@@ -116,7 +116,9 @@ pub fn load_pipeline_state(ctx: &AppContext) -> Result<PipelineState, String> {
|
||||
|
||||
// Story 945: review_hold is `Stage::ReviewHold`; qa_mode and epic_id
|
||||
// come from typed CRDT registers. merge_failure detail lives on the
|
||||
// MergeJob CRDT entry (same as status_tools.rs).
|
||||
// MergeJob CRDT entry, but only surfaces for items currently in a
|
||||
// failure stage — Done/terminal items must never inherit stale errors
|
||||
// from a previously-failed merge attempt (story 1052).
|
||||
let view = crate::crdt_state::read_item(sid);
|
||||
let review_hold = if matches!(item.stage, Stage::ReviewHold { .. }) {
|
||||
Some(true)
|
||||
@@ -125,7 +127,14 @@ pub fn load_pipeline_state(ctx: &AppContext) -> Result<PipelineState, String> {
|
||||
};
|
||||
let qa = view.as_ref().and_then(|v| v.qa_mode());
|
||||
let epic_id = view.as_ref().and_then(|v| v.epic());
|
||||
let merge_failure = crate::crdt_state::read_merge_job(sid).and_then(|j| j.error);
|
||||
let merge_failure = if matches!(
|
||||
item.stage,
|
||||
Stage::MergeFailure { .. } | Stage::MergeFailureFinal { .. }
|
||||
) {
|
||||
crate::crdt_state::read_merge_job(sid).and_then(|j| j.error)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let story = UpcomingStory {
|
||||
story_id: sid.clone(),
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn aggregate_pipeline_counts(pipeline: &Value) -> Value {
|
||||
.map(|f| !f.is_null() && f != "")
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_blocked || has_merge_failure {
|
||||
if (is_blocked || has_merge_failure) && stage != "done" {
|
||||
let story_id = item
|
||||
.get("story_id")
|
||||
.and_then(|s| s.as_str())
|
||||
|
||||
@@ -167,6 +167,9 @@ pub(crate) async fn init_subsystems(app_state: &Arc<SessionState>, cwd: &Path) {
|
||||
crdt_state::migrate_merge_job(db_path);
|
||||
// Story 1009: drop legacy node-hex claims that can't be converted to AgentName.
|
||||
crdt_state::migrate_node_claims_to_agent_claims();
|
||||
// Story 1052: remove stale MergeJob entries for terminal-stage
|
||||
// stories so they can never cause "FAILED" labels in the UI.
|
||||
crdt_state::purge_done_stage_merge_jobs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user