From 6b4a7cab18b2777a4dd03a9159e66f048a545951 Mon Sep 17 00:00:00 2001 From: mintaka Date: Mon, 31 Aug 2026 22:01:07 -0400 Subject: [PATCH] refactor(app): remove the vestigial mode toggle; client is the only mode (RIG-3111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Embedded mode was retired in RIG-2554, so the app no longer supervises a stack and `client` is the only mode; the mode-selection machinery had become dead weight. Go side: the `Mode` type, `ModeClient` const, `String()` method, and `modeStr*` consts are deleted. `Config` is now `{ServerURL, CACert}`. `Parse` drops the mode switch and calls `parseClient` directly after the unknown-key check. The `mode` app.toml key is removed entirely (Option B, hard-remove, no compat shim): with the field gone from the TOML struct, a stale `mode = "client"` line is now an ordinary unknown-key rejection that names the offending key legibly — that IS the migration message. UI side: the `window.__COMPASS_MODE__` global is KEPT — it still distinguishes the shell-from-browser boot path (`shellMode() === "client"` picks the native boot). Only its type union is narrowed from `"embedded" | "client"` to `"client"` and the dead 'embedded' prose is removed. Spec-impact: supersedes compass-native-client-only design §A3 'sanctioned residue' (the mode="embedded" legible-rejection arm) — the mode key is removed entirely per Matt (RIG-3111). Refs RIG-3111 Co-authored-by: Matt Wilkinson --- apps/ui/src/index.tsx | 3 - apps/ui/src/shell-globals.test.ts | 5 -- apps/ui/src/shell-globals.ts | 4 +- go/cmd/compass-app/client_test.go | 3 +- go/cmd/compass-app/main.go | 16 +--- go/e2e/client_mode_test.go | 18 ++--- go/internal/appconfig/appconfig.go | 87 ++++---------------- go/internal/appconfig/appconfig_test.go | 102 ++++++++---------------- go/internal/appconfig/doc.go | 4 +- tools/compass-app-dev/app.toml.example | 4 - 10 files changed, 65 insertions(+), 181 deletions(-) diff --git a/apps/ui/src/index.tsx b/apps/ui/src/index.tsx index 14f467956..7110843b8 100644 --- a/apps/ui/src/index.tsx +++ b/apps/ui/src/index.tsx @@ -24,9 +24,6 @@ if (!root) { // // "client" → bootNativeClient gates on the shell's armed connection (probe + // connect screen), then boots through the SAME main() chain. -// "embedded" → the native embedded provider. T5.6 wires that provider; until -// then embedded shares the env path (a co-hosted server is dialed -// by env just like the browser dev build), so it falls through. // absent → the UNCHANGED browser-dev path: envConnectionProvider. // fixture → the offline fixture boot (§A1), a third BROWSER-ONLY arm checked // first: Vite statically replaces `import.meta.env.MODE`, so in a diff --git a/apps/ui/src/shell-globals.test.ts b/apps/ui/src/shell-globals.test.ts index 2a0bea7d0..af89c3d47 100644 --- a/apps/ui/src/shell-globals.test.ts +++ b/apps/ui/src/shell-globals.test.ts @@ -28,9 +28,4 @@ describe("shellMode / shellServerUrl", () => { expect(shellMode()).toBeUndefined(); expect(shellServerUrl()).toBeUndefined(); }); - - test("distinguish embedded from client mode", () => { - w.__COMPASS_MODE__ = "embedded"; - expect(shellMode()).toBe("embedded"); - }); }); diff --git a/apps/ui/src/shell-globals.ts b/apps/ui/src/shell-globals.ts index 271e3b8ba..9b4390a6b 100644 --- a/apps/ui/src/shell-globals.ts +++ b/apps/ui/src/shell-globals.ts @@ -13,14 +13,14 @@ declare global { interface Window { - __COMPASS_MODE__?: "embedded" | "client"; + __COMPASS_MODE__?: "client"; __COMPASS_SERVER_URL__?: string; } } /** The shell-injected launch mode, or undefined in a browser dev build (no * shell). typeof-guarded so it never throws when `window` is absent. */ -export function shellMode(): "embedded" | "client" | undefined { +export function shellMode(): "client" | undefined { if (typeof window === "undefined") { return undefined; } diff --git a/go/cmd/compass-app/client_test.go b/go/cmd/compass-app/client_test.go index 82f1f8b63..cad869039 100644 --- a/go/cmd/compass-app/client_test.go +++ b/go/cmd/compass-app/client_test.go @@ -27,7 +27,6 @@ import ( // clientConfig is a representative valid native-client config. ServerURL is an // absolute https URL (validated upstream by appconfig); runClient never dials it. var clientConfig = appconfig.Config{ - Mode: appconfig.ModeClient, ServerURL: "https://remote.example:8443", } @@ -126,7 +125,7 @@ func TestRunClientSharesOneTargetAcrossPumpAndService(t *testing.T) { if err := os.WriteFile(caFile, caPEM, 0o600); err != nil { t.Fatalf("writing CA fixture: %v", err) } - cfg := appconfig.Config{Mode: appconfig.ModeClient, ServerURL: srv.URL, CACert: caFile} + cfg := appconfig.Config{ServerURL: srv.URL, CACert: caFile} svc, err := runClient(cfg, t.TempDir()) if err != nil { diff --git a/go/cmd/compass-app/main.go b/go/cmd/compass-app/main.go index 9a0fb6f47..f1a5834f3 100644 --- a/go/cmd/compass-app/main.go +++ b/go/cmd/compass-app/main.go @@ -121,7 +121,7 @@ func run() error { newAppWindow(app, svc, name, "Compass", startupJS) } - slog.Info("compass-app starting", "mode", cfg.Mode, "assets", assetsDir, "version", version) + slog.Info("compass-app starting", "assets", assetsDir, "version", version) return app.Run() } @@ -190,18 +190,10 @@ func firstFreeName(base string, exists func(string) bool) string { // launch wires the native-client bridge service (design §T5.6). Embedded mode — // the in-process stack supervisor — was retired in RIG-2554, so launch is a thin // wrapper over runClient: there is no stack to spawn, monitor, or tear down, and -// no quit controller. The Mode was validated to client by appconfig.Load; the -// default arm rejects any other resolved Mode legibly rather than dialing a -// half-configured target. +// no quit controller. The window opens immediately: no pre-window probe (the +// single auto-connect is the UI's boot-time shellConnect(""), T5.5). func launch(cfg appconfig.Config, stateDir string) (*bridgeService, error) { - switch cfg.Mode { - case appconfig.ModeClient: - // Client mode opens the window immediately: no pre-window probe (the - // single auto-connect is the UI's boot-time shellConnect(""), T5.5). - return runClient(cfg, stateDir) - default: - return nil, fmt.Errorf("unknown app mode %v", cfg.Mode) - } + return runClient(cfg, stateDir) } // shellStartupJS builds the OQ-8 startup script the webview loads before the app diff --git a/go/e2e/client_mode_test.go b/go/e2e/client_mode_test.go index 430bce2cd..a7818800c 100644 --- a/go/e2e/client_mode_test.go +++ b/go/e2e/client_mode_test.go @@ -169,16 +169,14 @@ func TestClientModeHeadlessChain(t *testing.T) { // the door as a bearer, never on a command line. assertTokenNotInCmdlines(t, f.RuntimeDir(), f.AdminToken()) - // A client-mode app.toml carries mode/server_url/ca_cert but NEVER the token - // (the token lives in the tokenstore, DL-109). NOTE: this is a - // design-conformance placeholder, not regression coverage — compass-app has - // no app.toml WRITER yet (embedded.go/main.go only Load it), so this - // constructs the TOML the client setup would write and asserts the shape. - // The real hygiene coverage is the /proc scan + tokenstore legs above; when a - // production client-mode config writer lands, point this at it instead of a - // test-authored literal so it catches a real leak. - appToml := "mode = \"client\"\n" + - "server_url = " + strconv.Quote(f.ServerURL()) + "\n" + + // A client app.toml carries server_url/ca_cert but NEVER the token (the token + // lives in the tokenstore, DL-109). NOTE: this is a design-conformance + // placeholder, not regression coverage — compass-app has no app.toml WRITER + // yet (main.go only Loads it), so this constructs the TOML the client setup + // would write and asserts the shape. The real hygiene coverage is the /proc + // scan + tokenstore legs above; when a production client config writer lands, + // point this at it instead of a test-authored literal so it catches a real leak. + appToml := "server_url = " + strconv.Quote(f.ServerURL()) + "\n" + "ca_cert = " + strconv.Quote(f.CAPath()) + "\n" appTomlPath := filepath.Join(t.TempDir(), "app.toml") if err := os.WriteFile(appTomlPath, []byte(appToml), 0o600); err != nil { diff --git a/go/internal/appconfig/appconfig.go b/go/internal/appconfig/appconfig.go index 4412642ee..a21987327 100644 --- a/go/internal/appconfig/appconfig.go +++ b/go/internal/appconfig/appconfig.go @@ -11,46 +11,14 @@ import ( "github.com/BurntSushi/toml" ) -// Mode is the native app's operating mode. Client is the only mode — embedded -// mode was retired in RIG-2554 (the app no longer supervises a stack). Mode is -// kept as a validation-only concept: Load/Parse only ever yield ModeClient or a -// legible error, and the shell's launch dispatch keeps a default rejection arm. -type Mode int - -const ( - // ModeClient connects to a remote compass-server over its authenticated - // loopback/network door; it requires a ServerURL and may carry a CACert. - ModeClient Mode = iota -) - -// modeStrClient is the canonical client mode string as written in app.toml — the -// single source of truth shared by String and Parse. -const modeStrClient = "client" - -// modeStrEmbedded is the retired mode value. It is NOT a supported mode: it -// parses to a legible rejection (see Parse) naming the retirement, not a -// compatibility arm. -const modeStrEmbedded = "embedded" - -// String renders the mode as it is written in app.toml (the TOML mode value), -// for logs and round-tripping. -func (m Mode) String() string { - switch m { - case ModeClient: - return modeStrClient - default: - return fmt.Sprintf("Mode(%d)", int(m)) - } -} - -// Config is the resolved app configuration. It carries neither the bearer token -// (OS keychain, DL-109) nor the caller account id (WhoAmI RPC, DL-111); neither -// lives in the config file. +// Config is the resolved app configuration for the native Compass app, which is +// a client: it connects to a remote compass-server over its authenticated +// loopback/network door. Config carries neither the bearer token (OS keychain, +// DL-109) nor the caller account id (WhoAmI RPC, DL-111); neither lives in the +// config file. type Config struct { - // Mode is the resolved operating mode. Client is the only mode. - Mode Mode // ServerURL is the native-client base URL (an absolute https URL). It is - // always required (client is the only mode). + // always required. ServerURL string // CACert is an optional path to a private trust anchor (PEM) for a // native-client connection whose server presents a private-CA certificate. @@ -61,21 +29,13 @@ type Config struct { // fileConfig is the on-disk TOML shape. It is decoded and then validated into a // Config. type fileConfig struct { - Mode string `toml:"mode"` ServerURL string `toml:"server_url"` CACert string `toml:"ca_cert"` } // Parse decodes and validates an app.toml byte slice into a Config. It performs -// no I/O. The rules (design §A3, client-only): -// - absent/empty mode or mode="client" → client mode, which REQUIRES a -// non-empty server_url that parses as an absolute https URL (ca_cert is -// optional); -// - mode="embedded" → a legible rejection naming the retirement (embedded mode -// was retired; run a headless stack with `compass-stack up` and point the -// app at it in client mode). This is an error string, NOT a compatibility -// arm (Global Constraint 5's sanctioned residue). -// - any other mode value is an error naming the one valid mode. +// no I/O. The app is a client and REQUIRES a non-empty server_url that parses +// as an absolute https URL (ca_cert is optional). Unknown keys are rejected. func Parse(data []byte) (Config, error) { var fc fileConfig md, err := toml.Decode(string(data), &fc) @@ -89,23 +49,10 @@ func Parse(data []byte) (Config, error) { if undecoded := md.Undecoded(); len(undecoded) > 0 { return Config{}, fmt.Errorf("appconfig: unknown key(s) in app.toml: %v", undecoded) } - - switch strings.TrimSpace(fc.Mode) { - case "", modeStrClient: - return parseClient(fc) - case modeStrEmbedded: - return Config{}, errors.New( - `appconfig: mode="embedded" is no longer supported: embedded mode was retired (RIG-2554). ` + - "Run a headless Compass stack with `compass-stack up` on a dedicated machine and " + - `point the app at it in client mode (mode="client" with server_url = "https://host:8443")`) - default: - return Config{}, fmt.Errorf( - "appconfig: unknown mode %q in app.toml: the only valid mode is %q", - fc.Mode, modeStrClient) - } + return parseClient(fc) } -// parseClient validates the client-mode fields. +// parseClient validates the client fields. func parseClient(fc fileConfig) (Config, error) { if strings.TrimSpace(fc.ServerURL) == "" { return Config{}, errors.New( @@ -114,11 +61,7 @@ func parseClient(fc fileConfig) (Config, error) { if err := validateServerURL(fc.ServerURL); err != nil { return Config{}, err } - return Config{ - Mode: ModeClient, - ServerURL: fc.ServerURL, - CACert: fc.CACert, - }, nil + return Config(fc), nil } // validateServerURL enforces that a client server_url is an absolute https URL. @@ -151,10 +94,8 @@ func validateServerURL(raw string) error { // configHome/compass/app.toml when configHome is non-empty (the resolved // $XDG_CONFIG_HOME), else home/.config/compass/app.toml. The caller reads the // env; Load performs the resolution so it stays testable. -// -// An absent file is a legible first-run error: client mode requires a server_url -// and there is no zero-config default any more (embedded mode was retired, -// RIG-2554). A present file is read and Parsed. +// An absent file is a legible first-run error: the app is a client and requires +// a server_url to connect to. A present file is read and Parsed. func Load(configHome, home string) (Config, error) { path, err := configPath(configHome, home) if err != nil { @@ -170,7 +111,7 @@ func Load(configHome, home string) (Config, error) { case errors.Is(readErr, os.ErrNotExist): return Config{}, fmt.Errorf( "appconfig: no app config found at %s: the Compass app is a client and needs a "+ - `server_url to connect to. Create it with mode="client" and `+ + `server_url to connect to. Create it with `+ `server_url = "https://host:8443" (the address of a headless stack started with `+ "`compass-stack up`)", path) default: diff --git a/go/internal/appconfig/appconfig_test.go b/go/internal/appconfig/appconfig_test.go index eb75846cf..16aaf93c5 100644 --- a/go/internal/appconfig/appconfig_test.go +++ b/go/internal/appconfig/appconfig_test.go @@ -16,85 +16,74 @@ func TestParse(t *testing.T) { errSubstrs []string }{ { - name: "empty file → client (server_url required)", + name: "empty file → error (server_url required)", data: "", - // Absent mode defaults to client, which requires server_url; an - // empty file therefore fails legibly rather than resolving. + // The app is a client and requires server_url; an empty file + // therefore fails legibly rather than resolving. wantErr: true, errSubstrs: []string{"server_url"}, }, { - name: "client with server_url", - data: "mode = \"client\"\nserver_url = \"https://host:8443\"\n", - want: Config{Mode: ModeClient, ServerURL: "https://host:8443"}, - }, - { - name: "absent mode with server_url → client", + name: "server_url only → client", data: "server_url = \"https://host:8443\"\n", - want: Config{Mode: ModeClient, ServerURL: "https://host:8443"}, + want: Config{ServerURL: "https://host:8443"}, }, { - name: "client with ca_cert parsed through", - data: "mode = \"client\"\nserver_url = \"https://host:8443\"\nca_cert = \"/etc/anchor.pem\"\n", - want: Config{Mode: ModeClient, ServerURL: "https://host:8443", CACert: "/etc/anchor.pem"}, + name: "ca_cert parsed through", + data: "server_url = \"https://host:8443\"\nca_cert = \"/etc/anchor.pem\"\n", + want: Config{ServerURL: "https://host:8443", CACert: "/etc/anchor.pem"}, }, { - name: "embedded → legible retirement rejection", - data: `mode = "embedded"`, + // Regression: the mode key was hard-removed (RIG-3111). A stale + // mode = "client" line is now an ordinary unknown-key rejection + // that names the offending key — that IS the migration message. + name: "stale mode key → unknown-key rejection", + data: "mode = \"client\"\nserver_url = \"https://host:8443\"\n", wantErr: true, - errSubstrs: []string{"embedded", "retired", "compass-stack up", "client"}, + errSubstrs: []string{"unknown key", "mode"}, }, { - name: "client missing server_url → error", - data: `mode = "client"`, + // A populated file that omits server_url still errors (distinct from + // the empty-file case: a non-empty config carrying only other valid + // keys is not a pass). + name: "populated file missing server_url → error", + data: "ca_cert = \"/etc/anchor.pem\"\n", wantErr: true, errSubstrs: []string{"server_url"}, }, { - name: "client with http server_url → error", - data: "mode = \"client\"\nserver_url = \"http://host:8443\"\n", + name: "http server_url → error", + data: "server_url = \"http://host:8443\"\n", wantErr: true, errSubstrs: []string{"https", "cleartext"}, }, { - name: "client with relative server_url → error", - data: "mode = \"client\"\nserver_url = \"host:8443\"\n", + name: "relative server_url → error", + data: "server_url = \"host:8443\"\n", wantErr: true, errSubstrs: []string{"server_url"}, }, - { - name: "unknown mode → error", - data: `mode = "proxy"`, - wantErr: true, - errSubstrs: []string{"proxy", "client"}, - }, { name: "malformed toml → error", - data: "mode = ", + data: "server_url = ", wantErr: true, errSubstrs: []string{"app.toml"}, }, { - name: "whitespace-only mode → client (server_url required)", - data: `mode = " "`, + name: "whitespace-only server_url → error", + data: "server_url = \" \"\n", wantErr: true, errSubstrs: []string{"server_url"}, }, { - name: "client whitespace-only server_url → error", - data: "mode = \"client\"\nserver_url = \" \"\n", - wantErr: true, - errSubstrs: []string{"server_url"}, - }, - { - name: "client server_url with embedded credentials → error", - data: "mode = \"client\"\nserver_url = \"https://user:pass@host:8443\"\n", + name: "server_url with embedded credentials → error", + data: "server_url = \"https://user:pass@host:8443\"\n", wantErr: true, errSubstrs: []string{"credentials", "keychain"}, }, { name: "unknown key → error", - data: "mode = \"client\"\nserver_url = \"https://host:8443\"\ncacert = \"/etc/anchor.pem\"\n", + data: "server_url = \"https://host:8443\"\ncacert = \"/etc/anchor.pem\"\n", wantErr: true, errSubstrs: []string{"unknown key", "cacert"}, }, @@ -125,8 +114,8 @@ func TestParse(t *testing.T) { } // TestLoadAbsentFileIsFirstRunError: an absent app.toml is a legible first-run -// error (client mode needs a server_url; embedded's zero-config default was -// retired in RIG-2554), naming the config path and pointing at the client setup. +// error (the app is a client and needs a server_url), naming the config path +// and pointing at the client setup. func TestLoadAbsentFileIsFirstRunError(t *testing.T) { dir := t.TempDir() got, err := Load(dir, "") @@ -141,55 +130,34 @@ func TestLoadAbsentFileIsFirstRunError(t *testing.T) { } func TestLoadReadsPresentFile(t *testing.T) { - dir := writeConfig(t, "mode = \"client\"\nserver_url = \"https://host:8443\"\n") + dir := writeConfig(t, "server_url = \"https://host:8443\"\n") got, err := Load(dir, "") if err != nil { t.Fatalf("unexpected error: %v", err) } - if want := (Config{Mode: ModeClient, ServerURL: "https://host:8443"}); got != want { + if want := (Config{ServerURL: "https://host:8443"}); got != want { t.Errorf("got %+v, want %+v", got, want) } } -// TestLoadEmbeddedFileIsRejected: a present file selecting the retired embedded -// mode is rejected through Load (not just Parse) with the retirement copy. -func TestLoadEmbeddedFileIsRejected(t *testing.T) { - dir := writeConfig(t, `mode = "embedded"`) - got, err := Load(dir, "") - if err == nil { - t.Fatalf("embedded file: want a rejection, got config %+v", got) - } - for _, sub := range []string{"embedded", "retired", "compass-stack up"} { - if !strings.Contains(err.Error(), sub) { - t.Errorf("embedded rejection %q missing substring %q", err, sub) - } - } -} - func TestLoadHomeFallbackPath(t *testing.T) { home := t.TempDir() path := filepath.Join(home, ".config", "compass", "app.toml") if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } - if err := os.WriteFile(path, []byte("mode = \"client\"\nserver_url = \"https://host:8443\"\n"), 0o644); err != nil { + if err := os.WriteFile(path, []byte("server_url = \"https://host:8443\"\n"), 0o644); err != nil { t.Fatal(err) } got, err := Load("", home) if err != nil { t.Fatalf("unexpected error: %v", err) } - if want := (Config{Mode: ModeClient, ServerURL: "https://host:8443"}); got != want { + if want := (Config{ServerURL: "https://host:8443"}); got != want { t.Errorf("got %+v, want %+v", got, want) } } -func TestModeString(t *testing.T) { - if got := ModeClient.String(); got != "client" { - t.Errorf("ModeClient.String() = %q, want client", got) - } -} - // writeConfig writes app.toml under a fresh temp configHome and returns that // configHome dir (so Load resolves configHome/compass/app.toml). func writeConfig(t *testing.T, contents string) string { diff --git a/go/internal/appconfig/doc.go b/go/internal/appconfig/doc.go index 1363acaac..14326217e 100644 --- a/go/internal/appconfig/doc.go +++ b/go/internal/appconfig/doc.go @@ -1,9 +1,7 @@ // Package appconfig is the native app's client config parser (design §A3). One // file — $XDG_CONFIG_HOME/compass/app.toml (fallback ~/.config/compass/app.toml) // — configures the native-client connection: a base URL plus an optional -// private-anchor CA cert. Embedded mode was retired in RIG-2554, so client is -// the only mode; a mode="embedded" value parses to a legible rejection, not a -// compatibility arm. +// private-anchor CA cert. The native app is a client. // // The core is pure: Parse decodes and validates a TOML byte slice with no I/O, // and Load layers path resolution on top. Mirroring the stack package idiom, the diff --git a/tools/compass-app-dev/app.toml.example b/tools/compass-app-dev/app.toml.example index 51571acbd..06b7775ff 100644 --- a/tools/compass-app-dev/app.toml.example +++ b/tools/compass-app-dev/app.toml.example @@ -3,10 +3,6 @@ # ONLY this file for connection config — there is no env var or CLI flag for the # server URL (go/internal/appconfig). Unknown keys are rejected. -# The only mode. (Embedded mode was retired in RIG-2554 — the app no longer -# supervises a stack; it always connects to a remote compass-server.) -mode = "client" - # The compass-server network door. REQUIRED; must be an absolute https URL. server_url = "https://your-server.example.ts.net:50061"