Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions apps/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 0 additions & 5 deletions apps/ui/src/shell-globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});
4 changes: 2 additions & 2 deletions apps/ui/src/shell-globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
3 changes: 1 addition & 2 deletions go/cmd/compass-app/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}

Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 4 additions & 12 deletions go/cmd/compass-app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down Expand Up @@ -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
Expand Down
18 changes: 8 additions & 10 deletions go/e2e/client_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
87 changes: 14 additions & 73 deletions go/internal/appconfig/appconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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(
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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:
Expand Down
Loading
Loading