Skip to content

Commit 56bf1a0

Browse files
fix(doctor): route read-only check recommendation by failing asset class (#374 Defect 2B)
Read-only `cass doctor check` hardcoded `next_exact_command = cass doctor --fix --json` regardless of what failed. But `--fix` maps to the SafeAutoRun repair mode, whose allowed mutation classes are derived-cleanup only (RetainedPublish Backup / ReclaimableDerivedCache / MemoCache) — it can never repair a failing canonical-archive (`database`) or source-authority check, so it was a dead-end recommendation for exactly the malformed-DB case that most needs guidance. Added `doctor_read_only_next_command`, which routes by the failing check: a canonical-archive / backup / reconstruct-candidate failure -> `cass doctor repair --dry-run --json` (the mode that CAN reconstruct/restore that asset), a source-coverage / inventory / raw-mirror failure -> `cass doctor archive-scan --json`, and otherwise the safe auto-fix path -> `cass doctor --fix --json`. Archive faults are prioritized over incidental cleanup failures. Doctor robot goldens unchanged (healthy fixtures still recommend --fix). Refs #374 (Defect 2B).
1 parent 52de35e commit 56bf1a0

1 file changed

Lines changed: 70 additions & 1 deletion

File tree

src/lib.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31427,6 +31427,41 @@ fn doctor_safe_auto_manual_next_command(check: &DoctorCheckReport) -> &'static s
3142731427
}
3142831428
}
3142931429

31430+
/// Pick the next command to recommend from a read-only `doctor check`.
31431+
///
31432+
/// #374 Defect 2B: the read-only surface used to hardcode `cass doctor --fix
31433+
/// --json`, but `--fix` maps to the SafeAutoRun repair mode whose allowed
31434+
/// mutation classes are derived-cleanup only (RetainedPublishBackup /
31435+
/// ReclaimableDerivedCache / MemoCache). It can never repair a failing
31436+
/// canonical-archive or source-authority check, so recommending it there is a
31437+
/// dead end. Route those failures to the command that can actually address them
31438+
/// (a dry-run reconstruct/restore, or an archive scan), prioritizing the most
31439+
/// fundamental fault; fall back to `--fix` only when the failures are things the
31440+
/// safe auto-fix path genuinely handles (derived cleanup, staging, locks).
31441+
fn doctor_read_only_next_command(check_reports: &[DoctorCheckReport]) -> String {
31442+
let failing = |name: &str| {
31443+
check_reports
31444+
.iter()
31445+
.any(|check| check.name == name && (check.status == "fail" || check.status == "error"))
31446+
};
31447+
if failing("database")
31448+
|| failing("database_backup")
31449+
|| failing("safe_auto_archive_rebuild")
31450+
|| failing("candidate_staging")
31451+
|| failing("coverage_comparison_gate")
31452+
{
31453+
return "cass doctor repair --dry-run --json".to_string();
31454+
}
31455+
if failing("source_coverage")
31456+
|| failing("source_inventory")
31457+
|| failing("raw_mirror")
31458+
|| failing("raw_mirror_backfill")
31459+
{
31460+
return "cass doctor archive-scan --json".to_string();
31461+
}
31462+
"cass doctor --fix --json".to_string()
31463+
}
31464+
3143031465
fn build_doctor_safe_auto_run_report(
3143131466
input: DoctorSafeAutoRunBuildInput<'_>,
3143231467
) -> DoctorSafeAutoRunReport {
@@ -31469,7 +31504,7 @@ fn build_doctor_safe_auto_run_report(
3146931504
report
3147031505
.why_blocked
3147131506
.push("safe auto-run was not requested for this command surface".to_string());
31472-
report.next_exact_command = Some("cass doctor --fix --json".to_string());
31507+
report.next_exact_command = Some(doctor_read_only_next_command(input.check_reports));
3147331508
return report;
3147431509
}
3147531510

@@ -68961,6 +68996,40 @@ paths = ["~/.claude/projects"]
6896168996
);
6896268997
}
6896368998

68999+
#[test]
69000+
fn doctor_read_only_next_command_routes_by_failing_asset_class() {
69001+
// #374 Defect 2B: a read-only `doctor check` must not recommend `--fix`
69002+
// for a failing canonical-archive or source-authority check, since
69003+
// SafeAutoRun (`--fix`) can only mutate derived-cleanup asset classes.
69004+
let fail_db = doctor_check_report("database", "fail", "archive unreadable", false, false);
69005+
assert_eq!(
69006+
doctor_read_only_next_command(std::slice::from_ref(&fail_db)),
69007+
"cass doctor repair --dry-run --json"
69008+
);
69009+
69010+
let fail_source =
69011+
doctor_check_report("source_coverage", "fail", "coverage shrank", false, false);
69012+
assert_eq!(
69013+
doctor_read_only_next_command(std::slice::from_ref(&fail_source)),
69014+
"cass doctor archive-scan --json"
69015+
);
69016+
69017+
// All-pass (or only derived-cleanup findings) keeps the safe auto-fix path.
69018+
let pass_db = doctor_check_report("database", "pass", "Database OK", true, true);
69019+
assert_eq!(
69020+
doctor_read_only_next_command(std::slice::from_ref(&pass_db)),
69021+
"cass doctor --fix --json"
69022+
);
69023+
69024+
// An archive fault outranks an incidental cleanup failure.
69025+
let cleanup =
69026+
doctor_check_report("derivative_cleanup", "fail", "stale cache", true, true);
69027+
assert_eq!(
69028+
doctor_read_only_next_command(&[cleanup, fail_db]),
69029+
"cass doctor repair --dry-run --json"
69030+
);
69031+
}
69032+
6896469033
#[test]
6896569034
fn doctor_source_authority_report_refuses_unverified_mirror_when_archive_missing() {
6896669035
let temp = tempfile::TempDir::new().expect("tempdir");

0 commit comments

Comments
 (0)