Connections panel — credentialed backends (Company Compute + Pasqal Cloud) in the app UI - #172
Merged
Merged
Conversation
…ation (AC1) New standalone validator (sibling of the connectivity spike's connector): env-only credentials, authenticates via the pasqal_cloud SDK, extracts the bearer token best-effort through the SDK's token-provider chain (null token = session-only fallback signal), enumerates devices, and emits exactly one JSON line: ok / project_id / devices / token (nullable) / expires_at. Tests stub the SDK fully via sys.modules injection; no network, no pasqal-cloud install required. Part of #164 (parent #159). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Auth failure -> 2, network failure (connect or device fetch) -> 3, project-authorization failure -> 4; stderr carries a fixed, distinct, token- and password-free message per class and stdout stays empty. Stub exceptions embed a poison password so any echoed exception text fails the hygiene assertions (verified by mutation: swapping an exit code and echoing exception text each break the suite). Part of #164. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…DK shape (AC3, AC4) - Stub now mirrors pasqal-cloud 0.23.0's real object graph: PasqalCloudConnection.cloud_client._client.authenticator.token_provider; token extraction walks that chain (verified offline against the real SDK) and reads the exact expiry from ExpiringTokenProvider's token cache, with JWT exp decode as fallback. - Devices come from cloud_client.get_device_specs_dict() (pure SDK) — never fetch_available_devices, which deserializes specs via pulser. - AC3: recorder-backed stub proves no run/submit/backend/job call path. - AC4: poison password appears in no argv and no output stream, across every scenario; spawn tests are hermetic via a PYTHONPATH shadow stub (never the live service, even where pasqal-cloud is installed). - Misconfigured python (SDK not importable) renders a fixed exit-1 message, distinct from unreachable-service, never a traceback. Part of #164. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
requirements.txt pins pasqal-cloud==0.23.0 (the spike's pin; the token extraction chain in pasqal_validate.py is verified against this version's object graph). tests/slow_live.py is the optional live smoke: excluded from default discovery by filename, gated on PASQAL_LIVE_SMOKE=1, and asserts token shape without echoing it. Closes out #164's testing decisions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fork's route auth (ServerAuth @ v1.17.3-amicode.5) is a verified no-op without OPENCODE_SERVER_PASSWORD in the server env. New server_auth module mints a cryptographically random per-boot password (32B base64url, in-memory only) and builds the spawn env the extension ADDS — PATH + config content + password — used by all three ServerManager sites (boot, solver switch, vault respawn), so no respawn path can drop it. Tests pin exactly the ADDED env keys (never full-env equality — the server inherits the host env by design), the fork's Basic base64(opencode:pw) credential shape, and that two mints differ. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With route auth armed, the extension's own calls 401 without it — found four surfaces, each now authenticated and pinned by tests against real transports: - ServerManager health probe (derives Basic from the spawn env it injected, so probe and server can never drift; real-spawn fake-binary test) - SSE /event subscription (silent retry-loop forever otherwise; live http server captures the Authorization header) - fetchProviderSignal /config* probes (boot signal, healthcheck, chat gate — would read 'server unreachable' forever) - chat iframe src ?auth_token= — the fork app's own credential bootstrap (entry.tsx adopts it for its authenticated-fetch path and strips the URL); no-token path unchanged for the unsecured dev server Also fork parity on OPENCODE_SERVER_USERNAME: the spawned server inherits a host-env override, so the credential builders honor it too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The channel scans landed with their transports (server_manager / sse_client sweep real captured output; chat_panel pins the raw value out of the webview html). These close the two remaining leak seams: mintServerPassword never writes the host process env (in-memory only, per the no-persist decision), and a poisoned env can't surface the password in OPENCODE_CONFIG_CONTENT (dumpable via opencode debug config) — same idiom as the D11 key guard. Verified against the live vendored fork (v1.17.3-amicode.5, serve on a scratch port): anonymous /, /amicode/vaults, /event, /config/providers all 401 with the password armed; our Basic header and the app's ?auth_token bootstrap both 200. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…off argv readPasqalCredentials (AMICO_PASQAL_FILE → ~/.amico/pasqal.json), interpreter resolution (AMICO_PYTHON → python3 on PATH, absolute-path spawn), and a built-from-scratch minimal child env: PATH + PASQAL_TOKEN + PASQAL_PROJECT_ID, never a process.env spread. The launcher takes no flags at all, so no secret can ride our argv; the fake-interpreter shim records argv+env and the tests assert env-carriage, exact-key minimality, argv cleanliness, and verbatim child exit-code passthrough (connector exit-3 stays 3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n-free
Missing file ('not connected — … Connections panel'), unparseable file
('malformed … reconnect'), wrong shape (names keys only — a value could be a
mistyped secret; null token rejected per the #162 no-nulls-at-rest contract),
expired token ('expired at <iso> — reconnect', injectable clock), and the two
interpreter lanes (AMICO_PYTHON named vs python3-on-PATH hint) — all exit-64
config-class, cleanly distinct from the connector's own exit-3 unreachable
passthrough. No message may ever contain the token value.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…corpus bytes
Vendor cloud.json + pasqal.json byte-for-byte (sha256-verified) from the
opencode fork's canonical corpus (packages/opencode/test/server/fixtures/
credentials, provenance header in the consuming test). The test pins BOTH the
exact at-rest bytes (2-space JSON + trailing newline, key order) and the real
parse: readRemoteConfig via AMICO_CLOUD_FILE → {baseUrl, token}, and
readPasqalCredentials via AMICO_PASQAL_FILE (parse-level only — expiry is
launch-time, keeping the golden test time-independent). Any byte drift on
either side of the repo boundary fails here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…le e2e Third bin from the package (the amico-run/amico pattern): package.json bin → launcher/amico-pasqal bash shim → dist/amico-pasqal.js (esbuild) → the thin pasqal_cli.ts entry over pasqal_launch.ts. Bundle-level e2e proves the shipped artifact: env-injection happy lane (argv stays token-free at the bin layer), the not-connected 64 lane on stderr, --help documenting the ENV ONLY contract, and the bin/launcher wiring itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kateebonner
marked this pull request as ready for review
July 20, 2026 03:06
…branch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
setCloudKey's logic re-points at the fork's one-round-trip submit route (POST /amicode/connections/credential, #163 boot credential in Authorization, token only in the JSON body): the server owns validate -> write -> HP flip, so the command's whole side-effect budget is one HTTP call. classifyValidation / validateCloudKey / buildCloudConfig are deleted (dead in the command path); the direct probe of the external Solve Service is gone with them. Review findings addressed in the mapping: (1) connected + error field renders as connected-warning, never a clean success; (5) the vacuous classifyValidation leak test is replaced by adversarial redaction tests over server/transport error text embedding the token; (6) the prod URL is single-sourced in DEFAULT_CLOUD_URL — package.json's amicode.cloudUrl default drops to "" (description: built-in production endpoint) and a test pins the non-duplication. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
extension.ts's runSetCloudKey is now a thin vscode wiring around runSetCloudKeyCommand: input box / progress / toasts injected, server = opencodeReadyUrl + the #163 Basic header (the sse_client/chat_panel idiom), cloudUrl passed raw (the core resolves the "" -> default). The direct cloud.json write and the direct applyEntitlementForMode + writeSolverModeSwitching flip are deleted from the command path — the server flips exactly once via #167 (AC3). writeSolverModeSwitching itself is removed from solver_mode.ts: its only caller was this command, and keeping an exported client-side flip writer around invites the duplicate-flip bug back (review finding 3 — rotation while already HP causes no client-side flip logic at all; server idempotence owns it). The watcher contract test now writes the server's literal {mode, status:"switching"} JSON instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #159
amicode-side integration branch for the Connections panel DAG. All amicode-side afk slices complete, gates green (extension 634+ · amico-run 282 · python 16/16). Companion fork PR: harmoniqs/opencode#33.
Merged slices:
pasqal_validate.py: auth-only validation + token mint (verified feasible vs pasqal-cloud 0.23) + device enumeration + exit-code contractamico-pasqallauncher: token env-injection, no-flags-by-design argv safety, cross-repo golden fixtures (sha256-pinned)Remaining in the DAG (not in this PR): #171 (re-point
setCloudKeyonto the panel seam) — hitl, gated onrchari/hp-cloud-keymerging first.🤖 Generated with Claude Code