From 212b605e74dccf60d7ce19368ccf15c9aec295c6 Mon Sep 17 00:00:00 2001 From: Gustavo Bertoi Date: Mon, 29 Jun 2026 14:18:00 -0300 Subject: [PATCH] =?UTF-8?q?feat(doctor):=20X6=20=E2=80=94=20safe=20`doctor?= =?UTF-8?q?=20--fix`=20(non-destructive=20reconcile)=20(spec=2013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire `--fix` to the only strictly non-destructive remediation: the self-healing ledger reconcile (prune ref rows for projects no longer live), via Manager.Reconcile. It NEVER drops a volume/DB/container — those stay explicit- confirmation jobs (`shared gc`, `workspace destroy`). Best-effort: a down daemon or non-workspace dir is reported, not fatal. With the trust/dns/shared probes (PRs #33/#35/#48) this completes X6: the full doctor matrix + a safe --fix. Verified: `doctor --fix` outside a workspace prints "reconcile skipped" and exits 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/cli/doctor.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/internal/cli/doctor.go b/internal/cli/doctor.go index 33134e5..668aabc 100644 --- a/internal/cli/doctor.go +++ b/internal/cli/doctor.go @@ -40,7 +40,7 @@ func newDoctorCmd(g *GlobalOpts) *cobra.Command { } renderChecks(cmd, checks) if fix { - fmt.Fprintln(cmd.OutOrStdout(), "\n(--fix has no automatic remediations wired yet; planned with M6 doctor)") + doctorFix(cmd) } for _, c := range checks { if c.Status == docker.StatusFail { @@ -55,6 +55,28 @@ func newDoctorCmd(g *GlobalOpts) *cobra.Command { return cmd } +// doctorFix applies the STRICTLY non-destructive remediations (spec 13): the +// self-healing ledger reconcile (prune ref rows for projects no longer live). It +// never drops a volume/DB/container — those are explicit-confirmation jobs +// (`shared gc`, `workspace destroy`). Best-effort: a down daemon or no workspace +// is reported, not fatal. +func doctorFix(cmd *cobra.Command) { + w := cmd.OutOrStdout() + fmt.Fprintln(w, "\n--fix: applying safe remediations…") + mgr, closeFn, err := buildManager(cmd) + if err != nil { + fmt.Fprintf(w, " reconcile skipped: %v\n", err) + return + } + defer closeFn() + pruned, err := mgr.Reconcile(cmd.Context()) + if err != nil { + fmt.Fprintf(w, " reconcile skipped: %v\n", err) + return + } + fmt.Fprintf(w, " reconciled the ledger: pruned %d stale ref row(s)\n", len(pruned)) +} + // rebuildLedger reconstructs the shared_service + ref ledger from on-disk config // intersected with live container labels (spec 09 §crash-recovery). func rebuildLedger(cmd *cobra.Command, g *GlobalOpts) error {