From 1e83e32776711deb80989cc5097c8c972e2f3396 Mon Sep 17 00:00:00 2001 From: Jack Champagne Date: Tue, 28 Jul 2026 17:11:10 -0400 Subject: [PATCH] test(server): isolate the keychain in the connections-route suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unit job died with exit 137 and reported nothing. Not an OOM (peak 0.37GB of 32GB, zero faults) — a segfault inside @napi-rs/keyring's native setPassword(), reached from the pasqal submit success path. Install the in-memory secret store, as the sibling amicode-connections.test.ts already does. Production is unaffected: the same write succeeds under `bun run … serve`, verified end-to-end against Pasqal with real credentials. Also swap the stub validator from a bun-executed .mjs to a python3-executed .py. amicode provisions /venvs/pasqal-connector and passes it as AMICO_PYTHON (harmoniqs/amicode#189), so the production interpreter is always a real python; bun-as-interpreter tested a configuration that never ships. Drop the windows unit lane: opencode.lock.json ships darwin-arm64 and linux-x64 only, and it was the ~50min long pole while red for an unrelated reason (#76). Refs #82, #76 --- .github/workflows/test.yml | 17 +++++++- .../server/amicode-connections-routes.test.ts | 43 +++++++++++++------ 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d438aaf92..ca3740c90 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,11 +27,14 @@ jobs: strategy: fail-fast: false matrix: + # linux only. opencode.lock.json ships darwin-arm64 + linux-x64, so a + # windows unit lane gates a platform this fork does not distribute, and + # it is the ~50min long pole on every run. It was also red on arrival + # for a reason unrelated to any change under test (POSIX path separators + # hardcoded in amicode-vaults.test.ts — harmoniqs/opencode#76). settings: - name: linux host: ubuntu-latest - - name: windows - host: windows-latest runs-on: ${{ matrix.settings.host }} defaults: run: @@ -47,6 +50,16 @@ jobs: with: node-version: "24" + # The pasqal connection-route tests spawn the validator through the REAL + # spawn path, with a recording stub standing in for pasqal_validate.py. + # In production amicode provisions /venvs/pasqal-connector and + # passes it as AMICO_PYTHON (harmoniqs/amicode#189), so the interpreter is + # always a real python. The stub needs an interpreter but NOT the SDK. + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Setup Bun uses: ./.github/actions/setup-bun diff --git a/packages/opencode/test/server/amicode-connections-routes.test.ts b/packages/opencode/test/server/amicode-connections-routes.test.ts index 312cc4607..e7413be6a 100644 --- a/packages/opencode/test/server/amicode-connections-routes.test.ts +++ b/packages/opencode/test/server/amicode-connections-routes.test.ts @@ -25,6 +25,7 @@ import { solverModeFile, PASQAL_CONFIG_WARNING, } from "@/server/amicode/connections" +import { inMemorySecretStore, setPasqalSecretStore } from "@/server/amicode/pasqal-secret" import { resetDatabase } from "../fixture/db" import { disposeAllInstances } from "../fixture/fixture" @@ -92,12 +93,23 @@ const ENV_KEYS = [ "AMICODE_OPS_DIR", "AMICO_PYTHON", "AMICO_PASQAL_VALIDATOR", + // pasqal-secret.ts exposes this so a sandbox/test run gets its own keychain + // slot. Saved/restored here so a leak can never reach the real slot even if + // the in-memory store below is ever removed. + "AMICO_PASQAL_KEYCHAIN_SERVICE", ] as const let savedEnv: Record let dir: string +let restoreSecretStore: () => void beforeEach(() => { savedEnv = Object.fromEntries(ENV_KEYS.map((k) => [k, process.env[k]])) + // MANDATORY, not hygiene: the real store reaches @napi-rs/keyring's native + // setPassword(), which segfaults Bun 1.3.14 when called from inside an Effect + // route handler under `bun test` (harmoniqs/opencode#82). Production is + // unaffected — the same write succeeds under `bun run … serve`. Without this + // seam the whole `unit` job dies with exit 137 and reports nothing. + restoreSecretStore = setPasqalSecretStore(inMemorySecretStore()) dir = mkdtempSync(path.join(tmpdir(), "amicode-conn-routes-")) process.env.AMICO_CLOUD_FILE = path.join(dir, "cloud.json") process.env.AMICO_PASQAL_FILE = path.join(dir, "pasqal.json") @@ -109,6 +121,7 @@ beforeEach(() => { }) afterEach(async () => { await backgroundRevalidationsSettled() // drain background refreshes BEFORE the env flips to the next test's dir + restoreSecretStore?.() for (const k of ENV_KEYS) { if (savedEnv[k] === undefined) delete process.env[k] else process.env[k] = savedEnv[k] @@ -282,24 +295,24 @@ function stageStubValidator() { const harness = mkdtempSync(path.join(tmpdir(), "amicode-pasqal-stub-")) const scenarioFile = path.join(harness, "scenario.json") const recordFile = path.join(harness, "record.json") - const script = path.join(harness, "pasqal_validate_stub.mjs") + const script = path.join(harness, "pasqal_validate_stub.py") writeFileSync( script, [ - `import { readFileSync, writeFileSync } from "node:fs"`, - `writeFileSync(${JSON.stringify(recordFile)}, JSON.stringify({`, - ` keys: Object.keys(process.env).sort(),`, - ` username: process.env.PASQAL_USERNAME ?? null,`, - ` password: process.env.PASQAL_PASSWORD ?? null,`, - ` project_id: process.env.PASQAL_PROJECT_ID ?? null,`, - ` argv: process.argv.slice(2),`, + `import json, os, sys`, + `open(${JSON.stringify(recordFile)}, "w").write(json.dumps({`, + ` "keys": sorted(os.environ.keys()),`, + ` "username": os.environ.get("PASQAL_USERNAME"),`, + ` "password": os.environ.get("PASQAL_PASSWORD"),`, + ` "project_id": os.environ.get("PASQAL_PROJECT_ID"),`, + ` "argv": sys.argv[1:],`, `}))`, - `const scenario = JSON.parse(readFileSync(${JSON.stringify(scenarioFile)}, "utf8"))`, - `if (scenario.stdout) console.log(scenario.stdout)`, - `process.exit(scenario.exitCode)`, + `scenario = json.load(open(${JSON.stringify(scenarioFile)}))`, + `if scenario.get("stdout"): print(scenario["stdout"])`, + `sys.exit(scenario["exitCode"])`, ].join("\n"), ) - process.env.AMICO_PYTHON = process.execPath // "interpreter" = the bun binary + process.env.AMICO_PYTHON = "python3" // a REAL interpreter, as production uses process.env.AMICO_PASQAL_VALIDATOR = script return { scenario(exitCode: number, stdout = "") { @@ -349,7 +362,11 @@ describe("pasqal over the route tree — REAL spawn against a staged stub valida // the child saw EXACTLY the minimal declared env — the real spawn spreads nothing const record = stub.record() - expect(record.keys).toEqual(["PASQAL_PASSWORD", "PASQAL_PROJECT_ID", "PASQAL_USERNAME", "PATH"]) + // CPython injects LC_CTYPE into its own environ under PEP 538 C-locale + // coercion, so filter interpreter-owned locale vars. The point of the + // assertion is that the SPAWNER declared nothing beyond this set. + const declared = record.keys.filter((k) => !/^(LC_[A-Z_]+|LANG)$/.test(k)) + expect(declared).toEqual(["PASQAL_PASSWORD", "PASQAL_PROJECT_ID", "PASQAL_USERNAME", "PATH"]) expect(record.username).toBe(PASQAL.username) expect(record.password).toBe(PASQAL.password) expect(record.project_id).toBe(PASQAL.project_id)