Skip to main content

ralph/queue/repair/
apply.rs

1//! Purpose: Undo-backed apply, validation, and persistence for queue repair.
2//!
3//! Responsibilities:
4//! - Plan a repair, then validate, snapshot undo, and save when changes exist.
5//! - Provide the public `apply_queue_repair_with_undo` and
6//!   `apply_queue_maintenance_repair_with_undo` entry points.
7//! - Validate planned mutations against the queue-set rules before saving.
8//! - Persist active and done queue files only when their respective halves of
9//!   the plan changed.
10//!
11//! Scope:
12//! - Apply orchestration only; planning lives in `planning.rs`.
13//! - Lock acquisition lives in `crate::queue` callers; this module assumes the
14//!   caller already holds the queue lock.
15//!
16//! Usage:
17//! - CLI, machine, and doctor recovery surfaces call the apply entry points to
18//!   normalize recoverable queue inconsistencies safely.
19//!
20//! Invariants/Assumptions:
21//! - No-change plans short-circuit before any validation, undo snapshot, or save.
22//! - When changes exist, validation runs before the undo snapshot is taken so
23//!   bad plans cannot rewrite the queue.
24//! - Saves are gated by per-half change flags so unmodified files stay untouched.
25
26use super::planning::{plan_queue_maintenance_repair, plan_queue_repair};
27use super::types::{QueueRepairPlan, RepairReport};
28use crate::config::Resolved;
29use crate::constants::queue::DEFAULT_MAX_DEPENDENCY_DEPTH;
30use crate::lock::DirLock;
31use crate::queue::{save_queue, validation};
32use anyhow::Result;
33use std::path::Path;
34
35pub fn apply_queue_repair_with_undo(
36    resolved: &Resolved,
37    _queue_lock: &DirLock,
38    operation: &str,
39) -> Result<RepairReport> {
40    apply_repair_plan_with_undo(
41        resolved,
42        operation,
43        plan_queue_repair(
44            &resolved.queue_path,
45            &resolved.done_path,
46            &resolved.id_prefix,
47            resolved.id_width,
48        )?,
49    )
50}
51
52pub fn apply_queue_maintenance_repair_with_undo(
53    resolved: &Resolved,
54    _queue_lock: &DirLock,
55    operation: &str,
56) -> Result<RepairReport> {
57    apply_repair_plan_with_undo(
58        resolved,
59        operation,
60        plan_queue_maintenance_repair(
61            &resolved.queue_path,
62            &resolved.done_path,
63            &resolved.id_prefix,
64            resolved.id_width,
65        )?,
66    )
67}
68
69fn apply_repair_plan_with_undo(
70    resolved: &Resolved,
71    operation: &str,
72    plan: QueueRepairPlan,
73) -> Result<RepairReport> {
74    let report = plan.report().clone();
75
76    if !plan.has_changes() {
77        return Ok(report);
78    }
79
80    validate_repair_plan(&plan, &resolved.id_prefix, resolved.id_width)?;
81    crate::undo::create_undo_snapshot(resolved, operation)?;
82    save_repair_plan(&resolved.queue_path, &resolved.done_path, &plan)?;
83    Ok(report)
84}
85
86fn validate_repair_plan(plan: &QueueRepairPlan, id_prefix: &str, id_width: usize) -> Result<()> {
87    validation::validate_queue_set(
88        &plan.active,
89        Some(&plan.done),
90        id_prefix,
91        id_width,
92        DEFAULT_MAX_DEPENDENCY_DEPTH,
93    )?;
94    Ok(())
95}
96
97fn save_repair_plan(queue_path: &Path, done_path: &Path, plan: &QueueRepairPlan) -> Result<()> {
98    if plan.queue_changed {
99        save_queue(queue_path, &plan.active)?;
100    }
101    if plan.done_changed {
102        save_queue(done_path, &plan.done)?;
103    }
104    Ok(())
105}