1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// SPDX-License-Identifier: MIT OR Apache-2.0
//! G119 L3 + L5 — `wal-stats` and `wal-heal` subcommands.
//!
//! `wal-stats` is read-only local diagnostics (G119 L5). `wal-heal` is the
//! explicit operator-facing version of the auto-heal pass (G119 L3)
//! that future releases will run on startup.
//!
//! Workload: I/O-bound (WalkParallel discovery + multi-journal parse).
//! Parallelism: inherits `wal::walk_journal_paths` (WalkParallel) and
//! `par_iter` parse/classify/unlink in stats/heal. Bound: process-wide pool.
use anyhow::Result;
use crate::cli::{GlobalArgs, WalHealArgs, WalStatsArgs};
use crate::output::NdjsonWriter;
/// Emit a NDJSON snapshot of the workspace's WAL sidecar state.
///
/// Read-only and safe to call from any context. Used by local gates and
/// agent health checks to detect accumulating junk before it pollutes
/// `git status --porcelain`.
#[tracing::instrument(skip_all, fields(command = "wal-stats"))]
pub fn cmd_wal_stats(
args: &WalStatsArgs,
global: &GlobalArgs,
writer: &mut NdjsonWriter<impl std::io::Write>,
) -> Result<()> {
let workspace = global.resolve_workspace()?;
if args.dry_run {
let plan = crate::ndjson_types::DryRunPlan {
r#type: "plan",
operation: "wal-stats".into(),
path: workspace.display().to_string(),
would_modify: false,
details: Some("scan workspace for .atomwrite.journal.*.json sidecars".into()),
};
writer.write_event(&plan)?;
return Ok(());
}
let stats = crate::wal::compute_wal_stats(&workspace)?;
writer.write_event(&stats)?;
Ok(())
}
/// Remove stale terminal journals (G119 L3).
///
/// Walks the workspace, removes every `Committed`/`Aborted` sidecar
/// whose last entry is older than `--threshold-secs`, and emits a
/// NDJSON report. `Started` sidecars are NEVER removed (they are
/// potential orphans that need `recover_orphan_journals`).
#[tracing::instrument(skip_all, fields(command = "wal-heal"))]
pub fn cmd_wal_heal(
args: &WalHealArgs,
global: &GlobalArgs,
writer: &mut NdjsonWriter<impl std::io::Write>,
) -> Result<()> {
let workspace = global.resolve_workspace()?;
if args.dry_run {
let stats = crate::wal::compute_wal_stats(&workspace)?;
// B-003: honest would_modify — 0 journals means no mutation planned.
let would_modify = stats.total_journals > 0;
let plan = crate::ndjson_types::DryRunPlan {
r#type: "plan",
operation: "wal-heal".into(),
path: workspace.display().to_string(),
would_modify,
details: Some(format!(
"would remove up to {} terminal journals older than {}s (preserving Started orphans)",
stats.total_journals, args.threshold_secs
)),
};
writer.write_event(&plan)?;
return Ok(());
}
let report =
crate::wal::auto_heal_on_startup(&workspace, args.threshold_secs, args.max_duration_ms)?;
writer.write_event(&report)?;
Ok(())
}