Files
PaperClipAI/server/src/__tests__/ai-connections.test.ts
T
Devin FoleyandPaperclip 49b3804830 fix(connections): load permissions and show AI account identity (#18653)
## Thinking Path

> - Paperclip manages AI agents and their access to saved connections.
> - The Permissions page loads account details and permitted agent
choices.
> - Company-wide AI accounts repeat retained installation checks for
every candidate agent.
> - This makes target checks grow with the square of the roster size.
> - The browser waits without a deadline, and connection names do not
identify the provider login.
> - This pull request batches direct AI checks and bounds loading with
recovery controls.
> - Account details load separately and show a provider email or an
explicit profile-permission limit.

## Linked Issues or Issue Description

**What happened?**

Opening a saved AI account can leave Permissions on “Loading AI
account…” for minutes. The account itself can be connected. The existing
capability batch excludes AI connections, so a company-wide install
repeats checks for every installed agent for every candidate. Saved
names and Paperclip ownership also do not establish which provider login
a credential uses.

**Expected behavior**

Direct AI accounts reuse retained-target checks within one descriptive
request. The page shows loaded account controls or a bounded failure
with Retry. Permissions shows the provider email and organization when
the selected credential permits profile access. An unavailable profile
must not block maintenance or disconnect a working inference credential.
Writes still authorize current permissions.

**Steps to reproduce**

1. Save an AI account in a company with several agents.
2. Install the account company-wide.
3. Open its Permissions page.
4. Delay the account-list or grants response to exercise the loading
failure.

The maintained benchmark reproduces database work with synthetic local
data. Refs #17945, which added batching for non-AI connections. Searches
found no open PR for this AI-account loading fix.

## What Changed

- Include direct AI accounts in the existing board capability batch.
Referenced pool members, delegated actors, complex selectors, and
mutation authorization keep their existing evaluator.
- Add opt-in per-caller GET deadlines to the API client. A timed-out
caller detaches the stuck coalesced request so Retry starts a fresh
fetch. Other callers retain their own cancellation and results.
- Apply a 15-second deadline to AI account lists and grants reads. The
permissions view retries transient failures once and reports other
failures immediately.
- Share the parent Permissions grants query with AI account details.
Keep loaded controls visible after a background error and offer Retry.
- Add a grant-scoped identity GET and OpenAPI contract. The service
checks current membership and credential audience before it reads the
selected credential. It projects only the provider email and
organization; provider IDs, tokens, and raw errors stay private.
- Show account details independently of maintenance controls. Anthropic
profile denial has an explicit message and preserves connected status.
Transient profile failures have a separate Retry action. Observations
are cached for five minutes under the company, connection, grant, and
grant revision.
- Extend real PostgreSQL and authenticated HTTP coverage,
client/component regressions, map guidance, and the existing benchmark's
AI mode.
- Address the Apex file-size finding by moving gallery identity lookup
into the existing app display helper module. `AppDetail.tsx` is 984
lines; lookup behavior is unchanged.

## Verification

Local source checkout on macOS, Node 25.6.1, pnpm 9.15.4, and disposable
PostgreSQL 18.1. Provider calls use fixtures. No hosted deployment or
provider qualification is claimed.

- Passed `pnpm -r typecheck` and `pnpm build`.
- Passed all 87 tests in `connection-management.integration.test.ts` and
all 33 tests in `connection-install-capabilities.test.ts`.
- Passed 196 AI connection, identity-provider, and OpenAPI tests:
`ai-connections.test.ts`, `ai-account-identity.test.ts`, and
`openapi-routes.test.ts`.
- Passed 107 focused UI/client tests: `client.test.ts`,
`ManagedAiConnectionDetails.test.tsx`,
`AiConnectionAccountDetails.test.tsx`, and `AppDetail.test.tsx`.
- After the Apex extraction, passed UI typecheck/build and 106 component
tests across `AppDetail.test.tsx`, `AppNotConnected.test.tsx`,
`ManagedAiConnectionDetails.test.tsx`, and
`AiConnectionAccountDetails.test.tsx`.
- The authenticated HTTP tests compare complete grants responses across
human roles, for tool and AI connections. They verify a real install
mutation succeeds before revocation and fails afterward.
- A company-wide personal AI fixture checks 20 targets instead of 400.
Pool-member permission loss still denies the complete pool description.
- UI states covered: shared cached grants, stuck account/grants fetches,
one automatic retry, terminal failure, manual recovery, authorization
failure, background failure with retained controls, and unmount
cancellation. Client tests also cover stalled response bodies and
independent coalesced callers.
- Identity coverage includes actual bearer-key/JWT reads, private-user
and cross-company denial before provider access, revoked credentials,
provider 401/403/429/500, and grant-change isolation. A real local HTTP
fixture stalls the response body, verifies the shared 15-second
deadline, and keeps unrelated HTTP requests responsive. The test
shortens the native deadline to 100 ms. Profile permission denial leaves
the saved account connected.
- Passed `pnpm check:token-gates`, `node scripts/check-maps.mjs`, `node
scripts/check-module-boundaries.mjs`, and `git diff --check`.
- The full local `pnpm test:run` started with the loading-only change.
All six chat shards passed. It recorded one failure in the unchanged
`heartbeat-retry-scheduling.test.ts` quota-rejection case. That exact
case passed a focused rerun on the final head. The initial failure cause
remains unconfirmed and its local log is retained; this is not a full
local-suite pass. The redundant local run was stopped with SIGINT after
final-head CI completed successfully.
- [Final-head
CI](https://github.com/paperclipai/paperclip/actions/runs/41515556725)
passed on `ead8d276d4a2023614fff54b3e9a858f28c3178b`: typecheck, build,
all 12 server shards, all 3 chat shards, workspace suites, all 9
serialized suites, all 8 end-to-end shards, Runner checks, and the
aggregate verify gate. Security checks passed. Optional Storybook checks
were skipped by workflow policy. GitHub reports the branch mergeable
without conflicts.
- Apex reviewed head `ead8d276d4a2023614fff54b3e9a858f28c3178b` at 5/5,
confirmed the extraction resolves its earlier file-size finding, and has
no unresolved review threads.

The existing benchmark ran for the loading-only change now recorded in
commit `5f725b36fa52002545241a5a8b7a10602f0cc0ce`, with `--purpose=ai
--baseline-ref=026fd9e3452`. The identity follow-up leaves that batch
source unchanged. Two warmups and five alternating samples exercise each
of eight fixture shapes. Complete positional capabilities, reasons, and
operations match the baseline in every sample.

| AI fixture | SQL calls before → after | Target reads before → after |
| --- | ---: | ---: |
| 1 agent, company install | 9 → 9 | 1 → 1 |
| 32 agents, company install | 5,248 → 164 | 1,024 → 32 |
| 100 agents, company install | 50,400 → 504 | 10,000 → 100 |
| 100 agents, partial install | 13,175 → 503 | 2,575 → 100 |

These counts measure the real service over local PostgreSQL. They do not
predict hosted page latency. To reproduce the standalone benchmark, use
a checkout of commit `5f725b36fa52002545241a5a8b7a10602f0cc0ce` and run
the command below. Its isolation check intentionally rejects the later
identity follow-up's additional runtime changes.

```sh
node --import ./server/node_modules/tsx/dist/esm/index.mjs scripts/benchmark-connection-install-capabilities.ts --purpose=ai --baseline-ref=026fd9e3452
```

Run the final-head regression suites from the repository root:

```sh
cd server
pnpm exec vitest run src/__tests__/connection-management.integration.test.ts src/__tests__/connection-install-capabilities.test.ts src/__tests__/ai-connections.test.ts src/services/ai-account-identity.test.ts src/__tests__/openapi-routes.test.ts
cd ../ui
pnpm exec vitest run src/api/client.test.ts src/components/ai-connections/ManagedAiConnectionDetails.test.tsx src/components/ai-connections/AiConnectionAccountDetails.test.tsx src/pages/apps/AppDetail.test.tsx
```

## Risks

Descriptive permissions reuse retained facts within one request. A
concurrent change can leave a displayed choice stale; the next request
and every write still authorize fresh. Pools with referenced members
retain their existing cost.

The account and grants clients now bound each caller's wait. A browser
abort does not cancel an authorization read already admitted by the
server. The independent profile request uses a fixed Anthropic origin, a
bounded body, no redirects, and a 15-second deadline. Inference-only
credentials can return no email; that is reported without guessing from
a Paperclip owner or changing connection health. Profiles are fetched
only for the caller's authorized selected grant. No ambient CLI
identity, persistent identity cache, background polling, or
billing-identity change is added. Other API reads keep their existing
behavior. No migration is required. Staging has not been redeployed.

## Model Used

OpenAI `gpt-6.1-sol` through the Codex harness in T3 Code, with xhigh
reasoning, code editing, execution, and test tools. The host does not
expose the context-window size.

## Checklist

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

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-02 12:29:03 -08:00

2799 lines
232 KiB
TypeScript

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