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, type Db, 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, isAiConnectionIncompatible, 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>; let db: ReturnType; let home: string; const companyId = randomUUID(); const otherCompanyId = randomUUID(); const agentId = randomUUID(); let service: ReturnType; 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({ 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" as const, 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, 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) => { rejectRecoveryWrite(args[0]); return realOpen(...args); }); const mkdir = vi.spyOn(fs, "mkdir").mockImplementation(async (...args: Parameters) => { 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> | 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 | 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> | 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> | 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 | undefined; let lock: ReturnType | 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((_resolve, reject) => { rejectRetirement = reject; }); void retired.catch(() => undefined); let original: Parameters[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> | 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(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> | undefined; let cleaning: Promise | undefined; let lock: ReturnType | 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 | undefined; let lock: ReturnType | 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> | undefined; let lock: ReturnType | 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: "delegated", 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: "delegated", 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"], { actorType: "user", actorId: "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"], { actorType: "user", actorId: "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], routing: { ...routing, models: [...routing.models] } }, "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", provider: "local_encrypted", }, { 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; const apiEnvValues = apiRun.config.env as Record; 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 as Record).model).toBe(bot.config.model); expect((apiRun.config as Record).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[2]; try { execute.mockResolvedValue({ exitCode: 42, stdout: "", stderr: "", signal: null, timedOut: false } as Awaited>); 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>); await expect(assertManagedAiProjectAuth({}, "openai", target)).resolves.toBeUndefined(); await expect(assertManagedAiProjectAuth({ args: ["--api-key=override"] }, "xai", target)).rejects.toThrow("overrides"); } finally { execute.mockRestore(); } }); it("reports only a found conflict as configuration, naming the files to fix", async () => { const execute = vi.spyOn(executionTarget, "runAdapterExecutionTargetProcess"); const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace/project" } as Parameters[2]; const outcome = (exitCode: number | null, timedOut = false) => ({ exitCode, stdout: "", stderr: "", signal: null, timedOut }) as Awaited>; try { execute.mockResolvedValue(outcome(42)); const conflict = await assertManagedAiProjectAuth({}, "anthropic", target).catch((error: unknown) => error); expect(isAiConnectionIncompatible(conflict)).toBe(true); expect(String((conflict as Error).message)).toContain(".claude/settings.json"); // A check that could not complete is not proof of a configuration conflict. for (const failed of [outcome(43), outcome(null, true), outcome(127)]) { execute.mockResolvedValue(failed); const unavailable = await assertManagedAiProjectAuth({}, "anthropic", target).catch((error: unknown) => error); expect(unavailable).toBeInstanceOf(Error); expect(isAiConnectionIncompatible(unavailable)).toBe(false); } expect(isAiConnectionIncompatible(await assertManagedAiProjectAuth({ args: ["--settings=x"] }, "anthropic", target).catch((error: unknown) => error))).toBe(true); } 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[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"], { actorType: "user", actorId: "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"], { actorType: "user", actorId: "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, [], { actorType: "user", actorId: "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, be = b.config.env as Record; 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 as Record).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("keeps account summaries ordered across sequential scans and repeat migration", async () => { const orderedCompanyId = randomUUID(), userId = "stable-list-owner"; await db.insert(companies).values({ id: orderedCompanyId, name: "Stable account list", issuePrefix: `O${orderedCompanyId.slice(0, 5)}` }); await db.insert(companyMemberships).values({ companyId: orderedCompanyId, principalId: userId, principalType: "user", status: "active", membershipRole: "member" }); const accounts = []; for (const name of ["First account", "Second account", "Third account"]) { accounts.push(await service.save(orderedCompanyId, userId, { provider: "anthropic", method: "api_key", ownership: "personal", name, apiKey: "fixture-stable-list", agentIds: [], allAgents: false }, "fixture-stable-list")); } const expected = accounts.map(({ connectionId: id, grantId }) => ({ id, grantId })) .sort((a, b) => a.id.localeCompare(b.id) || a.grantId.localeCompare(b.grantId)); await db.transaction(async tx => { await tx.execute(sql`SET LOCAL enable_indexscan = off`); await tx.execute(sql`SET LOCAL enable_bitmapscan = off`); await tx.execute(sql`SET LOCAL enable_hashjoin = off`); await tx.execute(sql`SET LOCAL enable_mergejoin = off`); for (const account of [...expected].reverse()) { await tx.update(toolConnections).set({ name: sql`${toolConnections.name}` }).where(eq(toolConnections.id, account.id)); await tx.update(connectionGrants).set({ status: sql`${connectionGrants.status}` }).where(eq(connectionGrants.id, account.grantId)); } const transactionalService = aiConnectionService(tx as unknown as Db); const before = await transactionalService.list(orderedCompanyId, userId); expect(before.map(({ id, grantId }) => ({ id, grantId }))).toEqual(expected); const migration = await readFile(new URL("../../../packages/db/src/migrations/0276_hard_mandroid.sql", import.meta.url), "utf8"); for (const statement of migration.split("--> statement-breakpoint").filter(value => value.trim())) await tx.execute(sql.raw(statement)); expect(await transactionalService.list(orderedCompanyId, userId)).toEqual(before); }); }); 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(resolve => { holdAcquired = resolve; }); let releaseHold!: () => void; const holdReleased = new Promise(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 as Record).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 legacy or task 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); if (!inline) { const [connection] = await db.select().from(toolConnections).where(eq(toolConnections.id, account.connectionId)); await db.update(toolConnections).set({ healthStatus: "unknown", config: { ...connection.config, aiLegacyAdoption: true } }) .where(eq(toolConnections.id, account.connectionId)); } 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, signal: null, stdout: "", stderr: "", timedOut: false, pid: null, startedAt: null })) } } 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(done => { resolve = done; }); return { promise, resolve }; }