Files
PaperClipAI/server/src/__tests__/ai-connections.test.ts
T
DottaandPaperclip 2b14ec03a4 fix(ai-connections): remove account execution locks (#18778)
## Thinking Path

> - Paperclip manages AI agents and the accounts that supply their model
credentials.
> - Several runs can use the same AI connection at the same time.
> - Runtime preparation treated unfinished credential cleanup as
exclusive account ownership.
> - Manual account tests also held a company credential mutex for the
entire provider test.
> - A failed cleanup could therefore cancel fresh runs before the
provider started and keep them in automatic retries.
> - This pull request removes that execution gate and those read and
probe locks.
> - Each invocation keeps its own credential home. Credential updates
still use the existing freshness and authorization checks.

## Linked Issues or Issue Description

**What happened?**

Fresh runs stopped with `ai_connection_busy` and the message “The
selected AI account is still completing credential cleanup. Retry
shortly.” Pending cleanup from an earlier invocation blocked every new
invocation of that account. Concurrent manual account tests also waited
on a company mutex.

**Expected behavior**

AI connections support concurrent runs and account tests. Cleanup for
one private runtime must not prevent another private runtime from using
the same account.

**Steps to reproduce**

1. Prepare a managed OpenAI account and retain an earlier runtime whose
cleanup has not finished.
2. Prepare another run with that account. The old implementation rejects
it before provider work starts.
3. Alternatively, keep one manual account test open and start a second
test with the same account. The old implementation prevents the second
provider callback from starting.

**Paperclip version or commit**

Reproduced with two PostgreSQL regressions on
`17e8bd773543d62ab3f444c55255acfd82575efd`. Both fail before the fix and
pass after it.

**Deployment mode**

The reported deployment used authenticated Cloud. Local reproduction
uses disposable PostgreSQL and fixture credentials. No live provider
credentials are required for these regressions.

Related search found #15952, which concerns account removal and provider
application archival. It does not remove this runtime cleanup gate. This
fix does not introduce the account concurrency cap requested in #14564.
ROADMAP.md was checked; this change restores concurrent account use.

## What Changed

- Remove the account-wide cleanup assertion and cleanup mutex from run
admission.
- Read ordinary subscription credentials without acquiring the company
file mutex or grant and secret write locks. Only an actual durable
replacement recovery enters the existing write transaction.
- Remove the mutex held across manual account tests.
- Retry retained cleanup without making new runtime preparation wait for
it. A home-specific cleanup check considers only that exact private
home.
- Keep cleanup attempts for one runtime idempotent. Keep physical
retirement, credential freshness, reconnect, revocation and company
checks.
- Update concurrency, refresh and cleanup regressions, and the
implementation contract.

## Verification

- Before/after: the pending-cleanup and simultaneous-probe regressions
both fail on the original code and pass with the fix.
- Affected suites: 186 tests passed across `ai-connections.test.ts`,
`antigravity-ai-connections.integration.test.ts`, and
`retained-google-credential-cleanup.test.ts`. They use real PostgreSQL,
encrypted fixture credentials and controlled provider callbacks.
- `pnpm -r typecheck` passed.
- `node scripts/check-maps.mjs` and `git diff --check` passed.
- `pnpm build` passed on the rebased branch. The server typecheck and
production server build both passed again on final commit
`03bd3973bdf85108bbd82355abdb217f0e1c83bd` after the runtime-home
ownership changes.
- Caller coverage: all 166 cases pass across native credential handoff,
managed Google subscription writeback, model discovery, heartbeat
configuration and hired-agent execution. The full hiring file passed all
79 cases after its admission assertion was corrected. Together with the
affected suites, 352 targeted cases pass.
- The final home-binding check passed all 188 cases across the account,
managed Google writeback, model discovery and retained-cleanup suites.
- Review follow-up: two real-PostgreSQL cases pass for quota refresh and
concurrent Google account tests. The quota case observes a credential
row update in progress, verifies that the runtime takes no account file
mutex, and confirms that the private home receives the committed
replacement token. The Google case completes a second provider callback
while the first callback remains open in a separate private home.
- Greptile rated the runtime change 4/5 and found only misplaced map
patch notes. Those additions were removed. The implementation
documentation remains in `doc/connections/AI-CONNECTIONS.md` and the
implementation contract.
- `pnpm test:run` was attempted. Chat partition 1 passed 351 expanded
cases. Partition 2 passed 324 cases and failed one Teams file-receipt
API case at its 15-second deadline: `refuses generic resolution with
stale version transfer preconditions without changing receipts`. That
case exercises publication resolution, not AI account execution. Its
isolated rerun passed. The remaining local partitions did not run after
that failure. This is not a claim that the full local suite passed.
- Wider caller checks initially identified old serialization
expectations and two cleanup retry cases. Updated assertions now require
concurrent account use. The partial-removal correction lets a retained
home finish removal after a durable credential handoff. All affected
caller cases pass, including the full hiring file.
- Live provider concurrency and deployment recovery have not been
qualified. This PR does not claim that the reported Cloud deployment
contains the fix.

## Risks

- A new invocation can use the currently saved credential while another
runtime still owns a refreshed copy. The existing writeback checks
decide which credential can replace the saved value.
- Actual credential updates and durable replacement recovery retain
atomic storage transactions. This change removes execution
serialization, not the integrity checks on stored credentials.
- Unfinished cleanup retains its private home and its existing idle
hold. This change does not certify a previous provider process as
stopped.
- There is no database migration, credential format change, permission
expansion or ambient-account fallback.

## Model Used

- OpenAI GPT-6 through Codex desktop, with source editing, shell
execution, PostgreSQL fixture tests and read-only browser diagnosis. The
session does not expose the exact deployment ID or context-window size.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-03 07:56:37 -06:00

2856 lines
236 KiB
TypeScript

import { completeSetupTokenAiConnection } from "../services/setup-token-ai-connection.js";
import { connectionIntentService } from "../services/connection-intents.js";
import { connectionIntentDeliveryService } from "../services/connection-intent-delivery.js";
import { issueRecoveryActionService } from "../services/issue-recovery-actions.js";
import { localAiLoginService } from "../services/local-ai-login.js";
// Background plan observations have their own provider-fixture coverage. Runtime
// auth tests must not send the selected fixture credentials to a real provider.
vi.mock("../services/subscription-refresh.js", () => ({ refreshSubscriptionConnection: vi.fn().mockResolvedValue(undefined) }));
import * as localCredentials from "../services/local-ai-credentials.js";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { createHash, randomUUID } from "node:crypto";
import { spawn, spawnSync } from "node:child_process";
import { createRequire } from "node:module";
import { localEncryptedProvider } from "../secrets/local-encrypted-provider.js";
import { promises as fs } from "node:fs";
import { mkdtemp, realpath, rm, access, readFile, writeFile, stat } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { and, eq, sql } from "drizzle-orm";
import { createDb, companies, agents, agentTaskSessions, heartbeatRuns, companyMemberships, connectionGrants, toolApplications, connectionGrantDelegations, connectionGrantMembers, toolConnections, toolConnectionInstalls, aiConnectionDefaults, aiProviderDefaults, adapterAuthSessions, environments, plugins, issues, issueThreadInteractions, issueRecoveryActions, connectionIntentDeliveries, agentWakeupRequests, companySecrets, companySecretVersions, userSecretDefinitions, principalPermissionGrants } from "@paperclipai/db";
import { startEmbeddedPostgresTestDatabase } from "@paperclipai/db/test-embedded-postgres";
import { fetchCompanyQuotaWindows } from "../services/quota-windows.js";
import { quotaCredentialRecoveryPath, quotaCredentialRecovery, quotaCredentialHash } from "../services/quota-credential-recovery.js";
import { reconcileRetainedAiCleanup, retainAiCredentialCleanup } from "../services/retained-ai-credential-cleanup.js";
import { readAiConnectionConfigurationFailure } from "../services/ai-connection-configuration-failure.js";
import { aiConnectionService } from "../services/ai-connections.js";
import { DELIVERY_QUEUES, subscribeDeliveryWork } from "../services/delivery-work-notifications.js";
import { syncConnectionCredentialBindings } from "../services/connection-credential-bindings.js";
import * as codexAdapter from "@paperclipai/adapter-codex-local/server";
import { WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE } from "@paperclipai/adapter-utils/workspace-restore-merge";
import * as executionTarget from "@paperclipai/adapter-utils/execution-target";
import * as runnerProbe from "../vendor/paperclip-runner/index.js";
import * as nativeCodexRunner from "../services/native-runtime/native-codex-runner.js";
import { testNativeRunnerAuthentication } from "../services/native-runtime/setup-readiness.js";
import { prepareManagedAiRuntime, withManagedAiProbe, assertManagedAiProjectAuth, isAiConnectionBusy, managedAiSessionFingerprintConfig } from "../services/ai-connection-runtime.js";
import { execute as executeGemini, testEnvironment as testGeminiEnvironment } from "@paperclipai/adapter-gemini-local/server";
import { toolAccessService } from "../services/tool-access.js";
import { secretService } from "../services/secrets.js";
import { resolveExecutionRunAdapterConfig } from "../services/heartbeat.js";
import { aiConnectionBindingSchema, connectionPurposeTransportSchema, isAiConnectionCompatible } from "@paperclipai/shared";
import { openRouterClaudeHeaders } from "@paperclipai/shared/openrouter-attribution";
import { createServer } from "node:http";
import { once } from "node:events";
import express from "express";
import request from "supertest";
import { aiConnectionRoutes, canInstallSharedAiConnectionForNewAgent, responsibleUserForAiRequest } from "../routes/ai-connections.js";
import { validateAiApiKey } from "../routes/ai-connections.js";
import * as copilotProbe from "../services/copilot-connection-probe.js";
import * as settingsModule from "../services/instance-settings.js";
import { agentRoutes } from "../routes/agents.js";
import type { PluginWorkerManager } from "../services/plugin-worker-manager.js";
vi.mock("../services/local-ai-browser-login.js", () => ({
startLocalBrowserLogin: () => ({ authorizationUrl: "https://auth.openai.com/codex/device", code: "ABCD-EFGHJ", abort: () => {} }),
}));
let database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
let db: ReturnType<typeof createDb>;
let home: string;
const companyId = randomUUID();
const otherCompanyId = randomUUID();
const agentId = randomUUID();
let service: ReturnType<typeof aiConnectionService>;
const binding = { provider: "anthropic", method: "api_key", mode: "responsible_user" } as const;
const input = { companyId, agentId, adapterType: "claude_local", binding };
const create = (userId: string, name: string, ownership: "personal" | "shared" = "personal") => service.save(companyId, userId, { provider: "anthropic", method: "api_key", ownership, name, apiKey: "fixture", agentIds: [], allAgents: true }, `fixture-${name}`);
beforeAll(async () => {
home = await realpath(await mkdtemp(path.join(os.tmpdir(), "paperclip-ai-tests-")));
vi.stubEnv("PAPERCLIP_HOME", home);
vi.stubEnv("PAPERCLIP_INSTANCE_ID", "ai-connection-fixture");
database = await startEmbeddedPostgresTestDatabase("paperclip-ai-db-");
db = createDb(database.connectionString);
service = aiConnectionService(db);
await db.insert(companies).values([{ id: companyId, name: "AI connection tests", issuePrefix: "AIT" }, { id: otherCompanyId, name: "Other", issuePrefix: "AIO" }]);
await db.insert(agents).values({ id: agentId, companyId, name: "Nova", adapterType: "claude_local" });
await db.insert(companyMemberships).values(["alice", "bob"].map(principalId => ({ companyId, principalId, principalType: "user", status: "active", membershipRole: "member" })));
}, 90000);
afterAll(async () => { await database?.cleanup(); vi.unstubAllEnvs(); if (home) await rm(home, { recursive: true, force: true }); });
describe("managed AI connections", () => {
it.each([
{ provider: "openrouter", envKey: "OPENROUTER_API_KEY", routed: false },
{ provider: "openrouter", envKey: "OPENROUTER_API_KEY", routed: true },
{ provider: "openai", envKey: "OPENAI_API_KEY", routed: false },
{ provider: "anthropic", envKey: "ANTHROPIC_API_KEY", routed: false },
{ provider: "google", envKey: "GEMINI_API_KEY", routed: false },
] as const)("prepares native Pi with isolated $provider credentials (routing=$routed)", async ({ provider, envKey, routed }) => {
const owner = `pi-managed-${provider}-${routed}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const token = "fixture-pi-provider-key";
const account = await service.save(companyId, owner, { provider, method: "api_key", ownership: "personal", name: owner, apiKey: token, allAgents: true, agentIds: [],
...(routed ? { routing: { kind: "openrouter" as const, protocol: "chat" as const, auth: "bearer" as const, models: [] } } : {}) }, token);
if (!routed) await service.setDefault(companyId, owner, account.grantId);
const selected = routed
? { provider, method: "api_key" as const, mode: "delegated" as const, ...account }
: { provider, method: "api_key" as const, mode: "responsible_user" as const };
const piInput = { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner", binding: selected,
config: { provider: "acpx", acpxAgent: "pi", model: `${provider}/provider-model`, piThinkingLevel: "low", env: { OPENAI_API_KEY: "stale-openai", ANTHROPIC_API_KEY: "stale-anthropic" } } };
const runtime = await prepareManagedAiRuntime(db, piInput);
try {
expect(runtime.config).toMatchObject({ provider: "acpx", acpxAgent: "pi", model: piInput.config.model, env: { OPENCODE_CONFIG_CONTENT: "", PAPERCLIP_PI_PROVIDERS: "" } });
for (const key of ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GEMINI_API_KEY", "OPENROUTER_API_KEY", "CODEX_API_KEY"]) expect(runtime.config.env[key]).toBe(key === envKey ? token : "");
expect(runtime.attribution).toMatchObject({ connectionId: account.connectionId, grantId: account.grantId, provider });
} finally { await runtime.cleanup(); }
await expect(prepareManagedAiRuntime(db, { ...piInput, config: { ...piInput.config, env: { PAPERCLIP_PI_PROVIDERS: "{}" } } })).rejects.toThrow("routing is incompatible");
});
it.each([200, 401])("reverifies native Pi OpenRouter keys without requiring the Legacy Pi CLI (HTTP %s)", async status => {
const owner = `pi-adoption-${status}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const token = `fixture-pi-adoption-${status}`;
const account = await service.save(companyId, owner, { provider: "openrouter", method: "api_key", ownership: "personal", name: owner, apiKey: token, allAgents: true, agentIds: [], routing: { kind: "openrouter", protocol: "chat", auth: "bearer", models: [] } }, token);
const selected = { provider: "openrouter", method: "api_key", mode: "delegated", ...account } as const;
const runtime = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner", binding: selected, config: { provider: "acpx", acpxAgent: "pi", model: "openrouter/anthropic/claude-sonnet-4.5" } });
const { requireServerAdapter } = await import("../adapters/index.js");
const { agentEnvironmentTestService } = await import("../services/agent-environment-test.js");
const native = vi.spyOn(requireServerAdapter("paperclip_runner"), "testEnvironment").mockResolvedValue({ adapterType: "paperclip_runner", status: "pass", testedAt: new Date().toISOString(), checks: [{ code: "acpx_runtime_ready", level: "info", message: "Pinned Pi runtime verified" }] });
const legacy = vi.spyOn(requireServerAdapter("pi_local"), "testEnvironment").mockRejectedValue(new Error("Legacy Pi CLI must not be required"));
const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify({ data: {} }), { status }));
try {
const result = await agentEnvironmentTestService(db).testManagedEnvironment("paperclip_runner", { companyId, adapterType: "paperclip_runner", config: runtime.config }, selected, runtime, agentId);
expect(result.status).toBe(status === 200 ? "pass" : "fail");
expect(result.checks).toContainEqual(expect.objectContaining({ code: status === 200 ? "ai_connection_api_key_reverified" : "ai_connection_api_key_rejected" }));
expect(legacy).not.toHaveBeenCalled();
expect(request).toHaveBeenCalled();
} finally { native.mockRestore(); legacy.mockRestore(); request.mockRestore(); await runtime.cleanup(); }
});
it("defers a new plugin agent's Copilot check until its saved-agent lifecycle can acquire the computer", async () => {
const { errorHandler } = await import("../middleware/index.js");
const owner = "copilot-plugin-bootstrap", pluginId = randomUUID();
const pluginKey = `test.bootstrap.${pluginId}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(plugins).values({ id: pluginId, pluginKey, packageName: "@test/bootstrap", version: "1.0.0", apiVersion: 1,
categories: ["automation"], status: "ready", manifestJson: { id: pluginKey, apiVersion: 1, version: "1.0.0",
displayName: "Bootstrap", description: "Bootstrap fixture", author: "Test", categories: ["automation"],
capabilities: ["environment.drivers.register"], entrypoints: { worker: "worker.js" },
environmentDrivers: [{ driverKey: "tasks", displayName: "Tasks", supportsTasks: true, configSchema: { type: "object" } }] } });
const [environment] = await db.insert(environments).values({ companyId, name: "Agent computer", driver: "plugin",
config: { pluginKey, driverKey: "tasks", driverConfig: {} } }).returning();
const token = "github_pat_fixture_plugin_bootstrap";
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Bootstrap", apiKey: token, agentIds: [], allAgents: false }, token);
await service.setDefault(companyId, owner, account.grantId);
const probe = vi.spyOn(copilotProbe, "probeCopilotExecutionTarget").mockRejectedValue(new Error("No computer exists before the agent is saved"));
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: owner, companyIds: [companyId], isInstanceAdmin: true }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
try {
const response = await request(app).post(`/api/companies/${companyId}/agents`).send({ name: "Plugin bootstrap", role: "general",
adapterType: "paperclip_runner", adapterConfig: { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" },
defaultEnvironmentId: environment.id, runtimeConfig: { aiConnection: { provider: "github", method: "api_key", mode: "responsible_user" } } });
expect(response.status, JSON.stringify(response.body)).toBe(201);
expect(response.body.defaultEnvironmentId).toBe(environment.id);
expect(response.body.lifecycleState).toBe("preparing");
expect(probe).not.toHaveBeenCalled();
} finally { probe.mockRestore(); }
});
it.each(["create", "hire", "edit"])("refuses Copilot %s before probing or persisting when forced Kubernetes is missing", async action => {
const { errorHandler } = await import("../middleware/index.js");
const owner = `copilot-kubernetes-${action}`, id = randomUUID();
const config = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" };
const aiConnection = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(agents).values({ id, companyId, name: "Kubernetes fixture", adapterType: "paperclip_runner", adapterConfig: config, runtimeConfig: { aiConnection } });
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Kubernetes fixture", apiKey: "github_pat_fixture_kubernetes", agentIds: [id], allAgents: false }, "github_pat_fixture_kubernetes");
await service.setDefault(companyId, owner, account.grantId);
const [company] = await db.select().from(companies).where(eq(companies.id, companyId));
await db.update(companies).set({ requireBoardApprovalForNewAgents: false }).where(eq(companies.id, companyId));
const before = await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId));
const originalSettings = settingsModule.instanceSettingsService;
const settings = vi.spyOn(settingsModule, "instanceSettingsService").mockImplementation(database => {
const service = originalSettings(database);
return { ...service, getGeneral: async () => ({ ...await service.getGeneral(), executionMode: "kubernetes" as const }) };
});
const probe = vi.spyOn(copilotProbe, "probeCopilotExecutionTarget");
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: owner, companyIds: [companyId], isInstanceAdmin: true }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
try {
const changedConfig = { ...config, model: "prospective-model" };
const response = action === "edit"
? await request(app).patch(`/api/agents/${id}`).send({ adapterConfig: changedConfig })
: await request(app).post(`/api/companies/${companyId}/${action === "hire" ? "agent-hires" : "agents"}`).send({ name: "Forced Kubernetes", role: "general", adapterType: "paperclip_runner", adapterConfig: changedConfig, runtimeConfig: { aiConnection } });
expect(response.status, JSON.stringify(response.body)).toBe(422);
expect(response.body.details.code).toBe("copilot_environment_unavailable");
expect(probe).not.toHaveBeenCalled();
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].adapterConfig).toEqual(config);
expect(await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId))).toHaveLength(before.length);
} finally {
probe.mockRestore(); settings.mockRestore();
await db.update(companies).set({ requireBoardApprovalForNewAgents: company.requireBoardApprovalForNewAgents }).where(eq(companies.id, companyId));
}
});
it.each(["create", "hire", "edit"].flatMap(action => [true, false].map(connectionProvided => ({ action, connectionProvided }))))(
"refuses unavailable Copilot models before $action persists configuration (connection binding: $connectionProvided)", async ({ action, connectionProvided }) => {
const { unprocessable } = await import("../errors.js");
const { errorHandler } = await import("../middleware/index.js");
const owner = `copilot-model-${action}-${connectionProvided}`, id = randomUUID();
const savedConfig = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" };
const aiConnection = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(agents).values({ id, companyId, name: `Copilot ${action} fixture`, adapterType: "paperclip_runner", adapterConfig: savedConfig, runtimeConfig: connectionProvided ? { aiConnection } : {} });
const token = `github_pat_fixture_model_${action}`;
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: `Model ${action}`, apiKey: token, agentIds: [id], allAgents: false }, token);
await service.setDefault(companyId, owner, account.grantId);
const [company] = await db.select().from(companies).where(eq(companies.id, companyId));
await db.update(companies).set({ requireBoardApprovalForNewAgents: false }).where(eq(companies.id, companyId));
const before = await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId));
const probe = vi.spyOn(copilotProbe, "probeCopilotExecutionTarget").mockRejectedValue(unprocessable("The selected Copilot model is unavailable for this account.", { code: "COPILOT_MODEL_UNAVAILABLE" }));
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: owner, companyIds: [companyId], isInstanceAdmin: true }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
const config = { ...savedConfig, model: "unavailable-copilot-model" };
try {
const response = action === "edit"
? await request(app).patch(`/api/agents/${id}`).send({ adapterConfig: config })
: await request(app).post(`/api/companies/${companyId}/${action === "hire" ? "agent-hires" : "agents"}`).send({ name: `Unavailable ${action}`, role: "general", adapterType: "paperclip_runner", adapterConfig: config, ...(connectionProvided ? { runtimeConfig: { aiConnection } } : {}) });
expect(response.status, JSON.stringify(response.body)).toBe(422);
expect(response.body).toMatchObject({ error: "The selected Copilot model is unavailable for this account." });
expect(probe).toHaveBeenCalledExactlyOnceWith(token, null, "unavailable-copilot-model");
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].adapterConfig).toEqual(savedConfig);
expect(await db.select({ id: agents.id }).from(agents).where(eq(agents.companyId, companyId))).toHaveLength(before.length);
expect(JSON.stringify(response.body)).not.toContain(token);
} finally {
probe.mockRestore();
await db.update(companies).set({ requireBoardApprovalForNewAgents: company.requireBoardApprovalForNewAgents }).where(eq(companies.id, companyId));
}
});
it("verifies the exact available Copilot model before a model-only edit with an unchanged connection", async () => {
const { errorHandler } = await import("../middleware/index.js");
const owner = "copilot-model-valid", id = randomUUID();
const aiConnection = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
const config = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna" };
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(agents).values({ id, companyId, name: "Copilot available model", adapterType: "paperclip_runner", adapterConfig: { ...config, model: "previous-model" }, runtimeConfig: { aiConnection } });
const token = "github_pat_fixture_model_valid";
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Valid model", apiKey: token, agentIds: [id], allAgents: false }, token);
await service.setDefault(companyId, owner, account.grantId);
const probe = vi.spyOn(copilotProbe, "probeCopilotExecutionTarget").mockResolvedValue({ status: "verified", version: "1.0.88", profileDigest: "fixture-profile", promptSent: false, models: [{ id: config.model, label: "GPT" }] });
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: owner, companyIds: [companyId], isInstanceAdmin: true }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
try {
const response = await request(app).patch(`/api/agents/${id}`).send({ adapterConfig: config });
expect(response.status, JSON.stringify(response.body)).toBe(200);
expect(probe).toHaveBeenCalledExactlyOnceWith(token, null, config.model);
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].adapterConfig).toMatchObject(config);
expect(response.body.runtimeConfig.aiConnection).toEqual(aiConnection);
} finally { probe.mockRestore(); }
});
it("passes the live sandbox worker manager to Copilot connection verification", async () => {
const pluginWorkerManager = {} as PluginWorkerManager;
const probe = vi.spyOn(copilotProbe, "probeCopilotConnection").mockResolvedValue({
status: "verified", version: "1.0.88", profileDigest: "fixture-profile",
promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }],
});
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] };
next();
});
app.use("/api", aiConnectionRoutes(db, { pluginWorkerManager }));
try {
const response = await request(app).post(`/api/companies/${companyId}/ai-connections`).send({
provider: "github", method: "api_key", name: "Copilot remote route", ownership: "personal",
apiKey: "github_pat_fixture_remote", agentIds: [], allAgents: false,
});
expect(response.status).toBe(201);
expect(probe).toHaveBeenCalledWith(db, companyId, "github_pat_fixture_remote", undefined, undefined, { pluginWorkerManager });
} finally { probe.mockRestore(); }
});
it("discovers Copilot models in the selected environment with its sandbox worker", async () => {
const pluginWorkerManager = {} as PluginWorkerManager;
const [environment] = await db.insert(environments).values({ name: "Copilot model discovery", driver: "sandbox", config: { provider: "daytona" } }).returning();
const token = "github_pat_fixture_model_discovery";
const account = await service.save(companyId, "alice", { provider: "github", method: "api_key", ownership: "personal", name: "Model discovery account", apiKey: token, agentIds: [agentId], allAgents: false }, token);
await service.setDefault(companyId, "alice", account.grantId);
const probe = vi.spyOn(copilotProbe, "probeCopilotConnection").mockResolvedValue({ status: "verified", version: "1.0.88", profileDigest: "fixture-profile", promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }] });
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] }; next(); });
app.use("/api", agentRoutes(db, { pluginWorkerManager }));
try {
const response = await request(app).get(`/api/companies/${companyId}/adapters/paperclip_runner/models`).query({ provider: "acpx", acpxAgent: "copilot", environmentId: environment.id, agentId, aiConnection: JSON.stringify({ provider: "github", method: "api_key", mode: "responsible_user" }) });
expect(response.status).toBe(200);
expect(response.headers["cache-control"]).toBe("no-store");
expect(response.body).toEqual([{ id: "gpt-5.6-luna", label: "GPT" }]);
expect(probe).toHaveBeenCalledWith(db, companyId, token, environment.id, undefined, { pluginWorkerManager, targetAgentId: agentId });
expect(JSON.stringify(response.body)).not.toContain(token);
} finally { probe.mockRestore(); }
});
it("keeps Copilot model discovery behind shared installation and agent configuration authority", async () => {
const { errorHandler } = await import("../middleware/index.js");
const owner = "copilot-discovery-owner", audience = "copilot-discovery-audience";
await db.insert(companyMemberships).values([owner, audience].map(principalId => ({ companyId, principalId, principalType: "user", status: "active", membershipRole: "member" })));
await db.insert(principalPermissionGrants).values([owner, audience].map(principalId => ({ companyId, principalId, principalType: "user", permissionKey: "agents:create" })));
const token = "github_pat_fixture_shared_discovery";
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "shared", name: "Shared discovery", apiKey: token, agentIds: [], allAgents: false }, token);
await db.insert(connectionGrantMembers).values([owner, audience].map(subjectId => ({ companyId, grantId: account.grantId, subjectType: "user", subjectId })));
const binding = { provider: "github", method: "api_key", mode: "shared", ...account };
const probe = vi.spyOn(copilotProbe, "probeCopilotConnection").mockResolvedValue({ status: "verified", version: "1.0.88", profileDigest: "fixture-profile", promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }] });
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "session", userId: String(req.headers["x-test-user"] ?? audience), companyIds: [companyId], memberships: [{ companyId, membershipRole: "member", status: "active" }] }; next(); });
app.use("/api", agentRoutes(db)); app.use(errorHandler);
const url = `/api/companies/${companyId}/adapters/paperclip_runner/models`;
const query = { provider: "acpx", acpxAgent: "copilot", aiConnection: JSON.stringify(binding) };
try {
const denied = await request(app).get(url).query(query);
expect(denied.status, JSON.stringify(denied.body)).toBe(403);
expect(denied.body.error).toContain("not permitted for this agent");
expect(probe).not.toHaveBeenCalled();
const allowed = await request(app).get(url).set("x-test-user", owner).query(query);
expect(allowed.status, JSON.stringify(allowed.body)).toBe(200);
expect(probe).toHaveBeenCalledExactlyOnceWith(db, companyId, token, null, undefined, { pluginWorkerManager: undefined });
probe.mockClear();
const foreign = await request(app).get(url).set("x-test-user", owner).query({ ...query, agentId: randomUUID() });
expect(foreign.status).toBe(404);
expect(probe).not.toHaveBeenCalled();
expect(JSON.stringify([denied.body, allowed.body, foreign.body])).not.toContain(token);
} finally { probe.mockRestore(); }
});
it("preserves repository GitHub credentials for managed non-Copilot execution", async () => {
const owner = "repo-github-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await create(owner, owner);
for (const configured of [{ GH_TOKEN: "repo-gh-fixture", GITHUB_TOKEN: "repo-github-fixture" }, {}]) {
const runtime = await prepareManagedAiRuntime(db, { ...input, responsibleUserId: owner, config: { env: configured } });
try {
expect(runtime.config.env).toMatchObject({ COPILOT_GITHUB_TOKEN: "", ANTHROPIC_API_KEY: `fixture-${owner}` });
for (const key of ["GH_TOKEN", "GITHUB_TOKEN"] as const) {
if (key in configured) expect(runtime.config.env?.[key]).toBe(configured[key as keyof typeof configured]);
else expect(runtime.config.env).not.toHaveProperty(key);
}
} finally { await runtime.cleanup(); }
}
});
it("uses a vaulted Copilot token with GitHub attribution and refuses revoked or foreign grants", async () => {
const owner = "copilot-owner";
const token = "github_pat_fixture_copilot";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Copilot", apiKey: token, agentIds: [agentId], allAgents: false }, token);
const selectedBinding = { provider: "github", method: "api_key", mode: "responsible_user" } as const;
const config = { provider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna", env: { COPILOT_GITHUB_TOKEN: "ambient-never-use", GH_TOKEN: "project-never-use" } };
const runInput = { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner", binding: selectedBinding, config };
const selected = await service.select({ ...runInput, userId: owner, runnerProvider: "acpx", acpxAgent: "copilot", model: config.model });
expect(await service.credential(selected)).toBe(token);
expect(JSON.stringify(await service.list(companyId, owner))).not.toContain(token);
const runtime = await prepareManagedAiRuntime(db, runInput);
try {
expect(runtime.config).toMatchObject({ provider: "acpx", acpxAgent: "copilot", model: config.model, env: { COPILOT_GITHUB_TOKEN: token, GH_TOKEN: "", GITHUB_TOKEN: "", OPENAI_API_KEY: "", ANTHROPIC_API_KEY: "" } });
expect(runtime.attribution).toMatchObject({ provider: "github", method: "api_key", connectionId: account.connectionId, grantId: account.grantId });
expect(config.env.COPILOT_GITHUB_TOKEN).toBe("ambient-never-use");
} finally { await runtime.cleanup(); }
await expect(access(runtime.home!)).rejects.toThrow();
await expect(service.select({ ...runInput, userId: "bob", runnerProvider: "acpx", acpxAgent: "copilot" })).rejects.toThrow();
await expect(service.select({ ...runInput, companyId: otherCompanyId, userId: owner, binding: { ...selectedBinding, mode: "shared", ...account }, runnerProvider: "acpx", acpxAgent: "copilot" })).rejects.toThrow();
const network = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("usage must not send credentials"));
try {
expect(await service.probeUsage(companyId, owner, account.connectionId, account.grantId)).toMatchObject({ provider: "github", status: "unsupported", limits: [] });
expect(network).not.toHaveBeenCalled();
} finally { network.mockRestore(); }
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, account.grantId));
await expect(prepareManagedAiRuntime(db, runInput)).rejects.toThrow();
});
it("marks explicit AI selection rejections without changing their HTTP contract", async () => {
const owner = "selection-blocker-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const expectSelectionFailure = async (promise: Promise<unknown>, reason: string) => {
const error = await promise.catch((error: unknown) => error);
expect(error).toMatchObject({ status: 422, details: { code: reason } });
expect(readAiConnectionConfigurationFailure(error)).toBe(reason);
};
await expectSelectionFailure(service.select({ ...input, userId: null }), "ai_connection_responsible_user_missing");
await expectSelectionFailure(service.select({ ...input, userId: owner, adapterType: "codex_local" }), "ai_connection_incompatible");
await expectSelectionFailure(service.select({ ...input, userId: owner }), "ai_connection_default_missing");
await expectSelectionFailure(service.select({ ...input, userId: owner, binding: {
...binding, mode: "delegated", connectionId: randomUUID(), grantId: randomUUID(),
} }), "ai_connection_missing");
const account = await create(owner, "Selection blocker account");
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, account.grantId));
await expectSelectionFailure(service.select({ ...input, userId: owner }), "ai_connection_unavailable");
});
it("commits one exchanged token without writable credential recovery storage", async () => {
const owner = "quota-database-first";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { account_id: owner, access_token: "expired", refresh_token: "single-use" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const file = quotaCredentialRecoveryPath(companyId, row.grant.id);
const recoveryRoot = path.dirname(path.dirname(file));
await expect(access(recoveryRoot)).rejects.toMatchObject({ code: "ENOENT" });
const writes: string[] = [];
const rejectRecoveryWrite = (target: unknown) => {
if (String(target).startsWith(recoveryRoot)) {
writes.push(String(target));
throw Object.assign(new Error("Fixture recovery storage is read-only"), { code: "EROFS" });
}
};
const realOpen = fs.open.bind(fs), realMkdir = fs.mkdir.bind(fs);
const open = vi.spyOn(fs, "open").mockImplementation(async (...args: Parameters<typeof fs.open>) => {
rejectRecoveryWrite(args[0]);
return realOpen(...args);
});
const mkdir = vi.spyOn(fs, "mkdir").mockImplementation(async (...args: Parameters<typeof fs.mkdir>) => {
rejectRecoveryWrite(args[0]);
return realMkdir(...args);
});
const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json({ access_token: "fresh-direct", refresh_token: "replacement-direct" }));
try {
const refreshed = await service.refreshQuotaCredential(row, original, new AbortController().signal);
expect(JSON.parse(refreshed)).toMatchObject({ tokens: { access_token: "fresh-direct", refresh_token: "replacement-direct" } });
expect(await service.credential(row)).toBe(refreshed);
expect(request).toHaveBeenCalledTimes(1);
expect(writes).toEqual([]);
await expect(access(file)).rejects.toMatchObject({ code: "ENOENT" });
await expect(access(recoveryRoot)).rejects.toMatchObject({ code: "ENOENT" });
} finally { open.mockRestore(); mkdir.mockRestore(); request.mockRestore(); }
});
it("starts a second run while the same account has unfinished credential cleanup", async () => {
const owner = "concurrent-retained-cleanup";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = JSON.stringify({ tokens: { account_id: owner, access_token: "valid-access", refresh_token: "valid-refresh" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth);
const [row] = await service.quotaAccounts(companyId, owner);
const scope = { companyId, accountKey: row.grant.id }, retired = deferredSignal(), cleanup = vi.fn(async () => {});
const input = { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: {} };
retainAiCredentialCleanup({ scope, retired: retired.promise, cleanup });
let runtime: Awaited<ReturnType<typeof prepareManagedAiRuntime>> | undefined;
try {
runtime = await prepareManagedAiRuntime(db, input);
expect(await readFile(path.join(String(runtime.config.env.CODEX_HOME), "auth.json"), "utf8")).toBe(auth);
expect(cleanup).not.toHaveBeenCalled();
await runtime.cleanup();
expect(cleanup).not.toHaveBeenCalled();
} finally { retired.resolve(); await reconcileRetainedAiCleanup(scope); await runtime?.cleanup(); }
});
it("runs concurrent probes of the same account without holding the company credential mutex", async () => {
const owner = "concurrent-account-probes";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = JSON.stringify({ tokens: { account_id: owner, access_token: "valid-access", refresh_token: "valid-refresh" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth);
const input = { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: {} };
const entered = deferredSignal(), release = deferredSignal(), secondEntered = vi.fn();
const first = withManagedAiProbe(db, input, async () => { entered.resolve(); await release.promise; });
let second: Promise<void> | undefined;
try {
await entered.promise;
second = withManagedAiProbe(db, input, async () => { secondEntered(); });
await vi.waitFor(() => expect(secondEntered).toHaveBeenCalledOnce(), { timeout: 1000 });
await second;
} finally { release.resolve(); await Promise.allSettled([first, second]); }
});
it("prepares a run without waiting for another runtime's active cleanup", async () => {
const owner = "concurrent-active-cleanup";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = JSON.stringify({ tokens: { account_id: owner, access_token: "valid-access", refresh_token: "valid-refresh" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth);
const [row] = await service.quotaAccounts(companyId, owner);
const scope = { companyId, accountKey: row.grant.id }, entered = deferredSignal(), release = deferredSignal();
retainAiCredentialCleanup({ scope, retired: Promise.resolve(), cleanup: async () => { entered.resolve(); await release.promise; } });
await entered.promise;
let runtime: Awaited<ReturnType<typeof prepareManagedAiRuntime>> | undefined;
const preparing = prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} }).then(value => { runtime = value; });
try {
await vi.waitFor(() => expect(runtime).toBeDefined(), { timeout: 1000 });
expect(await readFile(path.join(String(runtime!.config.env.CODEX_HOME), "auth.json"), "utf8")).toBe(auth);
} finally { release.resolve(); await preparing; await reconcileRetainedAiCleanup(scope); await runtime?.cleanup(); }
});
it.each(["timeout", "unexpected"] as const)("does not acquire a company credential lock for an ordinary probe (%s)", async (kind) => {
const owner = `manual-probe-lock-${kind}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = JSON.stringify({ tokens: { account_id: owner, access_token: "valid-access", refresh_token: "valid-refresh" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth);
const [row] = await service.quotaAccounts(companyId, owner);
const probe = vi.fn().mockResolvedValue("ok");
const input = { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: {} };
const failure = Object.assign(new Error("injected private lock diagnostic"), { code: kind === "timeout" ? WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE : "EACCES" });
const lock = vi.spyOn(codexAdapter, "withAccountHomeSecretMutationLock").mockRejectedValueOnce(failure);
try {
expect(await withManagedAiProbe(db, input, probe)).toBe("ok");
expect(lock).not.toHaveBeenCalled();
expect(probe).toHaveBeenCalledOnce();
expect((await db.select().from(connectionGrants).where(eq(connectionGrants.id, row.grant.id)))[0].status).toBe("active");
} finally { lock.mockRestore(); }
expect(await withManagedAiProbe(db, input, probe)).toBe("ok");
expect(probe).toHaveBeenCalledTimes(2);
expect(await service.credential(row)).toBe(auth);
});
it("reads OpenAI credentials independently of another runtime cleanup or mutation lock", async () => {
const owner = "retained-openai-read-fence";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = JSON.stringify({ tokens: { account_id: owner, access_token: "valid-access", refresh_token: "valid-refresh" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth);
const [row] = await service.quotaAccounts(companyId, owner);
const scope = { companyId, accountKey: row.grant.id }, retired = deferredSignal(), cleanup = vi.fn(async () => {});
retainAiCredentialCleanup({ scope, retired: retired.promise, cleanup });
try {
await expect(service.runtimeCredential(row)).resolves.toBe(auth);
await codexAdapter.withAccountHomeSecretMutationLock(undefined, companyId, async () => {
await expect(service.runtimeCredential(row)).resolves.toBe(auth);
});
expect(cleanup).not.toHaveBeenCalled();
retired.resolve(); await reconcileRetainedAiCleanup(scope);
expect(cleanup).toHaveBeenCalledOnce();
await expect(service.runtimeCredential(row)).resolves.toBe(auth);
} finally { retired.resolve(); await reconcileRetainedAiCleanup(scope).catch(() => {}); }
});
it("allows the same account to run while retired Codex probe cleanup is pending or fails", async () => {
const owner = "retired-probe-retry", otherOwner = "retired-probe-other";
const auth = (user: string, hour: string) => JSON.stringify({ tokens: { account_id: user, id_token: "identity", access_token: `access-${hour}`, refresh_token: `refresh-${hour}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
for (const user of [owner, otherOwner]) {
await db.insert(companyMemberships).values({ companyId, principalId: user, principalType: "user", status: "active", membershipRole: "member" });
await service.save(companyId, user, { provider: "openai", method: "subscription", ownership: "personal", name: user, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth(user, "10"));
}
const [row] = await service.quotaAccounts(companyId, owner);
const input = { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: {} };
const stopped = deferredSignal();
const provider = vi.fn(async runtime => readFile(path.join(String(runtime.config.env.CODEX_HOME), "auth.json"), "utf8"));
const merge = vi.spyOn(codexAdapter, "decideCodexAuthMerge").mockRejectedValue(new Error("fixture cleanup storage unavailable"));
let retained: Awaited<ReturnType<typeof prepareManagedAiRuntime>> | undefined;
try {
await expect(withManagedAiProbe(db, input, async runtime => {
retained = runtime;
await writeFile(path.join(String(runtime.config.env.CODEX_HOME), "auth.json"), auth(owner, "11"));
throw new runnerProbe.NativeEnvironmentProbeCleanupError(stopped.promise, new Error("fixture probe interruption"), []);
})).rejects.toMatchObject({ details: { code: "native_probe_cleanup_pending" } });
await expect(withManagedAiProbe(db, input, provider)).resolves.toBe(auth(owner, "10"));
expect(provider).toHaveBeenCalledOnce(); expect(merge).not.toHaveBeenCalled();
stopped.resolve();
await vi.waitFor(() => expect(merge).toHaveBeenCalledOnce());
await expect(withManagedAiProbe(db, input, provider)).resolves.toBe(auth(owner, "10"));
await vi.waitFor(() => expect(merge).toHaveBeenCalledTimes(2));
await expect(withManagedAiProbe(db, { ...input, responsibleUserId: otherOwner }, provider)).resolves.toBe(auth(otherOwner, "10"));
expect(await readFile(path.join(String(retained!.config.env.CODEX_HOME), "auth.json"), "utf8")).toBe(auth(owner, "11"));
merge.mockRestore();
await reconcileRetainedAiCleanup({ companyId, accountKey: row.grant.id });
await expect(withManagedAiProbe(db, input, provider)).resolves.toBe(auth(owner, "11"));
expect(await service.credential(row)).toBe(auth(owner, "11"));
await expect(access(retained!.home!)).rejects.toMatchObject({ code: "ENOENT" });
} finally { stopped.resolve(); merge.mockRestore(); await retained?.cleanup().catch(() => {}); }
});
it("allows quota refresh alongside a manual probe and keeps the freshest saved credential", async () => {
const owner = "manual-probe-race";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = (hour: string) => JSON.stringify({ tokens: { account_id: owner, id_token: "identity", access_token: `access-${hour}`, refresh_token: `refresh-${hour}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth("10"));
const [row] = await service.quotaAccounts(companyId, owner);
const entered = deferredSignal(), release = deferredSignal();
const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json({ access_token: "quota-access", refresh_token: "quota-refresh" }));
const probing = withManagedAiProbe(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} }, async runtime => {
entered.resolve(); await release.promise;
await writeFile(path.join(String(runtime.config.env.CODEX_HOME), "auth.json"), auth("11"));
});
let refreshing: Promise<string> | undefined;
let lock: ReturnType<typeof vi.spyOn> | undefined;
try {
await entered.promise;
lock = vi.spyOn(codexAdapter, "withAccountHomeSecretMutationLock");
// Start outside the probe's async scope; it is a separate HTTP request.
refreshing = service.refreshQuotaCredential(row, auth("10"), new AbortController().signal);
const refreshed = await refreshing;
expect(request).toHaveBeenCalledOnce();
expect(JSON.parse(refreshed)).toMatchObject({ tokens: { access_token: "quota-access", refresh_token: "quota-refresh" } });
release.resolve(); await probing;
expect(await service.credential(row)).toBe(refreshed);
expect(request).toHaveBeenCalledOnce();
} finally { release.resolve(); await Promise.allSettled([probing, refreshing]); lock?.mockRestore(); request.mockRestore(); }
});
it("persists native Codex setup's refreshed selected login without changing existing agents", async () => {
const owner = "native-codex-setup-refresh";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = (hour: string) => JSON.stringify({ tokens: { account_id: owner, id_token: "identity", access_token: `access-${hour}`, refresh_token: `refresh-${hour}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth("10"));
const [row] = await service.quotaAccounts(companyId, owner);
const [before] = await db.select({ adapterType: agents.adapterType, adapterConfig: agents.adapterConfig }).from(agents).where(eq(agents.id, agentId));
const runnerBinary = path.join(home, "fixture-runnerd");
const resolveBinary = vi.spyOn(nativeCodexRunner, "resolvePaperclipRunnerBinary").mockReturnValue(runnerBinary);
const probe = vi.spyOn(runnerProbe, "probeNativeRunnerEnvironment").mockImplementation(async input => {
expect(input.transportOptions?.runnerBinary).toBe(runnerBinary);
expect(input.transportOptions?.sourceCodexHome).toContain("/provider");
expect(await readFile(path.join(input.transportOptions!.sourceCodexHome!, "auth.json"), "utf8")).toBe(auth("10"));
const providerHome = path.join(input.runtimeDirectory, "codex-home");
await fs.mkdir(providerHome, { mode: 0o700 });
const refreshedFile = path.join(providerHome, "auth.json");
await writeFile(refreshedFile, auth("11"), { mode: 0o600 });
await input.onCodexCredentialRefresh!(refreshedFile);
await input.onCleanupConfirmed!();
return { provider: "codex", providerDriver: "codex_app_server", effectiveModel: "gpt-5.6-sol", helloProbePassed: true };
});
let managedHome: string | undefined;
try {
const result = await withManagedAiProbe(db, { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: { provider: "codex" } }, async runtime => {
managedHome = runtime.home;
return testNativeRunnerAuthentication({ companyId, adapterType: "paperclip_runner", config: runtime.config, managedAiCredentialHome: path.join(runtime.home!, "provider") }, "codex", "gpt-5.6-sol");
});
expect(result.status, JSON.stringify(result)).toBe("pass"); expect(probe).toHaveBeenCalledOnce();
expect(await service.credential(row)).toBe(auth("11"));
await expect(access(managedHome!)).rejects.toMatchObject({ code: "ENOENT" });
const [after] = await db.select({ adapterType: agents.adapterType, adapterConfig: agents.adapterConfig }).from(agents).where(eq(agents.id, agentId));
expect(after).toEqual(before);
} finally { probe.mockRestore(); resolveBinary.mockRestore(); }
});
it("preserves the original Codex setup cleanup while another probe can run", async () => {
const owner = "native-codex-setup-retirement-retry";
const auth = (hour: string) => JSON.stringify({ tokens: { account_id: owner, id_token: "identity", access_token: `access-${hour}`, refresh_token: `refresh-${hour}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth("10"));
const [row] = await service.quotaAccounts(companyId, owner);
const input = { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { provider: "codex" } };
let rejectRetirement!: (error: Error) => void, physical = false;
const retired = new Promise<void>((_resolve, reject) => { rejectRetirement = reject; });
void retired.catch(() => undefined);
let original: Parameters<typeof runnerProbe.probeNativeRunnerEnvironment>[0], retainedHome: string | undefined;
const directories: string[] = [];
const retry = vi.fn(async () => {
if (!physical) throw new Error("fixture original owner remains live");
await original.onCodexCredentialRefresh!(path.join(original.runtimeDirectory, "codex-home", "auth.json"));
await original.onCleanupConfirmed!();
});
const binary = vi.spyOn(nativeCodexRunner, "resolvePaperclipRunnerBinary").mockReturnValue(path.join(home, "fixture-runnerd"));
const probe = vi.spyOn(runnerProbe, "probeNativeRunnerEnvironment").mockImplementationOnce(async options => {
original = options; directories.push(options.runtimeDirectory);
await fs.mkdir(path.join(options.runtimeDirectory, "codex-home"), { mode: 0o700 });
await writeFile(path.join(options.runtimeDirectory, "codex-home", "auth.json"), auth("11"), { mode: 0o600 });
throw new runnerProbe.NativeEnvironmentProbeCleanupError(retired, new Error("fixture interrupted probe"), [], retry);
}).mockImplementation(async options => {
directories.push(options.runtimeDirectory);
expect(await readFile(path.join(options.transportOptions!.sourceCodexHome!, "auth.json"), "utf8")).toBe(auth(physical ? "11" : "10"));
await options.onCleanupConfirmed!();
return { provider: "codex", providerDriver: "codex_app_server", effectiveModel: "gpt-5.6-sol", helloProbePassed: true };
});
const invoke = () => withManagedAiProbe(db, input, runtime => {
retainedHome ??= runtime.home;
return testNativeRunnerAuthentication({ companyId, adapterType: "paperclip_runner", config: runtime.config,
managedAiCredentialHome: path.join(runtime.home!, "provider") }, "codex", "gpt-5.6-sol");
});
try {
await expect(invoke()).rejects.toMatchObject({ details: { code: "native_probe_cleanup_pending" } });
await expect(access(retainedHome!)).resolves.toBeUndefined();
rejectRetirement(new Error("fixture initial retirement proof failed"));
await Promise.resolve();
expect((await invoke()).status).toBe("pass");
expect(probe).toHaveBeenCalledTimes(2); expect(retry).toHaveBeenCalledOnce();
await expect(access(retainedHome!)).resolves.toBeUndefined();
physical = true;
await reconcileRetainedAiCleanup({ companyId, accountKey: row.grant.id });
expect((await invoke()).status).toBe("pass");
expect(retry).toHaveBeenCalledTimes(2); expect(probe).toHaveBeenCalledTimes(3);
expect(await service.credential(row)).toBe(auth("11"));
await expect(access(retainedHome!)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
physical = true; rejectRetirement(new Error("fixture teardown"));
await reconcileRetainedAiCleanup({ companyId, accountKey: row.grant.id }).catch(() => {});
probe.mockRestore(); binary.mockRestore();
await Promise.all(directories.map(directory => rm(directory, { recursive: true, force: true })));
}
});
it("rejects preparation when reconnect replaces the selected credential reference", async () => {
const owner = "replaced-ref-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = (id: string) => JSON.stringify({ tokens: { account_id: id, access_token: id, refresh_token: id } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth("old-account"));
const [row] = await service.quotaAccounts(companyId, owner);
const definition = await secretService(db).createUserSecretDefinition(companyId, { key: "replacement_credential", name: "Replacement", provider: "local_encrypted" }, { userId: owner });
const replacement = await secretService(db).createCurrentUserSecretValue(companyId, owner, { definitionId: definition.id, value: auth("new-account") }, { userId: owner });
const prepare = () => prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} });
const original = localEncryptedProvider.resolveVersion;
const lock = vi.spyOn(localEncryptedProvider, "resolveVersion").mockImplementationOnce(async options => {
await db.update(connectionGrants).set({ credentialSecretRefs: row.grant.credentialSecretRefs.map(ref => ref.configPath === "ai.credential" ? { ...ref, secretId: replacement.id } : ref) }).where(eq(connectionGrants.id, row.grant.id));
await syncConnectionCredentialBindings(db, row.connection);
return original(options);
});
try { await expect(prepare()).rejects.toThrow("AI credential changed during preparation"); }
finally { lock.mockRestore(); }
const runtime = await prepare();
try {
expect(runtime.sessionIdentity).toContain(replacement.id);
expect(await readFile(path.join(String(runtime.config.env.CODEX_HOME), "auth.json"), "utf8")).toBe(auth("new-account"));
} finally { await runtime.cleanup(); }
});
it("caches quota after a real secret read but invalidates credential rotation and revocation", async () => {
const owner = "quota-cache-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const account = await service.save(companyId, owner, { provider: "anthropic", method: "subscription", ownership: "personal", name: "Quota cache account", loginSessionId: "fixture", agentIds: [], allAgents: true }, "quota-cache-original");
const [grant] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, account.grantId));
const secretId = grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!.secretId;
const [before] = await db.select().from(companySecrets).where(eq(companySecrets.id, secretId));
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(JSON.stringify({ five_hour: { utilization: 42 } })));
try {
const first = await fetchCompanyQuotaWindows(db, companyId, owner);
const [after] = await db.select().from(companySecrets).where(eq(companySecrets.id, secretId));
expect(after.lastResolvedAt).not.toBeNull();
expect(after.updatedAt.getTime()).toBeGreaterThan(before.updatedAt.getTime());
expect(first[0].ok).toBe(true);
expect(await fetchCompanyQuotaWindows(db, companyId, owner)).toEqual(first);
expect(fetchSpy).toHaveBeenCalledTimes(1);
await secretService(db).rotate(secretId, { value: "quota-cache-rotated" });
const rotated = await fetchCompanyQuotaWindows(db, companyId, owner);
expect(rotated[0].ok).toBe(true);
expect(rotated[0].accountKey).not.toBe(first[0].accountKey);
expect(fetchSpy).toHaveBeenCalledTimes(2);
expect(fetchSpy).toHaveBeenLastCalledWith(expect.any(String), expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer quota-cache-rotated" }) }));
await db.update(userSecretDefinitions).set({ status: "disabled" }).where(eq(userSecretDefinitions.id, before.userSecretDefinitionId!));
const disabled = await fetchCompanyQuotaWindows(db, companyId, owner);
expect(disabled[0]).toMatchObject({ ok: false, errorFamily: "credentials_unavailable", windows: [] });
expect(disabled[0].accountKey).not.toBe(rotated[0].accountKey);
expect(fetchSpy).toHaveBeenCalledTimes(2);
await db.update(userSecretDefinitions).set({ status: "active" }).where(eq(userSecretDefinitions.id, before.userSecretDefinitionId!));
expect((await fetchCompanyQuotaWindows(db, companyId, owner))[0].ok).toBe(true);
await db.update(companySecretVersions).set({ status: "disabled", revokedAt: new Date() }).where(and(eq(companySecretVersions.secretId, secretId), eq(companySecretVersions.version, 2)));
const revoked = await fetchCompanyQuotaWindows(db, companyId, owner);
expect(revoked[0].ok).toBe(false);
expect(revoked[0].windows).toEqual([]);
expect(fetchSpy).toHaveBeenCalledTimes(2);
} finally { fetchSpy.mockRestore(); }
});
it("caches refreshed quota under the saved grant and secret revision", async () => {
const owner = "quota-refreshed-cache-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired-cache", refresh_token: "single-use", account_id: "cache-account" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: "Refreshed cache", loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const request = vi.spyOn(globalThis, "fetch").mockImplementation(async (url, options) => {
if (String(url).endsWith("/oauth/token")) return Response.json({ access_token: "fresh-cache", refresh_token: "replacement" });
if (new Headers(options?.headers).get("authorization") === "Bearer expired-cache") return new Response("Expired", { status: 401 });
return Response.json({ rate_limit: { primary_window: { used_percent: 42 } } });
});
try {
const first = await fetchCompanyQuotaWindows(db, companyId, owner);
expect(first[0]).toMatchObject({ ok: true, windows: [{ usedPercent: 42 }] });
expect(await fetchCompanyQuotaWindows(db, companyId, owner)).toEqual(first);
expect(request).toHaveBeenCalledTimes(3); // rejected quota, OAuth, refreshed quota
} finally { request.mockRestore(); }
});
it.each(["rotation", "secret_metadata", "activity", "commit"] as const)("fails truthfully after %s persistence failure and permits sign-in recovery without a journal", async (failure) => {
const owner = `quota-rollback-${failure}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: `expired-${failure}`, refresh_token: "single-use", account_id: owner } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const table = failure === "secret_metadata" ? "company_secrets" : failure === "activity" ? "activity_log" : "company_secret_versions";
const condition = failure === "secret_metadata" ? `new.id = '${ref.secretId}'::uuid and new.latest_version > old.latest_version` : failure === "activity" ? `new.action = 'ai_connection.credential_refreshed'` : `new.secret_id = '${ref.secretId}'::uuid`;
await db.execute(sql.raw(`create function quota_save_failure() returns trigger language plpgsql as $$ begin
if ${condition} then raise exception 'injected quota save failure'; end if; return new; end $$;
create ${failure === "commit" ? "constraint " : ""}trigger quota_save_failure ${failure === "commit" ? "after" : "before"} ${failure === "secret_metadata" ? "update" : "insert"} on ${table}
${failure === "commit" ? "deferrable initially deferred" : ""} for each row execute function quota_save_failure();`));
const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json({ access_token: `fresh-${failure}`, refresh_token: `replacement-${failure}` }));
const file = quotaCredentialRecoveryPath(companyId, row.grant.id);
let triggerDropped = false;
try {
await expect(service.refreshQuotaCredential(row, original, new AbortController().signal)).rejects.toThrow();
expect(request).toHaveBeenCalledTimes(1);
expect(await service.credential(row)).toBe(original);
await expect(access(file)).rejects.toMatchObject({ code: "ENOENT" });
await db.execute(sql.raw(`drop trigger quota_save_failure on ${table}; drop function quota_save_failure();`));
triggerDropped = true;
// A new receiver has no replacement to recover. A failed use routes to the
// existing sign-in flow, rather than claiming the failed save succeeded.
const receiver = aiConnectionService(db);
const generation = createHash("sha256").update(original).digest("hex").slice(0, 16);
await receiver.markAuthenticationFailed({ companyId, runStartedAt: new Date(), attribution: {
provider: "openai", method: "subscription", connectionId: row.connection.id, grantId: row.grant.id,
mode: "responsible_user", responsibleUserId: owner, identity: `${row.grant.id}:${owner}:${generation}`,
} });
expect((await db.select().from(connectionGrants).where(eq(connectionGrants.id, row.grant.id)))[0].status).toBe("needs_reauthorization");
const signedIn = JSON.stringify({ tokens: { access_token: "signed-in", refresh_token: "new-login" } });
await receiver.save(companyId, owner, { connectionId: row.connection.id, provider: "openai", method: "subscription",
ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, signedIn);
const [current] = await receiver.quotaAccounts(companyId, owner);
expect(current.grant.status).toBe("active");
expect(await receiver.runtimeCredential(current)).toBe(signedIn);
expect(request).toHaveBeenCalledTimes(1);
expect((await db.select().from(companySecretVersions).where(eq(companySecretVersions.secretId, ref.secretId))).map(version => version.version).sort()).toEqual([1, 2]);
} finally {
request.mockRestore();
if (!triggerDropped) await db.execute(sql.raw(`drop trigger quota_save_failure on ${table}; drop function quota_save_failure();`));
}
});
it.each(["company_file", "grant_row"] as const)("reads runtime credentials without an execution lock while a %s writer is active", async (kind) => {
const owner = `runtime-lock-${kind}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = JSON.stringify({ tokens: { access_token: "valid-access", refresh_token: "valid-refresh", account_id: owner } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth);
const [row] = await service.quotaAccounts(companyId, owner);
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const prepare = () => prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} });
const held = deferredSignal(), release = deferredSignal();
const lock = kind === "company_file" ? vi.spyOn(codexAdapter, "withAccountHomeSecretMutationLock")
.mockRejectedValueOnce(Object.assign(new Error("company lock held by another account"), { code: WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE })) : undefined;
const holding = kind === "company_file" ? Promise.resolve() : db.transaction(async tx => {
if (kind === "grant_row") await tx.select().from(connectionGrants).where(eq(connectionGrants.id, row.grant.id)).for("update");
else await tx.select().from(companySecrets).where(eq(companySecrets.id, ref.secretId)).for("update");
held.resolve(); await release.promise;
});
try {
if (kind !== "company_file") await held.promise;
expect(await service.runtimeCredential(row)).toBe(auth);
expect(lock?.mock.calls ?? []).toHaveLength(0);
expect((await db.select().from(connectionGrants).where(eq(connectionGrants.id, row.grant.id)))[0].status).toBe("active");
} finally { release.resolve(); await holding; lock?.mockRestore(); }
const run = await prepare();
try { expect(await readFile(path.join(String(run.config.env.CODEX_HOME), "auth.json"), "utf8")).toBe(auth); }
finally { await run.cleanup(); }
});
it.each([
["runtime", "rotation"], ["runtime", "commit"], ["failed_run", "rotation"], ["failed_run", "commit"],
] as const)("recovers pending quota credentials before %s can reuse or invalidate the consumed token after %s failure", async (consumer, failure) => {
const owner = `quota-consumer-${consumer}-${failure}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = (marker: string) => JSON.stringify({ tokens: { access_token: `${marker}-access`, refresh_token: `${marker}-refresh`, account_id: owner } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth("old"));
const [row] = await service.quotaAccounts(companyId, owner);
const prepare = () => prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local",
binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} });
const oldRun = await prepare();
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const journal = quotaCredentialRecovery(companyId, row.grant.id);
await journal.write({ companyId, grantId: row.grant.id, connectionId: row.connection.id, secretId: ref.secretId,
baseHash: quotaCredentialHash(auth("old")), value: auth("new") });
const fail = () => service.markAuthenticationFailed({ companyId, agentId, runStartedAt: new Date(),
attribution: { ...oldRun.attribution, identity: oldRun.identity } });
await db.execute(sql.raw(`create function reject_consumer_recovery() returns trigger language plpgsql as $$ begin
if new.secret_id = '${ref.secretId}'::uuid then raise exception 'recovery unavailable'; end if; return new; end $$;
create ${failure === "commit" ? "constraint " : ""}trigger reject_consumer_recovery ${failure === "commit" ? "after" : "before"} insert on company_secret_versions
${failure === "commit" ? "deferrable initially deferred" : ""} for each row execute function reject_consumer_recovery();`));
try {
if (consumer === "runtime") {
const error = await prepare().then(() => { throw new Error("Runtime must defer"); }, error => error);
expect(isAiConnectionBusy(error)).toBe(true);
expect(error).toMatchObject({ status: 422, details: { code: "ai_connection_busy" } });
} else await expect(fail()).rejects.toThrow();
expect((await db.select().from(connectionGrants).where(eq(connectionGrants.id, row.grant.id)))[0].status).toBe("active");
expect(await service.credential(row)).toBe(auth("old"));
expect(await journal.read()).not.toBeNull();
} finally {
await db.execute(sql`drop trigger reject_consumer_recovery on company_secret_versions; drop function reject_consumer_recovery()`);
}
const request = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("Recovery must not exchange again"));
let nextRun: Awaited<ReturnType<typeof prepare>> | undefined;
try {
if (consumer === "failed_run") await fail();
nextRun = await prepare();
expect(await readFile(path.join(String(nextRun.config.env.CODEX_HOME), "auth.json"), "utf8")).toBe(auth("new"));
expect(nextRun.sessionIdentity).toBe(oldRun.sessionIdentity);
await fail(); // A late failure of the old run cannot invalidate the recovered identity.
expect((await db.select().from(connectionGrants).where(eq(connectionGrants.id, row.grant.id)))[0].status).toBe("active");
expect(await service.credential(row)).toBe(auth("new"));
expect(await journal.read()).toBeNull();
expect(request).not.toHaveBeenCalled();
expect(await db.select().from(companySecretVersions).where(eq(companySecretVersions.secretId, ref.secretId))).toHaveLength(2);
} finally { request.mockRestore(); await oldRun.cleanup(); await nextRun?.cleanup(); await journal.clear(); }
});
it("requires sign-in after the receiver process dies with an exchanged but unsaved token", async () => {
const owner = "quota-receiver-process-loss";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired-process", refresh_token: "single-use-process" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const held = deferredSignal(), release = deferredSignal();
const lockKey = 7192031;
const holder = db.transaction(async tx => {
await tx.execute(sql`select pg_advisory_xact_lock(${lockKey})`);
held.resolve(); await release.promise;
});
await held.promise;
await db.execute(sql.raw(`create function hold_receiver_save() returns trigger language plpgsql as $$ begin
if new.secret_id = '${ref.secretId}'::uuid then perform pg_advisory_xact_lock(${lockKey}); end if; return new; end $$;
create trigger hold_receiver_save before insert on company_secret_versions for each row execute function hold_receiver_save();`));
let exchanges = 0;
const provider = createServer((req, res) => {
exchanges++; req.resume();
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ access_token: "unsaved-process", refresh_token: "replacement-process" }));
});
provider.listen(0, "127.0.0.1"); await once(provider, "listening");
const address = provider.address() as { port: number };
const child = spawn(process.execPath, ["--import", createRequire(import.meta.url).resolve("tsx"), new URL("./helpers/quota-credential-process.ts", import.meta.url).pathname], {
env: { ...process.env, CREDENTIAL_TEST_DATABASE_URL: database.connectionString,
CREDENTIAL_TEST_PROVIDER_URL: `http://127.0.0.1:${address.port}`, CREDENTIAL_TEST_COMPANY_ID: companyId, CREDENTIAL_TEST_OWNER: owner },
stdio: "ignore",
});
const exited = once(child, "exit");
try {
let blocked = false;
const deadline = Date.now() + 25_000;
while (!blocked && Date.now() < deadline && child.exitCode === null) {
const rows = await db.execute(sql`select pid from pg_stat_activity where datname = current_database()
and wait_event_type = 'Lock' and wait_event = 'advisory'`);
blocked = exchanges === 1 && rows.length > 0;
if (!blocked) await new Promise(resolve => setTimeout(resolve, 25));
}
expect(blocked).toBe(true);
child.kill("SIGKILL"); await exited;
} finally {
if (child.exitCode === null && child.signalCode === null) { child.kill("SIGKILL"); await exited; }
release.resolve(); await holder;
await db.execute(sql`drop trigger hold_receiver_save on company_secret_versions; drop function hold_receiver_save()`);
await new Promise<void>(resolve => provider.close(() => resolve()));
}
expect(exchanges).toBe(1);
expect(await service.credential(row)).toBe(original);
await expect(access(quotaCredentialRecoveryPath(companyId, row.grant.id))).rejects.toMatchObject({ code: "ENOENT" });
const receiver = aiConnectionService(db);
const generation = createHash("sha256").update(original).digest("hex").slice(0, 16);
await receiver.markAuthenticationFailed({ companyId, runStartedAt: new Date(), attribution: {
provider: "openai", method: "subscription", connectionId: row.connection.id, grantId: row.grant.id,
mode: "responsible_user", responsibleUserId: owner, identity: `${row.grant.id}:${owner}:${generation}`,
} });
expect((await db.select().from(connectionGrants).where(eq(connectionGrants.id, row.grant.id)))[0].status).toBe("needs_reauthorization");
const signedIn = JSON.stringify({ tokens: { access_token: "process-sign-in", refresh_token: "process-new-login" } });
await receiver.save(companyId, owner, { connectionId: row.connection.id, provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, signedIn);
const [current] = await receiver.quotaAccounts(companyId, owner);
expect(await receiver.runtimeCredential(current)).toBe(signedIn);
}, 40_000);
it("does not exchange a token when a required database transaction fails", async () => {
const owner = "quota-db-prerequisite";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired", refresh_token: "single-use" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const request = vi.spyOn(globalThis, "fetch");
const unavailableUrl = new URL(database.connectionString);
unavailableUrl.port = "1";
const unavailable = createDb(unavailableUrl.toString());
try {
await expect(aiConnectionService(unavailable).refreshQuotaCredential(row, original, new AbortController().signal)).rejects.toThrow();
expect(request).not.toHaveBeenCalled();
await expect(access(quotaCredentialRecoveryPath(companyId, row.grant.id))).rejects.toMatchObject({ code: "ENOENT" });
} finally { await unavailable.$client.end(); request.mockRestore(); }
});
it.each(["reconnect", "same_value_new_epoch", "revoked"] as const)("lets %s win after a returned token's save rolls back", async change => {
const owner = `quota-delayed-save-${change}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired", refresh_token: "single-use" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const transaction = db.transaction.bind(db);
const interrupted = vi.spyOn(db, "transaction").mockImplementationOnce(async (work, config) => {
await transaction(async tx => { await work(tx); throw new Error("fixture rollback"); }, config).catch(() => {});
if (change === "revoked") await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, row.grant.id));
else await secretService(db).rotate(ref.secretId, { value: change === "reconnect" ? "new-login" : original });
throw new Error("fixture lost response");
});
const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json({ access_token: "late", refresh_token: "late-refresh" }));
try {
const refresh = service.refreshQuotaCredential(row, original, new AbortController().signal);
if (change === "revoked") await expect(refresh).rejects.toThrow("credentials_unavailable");
else expect(await refresh).toBe(change === "reconnect" ? "new-login" : original);
expect(await service.credential(row)).toBe(change === "reconnect" ? "new-login" : original);
expect(request).toHaveBeenCalledTimes(1);
await expect(access(quotaCredentialRecoveryPath(companyId, row.grant.id))).rejects.toMatchObject({ code: "ENOENT" });
} finally { interrupted.mockRestore(); request.mockRestore(); }
});
it.each(["rollback", "lost_commit_reply"] as const)("automatically saves one exchanged token across %s", async (failure) => {
const owner = `quota-once-${failure}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired", refresh_token: "single-use", account_id: owner } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const transaction = db.transaction.bind(db);
const interrupted = vi.spyOn(db, "transaction").mockImplementationOnce(async (work, config) => {
await transaction(async (tx) => {
await work(tx);
if (failure === "rollback") throw new Error("Injected rollback after writes");
}, config);
throw new Error("Injected lost commit response");
});
const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json({ access_token: "fresh-once", refresh_token: "replacement-once" }));
try {
await service.refreshQuotaCredential(row, original, new AbortController().signal);
expect(JSON.parse(await service.credential(row))).toMatchObject({ tokens: { access_token: "fresh-once", refresh_token: "replacement-once" } });
expect(request).toHaveBeenCalledTimes(1);
await expect(access(quotaCredentialRecoveryPath(companyId, row.grant.id))).rejects.toThrow();
} finally { interrupted.mockRestore(); request.mockRestore(); }
});
it("recovers the current credential's pending replacement before answering a delayed older poll", async () => {
const owner = "delayed-quota-poll";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "A", refresh_token: "refresh-A", account_id: owner } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const journal = quotaCredentialRecovery(companyId, row.grant.id);
const request = vi.spyOn(globalThis, "fetch")
.mockResolvedValueOnce(Response.json({ access_token: "B", refresh_token: "refresh-B" }))
.mockResolvedValueOnce(Response.json({ access_token: "C", refresh_token: "refresh-C" }));
let triggerInstalled = false;
try {
const current = await service.refreshQuotaCredential(row, original, new AbortController().signal);
await db.execute(sql.raw(`create function delayed_quota_commit_failure() returns trigger language plpgsql as $$ begin
if new.secret_id = '${ref.secretId}'::uuid then raise exception 'injected delayed quota commit failure'; end if; return new; end $$;
create constraint trigger delayed_quota_commit_failure after insert on company_secret_versions deferrable initially deferred
for each row execute function delayed_quota_commit_failure();`));
triggerInstalled = true;
await expect(service.refreshQuotaCredential(row, current, new AbortController().signal)).rejects.toThrow();
expect(await service.credential(row)).toBe(current);
expect(await journal.read()).toBeNull();
// Only observed records from the earlier release use the legacy reader.
await journal.write({ companyId, grantId: row.grant.id, connectionId: row.connection.id, secretId: ref.secretId,
baseHash: quotaCredentialHash(current), value: JSON.stringify({ tokens: { access_token: "C", refresh_token: "refresh-C" } }) });
await db.execute(sql`drop trigger delayed_quota_commit_failure on company_secret_versions; drop function delayed_quota_commit_failure()`);
triggerInstalled = false;
const recovered = await service.refreshQuotaCredential(row, original, new AbortController().signal);
expect(JSON.parse(recovered).tokens).toMatchObject({ access_token: "C", refresh_token: "refresh-C" });
expect(await service.credential(row)).toBe(recovered);
expect(await journal.read()).toBeNull();
expect(request).toHaveBeenCalledTimes(2);
} finally {
if (triggerInstalled) await db.execute(sql`drop trigger delayed_quota_commit_failure on company_secret_versions; drop function delayed_quota_commit_failure()`);
request.mockRestore(); await journal.clear();
}
});
it.each(["reconnected", "expired_reconnect", "revoked"] as const)("does not apply a pending refresh over a %s identity", async (change) => {
const owner = `quota-replay-${change}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired", refresh_token: "single-use", account_id: owner } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const journal = quotaCredentialRecovery(companyId, row.grant.id);
await journal.write({ companyId, grantId: row.grant.id, connectionId: row.connection.id, secretId: ref.secretId, baseHash: quotaCredentialHash(original), value: "old-pending-replacement" });
const request = vi.spyOn(globalThis, "fetch");
try {
if (change === "reconnected") {
await secretService(db).rotate(ref.secretId, { value: "new-authorized-identity" });
expect(await service.refreshQuotaCredential(row, original, new AbortController().signal)).toBe("new-authorized-identity");
expect(await service.credential(row)).toBe("new-authorized-identity");
expect(await journal.read()).toBeNull();
} else if (change === "expired_reconnect") {
const reconnected = JSON.stringify({ tokens: { access_token: "expired-new-account", refresh_token: "new-account-refresh", account_id: owner } });
await secretService(db).rotate(ref.secretId, { value: reconnected });
request.mockResolvedValueOnce(Response.json({ access_token: "new-account-fresh", refresh_token: "new-account-replacement" }));
await service.refreshQuotaCredential(row, reconnected, new AbortController().signal);
expect(request).toHaveBeenCalledOnce();
expect(JSON.parse(String(request.mock.calls[0][1]?.body))).toMatchObject({ refresh_token: "new-account-refresh" });
expect(JSON.parse(await service.credential(row))).toMatchObject({ tokens: { access_token: "new-account-fresh", refresh_token: "new-account-replacement" } });
expect(await journal.read()).toBeNull();
} else {
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, row.grant.id));
await expect(service.refreshQuotaCredential(row, original, new AbortController().signal)).rejects.toThrow("credentials_unavailable");
expect(await journal.read()).not.toBeNull();
}
if (change !== "expired_reconnect") expect(request).not.toHaveBeenCalled();
} finally { request.mockRestore(); await journal.clear(); }
});
it("saves exchanged tokens when the quota deadline expires during response-body reading", async () => {
const owner = "quota-body-deadline-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired", refresh_token: "single-use", account_id: "deadline-account" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: "Slow token body", loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const reading = deferredSignal(), releaseBody = deferredSignal(), quotaDeadline = new AbortController();
let refreshSignal: AbortSignal | null | undefined;
const request = vi.spyOn(globalThis, "fetch").mockImplementation(async (_url, options) => {
refreshSignal = options?.signal;
return { ok: true, json: async () => {
reading.resolve(); await releaseBody.promise;
refreshSignal?.throwIfAborted();
return { access_token: "fresh-after-deadline", refresh_token: "replacement" };
} } as Response;
});
const saving = service.refreshQuotaCredential(row, original, quotaDeadline.signal);
try {
await reading.promise;
quotaDeadline.abort();
expect(refreshSignal?.aborted).toBe(false);
releaseBody.resolve();
await saving;
expect(JSON.parse(await service.credential(row))).toMatchObject({ tokens: { access_token: "fresh-after-deadline", refresh_token: "replacement" } });
await expect(service.refreshQuotaCredential(row, original, quotaDeadline.signal)).rejects.toThrow();
expect(request).toHaveBeenCalledTimes(1);
} finally { releaseBody.resolve(); await saving.catch(() => {}); request.mockRestore(); }
});
it("retries another account's token write-back while a slow quota exchange holds the company lock", async () => {
const auth = (account: string, marker: string) => JSON.stringify({ tokens: { account_id: account, id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: marker === "old" ? "2026-09-10T10:00:00Z" : "2026-09-10T11:00:00Z" });
const quotaOwner = "slow-quota-owner", runOwner = "slow-quota-other-run";
await db.insert(companyMemberships).values([quotaOwner, runOwner].map(principalId => ({ companyId, principalId, principalType: "user" as const, status: "active" as const, membershipRole: "member" as const })));
for (const owner of [quotaOwner, runOwner]) await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth(owner, "old"));
const [quotaRow] = await service.quotaAccounts(companyId, quotaOwner);
// Runtime credential reads now serialize with quota exchanges. Prepare this
// already-running, different account before testing its contended write-back.
const preparedRun = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: runOwner, adapterType: "codex_local", binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} });
const started = deferredSignal(), release = deferredSignal();
const request = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
started.resolve(); await release.promise;
return Response.json({ access_token: "quota-fresh", refresh_token: "quota-replacement" });
});
const refreshing = service.refreshQuotaCredential(quotaRow, auth(quotaOwner, "old"), new AbortController().signal);
let run: Awaited<ReturnType<typeof prepareManagedAiRuntime>> | undefined;
let cleaning: Promise<void> | undefined;
let lock: ReturnType<typeof vi.spyOn> | undefined;
try {
await started.promise;
run = preparedRun;
const authFile = path.join(String(run.config.env.CODEX_HOME), "auth.json");
await writeFile(authFile, auth(runOwner, "replacement"));
// Inject the two 30-second acquisition expirations, then use the real
// still-held lock and real Postgres write-back for the final attempt.
lock = vi.spyOn(codexAdapter, "withAccountHomeSecretMutationLock")
.mockRejectedValueOnce(Object.assign(new Error("lock timeout"), { code: WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE }))
.mockRejectedValueOnce(Object.assign(new Error("lock timeout"), { code: WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE }));
cleaning = run.cleanup();
await vi.waitFor(() => expect(lock).toHaveBeenCalledTimes(3));
expect(await readFile(authFile, "utf8")).toBe(auth(runOwner, "replacement"));
release.resolve();
await Promise.all([refreshing, cleaning]);
const [runRow] = await service.quotaAccounts(companyId, runOwner);
expect(await service.credential(runRow)).toBe(auth(runOwner, "replacement"));
expect(JSON.parse(await service.credential(quotaRow))).toMatchObject({ tokens: { access_token: "quota-fresh", refresh_token: "quota-replacement" } });
await expect(access(run.home!)).rejects.toMatchObject({ code: "ENOENT" });
} finally { release.resolve(); await Promise.allSettled([refreshing, cleaning]); lock?.mockRestore(); request.mockRestore(); }
});
it.each(["new", "reconnect"])("retains a completed %s sign-in across two lock waits behind a quota refresh", async kind => {
const owner = `slow-quota-login-${kind}`, loginOwner = kind === "reconnect" ? owner : "contended-login-owner";
await db.insert(companyMemberships).values([...new Set([owner, loginOwner])].map(principalId => ({ companyId, principalId, principalType: "user", status: "active", membershipRole: "member" })));
const original = JSON.stringify({ tokens: { account_id: owner, access_token: "old", refresh_token: "old-refresh" } });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [quotaRow] = await service.quotaAccounts(companyId, owner);
const [environment] = await db.insert(environments).values({ name: "Contended AI login", driver: "sandbox" }).returning();
const intent = { provider: "openai", method: "subscription", ownership: "personal", name: "Contended sign-in", agentIds: [], allAgents: true, ...(kind === "reconnect" ? { connectionId: quotaRow.connection.id } : {}) } as const;
const sessionId = randomUUID();
await db.insert(adapterAuthSessions).values({ id: sessionId, publicSessionId: randomUUID(), companyId, environmentId: environment.id, adapterType: "codex_local", startedByUserId: loginOwner, status: "promoting", aiConnection: { ...intent, agentIds: [] }, expiresAt: new Date(Date.now() + 600_000) });
const started = deferredSignal(), release = deferredSignal();
const request = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
started.resolve(); await release.promise;
return Response.json({ access_token: "quota-fresh", refresh_token: "quota-replacement" });
});
const refreshing = service.refreshQuotaCredential(quotaRow, original, new AbortController().signal);
let saving: ReturnType<typeof service.save> | undefined;
let lock: ReturnType<typeof vi.spyOn> | undefined;
const credential = JSON.stringify({ tokens: { account_id: "signed-in", access_token: "login-token", refresh_token: "login-refresh" } });
try {
await started.promise;
// Represent two expired 30-second acquisitions, then wait on the real
// held lock. The credential must survive until that final save commits.
lock = vi.spyOn(codexAdapter, "withAccountHomeSecretMutationLock")
.mockRejectedValueOnce(Object.assign(new Error("lock timeout"), { code: WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE }))
.mockRejectedValueOnce(Object.assign(new Error("lock timeout"), { code: WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE }));
saving = service.save(companyId, loginOwner, { ...intent, agentIds: [] }, credential, { id: sessionId });
void saving.catch(() => {});
await vi.waitFor(() => expect(lock).toHaveBeenCalledTimes(3));
expect((await db.select().from(adapterAuthSessions).where(eq(adapterAuthSessions.id, sessionId)))[0].connectionId).toBeNull();
release.resolve();
const [, saved] = await Promise.all([refreshing, saving]);
const [row] = (await service.quotaAccounts(companyId, loginOwner)).filter(row => row.connection.id === saved.connectionId);
expect(await service.credential(row)).toBe(credential);
expect(await service.save(companyId, loginOwner, { ...intent, agentIds: [] }, credential, { id: sessionId })).toEqual(saved);
if (kind === "new") expect(JSON.parse(await service.credential(quotaRow))).toMatchObject({ tokens: { access_token: "quota-fresh", refresh_token: "quota-replacement" } });
} finally {
release.resolve(); await Promise.allSettled([refreshing, saving]); lock?.mockRestore(); request.mockRestore();
await db.delete(adapterAuthSessions).where(eq(adapterAuthSessions.id, sessionId));
await db.delete(environments).where(eq(environments.id, environment.id));
}
});
it.each(["quota", "recovery", "runtime"])("allows an in-flight reconnect after automatic %s credential rotation", async kind => {
const owner = `reconnect-refresh-${kind}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = (label: string, hour: number) => JSON.stringify({ tokens: { account_id: owner, id_token: "identity", access_token: `access-${label}`, refresh_token: `refresh-${label}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
const intent = { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true } as const;
const account = await service.save(companyId, owner, { ...intent, agentIds: [] }, auth("old", 10));
const [row] = await service.quotaAccounts(companyId, owner);
const ref = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!;
const before = (await db.select().from(companySecrets).where(eq(companySecrets.id, ref.secretId)))[0];
const started = new Date();
if (kind === "quota") {
const request = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json({ access_token: "access-fresh", refresh_token: "refresh-fresh" }));
try { await service.refreshQuotaCredential(row, auth("old", 10), new AbortController().signal); }
finally { request.mockRestore(); }
} else if (kind === "recovery") {
const recovery = quotaCredentialRecovery(companyId, row.grant.id);
await recovery.write({ companyId, grantId: row.grant.id, connectionId: row.connection.id, secretId: ref.secretId, baseHash: quotaCredentialHash(auth("old", 10)), value: auth("fresh", 11) });
expect(await service.runtimeCredential(row)).toBe(auth("fresh", 11));
} else {
const runtime = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local", binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} });
await writeFile(path.join(String(runtime.config.env.CODEX_HOME), "auth.json"), auth("fresh", 11));
await runtime.cleanup();
}
const after = (await db.select().from(companySecrets).where(eq(companySecrets.id, ref.secretId)))[0];
expect(after.latestVersion).toBeGreaterThan(before.latestVersion);
expect(after.aiSessionEpoch).toBe(before.aiSessionEpoch);
const saved = await service.save(companyId, owner, { ...intent, agentIds: [], connectionId: account.connectionId }, auth("reconnected", 12), undefined, started);
expect(saved).toEqual(account);
expect(await service.credential(row)).toBe(auth("reconnected", 12));
await expect(service.save(companyId, owner, { ...intent, agentIds: [], connectionId: account.connectionId }, auth("older-login", 13), undefined, started)).rejects.toThrow("changed");
// A real later revocation still fences this same old reconnect attempt.
await db.update(connectionGrants).set({ status: "revoked", updatedAt: new Date() }).where(eq(connectionGrants.id, account.grantId));
await expect(service.save(companyId, owner, { ...intent, agentIds: [], connectionId: account.connectionId }, auth("stale", 13), undefined, started)).rejects.toThrow("changed");
});
it.each(["lock", "database"])("preserves replacement tokens after exhausted %s write-back and permits a later retry", async failure => {
const owner = `retained-writeback-${failure}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const auth = (hour: string) => JSON.stringify({ tokens: { account_id: owner, id_token: "identity", access_token: `access-${hour}`, refresh_token: `refresh-${hour}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: owner, loginSessionId: "fixture", agentIds: [], allAgents: true }, auth("10"));
const run = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local", binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} });
const authFile = path.join(String(run.config.env.CODEX_HOME), "auth.json");
await writeFile(authFile, auth("11"));
const injected = failure === "lock"
? vi.spyOn(codexAdapter, "withAccountHomeSecretMutationLock").mockRejectedValue(Object.assign(new Error("lock timeout"), { code: WORKSPACE_RESTORE_LOCK_TIMEOUT_CODE }))
: vi.spyOn(db, "transaction").mockRejectedValue(new Error("database unavailable"));
try {
await expect(run.cleanup()).rejects.toThrow(failure === "lock" ? "lock timeout" : "database unavailable");
expect(injected).toHaveBeenCalledTimes(failure === "lock" ? 3 : 1);
expect(await readFile(authFile, "utf8")).toBe(auth("11"));
} finally { injected.mockRestore(); }
await run.cleanup();
const [row] = await service.quotaAccounts(companyId, owner);
expect(await service.credential(row)).toBe(auth("11"));
await expect(access(run.home!)).rejects.toMatchObject({ code: "ENOENT" });
});
it("rereads committed quota rotation without taking an account execution mutex", async () => {
const owner = "quota-race-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "old-access", refresh_token: "single-use", id_token: "identity", account_id: "race-account" } });
const account = await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: "Racing refresh", loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const secretId = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!.secretId;
const exchangeStarted = deferredSignal();
const releaseExchange = deferredSignal();
const request = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
exchangeStarted.resolve(); await releaseExchange.promise;
return Response.json({ access_token: "new-access", refresh_token: "new-refresh" });
});
let runtime: Awaited<ReturnType<typeof prepareManagedAiRuntime>> | undefined;
let lock: ReturnType<typeof vi.spyOn> | undefined;
const refreshing = service.refreshQuotaCredential(row, original, new AbortController().signal);
try {
await exchangeStarted.promise;
lock = vi.spyOn(codexAdapter, "withAccountHomeSecretMutationLock");
const preparing = prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: owner, adapterType: "codex_local", binding: { provider: "openai", method: "subscription", mode: "responsible_user" }, config: {} });
let prepared = false;
void preparing.then(() => { prepared = true; });
await vi.waitFor(async () => {
const waiting = await db.execute(sql`select pid from pg_stat_activity
where datname = current_database() and wait_event_type = 'Lock' and query like '%company_secrets%'`);
expect(waiting.length).toBeGreaterThan(0);
});
expect(prepared).toBe(false);
expect(lock).not.toHaveBeenCalled();
const edit = secretService(db).update(secretId, { description: "Changed during refresh" });
releaseExchange.resolve();
const results = await Promise.all([refreshing, edit, preparing]);
runtime = results[2];
expect(JSON.parse(await readFile(path.join(String(runtime.config.env.CODEX_HOME), "auth.json"), "utf8"))).toMatchObject({ tokens: { access_token: "new-access", refresh_token: "new-refresh" } });
expect(JSON.parse(await service.credential(row))).toMatchObject({ tokens: { access_token: "new-access", refresh_token: "new-refresh" } });
expect((await secretService(db).getById(secretId))?.description).toBe("Changed during refresh");
} finally { releaseExchange.resolve(); await runtime?.cleanup(); request.mockRestore(); lock?.mockRestore(); }
}, 10_000);
it("serializes quota credential refresh, persists rotation, and defers while the provider is active", async () => {
const owner = "quota-refresh-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const original = JSON.stringify({ tokens: { access_token: "expired-access", refresh_token: "single-use", id_token: "identity", account_id: null } });
const account = await service.save(companyId, owner, { provider: "openai", method: "subscription", ownership: "personal", name: "Refresh account", loginSessionId: "fixture", agentIds: [], allAgents: true }, original);
const [row] = await service.quotaAccounts(companyId, owner);
const request = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(JSON.stringify({ access_token: "fresh-access", refresh_token: "rotated-once" })));
const signal = new AbortController().signal;
try {
// A pre-existing transaction may own a secret row before asking for the
// company file lock. Refresh must defer before exchanging its token.
await db.transaction(async tx => {
const secretId = row.grant.credentialSecretRefs.find(ref => ref.configPath === "ai.credential")!.secretId;
await tx.select().from(companySecrets).where(eq(companySecrets.id, secretId)).for("update");
await expect(service.refreshQuotaCredential(row, original, signal)).rejects.toThrow();
expect(request).not.toHaveBeenCalled();
});
const activeId = randomUUID();
await db.insert(heartbeatRuns).values({ id: activeId, companyId, agentId, invocationSource: "on_demand", status: "running", contextSnapshot: { aiConnection: { grantId: account.grantId } } });
await expect(service.refreshQuotaCredential(row, original, signal)).rejects.toThrow("provider_unavailable");
expect(request).not.toHaveBeenCalled();
await db.update(heartbeatRuns).set({ status: "succeeded" }).where(eq(heartbeatRuns.id, activeId));
const [first, second] = await Promise.all([
service.refreshQuotaCredential(row, original, signal),
service.refreshQuotaCredential(row, original, signal),
]);
expect(first).toBe(second);
expect(request).toHaveBeenCalledTimes(1);
expect(JSON.parse(await service.credential(row))).toMatchObject({ tokens: { access_token: "fresh-access", refresh_token: "rotated-once", account_id: null } });
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, account.grantId));
await expect(service.refreshQuotaCredential(row, first, signal)).rejects.toThrow("credentials_unavailable");
expect(request).toHaveBeenCalledTimes(1);
} finally { request.mockRestore(); }
});
it.each([
["openai", "codex_local", "responses"],
["anthropic", "claude_local", "messages"],
["openai", "opencode_local", "chat"],
] as const)("prepares a no-auth %s endpoint without a vault secret", async (provider, adapterType, protocol) => {
const account = await service.save(companyId, "alice", {
provider, method: "api_key", ownership: "personal", name: `No-auth ${adapterType}`,
agentIds: [], allAgents: true,
routing: { kind: "local", protocol, baseUrl: "http://127.0.0.1:9000/v1", auth: "none", models: [] },
}, "");
const binding = { provider, method: "api_key", mode: "connection", connectionId: account.connectionId, grantId: account.grantId } as const;
const config = { model: "fixture-model", env: { OPENAI_API_KEY: "host-key", ANTHROPIC_API_KEY: "host-key" } };
const first = await prepareManagedAiRuntime(db, { ...input, adapterType, responsibleUserId: "alice", binding, config });
const second = await prepareManagedAiRuntime(db, { ...input, adapterType, responsibleUserId: "alice", binding, config });
try {
expect(first.sessionIdentity).toBe(second.sessionIdentity);
expect(first.sessionIdentity).toContain("no-auth");
expect(JSON.stringify(first.config)).not.toContain("host-key");
const [grant] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, account.grantId));
expect(grant.credentialSecretRefs).toEqual([]);
} finally { await Promise.all([first.cleanup(), second.cleanup()]); }
});
it("never restores retained Grok transcripts into same-user runtime homes", async () => {
const account = await service.save(companyId, "alice", {
provider: "xai", method: "api_key", ownership: "personal",
name: "Grok history isolation", agentIds: [], allAgents: true,
}, "fixture-grok-key");
const taskKey = `grok-history-${randomUUID()}`;
const runInput = { ...input, taskKey, adapterType: "grok_local", responsibleUserId: "alice", config: {},
binding: { provider: "xai", method: "api_key", mode: "connection", connectionId: account.connectionId, grantId: account.grantId } as const };
const first = await prepareManagedAiRuntime(db, runInput);
const scope = createHash("sha256").update(JSON.stringify([
companyId, agentId, taskKey, first.sessionIdentity,
])).digest("hex");
await first.cleanup();
// Seed a valid archive from the earlier implementation. Runtime preparation
// must not decrypt it onto the host, even for its authorized agent/task.
const retained = await localEncryptedProvider.createVersion({ value: JSON.stringify({
scope, entries: [{ name: "history.json", bytes: Buffer.from("previous-task-private-transcript").toString("base64") }],
}) });
await db.insert(agentTaskSessions).values({ companyId, agentId, adapterType: "grok_local", taskKey,
sessionParamsJson: { sessionId: "history", paperclipGrokHistory: { scope, material: retained.material } } });
const resumed = await prepareManagedAiRuntime(db, runInput);
const peer = await prepareManagedAiRuntime(db, { ...runInput, agentId: randomUUID() });
try {
expect(resumed.home).not.toBe(first.home);
expect(peer.home).not.toBe(resumed.home);
const transcript = path.join(String(resumed.config.env.GROK_HOME), "sessions", "history.json");
// A separate same-UID process can bypass 0700. It must find no restored
// transcript because none was materialized, not because of permissions.
const crossAgentRead = spawnSync(process.execPath, ["-e", `
const fs = require("node:fs");
try { process.stdout.write(fs.readFileSync(process.argv[1], "utf8")); }
catch (error) { process.stdout.write(error.code); }
`, transcript], { encoding: "utf8" });
expect(crossAgentRead.status).toBe(0);
expect(crossAgentRead.stdout).toBe("ENOENT");
await expect(access(transcript)).rejects.toMatchObject({ code: "ENOENT" });
} finally { await Promise.all([resumed.cleanup(), peer.cleanup()]); }
});
it("isolates the selected Google AI credential for pending native Antigravity preparation", async () => {
const owner = "antigravity-managed-key";
const token = "saved-antigravity-key";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const saved = await service.save(companyId, owner, { provider: "google", method: "api_key", ownership: "personal",
name: "Antigravity Google AI", apiKey: token, allAgents: true, agentIds: [] }, token);
await service.setDefault(companyId, owner, saved.grantId);
const input = { companyId, agentId, responsibleUserId: owner, adapterType: "paperclip_runner",
binding: { provider: "google", method: "api_key", mode: "responsible_user" } as const,
config: { provider: "acpx", acpxAgent: "antigravity", model: "exact-antigravity-model", env: {
GEMINI_API_KEY: "ambient-key", GOOGLE_API_KEY: "ambient-other-key", GEMINI_HOME: "/ambient/home",
GOOGLE_APPLICATION_CREDENTIALS: "/ambient/google.json", GOOGLE_CLOUD_ACCESS_TOKEN: "ambient-oauth-token",
} } };
const runtime = await prepareManagedAiRuntime(db, input);
const reopened = await prepareManagedAiRuntime(db, input);
try {
expect(runtime.attribution).toMatchObject({ provider: "google", method: "api_key", connectionId: saved.connectionId, grantId: saved.grantId });
expect(runtime.config).toMatchObject({ provider: "acpx", acpxAgent: "antigravity", model: input.config.model,
env: { GEMINI_API_KEY: token, GOOGLE_API_KEY: "", GOOGLE_APPLICATION_CREDENTIALS: "", GOOGLE_CLOUD_ACCESS_TOKEN: "" } });
expect(runtime.config.env.GEMINI_HOME).toBe(path.join(runtime.home!, "provider"));
const settingsFile = path.join(String(runtime.config.env.GEMINI_HOME), "antigravity-acp", "settings.json");
expect(JSON.parse(await readFile(settingsFile, "utf8"))).toEqual({ auth: { type: "gemini-api-key" } });
expect(await readFile(settingsFile, "utf8")).not.toContain(token);
expect((await stat(settingsFile)).mode & 0o777).toBe(0o600);
expect(managedAiSessionFingerprintConfig(runtime.config, runtime.home))
.toEqual(managedAiSessionFingerprintConfig(reopened.config, reopened.home));
expect(input.config.env.GEMINI_HOME).toBe("/ambient/home");
await expect(prepareManagedAiRuntime(db, { ...input, responsibleUserId: "bob" })).rejects.toThrow();
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, saved.grantId));
await expect(prepareManagedAiRuntime(db, input)).rejects.toThrow();
} finally { await Promise.all([runtime.cleanup(), reopened.cleanup()]); }
await expect(access(runtime.home!)).rejects.toThrow();
await expect(access(reopened.home!)).rejects.toThrow();
});
it("authenticates local Gemini probes and runs with the saved key in an isolated home", async () => {
const root = await mkdtemp(path.join(home, "gemini-auth-"));
const command = path.join(root, "gemini");
await writeFile(command, `#!/usr/bin/env node
const fs = require("node:fs");
const path = require("node:path");
const settings = JSON.parse(fs.readFileSync(path.join(process.env.HOME, ".gemini/settings.json"), "utf8"));
if (settings.selectedAuthType !== "gemini-api-key" || settings.security?.auth?.selectedType !== "gemini-api-key" || process.env.GEMINI_API_KEY !== "saved-google-key") {
console.error("Invalid auth method selected");
process.exit(1);
}
console.log(JSON.stringify({ type: "system", subtype: "init", session_id: "gemini-managed" }));
console.log(JSON.stringify({ type: "assistant", message: { content: [{ type: "output_text", text: "hello" }] } }));
console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello", session_id: "gemini-managed" }));
`, { mode: 0o700 });
const saved = await service.save(companyId, "alice", {
provider: "google", method: "api_key", ownership: "personal", name: "Saved Google",
apiKey: "fixture", allAgents: true, agentIds: [],
}, "saved-google-key");
const runtime = await prepareManagedAiRuntime(db, {
companyId, agentId, responsibleUserId: "alice", adapterType: "gemini_local",
binding: { provider: "google", method: "api_key", mode: "responsible_user" },
config: { engine: "cli", command, cwd: root, promptTemplate: "Say hello.", paperclipRuntimeSkills: [], env: { GEMINI_API_KEY: "ambient-google-key" } },
});
try {
expect(runtime.attribution.grantId).toBe(saved.grantId);
expect(runtime.home).not.toBe(os.homedir());
const settingsFile = path.join(runtime.home!, ".gemini/settings.json");
expect(await readFile(settingsFile, "utf8")).not.toContain("saved-google-key");
expect((await stat(settingsFile)).mode & 0o777).toBe(0o600);
const probe = await testGeminiEnvironment({ companyId, adapterType: "gemini_local", config: runtime.config });
expect(probe.status).toBe("pass");
expect(probe.checks).toContainEqual(expect.objectContaining({ code: "gemini_hello_probe_passed" }));
const result = await executeGemini({
runId: randomUUID(),
agent: { id: agentId, companyId, name: "Gemini", adapterType: "gemini_local", adapterConfig: { engine: "cli" } },
runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
config: runtime.config, context: {}, onLog: async () => {},
});
expect(result.exitCode).toBe(0);
expect(result.sessionId).toBe("gemini-managed");
} finally {
await runtime.cleanup();
await rm(root, { recursive: true, force: true });
}
await expect(access(runtime.home!)).rejects.toThrow();
});
it("preserves Google and Copilot account defaults when the current provider constraint migration is reapplied", async () => {
const saved = await service.save(companyId, "bob", {
provider: "google", method: "api_key", ownership: "personal", name: "Google migration",
apiKey: "fixture", allAgents: true, agentIds: [],
}, "google-migration-key");
await db.insert(companyMemberships).values({ companyId, principalId: "migration-copilot", principalType: "user", status: "active", membershipRole: "member" });
const copilot = await service.save(companyId, "migration-copilot", {
provider: "github", method: "api_key", ownership: "personal", name: "Copilot migration",
apiKey: "fixture", allAgents: true, agentIds: [],
}, "github_pat_fixture_migration");
await service.setDefault(companyId, "migration-copilot", copilot.grantId);
const migration = await readFile(new URL("../../../packages/db/src/migrations/0361_mighty_dracula.sql", import.meta.url), "utf8");
for (let attempt = 0; attempt < 2; attempt += 1) {
await db.transaction(async (tx) => {
for (const statement of migration.split("--> statement-breakpoint")) {
if (statement.trim()) await tx.execute(sql.raw(statement));
}
});
}
const selected = await service.select({
companyId, agentId, userId: "bob", adapterType: "gemini_local",
binding: { provider: "google", method: "api_key", mode: "responsible_user" },
});
expect(selected.grant.id).toBe(saved.grantId);
const selectedCopilot = await service.select({
companyId, agentId, userId: "migration-copilot", adapterType: "paperclip_runner",
runnerProvider: "acpx", acpxAgent: "copilot", model: "gpt-5.6-luna",
binding: { provider: "github", method: "api_key", mode: "responsible_user" },
});
expect(selectedCopilot.grant.id).toBe(copilot.grantId);
});
it.each([false, true])("reports the authoritative connection-manager capability for custom grants (manager: %s)", async (manager) => {
const userId = `custom-manager-${manager}`;
await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member" });
if (manager) await db.insert(principalPermissionGrants).values({ companyId, principalType: "user", principalId: userId, permissionKey: "tools:manage_connections" });
const app = express();
app.use((req, _res, next) => {
req.actor = { type: "board", source: "session", userId, companyIds: [companyId], memberships: [{ companyId, status: "active", membershipRole: "member" }] };
next();
});
app.use("/api", aiConnectionRoutes(db));
const response = await request(app).get(`/api/companies/${companyId}/ai-connections`);
expect(response.status).toBe(200);
expect(response.body.canManageConnections).toBe(manager);
});
it("probes only the requested authorized grant using its vaulted subscription token", async () => {
const owner = "usage-probe-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const account = await service.save(companyId, owner, { provider: "anthropic", method: "subscription", ownership: "personal", name: "Usage probe account", loginSessionId: "fixture", agentIds: [], allAgents: true }, "usage-selected-secret");
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(JSON.stringify({ five_hour: { utilization: 42 }, seven_day: { utilization: 15 } })));
const app = express();
app.use((req, _res, next) => {
req.actor = req.header("x-agent")
? { type: "agent", agentId, companyId, companyIds: [companyId], onBehalfOfUserId: owner }
: { type: "board", userId: req.header("x-test-user") ?? owner, companyIds: [companyId] };
next();
});
app.use("/api", aiConnectionRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const url = `/api/companies/${companyId}/ai-connections/${account.connectionId}/usage?grantId=${account.grantId}`;
try {
const result = await request(app).get(url);
expect(result.status).toBe(200);
expect(result.headers["cache-control"]).toBe("no-store");
expect(result.body).toMatchObject({ ...account, status: "ok", provider: "anthropic", limits: [{ usedPercent: 42 }, { usedPercent: 15 }] });
expect(fetchSpy).toHaveBeenLastCalledWith("https://api.anthropic.com/api/oauth/usage", expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer usage-selected-secret" }) }));
expect(JSON.stringify(result.body)).not.toContain("usage-selected-secret");
fetchSpy.mockClear();
expect((await request(app).get(url).set("x-test-user", "bob")).status).toBe(404);
expect((await request(app).get(url.replace(companyId, otherCompanyId))).status).toBe(403);
expect((await request(app).get(url.replace(account.grantId, randomUUID()))).status).toBe(404);
expect((await request(app).get(url).set("x-agent", "yes")).status).toBe(403);
expect(fetchSpy).not.toHaveBeenCalled();
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, account.grantId));
const revoked = await request(app).get(url);
expect(revoked.body).toMatchObject({ status: "unavailable", errorCode: "connection_unavailable", limits: [] });
expect(fetchSpy).not.toHaveBeenCalled();
} finally { fetchSpy.mockRestore(); }
});
it("reads provider identity only for an authorized credential and preserves connected state when profile access is denied", async () => {
const owner = "identity-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const account = await service.save(companyId, owner, { provider: "anthropic", method: "subscription", ownership: "personal", name: "Identity account", loginSessionId: "fixture", agentIds: [], allAgents: true }, "identity-selected-secret");
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json({ account: { email: "avery@example.test", uuid: "private-account-id" }, organization: { name: "Example team", uuid: "private-org-id" } }));
const app = express();
app.use((req, _res, next) => {
req.actor = req.header("x-test-anonymous") ? { type: "none" }
: req.header("x-agent") ? { type: "agent", agentId, companyId, companyIds: [companyId], onBehalfOfUserId: owner }
: { type: "board", source: "session", userId: req.header("x-test-user") ?? owner, companyIds: [companyId] };
next();
});
app.use("/api", aiConnectionRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const url = `/api/companies/${companyId}/ai-connections/${account.connectionId}/identity?grantId=${account.grantId}`;
try {
const result = await request(app).get(url);
expect(result.status).toBe(200);
expect(result.headers["cache-control"]).toBe("no-store");
expect(result.body).toMatchObject({ ...account, status: "available", accountLabel: "avery@example.test", organizationLabel: "Example team" });
expect(fetchSpy).toHaveBeenCalledWith("https://api.anthropic.com/api/oauth/profile", expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer identity-selected-secret" }) }));
for (const value of ["identity-selected-secret", "private-account-id", "private-org-id"]) expect(JSON.stringify(result.body)).not.toContain(value);
fetchSpy.mockReset();
expect((await request(app).get(url).set("x-test-user", "bob")).status).toBe(404);
expect((await request(app).get(url.replace(companyId, otherCompanyId))).status).toBe(403);
expect((await request(app).get(url.replace(account.grantId, randomUUID()))).status).toBe(404);
expect((await request(app).get(url).set("x-test-anonymous", "yes")).status).toBe(401);
expect((await request(app).get(url).set("x-agent", "yes")).status).toBe(403);
expect((await request(app).get(url.replace(account.connectionId, "invalid"))).status).toBe(404);
expect(fetchSpy).not.toHaveBeenCalled();
fetchSpy.mockResolvedValue(Response.json({ error: "private-provider-error" }, { status: 403 }));
expect((await request(app).get(url)).body).toMatchObject({ status: "unavailable", errorCode: "permission_denied", accountLabel: null });
expect((await service.list(companyId, owner)).find(item => item.id === account.connectionId)?.status).toBe("connected");
fetchSpy.mockClear();
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, account.grantId));
expect((await request(app).get(url)).body).toMatchObject({ status: "unavailable", errorCode: "connection_unavailable" });
expect(fetchSpy).not.toHaveBeenCalled();
} finally { fetchSpy.mockRestore(); }
});
it("uses the same usage probe for shared accounts across legacy and native runner selections", async () => {
const account = await service.save(companyId, "alice", { provider: "anthropic", method: "subscription", ownership: "shared", name: "Shared usage account", loginSessionId: "fixture", agentIds: [], allAgents: true }, "shared-usage-secret");
const selected = { provider: "anthropic", method: "subscription", mode: "shared", ...account } as const;
const tools = toolAccessService(db);
await tools.replaceConnectionGrantMembers(account.connectionId, account.grantId, ["alice"], { userId: "alice" });
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(JSON.stringify({ five_hour: { utilization: 100 }, extra_usage: { is_enabled: false } })));
try {
await expect(service.probeUsage(companyId, "bob", account.connectionId)).rejects.toThrow("not found");
expect(fetchSpy).not.toHaveBeenCalled();
await tools.replaceConnectionGrantMembers(account.connectionId, account.grantId, ["bob"], { userId: "alice" });
for (const config of [{ adapterType: "claude_local" }, { adapterType: "paperclip_runner", runnerProvider: "acpx", acpxAgent: "claude" }]) {
const row = await service.select({ companyId, userId: "bob", agentId, binding: selected, ...config });
expect(await service.probeUsage(companyId, "bob", row.connection.id, row.grant.id)).toMatchObject({ status: "ok", overage: { available: false }, limits: [{ limitReached: true }, { allowed: false }] });
}
// Unsupported methods do not resolve or send secrets.
const owner = "usage-api-owner";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const apiAccount = await create(owner, "Unsupported usage method");
fetchSpy.mockClear();
expect(await service.probeUsage(companyId, owner, apiAccount.connectionId)).toMatchObject({ status: "unsupported" });
expect(fetchSpy).not.toHaveBeenCalled();
} finally { fetchSpy.mockRestore(); }
});
it("vaults routed credentials, enforces compatibility and access, and configures both Codex runners", async () => {
const routing = { kind: "openrouter", protocol: "responses", auth: "bearer", models: [{ id: "openai/gpt-5.4" }] } as const;
const saved = await service.save(companyId, "alice", { provider: "openrouter", method: "api_key", name: "Routed test", ownership: "personal", apiKey: "fixture", allAgents: true, agentIds: [], routing: { ...routing, models: [...routing.models] } }, "fixture-routed-credential");
const selected = { provider: "openrouter", method: "api_key", mode: "delegated", ...saved } as const;
expect(JSON.stringify(await service.list(companyId, "alice"))).not.toContain("fixture-routed-credential");
expect(await service.list(companyId, "alice")).toEqual(expect.arrayContaining([expect.objectContaining({ id: saved.connectionId, routing, isDefault: false })]));
await expect(service.setDefault(companyId, "alice", saved.grantId)).rejects.toThrow("explicit connection");
await expect(service.select({ ...input, binding: selected, userId: "bob", adapterType: "codex_local", model: "openai/gpt-5.4" })).rejects.toThrow("not shared");
await expect(service.select({ ...input, companyId: otherCompanyId, binding: selected, userId: "alice", adapterType: "codex_local" })).rejects.toThrow();
for (const adapterType of ["codex_local", "paperclip_runner"]) {
const runtime = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: "alice", binding: selected, adapterType, config: { provider: "codex", model: "openai/gpt-5.4", env: { OPENAI_API_KEY: "ambient" } } });
try {
expect(runtime.config.env.OPENAI_API_KEY).toBe("");
expect(runtime.config.env.PAPERCLIP_AI_PROVIDER_KEY).toBe("fixture-routed-credential");
const toml = await readFile(path.join(String(runtime.config.env.CODEX_HOME), "config.toml"), "utf8");
expect(toml).toContain('base_url = "https://openrouter.ai/api/v1"');
expect(toml).toContain('wire_api = "responses"');
expect(toml).not.toContain("fixture-routed-credential");
} finally { await runtime.cleanup(); }
}
await expect(service.save(companyId, "alice", { provider: "openrouter", method: "api_key", name: "Routed test", ownership: "personal", apiKey: "fixture", connectionId: saved.connectionId, allAgents: true, agentIds: [], routing: { ...routing, kind: "gateway", baseUrl: "https://other.example/v1", models: [] } }, "fixture-replacement")).rejects.toThrow("retain");
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, saved.grantId));
await expect(service.select({ ...input, binding: selected, userId: "alice", adapterType: "codex_local", model: "openai/gpt-5.4" })).rejects.toThrow("Reconnect");
});
it.each(["openrouter", "gateway"] as const)("preserves Claude custom headers for managed %s connections", async (kind) => {
const provider = kind === "openrouter" ? "openrouter" : "anthropic";
const routing = { kind, protocol: "messages", auth: "bearer", ...(kind === "gateway" ? { baseUrl: "https://gateway.example/v1" } : {}), models: [] } as const;
const saved = await service.save(companyId, "alice", { provider, method: "api_key", name: `Headers ${kind}`, ownership: "personal", allAgents: true, agentIds: [], routing: { ...routing, models: [] } }, "fixture-credential");
const selected = { provider, method: "api_key", mode: "delegated", ...saved } as const;
const customHeaders = "X-Gateway-Route: account-route\nx-title: Previous harness";
const reference = { type: "secret_ref", secretId: randomUUID(), version: "latest" };
const inherited = process.env.ANTHROPIC_CUSTOM_HEADERS;
process.env.ANTHROPIC_CUSTOM_HEADERS = "X-Gateway-Route: inherited-route";
try {
for (const adapterType of ["claude_local", "paperclip_runner"]) {
for (const configured of [customHeaders, reference, undefined]) {
const runtime = await prepareManagedAiRuntime(db, { companyId, agentId, responsibleUserId: "alice", binding: selected, adapterType,
config: { provider: "acpx", acpxAgent: "claude", env: configured === undefined ? {} : { ANTHROPIC_CUSTOM_HEADERS: configured } } });
try {
const effective = configured ?? process.env.ANTHROPIC_CUSTOM_HEADERS;
const expected = kind === "openrouter" && typeof effective === "string"
? openRouterClaudeHeaders(effective)
: configured;
expect(runtime.config.env.ANTHROPIC_CUSTOM_HEADERS).toEqual(expected);
} finally { await runtime.cleanup(); }
}
}
} finally {
if (inherited === undefined) delete process.env.ANTHROPIC_CUSTOM_HEADERS;
else process.env.ANTHROPIC_CUSTOM_HEADERS = inherited;
}
});
it("reconnects JSONB routing without changing its identity or access", async () => {
const routing = { kind: "gateway", protocol: "responses", auth: "bearer", baseUrl: "https://gateway.example/v1", models: [{ id: "gateway-model" }] } as const;
const input = { provider: "openai", method: "api_key", name: "Reconnect gateway", ownership: "personal", allAgents: false, agentIds: [agentId], routing: { ...routing, models: [...routing.models] } } as const;
const saved = await service.save(companyId, "alice", { ...input, agentIds: [...input.agentIds] }, "old-gateway-credential");
const [stored] = await db.select().from(toolConnections).where(eq(toolConnections.id, saved.connectionId));
expect(stored!.config.ai).toMatchObject({ routing });
expect(stored!.config.sourceTemplateKey).toBe("responses-api");
const [application] = await db.select().from(toolApplications).where(eq(toolApplications.id, stored!.applicationId));
expect(application).toMatchObject({ applicationKey: "app-gallery:responses-api", metadata: { sourceTemplateKey: "responses-api" } });
// PostgreSQL JSONB reorders object keys; compare values, not serialization.
const reordered = { models: [...routing.models], auth: routing.auth, baseUrl: routing.baseUrl, protocol: routing.protocol, kind: routing.kind };
expect(await service.save(companyId, "alice", { ...input, agentIds: [], allAgents: true, connectionId: saved.connectionId, routing: reordered }, "new-gateway-credential")).toEqual(saved);
const selection = { companyId, agentId, userId: "alice", adapterType: "codex_local", binding: { provider: "openai", method: "api_key", mode: "delegated", ...saved } } as const;
const selected = await service.select(selection);
expect(await service.credential(selected)).toBe("new-gateway-credential");
const installs = await db.select().from(toolConnectionInstalls).where(eq(toolConnectionInstalls.connectionId, saved.connectionId));
expect(installs).toEqual([expect.objectContaining({ targetType: "agent", targetId: agentId })]);
await expect(service.save(companyId, "alice", { ...input, agentIds: [agentId], connectionId: saved.connectionId, routing: { ...reordered, baseUrl: "https://different.example/v1" } }, "rejected-credential")).rejects.toThrow("retain");
expect(await service.credential(await service.select(selection))).toBe("new-gateway-credential");
});
it("saves no-auth endpoints without a secret and refuses protocol mismatches", async () => {
const routing = { kind: "local", protocol: "chat", auth: "none", baseUrl: "http://localhost:11434/v1", models: [] } as const;
const saved = await service.save(companyId, "alice", { provider: "openai", method: "api_key", name: "Local", ownership: "personal", allAgents: true, agentIds: [], routing: { ...routing, models: [] } }, "");
const selected = { provider: "openai", method: "api_key", mode: "delegated", ...saved } as const;
await expect(service.select({ ...input, binding: selected, userId: "alice", adapterType: "codex_local" })).rejects.toThrow("incompatible");
const row = await service.select({ ...input, binding: selected, userId: "alice", adapterType: "opencode_local", model: "qwen" });
expect(row.grant.credentialSecretRefs).toEqual([]);
expect(await service.credential(row)).toBe("");
});
it.each([
["anthropic", false], ["openai", false], ["anthropic", true], ["openai", true],
] as const)("turns a %s auth failure into one card and resumes after repair (switch method: %s)", async (provider, switchMethod) => {
const userId = `auth-recovery-${provider}-${switchMethod}`;
const id = randomUUID();
const issueId = randomUUID();
const runId = randomUUID();
const adapterType = provider === "openai" ? "codex_local" : "claude_local";
const selectedBinding = { provider, method: "api_key", mode: "responsible_user" } as const;
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
await db.insert(agents).values({ id, companyId, name: "Auth recovery", adapterType, runtimeConfig: { aiConnection: selectedBinding } });
await db.insert(issues).values({ id: issueId, companyId, title: "Fix provider login", status: "in_progress", assigneeAgentId: id });
const account = await service.save(companyId, userId, { provider, method: "api_key", ownership: "personal", name: "Recovery account", apiKey: "fixture", agentIds: [id], allAgents: false }, "fixture-recovery-key");
const runtime = await prepareManagedAiRuntime(db, { companyId, agentId: id, responsibleUserId: userId, adapterType, binding: selectedBinding, config: {} });
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId: id, status: "failed", errorCode: "acpx_auth_required", responsibleUserId: userId,
contextSnapshot: { issueId, aiConnection: { ...runtime.attribution, identity: runtime.identity } } });
await runtime.cleanup();
const intents = connectionIntentService(db);
const card = await intents.requestForRunAuthFailure(runId);
expect(card).toMatchObject({ state: "needs_user_action", service: provider });
expect((await intents.requestForRunAuthFailure(runId))?.interactionId).toBe(card!.interactionId);
const setup = await intents.setupOptions(card!.interactionId!);
expect(setup.existingConnections).toEqual([]);
expect(setup.aiRepair).toMatchObject({ canReconnect: true, connection: { id: account.connectionId, status: "needs_attention" } });
await expect(intents.complete(card!.interactionId!, account.connectionId, userId)).rejects.toThrow();
// A stopped run's token cannot call the public request path.
await expect(intents.request({ sub: id, company_id: companyId, run_id: runId, responsible_user_id: userId }, provider, { purpose: "ai" })).rejects.toThrow("no longer active");
// Reverification can keep the same credential bytes; it still supersedes the failure.
let repaired = account;
if (switchMethod) {
const token = provider === "openai" ? JSON.stringify({ tokens: { access_token: "fixture-subscription", refresh_token: "fixture-refresh", id_token: "fixture-id", account_id: "fixture-account" } }) : "fixture-subscription";
repaired = await service.save(companyId, userId, { provider, method: "subscription", ownership: "personal", name: "Recovery subscription", loginSessionId: "fixture", agentIds: [id], allAgents: false }, token);
await service.setDefault(companyId, userId, repaired.grantId);
const [original] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, account.grantId));
expect(original.status).toBe("needs_reauthorization");
} else {
await service.save(companyId, userId, { provider, method: "api_key", ownership: "personal", name: "Recovery account", connectionId: account.connectionId, apiKey: "fixture", agentIds: [id], allAgents: false }, provider === "openai" ? "fixture-recovery-key" : "fixture-repaired-key");
}
expect((await intents.setupOptions(card!.interactionId!)).existingConnections.map(connection => connection.id)).toEqual([repaired.connectionId]);
await intents.complete(card!.interactionId!, repaired.connectionId, userId);
const wakeup = vi.fn(async (_agentId, opts) => {
await db.insert(agentWakeupRequests).values({ companyId, agentId: id, source: "automation", status: "queued", idempotencyKey: opts.idempotencyKey });
return null;
});
await connectionIntentDeliveryService(db, { wakeup } as never).deliver(card!.interactionId!);
expect(wakeup).toHaveBeenCalledTimes(1);
expect(wakeup).toHaveBeenCalledWith(id, expect.objectContaining({ contextSnapshot: expect.objectContaining({ forceFreshSession: true }) }));
// Reprocessing an old failure cannot invalidate the newly saved credential.
await intents.requestForRunAuthFailure(runId);
expect((await service.select({ companyId, agentId: id, userId, adapterType, binding: selectedBinding })).grant.status).toBe("active");
});
it("offers a compatible connection for legacy auth failures without silently adopting it", async () => {
const id = randomUUID();
const issueId = randomUUID();
const runId = randomUUID();
await db.insert(agents).values({ id, companyId, name: "Legacy Codex", adapterType: "paperclip_runner", adapterConfig: { provider: "codex" } });
await db.insert(issues).values({ id: issueId, companyId, title: "Legacy auth", status: "in_progress", assigneeAgentId: id });
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId: id, nativeIssueId: issueId, status: "failed", errorCode: "acpx_auth_required", responsibleUserId: "alice", contextSnapshot: {} });
const intents = connectionIntentService(db);
const card = await intents.requestForRunAuthFailure(runId);
expect(card).toMatchObject({ service: "openai", state: "needs_user_action" });
expect(await intents.setupOptions(card!.interactionId!)).toMatchObject({ aiConnectionRequiresAdoption: true, aiConnection: { provider: "openai", mode: "responsible_user" } });
const [unchanged] = await db.select().from(agents).where(eq(agents.id, id));
expect(unchanged.runtimeConfig.aiConnection).toBeUndefined();
await db.insert(heartbeatRuns).values({ companyId, agentId: id, status: "succeeded", responsibleUserId: "alice", contextSnapshot: { issueId }, createdAt: new Date(Date.now() + 1000) });
expect(await intents.requestForRunAuthFailure(runId)).toBeNull();
});
it("lists quotas only for the current member's accessible subscription accounts", async () => {
const user = `quota-${randomUUID()}`;
await db.insert(companyMemberships).values({ companyId, principalId: user, principalType: "user", status: "active", membershipRole: "member" });
const subscription = await service.save(companyId, user, { provider: "openai", method: "subscription", ownership: "personal", name: "Private quota", loginSessionId: "fixture", allAgents: true, agentIds: [] }, JSON.stringify({ tokens: { access_token: "fixture", account_id: "fixture", refresh_token: "fixture" } }));
expect((await service.quotaAccounts(companyId, user)).some(row => row.connection.id === subscription.connectionId)).toBe(true);
expect((await service.quotaAccounts(companyId, "bob")).some(row => row.connection.id === subscription.connectionId)).toBe(false);
expect(await service.quotaAccounts(otherCompanyId, user)).toEqual([]);
await db.update(companyMemberships).set({ status: "inactive" }).where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalId, user)));
expect(await service.quotaAccounts(companyId, user)).toEqual([]);
});
it("repairs a teammate's missing onboarding key with their own inline AI connection", async () => {
const userId = `new-teammate-${randomUUID()}`;
const id = randomUUID();
const issueId = randomUUID();
const runId = randomUUID();
const vault = secretService(db);
const definition = await vault.createUserSecretDefinition(companyId, {
key: `ANTHROPIC_API_KEY.${randomUUID()}`, name: "ANTHROPIC_API_KEY for onboarding",
}, { userId: "alice" });
const ownerSecret = await vault.createCurrentUserSecretValue(companyId, "alice", {
definitionId: definition.id, value: "fixture-original-owner-key",
}, { userId: "alice" });
const adapterConfig = { env: { ANTHROPIC_API_KEY: {
type: "user_secret_ref", key: definition.key, version: "latest", required: true,
} } };
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const [agent] = await db.insert(agents).values({ id, companyId, name: "Chief of Staff", adapterType: "claude_local", adapterConfig }).returning();
await vault.syncEnvBindingsForTarget(companyId, { targetType: "agent", targetId: id }, adapterConfig.env);
await db.insert(issues).values({ id: issueId, companyId, title: "Chat with Chief of Staff", status: "blocked", assigneeAgentId: id, responsibleUserId: userId });
const failure = await resolveExecutionRunAdapterConfig({
companyId, agentId: id, adapterType: "claude_local", issueId,
responsibleUserId: userId, executionRunConfig: adapterConfig, projectEnv: null, secretsSvc: vault,
}).catch(error => error);
expect(failure).toMatchObject({ code: "configuration_incomplete", resultJson: {
configurationIncomplete: { reason: "secret_binding_missing", missingBindings: [
expect.objectContaining({ bindingType: "user_secret_ref", envKey: "ANTHROPIC_API_KEY", errorCode: "user_secret_missing", responsibleUserId: userId }),
] },
} });
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId: id, status: "failed",
errorCode: failure.code, resultJson: failure.resultJson, responsibleUserId: userId, contextSnapshot: { issueId } });
await issueRecoveryActionService(db).upsertSourceScoped({ companyId, sourceIssueId: issueId,
kind: "configuration_validation", cause: "configuration_incomplete", fingerprint: `missing-key:${issueId}`,
nextAction: "Connect your AI account", ownerType: "board", evidence: { latestRunId: runId } });
const intents = connectionIntentService(db);
const card = await intents.requestForRunAuthFailure(runId);
expect(card).toMatchObject({ service: "anthropic", state: "needs_user_action" });
expect((await intents.setupOptions(card!.interactionId!))).toMatchObject({
aiConnectionRequiresAdoption: true, aiConnection: { provider: "anthropic", mode: "responsible_user" },
interaction: { addresseeUserId: userId, sourceRunId: runId },
});
expect((await intents.requestForRunAuthFailure(runId))?.interactionId).toBe(card!.interactionId);
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].runtimeConfig.aiConnection).toBeUndefined();
const account = await create(userId, "Teammate's Claude");
const setup = await intents.setupOptions(card!.interactionId!);
await intents.complete(card!.interactionId!, account.connectionId, userId, {
validatedAdoption: { agentUpdatedAt: agent.updatedAt, binding: setup.aiConnection! },
});
const wakeup = vi.fn(async (_agentId, opts) => {
await db.insert(agentWakeupRequests).values({ companyId, agentId: id, source: "automation", status: "queued", idempotencyKey: opts.idempotencyKey });
return null;
});
const delivery = connectionIntentDeliveryService(db, { wakeup } as never);
await delivery.deliver(card!.interactionId!);
await delivery.deliver(card!.interactionId!);
expect((await db.select().from(issues).where(eq(issues.id, issueId)))[0].status).toBe("in_progress");
expect(wakeup).toHaveBeenCalledTimes(1);
expect(wakeup).toHaveBeenCalledWith(id, expect.objectContaining({ contextSnapshot: expect.objectContaining({ forceFreshSession: true }) }));
expect(await issueRecoveryActionService(db).getActiveForIssue(companyId, issueId)).toBeNull();
expect((await db.select().from(companySecrets).where(eq(companySecrets.id, ownerSecret.id)))[0]).toMatchObject({ ownerUserId: "alice", latestVersion: 1, status: "active" });
const runtime = await prepareManagedAiRuntime(db, { companyId, agentId: id, responsibleUserId: userId, adapterType: "claude_local", binding: setup.aiConnection!, config: adapterConfig });
try {
expect(runtime.config.env).toMatchObject({ ANTHROPIC_API_KEY: "fixture-Teammate's Claude" });
expect(runtime.attribution.grantId).toBe(account.grantId);
} finally { await runtime.cleanup(); }
}, 30000);
it.each([
["anthropic", "claude_local", "CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
["openai", "codex_local", "CODEX_HOME", "OPENAI_API_KEY"],
] as const)("runs the same %s agent with each responsible user's subscription or API key", async (provider, adapterType, subscriptionEnv, apiEnv) => {
const subscriptionUser = `${provider}-subscription-user`;
const apiUser = `${provider}-api-user`;
await db.insert(companyMemberships).values([subscriptionUser, apiUser].map(principalId => ({ companyId, principalId, principalType: "user", status: "active", membershipRole: "member" })));
const token = provider === "openai" ? JSON.stringify({ tokens: { access_token: "fixture-subscription", refresh_token: "fixture-refresh", id_token: "fixture-id", account_id: "fixture-account" } }) : "fixture-subscription";
const subscription = await service.save(companyId, subscriptionUser, { provider, method: "subscription", ownership: "personal", name: "Subscription", loginSessionId: "fixture", allAgents: true, agentIds: [] }, token);
const api = await service.save(companyId, apiUser, { provider, method: "api_key", ownership: "personal", name: "API", apiKey: "fixture", allAgents: true, agentIds: [] }, "fixture-api");
// This is the exact same saved bot config, including a legacy setup method.
const bot = { ...input, adapterType, binding: { provider, method: "subscription", mode: "responsible_user" } as const, config: { model: "unchanged-model", env: { [apiEnv]: "ambient", CLAUDE_CODE_OAUTH_TOKEN: "ambient" } } };
const original = structuredClone(bot);
const [subRun, apiRun] = await Promise.all([subscriptionUser, apiUser].map(responsibleUserId => prepareManagedAiRuntime(db, { ...bot, responsibleUserId })));
try {
expect(subRun.attribution).toMatchObject({ grantId: subscription.grantId, method: "subscription", responsibleUserId: subscriptionUser });
expect(apiRun.attribution).toMatchObject({ grantId: api.grantId, method: "api_key", responsibleUserId: apiUser });
const subEnv = subRun.config.env as Record<string, string>;
const apiEnvValues = apiRun.config.env as Record<string, string>;
expect(subEnv[apiEnv]).toBe("");
expect(apiEnvValues[apiEnv]).toBe("fixture-api");
if (provider === "anthropic") {
expect(subEnv[subscriptionEnv]).toBe(token);
expect(apiEnvValues[subscriptionEnv]).toBe("");
} else {
expect(await readFile(path.join(subEnv.CODEX_HOME, "auth.json"), "utf8")).toBe(token);
expect(JSON.parse(await readFile(path.join(apiEnvValues.CODEX_HOME, "auth.json"), "utf8"))).toEqual({ OPENAI_API_KEY: "fixture-api" });
}
expect(subEnv.HOME).not.toBe(apiEnvValues.HOME);
expect(subRun.identity).not.toBe(apiRun.identity);
expect(subRun.config.model).toBe(bot.config.model);
expect(apiRun.config.model).toBe(bot.config.model);
expect(bot).toEqual(original);
expect(aiConnectionBindingSchema.parse(bot.binding)).toEqual(bot.binding);
} finally { await Promise.all([subRun.cleanup(), apiRun.cleanup()]); }
});
it("has one provider default across methods, retains unavailable defaults and honors explicit account methods", async () => {
const userId = "provider-default-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const api = await create(userId, "Provider API");
const subscription = await service.save(companyId, userId, { provider: "anthropic", method: "subscription", ownership: "personal", name: "Provider subscription", loginSessionId: "fixture", allAgents: true, agentIds: [] }, "fixture-provider-subscription");
expect((await service.select({ ...input, userId })).grant.id).toBe(api.grantId);
expect((await service.list(companyId, userId)).filter(account => account.isDefault).map(account => account.grantId)).toEqual([api.grantId]);
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, api.grantId));
await expect(service.select({ ...input, userId })).rejects.toThrow("Reconnect");
await create(userId, "Another API");
await expect(service.select({ ...input, userId })).rejects.toThrow("Reconnect");
await service.setDefault(companyId, userId, subscription.grantId);
expect((await service.select({ ...input, userId })).attribution).toMatchObject({ method: "subscription", grantId: subscription.grantId });
expect((await service.list(companyId, userId)).filter(account => account.isDefault)).toHaveLength(1);
await expect(service.select({ ...input, userId, binding: { ...binding, mode: "delegated", ...subscription } })).rejects.toThrow("incompatible");
});
it("backfills provider defaults repeatably without deleting old preferences or replacing an unavailable choice", async () => {
const userId = "provider-default-migration-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const api = await create(userId, "Migration API");
const subscription = await service.save(companyId, userId, { provider: "anthropic", method: "subscription", ownership: "personal", name: "Migration subscription", loginSessionId: "fixture", allAgents: true, agentIds: [] }, "fixture-migration-subscription");
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, api.grantId));
await db.update(aiConnectionDefaults).set({ updatedAt: new Date("2030-01-01") }).where(eq(aiConnectionDefaults.grantId, api.grantId));
await db.delete(aiProviderDefaults).where(and(eq(aiProviderDefaults.companyId, companyId), eq(aiProviderDefaults.userId, userId)));
const legacyRows = await db.select().from(aiConnectionDefaults).where(eq(aiConnectionDefaults.userId, userId));
const migration = await readFile(new URL("../../../packages/db/src/migrations/0277_uneven_lady_deathstrike.sql", import.meta.url), "utf8");
for (let pass = 0; pass < 2; pass++) for (const statement of migration.split("--> statement-breakpoint").filter(value => value.trim())) await db.execute(sql.raw(statement));
expect(await db.select().from(aiConnectionDefaults).where(eq(aiConnectionDefaults.userId, userId))).toEqual(legacyRows);
await expect(service.select({ ...input, userId })).rejects.toThrow("Reconnect");
await service.setDefault(companyId, userId, subscription.grantId);
for (const statement of migration.split("--> statement-breakpoint").filter(value => value.trim())) await db.execute(sql.raw(statement));
expect((await service.select({ ...input, userId })).grant.id).toBe(subscription.grantId);
});
it("observes old-server default changes during rolling upgrades without treating new accounts as default changes", async () => {
const userId = "rolling-upgrade-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const api = await create(userId, "Rolling API");
const subscription = await service.save(companyId, userId, { provider: "anthropic", method: "subscription", ownership: "personal", name: "Rolling subscription", loginSessionId: "fixture", allAgents: true, agentIds: [] }, "fixture-rolling-subscription");
expect((await service.select({ ...input, userId })).grant.id).toBe(api.grantId);
// An older server updates only the legacy per-method row on Make default.
await db.update(aiConnectionDefaults).set({ grantId: subscription.grantId, updatedAt: new Date() })
.where(and(eq(aiConnectionDefaults.userId, userId), eq(aiConnectionDefaults.method, "subscription")));
expect((await service.select({ ...input, userId })).attribution).toMatchObject({ grantId: subscription.grantId, method: "subscription" });
expect((await service.list(companyId, userId)).filter(account => account.isDefault).map(account => account.grantId)).toEqual([subscription.grantId]);
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, subscription.grantId));
await create(userId, "Rolling second API");
await expect(service.select({ ...input, userId })).rejects.toThrow("Reconnect");
await db.update(aiConnectionDefaults).set({ grantId: api.grantId, updatedAt: new Date() })
.where(and(eq(aiConnectionDefaults.userId, userId), eq(aiConnectionDefaults.method, "api_key")));
expect((await service.select({ ...input, userId })).grant.id).toBe(api.grantId);
});
it("checks the selected environment for project auth overrides without exposing their contents", async () => {
const execute = vi.spyOn(executionTarget, "runAdapterExecutionTargetProcess");
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace/project" } as Parameters<typeof assertManagedAiProjectAuth>[2];
try {
execute.mockResolvedValue({ exitCode: 42, stdout: "", stderr: "", signal: null, timedOut: false } as Awaited<ReturnType<typeof executionTarget.runAdapterExecutionTargetProcess>>);
await expect(assertManagedAiProjectAuth({}, "openai", target)).rejects.toThrow("project authentication settings");
expect(execute.mock.calls[0][3]).toContain("/workspace/project");
expect(execute.mock.calls[0][3]).toContain(".codex/config.toml");
execute.mockResolvedValue({ exitCode: 0, stdout: "", stderr: "", signal: null, timedOut: false } as Awaited<ReturnType<typeof executionTarget.runAdapterExecutionTargetProcess>>);
await expect(assertManagedAiProjectAuth({}, "openai", target)).resolves.toBeUndefined();
await expect(assertManagedAiProjectAuth({ args: ["--api-key=override"] }, "xai", target)).rejects.toThrow("overrides");
} finally { execute.mockRestore(); }
});
it("requires a typed project authentication capability for task-only environments", async () => {
const execute = vi.spyOn(executionTarget, "runAdapterExecutionTargetProcess");
const description = { kind: "description", taskId: "task", projectAuthCheckVersion: 1,
runner: { version: "fixture", digest: `sha256:${"a".repeat(64)}` } };
const task = vi.fn(async () => description);
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/unused", runnerTask: task } as Parameters<typeof assertManagedAiProjectAuth>[2];
try {
await expect(assertManagedAiProjectAuth({}, "openai", target)).resolves.toBeUndefined();
await expect(assertManagedAiProjectAuth({ args: ["--api-key=override"] }, "openai", target)).rejects.toThrow("overrides");
expect(task).toHaveBeenCalledWith({ kind: "describe" });
task.mockResolvedValueOnce({ ...description, projectAuthCheckVersion: undefined } as never);
await expect(assertManagedAiProjectAuth({}, "openai", target)).rejects.toThrow("cannot validate");
expect(execute).not.toHaveBeenCalled();
} finally { execute.mockRestore(); }
});
it("keeps personal defaults separate and does not replace the first default", async () => {
const first = await create("alice", "Alice first");
await create("alice", "Alice second");
const bob = await create("bob", "Bob first");
const [a,b] = await Promise.all([service.select({ ...input, userId: "alice" }), service.select({ ...input, userId: "bob" })]);
expect(a.grant.id).toBe(first.grantId); expect(b.grant.id).toBe(bob.grantId);
expect(await service.credential(a)).toBe("fixture-Alice first");
expect(await service.credential(b)).toBe("fixture-Bob first");
expect(JSON.stringify(await service.list(companyId, "alice"))).not.toContain("fixture-");
expect(await service.list(otherCompanyId, "alice")).toEqual([]);
});
it("retains a revoked default without automatic fallback", async () => {
const selected = await service.select({ ...input, userId: "alice" });
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, selected.grant.id));
await create("alice", "Alice third");
expect(await toolAccessService(db).getConnection(selected.connection.id, companyId)).toMatchObject({ healthStatus: "missing_secret", requiresReauthorization: true });
expect((await toolAccessService(db).listConnections(companyId)).find(connection => connection.id === selected.connection.id)?.healthStatus).toBe("missing_secret");
await expect(service.select({ ...input, userId: "alice" })).rejects.toThrow("Reconnect");
const second = (await service.list(companyId, "alice")).find(a => a.name === "Alice second")!;
await service.setDefault(companyId, "alice", second.grantId);
expect((await service.select({ ...input, userId: "alice" })).grant.id).toBe(second.grantId);
await expect(service.setDefault(companyId, "bob", second.grantId)).rejects.toThrow("owner");
});
it("uses human access for every selection; an agent delegation cannot override Just me", async () => {
const personal = await service.select({ ...input, userId: "alice" });
const delegated = { ...binding, mode: "delegated" as const, connectionId: personal.connection.id, grantId: personal.grant.id };
const rejection = await service.select({ ...input, userId: "bob", binding: delegated }).catch(error => error);
expect(rejection).toMatchObject({ status: 403, message: "This credential is not shared with the responsible user" });
expect(rejection.details).toBeUndefined();
expect(readAiConnectionConfigurationFailure(rejection)).toBe("ai_connection_credential_not_shared");
// Existing delegation records no longer confer an independent AI permission.
await db.insert(connectionGrantDelegations).values({ companyId, grantId: personal.grant.id, agentId, createdByUserId: "alice" });
await expect(service.select({ ...input, userId: "bob", binding: delegated })).rejects.toThrow("not shared");
expect((await service.select({ ...input, userId: "alice", binding: delegated })).grant.id).toBe(personal.grant.id);
expect((await service.list(companyId, "bob", agentId)).some(account => account.id === personal.connection.id)).toBe(false);
await expect(toolAccessService(db).createConnectionGrantDelegation(personal.connection.id, personal.grant.id, agentId, "alice")).rejects.toThrow("human access settings");
});
it("applies the existing human audience editor to AI listing and execution without a second authorization", async () => {
const shared = await create("alice", "Engineering", "shared");
const sharedBinding = { ...binding, mode: "shared" as const, ...shared };
const tools = toolAccessService(db);
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["alice"], { userId: "alice" });
const rejection = await service.select({ ...input, userId: "bob", binding: sharedBinding }).catch(error => error);
expect(rejection).toMatchObject({ status: 403, message: "This credential is not shared with the responsible user" });
expect(readAiConnectionConfigurationFailure(rejection)).toBe("ai_connection_credential_not_shared");
expect((await service.list(companyId, "bob", agentId)).some(account => account.id === shared.connectionId)).toBe(false);
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["bob"], { userId: "alice" });
expect((await service.select({ ...input, userId: "bob", binding: sharedBinding })).grant.id).toBe(shared.grantId);
expect((await service.list(companyId, "bob", agentId)).some(account => account.id === shared.connectionId)).toBe(true);
await expect(service.select({ ...input, userId: "alice", binding: sharedBinding })).rejects.toThrow("not shared");
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, [], { userId: "alice" });
for (const userId of ["alice", "bob"]) {
expect((await service.select({ ...input, userId, binding: sharedBinding })).grant.id).toBe(shared.grantId);
}
const missingIdentity = await service.select({ ...input, userId: null, binding: sharedBinding }).catch(error => error);
expect(missingIdentity).toMatchObject({ status: 403, message: "This credential is not shared with the responsible user" });
expect(readAiConnectionConfigurationFailure(missingIdentity)).toBeNull();
const inactiveMember = await service.select({ ...input, userId: "inactive-user", binding: sharedBinding }).catch(error => error);
expect(inactiveMember).toMatchObject({ status: 403, message: "The responsible user is not an active company member" });
expect(readAiConnectionConfigurationFailure(inactiveMember)).toBeNull();
// Human permission still cannot bypass the separate agent-access setting.
await db.delete(toolConnectionInstalls).where(eq(toolConnectionInstalls.connectionId, shared.connectionId));
const missingAgentPermission = await service.select({ ...input, userId: "bob", binding: sharedBinding }).catch(error => error);
expect(missingAgentPermission).toMatchObject({ status: 403 });
expect(missingAgentPermission.message).toContain("not permitted for this agent");
expect(readAiConnectionConfigurationFailure(missingAgentPermission)).toBeNull();
expect(sharedBinding).toEqual({ ...binding, mode: "shared", ...shared });
});
it.each(["inactive_owner", "wrong_binding_kind", "wrong_company_membership"] as const)(
"does not classify other credential authorization denials as sharing configuration: %s", async (cause) => {
const owner = `sharing-${cause}-owner`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const account = await create(owner, "Sharing permission fixture");
const selected = { ...binding, mode: cause === "wrong_binding_kind" ? "shared" as const : "delegated" as const, ...account };
let userId = "bob";
if (cause === "inactive_owner") {
await db.update(companyMemberships).set({ status: "inactive" }).where(and(eq(companyMemberships.companyId, companyId), eq(companyMemberships.principalId, owner)));
}
if (cause === "wrong_company_membership") {
userId = "other-company-sharing-user";
await db.insert(companyMemberships).values({ companyId: otherCompanyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
}
const error = await service.select({ ...input, userId, binding: selected }).catch(error => error);
expect(error).toMatchObject({ status: 403, message: cause === "inactive_owner"
? "The account owner is no longer an active company member"
: cause === "wrong_binding_kind" ? "Select a company-shared account"
: "The responsible user is not an active company member" });
expect(readAiConnectionConfigurationFailure(error)).toBeNull();
},
);
it("isolates concurrent homes and overrides ambient credentials without changing the model", async () => {
vi.stubEnv("ANTHROPIC_API_KEY", "ambient-never-use");
const config = { model: "unchanged-model", env: { ANTHROPIC_API_KEY: "project-never-use" } };
const [a,b] = await Promise.all(["alice", "bob"].map(responsibleUserId => prepareManagedAiRuntime(db, { ...input, responsibleUserId, config })));
const ae = a.config.env as Record<string,string>, be = b.config.env as Record<string,string>;
expect(ae.ANTHROPIC_API_KEY).toBe("fixture-Alice second"); expect(be.ANTHROPIC_API_KEY).toBe("fixture-Bob first");
expect(ae.HOME).not.toBe(be.HOME); expect(a.identity).not.toBe(b.identity);
expect(a.config.model).toBe("unchanged-model"); expect(config.env.ANTHROPIC_API_KEY).toBe("project-never-use");
await Promise.all([a.cleanup(), b.cleanup()]); await expect(access(ae.HOME)).rejects.toThrow();
});
it("blocks missing identity, incompatible providers, and cross-company explicit selections", async () => {
await expect(service.select({ ...input, userId: null })).rejects.toThrow("responsible user");
await expect(service.select({ ...input, userId: "alice", adapterType: "codex_local" })).rejects.toThrow("compatible");
const account = await service.select({ ...input, userId: "alice" });
await expect(service.select({ ...input, companyId: otherCompanyId, userId: "alice", binding: { ...binding, mode: "shared", connectionId: account.connection.id, grantId: account.grant.id } })).rejects.toThrow();
});
it("resolves shared encrypted credentials through the existing secret binding system", async () => {
const created = await create("alice", "Shared credential proof", "shared");
const selected = await service.select({ ...input, userId: "bob", binding: { ...binding, mode: "shared", ...created } });
expect(await service.credential(selected)).toBe("fixture-Shared credential proof");
});
it("saves successful login completion once and rejects abandoned attempts", async () => {
const [environment] = await db.insert(environments).values({ name: "AI login test", driver: "sandbox" }).returning();
const intent = { provider: "anthropic", method: "subscription", ownership: "personal", name: "Claude subscription", agentIds: [], allAgents: true } as const;
const sessionId = randomUUID();
await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id, adapterType: "claude_local", startedByUserId: "alice", publicSessionId: sessionId, status: "submitting", aiConnection: { ...intent, agentIds: [] }, expiresAt: new Date(Date.now() + 60000) });
const first = await completeSetupTokenAiConnection(db, { scope: { companyId, ownerUserId: "alice", environmentId: environment.id, adapterType: "claude_local", aiConnection: { ...intent, agentIds: [] } }, token: "fixture-subscription", sessionId });
expect(await completeSetupTokenAiConnection(db, { scope: { companyId, ownerUserId: "alice", environmentId: environment.id, adapterType: "claude_local", aiConnection: { ...intent, agentIds: [] } }, token: "fixture-subscription", sessionId })).toEqual(first);
expect((await db.select().from(adapterAuthSessions).where(eq(adapterAuthSessions.publicSessionId, sessionId)))[0]).toMatchObject({ status: "stored", connectionId: first.connectionId, connectionGrantId: first.grantId });
const cancelled = randomUUID();
await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id, adapterType: "claude_local", startedByUserId: "alice", publicSessionId: cancelled, status: "cancelled", expiresAt: new Date(Date.now() + 60000) });
await expect(completeSetupTokenAiConnection(db, { scope: { companyId, ownerUserId: "alice", environmentId: environment.id, adapterType: "claude_local", aiConnection: { ...intent, agentIds: [] } }, token: "fixture-never-save", sessionId: cancelled })).rejects.toThrow("no longer active");
});
it.each(["anthropic", "openai"] as const)("binds %s by internal session ID without choosing its completion state", async provider => {
const owner = `session-owner-${provider}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const [environment] = await db.insert(environments).values({ name: `Session identity ${provider}`, driver: "sandbox" }).returning();
const intent = { provider, method: "subscription", ownership: "personal", name: `Session identity ${provider}`, allAgents: false, agentIds: [] } as const;
const [session] = await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id,
adapterType: provider === "anthropic" ? "claude_local" : "codex_local", startedByUserId: owner,
publicSessionId: randomUUID(), status: "promoting", aiConnection: { ...intent, agentIds: [] },
expiresAt: new Date(Date.now() + 60_000),
}).returning();
const adapterWake = vi.fn(), setupWake = vi.fn();
const unsubscribe = [
subscribeDeliveryWork(db, DELIVERY_QUEUES.adapterLoginCleanup, adapterWake),
subscribeDeliveryWork(db, DELIVERY_QUEUES.setupTokenCleanup, setupWake),
];
const complete = vi.fn(async () => {
expect(adapterWake).not.toHaveBeenCalled();
expect(setupWake).not.toHaveBeenCalled();
});
try {
await expect(service.save(companyId, owner, { ...intent, agentIds: [] }, "fixture", { id: session.publicSessionId, complete })).rejects.toThrow("ownership mismatch");
await expect(service.save(companyId, "bob", { ...intent, agentIds: [] }, "fixture", { id: session.id, complete })).rejects.toThrow("ownership mismatch");
await expect(service.save(companyId, owner, { ...intent, agentIds: [], ownership: "shared" }, "fixture", { id: session.id, complete })).rejects.toThrow("target mismatch");
expect(complete).not.toHaveBeenCalled();
const saved = await service.save(companyId, owner, { ...intent, agentIds: [] }, "fixture", { id: session.id, complete });
expect((await db.select().from(adapterAuthSessions).where(eq(adapterAuthSessions.id, session.id)))[0]).toMatchObject({
connectionId: saved.connectionId, connectionGrantId: saved.grantId, status: "promoting",
});
expect(await service.save(companyId, owner, { ...intent, agentIds: [] }, "replacement", { id: session.id, complete })).toEqual(saved);
expect(complete).toHaveBeenCalledTimes(1);
expect(adapterWake).toHaveBeenCalledTimes(1);
expect(setupWake).toHaveBeenCalledTimes(1);
} finally {
unsubscribe.forEach(stop => stop());
await db.delete(adapterAuthSessions).where(eq(adapterAuthSessions.id, session.id));
}
});
it("rolls back credential and connection writes if setup-token completion loses its claim", async () => {
const owner = "lost-setup-token-claim";
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const [environment] = await db.insert(environments).values({ name: "Lost setup claim", driver: "sandbox" }).returning();
const intent = { provider: "anthropic", method: "subscription", ownership: "personal", name: "Lost setup claim", allAgents: false, agentIds: [] } as const;
const [session] = await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id,
adapterType: "claude_local", startedByUserId: owner, publicSessionId: randomUUID(), status: "submitting",
aiConnection: { ...intent, agentIds: [] }, expiresAt: new Date(Date.now() + 60_000), boundAt: new Date(),
}).returning();
try {
await expect(completeSetupTokenAiConnection(db, { scope: { companyId, ownerUserId: owner,
environmentId: environment.id, adapterType: "claude_local", aiConnection: { ...intent, agentIds: [] } },
sessionId: session.publicSessionId, token: "must-rollback",
})).rejects.toThrow();
expect((await db.select().from(adapterAuthSessions).where(eq(adapterAuthSessions.id, session.id)))[0]).toMatchObject({
connectionId: null, connectionGrantId: null, status: "submitting",
});
expect(await db.select().from(toolConnections).where(and(eq(toolConnections.companyId, companyId), eq(toolConnections.name, intent.name)))).toEqual([]);
expect(await db.select().from(companySecrets).where(eq(companySecrets.ownerUserId, owner))).toEqual([]);
} finally { await db.delete(adapterAuthSessions).where(eq(adapterAuthSessions.id, session.id)); }
});
it("preserves connection identity and defaults through reconnect; revocation wins over older attempts", async () => {
const current = await service.select({ ...input, userId: "bob" });
const reconnect = { ...binding, ownership: "personal" as const, name: current.connection.name, apiKey: "fixture", agentIds: [], allAgents: true, connectionId: current.connection.id };
const result = await service.save(companyId, "bob", reconnect, "fixture-reconnected");
expect(result.grantId).toBe(current.grant.id);
expect(await service.credential(await service.select({ ...input, userId: "bob" }))).toBe("fixture-reconnected");
const beforeRevocation = new Date(Date.now() - 1000);
await db.update(connectionGrants).set({ status: "revoked", updatedAt: new Date() }).where(eq(connectionGrants.id, current.grant.id));
await expect(service.save(companyId, "bob", reconnect, "fixture-stale", undefined, beforeRevocation)).rejects.toThrow("changed");
await expect(service.select({ ...input, userId: "bob" })).rejects.toThrow("Reconnect");
});
it("rejects invalid purpose/transport combinations in the database", async () => {
const selected = await service.select({ ...input, userId: "alice" });
await expect(db.update(toolConnections).set({ transport: "mcp_remote" }).where(eq(toolConnections.id, selected.connection.id))).rejects.toThrow();
await expect(db.update(toolConnections).set({ connectionPurpose: "tool" }).where(eq(toolConnections.id, selected.connection.id))).rejects.toThrow();
});
it("indexes only known user credentials, retains references, and is repeatable without adopting agents", async () => {
const selected = await service.select({ ...input, userId: "alice" });
const [emailConnection] = await db.insert(toolConnections).values({
companyId,
applicationId: selected.connection.applicationId,
name: "Existing AgentMail inbox",
uid: `agentmail-migration-${randomUUID()}`,
connectionPurpose: "channel",
transport: "rest_api",
authKind: "api_key",
config: { provider: "agentmail" },
}).returning();
const vault = secretService(db);
const definition = await vault.createUserSecretDefinition(companyId, { key: "legacy_claude", name: "Existing owned Claude key", provider: "local_encrypted" }, { userId: "alice" });
const secret = await vault.createCurrentUserSecretValue(companyId, "alice", { definitionId: definition.id, value: "fixture-legacy" }, { userId: "alice" });
await vault.syncUserSecretDeclarationsForTarget(companyId, { targetType: "agent", targetId: agentId }, [{ definitionKey: definition.key, configPath: "env.ANTHROPIC_API_KEY", envKey: "ANTHROPIC_API_KEY", required: true }]);
const migration = await readFile(new URL("../../../packages/db/src/migrations/0276_hard_mandroid.sql", import.meta.url), "utf8");
const adoption = migration.slice(migration.indexOf("DO $$", migration.indexOf("-- Only declared")));
await db.execute(sql.raw(adoption));
const before = await service.list(companyId, "alice");
for (const statement of migration.split("--> statement-breakpoint").filter(value => value.trim())) await db.execute(sql.raw(statement));
expect(await service.list(companyId, "alice")).toEqual(before);
const [preservedEmail] = await db.select().from(toolConnections).where(eq(toolConnections.id, emailConnection.id));
expect(preservedEmail).toEqual(emailConnection);
const indexed = before.find(account => account.name === secret.name)!;
expect(indexed.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
const [grant] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, indexed.grantId));
expect(grant.credentialSecretRefs[0].secretId).toBe(secret.id);
const [agent] = await db.select().from(agents).where(eq(agents.id, agentId));
expect(agent.runtimeConfig.aiConnection).toBeUndefined();
});
it("runs two Claude subscription executions for the same grant at the same time", async () => {
// Claude writes no auth file back to the grant, so two runs share no
// mutable state and must not wait for each other.
const subscription = { ...input, binding: { ...binding, method: "subscription" as const }, responsibleUserId: "alice", config: { model: "same-model" } };
const account = (await service.list(companyId, "alice")).find(account => account.provider === "anthropic" && account.method === "subscription")!;
await service.setDefault(companyId, "alice", account.grantId);
const [first, second] = await Promise.all([prepareManagedAiRuntime(db, subscription), prepareManagedAiRuntime(db, subscription)]);
try {
expect(second.identity).toBe(first.identity);
} finally {
await Promise.all([first.cleanup(), second.cleanup()]);
}
});
it("runs a same-agent OpenAI subscription child alongside a still-open parent", async () => {
const userId = "subscription-contention-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const credential = JSON.stringify({ tokens: { access_token: "fixture-access", refresh_token: "fixture-refresh", id_token: "fixture-id", account_id: "fixture-account" } });
const account = await service.save(companyId, userId, { provider: "openai", method: "subscription", ownership: "personal", name: "Contention fixture", loginSessionId: "fixture", allAgents: true, agentIds: [] }, credential);
const runInput = {
companyId,
agentId,
adapterType: "codex_local",
responsibleUserId: userId,
binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const,
config: {},
};
// A parent can create and assign a child before its own execution ends.
// Both runs select the same personal subscription, even on the same agent.
const parent = await prepareManagedAiRuntime(db, runInput);
const child = await prepareManagedAiRuntime(db, runInput);
try {
expect(child.identity).toBe(parent.identity);
expect(child.attribution.grantId).toBe(account.grantId);
expect(parent.attribution.subscriptionId).toEqual(expect.any(String));
expect(child.attribution.subscriptionId).toBe(parent.attribution.subscriptionId);
expect(child.config.managedAiConnection).toMatchObject({ subscriptionId: parent.attribution.subscriptionId });
} finally {
await Promise.all([parent.cleanup(), child.cleanup()]);
}
});
it("persists the freshest refreshed credential to its original grant across a same-account reconnect", async () => {
const auth = (marker: string, hour: number) => JSON.stringify({ tokens: { account_id: "fixture-account", id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
const intent = { provider: "openai" as const, method: "subscription" as const, name: "Refresh test", ownership: "personal" as const, agentIds: [], allAgents: true, loginSessionId: "fixture" };
const saved = await service.save(companyId, "alice", intent, auth("first", 10));
const runInput = { ...input, adapterType: "codex_local", responsibleUserId: "alice", binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { model: "same-model" } };
const first = await prepareManagedAiRuntime(db, runInput);
await writeFile(path.join(String(first.config.env.CODEX_HOME), "auth.json"), auth("refreshed", 11));
await first.cleanup();
const selected = await service.select({ ...runInput, userId: "alice" });
expect(await service.credential(selected)).toBe(auth("refreshed", 11));
const second = await prepareManagedAiRuntime(db, runInput);
expect(second.identity).not.toBe(first.identity);
// A same-account reconnect writes an older last_refresh than the run
// that is still open.
await service.save(companyId, "alice", { ...intent, connectionId: saved.connectionId }, auth("reconnect", 12));
await writeFile(path.join(String(second.config.env.CODEX_HOME), "auth.json"), auth("later-refresh", 13));
await second.cleanup();
// The newer refresh persists to the grant it started from.
expect(await service.credential(await service.select({ ...runInput, userId: "alice" }))).toBe(auth("later-refresh", 13));
});
it("resolves two concurrent OpenAI subscription write-backs by freshness, not by order", async () => {
const userId = "concurrent-freshness-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const auth = (marker: string, hour: number) => JSON.stringify({ tokens: { account_id: "fixture-account", id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
await service.save(companyId, userId, { provider: "openai", method: "subscription", ownership: "personal", name: "Freshness fixture", loginSessionId: "fixture", allAgents: true, agentIds: [] }, auth("start", 10));
const runInput = { ...input, adapterType: "codex_local", responsibleUserId: userId, binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { model: "same-model" } };
// Two runs use the same OpenAI subscription grant at the same time.
// Neither call below throws ai_connection_busy.
const older = await prepareManagedAiRuntime(db, runInput);
const newer = await prepareManagedAiRuntime(db, runInput);
await writeFile(path.join(String(older.config.env.CODEX_HOME), "auth.json"), auth("older", 11));
await writeFile(path.join(String(newer.config.env.CODEX_HOME), "auth.json"), auth("newer", 12));
// The run with the newer last_refresh writes back first. The run with
// the older last_refresh writes back last and must not overwrite it.
await newer.cleanup();
await older.cleanup();
const stored = await service.credential(await service.select({ ...runInput, userId }));
expect(stored).toBe(auth("newer", 12));
});
it("resolves two concurrent OpenAI subscription write-backs by freshness in reverse arrival order", async () => {
const userId = "concurrent-freshness-reverse-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const auth = (marker: string, hour: number) => JSON.stringify({ tokens: { account_id: "fixture-account", id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
await service.save(companyId, userId, { provider: "openai", method: "subscription", ownership: "personal", name: "Reverse freshness fixture", loginSessionId: "fixture", allAgents: true, agentIds: [] }, auth("start", 10));
const runInput = { ...input, adapterType: "codex_local", responsibleUserId: userId, binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { model: "same-model" } };
// Two runs use the same OpenAI subscription grant at the same time.
// Neither call below throws ai_connection_busy.
const older = await prepareManagedAiRuntime(db, runInput);
const newer = await prepareManagedAiRuntime(db, runInput);
await writeFile(path.join(String(older.config.env.CODEX_HOME), "auth.json"), auth("older", 11));
await writeFile(path.join(String(newer.config.env.CODEX_HOME), "auth.json"), auth("newer", 12));
// The run with the older last_refresh writes back first. The run with
// the newer last_refresh writes back last and must win.
await older.cleanup();
await newer.cleanup();
const stored = await service.credential(await service.select({ ...runInput, userId }));
expect(stored).toBe(auth("newer", 12));
});
it("resolves two concurrent xAI subscription write-backs by freshness, not by order", async () => {
const userId = "concurrent-freshness-xai-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const identityKey = "https://auth.x.ai::33333333-3333-3333-3333-333333333333";
const auth = (marker: string, expiresAtMs: number) => JSON.stringify({ [identityKey]: { key: `key-${marker}`, refresh_token: `refresh-${marker}`, expires_at: new Date(expiresAtMs).toISOString() } });
const now = Date.now();
await service.save(companyId, userId, { provider: "xai", method: "subscription", ownership: "personal", name: "Grok freshness fixture", loginSessionId: "fixture", allAgents: true, agentIds: [] }, auth("start", now));
const runInput = { ...input, adapterType: "grok_local", responsibleUserId: userId, binding: { provider: "xai", method: "subscription", mode: "responsible_user" } as const, config: { model: "same-model" } };
// Two runs use the same xAI subscription grant at the same time.
// Neither call below throws ai_connection_busy.
const older = await prepareManagedAiRuntime(db, runInput);
const newer = await prepareManagedAiRuntime(db, runInput);
await writeFile(path.join(String(older.config.env.GROK_HOME), "auth.json"), auth("older", now + 60 * 60 * 1000));
await writeFile(path.join(String(newer.config.env.GROK_HOME), "auth.json"), auth("newer", now + 2 * 60 * 60 * 1000));
// The run with the older expiry writes back first. The run with the
// newer expiry writes back last and must win.
await older.cleanup();
await newer.cleanup();
const stored = await service.credential(await service.select({ ...runInput, userId }));
expect(stored).toBe(auth("newer", now + 2 * 60 * 60 * 1000));
});
it("discards a credential write-back when the grant is revoked while the run is open", async () => {
const userId = "revoked-write-back-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const auth = (marker: string, hour: number) => JSON.stringify({ tokens: { account_id: "fixture-account", id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
const saved = await service.save(companyId, userId, { provider: "openai", method: "subscription", ownership: "personal", name: "Revocation fixture", loginSessionId: "fixture", allAgents: true, agentIds: [] }, auth("start", 10));
const runInput = { ...input, adapterType: "codex_local", responsibleUserId: userId, binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { model: "same-model" } };
const run = await prepareManagedAiRuntime(db, runInput);
const [grantBeforeCleanup] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, saved.grantId));
const ref = grantBeforeCleanup.credentialSecretRefs.find(r => r.configPath === "ai.credential")!;
const [secretBefore] = await db.select().from(companySecrets).where(eq(companySecrets.id, ref.secretId));
// A newer last_refresh would win the freshness merge if the grant stayed
// active. The revoked grant must discard the write-back before that merge
// decides anything.
await writeFile(path.join(String(run.config.env.CODEX_HOME), "auth.json"), auth("revoked-run", 11));
await db.update(connectionGrants).set({ status: "revoked" }).where(eq(connectionGrants.id, saved.grantId));
await run.cleanup();
const [secretAfter] = await db.select().from(companySecrets).where(eq(companySecrets.id, ref.secretId));
// service.select rejects a revoked grant, so it cannot read the stored
// credential here. Compare the stored secret version directly instead.
expect(secretAfter.latestVersion).toBe(secretBefore.latestVersion);
});
it("does not let a stale write-back overwrite an authorized secret rotation that commits while cleanup waits on the credential lock", async () => {
const userId = "credential-lock-race-user";
await db.insert(companyMemberships).values({ companyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" });
const auth = (marker: string, hour: number) => JSON.stringify({ tokens: { account_id: "fixture-account", id_token: `id-${marker}`, access_token: `access-${marker}`, refresh_token: `refresh-${marker}` }, last_refresh: `2026-09-10T${hour}:00:00Z` });
const saved = await service.save(companyId, userId, { provider: "openai", method: "subscription", ownership: "personal", name: "Credential lock race fixture", loginSessionId: "fixture", allAgents: true, agentIds: [] }, auth("start", 10));
const runInput = { ...input, adapterType: "codex_local", responsibleUserId: userId, binding: { provider: "openai", method: "subscription", mode: "responsible_user" } as const, config: { model: "same-model" } };
const run = await prepareManagedAiRuntime(db, runInput);
// The run's own refresh looks newer than the value it started with, but
// it must lose to a company-authorized rotation that commits while
// cleanup is still waiting on the credential secret's row lock.
await writeFile(path.join(String(run.config.env.CODEX_HOME), "auth.json"), auth("run-refresh", 11));
const [grant] = await db.select().from(connectionGrants).where(eq(connectionGrants.id, saved.grantId));
const ref = grant.credentialSecretRefs.find(r => r.configPath === "ai.credential")!;
let holdAcquired!: () => void;
const holdAcquiredPromise = new Promise<void>(resolve => { holdAcquired = resolve; });
let releaseHold!: () => void;
const holdReleased = new Promise<void>(resolve => { releaseHold = resolve; });
// An authorized rotation writes the new credential inside its own open
// transaction, so it still holds the secret row's lock when signaled.
const holder = db.transaction(async tx => {
await secretService(tx).rotate(ref.secretId, { value: auth("authorized-rotation", 12) }, { userId });
holdAcquired();
await holdReleased;
});
await holdAcquiredPromise;
const cleanupPromise = run.cleanup();
releaseHold();
await holder;
await cleanupPromise;
const stored = await service.credential(await service.select({ ...runInput, userId }));
expect(stored).toBe(auth("authorized-rotation", 12));
});
it("enforces the shared transport discriminator and existing harness compatibility", () => {
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "ai", transport: "mcp_remote" }).success).toBe(false);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "tool", transport: "runtime_auth" }).success).toBe(false);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "rest_api", config: { provider: "agentmail" } }).success).toBe(true);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "rest_api", config: { provider: "slack" } }).success).toBe(false);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "runtime_auth", config: { provider: "agentmail" } }).success).toBe(false);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "voice", config: { provider: "speko" } }).success).toBe(true);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "voice", config: { provider: "slack" } }).success).toBe(false);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "channel", transport: "chat_sdk", config: { provider: "speko" } }).success).toBe(false);
expect(connectionPurposeTransportSchema.safeParse({ connectionPurpose: "tool", transport: "voice", config: { provider: "speko" } }).success).toBe(false);
expect(aiConnectionBindingSchema.safeParse({ provider: "anthropic", mode: "responsible_user" }).success).toBe(false);
expect(aiConnectionBindingSchema.safeParse({ provider: "anthropic", mode: "shared", connectionId: randomUUID(), grantId: randomUUID() }).success).toBe(false);
expect(isAiConnectionCompatible({ provider: "anthropic", method: "api_key", mode: "responsible_user" }, "paperclip_runner", "same-model", "acpx", "claude")).toBe(true);
expect(isAiConnectionCompatible(binding, "paperclip_runner", "same-model", "acpx", "claude")).toBe(true);
expect(isAiConnectionCompatible(binding, "paperclip_runner", "same-model", "acpx", "codex")).toBe(false);
expect(isAiConnectionCompatible({ provider: "openrouter", method: "api_key" }, "opencode_local", "anthropic/model")).toBe(false);
});
it("does not let a forged delegation bypass human access or accept an expired subscription attempt", async () => {
const selected = await service.select({ ...input, userId: "alice" });
const otherAgent = randomUUID();
await db.insert(agents).values({ id: otherAgent, companyId, name: "Other" });
await db.insert(connectionGrantDelegations).values({ companyId, grantId: selected.grant.id, agentId: otherAgent, createdByUserId: "bob" });
await expect(service.select({ ...input, agentId: otherAgent, userId: "bob", binding: { ...binding, method: selected.attribution.method, mode: "delegated", connectionId: selected.connection.id, grantId: selected.grant.id } })).rejects.toThrow("not shared");
const [environment] = await db.select().from(environments).limit(1);
const sessionId = randomUUID();
const intent = { provider: "anthropic", method: "subscription", ownership: "personal", name: "Expired", agentIds: [], allAgents: true } as const;
await db.insert(adapterAuthSessions).values({ companyId, environmentId: environment.id, adapterType: "claude_local", startedByUserId: "alice", publicSessionId: sessionId, status: "submitting", aiConnection: { ...intent, agentIds: [] }, expiresAt: new Date(Date.now() - 1000) });
await expect(completeSetupTokenAiConnection(db, { scope: { companyId, ownerUserId: "alice", environmentId: environment.id, adapterType: "claude_local", aiConnection: { ...intent, agentIds: [] } }, token: "fixture-never-save", sessionId })).rejects.toThrow("no longer active");
expect((await service.list(companyId, "alice")).some(account => account.name === "Expired")).toBe(false);
});
it("authorizes account creation, reconnect and defaults at the HTTP boundary before provider calls", async () => {
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
const userId = String(req.headers["x-test-user"] ?? "alice");
const role = req.headers["x-test-role"] === "viewer" ? "viewer" : "member";
req.actor = { type: "board", source: "session", userId, companyIds: [companyId], memberships: [{ companyId, membershipRole: role, status: "active" }] };
next();
});
app.use("/api", aiConnectionRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const personal = await service.select({ ...input, userId: "alice" });
const base = `/api/companies/${companyId}/ai-connections`;
expect((await request(app).get(`/api/companies/${otherCompanyId}/ai-connections`)).status).toBe(403);
expect((await request(app).put(`${base}/default`).set("x-test-user", "bob").send({ grantId: personal.grant.id })).status).toBe(403);
expect((await request(app).put(`${base}/default`).set("x-test-role", "viewer").send({ grantId: personal.grant.id })).status).toBe(403);
const payload = { provider: "anthropic", method: "api_key", name: "Fixture", ownership: "personal", apiKey: "fixture", allAgents: false, agentIds: [] };
const network = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("must not reach provider"));
try {
expect((await request(app).post(base).set("x-test-role", "viewer").send(payload)).status).toBe(403);
expect((await request(app).post(base).send({ ...payload, ownership: "shared" })).status).toBe(403);
expect((await request(app).post(base).set("x-test-user", "bob").send({ ...payload, connectionId: personal.connection.id })).status).toBe(403);
expect(network).not.toHaveBeenCalled();
} finally { network.mockRestore(); }
});
it.each(["anthropic", "openai"] as const)("reports a missing %s browser process after a server restart", async provider => {
const [environment] = await db.select().from(environments).where(eq(environments.driver, "local")).limit(1);
const id = randomUUID();
const owner = `restart-${provider}`;
const intent = { provider, method: "subscription", ownership: "personal", name: "Interrupted sign-in", allAgents: false, agentIds: [] } as const;
await db.insert(adapterAuthSessions).values({ id, publicSessionId: id, companyId, environmentId: environment.id, startedByUserId: owner, adapterType: provider === "anthropic" ? "claude_local" : "codex_local", aiConnection: { ...intent, agentIds: [] }, connectionMethod: "local_subscription", status: "waiting_for_user", expiresAt: new Date(Date.now() + 60_000) });
const reader = vi.spyOn(localCredentials, "readVerifiedLocalAiCredentialDetails").mockRejectedValue(new Error("No credential yet"));
try {
const login = localAiLoginService(db);
await expect(login.check(companyId, owner, { ...intent, agentIds: [] }, id)).resolves.toEqual({ status: "sign_in_required", error: "The server restarted during sign-in. Start sign-in again." });
await expect(login.check(companyId, "bob", { ...intent, agentIds: [] }, id)).rejects.toThrow("not found");
reader.mockResolvedValue({ credential: "completed-before-restart", accountLabel: undefined });
await expect(login.check(companyId, owner, { ...intent, agentIds: [] }, id)).resolves.toEqual({ status: "ready" });
} finally { reader.mockRestore(); await db.delete(adapterAuthSessions).where(eq(adapterAuthSessions.id, id)); }
});
it("requires an owned browser sign-in attempt for local subscriptions", async () => {
const reader = vi.spyOn(localCredentials, "readVerifiedLocalAiCredentialDetails").mockResolvedValue({ credential: "fixture-local-token", accountLabel: undefined });
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
req.actor = { type: "board", source: req.headers["x-local"] === "yes" ? "local_implicit" : "session", userId: String(req.headers["x-test-user"] ?? "alice"), companyIds: [companyId], memberships: [{ companyId, status: "active", membershipRole: "member" }] };
next();
});
app.use("/api", aiConnectionRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const url = `/api/companies/${companyId}/ai-connections/local`;
const payload = { provider: "anthropic", method: "subscription", name: "Local account test", ownership: "personal", agentIds: [agentId], allAgents: false };
try {
expect((await request(app).post(url).send(payload)).status).toBe(403);
expect(reader).not.toHaveBeenCalled();
expect((await request(app).post(`${url}/check`).send(payload)).status).toBe(403);
expect(reader).not.toHaveBeenCalled();
const checked = await request(app).post(`${url}/check`).set("x-local", "yes").send(payload);
expect(checked.status).toBe(422);
expect((await service.list(companyId, "alice")).some(c => c.name === payload.name)).toBe(false);
const connected = await request(app).post(url).set("x-local", "yes").send(payload);
expect(connected.status).toBe(422);
expect(reader).not.toHaveBeenCalled();
const codex = { ...payload, provider: "openai", name: "Isolated terminal login" };
const attempts = `${url}/attempts`;
expect((await request(app).post(attempts).send(codex)).status).toBe(403); // This member cannot authorize agentId.
expect((await request(app).post(url).set("x-local", "yes").send(codex)).status).toBe(422);
const prepared = await request(app).post(attempts).set("x-local", "yes").send(codex);
expect(prepared.status).toBe(201);
expect(prepared.body.command).toBeUndefined();
expect((await request(app).post(attempts).set("x-local", "yes").send(codex)).body).toEqual(prepared.body);
expect((await request(app).delete(`${attempts}/${prepared.body.sessionId}`).set("x-test-user", "bob").send()).status).toBe(404);
expect((await request(app).delete(`${attempts}/${prepared.body.sessionId}`).set("x-local", "yes").send()).status).toBe(200);
expect((await request(app).post(url).set("x-local", "yes").send({ ...codex, localSessionId: prepared.body.sessionId })).status).toBe(422);
} finally { reader.mockRestore(); }
});
it.each(["anthropic", "openai"] as const)("blocks server-host %s login on a public deployment without a trusted host", async provider => {
const reader = vi.spyOn(localCredentials, "readVerifiedLocalAiCredentialDetails");
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
req.actor = { type: "board", source: "session", userId: "alice", companyIds: [companyId], memberships: [{ companyId, status: "active", membershipRole: "member" }] };
next();
});
app.use("/api", aiConnectionRoutes(db, { deploymentMode: "authenticated", deploymentExposure: "public", trustedLocalStdioRuntimeHost: "" }));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const base = `/api/companies/${companyId}/ai-connections/local`;
const intent = { provider, method: "subscription", ownership: "personal", name: "Hosted account", allAgents: false, agentIds: [] };
try {
for (const endpoint of [base, `${base}/attempts`, `${base}/check`]) {
const result = await request(app).post(endpoint).send(intent);
expect(result.status).toBe(422);
expect(result.body.error).toContain("unavailable on this hosted instance");
}
expect(reader).not.toHaveBeenCalled();
} finally { reader.mockRestore(); }
});
it.each(["anthropic", "openai"] as const)("lets authenticated users connect only their own isolated %s login", async provider => {
const owner = `self-hosted-${provider}`;
await db.insert(companyMemberships).values({ companyId, principalId: owner, principalType: "user", status: "active", membershipRole: "member" });
const reader = vi.spyOn(localCredentials, "readVerifiedLocalAiCredentialDetails").mockResolvedValue({ credential: "isolated-fixture-token", accountLabel: undefined });
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
req.actor = { type: "board", source: "session", userId: String(req.headers["x-test-user"] ?? owner), companyIds: [companyId], memberships: [{ companyId, status: "active", membershipRole: req.headers["x-viewer"] ? "viewer" : "member" }] };
next();
});
app.use("/api", aiConnectionRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const base = `/api/companies/${companyId}/ai-connections/local`;
const intent = { provider, method: "subscription", ownership: "personal", name: `Self-hosted ${provider}`, allAgents: false, agentIds: [] };
try {
expect((await request(app).post(`${base}/attempts`).set("x-viewer", "yes").send(intent)).status).toBe(403);
const started = await request(app).post(`${base}/attempts`).send(intent);
expect(started.status).toBe(201);
expect(started.headers["cache-control"]).toBe("no-store");
expect(started.body.command).toBeUndefined();
expect((await request(app).post(`${base}/attempts`).send(intent)).body).toEqual(started.body);
await db.update(adapterAuthSessions).set({ publicSessionId: randomUUID() }).where(eq(adapterAuthSessions.id, started.body.sessionId));
const input = { ...intent, localSessionId: started.body.sessionId };
for (const endpoint of [base, `${base}/check`]) {
expect((await request(app).post(endpoint).set("x-test-user", "bob").send(input)).status).toBe(404);
expect((await request(app).post(endpoint.replace(companyId, otherCompanyId)).send(input)).status).toBe(403);
}
if (provider === "anthropic") {
const codeUrl = `${base}/attempts/${started.body.sessionId}/code`;
expect((await request(app).post(codeUrl).set("x-test-user", "bob").send({ browserCode: "fixture-code" })).status).toBe(404);
expect((await request(app).post(codeUrl).send({ browserCode: "fixture-code" })).status).toBe(422);
}
expect(reader).not.toHaveBeenCalled();
const checked = await request(app).post(`${base}/check`).send(input);
expect(checked.body).toEqual({ status: "ready" });
expect(reader).toHaveBeenLastCalledWith(provider, path.join(home, "instances/ai-connection-fixture/ai-local-logins", started.body.sessionId));
const saved = await request(app).post(base).send(input);
expect(saved.status).toBe(201);
expect((await request(app).post(base).send(input)).body).toEqual(saved.body);
expect(JSON.stringify(saved.body)).not.toContain("isolated-fixture-token");
expect((await service.list(companyId, owner)).filter(c => c.name === intent.name)).toHaveLength(1);
} finally { reader.mockRestore(); }
});
it("rejects invalid credentials without exposing the provider response", async () => {
const request = vi.fn().mockResolvedValue(new Response("secret-provider-body", { status: 401 }));
await expect(validateAiApiKey("anthropic", "fixture", request)).rejects.toThrow("rejected");
expect(request.mock.calls[0][1].redirect).toBe("error");
});
it("uses the authenticated responsible user for agent-originated configuration and tests", async () => {
const req = { actor: { type: "agent", agentId, onBehalfOfUserId: "alice" } } as express.Request;
const selected = await service.select({ ...input, userId: responsibleUserForAiRequest(req) });
expect(selected.grant.subjectUserId).toBe("alice");
req.actor.onBehalfOfUserId = undefined;
expect(responsibleUserForAiRequest(req)).toBeNull();
await expect(service.select({ ...input, userId: responsibleUserForAiRequest(req) })).rejects.toThrow();
});
it("protects active-run attribution with the connection human audience", async () => {
const account = await create("alice", "Private run attribution");
const runId = randomUUID();
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, status: "running", contextSnapshot: { aiConnection: { connectionId: account.connectionId, grantId: account.grantId } } });
const app = express();
app.use((req, _res, next) => {
req.actor = { type: "board", source: "session", userId: String(req.headers["x-test-user"] ?? "alice"), companyIds: [companyId] };
next();
});
app.use("/api", aiConnectionRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const url = `/api/companies/${companyId}/ai-connections/${account.connectionId}/active-runs`;
const own = await request(app).get(url);
expect(own.status).toBe(200);
expect(own.headers["cache-control"]).toBe("no-store");
expect(own.body).toEqual([expect.objectContaining({ id: runId, agentId })]);
expect((await request(app).get(url).set("x-test-user", "bob")).status).toBe(404);
await db.update(connectionGrants).set({ kind: "organization", subjectUserId: null }).where(eq(connectionGrants.id, account.grantId));
await db.insert(connectionGrantMembers).values({ companyId, grantId: account.grantId, subjectType: "user", subjectId: "alice" });
expect((await request(app).get(url).set("x-test-user", "bob")).status).toBe(404);
await db.insert(connectionGrantMembers).values({ companyId, grantId: account.grantId, subjectType: "user", subjectId: "bob" });
expect((await request(app).get(url).set("x-test-user", "bob")).body).toEqual(own.body);
expect((await request(app).get(url.replace(companyId, otherCompanyId))).status).toBe(403);
});
it("permits new-agent shared installation only for a connection configurator, without bypassing audience", async () => {
const account = await service.save(companyId, "alice", { provider: "anthropic", method: "api_key", ownership: "shared", name: "Restricted shared", apiKey: "fixture", agentIds: [], allAgents: false }, "fixture-restricted");
const selected = { provider: "anthropic", method: "api_key", mode: "shared", ...account } as const;
const futureAgentId = randomUUID();
const req = (userId: string, role = "member") => ({ actor: { type: "board", source: "session", userId, companyIds: [companyId], memberships: [{ companyId, membershipRole: role, status: "active" }] } }) as express.Request;
expect(await canInstallSharedAiConnectionForNewAgent(db, req("alice"), companyId, selected)).toBe(true);
expect(await canInstallSharedAiConnectionForNewAgent(db, req("bob"), companyId, selected)).toBe(false);
expect(await canInstallSharedAiConnectionForNewAgent(db, req("alice", "viewer"), companyId, selected)).toBe(false);
expect(await canInstallSharedAiConnectionForNewAgent(db, { actor: { type: "agent", onBehalfOfUserId: "alice" } } as express.Request, companyId, selected)).toBe(false);
const selectionInput = { ...input, agentId: futureAgentId, userId: "alice", binding: selected };
await expect(service.select(selectionInput)).rejects.toThrow("not permitted for this agent");
const run = await prepareManagedAiRuntime(db, { companyId, agentId: futureAgentId, responsibleUserId: "alice", adapterType: "claude_local", binding: selected, config: { cwd: home, model: "same-model" }, allowUninstalledShared: true });
expect(run.config.model).toBe("same-model");
await run.cleanup();
await db.insert(connectionGrantMembers).values({ companyId, grantId: account.grantId, subjectType: "user", subjectId: "bob" });
await expect(service.select({ ...selectionInput, allowUninstalledShared: true })).rejects.toThrow("not shared with the responsible user");
await db.delete(connectionGrantMembers).where(eq(connectionGrantMembers.grantId, account.grantId));
await db.insert(agents).values({ id: futureAgentId, companyId, name: "New shared agent", adapterType: "claude_local" });
await db.insert(toolConnectionInstalls).values({ companyId, connectionId: account.connectionId, targetType: "agent", targetId: futureAgentId, createdByUserId: "alice" });
expect((await service.select(selectionInput)).grant.id).toBe(account.grantId);
});
it.each([false, true])("validates runner account adoption in the inherited sandbox (inline: %s)", async (inline) => {
const { agentRoutes } = await import("../routes/agents.js");
const { instanceSettingsService } = await import("../services/instance-settings.js");
const targetModule = await import("../services/environment-execution-target.js");
const runtimeModule = await import("../services/environment-runtime.js");
const { requireServerAdapter } = await import("../adapters/index.js");
const settings = instanceSettingsService(db);
const previous = await settings.get();
const [environment] = await db.insert(environments).values({ name: `Adoption sandbox ${inline}`, driver: "sandbox", config: { provider: "daytona" } }).returning();
await settings.update({ defaultEnvironmentId: environment.id });
const id = randomUUID();
await db.insert(agents).values({ id, companyId, name: "Runner adoption", adapterType: "paperclip_runner", adapterConfig: { provider: "codex", model: "gpt-5.6-sol" } });
const account = await service.save(companyId, "alice", { provider: "openai", method: "api_key", ownership: "personal", name: "Runner adoption account", apiKey: "fixture-adoption-key", agentIds: inline ? [] : [id], allAgents: false }, "fixture-adoption-key");
await service.setDefault(companyId, "alice", account.grantId);
const tryDeliver = vi.fn(async (interactionId: string) => {
// Dispatch observes committed adoption, never a partially saved choice.
expect((await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, interactionId)))[0].status).toBe("accepted");
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].runtimeConfig.aiConnection).toMatchObject({ mode: "responsible_user" });
});
const deliveryModule = await import("../services/connection-intent-delivery.js");
const delivery = vi.spyOn(deliveryModule, "connectionIntentDeliveryService").mockReturnValue({ tryDeliver } as never);
let interactionId: string | undefined;
if (inline) {
const issueId = randomUUID();
const runId = randomUUID();
await db.insert(issues).values({ id: issueId, companyId, title: "Atomic adoption", status: "in_progress", assigneeAgentId: id });
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId: id, status: "failed", errorCode: "acpx_auth_required", responsibleUserId: "alice", contextSnapshot: { issueId } });
interactionId = (await connectionIntentService(db).requestForRunAuthFailure(runId))!.interactionId!;
}
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace", providerKey: "daytona", runner: { execute: vi.fn(async () => ({ exitCode: 0, stdout: "", stderr: "", timedOut: false })) } } as const;
const resolveTarget = vi.spyOn(targetModule, "resolveEnvironmentExecutionTarget").mockResolvedValue(target);
const release = vi.fn(async () => undefined);
const acquire = vi.fn(async () => ({ lease: { id: randomUUID(), provider: "daytona", providerLeaseId: "test-sandbox", metadata: {} }, leaseContext: {} }));
const runtime = vi.spyOn(runtimeModule, "environmentRuntimeService").mockReturnValue({ acquireRunLease: acquire, realizeWorkspace: vi.fn(async () => ({ cwd: "/workspace" })), getDriver: () => ({ releaseRunLease: release }) } as any);
const probe = vi.spyOn(requireServerAdapter("paperclip_runner"), "testEnvironment").mockImplementation(async context => ({ adapterType: "paperclip_runner", status: context.executionTarget ? "pass" : "fail", testedAt: new Date().toISOString(), checks: [{ code: context.executionTarget ? "codex_hello_probe_passed" : "host_probe_failed", level: context.executionTarget ? "info" : "error", message: "fixture" }] }));
const app = express();
app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] }; next(); });
app.use("/api", agentRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const selected = { provider: "openai", method: "api_key", mode: "responsible_user" } as const;
const providerRequest = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(null, { status: 200 }));
const adopt = () => inline
? request(app).post(`/api/agents/${id}/connection-intents/${interactionId}/adopt`).send({ connectionId: account.connectionId })
: request(app).patch(`/api/agents/${id}`).send({ runtimeConfig: { aiConnection: selected } });
try {
// Target resolution can return only warning checks. Adoption still must
// fail closed, rather than quietly running the probe on the server host.
resolveTarget.mockResolvedValueOnce(null);
const unavailable = await adopt();
expect(unavailable.status, JSON.stringify(unavailable.body)).toBe(422);
expect(tryDeliver).not.toHaveBeenCalled();
expect(probe).not.toHaveBeenCalled();
expect(providerRequest).not.toHaveBeenCalled();
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].runtimeConfig.aiConnection).toBeUndefined();
if (inline) {
const assertUnchanged = async () => {
expect((await db.select().from(agents).where(eq(agents.id, id)))[0].runtimeConfig.aiConnection).toBeUndefined();
expect(await db.select().from(toolConnectionInstalls).where(eq(toolConnectionInstalls.connectionId, account.connectionId))).toEqual([]);
expect((await db.select().from(issueThreadInteractions).where(eq(issueThreadInteractions.id, interactionId!)))[0].status).toBe("pending");
};
await assertUnchanged();
// Force the last write to fail after binding and install changes.
await db.execute(sql.raw(`CREATE FUNCTION reject_atomic_adoption() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.id = '${interactionId}'::uuid THEN RAISE EXCEPTION 'injected resolution failure'; END IF; RETURN NEW; END $$`));
await db.execute(sql.raw("CREATE TRIGGER reject_atomic_adoption BEFORE UPDATE ON issue_thread_interactions FOR EACH ROW EXECUTE FUNCTION reject_atomic_adoption()"));
try {
const failed = await adopt();
expect(failed.status).toBe(500);
expect(tryDeliver).not.toHaveBeenCalled();
await assertUnchanged();
} finally {
await db.execute(sql.raw("DROP TRIGGER reject_atomic_adoption ON issue_thread_interactions"));
await db.execute(sql.raw("DROP FUNCTION reject_atomic_adoption()"));
}
}
const saved = await adopt();
expect(saved.status, JSON.stringify(saved.body)).toBe(200);
const savedAgent = (await db.select().from(agents).where(eq(agents.id, id)))[0];
expect(savedAgent.defaultEnvironmentId).toBeNull();
expect(savedAgent.runtimeConfig.aiConnection).toEqual(inline ? { ...selected, method: "subscription" } : selected);
if (inline) {
expect(saved.body.status).toBe("accepted");
expect(await db.select().from(toolConnectionInstalls).where(eq(toolConnectionInstalls.connectionId, account.connectionId))).toHaveLength(1);
expect((await adopt()).status).toBe(200);
expect(tryDeliver).toHaveBeenCalledTimes(2);
expect(tryDeliver).toHaveBeenCalledWith(interactionId);
}
expect(acquire).toHaveBeenCalledWith(expect.objectContaining({ companyId, environment: expect.objectContaining({ id: environment.id }) }));
expect(probe).toHaveBeenCalledWith(expect.objectContaining({ executionTarget: target, config: expect.objectContaining({ provider: "codex", model: "gpt-5.6-sol", managedAiConnection: expect.any(Object) }) }));
expect(providerRequest).toHaveBeenCalledWith("https://api.openai.com/v1/models", expect.objectContaining({
headers: { Authorization: "Bearer fixture-adoption-key" },
redirect: "error",
}));
expect(release).toHaveBeenCalledTimes(inline ? 3 : 2);
expect(JSON.stringify(saved.body)).not.toContain("fixture-adoption-key");
} finally {
delivery.mockRestore(); providerRequest.mockRestore(); probe.mockRestore(); runtime.mockRestore(); resolveTarget.mockRestore();
await settings.update({ defaultEnvironmentId: previous.defaultEnvironmentId });
}
});
it("saves a restricted shared connection during setup and installs it only when setup finishes", async () => {
const { requireServerAdapter } = await import("../adapters/index.js");
const setupId = randomUUID();
const account = await service.save(companyId, "alice", { provider: "anthropic", method: "api_key", ownership: "shared", name: "Shared setup", apiKey: "fixture", agentIds: [], allAgents: false }, "fixture-setup-key");
const selected = { provider: "anthropic", method: "api_key", mode: "shared", ...account } as const;
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] }; next(); });
app.use("/api", agentRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const initial = await request(app).post(`/api/companies/${companyId}/agents`).send({ setupId, name: "Shared setup agent", adapterType: "claude_local", runner: "legacy" });
expect(initial.status, JSON.stringify(initial.body)).toBe(201);
const probe = vi.spyOn(requireServerAdapter("claude_local"), "testEnvironment");
const providerRequest = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(null, { status: 200 }));
try {
const saved = await request(app).patch(`/api/agents/${setupId}`).send({ runtimeConfig: { aiConnection: selected } });
expect(saved.status, JSON.stringify(saved.body)).toBe(200);
expect(saved.body).toMatchObject({ pauseReason: "setup", runtimeConfig: { aiConnection: selected } });
expect(probe).not.toHaveBeenCalled();
probe.mockResolvedValue({ adapterType: "claude_local", status: "pass", checks: [], testedAt: new Date().toISOString() });
const tested = await request(app).post(`/api/companies/${companyId}/adapters/claude_local/test-environment`).send({
agentId: setupId, runner: "legacy", adapterConfig: {}, aiConnection: selected,
});
expect(tested.status, JSON.stringify(tested.body)).toBe(200);
expect(probe).toHaveBeenCalledTimes(1);
expect(await db.select().from(toolConnectionInstalls).where(eq(toolConnectionInstalls.targetId, setupId))).toHaveLength(0);
const finished = await request(app).post(`/api/companies/${companyId}/agent-hires`).send({
setupAgentId: setupId, name: "Shared setup agent", adapterType: "claude_local", runner: "legacy", runtimeConfig: { aiConnection: selected },
});
expect(finished.status, JSON.stringify(finished.body)).toBe(201);
expect(finished.body.agent.id).toBe(setupId);
expect(await db.select().from(toolConnectionInstalls).where(and(eq(toolConnectionInstalls.connectionId, account.connectionId), eq(toolConnectionInstalls.targetId, setupId)))).toHaveLength(1);
} finally { probe.mockRestore(); providerRequest.mockRestore(); }
});
it.each([["responsible_user", "codex_local"], ["shared", "codex_local"], ["responsible_user", "claude_local"]] as const)("drops a superseded saved %s connection when %s setup changes authentication", async (mode, adapterType) => {
const setupId = randomUUID();
await db.insert(agents).values({ id: setupId, companyId, name: "Switch provider", createdByUserId: "alice",
adapterType, status: "paused", lifecycleState: "paused", pauseReason: "setup", lifecycleHolds: ["setup"],
runtimeConfig: { aiConnection: mode === "shared"
? { provider: "openai", method: "api_key", mode, connectionId: randomUUID(), grantId: randomUUID() }
: { provider: adapterType === "claude_local" ? "anthropic" : "openai", method: "subscription", mode } },
});
const app = express(); app.use(express.json());
app.use((req, _res, next) => { req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] }; next(); });
app.use("/api", agentRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
const saved = await request(app).patch(`/api/agents/${setupId}`).send({ adapterType: "claude_local", runner: "legacy",
adapterConfig: {}, runtimeConfig: { heartbeat: { enabled: false } },
});
expect(saved.status, JSON.stringify(saved.body)).toBe(200);
expect(saved.body.runtimeConfig).not.toHaveProperty("aiConnection");
const [row] = await db.select().from(agents).where(eq(agents.id, setupId));
expect(row.runtimeConfig).not.toHaveProperty("aiConnection");
});
it("creates and hires agents with an authorized restricted shared connection", async () => {
const { agentRoutes } = await import("../routes/agents.js");
await db.update(companies).set({ requireBoardApprovalForNewAgents: false }).where(eq(companies.id, companyId));
const account = await service.save(companyId, "alice", { provider: "anthropic", method: "api_key", ownership: "shared", name: "Shared creation routes", apiKey: "fixture", agentIds: [], allAgents: false }, "fixture-create-routes");
const selected = { ...binding, mode: "shared", ...account } as const;
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
req.actor = { type: "board", source: "local_implicit", userId: "alice", companyIds: [companyId] };
next();
});
app.use("/api", agentRoutes(db));
app.use((error: { status?: number; message: string }, _req: express.Request, res: express.Response, _next: express.NextFunction) => { res.status(error.status ?? 500).json({ error: error.message }); });
for (const endpoint of ["agents", "agent-hires"]) {
const response = await request(app).post(`/api/companies/${companyId}/${endpoint}`).send({ name: `Shared ${endpoint}`, role: "general", adapterType: "claude_local", adapterConfig: { model: "claude-sonnet-4-6" }, runtimeConfig: { aiConnection: selected } });
expect(response.status, JSON.stringify(response.body)).toBe(201);
const agent = endpoint === "agents" ? response.body : response.body.agent;
expect(agent.adapterConfig.model).toBe("claude-sonnet-4-6");
expect(agent.runtimeConfig.aiConnection).toEqual(selected);
const installs = await db.select().from(toolConnectionInstalls).where(and(eq(toolConnectionInstalls.connectionId, account.connectionId), eq(toolConnectionInstalls.targetId, agent.id)));
expect(installs).toHaveLength(1);
expect((await service.select({ ...input, agentId: agent.id, userId: "alice", binding: selected })).grant.id).toBe(account.grantId);
}
// A database failure between the two inserts must roll back the agent too.
await db.execute(sql`CREATE FUNCTION reject_test_ai_install() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'fixture install failure'; END $$`);
await db.execute(sql`CREATE TRIGGER reject_test_ai_install BEFORE INSERT ON tool_connection_installs FOR EACH ROW EXECUTE FUNCTION reject_test_ai_install()`);
try {
for (const endpoint of ["agents", "agent-hires"]) {
const name = `Rollback ${endpoint}`;
const response = await request(app).post(`/api/companies/${companyId}/${endpoint}`).send({ name, role: "general", adapterType: "claude_local", adapterConfig: { model: "claude-sonnet-4-6" }, runtimeConfig: { aiConnection: selected } });
expect(response.status).toBe(500);
expect(await db.select().from(agents).where(and(eq(agents.companyId, companyId), eq(agents.name, name)))).toEqual([]);
}
} finally {
await db.execute(sql`DROP TRIGGER reject_test_ai_install ON tool_connection_installs`);
await db.execute(sql`DROP FUNCTION reject_test_ai_install()`);
}
}, 30000);
});
describe("AI connection recovery delivery", () => {
it.each(["ai_connection_unavailable", "secret_binding_missing"].flatMap(reason =>
["restored", "newer failure", "different blocker", "revoked again", "closed task"].map(scenario => [reason, scenario]),
))(
"continues only the repaired %s source failure: %s", async (reason, scenario) => {
const userId = `recovery-${randomUUID()}`;
const recoveringAgentId = randomUUID();
const issueId = randomUUID();
const failedRunId = randomUUID();
await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member" });
await db.insert(agents).values({ id: recoveringAgentId, companyId, name: "Recovery agent", status: "active", adapterType: "claude_local", runtimeConfig: { aiConnection: binding } });
await db.insert(issues).values({ id: issueId, companyId, title: "Restore selected account", status: "in_progress", assigneeAgentId: recoveringAgentId });
await db.insert(heartbeatRuns).values({ id: failedRunId, companyId, agentId: recoveringAgentId, status: "running", responsibleUserId: userId, contextSnapshot: { issueId } });
const intents = connectionIntentService(db);
const pending = await intents.request({ sub: recoveringAgentId, company_id: companyId, run_id: failedRunId, responsible_user_id: userId }, "anthropic", { purpose: "ai" });
const account = await create(userId, `Recovered ${scenario}`);
expect((await intents.setupOptions(pending.interactionId!)).existingConnections.map(connection => connection.id)).toEqual([account.connectionId]);
await db.update(heartbeatRuns).set({ status: "failed", errorCode: "configuration_incomplete", resultJson: { configurationIncomplete: { reason,
...(reason === "secret_binding_missing" ? { agentId: recoveringAgentId, missingBindings: [{ bindingType: "user_secret_ref", consumerType: "agent", consumerId: recoveringAgentId, envKey: "ANTHROPIC_API_KEY", configPath: "env.ANTHROPIC_API_KEY", errorCode: "user_secret_missing" }] } : {}),
} } }).where(eq(heartbeatRuns.id, failedRunId));
await db.update(issues).set({ status: "blocked" }).where(eq(issues.id, issueId));
await issueRecoveryActionService(db).upsertSourceScoped({ companyId, sourceIssueId: issueId, kind: "configuration_validation", cause: "configuration_incomplete", fingerprint: `ai:${issueId}`, nextAction: "Reconnect", ownerType: "board", evidence: { latestRunId: failedRunId } });
await intents.complete(pending.interactionId!, account.connectionId, userId);
if (scenario === "newer failure") await db.insert(heartbeatRuns).values({ companyId, agentId: recoveringAgentId, status: "failed", contextSnapshot: { issueId }, createdAt: new Date(Date.now() + 1000) });
if (scenario === "different blocker") await db.update(issueRecoveryActions).set({ cause: "workspace_validation_failed" }).where(eq(issueRecoveryActions.sourceIssueId, issueId));
if (scenario === "closed task") await db.update(issues).set({ status: "done" }).where(eq(issues.id, issueId));
if (scenario === "revoked again") {
await toolAccessService(db).revokeConnectionGrant(account.connectionId, account.grantId, { actorType: "user", actorId: userId });
const repairOptions = await intents.setupOptions(pending.interactionId!);
expect(repairOptions.existingConnections).toEqual([]);
expect(repairOptions.aiRepair).toMatchObject({ canReconnect: true, connection: { id: account.connectionId, grantId: account.grantId, isDefault: true, status: "revoked" } });
}
const wakeup = vi.fn(async (_agentId, opts) => {
await db.insert(agentWakeupRequests).values({ companyId, agentId: recoveringAgentId, source: "automation", status: "queued", idempotencyKey: opts.idempotencyKey });
return null;
});
const delivery = connectionIntentDeliveryService(db, { wakeup } as never);
await delivery.deliver(pending.interactionId!);
await delivery.deliver(pending.interactionId!);
const [issue] = await db.select().from(issues).where(eq(issues.id, issueId));
if (scenario === "restored") {
expect(issue.status).toBe("in_progress");
expect(wakeup).toHaveBeenCalledTimes(1);
expect(wakeup).toHaveBeenCalledWith(recoveringAgentId, expect.objectContaining({ contextSnapshot: expect.objectContaining({ forceFreshSession: true }) }));
expect(await issueRecoveryActionService(db).getActiveForIssue(companyId, issueId)).toBeNull();
const [receipt] = await db.select().from(connectionIntentDeliveries).where(eq(connectionIntentDeliveries.interactionId, pending.interactionId!));
expect(receipt.deliveredAt).not.toBeNull();
} else {
expect(wakeup).not.toHaveBeenCalled();
expect(issue.status).toBe(scenario === "closed task" ? "done" : "blocked");
}
}, 30000,
);
});
function deferredSignal() {
let resolve!: () => void;
const promise = new Promise<void>(done => { resolve = done; });
return { promise, resolve };
}