Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8b7a989
pasqal_validate: auth-only validation with token mint + device enumer…
kateebonner Jul 20, 2026
b54e78a
pasqal_validate tests: exit-code classification contract (AC2)
kateebonner Jul 20, 2026
bd06ff6
pasqal_validate: no-submission invariant, secret hygiene, real 0.23 S…
kateebonner Jul 20, 2026
0a9ef2a
pasqal-connector: pin pasqal-cloud + optional live smoke behind opt-in
kateebonner Jul 20, 2026
10a05bb
pasqal-connector: ignore python bytecode caches
kateebonner Jul 20, 2026
f9ba2af
163/AC1+AC4: per-boot server password armed at every opencode spawn
kateebonner Jul 20, 2026
a6dd108
163/AC2: every extension call to the server carries the boot credential
kateebonner Jul 20, 2026
db8d91b
163/AC3: seam guards — the spawn env is the password's only carriage
kateebonner Jul 20, 2026
1c64501
merge: issue #163 (frontier via worktree)
kateebonner Jul 20, 2026
df178fd
merge: issue #164 (frontier via worktree)
kateebonner Jul 20, 2026
2b7b656
168/AC1+AC2: amico-pasqal launch core — token env-injection, secrets …
kateebonner Jul 20, 2026
eb738b2
168/AC3: pin the launcher error taxonomy — distinct, actionable, toke…
kateebonner Jul 20, 2026
ea07590
168/AC4: cross-repo golden fixtures — the REAL readers parse the #162…
kateebonner Jul 20, 2026
abb9c42
168: wire the amico-pasqal bin — launcher script, esbuild entry, bund…
kateebonner Jul 20, 2026
02c7592
merge: issue #168 (frontier via worktree)
kateebonner Jul 20, 2026
dec9697
merge: main (hp-cloud-key, PR #173) into the connections integration …
kateebonner Jul 20, 2026
0176327
171: cloud_key.ts becomes the connections-seam client + command core
kateebonner Jul 20, 2026
fbc68df
171: re-point amicode.setCloudKey onto the connections seam
kateebonner Jul 20, 2026
a2b2780
merge: issue #171 (frontier via worktree)
kateebonner Jul 20, 2026
9a9da0d
chore: pin opencode v1.17.3-amicode.6 (the Connections panel release)
kateebonner Jul 20, 2026
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
9 changes: 6 additions & 3 deletions packages/amico-run/esbuild.config.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { build } from "esbuild";
import { chmodSync } from "node:fs";

// Two bins from one package: the historical `amico-run` (entry cli.ts) and the new `amico`
// verb router (entry amico.ts, issue #108). Both share the launch path (src/launch.ts);
// amico.ts additionally bundles the spine verbs + the mcp-serve facade.
// Three bins from one package: the historical `amico-run` (entry cli.ts), the `amico`
// verb router (entry amico.ts, issue #108) — both sharing the launch path (src/launch.ts;
// amico.ts additionally bundles the spine verbs + the mcp-serve facade) — and the
// `amico-pasqal` connector launcher (entry pasqal_cli.ts, issue #168: token env-injection,
// secrets off argv).
const common = {
bundle: true,
platform: "node",
Expand All @@ -19,6 +21,7 @@ const common = {
for (const [entry, outfile] of [
["src/cli.ts", "dist/amico-run.js"],
["src/amico.ts", "dist/amico.js"],
["src/pasqal_cli.ts", "dist/amico-pasqal.js"],
]) {
await build({ ...common, entryPoints: [entry], outfile });
chmodSync(outfile, 0o755);
Expand Down
17 changes: 17 additions & 0 deletions packages/amico-run/launcher/amico-pasqal
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Thin launcher for `amico-pasqal`: resolve node, exec the bundled Pasqal connector
# launcher. No logic lives here (mirror of launcher/amico-run). See src/pasqal_cli.ts —
# the secret travels ONLY in the child env the bundle builds; nothing here touches it.
set -euo pipefail
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve symlink chains (node_modules/.bin)
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
if ! command -v node >/dev/null 2>&1; then
echo "amico-pasqal: node >= 20 not found on PATH (install node or fix PATH; see provisioning runbook)" >&2
exit 64
fi
exec node "$DIR/../dist/amico-pasqal.js" "$@"
3 changes: 2 additions & 1 deletion packages/amico-run/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"types": "./src/index.ts",
"bin": {
"amico-run": "./launcher/amico-run",
"amico": "./launcher/amico"
"amico": "./launcher/amico",
"amico-pasqal": "./launcher/amico-pasqal"
},
"engines": {
"node": ">=20"
Expand Down
18 changes: 18 additions & 0 deletions packages/amico-run/src/pasqal_cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// The `amico-pasqal` bin entry point (issue #168). The launcher logic lives in
// pasqal_launch.ts (the cli.ts / launch.ts split) — this file is intentionally thin:
// resolve argv → pasqalLaunch() → set the process exit code. SECURITY: even the
// unexpected-error lane prints only the error's own text — pasqal_launch.ts
// guarantees no ConfigError message ever carries the token, and the token itself
// lives only in the child env, never in any argv or log line.
import { pasqalLaunch } from "./pasqal_launch.js";

pasqalLaunch(process.argv.slice(2)).then(
(c) => {
process.exitCode = c;
},
(e) => {
// Any unexpected throw is a launcher fault, not a connector failure → 64.
console.error(`amico-pasqal: unexpected error: ${e instanceof Error ? (e.stack ?? e.message) : e}`);
process.exitCode = 64;
},
);
159 changes: 159 additions & 0 deletions packages/amico-run/src/pasqal_launch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// packages/amico-run/src/pasqal_launch.ts
// The `amico-pasqal` launcher (issue #168, 159/S8): hand the stored Pasqal
// credential to a connector script WITHOUT the secret ever riding a command
// line. SECURITY: the token value must never appear in any argv at any layer
// (ps-visible, transcript-persisted), in an error message, or in a log line
// (the remote_config.ts / llm_creds.mjs stance) — the child environment
// (PASQAL_TOKEN + PASQAL_PROJECT_ID) is the secret's ONLY carriage, and the
// child env is built from scratch (never a process.env spread).
import { spawn } from "node:child_process";
import { accessSync, constants as fsConstants, existsSync, readFileSync } from "node:fs";
import { constants as osConstants, homedir } from "node:os";
import { delimiter, join, resolve } from "node:path";
import { ConfigError } from "./types.js";

export interface PasqalCredentials {
projectId: string;
token: string; // always a real string — no nulls at rest (slice #162 contract)
expiresAt?: string; // ISO 8601; absent = the panel stored no expiry
}

/** $AMICO_PASQAL_FILE overrides the path (tests) — the cloudConfigFile idiom. */
export function pasqalCredentialFile(env: NodeJS.ProcessEnv = process.env): string {
const v = env.AMICO_PASQAL_FILE;
if (v && v.trim() !== "") return v;
return join(homedir(), ".amico", "pasqal.json");
}

/** Read + shape-check the credential file. Distinct, actionable, TOKEN-FREE errors
* (AC3): the launcher never prompts and never mints — absent/expired credentials
* are the Connections panel's job to fix. Expiry is checked separately
* (assertPasqalFresh) so parse-level consumers (golden-fixture tests) stay
* time-independent. All failures are ConfigError — exit-64 class: nothing ran. */
export function readPasqalCredentials(env: NodeJS.ProcessEnv = process.env): PasqalCredentials {
const file = pasqalCredentialFile(env);
if (!existsSync(file))
throw new ConfigError(`not connected — add Pasqal credentials in the Connections panel (no credential file at ${file})`);
let raw: unknown;
try {
raw = JSON.parse(readFileSync(file, "utf8"));
} catch {
throw new ConfigError(`malformed Pasqal credential file at ${file} — reconnect in the Connections panel to rewrite it`);
}
const d = (typeof raw === "object" && raw !== null ? raw : {}) as Record<string, unknown>;
// Shape errors name KEYS only — a value could be a mistyped secret.
if (typeof d.project_id !== "string" || d.project_id === "" || typeof d.token !== "string" || d.token === "")
throw new ConfigError(
`Pasqal credential file at ${file} needs non-empty string keys "project_id" and "token" — reconnect in the Connections panel`,
);
if (d.expires_at !== undefined && (typeof d.expires_at !== "string" || Number.isNaN(Date.parse(d.expires_at))))
throw new ConfigError(
`Pasqal credential file at ${file} has an unreadable "expires_at" — reconnect in the Connections panel`,
);
return { projectId: d.project_id, token: d.token, expiresAt: d.expires_at as string | undefined };
}

/** Expired token → distinct actionable error (AC3). The expiry timestamp is not a
* secret; the token value never appears. `now` is injectable for tests. */
export function assertPasqalFresh(creds: PasqalCredentials, now: Date = new Date()): void {
if (creds.expiresAt !== undefined && Date.parse(creds.expiresAt) <= now.getTime())
throw new ConfigError(
`Pasqal token expired at ${creds.expiresAt} — reconnect in the Connections panel to mint a fresh one`,
);
}

/** Interpreter resolution: $AMICO_PYTHON override (the documented escape hatch for
* GUI-launched macOS PATH issues), else python3 on PATH. Returns an ABSOLUTE path
* so the spawn is deterministic. Misconfiguration here is exit-64 class — clearly
* distinct from the connector's own exit-3 "service unreachable", which the
* launcher passes through untouched. */
export function resolvePasqalInterpreter(env: NodeJS.ProcessEnv = process.env): string {
const override = env.AMICO_PYTHON;
const bin = override && override.trim() !== "" ? override : "python3";
const candidates = bin.includes("/")
? [resolve(bin)]
: (env.PATH ?? "")
.split(delimiter)
.filter(Boolean)
.map((d) => join(d, bin));
for (const c of candidates) {
try {
accessSync(c, fsConstants.X_OK);
return c;
} catch {
/* keep looking */
}
}
throw override
? new ConfigError(`AMICO_PYTHON interpreter not found or not executable: ${override}`)
: new ConfigError(`python3 not found on PATH — install Python 3, or set AMICO_PYTHON to your interpreter`);
}

function signalCode(signal: NodeJS.Signals | null): number {
const n = signal ? (osConstants.signals as Record<string, number>)[signal] : undefined;
return 128 + (n ?? 1);
}

const USAGE = `usage: amico-pasqal <connector-script.py> [args…]
credential file: $AMICO_PASQAL_FILE, else ~/.amico/pasqal.json
interpreter: $AMICO_PYTHON, else python3 on PATH
The Pasqal token + project id reach the connector in ENV ONLY
(PASQAL_TOKEN, PASQAL_PROJECT_ID) — never on any command line.
The launcher takes NO secret arguments and passes everything after
the script through to the connector verbatim.`;

/** The launcher body. Config-class failures print one actionable line and exit 64
* (nothing ran); once the connector is spawned, its exit code passes through. */
export async function pasqalLaunch(argv: string[], env: NodeJS.ProcessEnv = process.env): Promise<number> {
const head = argv[0];
if (head === "--help" || head === "-h") {
console.log(USAGE);
return 0;
}
if (!head) {
console.error(`amico-pasqal: no connector script given\n${USAGE}`);
return 64;
}
if (head.startsWith("-")) {
// No launcher flags exist by design — nothing can put a secret on OUR argv.
console.error(`amico-pasqal: unknown flag ${head} (the launcher takes no flags)\n${USAGE}`);
return 64;
}
const script = resolve(head);
if (!existsSync(script)) {
console.error(`amico-pasqal: connector script not found: ${script}`);
return 64;
}

let creds: PasqalCredentials;
let python: string;
try {
creds = readPasqalCredentials(env);
assertPasqalFresh(creds);
python = resolvePasqalInterpreter(env);
} catch (e) {
if (e instanceof ConfigError) {
console.error(`amico-pasqal: ${e.message}`);
return 64;
}
throw e;
}

// Minimal child env, built from scratch: the resolved PATH (shebang/interpreter
// resolution inside the child) + the credential pair. NOTHING else crosses over.
const childEnv: Record<string, string> = {
PASQAL_TOKEN: creds.token,
PASQAL_PROJECT_ID: creds.projectId,
};
if (env.PATH) childEnv.PATH = env.PATH;

return new Promise<number>((resolveP) => {
const child = spawn(python, [script, ...argv.slice(1)], { stdio: "inherit", env: childEnv });
child.on("error", (e) => {
// Post-resolution spawn fault (raced deletion, EACCES) — still config-class.
console.error(`amico-pasqal: failed to start interpreter: ${(e as NodeJS.ErrnoException).code ?? "spawn error"}`);
resolveP(64);
});
child.on("close", (code, signal) => resolveP(code ?? signalCode(signal)));
});
}
59 changes: 59 additions & 0 deletions packages/amico-run/test/cross_repo_fixtures.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// packages/amico-run/test/cross_repo_fixtures.test.ts — the amicode half of the
// cross-repo golden-fixture handshake (issue #168 AC4, corpus minted by #162).
//
// PROVENANCE: test/fixtures/credentials/{cloud.json,pasqal.json} are vendored
// BYTE-FOR-BYTE from the opencode fork's canonical corpus at
// packages/opencode/test/server/fixtures/credentials/cloud.json
// packages/opencode/test/server/fixtures/credentials/pasqal.json
// (source of record; do not hand-edit the vendored copies). Both sides pin the
// same bytes: if either repo drifts — key order, whitespace, the 2-space-JSON +
// trailing-newline at-rest serialization, or any value — THIS test fails before
// the panels can disagree about what a credential file looks like.
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { readRemoteConfig } from "../src/remote_config.js";
import { readPasqalCredentials } from "../src/pasqal_launch.js";

const FIXTURES = join(__dirname, "fixtures", "credentials");

// The canonical at-rest bytes (2-space JSON + trailing newline), spelled out so a
// drifted vendored copy AND a drifted serializer both fail loudly.
const CLOUD_BYTES = `{
"base_url": "https://solves.staging.harmoniqs.co",
"token": "tok-fixture-company-compute"
}
`;
const PASQAL_BYTES = `{
"project_id": "proj-fixture-pasqal",
"token": "tok-fixture-pasqal",
"expires_at": "2026-08-01T00:00:00Z"
}
`;

describe("cross-repo golden fixtures (AC4) — the #162 corpus, parsed by the REAL readers", () => {
it("cloud.json: vendored bytes are exactly the canonical serialization", () => {
expect(readFileSync(join(FIXTURES, "cloud.json"), "utf8")).toBe(CLOUD_BYTES);
});

it("cloud.json: the real readRemoteConfig (via AMICO_CLOUD_FILE) parses it to the expected {baseUrl, token}", () => {
const c = readRemoteConfig({ AMICO_CLOUD_FILE: join(FIXTURES, "cloud.json") } as NodeJS.ProcessEnv);
expect(c).toEqual({
baseUrl: "https://solves.staging.harmoniqs.co",
token: "tok-fixture-company-compute",
});
});

it("pasqal.json: vendored bytes are exactly the canonical serialization", () => {
expect(readFileSync(join(FIXTURES, "pasqal.json"), "utf8")).toBe(PASQAL_BYTES);
});

it("pasqal.json: the real readPasqalCredentials (via AMICO_PASQAL_FILE) parses it — expiry deliberately NOT checked here (parse-level, time-independent)", () => {
const c = readPasqalCredentials({ AMICO_PASQAL_FILE: join(FIXTURES, "pasqal.json") } as NodeJS.ProcessEnv);
expect(c).toEqual({
projectId: "proj-fixture-pasqal",
token: "tok-fixture-pasqal",
expiresAt: "2026-08-01T00:00:00Z",
});
});
});
4 changes: 4 additions & 0 deletions packages/amico-run/test/fixtures/credentials/cloud.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"base_url": "https://solves.staging.harmoniqs.co",
"token": "tok-fixture-company-compute"
}
5 changes: 5 additions & 0 deletions packages/amico-run/test/fixtures/credentials/pasqal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"project_id": "proj-fixture-pasqal",
"token": "tok-fixture-pasqal",
"expires_at": "2026-08-01T00:00:00Z"
}
79 changes: 79 additions & 0 deletions packages/amico-run/test/pasqal_cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// packages/amico-run/test/pasqal_cli.test.ts — the `amico-pasqal` BIN end-to-end
// (issue #168): esbuild bundle → node dist/amico-pasqal.js, the cli.test.ts idiom.
// The unit contract lives in pasqal_launch.test.ts; this file proves the wiring —
// entry point, exit-code relay, stderr carriage — through the shipped artifact.
import { describe, it, expect, beforeAll } from "vitest";
import { execFileSync } from "node:child_process";
import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpRoot } from "./helpers.js";

const BUNDLE = join(__dirname, "..", "dist", "amico-pasqal.js");
beforeAll(() => {
execFileSync("node", [join(__dirname, "..", "esbuild.config.mjs")], { cwd: join(__dirname, "..") });
});

const TOKEN = "tok-sekret-bin-layer";
const PROJECT = "proj-bin-layer";

function run(args: string[], env: Record<string, string> = {}): { code: number; stdout: string; stderr: string } {
try {
const stdout = execFileSync("node", [BUNDLE, ...args], { encoding: "utf8", env: { ...process.env, ...env } });
return { code: 0, stdout, stderr: "" };
} catch (e) {
const err = e as { status?: number; stdout?: string; stderr?: string };
return { code: err.status ?? -1, stdout: err.stdout ?? "", stderr: err.stderr ?? "" };
}
}

describe("amico-pasqal bin (bundle e2e)", () => {
it("happy lane: env-injects the credential pair into the connector, exit 0, silent stderr", () => {
const root = tmpRoot();
const out = join(root, "record.json");
const shim = join(root, "fake-python");
writeFileSync(
shim,
`#!/usr/bin/env node\nrequire("node:fs").writeFileSync(${JSON.stringify(out)}, JSON.stringify({ argv: process.argv, env: process.env }));\n`,
);
chmodSync(shim, 0o755);
const cred = join(root, "pasqal.json");
writeFileSync(cred, JSON.stringify({ project_id: PROJECT, token: TOKEN }, null, 2) + "\n");
const script = join(root, "connector.py");
writeFileSync(script, "# fake connector\n");

const r = run([script, "--devices", "FRESNEL"], { AMICO_PASQAL_FILE: cred, AMICO_PYTHON: shim });
expect(r.code).toBe(0);
expect(r.stderr).toBe("");
const rec = JSON.parse(readFileSync(out, "utf8")) as { argv: string[]; env: Record<string, string> };
expect(rec.env.PASQAL_TOKEN).toBe(TOKEN);
expect(rec.env.PASQAL_PROJECT_ID).toBe(PROJECT);
expect(rec.argv.slice(2)).toEqual([script, "--devices", "FRESNEL"]);
for (const a of rec.argv) expect(a).not.toContain(TOKEN); // AC2 holds at the bin layer too
});

it("missing credential file → 64, 'not connected … Connections panel' on stderr", () => {
const root = tmpRoot();
const script = join(root, "connector.py");
writeFileSync(script, "# fake connector\n");
const r = run([script], { AMICO_PASQAL_FILE: join(root, "absent.json") });
expect(r.code).toBe(64);
expect(r.stderr).toMatch(/not connected/);
expect(r.stderr).toMatch(/Connections panel/);
});

it("--help → 0 and documents the env-only contract; a bare call is a usage error (64)", () => {
const help = run(["--help"]);
expect(help.code).toBe(0);
expect(help.stdout).toMatch(/PASQAL_TOKEN, PASQAL_PROJECT_ID/);
expect(help.stdout).toMatch(/ENV ONLY/);
expect(run([]).code).toBe(64);
});

it("the bin is declared and its launcher script exists (package wiring)", () => {
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8")) as {
bin: Record<string, string>;
};
expect(pkg.bin["amico-pasqal"]).toBe("./launcher/amico-pasqal");
expect(existsSync(join(__dirname, "..", "launcher", "amico-pasqal"))).toBe(true);
});
});
Loading
Loading