Skip to content

feat(compass-app): embedded-mode launch pipeline (mode-select, preflight, stack supervise, WhoAmI) (SEA-1685) - #209

Open
seal-agent wants to merge 2 commits into
compass-native-1684-wails-shell-scaffoldfrom
compass-native-1685-embedded-launch
Open

feat(compass-app): embedded-mode launch pipeline (mode-select, preflight, stack supervise, WhoAmI) (SEA-1685)#209
seal-agent wants to merge 2 commits into
compass-native-1684-wails-shell-scaffoldfrom
compass-native-1685-embedded-launch

Conversation

@seal-agent

@seal-agent seal-agent commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 2 PRs:

  1. main
  2. feat(compass-app): Wails v3 desktop shell + compass_rpc IPC bridge #191
  3. "feat(compass-app): embedded-mode launch pipeline (mode-select, preflight, stack supervise, WhoAmI) (SEA-1685)" (this PR)

What

T4.1 — the embedded-mode launch pipeline for compass-app, additive on the #191 shell scaffold. Wires embedded mode end-to-end before the window opens: mode-select → host preflight → spawn/supervise the private stack (via the compass-stack CLI) → learn the caller account id (WhoAmI, DL-111) → hand the resolved socket + account id to the bridge/UI.

  • Mode selection (main.go, embedded.go): appconfig.Load + a --mode/$COMPASS_APP_MODE override; embedded runs the pipeline, client returns a not yet implemented (T5) sentinel (no fake stub).
  • Host preflight (preflight_adapters.go): the 3 real adapters — podmanRootless (podman info), imagePresent (podman image exists), dbReachable (short-timeout pgx connect+ping, mirroring stack/adapters/dbprobe.go). Severity split at the T4 wiring boundary (classifyPreflight): host-capability checks up cannot create ({OS, uid, rootless podman}) are fatal; the checks up itself ensures ({image, database}) are advisory — see Open Question 2.
  • Stack supervision (embedded.go): shells out to the compass-stack binary (up --state-dir --image --socket), waits for its fire-and-return exit 0 (Ready), surfaces stderr on a non-zero exit. Does not import internal/stack (design.md:482 — "consumes T2's compass-stack CLI"). The resolved socket is the single value passed to --socket and dialed for WhoAmI/bridge. Argv is built by a pure, unit-tested stackUpArgs; --database is omitted so the CLI recomputes the identical default DSN from --state-dir (no drifting second DSN definition).
  • Caller identity (embedded.go): whoAmIOverUDS dials the stack socket over h2c-UDS (transport shape from stack/adapters/health.go) and calls WhoAmI for the server-derived account id (never guessed). Exposed to the JS/UI via a bound bridgeService.AccountID(ctx) IPC getter (the JS ConnectionProvider construction is a separate compass-ui hand-off; no apps/ui/** edits here).
  • Agent uid (preflight/uid.go): exported preflight.DefaultAgentUID = 1000, cited to compass-runner's unimportable package main const — see Open Question 1.

Pipeline seams (preflight, stackUp, whoAmI) are injectable function fields; launchByMode, stackUpArgs, and classifyPreflight are pure. All the real effects are supplied only in run().

Tests

embedded_test.go drives the whole pipeline with injected effects (no real podman/postgres/stack/exec): mode embedded vs client; preflight short-circuit; happy-path argv + single-socket invariant + WhoAmI; stack-up non-zero exit; WhoAmI error; the preflight severity split (host-cap unmet → fatal; image/DB-only unmet with host-caps OK → nil, the load-bearing cold-start case; advisory never masks a host gap nor leaks into the fatal fold). The whoAmIOverUDS transport is proven against a real in-process compass.v1 WhoAmI server over h2c on a UDS. runStackUp's exec+stderr contract is driven with a real child process.

Gate (all on the real tree, both tag sets): gofmt clean; go vet + go vet -tags gtk3 clean; go test -race ./cmd/compass-app/... ./internal/preflight/... + go test -tags gtk3 ./cmd/compass-app/... pass; go build (nogtk3) + go build -tags gtk3 both link; golangci-lint run 0 issues on both tag sets.

Open Questions (parked for Matt — overnight mode)

  1. Agent-uid single source of truth. preflight.DefaultAgentUID here duplicates compass-runner's defaultAgentUID (cmd/compass-runner/main.go), which lives in package main and is unimportable. This PR takes the tonight-move (duplicate + cite, tracked as deliberate). The fork: lift to one shared exported const and refactor compass-runner to consume it — that refactor is in compass-runner's lane (its owner has acknowledged and will drive it once you rule). No drift ships; the two consts are pinned to the same value with a cross-reference.

  2. Preflight severity placement. The severity split (host-cap fatal / image+DB advisory) is applied at the T4 wiring boundary (classifyPreflight), not in preflight.Run/Err. This is required for cold start: up starts postgres (design.md:176) and pulls the image (stack/adapters/image.go), so on a fresh state dir image+DB necessarily fail pre-up; gating on them would make the app unable to cold-start, and up-Ready (GetServerInfo answering, design.md:189-191) transitively verifies both, so no post-up re-check is needed. The fork: keep the split at the boundary (as here — preflight.Run/Err stays a pure "report every unmet precondition" surface for its operator-facing use) vs. lift the advisory/fatal distinction into the core's Err() (one SoT for cold-start severity). Cold-start behavior is correct either way; this is about where the knowledge lives.


Review round 1 (sole review agent) — dispositioned

0 high, 3 medium, 3 low; zero-high earned (per-axis ground trace). All findings fixed or deferred with a filed issue:

  • M1 — cross-binary DSN duplication + a comment that denied it. Fixed: embeddedDatabaseDSN's comment now states it is a deliberate copy of compass-stack's defaultDSN (the SoT), guarded by a golden drift test. Deferred: the SoT consolidation (one importable helper) → SEA-1856.
  • M2 — 60s bring-up window predictably fails a cold first-run GHCR image pull. Partial fix: the timeout error now names the bring-up window and the likely cold-pull cause (was a bare deadline error). Deferred (window sizing): kept at 60s here; a longer stack-up window distinct from the fast preflight/whoAmI legs is T4.2 lifecycle (SEA-1685) — the in-code comment names it. Parked for your call on the T4.1/T4.2 boundary.
  • M3 — config resolvers (resolveStackBin/resolveStateDir/resolveImage/resolveMode) had no coverage → added table tests (incl. the load-bearing relative-XDG_STATE_HOME-falls-through determinism guard).
  • L4 — a successful WhoAmI with an empty account id is now rejected (was resolved as an empty identity).
  • L5 — removed the dead --listen scaffolding (no caller; T4.3 re-adds it when the headless variant needs it).
  • L6accountID doc comment corrected to match its real write site.

Refs SEA-1685

@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown

SEA-1685

…timeout copy, resolver+identity tests)

Additive review-fix commit on the T4.1 embedded-launch bookmark.

- embeddedDatabaseDSN: doc comment now states it is a deliberate second copy
  of cmd/compass-stack's defaultDSN (the source of truth), guarded by a golden
  test; SoT consolidation tracked as SEA-1856.
- runStackUp: a context-deadline failure now names the bring-up window and the
  likely cold GHCR image-pull cause instead of a bare deadline error.
- whoAmIOverUDS: reject an empty account id from a successful WhoAmI rather than
  resolving an empty identity downstream.
- Remove the dead --listen scaffolding (field, argv branch, test) with no caller.
- Add table tests for resolveStackBin/resolveStateDir/resolveImage/resolveMode.
- accountID doc comment corrected to match its real write site.

Co-authored-by: Matt Wilkinson <matt@sealedsecurity.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant