From a7315e45b925b3235c9fdc5360a3157f8ed0c08e Mon Sep 17 00:00:00 2001 From: Gustavo Bertoi Date: Mon, 29 Jun 2026 12:56:55 -0300 Subject: [PATCH] =?UTF-8?q?feat(doctor):=20X6=20(part)=20=E2=80=94=20trust?= =?UTF-8?q?=20readiness=20probe=20in=20the=20doctor=20matrix=20(spec=2005/?= =?UTF-8?q?13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `trust (mkcert)` probe to `devstack doctor`: it reports local-CA readiness (mkcert on PATH, CAROOT rootCA.pem, certutil for Firefox/NSS) with the exact remediation, as a WARNING (local HTTPS is opt-in, so a missing CA never fails doctor). This is the decision-#3 self-verification for the sudo-gated trust feature (N2) and a step toward the full X6 doctor matrix + safe --fix. Test: doctor --json lists the trust probe. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/cli/doctor.go | 15 +++++++++++++++ internal/cli/gc_test.go | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/internal/cli/doctor.go b/internal/cli/doctor.go index 3a4d227..3ad6792 100644 --- a/internal/cli/doctor.go +++ b/internal/cli/doctor.go @@ -9,6 +9,7 @@ import ( "github.com/open-source-cloud/devstack/internal/docker" "github.com/open-source-cloud/devstack/internal/state" + "github.com/open-source-cloud/devstack/internal/trust" "github.com/open-source-cloud/devstack/internal/xdg" ) @@ -134,6 +135,20 @@ func runDoctor(cmd *cobra.Command) []docker.Check { checks = append(checks, docker.Check{Name: "state ledger", Status: docker.StatusOK, Detail: fmt.Sprintf("schema v%d @ %s", v, ctxName)}) } + // Local-CA trust (spec 05) — opt-in, so never fatal: report readiness as a + // warning with the exact remediation when not fully set up. + ts := trust.New().Status(ctx) + if ts.OK() { + checks = append(checks, docker.Check{Name: "trust (mkcert)", Status: docker.StatusOK, Detail: "local CA installed (" + ts.CARoot + ")"}) + } else { + checks = append(checks, docker.Check{ + Name: "trust (mkcert)", + Status: docker.StatusWarn, + Detail: fmt.Sprintf("mkcert=%v CA=%v firefox=%v", ts.MkcertFound, ts.CAInstalled, ts.FirefoxTrust), + Remediation: ts.Remediation, + }) + } + return checks } diff --git a/internal/cli/gc_test.go b/internal/cli/gc_test.go index b98e662..ca7c4c4 100644 --- a/internal/cli/gc_test.go +++ b/internal/cli/gc_test.go @@ -45,3 +45,17 @@ func TestDoctorRebuildStateFlag(t *testing.T) { t.Error("doctor is missing the --rebuild-state flag") } } + +func TestDoctorIncludesTrustProbe(t *testing.T) { + var out strings.Builder + root := NewRootCmd(Options{}) + root.SetArgs([]string{"doctor", "--json"}) + root.SetOut(&out) + root.SetErr(&out) + // doctor exits non-zero only on a hard FAIL; the trust probe is a warning, so + // in CI (no mkcert) doctor still succeeds and the JSON lists the probe. + _ = root.Execute() + if !strings.Contains(out.String(), `"trust (mkcert)"`) { + t.Errorf("doctor --json missing the trust probe:\n%s", out.String()) + } +}