Skip to content
Merged
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
12 changes: 11 additions & 1 deletion docs/adr/0001-credentials-on-disk.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Credentials at rest are revocable tokens on disk under ~/.amico — passwords are never persisted

Status: accepted (2026-07-19)
Status: accepted (2026-07-19) · amended for Pasqal Cloud (2026-07-21, see addendum)

Connection credentials are stored as `0600` JSON files under `~/.amico/`. The decided write discipline for the new storage seam is **atomic with the mode set at file birth** — the tmp file is created `0600` before rename, because the default write mode is not owner-only (`0o666 & ~umask`, typically `0644`). This is new work: no such write path exists yet (the repo's atomic-write helper takes no mode argument, and the in-flight key-entry command writes `cloud.json` mode-at-birth but non-atomically — it migrates onto the seam after its branch merges; the migration is scoped into the Connections-panel issue). **Only revocable tokens are ever at rest.** For Company Compute that is the service token (`cloud.json` — shape and location unchanged; its writers adopt the seam's discipline). For Pasqal Cloud (`pasqal.json`: `{project_id, token}` plus expiry metadata; exact shape finalized against the SDK in the implementing issue), the user's username/password are used **in memory only** at key-entry time — for the validation handshake and to mint an Auth0 token via the SDK's token-provider mechanism — and only the token is persisted; runtime authentication uses the stored token through a `TokenProvider`. If token refresh proves unsupported against the SDK, the fallback is **session-only** (re-prompt, token held in process memory) — never password-on-disk. Pasqal's own SDK documentation warns against persisting passwords.

Expand All @@ -11,3 +11,13 @@ Connection credentials are stored as `0600` JSON files under `~/.amico/`. The de
**Accepted residuals:** a stolen token file grants access until expiry/revocation (bounded by `0600` + the same-user trust model); token expiry can surface a "reconnect" prompt when no refresh path exists. Each connection type reads/writes through a single storage-seam interface, so a later **OS-keychain backend reachable from the server process** (e.g. shelling to the platform keychain CLI) remains a contained swap. VSCode SecretStorage specifically is *not* a contained swap — it is reachable only from the extension host, and per ADR 0002's flip condition a SecretStorage migration would flip the seam itself.

This resolves the open question "where the extension stores it" in the cloud-solve-service team spec of 2026-06-28 (team vault, armonissima specs).

## Addendum (2026-07-21): Pasqal password moves to the OS keychain — evidence-forced

**What changed.** For the Pasqal Cloud connection ONLY, the username+password are stored in the **OS keychain** (macOS login Keychain / Linux Secret Service; never a plaintext file), and the fork server silently re-runs the password grant when the ~24h access token lapses. The token continues to persist in `pasqal.json` (`0600`, atomic, mode-at-birth) so every token consumer — the `amico-pasqal` launcher contract above all — is unchanged. Disconnect wipes both the keychain slots and the token file. Everything else in this ADR stands, including Company Compute (service token, no password ever exists).

**Why the original stance broke.** This ADR's no-password design assumed at least one of: a mintable long-lived token, a working refresh path, or a tolerable re-prompt cadence. Live-API evidence (Jack's session of 2026-07-21, `pasqal-auth-bundle`; independently probed the same day) eliminated all three: (1) authorization-code/PKCE cannot complete — Pasqal's public client registers no localhost callback and we do not own the tenant; (2) the device-code grant is disabled on the client; (3) **refresh tokens are issued but unusable** — the API returns 403 for any access token whose `gty` claim includes `refresh_token`, with otherwise identical `aud`/`scope`/`permissions` to a working password-grant token (deliberate grant-type gating); (4) access tokens live ~24h. Net: for a user account, a valid token cannot exist for more than ~24h without the password being present somewhere. The named fallback (session-only re-prompt) would mean typing the password roughly daily; product decision (Kate, 2026-07-21) is that this friction is not acceptable for the panel, adopting Jack's keychain interim.

**Revised risk position.** The keychain is same-user-readable, so a local process running as the user can obtain the password — the exact blast radius this ADR originally refused. Accepted knowingly because Pasqal's design caps what any client can do: the password must be *presentable* to keep working at all. Mitigations: keychain-only (no plaintext file fallback), per-connection slots, disconnect wipes, and the panel states plainly that the password is saved to the system keychain. **Backend (per Jack's validated `pasqalAuth.ts`, adopted by Kate):** the OS keychain is reached via `@napi-rs/keyring` (macOS login Keychain / Linux Secret Service / Windows Credential Manager) — the same native module Jack's standalone session used, joining node-pty and tree-sitter as native deps already carried in the bun-compiled fork binary. Where the native binding cannot load (headless Linux with no Secret Service daemon), the store degrades to **session-memory** — password held in the server process only, re-prompt after restart — which is this ADR's original named fallback, never a plaintext file. (An earlier draft of this addendum proposed shelling to the platform CLI; superseded by Jack's native-module choice.)

**Exit condition.** A Pasqal **service account** (`client_credentials`) retires the password entirely — client id+secret are revocable-credential class and fit this ADR's original at-rest rules with no exception. Request is with Pasqal (Baptiste Peyras, project `Harmoniqs Tests`); the day it is provisioned, the keychain password slots are deleted and this addendum's exception ends. Until then this exception is scoped to `pasqal-cloud` and must not be cited as precedent for any other connection.
Loading