From d05ece9ec3e72471e6d1edfaf8c08a0b7e23a2e3 Mon Sep 17 00:00:00 2001 From: Lukas Wuttke Date: Wed, 8 Jul 2026 20:14:34 +0200 Subject: [PATCH] fix(client): hide `client create` from the user surface (installer-internal) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `tracebloc client` offered users `create` — but provisioning is the installer's job (provision.sh calls `client create` with zero flags, cli#137), not a human command. Exposing it is inconsistent (its sibling `list` is already Hidden as "installer-internal, off the user-facing surface") and it's the front door to phantom-minting: a human running `tracebloc client create` STANDALONE mints a client the installer never deploys — an orphaned phantom (backend#970, the root of the cluster_conflict saga). Mark `create` Hidden, mirroring `list`. It stays fully callable — including `create --help`, so provision.sh's `_cli_supports_provisioning` probe is unaffected — but is no longer advertised. `tracebloc client` now shows only the user-useful `status`. Test: TestClientSubcommandVisibility pins create+list Hidden, status visible, and create still runnable (hidden != disabled). Full suite green; gofmt -s / errcheck / ineffassign / misspell clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/cli/client.go | 9 ++++++++- internal/cli/client_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/internal/cli/client.go b/internal/cli/client.go index 4277bb0..cbbbc42 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -64,7 +64,14 @@ func newClientCreateCmd() *cobra.Command { cmd := &cobra.Command{ Use: "create", Short: "Provision a tracebloc client for this machine (auto-named; no flags required)", - Args: cobra.NoArgs, + // HIDDEN: provisioning is the installer's job — provision.sh calls this with + // zero flags (cli#137). It stays fully callable (including `--help`, so the + // installer's capability probe still works), but is kept off the user-facing + // surface: a human running `client create` STANDALONE mints a client the + // installer never deploys — an orphaned "phantom" (backend#970). Mirrors the + // hidden `list`; leaves `tracebloc client` showing only the user-useful `status`. + Hidden: true, + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { return runClientCreate(cmd.Context(), printerFor(cmd), clientPrompter(), clientCreateOpts{name: name, location: location, kubeconfigPath: kubeconfigPath, contextOverride: contextOverride, credentialFile: credentialFile, yes: yes}) diff --git a/internal/cli/client_test.go b/internal/cli/client_test.go index a721d84..eb5718d 100644 --- a/internal/cli/client_test.go +++ b/internal/cli/client_test.go @@ -1338,3 +1338,29 @@ func TestClientStatus_WaitCtrlCIsSilent(t *testing.T) { t.Errorf("Ctrl-C should exit silently (nil-inner exitError), got: %v", err) } } + +func TestClientSubcommandVisibility(t *testing.T) { + // `create` and `list` are installer-internal — Hidden so a user isn't invited to + // run them (a standalone `tracebloc client create` mints a client the installer + // never deploys, i.e. an orphaned phantom, backend#970). `status` stays + // user-visible. Hidden != disabled: all remain runnable (the installer still + // invokes create/list). + hidden := map[string]bool{} + runnable := map[string]bool{} + for _, c := range newClientCmd().Commands() { + hidden[c.Name()] = c.Hidden + runnable[c.Name()] = c.RunE != nil + } + if !hidden["create"] { + t.Error("client create must be Hidden (installer-internal; standalone mints a phantom)") + } + if !hidden["list"] { + t.Error("client list must stay Hidden") + } + if hidden["status"] { + t.Error("client status must stay user-visible") + } + if !runnable["create"] { + t.Error("hidden create must still be runnable (the installer invokes it)") + } +}