Skip to content

Global data root (~/.openalice) + sealed broker credentials#324

Merged
luokerenx4 merged 8 commits into
masterfrom
UTA-issue
Jun 11, 2026
Merged

Global data root (~/.openalice) + sealed broker credentials#324
luokerenx4 merged 8 commits into
masterfrom
UTA-issue

Conversation

@luokerenx4

Copy link
Copy Markdown
Contributor

Summary

  • User-data root unified at ~/.openalice — one store shared by pnpm dev, bare pnpm start, and the packaged app; broker accounts get configured once, not per checkout. OPENALICE_HOME overrides (Docker /data unchanged; OPENALICE_HOME="$PWD" pnpm dev pins a checkout-local store). Legacy checkout stores get a manual-adoption banner (never auto-moved — multiple worktrees have no canonical answer); packaged Electron auto-relocates its single userData/data store crash-safely on first launch.
  • Broker credentials sealed at restaccounts.json is now an AES-256-GCM envelope, keyed by ~/.openalice/sealing.key (0600, auto-generated, outside the portable data/ subtree, so backing up / sharing data/ no longer carries the key). Migration 0009 reseals existing plaintext stores at first boot; an unreadable store (data copied without its key) is quarantined loudly, never deleted.
  • Leak fixes along the way: POST/PUT /uta no longer echo plaintext credentials (now masked like GET); env naming unified on OPENALICE_HOME (guardians previously injected a variable nothing read); 8 cwd-relative data/ literals routed through dataPath() — several were latent Electron bugs (UTA wipe path, restart-flag trigger).
  • Test hermeticity: vitest pins OPENALICE_HOME to a temp dir per worker so specs can never touch the real store; the smoke harness pins the checkout so its restart-flag check keeps testing the path it watches.

Test plan

  • npx tsc --noEmit clean (root + apps/desktop)
  • pnpm test — 1848 passing (new: sealing round-trip / plaintext compat / key-loss quarantine / migration 0009 idempotency / Electron relocation / masked echoes)
  • pnpm test:smoke — full stack boots on the new root, UTA restart-flag chain verified end-to-end
  • Manual: walk Settings → Trading on a fresh store; confirm on-disk accounts.json is a $sealed envelope, 0600

Boundary touch

Trading credentials (at-rest sealing + response masking), migrations (0009), data-root relocation (Electron + guardian bootstrap).

🤖 Generated with Claude Code

Ame and others added 7 commits June 11, 2026 22:28
Preparation for moving the default user-data home off cwd: a global
vitest setup file pins OPENALICE_HOME to a per-worker temp dir so
module-level dataPath() constants never resolve into the developer's
real data root, and the smoke harness pins the spawned Guardian to the
checkout so its SOFT restart-flag check keeps testing the same path it
watches. session.spec cleanup now sweeps the same dataPath the store
writes to (its cwd HACK comment was stale — SessionStore already goes
through dataPath).

No behavior change for production code; e2e config deliberately not
wired (it reads the real credential store by design).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… through dataPath()

Guardians (dev.ts, prod.mjs) now inject OPENALICE_HOME — the variable
src/core/paths.ts actually reads — instead of OPENALICE_USER_DATA_HOME,
which nothing in src/ consumed; dev children only worked via cwd
inheritance. prod.mjs keeps the legacy name as a deprecated fallback
for one release. Dockerfile drops the duplicate.

Eight call sites hardcoded cwd-relative 'data/...' paths instead of
dataPath(), so they silently split state from the configured root under
any non-cwd OPENALICE_HOME (e.g. packaged Electron): wipeUTATradingData,
the UTA restart-flag trigger, event-log, tool-call-log, cron store,
news store, snapshot store, and the order-sentinel script.

No behavior change while the default home is still cwd; this is the
ground work for moving it to ~/.openalice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One global store shared by every topology — pnpm dev, bare pnpm start,
and the packaged app — so broker credentials and trading state are
configured once, not re-entered per checkout. Joins the existing
~/.openalice conventions (workspaces/, provider-keys.json); the data/
subtree under it stays the portable unit.

Adoption is deliberately manual: when a checkout still carries a legacy
./data store and the global one is virgin, guardian dev and Alice's
boot path print a one-line mv instruction instead of auto-moving —
multiple worktrees may each have a ./data and only the user knows which
is canonical. OPENALICE_HOME="$PWD" pnpm dev pins a checkout-local
store for experiments that shouldn't touch real data.

Docker is unaffected (OPENALICE_HOME=/data stays explicit); Electron
relocation of existing stores lands separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… launch

The Electron shell joins the global-root story: OPENALICE_HOME points at
~/.openalice in both packaged and dev branches, and an existing
<userData>/data store from pre-global-root installs is moved there once
— before ports.json is read from the new root and before the backend
boots (which would run migrations against an empty store).

Relocation moves ONLY userData/data (userData also holds Electron's
browser-profile state, which stays). Same-volume = atomic rename;
cross-volume = copy to a tmp dir then atomic commit, legacy kept as
data.relocated — never deleted. Idempotent across boots; a stale
partial copy from a crashed run is swept and retried. On failure the
shell surfaces a dialog and quits rather than forking the user's
trading history next to the real store.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-bound key)

data/config/accounts.json — the broker credential store — is now an
AES-256-GCM envelope instead of plaintext JSON. The key is a random
32-byte secret at <userDataHome>/sealing.key (0600, auto-generated on
first write, zero user interaction), deliberately OUTSIDE the portable
data/ subtree: backing up, syncing, or sharing data/ no longer carries
the material needed to read the credentials inside it.

Honest scope: this does not defend against same-user malware or a
compromised Alice process — the structural answer there remains the
detached-UTA split. The envelope is versioned so the key can later move
into an OS keychain without a format break.

Every write funnels through one sealed 0600 writer; reads accept the
legacy plaintext array, and migration 0009 reseals existing stores
eagerly at first boot so plaintext doesn't linger until the next config
change. An unreadable store (data/ copied without its key) is
quarantined loudly — never deleted — and the app boots with an empty
store instead of crashing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/trading/config/ already masked secrets, but the create and
edit responses echoed the full validated record — plaintext broker
credentials — back over the wire. Both now return maskSecrets(...),
which also makes the client's local state shape-identical to what GET
returns (the edit round-trip already restores masked values server-side
via unmaskSecrets, so the UI needs no change).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CLAUDE.md (data/ entry), README (config paths, token rotation, persona
override), and safe/knowledge/config-files.md (path resolution, file
permissions, accounts.json at-rest shape, recon commands) now describe
the global user-data root, the OPENALICE_HOME override, and the sealed
credential store. Factual path updates only — no positioning copy
touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openalice-demo Ready Ready Preview, Comment Jun 11, 2026 3:08pm

Request Review

The wizard's onSave chained createUTA → reconnectUTA and only closed
the dialog when the first broker connection succeeded. A failed or slow
first connect left the dialog open over an ALREADY-persisted account —
the user reads it as "save failed", clicks Save again, and gets a 409
duplicate warning for their trouble.

Persist → close. The first connection now runs in the background where
the list's health badge already tracks it (Reconnecting → Connected /
Offline), and credential validity keeps its own explicit Test step in
the wizard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@luokerenx4 luokerenx4 merged commit 8ad2ad2 into master Jun 11, 2026
5 checks passed
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