diff --git a/cli/src/__tests__/managed-agent.test.ts b/cli/src/__tests__/managed-agent.test.ts new file mode 100644 index 0000000000..269f5c5ea8 --- /dev/null +++ b/cli/src/__tests__/managed-agent.test.ts @@ -0,0 +1,332 @@ +import { Command } from "commander"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { + CLAUDE_MANAGED_BETA_VERSION, + CLAUDE_MANAGED_SYSTEM_PROMPT, + assertSafeManagedAgent, + assertSafeManagedEnvironment, + registerManagedAgentCommands, + setupManagedAgent, + validateManagedAgentSetup, + type ManagedAgentSetupOptions, +} from "../commands/managed-agent.js"; + +const ORIGINAL_ENV = { ...process.env }; + +function setupOptions( + overrides: Partial = {}, +): ManagedAgentSetupOptions { + return { + profileKey: "primary", + displayName: "Primary Claude", + apiKeySecretId: "11111111-1111-4111-8111-111111111111", + model: "claude-sonnet-5", + maxSessionListCostUsd: "1.25", + acknowledgeRetention: true, + companyId: "company-1", + apiBase: "http://localhost:3100", + apiKey: "paperclip-board-token", + json: true, + ...overrides, + }; +} + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "content-type": "application/json" }, + }); +} + +function safeEnvironment(id = "env-1") { + return { + id, + archived_at: null, + config: { + type: "cloud", + networking: { + type: "limited", + allow_mcp_servers: false, + allow_package_managers: false, + allowed_hosts: [], + }, + packages: { + type: "packages", + apt: [], + cargo: [], + gem: [], + go: [], + npm: [], + pip: [], + }, + }, + }; +} + +function safeAgent(id = "agent-1") { + return { + id, + archived_at: null, + version: "7", + model: { id: "claude-sonnet-5" }, + system: CLAUDE_MANAGED_SYSTEM_PROMPT, + tools: [], + mcp_servers: [], + skills: [], + multiagent: null, + }; +} + +describe("managed-agent CLI registration", () => { + it("registers setup with an explicit retention gate and no Anthropic key option", () => { + const program = new Command(); + registerManagedAgentCommands(program); + + const managedAgent = program.commands.find((command) => command.name() === "managed-agent"); + const setup = managedAgent?.commands.find((command) => command.name() === "setup"); + + expect(setup).toBeDefined(); + expect(setup?.options.some((option) => option.long === "--acknowledge-retention")).toBe(true); + expect(setup?.options.some((option) => option.long === "--api-key-secret-id")).toBe(true); + expect(setup?.options.some((option) => option.long === "--anthropic-api-key")).toBe(false); + }); +}); + +describe("managed-agent CLI validation", () => { + it("requires the Anthropic key only through the CLI environment", () => { + expect(() => validateManagedAgentSetup(setupOptions(), {})).toThrow( + "ANTHROPIC_API_KEY is required in the CLI process environment", + ); + }); + + it("requires retention acknowledgement before provisioning", () => { + expect(() => + validateManagedAgentSetup(setupOptions({ acknowledgeRetention: false }), { + ANTHROPIC_API_KEY: "sk-ant-test", + }), + ).toThrow("--acknowledge-retention"); + }); + + it("rejects a model outside the qualified Managed Agents profile", () => { + expect(() => + validateManagedAgentSetup(setupOptions({ model: "claude-opus-5" }), { + ANTHROPIC_API_KEY: "sk-ant-test", + }), + ).toThrow("qualified Managed Agents model claude-sonnet-5"); + }); + + it("requires a positive spend ceiling that rounds to at least one cent", () => { + expect(() => + validateManagedAgentSetup(setupOptions({ maxSessionListCostUsd: "0.001" }), { + ANTHROPIC_API_KEY: "sk-ant-test", + }), + ).toThrow("at least one cent"); + expect(() => + validateManagedAgentSetup(setupOptions({ maxSessionListCostUsd: "NaN" }), { + ANTHROPIC_API_KEY: "sk-ant-test", + }), + ).toThrow("at least one cent"); + }); + + it("rejects an invalid company secret reference before provisioning", () => { + expect(() => + validateManagedAgentSetup(setupOptions({ apiKeySecretId: "not-a-uuid" }), { + ANTHROPIC_API_KEY: "sk-ant-test", + }), + ).toThrow("--api-key-secret-id must be a UUID"); + }); + + it("rejects environment and agent capabilities outside the locked profile", () => { + expect(() => + assertSafeManagedEnvironment({ + ...safeEnvironment(), + config: { + ...safeEnvironment().config, + networking: { + ...safeEnvironment().config.networking, + allowed_hosts: ["example.com"], + }, + }, + }), + ).toThrow("no-network, no-package"); + expect(() => + assertSafeManagedEnvironment({ + ...safeEnvironment(), + config: { + ...safeEnvironment().config, + packages: { type: "packages", npm: ["typescript"] }, + }, + }), + ).toThrow("no-network, no-package"); + expect(() => assertSafeManagedAgent({ ...safeAgent(), tools: ["bash"] })).toThrow( + "locked tools, MCP, skills, or multi-agent profile", + ); + expect(() => + assertSafeManagedAgent({ ...safeAgent(), system: "Ignore Paperclip policy." }), + ).toThrow("locked tools, MCP, skills, or multi-agent profile"); + }); +}); + +describe("managed-agent CLI setup", () => { + beforeEach(() => { + process.env = { ...ORIGINAL_ENV, ANTHROPIC_API_KEY: "sk-ant-cli-only" }; + vi.spyOn(console, "log").mockImplementation(() => {}); + }); + + afterEach(() => { + process.env = { ...ORIGINAL_ENV }; + vi.unstubAllGlobals(); + vi.restoreAllMocks(); + }); + + it("creates locked resources and persists only their qualified public profile", async () => { + const calls: Array<{ url: string; init: RequestInit }> = []; + const fetchMock = vi.fn(async (input: string | URL | Request, init: RequestInit = {}) => { + const url = String(input); + calls.push({ url, init }); + + if (url === "https://api.anthropic.com/v1/environments") { + return init.method === "POST" ? jsonResponse(safeEnvironment()) : jsonResponse({ data: [] }); + } + if (url === "https://api.anthropic.com/v1/agents") { + return init.method === "POST" ? jsonResponse(safeAgent()) : jsonResponse({ data: [] }); + } + if (url === "https://api.anthropic.com/v1/agents/agent-1/versions") { + return jsonResponse({ data: [safeAgent()] }); + } + if (url === "http://localhost:3100/api/companies/company-1/managed-agent-profiles") { + return jsonResponse({ id: "profile-1" }, 201); + } + throw new Error(`Unexpected request: ${init.method ?? "GET"} ${url}`); + }); + vi.stubGlobal("fetch", fetchMock); + + await setupManagedAgent(setupOptions()); + + const anthropicCalls = calls.filter((call) => call.url.startsWith("https://api.anthropic.com")); + expect(anthropicCalls).toHaveLength(5); + for (const call of anthropicCalls) { + const headers = new Headers(call.init.headers); + expect(headers.get("x-api-key")).toBe("sk-ant-cli-only"); + expect(headers.get("anthropic-beta")).toBe(CLAUDE_MANAGED_BETA_VERSION); + } + + const environmentCreate = calls.find( + (call) => call.url.endsWith("/v1/environments") && call.init.method === "POST", + ); + expect(JSON.parse(String(environmentCreate?.init.body))).toMatchObject({ + config: { + type: "cloud", + networking: { + type: "limited", + allow_mcp_servers: false, + allow_package_managers: false, + allowed_hosts: [], + }, + packages: { apt: [], cargo: [], gem: [], go: [], npm: [], pip: [] }, + }, + metadata: { paperclip_profile: "primary" }, + }); + + const agentCreate = calls.find( + (call) => call.url.endsWith("/v1/agents") && call.init.method === "POST", + ); + expect(JSON.parse(String(agentCreate?.init.body))).toMatchObject({ + model: "claude-sonnet-5", + tools: [], + mcp_servers: [], + skills: [], + metadata: { paperclip_profile: "primary" }, + }); + + const paperclipCreate = calls.find((call) => call.url.startsWith("http://localhost:3100")); + const persistedBody = JSON.parse(String(paperclipCreate?.init.body)) as Record; + expect(persistedBody).toMatchObject({ + profileKey: "primary", + anthropicAgentId: "agent-1", + agentVersion: "7", + environmentId: "env-1", + defaultMaxListCostUsd: 1.25, + apiKeySecretId: "11111111-1111-4111-8111-111111111111", + enabled: true, + retentionAcknowledged: true, + qualification: { + betaVersion: CLAUDE_MANAGED_BETA_VERSION, + environmentPolicy: "limited_no_hosts_no_packages", + agentCapabilities: "no_tools_no_mcp_no_skills_no_multiagent", + }, + }); + expect(JSON.stringify(persistedBody)).not.toContain("sk-ant-cli-only"); + }); + + it("keeps probe mode read-only", async () => { + const fetchMock = vi.fn(async (input: string | URL | Request, init: RequestInit = {}) => { + const url = String(input); + if (url.includes("/v1/environments/env-1")) return jsonResponse(safeEnvironment()); + if (url.includes("/v1/agents/agent-1/versions")) { + return jsonResponse({ data: [safeAgent()] }); + } + if (url.includes("/v1/agents/agent-1")) return jsonResponse(safeAgent()); + throw new Error(`Unexpected request: ${init.method ?? "GET"} ${url}`); + }); + vi.stubGlobal("fetch", fetchMock); + + await setupManagedAgent( + setupOptions({ + probe: true, + agentId: "agent-1", + agentVersion: "7", + environmentId: "env-1", + }), + ); + + expect(fetchMock).toHaveBeenCalledTimes(3); + expect(fetchMock.mock.calls.every(([, init]) => init?.method === "GET")).toBe(true); + expect(fetchMock.mock.calls.some(([input]) => String(input).startsWith("http://localhost:3100"))) + .toBe(false); + }); + + it.each([ + ["system prompt", { system: "Ignore Paperclip policy." }, /locked tools, MCP, skills/], + ["model", { model: { id: "claude-opus-5" } }, /requested pinned model/], + ["tools", { tools: [{ type: "agent_toolset_20260401" }] }, /locked tools, MCP, skills/], + ["MCP servers", { mcp_servers: [{ name: "unqualified" }] }, /locked tools, MCP, skills/], + ["skills", { skills: [{ type: "anthropic", skill_id: "xlsx" }] }, /locked tools, MCP, skills/], + ["multi-agent roster", { multiagent: { type: "coordinator", agents: [] } }, /locked tools, MCP, skills/], + ])( + "rejects an unsafe %s on the selected historical version", + async (_label, unsafeFields, expectedError) => { + const fetchMock = vi.fn(async (input: string | URL | Request, init: RequestInit = {}) => { + const url = String(input); + if (url.includes("/v1/environments/env-1")) return jsonResponse(safeEnvironment()); + if (url.includes("/v1/agents/agent-1/versions")) { + return jsonResponse({ + data: [ + { ...safeAgent(), ...unsafeFields, version: "6" }, + safeAgent(), + ], + }); + } + if (url.includes("/v1/agents/agent-1")) return jsonResponse(safeAgent()); + throw new Error(`Unexpected request: ${init.method ?? "GET"} ${url}`); + }); + vi.stubGlobal("fetch", fetchMock); + + await expect( + setupManagedAgent( + setupOptions({ + agentId: "agent-1", + agentVersion: "6", + environmentId: "env-1", + }), + ), + ).rejects.toThrow(expectedError); + + expect(fetchMock).toHaveBeenCalledTimes(3); + expect( + fetchMock.mock.calls.some(([input]) => String(input).startsWith("http://localhost:3100")), + ).toBe(false); + }, + ); +}); diff --git a/cli/src/commands/managed-agent.ts b/cli/src/commands/managed-agent.ts new file mode 100644 index 0000000000..ccf619820a --- /dev/null +++ b/cli/src/commands/managed-agent.ts @@ -0,0 +1,433 @@ +import { Command } from "commander"; + +import { + addCommonClientOptions, + apiPath, + handleCommandError, + printOutput, + resolveCommandContext, + type BaseClientOptions, +} from "./client/common.js"; + +const ANTHROPIC_ORIGIN = "https://api.anthropic.com"; +const ANTHROPIC_VERSION = "2023-06-01"; +export const CLAUDE_MANAGED_BETA_VERSION = "managed-agents-2026-04-01" as const; +export const CLAUDE_MANAGED_QUALIFIED_MODEL = "claude-sonnet-5" as const; +export const CLAUDE_MANAGED_SYSTEM_PROMPT = + "You are a Paperclip remote agent. Follow the current user turn and use only the custom tools supplied for that session. Paperclip tool authority, completion, blocking, review, and yielding are enforced by the runner. Never request or infer a Paperclip endpoint or credential."; + +export interface ManagedAgentSetupOptions extends BaseClientOptions { + companyId?: string; + profileKey: string; + displayName: string; + apiKeySecretId: string; + model: string; + maxSessionListCostUsd: string; + agentId?: string; + agentVersion?: string; + environmentId?: string; + probe?: boolean; + acknowledgeRetention?: boolean; +} + +interface RemoteResource { + id?: string; + [key: string]: unknown; +} + +interface ValidatedSetup { + anthropicApiKey: string; + profileKey: string; + displayName: string; + apiKeySecretId: string; + model: string; + agentId?: string; + agentVersion?: string; + environmentId?: string; + defaultMaxListCostUsd: number; +} + +const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +function record(value: unknown): Record { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : {}; +} + +function required(value: string | undefined, label: string): string { + const normalized = value?.trim() ?? ""; + if (!normalized) throw new Error(`${label} is required`); + return normalized; +} + +export function validateManagedAgentSetup( + options: ManagedAgentSetupOptions, + env: NodeJS.ProcessEnv = process.env, +): ValidatedSetup { + const anthropicApiKey = env.ANTHROPIC_API_KEY?.trim(); + if (!anthropicApiKey) { + throw new Error("ANTHROPIC_API_KEY is required in the CLI process environment"); + } + if (!options.acknowledgeRetention) { + throw new Error( + "Pass --acknowledge-retention to enable the stateful beta Managed Agents service", + ); + } + + const profileKey = required(options.profileKey, "--profile-key"); + const displayName = required(options.displayName, "--display-name"); + const apiKeySecretId = required(options.apiKeySecretId, "--api-key-secret-id"); + const model = required(options.model, "--model"); + if (model !== CLAUDE_MANAGED_QUALIFIED_MODEL) { + throw new Error( + `--model must be the qualified Managed Agents model ${CLAUDE_MANAGED_QUALIFIED_MODEL}`, + ); + } + if (!UUID_RE.test(apiKeySecretId)) { + throw new Error("--api-key-secret-id must be a UUID"); + } + + const defaultMaxListCostUsd = Number(options.maxSessionListCostUsd); + const cents = Math.round(defaultMaxListCostUsd * 100); + if ( + !Number.isFinite(defaultMaxListCostUsd) + || defaultMaxListCostUsd <= 0 + || !Number.isSafeInteger(cents) + || cents <= 0 + ) { + throw new Error("--max-session-list-cost-usd must resolve to at least one cent"); + } + + return { + anthropicApiKey, + profileKey, + displayName, + apiKeySecretId, + model, + agentId: options.agentId?.trim() || undefined, + agentVersion: options.agentVersion?.trim() || undefined, + environmentId: options.environmentId?.trim() || undefined, + defaultMaxListCostUsd, + }; +} + +async function anthropicRequest( + key: string, + method: "GET" | "POST", + path: string, + body?: Record, +): Promise> { + const response = await fetch(`${ANTHROPIC_ORIGIN}${path}`, { + method, + headers: { + "x-api-key": key, + "anthropic-version": ANTHROPIC_VERSION, + "anthropic-beta": CLAUDE_MANAGED_BETA_VERSION, + ...(body ? { "content-type": "application/json" } : {}), + }, + ...(body ? { body: JSON.stringify(body) } : {}), + signal: AbortSignal.timeout(15_000), + }); + if (!response.ok) { + throw new Error(`Anthropic Managed Agents request failed with HTTP ${response.status}`); + } + if (response.status === 204) return {}; + return record(await response.json()); +} + +async function listAll(key: string, path: string): Promise { + const rows: RemoteResource[] = []; + let page: string | null = null; + do { + const suffix = page ? `${path.includes("?") ? "&" : "?"}page=${encodeURIComponent(page)}` : ""; + const response = await anthropicRequest(key, "GET", `${path}${suffix}`); + for (const value of Array.isArray(response.data) ? response.data : []) { + rows.push(record(value) as RemoteResource); + } + page = typeof response.next_page === "string" && response.next_page + ? response.next_page + : null; + } while (page); + return rows; +} + +function resourceByProfile( + resources: RemoteResource[], + profileKey: string, + resourceLabel: string, +): RemoteResource | null { + const matches = resources.filter( + (resource) => + typeof resource.id === "string" + && record(resource.metadata).paperclip_profile === profileKey, + ); + if (matches.length > 1) { + throw new Error( + `Multiple Anthropic ${resourceLabel} resources use Paperclip profile ${profileKey}; pass an explicit resource ID`, + ); + } + return matches[0] ?? null; +} + +export function assertSafeManagedEnvironment(environment: Record): void { + const config = record(environment.config); + const networking = record(config.networking); + const packages = record(config.packages); + const installed = Object.entries(packages) + .filter(([key]) => key !== "type") + .flatMap(([, value]) => (Array.isArray(value) ? value : [value])) + .filter((value) => value !== undefined && value !== null); + if ( + environment.archived_at !== null + || config.type !== "cloud" + || networking.type !== "limited" + || networking.allow_mcp_servers !== false + || networking.allow_package_managers !== false + || !Array.isArray(networking.allowed_hosts) + || networking.allowed_hosts.length > 0 + || installed.length > 0 + ) { + throw new Error( + "Existing Anthropic Environment does not match Paperclip's no-network, no-package profile", + ); + } +} + +export function assertSafeManagedAgent(agent: Record): void { + const model = typeof agent.model === "string" ? agent.model : record(agent.model).id; + if ( + agent.archived_at !== null + || agent.system !== CLAUDE_MANAGED_SYSTEM_PROMPT + || typeof model !== "string" + || !model + || !Array.isArray(agent.tools) + || agent.tools.length > 0 + || !Array.isArray(agent.mcp_servers) + || agent.mcp_servers.length > 0 + || !Array.isArray(agent.skills) + || agent.skills.length > 0 + || agent.multiagent != null + ) { + throw new Error( + "Existing Anthropic Agent enables or omits the locked tools, MCP, skills, or multi-agent profile", + ); + } +} + +async function resolveEnvironment( + key: string, + options: ManagedAgentSetupOptions, +): Promise> { + if (options.environmentId) { + const environment = await anthropicRequest( + key, + "GET", + `/v1/environments/${encodeURIComponent(options.environmentId)}`, + ); + assertSafeManagedEnvironment(environment); + return environment; + } + + const existing = resourceByProfile( + await listAll(key, "/v1/environments"), + options.profileKey, + "Environment", + ); + if (existing) { + assertSafeManagedEnvironment(existing); + return existing; + } + if (options.probe) throw new Error("Probe found no matching Anthropic Environment"); + + const environment = await anthropicRequest(key, "POST", "/v1/environments", { + name: `Paperclip · ${options.displayName}`, + description: "Paperclip remote-agent environment: no network or added packages.", + config: { + type: "cloud", + networking: { + type: "limited", + allow_mcp_servers: false, + allow_package_managers: false, + allowed_hosts: [], + }, + packages: { + apt: [], + cargo: [], + gem: [], + go: [], + npm: [], + pip: [], + }, + }, + metadata: { paperclip_profile: options.profileKey }, + }); + assertSafeManagedEnvironment(environment); + return environment; +} + +async function resolveAgent( + key: string, + options: ManagedAgentSetupOptions, +): Promise> { + if (options.agentId) { + const agent = await anthropicRequest( + key, + "GET", + `/v1/agents/${encodeURIComponent(options.agentId)}`, + ); + assertSafeManagedAgent(agent); + assertManagedAgentModel(agent, options.model); + return agent; + } + + const existing = resourceByProfile( + await listAll(key, "/v1/agents"), + options.profileKey, + "Agent", + ); + if (existing) { + assertSafeManagedAgent(existing); + assertManagedAgentModel(existing, options.model); + return existing; + } + if (options.probe) throw new Error("Probe found no matching Anthropic Agent"); + + const agent = await anthropicRequest(key, "POST", "/v1/agents", { + name: `Paperclip · ${options.displayName}`, + description: "Versioned Paperclip remote agent; runnerd supplies session tools.", + model: options.model, + system: CLAUDE_MANAGED_SYSTEM_PROMPT, + tools: [], + mcp_servers: [], + skills: [], + metadata: { paperclip_profile: options.profileKey }, + }); + assertSafeManagedAgent(agent); + assertManagedAgentModel(agent, options.model); + return agent; +} + +function assertManagedAgentModel(agent: Record, expectedModel: string): void { + const model = typeof agent.model === "string" ? agent.model : record(agent.model).id; + if (model !== expectedModel) { + throw new Error( + `Existing Anthropic Agent model does not match the requested pinned model ${expectedModel}`, + ); + } +} + +export async function setupManagedAgent(options: ManagedAgentSetupOptions): Promise { + const validated = validateManagedAgentSetup(options); + const normalizedOptions: ManagedAgentSetupOptions = { + ...options, + profileKey: validated.profileKey, + displayName: validated.displayName, + apiKeySecretId: validated.apiKeySecretId, + model: validated.model, + agentId: validated.agentId, + agentVersion: validated.agentVersion, + environmentId: validated.environmentId, + }; + const [environment, agent] = await Promise.all([ + resolveEnvironment(validated.anthropicApiKey, normalizedOptions), + resolveAgent(validated.anthropicApiKey, normalizedOptions), + ]); + const agentId = String(agent.id ?? ""); + const environmentId = String(environment.id ?? ""); + if (!agentId || !environmentId) { + throw new Error("Anthropic did not return usable Agent and Environment identities"); + } + + const versions = await listAll( + validated.anthropicApiKey, + `/v1/agents/${encodeURIComponent(agentId)}/versions`, + ); + const version = normalizedOptions.agentVersion + ?? String(agent.version ?? versions.at(-1)?.version ?? ""); + const pinnedAgent = version + ? versions.find((entry) => String(entry.version) === version) + : undefined; + if (!version || !pinnedAgent) { + throw new Error("Anthropic did not return a usable pinned Agent version"); + } + if (String(pinnedAgent.id ?? "") !== agentId) { + throw new Error("Anthropic pinned Agent version identity does not match the selected Agent"); + } + assertSafeManagedAgent(pinnedAgent); + assertManagedAgentModel(pinnedAgent, normalizedOptions.model); + + const qualification = { + probedAt: new Date().toISOString(), + betaVersion: CLAUDE_MANAGED_BETA_VERSION, + environmentPolicy: "limited_no_hosts_no_packages", + agentCapabilities: "no_tools_no_mcp_no_skills_no_multiagent", + }; + const profile = { + profileKey: normalizedOptions.profileKey, + displayName: normalizedOptions.displayName, + anthropicAgentId: agentId, + agentVersion: version, + environmentId, + defaultModel: normalizedOptions.model, + defaultMaxListCostUsd: validated.defaultMaxListCostUsd, + apiKeySecretId: normalizedOptions.apiKeySecretId, + enabled: !options.probe, + retentionAcknowledged: true, + qualification, + }; + + if (options.probe) { + printOutput({ mode: "probe", qualified: true, profile }, { json: options.json }); + return; + } + + const context = resolveCommandContext(options, { requireCompany: true }); + const stored = await context.api.post( + apiPath`/api/companies/${context.companyId}/managed-agent-profiles`, + profile, + ); + printOutput(stored, { json: context.json }); +} + +export function registerManagedAgentCommands(program: Command): void { + const command = program + .command("managed-agent") + .description("Provision and qualify remote managed-agent providers"); + addCommonClientOptions( + command + .command("setup") + .description( + "Create or adopt a locked-down Anthropic Agent and Environment, then store a company profile", + ) + .requiredOption("--profile-key ", "Stable company profile key") + .requiredOption("--display-name ", "Profile display name") + .requiredOption( + "--api-key-secret-id ", + "Existing company secret containing ANTHROPIC_API_KEY", + ) + .option("--model ", "Pinned Claude model", CLAUDE_MANAGED_QUALIFIED_MODEL) + .option( + "--max-session-list-cost-usd ", + "Default hard session ceiling", + "1.00", + ) + .option("--agent-id ", "Adopt an existing Anthropic Agent") + .option("--agent-version ", "Pin an existing Agent version") + .option("--environment-id ", "Adopt an existing Anthropic Environment") + .option("--probe", "Read-only qualification; create or persist nothing", false) + .option( + "--acknowledge-retention", + "Acknowledge beta retention and non-ZDR/non-HIPAA status", + false, + ) + .action(async (options: ManagedAgentSetupOptions) => { + try { + await setupManagedAgent(options); + } catch (error) { + handleCommandError(error); + } + }), + { includeCompany: true }, + ); +} diff --git a/cli/src/index.ts b/cli/src/index.ts index fa963cbd80..b83727883f 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -41,6 +41,7 @@ import { registerWorkspaceCommands } from "./commands/client/workspace.js"; import { registerAccessCommands } from "./commands/client/access.js"; import { registerRoutineApiCommands } from "./commands/client/routine-api.js"; import { registerAdapterCommands } from "./commands/client/adapter.js"; +import { registerManagedAgentCommands } from "./commands/managed-agent.js"; import { registerAssetCommands } from "./commands/client/asset.js"; import { registerSkillCommands } from "./commands/client/skill.js"; import { cliVersion } from "./version.js"; @@ -226,6 +227,7 @@ registerWorkspaceCommands(program); registerAccessCommands(program); registerRoutineApiCommands(program); registerAdapterCommands(program); +registerManagedAgentCommands(program); registerAssetCommands(program); registerSkillCommands(program); registerRoutineCommands(program); diff --git a/doc/architecture/paperclip-runner-compatibility.md b/doc/architecture/paperclip-runner-compatibility.md index 8630e45ca9..b648186c5f 100644 --- a/doc/architecture/paperclip-runner-compatibility.md +++ b/doc/architecture/paperclip-runner-compatibility.md @@ -34,9 +34,9 @@ The server resolves and persists the runtime once, before provider launch. | Persisted runtime | Adapter | Flag | Result | | --- | --- | --- | --- | | none | Any direct adapter | off or on | Use the existing direct path. | -| none | `paperclip_runner` with Codex | off | Reject the fresh start with a stable rollout-disabled error. | -| none | `paperclip_runner` with Codex | on | Use PRP v1 and runnerd. | -| none | `paperclip_runner` with another provider | on | Reject the unsupported provider before runnerd starts. | +| none | `paperclip_runner` with any qualified provider | off | Reject the fresh start with a stable rollout-disabled error. | +| none | `paperclip_runner` with a qualified provider | on | Use PRP v1 and the provider's persisted runnerd backend. | +| none | `paperclip_runner` with an incomplete or unqualified profile | on | Reject the profile before runnerd starts. | | direct | Any | changed later | Keep the persisted direct path. | | native | Any | changed later | Keep the persisted native path for read, cancel, recovery, and finalization. | @@ -84,8 +84,8 @@ When the rollout flag is off: When the rollout flag is on: -- creation, import, and edit accept `paperclip_runner` only with provider - `codex` and valid Codex configuration; +- creation, import, and edit accept `paperclip_runner` only with a qualified + Codex, OpenCode, Claude Managed, AWS AgentCore, or Claude/Codex ACPX profile; - switching from a direct adapter affects only future unresolved runs; and - switching away from the runner affects only future unresolved runs. diff --git a/doc/architecture/paperclip-runner.md b/doc/architecture/paperclip-runner.md index 85b41813ba..49bf3ceac3 100644 --- a/doc/architecture/paperclip-runner.md +++ b/doc/architecture/paperclip-runner.md @@ -17,9 +17,9 @@ This process needs durable delivery, restart recovery, and governed access to Paperclip actions. It must not become a second control plane. It must also land without changing the behavior of existing adapters. -The initial implementation is intentionally narrow. It supports Codex through -an explicit, experimental adapter. Other providers and developer tools remain -outside this decision. +The implementation remains behind one explicit experimental adapter and one +default-off instance flag. Its qualified provider catalog includes Codex, +OpenCode, Claude Managed, AWS AgentCore, and pinned Claude/Codex ACPX profiles. ## Decision @@ -28,8 +28,8 @@ the language-neutral Paperclip Runner Protocol (PRP), the Rust runner process, provider drivers, deterministic replay, and semantic action dispatch contracts. Add one explicit adapter named `paperclip_runner`. The adapter is available only -when an instance-level, default-off rollout flag is enabled. Its first supported -provider is Codex. +when an instance-level, default-off rollout flag is enabled. Provider selection +is persisted per run and may use only a qualified provider profile. Do not route existing adapters through Paperclip Runner. A direct adapter keeps its current invocation, transcript, interaction, cancellation, and finalization @@ -49,8 +49,8 @@ paths. - Replace existing direct adapters. - Move business authorization or issue status policy into Rust. - Give runnerd a broad Paperclip API credential. -- Support OpenCode, ACPX, Claude Managed, AWS AgentCore, or remote sandboxes in - the first production slice. +- Support unqualified provider versions, arbitrary ACPX agents, or editable + remote-resource identity in agent configuration. - Expose browser SDK, React SDK, eval, lab, or scenario-explorer package entry points in the initial release. - Commit recorded screenshots, stress logs, or construction history as product @@ -65,9 +65,9 @@ Paperclip server | authenticated PRP v1 WebSocket v paperclip-runnerd - | Codex app-server protocol + | qualified native provider protocol v -Codex +Codex / OpenCode / ACPX / Claude Managed / AWS AgentCore ``` The server opens a native run and launches a verified runnerd artifact in the @@ -234,7 +234,8 @@ The rollout has three gates: 1. The instance flag is enabled. 2. The agent explicitly selects `paperclip_runner`. -3. The adapter selects a supported provider. The initial provider is `codex`. +3. The adapter selects a provider from the qualified catalog: Codex, OpenCode, + pinned Claude/Codex ACPX, Claude Managed, or AWS AgentCore. The adapter is hidden from creation and selection surfaces while the flag is off. Server validation also rejects a fresh runner selection or start while the diff --git a/packages/adapter-utils/src/paperclip-runner-permissions.test.ts b/packages/adapter-utils/src/paperclip-runner-permissions.test.ts index ec6c7bec5f..152a98d7fe 100644 --- a/packages/adapter-utils/src/paperclip-runner-permissions.test.ts +++ b/packages/adapter-utils/src/paperclip-runner-permissions.test.ts @@ -18,8 +18,17 @@ describe("Paperclip Runner permission defaults", () => { it("recognizes only exact provider identifiers", () => { expect(isPaperclipRunnerProvider("codex")).toBe(true); expect(isPaperclipRunnerProvider("opencode")).toBe(true); + expect(isPaperclipRunnerProvider("claude_managed")).toBe(true); + expect(isPaperclipRunnerProvider("aws_agentcore")).toBe(true); expect(isPaperclipRunnerProvider("acpx")).toBe(true); expect(isPaperclipRunnerProvider("toString")).toBe(false); expect(isPaperclipRunnerProvider("__proto__")).toBe(false); }); + + it("keeps managed provider permissions under the qualified profile", () => { + expect(resolvePaperclipRunnerPermissionMode("claude_managed", "never")) + .toBe("provider-managed"); + expect(resolvePaperclipRunnerPermissionMode("aws_agentcore", "approve-all")) + .toBe("provider-managed"); + }); }); diff --git a/packages/adapter-utils/src/paperclip-runner-permissions.ts b/packages/adapter-utils/src/paperclip-runner-permissions.ts index 85ab429b21..27ef4d23b7 100644 --- a/packages/adapter-utils/src/paperclip-runner-permissions.ts +++ b/packages/adapter-utils/src/paperclip-runner-permissions.ts @@ -1,6 +1,8 @@ export type PaperclipRunnerProvider = | "codex" | "opencode" + | "claude_managed" + | "aws_agentcore" | "acpx"; export type CodexPermissionMode = "never" | "on-request" | "untrusted"; @@ -21,13 +23,20 @@ export interface PaperclipRunnerPermissionOption description: string; } -export interface PaperclipRunnerPermissionCapability { - configurable: true; - configKey: "codexPermissionMode" | "opencodePermissionMode" | "acpxPermissionMode"; - defaultMode: PaperclipRunnerPermissionMode; - options: readonly PaperclipRunnerPermissionOption[]; - description: string; -} +export type PaperclipRunnerPermissionCapability = + | { + configurable: true; + configKey: "codexPermissionMode" | "opencodePermissionMode" | "acpxPermissionMode"; + defaultMode: PaperclipRunnerPermissionMode; + options: readonly PaperclipRunnerPermissionOption[]; + description: string; + } + | { + configurable: false; + defaultMode: "provider-managed"; + options: readonly []; + description: string; + }; /** * Control-plane catalog for Paperclip Runner permission UX and validation. @@ -57,6 +66,18 @@ export const PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES = { { value: "deny", label: "Deny operations", description: "Reject protected OpenCode operations." }, ], }, + claude_managed: { + configurable: false, + defaultMode: "provider-managed", + options: [], + description: "Claude Managed runs non-interactively under its qualified provider profile and Paperclip policy.", + }, + aws_agentcore: { + configurable: false, + defaultMode: "provider-managed", + options: [], + description: "AWS AgentCore runs non-interactively under its qualified harness profile and Paperclip policy.", + }, acpx: { configurable: true, configKey: "acpxPermissionMode", @@ -71,14 +92,19 @@ export const PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES = { } as const satisfies Record; export function isPaperclipRunnerProvider(value: unknown): value is PaperclipRunnerProvider { - return value === "codex" || value === "opencode" || value === "acpx"; + return value === "codex" + || value === "opencode" + || value === "claude_managed" + || value === "aws_agentcore" + || value === "acpx"; } export function resolvePaperclipRunnerPermissionMode( provider: PaperclipRunnerProvider, value: unknown, -): PaperclipRunnerPermissionMode { +): PaperclipRunnerPermissionMode | "provider-managed" { const capability = PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES[provider]; + if (!capability.configurable) return capability.defaultMode; return capability.options.some((option) => option.value === value) ? value as PaperclipRunnerPermissionMode : capability.defaultMode; diff --git a/packages/adapters/codex-local/src/ui/build-config.test.ts b/packages/adapters/codex-local/src/ui/build-config.test.ts index b50f9c1d74..1b4998cc1d 100644 --- a/packages/adapters/codex-local/src/ui/build-config.test.ts +++ b/packages/adapters/codex-local/src/ui/build-config.test.ts @@ -126,14 +126,15 @@ describe("buildPaperclipRunnerConfig", () => { }); it("fails closed to the Codex profile and safe defaults for stale schema values", () => { - expect(buildPaperclipRunnerConfig(makeValues({ + const config = buildPaperclipRunnerConfig(makeValues({ adapterSchemaValues: { provider: "unknown", codexPermissionMode: "unrestricted", lifecycleMode: "forever", idleTimeoutMs: -1, }, - }))).toMatchObject({ + })); + expect(config).toMatchObject({ provider: "codex", codexPermissionMode: "untrusted", lifecycleMode: "per_turn", @@ -208,6 +209,71 @@ describe("buildPaperclipRunnerConfig", () => { }); }); + it("builds a Claude Managed profile reference with explicit retention and spend controls", () => { + const config = buildPaperclipRunnerConfig(makeValues({ + adapterType: "paperclip_runner", + model: "claude-sonnet-5", + adapterSchemaValues: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + maxSessionListCostUsd: 0.5, + anthropicAgentId: "editable-resource-id-must-not-survive", + }, + })); + expect(config).toMatchObject({ + provider: "claude_managed", + managedProfileId: "managed-primary", + model: "claude-sonnet-5", + managedAgentsRetentionAcknowledged: true, + maxSessionListCostUsd: 0.5, + }); + expect(config).not.toHaveProperty("anthropicAgentId"); + }); + + it("builds an AgentCore profile reference with bounded invocation controls", () => { + expect(buildPaperclipRunnerConfig(makeValues({ + adapterType: "paperclip_runner", + model: "", + adapterSchemaValues: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + maxEstimatedSessionCostUsd: 0.75, + maxIterations: 8, + maxOutputTokens: 2_048, + timeoutSeconds: 45, + }, + }))).toMatchObject({ + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + model: "global.anthropic.claude-sonnet-4-6", + agentCoreRetentionAcknowledged: true, + maxEstimatedSessionCostUsd: 0.75, + maxIterations: 8, + maxOutputTokens: 2_048, + timeoutSeconds: 45, + }); + }); + + it.each([ + ["maxIterations", 0], + ["maxIterations", 9], + ["maxIterations", "8"], + ["maxOutputTokens", 4_097], + ["timeoutSeconds", 301], + ])("rejects an unsafe AgentCore %s value", (field, value) => { + expect(() => buildPaperclipRunnerConfig(makeValues({ + adapterType: "paperclip_runner", + adapterSchemaValues: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + [field]: value, + }, + }))).toThrow("must be an integer between"); + }); + it("bounds warm lifecycle values to the shared safe default", () => { expect(buildPaperclipRunnerConfig(makeValues({ paperclipRunnerLifecycleMode: "warm", diff --git a/packages/adapters/codex-local/src/ui/build-config.ts b/packages/adapters/codex-local/src/ui/build-config.ts index 71c64a61b5..5f0e000d36 100644 --- a/packages/adapters/codex-local/src/ui/build-config.ts +++ b/packages/adapters/codex-local/src/ui/build-config.ts @@ -101,6 +101,55 @@ export function buildPaperclipRunnerConfig(v: CreateConfigValues): Record { + if (value === undefined || value === null || value === "") return fallback; + if ( + typeof value !== "number" + || !Number.isSafeInteger(value) + || value <= 0 + || value > maximum + ) { + throw new Error(`${label} must be an integer between 1 and ${maximum}.`); + } + return value; + }; + const maxIterations = boundedLimit( + schemaValues.maxIterations, + 8, + 8, + "AWS AgentCore maxIterations", + ); + const maxOutputTokens = boundedLimit( + schemaValues.maxOutputTokens, + 4_096, + 4_096, + "AWS AgentCore maxOutputTokens", + ); + const timeoutSeconds = boundedLimit( + schemaValues.timeoutSeconds, + 300, + 300, + "AWS AgentCore timeoutSeconds", + ); const lifecycleCandidate = v.paperclipRunnerLifecycleMode ?? schemaValues.lifecycleMode; const lifecycleMode = lifecycleCandidate === "warm" ? "warm" : "per_turn"; const configuredIdleTimeoutMs = @@ -115,6 +164,33 @@ export function buildPaperclipRunnerConfig(v: CreateConfigValues): Record 0 + ? maxSessionListCostUsd + : 1, + managedAgentsRetentionAcknowledged: + managedAgentsRetentionAcknowledged, + } + : {}), + ...(provider === "aws_agentcore" + ? { + ...(agentCoreProfileId ? { agentCoreProfileId } : {}), + model: configuredModel || "global.anthropic.claude-sonnet-4-6", + maxEstimatedSessionCostUsd: + Number.isFinite(maxEstimatedSessionCostUsd) + && maxEstimatedSessionCostUsd > 0 + ? maxEstimatedSessionCostUsd + : 1, + agentCoreRetentionAcknowledged: + agentCoreRetentionAcknowledged, + maxIterations, + maxOutputTokens, + timeoutSeconds, + } + : {}), lifecycleMode, ...(lifecycleMode === "warm" ? { idleTimeoutMs } : {}), }; diff --git a/packages/db/src/migrations/0237_clammy_colonel_america.sql b/packages/db/src/migrations/0237_clammy_colonel_america.sql new file mode 100644 index 0000000000..9703572883 --- /dev/null +++ b/packages/db/src/migrations/0237_clammy_colonel_america.sql @@ -0,0 +1,52 @@ +CREATE TABLE "managed_agent_profiles" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "profile_key" text NOT NULL, + "display_name" text NOT NULL, + "service" text DEFAULT 'anthropic_managed_agents' NOT NULL, + "anthropic_agent_id" text NOT NULL, + "agent_version" text NOT NULL, + "environment_id" text NOT NULL, + "beta_version" text DEFAULT 'managed-agents-2026-04-01' NOT NULL, + "default_model" text DEFAULT 'claude-sonnet-5' NOT NULL, + "default_max_list_cost_cents" integer DEFAULT 100 NOT NULL, + "api_key_secret_id" uuid NOT NULL, + "enabled" boolean DEFAULT false NOT NULL, + "retention_acknowledged" boolean DEFAULT false NOT NULL, + "qualification" jsonb DEFAULT '{}'::jsonb NOT NULL, + "qualified_at" timestamp with time zone, + "qualified_revision" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "managed_agent_profiles_service_check" CHECK ("managed_agent_profiles"."service" = 'anthropic_managed_agents'), + CONSTRAINT "managed_agent_profiles_beta_check" CHECK ("managed_agent_profiles"."beta_version" = 'managed-agents-2026-04-01'), + CONSTRAINT "managed_agent_profiles_positive_budget_check" CHECK ("managed_agent_profiles"."default_max_list_cost_cents" > 0), + CONSTRAINT "managed_agent_profiles_qualified_revision_check" CHECK (("managed_agent_profiles"."qualified_at" IS NULL AND "managed_agent_profiles"."qualified_revision" IS NULL) OR ("managed_agent_profiles"."qualified_at" IS NOT NULL AND "managed_agent_profiles"."qualification" <> '{}'::jsonb AND "managed_agent_profiles"."qualified_revision" ~ '^sha256:[0-9a-f]{64}$')) +); +--> statement-breakpoint +CREATE TABLE "remote_agent_profiles" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "company_id" uuid NOT NULL, + "profile_key" text NOT NULL, + "display_name" text NOT NULL, + "service" text NOT NULL, + "configuration" jsonb DEFAULT '{}'::jsonb NOT NULL, + "enabled" boolean DEFAULT false NOT NULL, + "retention_acknowledged" boolean DEFAULT false NOT NULL, + "qualification" jsonb DEFAULT '{}'::jsonb NOT NULL, + "qualified_at" timestamp with time zone, + "qualified_revision" text, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "remote_agent_profiles_service_check" CHECK ("remote_agent_profiles"."service" = 'aws_bedrock_agentcore_harness'), + CONSTRAINT "remote_agent_profiles_qualified_revision_check" CHECK (("remote_agent_profiles"."qualified_at" IS NULL AND "remote_agent_profiles"."qualified_revision" IS NULL) OR ("remote_agent_profiles"."qualified_at" IS NOT NULL AND "remote_agent_profiles"."qualification" <> '{}'::jsonb AND "remote_agent_profiles"."qualified_revision" ~ '^sha256:[0-9a-f]{64}$')) +); +--> statement-breakpoint +ALTER TABLE "managed_agent_profiles" ADD CONSTRAINT "managed_agent_profiles_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "managed_agent_profiles" ADD CONSTRAINT "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk" FOREIGN KEY ("api_key_secret_id") REFERENCES "public"."company_secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "remote_agent_profiles" ADD CONSTRAINT "remote_agent_profiles_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "managed_agent_profiles_company_idx" ON "managed_agent_profiles" USING btree ("company_id");--> statement-breakpoint +CREATE UNIQUE INDEX "managed_agent_profiles_company_key_uq" ON "managed_agent_profiles" USING btree ("company_id","profile_key");--> statement-breakpoint +CREATE UNIQUE INDEX "managed_agent_profiles_company_resource_uq" ON "managed_agent_profiles" USING btree ("company_id","anthropic_agent_id","agent_version","environment_id");--> statement-breakpoint +CREATE INDEX "remote_agent_profiles_company_idx" ON "remote_agent_profiles" USING btree ("company_id");--> statement-breakpoint +CREATE UNIQUE INDEX "remote_agent_profiles_company_key_uq" ON "remote_agent_profiles" USING btree ("company_id","profile_key"); \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0237_snapshot.json b/packages/db/src/migrations/meta/0237_snapshot.json new file mode 100644 index 0000000000..9465a9aeb4 --- /dev/null +++ b/packages/db/src/migrations/meta/0237_snapshot.json @@ -0,0 +1,42538 @@ +{ + "id": "03fb5599-6390-4497-ad7c-d43e02a7be47", + "prevId": "5129d326-4e5f-40bf-81ee-b2cc6f1506cf", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_log": { + "name": "activity_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "activity_log_company_created_idx": { + "name": "activity_log_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_agent_created_idx": { + "name": "activity_log_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_company_responsible_user_created_idx": { + "name": "activity_log_company_responsible_user_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_run_id_idx": { + "name": "activity_log_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "activity_log_entity_type_id_idx": { + "name": "activity_log_entity_type_id_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_log_company_id_companies_id_fk": { + "name": "activity_log_company_id_companies_id_fk", + "tableFrom": "activity_log", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_agent_id_agents_id_fk": { + "name": "activity_log_agent_id_agents_id_fk", + "tableFrom": "activity_log", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "activity_log_run_id_heartbeat_runs_id_fk": { + "name": "activity_log_run_id_heartbeat_runs_id_fk", + "tableFrom": "activity_log", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.adapter_auth_sessions": { + "name": "adapter_auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_session_id": { + "name": "public_session_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "promotion_expires_at": { + "name": "promotion_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bound_at": { + "name": "bound_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "adapter_auth_sessions_company_status_idx": { + "name": "adapter_auth_sessions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_company_owner_adapter_active_uq": { + "name": "adapter_auth_sessions_company_owner_adapter_active_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"adapter_auth_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'promoting', 'awaiting_code', 'submitting')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_public_session_id_uq": { + "name": "adapter_auth_sessions_public_session_id_uq", + "columns": [ + { + "expression": "public_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_environment_idx": { + "name": "adapter_auth_sessions_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_expires_idx": { + "name": "adapter_auth_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "adapter_auth_sessions_provider_lease_idx": { + "name": "adapter_auth_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "adapter_auth_sessions_company_id_companies_id_fk": { + "name": "adapter_auth_sessions_company_id_companies_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "adapter_auth_sessions_environment_id_environments_id_fk": { + "name": "adapter_auth_sessions_environment_id_environments_id_fk", + "tableFrom": "adapter_auth_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_api_keys": { + "name": "agent_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_api_keys_key_hash_idx": { + "name": "agent_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_api_keys_company_agent_idx": { + "name": "agent_api_keys_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_api_keys_agent_id_agents_id_fk": { + "name": "agent_api_keys_agent_id_agents_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_api_keys_company_id_companies_id_fk": { + "name": "agent_api_keys_company_id_companies_id_fk", + "tableFrom": "agent_api_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_config_revisions": { + "name": "agent_config_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'patch'" + }, + "rolled_back_from_revision_id": { + "name": "rolled_back_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changed_keys": { + "name": "changed_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "before_config": { + "name": "before_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "after_config": { + "name": "after_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_config_revisions_company_agent_created_idx": { + "name": "agent_config_revisions_company_agent_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_config_revisions_agent_created_idx": { + "name": "agent_config_revisions_agent_created_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_config_revisions_company_id_companies_id_fk": { + "name": "agent_config_revisions_company_id_companies_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_config_revisions_agent_id_agents_id_fk": { + "name": "agent_config_revisions_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_config_revisions_created_by_agent_id_agents_id_fk": { + "name": "agent_config_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "agent_config_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memberships": { + "name": "agent_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_memberships_company_user_idx": { + "name": "agent_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_starred_idx": { + "name": "agent_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_agent_idx": { + "name": "agent_memberships_agent_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memberships_company_user_agent_uq": { + "name": "agent_memberships_company_user_agent_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memberships_company_id_companies_id_fk": { + "name": "agent_memberships_company_id_companies_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memberships_agent_id_agents_id_fk": { + "name": "agent_memberships_agent_id_agents_id_fk", + "tableFrom": "agent_memberships", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_runtime_state": { + "name": "agent_runtime_state", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_json": { + "name": "state_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cached_input_tokens": { + "name": "total_cached_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost_cents": { + "name": "total_cost_cents", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_runtime_state_company_agent_idx": { + "name": "agent_runtime_state_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_runtime_state_company_updated_idx": { + "name": "agent_runtime_state_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_runtime_state_agent_id_agents_id_fk": { + "name": "agent_runtime_state_agent_id_agents_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_runtime_state_company_id_companies_id_fk": { + "name": "agent_runtime_state_company_id_companies_id_fk", + "tableFrom": "agent_runtime_state", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_task_sessions": { + "name": "agent_task_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_key": { + "name": "task_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_params_json": { + "name": "session_params_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_display_id": { + "name": "session_display_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_id": { + "name": "last_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_task_sessions_company_agent_adapter_task_uniq": { + "name": "agent_task_sessions_company_agent_adapter_task_uniq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "adapter_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_agent_updated_idx": { + "name": "agent_task_sessions_company_agent_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_task_sessions_company_task_updated_idx": { + "name": "agent_task_sessions_company_task_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_task_sessions_company_id_companies_id_fk": { + "name": "agent_task_sessions_company_id_companies_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_agent_id_agents_id_fk": { + "name": "agent_task_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_task_sessions_last_run_id_heartbeat_runs_id_fk": { + "name": "agent_task_sessions_last_run_id_heartbeat_runs_id_fk", + "tableFrom": "agent_task_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "last_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_wakeup_requests": { + "name": "agent_wakeup_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "coalesced_count": { + "name": "coalesced_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_by_actor_type": { + "name": "requested_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_actor_id": { + "name": "requested_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_wakeup_requests_company_agent_status_idx": { + "name": "agent_wakeup_requests_company_agent_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_requested_idx": { + "name": "agent_wakeup_requests_company_requested_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_agent_requested_idx": { + "name": "agent_wakeup_requests_agent_requested_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_review_path_recovery_idempotency_uq": { + "name": "agent_wakeup_requests_review_path_recovery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_review_path_lost:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_disposition_repair_idempotency_uq": { + "name": "agent_wakeup_requests_disposition_repair_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'issue_disposition_repair:%' AND \"agent_wakeup_requests\".\"status\" <> 'skipped'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_question_response_delivery_idempotency_uq": { + "name": "agent_wakeup_requests_question_response_delivery_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%' AND \"agent_wakeup_requests\".\"status\" NOT IN ('skipped', 'failed', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_wakeup_requests_company_payload_issue_idx": { + "name": "agent_wakeup_requests_company_payload_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_wakeup_requests_company_id_companies_id_fk": { + "name": "agent_wakeup_requests_company_id_companies_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_wakeup_requests_agent_id_agents_id_fk": { + "name": "agent_wakeup_requests_agent_id_agents_id_fk", + "tableFrom": "agent_wakeup_requests", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "reports_to": { + "name": "reports_to", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'process'" + }, + "adapter_config": { + "name": "adapter_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "runtime_config": { + "name": "runtime_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_reason": { + "name": "error_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_company_status_idx": { + "name": "agents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_reports_to_idx": { + "name": "agents_company_reports_to_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reports_to", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_company_default_environment_idx": { + "name": "agents_company_default_environment_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "default_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_company_id_companies_id_fk": { + "name": "agents_company_id_companies_id_fk", + "tableFrom": "agents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_reports_to_agents_id_fk": { + "name": "agents_reports_to_agents_id_fk", + "tableFrom": "agents", + "tableTo": "agents", + "columnsFrom": [ + "reports_to" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agents_default_environment_id_environments_id_fk": { + "name": "agents_default_environment_id_environments_id_fk", + "tableFrom": "agents", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approval_comments": { + "name": "approval_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_comments_company_idx": { + "name": "approval_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_idx": { + "name": "approval_comments_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_comments_approval_created_idx": { + "name": "approval_comments_approval_created_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_comments_company_id_companies_id_fk": { + "name": "approval_comments_company_id_companies_id_fk", + "tableFrom": "approval_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_approval_id_approvals_id_fk": { + "name": "approval_comments_approval_id_approvals_id_fk", + "tableFrom": "approval_comments", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approval_comments_author_agent_id_agents_id_fk": { + "name": "approval_comments_author_agent_id_agents_id_fk", + "tableFrom": "approval_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.approvals": { + "name": "approvals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approvals_company_status_type_idx": { + "name": "approvals_company_status_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approvals_company_id_companies_id_fk": { + "name": "approvals_company_id_companies_id_fk", + "tableFrom": "approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "approvals_requested_by_agent_id_agents_id_fk": { + "name": "approvals_requested_by_agent_id_agents_id_fk", + "tableFrom": "approvals", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.assets": { + "name": "assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_filename": { + "name": "original_filename", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "assets_company_created_idx": { + "name": "assets_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_provider_idx": { + "name": "assets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "assets_company_object_key_uq": { + "name": "assets_company_object_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "assets_company_id_companies_id_fk": { + "name": "assets_company_id_companies_id_fk", + "tableFrom": "assets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "assets_created_by_agent_id_agents_id_fk": { + "name": "assets_created_by_agent_id_agents_id_fk", + "tableFrom": "assets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_issuer_account_id_uq": { + "name": "account_issuer_account_id_uq", + "columns": [ + { + "expression": "issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.board_api_keys": { + "name": "board_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_keys_key_hash_idx": { + "name": "board_api_keys_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_keys_user_idx": { + "name": "board_api_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_api_keys_user_id_user_id_fk": { + "name": "board_api_keys_user_id_user_id_fk", + "tableFrom": "board_api_keys", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_incidents": { + "name": "budget_incidents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "threshold_type": { + "name": "threshold_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_limit": { + "name": "amount_limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_observed": { + "name": "amount_observed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_incidents_company_status_idx": { + "name": "budget_incidents_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_company_scope_idx": { + "name": "budget_incidents_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_incidents_policy_window_threshold_idx": { + "name": "budget_incidents_policy_window_threshold_idx", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "threshold_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"budget_incidents\".\"status\" <> 'dismissed'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_incidents_company_id_companies_id_fk": { + "name": "budget_incidents_company_id_companies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_policy_id_budget_policies_id_fk": { + "name": "budget_incidents_policy_id_budget_policies_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "budget_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "budget_incidents_approval_id_approvals_id_fk": { + "name": "budget_incidents_approval_id_approvals_id_fk", + "tableFrom": "budget_incidents", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.budget_policies": { + "name": "budget_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'billed_cents'" + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "warn_percent": { + "name": "warn_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "hard_stop_enabled": { + "name": "hard_stop_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notify_enabled": { + "name": "notify_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "budget_policies_company_scope_active_idx": { + "name": "budget_policies_company_scope_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_window_idx": { + "name": "budget_policies_company_window_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "budget_policies_company_scope_metric_unique_idx": { + "name": "budget_policies_company_scope_metric_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "budget_policies_company_id_companies_id_fk": { + "name": "budget_policies_company_id_companies_id_fk", + "tableFrom": "budget_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.built_in_managed_resources": { + "name": "built_in_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_key": { + "name": "bundle_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stock_version": { + "name": "stock_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stock_hash": { + "name": "stock_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "built_in_managed_resources_company_idx": { + "name": "built_in_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_resource_idx": { + "name": "built_in_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "built_in_managed_resources_company_bundle_resource_uq": { + "name": "built_in_managed_resources_company_bundle_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bundle_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "built_in_managed_resources_company_id_companies_id_fk": { + "name": "built_in_managed_resources_company_id_companies_id_fk", + "tableFrom": "built_in_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_attachments": { + "name": "case_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_attachments_company_case_idx": { + "name": "case_attachments_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_attachments_asset_uq": { + "name": "case_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_attachments_company_id_companies_id_fk": { + "name": "case_attachments_company_id_companies_id_fk", + "tableFrom": "case_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_case_id_cases_id_fk": { + "name": "case_attachments_case_id_cases_id_fk", + "tableFrom": "case_attachments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_attachments_asset_id_assets_id_fk": { + "name": "case_attachments_asset_id_assets_id_fk", + "tableFrom": "case_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_documents": { + "name": "case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_documents_company_case_key_uq": { + "name": "case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_document_uq": { + "name": "case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_documents_company_case_updated_idx": { + "name": "case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_documents_company_id_companies_id_fk": { + "name": "case_documents_company_id_companies_id_fk", + "tableFrom": "case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_case_id_cases_id_fk": { + "name": "case_documents_case_id_cases_id_fk", + "tableFrom": "case_documents", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_documents_document_id_documents_id_fk": { + "name": "case_documents_document_id_documents_id_fk", + "tableFrom": "case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.case_events": { + "name": "case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_events_case_created_idx": { + "name": "case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_events_company_case_idx": { + "name": "case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_events_company_id_companies_id_fk": { + "name": "case_events_company_id_companies_id_fk", + "tableFrom": "case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_case_id_cases_id_fk": { + "name": "case_events_case_id_cases_id_fk", + "tableFrom": "case_events", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_events_actor_agent_id_agents_id_fk": { + "name": "case_events_actor_agent_id_agents_id_fk", + "tableFrom": "case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_events_kind_check": { + "name": "case_events_kind_check", + "value": "\"case_events\".\"kind\" in (\n 'created',\n 'updated',\n 'fields_changed',\n 'status_changed',\n 'issue_linked',\n 'issue_unlinked',\n 'document_revised',\n 'child_linked',\n 'attachment_added',\n 'label_added',\n 'label_removed'\n )" + }, + "case_events_actor_type_check": { + "name": "case_events_actor_type_check", + "value": "\"case_events\".\"actor_type\" in ('user', 'agent', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.case_issue_links": { + "name": "case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_issue_links_case_issue_uq": { + "name": "case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_company_case_idx": { + "name": "case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_issue_links_issue_idx": { + "name": "case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_issue_links_company_id_companies_id_fk": { + "name": "case_issue_links_company_id_companies_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_case_id_cases_id_fk": { + "name": "case_issue_links_case_id_cases_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_issue_links_issue_id_issues_id_fk": { + "name": "case_issue_links_issue_id_issues_id_fk", + "tableFrom": "case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "case_issue_links_role_check": { + "name": "case_issue_links_role_check", + "value": "\"case_issue_links\".\"role\" in ('origin', 'work', 'reference')" + } + }, + "isRLSEnabled": false + }, + "public.case_labels": { + "name": "case_labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "case_labels_case_label_uq": { + "name": "case_labels_case_label_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_company_case_idx": { + "name": "case_labels_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "case_labels_label_idx": { + "name": "case_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "case_labels_company_id_companies_id_fk": { + "name": "case_labels_company_id_companies_id_fk", + "tableFrom": "case_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_case_id_cases_id_fk": { + "name": "case_labels_case_id_cases_id_fk", + "tableFrom": "case_labels", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "case_labels_label_id_labels_id_fk": { + "name": "case_labels_label_id_labels_id_fk", + "tableFrom": "case_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cases": { + "name": "cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_number": { + "name": "case_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "case_type": { + "name": "case_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cases_company_case_number_uq": { + "name": "cases_company_case_number_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_identifier_uq": { + "name": "cases_identifier_uq", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_key_uq": { + "name": "cases_company_type_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_status_idx": { + "name": "cases_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_type_idx": { + "name": "cases_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_company_project_idx": { + "name": "cases_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_parent_idx": { + "name": "cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cases_title_search_idx": { + "name": "cases_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_identifier_search_idx": { + "name": "cases_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "cases_summary_search_idx": { + "name": "cases_summary_search_idx", + "columns": [ + { + "expression": "summary", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "cases_company_id_companies_id_fk": { + "name": "cases_company_id_companies_id_fk", + "tableFrom": "cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cases_project_id_projects_id_fk": { + "name": "cases_project_id_projects_id_fk", + "tableFrom": "cases", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_parent_case_id_cases_id_fk": { + "name": "cases_parent_case_id_cases_id_fk", + "tableFrom": "cases", + "tableTo": "cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cases_created_by_agent_id_agents_id_fk": { + "name": "cases_created_by_agent_id_agents_id_fk", + "tableFrom": "cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cases_status_check": { + "name": "cases_status_check", + "value": "\"cases\".\"status\" in ('draft', 'in_progress', 'in_review', 'approved', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.cli_auth_challenges": { + "name": "cli_auth_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_hash": { + "name": "secret_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_access": { + "name": "requested_access", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'board'" + }, + "requested_company_id": { + "name": "requested_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pending_key_hash": { + "name": "pending_key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pending_key_name": { + "name": "pending_key_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cli_auth_challenges_secret_hash_idx": { + "name": "cli_auth_challenges_secret_hash_idx", + "columns": [ + { + "expression": "secret_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_approved_by_idx": { + "name": "cli_auth_challenges_approved_by_idx", + "columns": [ + { + "expression": "approved_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_auth_challenges_requested_company_idx": { + "name": "cli_auth_challenges_requested_company_idx", + "columns": [ + { + "expression": "requested_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_auth_challenges_requested_company_id_companies_id_fk": { + "name": "cli_auth_challenges_requested_company_id_companies_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "companies", + "columnsFrom": [ + "requested_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_approved_by_user_id_user_id_fk": { + "name": "cli_auth_challenges_approved_by_user_id_user_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "user", + "columnsFrom": [ + "approved_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk": { + "name": "cli_auth_challenges_board_api_key_id_board_api_keys_id_fk", + "tableFrom": "cli_auth_challenges", + "tableTo": "board_api_keys", + "columnsFrom": [ + "board_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.companies": { + "name": "companies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issue_prefix": { + "name": "issue_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'PAP'" + }, + "issue_counter": { + "name": "issue_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "budget_monthly_cents": { + "name": "budget_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spent_monthly_cents": { + "name": "spent_monthly_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "default_responsible_user_id": { + "name": "default_responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_board_approval_for_new_agents": { + "name": "require_board_approval_for_new_agents", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interaction_resolver_governance": { + "name": "interaction_resolver_governance", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "feedback_data_sharing_enabled": { + "name": "feedback_data_sharing_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feedback_data_sharing_consent_at": { + "name": "feedback_data_sharing_consent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_consent_by_user_id": { + "name": "feedback_data_sharing_consent_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_data_sharing_terms_version": { + "name": "feedback_data_sharing_terms_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "companies_issue_prefix_idx": { + "name": "companies_issue_prefix_idx", + "columns": [ + { + "expression": "issue_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_logos": { + "name": "company_logos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_logos_company_uq": { + "name": "company_logos_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_logos_asset_uq": { + "name": "company_logos_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_logos_company_id_companies_id_fk": { + "name": "company_logos_company_id_companies_id_fk", + "tableFrom": "company_logos", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_logos_asset_id_assets_id_fk": { + "name": "company_logos_asset_id_assets_id_fk", + "tableFrom": "company_logos", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_memberships": { + "name": "company_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_memberships_company_principal_unique_idx": { + "name": "company_memberships_company_principal_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_principal_status_idx": { + "name": "company_memberships_principal_status_idx", + "columns": [ + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_memberships_company_status_idx": { + "name": "company_memberships_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_memberships_company_id_companies_id_fk": { + "name": "company_memberships_company_id_companies_id_fk", + "tableFrom": "company_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_onboarding_seeds": { + "name": "company_onboarding_seeds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mission": { + "name": "mission", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_role": { + "name": "agent_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_title": { + "name": "first_task_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_task_details": { + "name": "first_task_details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_onboarding_seeds_company_uq": { + "name": "company_onboarding_seeds_company_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_onboarding_seeds_company_id_companies_id_fk": { + "name": "company_onboarding_seeds_company_id_companies_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_onboarding_seeds_goal_id_goals_id_fk": { + "name": "company_onboarding_seeds_goal_id_goals_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_agent_id_agents_id_fk": { + "name": "company_onboarding_seeds_agent_id_agents_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_onboarding_seeds_issue_id_issues_id_fk": { + "name": "company_onboarding_seeds_issue_id_issues_id_fk", + "tableFrom": "company_onboarding_seeds", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_bindings": { + "name": "company_secret_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "projection_allowlist_key": { + "name": "projection_allowlist_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_bindings_company_idx": { + "name": "company_secret_bindings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_secret_idx": { + "name": "company_secret_bindings_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_idx": { + "name": "company_secret_bindings_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_bindings_target_path_uq": { + "name": "company_secret_bindings_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_bindings_company_id_companies_id_fk": { + "name": "company_secret_bindings_company_id_companies_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_bindings_secret_id_company_secrets_id_fk": { + "name": "company_secret_bindings_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_bindings", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_proposals": { + "name": "company_secret_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "proposed_name": { + "name": "proposed_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_key": { + "name": "proposed_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposed_description": { + "name": "proposed_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "justification": { + "name": "justification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_ciphertext": { + "name": "value_ciphertext", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "value_fingerprint_sha256": { + "name": "value_fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_proposal_id": { + "name": "secret_proposal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "projection_class": { + "name": "projection_class", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unclassified'" + }, + "binding_target_policy_snapshot": { + "name": "binding_target_policy_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "proposer_ancestor_ids_snapshot": { + "name": "proposer_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_ancestor_ids_snapshot": { + "name": "target_ancestor_ids_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proposed_by_agent_id": { + "name": "proposed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_secret_id": { + "name": "created_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_binding_config_path": { + "name": "applied_binding_config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ciphertext_scrubbed_at": { + "name": "ciphertext_scrubbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_proposals_company_status_idx": { + "name": "company_secret_proposals_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_proposer_status_idx": { + "name": "company_secret_proposals_proposer_status_idx", + "columns": [ + { + "expression": "proposed_by_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_expiry_idx": { + "name": "company_secret_proposals_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_secret_proposal_idx": { + "name": "company_secret_proposals_secret_proposal_idx", + "columns": [ + { + "expression": "secret_proposal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_proposals_interaction_idx": { + "name": "company_secret_proposals_interaction_idx", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_proposals_company_id_companies_id_fk": { + "name": "company_secret_proposals_company_id_companies_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk": { + "name": "company_secret_proposals_secret_proposal_id_company_secret_proposals_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secret_proposals", + "columnsFrom": [ + "secret_proposal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_target_id_agents_id_fk": { + "name": "company_secret_proposals_target_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_proposed_by_agent_id_agents_id_fk": { + "name": "company_secret_proposals_proposed_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "agents", + "columnsFrom": [ + "proposed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_issue_id_issues_id_fk": { + "name": "company_secret_proposals_origin_issue_id_issues_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk": { + "name": "company_secret_proposals_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk": { + "name": "company_secret_proposals_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secret_proposals_created_secret_id_company_secrets_id_fk": { + "name": "company_secret_proposals_created_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_proposals", + "tableTo": "company_secrets", + "columnsFrom": [ + "created_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secret_proposals_kind_check": { + "name": "company_secret_proposals_kind_check", + "value": "\"company_secret_proposals\".\"kind\" in ('secret', 'binding')" + }, + "company_secret_proposals_status_check": { + "name": "company_secret_proposals_status_check", + "value": "\"company_secret_proposals\".\"status\" in ('pending', 'approved', 'rejected', 'withdrawn', 'expired')" + }, + "company_secret_proposals_projection_check": { + "name": "company_secret_proposals_projection_check", + "value": "\"company_secret_proposals\".\"projection_class\" = 'unclassified'" + }, + "company_secret_proposals_shape_check": { + "name": "company_secret_proposals_shape_check", + "value": "(\n \"company_secret_proposals\".\"kind\" = 'secret'\n and \"company_secret_proposals\".\"proposed_name\" is not null\n and \"company_secret_proposals\".\"proposed_key\" is not null\n and \"company_secret_proposals\".\"secret_id\" is null\n and \"company_secret_proposals\".\"secret_proposal_id\" is null\n and \"company_secret_proposals\".\"target_type\" is null\n and \"company_secret_proposals\".\"target_id\" is null\n and \"company_secret_proposals\".\"config_path\" is null\n ) or (\n \"company_secret_proposals\".\"kind\" = 'binding'\n and ((\"company_secret_proposals\".\"secret_id\" is not null)::int + (\"company_secret_proposals\".\"secret_proposal_id\" is not null)::int) = 1\n and \"company_secret_proposals\".\"target_type\" = 'agent'\n and \"company_secret_proposals\".\"target_id\" is not null\n and \"company_secret_proposals\".\"config_path\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_secret_provider_configs": { + "name": "company_secret_provider_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_details": { + "name": "health_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secret_provider_configs_company_idx": { + "name": "company_secret_provider_configs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_company_provider_idx": { + "name": "company_secret_provider_configs_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_provider_configs_default_uq": { + "name": "company_secret_provider_configs_default_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secret_provider_configs\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_provider_configs_company_id_companies_id_fk": { + "name": "company_secret_provider_configs_company_id_companies_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_provider_configs_created_by_agent_id_agents_id_fk": { + "name": "company_secret_provider_configs_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_provider_configs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secret_versions": { + "name": "company_secret_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "material": { + "name": "material", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "value_sha256": { + "name": "value_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_version_ref": { + "name": "provider_version_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'current'" + }, + "fingerprint_sha256": { + "name": "fingerprint_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotation_job_id": { + "name": "rotation_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "company_secret_versions_secret_idx": { + "name": "company_secret_versions_secret_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_value_sha256_idx": { + "name": "company_secret_versions_value_sha256_idx", + "columns": [ + { + "expression": "value_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_fingerprint_idx": { + "name": "company_secret_versions_fingerprint_idx", + "columns": [ + { + "expression": "fingerprint_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secret_versions_secret_version_uq": { + "name": "company_secret_versions_secret_version_uq", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secret_versions_secret_id_company_secrets_id_fk": { + "name": "company_secret_versions_secret_id_company_secrets_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_secret_versions_created_by_agent_id_agents_id_fk": { + "name": "company_secret_versions_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secret_versions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_secrets": { + "name": "company_secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_version": { + "name": "latest_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_secrets_company_idx": { + "name": "company_secrets_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_scope_idx": { + "name": "company_secrets_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_owner_idx": { + "name": "company_secrets_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_idx": { + "name": "company_secrets_user_definition_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_provider_idx": { + "name": "company_secrets_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_provider_config_idx": { + "name": "company_secrets_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_name_uq": { + "name": "company_secrets_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_company_key_uq": { + "name": "company_secrets_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'company' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_secrets_user_definition_owner_uq": { + "name": "company_secrets_user_definition_owner_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_secrets\".\"scope\" = 'user' and \"company_secrets\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_secrets_company_id_companies_id_fk": { + "name": "company_secrets_company_id_companies_id_fk", + "tableFrom": "company_secrets", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "company_secrets_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "company_secrets", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "company_secrets_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "company_secrets", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_secrets_created_by_agent_id_agents_id_fk": { + "name": "company_secrets_created_by_agent_id_agents_id_fk", + "tableFrom": "company_secrets", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "company_secrets_scope_shape_check": { + "name": "company_secrets_scope_shape_check", + "value": "(\n \"company_secrets\".\"scope\" = 'company'\n and \"company_secrets\".\"owner_user_id\" is null\n and \"company_secrets\".\"user_secret_definition_id\" is null\n ) or (\n \"company_secrets\".\"scope\" = 'user'\n and \"company_secrets\".\"owner_user_id\" is not null\n and \"company_secrets\".\"user_secret_definition_id\" is not null\n )" + } + }, + "isRLSEnabled": false + }, + "public.company_skill_policies": { + "name": "company_skill_policies", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_effect": { + "name": "default_effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rules": { + "name": "rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "company_skill_policies_company_id_companies_id_fk": { + "name": "company_skill_policies_company_id_companies_id_fk", + "tableFrom": "company_skill_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_comments": { + "name": "company_skill_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_comment_id": { + "name": "parent_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_comments_company_skill_created_idx": { + "name": "company_skill_comments_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_comments_parent_idx": { + "name": "company_skill_comments_parent_idx", + "columns": [ + { + "expression": "parent_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_comments_company_id_companies_id_fk": { + "name": "company_skill_comments_company_id_companies_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_company_skill_id_company_skills_id_fk": { + "name": "company_skill_comments_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_comments_parent_comment_id_company_skill_comments_id_fk": { + "name": "company_skill_comments_parent_comment_id_company_skill_comments_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "company_skill_comments", + "columnsFrom": [ + "parent_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_comments_author_agent_id_agents_id_fk": { + "name": "company_skill_comments_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_stars": { + "name": "company_skill_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_stars_skill_agent_idx": { + "name": "company_skill_stars_skill_agent_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_skill_user_idx": { + "name": "company_skill_stars_skill_user_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_stars_company_skill_created_idx": { + "name": "company_skill_stars_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_stars_company_id_companies_id_fk": { + "name": "company_skill_stars_company_id_companies_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_company_skill_id_company_skills_id_fk": { + "name": "company_skill_stars_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_stars_agent_id_agents_id_fk": { + "name": "company_skill_stars_agent_id_agents_id_fk", + "tableFrom": "company_skill_stars", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_inputs": { + "name": "company_skill_test_inputs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_inputs_company_skill_name_idx": { + "name": "company_skill_test_inputs_company_skill_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_inputs_company_skill_active_idx": { + "name": "company_skill_test_inputs_company_skill_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_inputs_company_id_companies_id_fk": { + "name": "company_skill_test_inputs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_inputs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_inputs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_inputs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_run_templates": { + "name": "company_skill_test_run_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_run_templates_company_active_idx": { + "name": "company_skill_test_run_templates_company_active_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_run_templates_company_id_companies_id_fk": { + "name": "company_skill_test_run_templates_company_id_companies_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_created_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk": { + "name": "company_skill_test_run_templates_updated_by_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_run_templates", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_test_runs": { + "name": "company_skill_test_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "input_id": { + "name": "input_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "input_snapshot": { + "name": "input_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "skill_version_id": { + "name": "skill_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_config_snapshot": { + "name": "agent_config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_name": { + "name": "template_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template_body": { + "name": "template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rendered_template_body": { + "name": "rendered_template_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "harness_issue_description": { + "name": "harness_issue_description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "output_document_key": { + "name": "output_document_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'output'" + }, + "output_snapshot": { + "name": "output_snapshot", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_expires_at": { + "name": "harness_issue_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "harness_issue_deleted_at": { + "name": "harness_issue_deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_test_runs_company_skill_created_idx": { + "name": "company_skill_test_runs_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_issue_idx": { + "name": "company_skill_test_runs_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_input_created_idx": { + "name": "company_skill_test_runs_company_input_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_status_idx": { + "name": "company_skill_test_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_test_runs_company_harness_expires_idx": { + "name": "company_skill_test_runs_company_harness_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_issue_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_test_runs_company_id_companies_id_fk": { + "name": "company_skill_test_runs_company_id_companies_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_id_company_skills_id_fk": { + "name": "company_skill_test_runs_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk": { + "name": "company_skill_test_runs_input_id_company_skill_test_inputs_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_test_inputs", + "columnsFrom": [ + "input_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk": { + "name": "company_skill_test_runs_skill_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "skill_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_agent_id_agents_id_fk": { + "name": "company_skill_test_runs_agent_id_agents_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "company_skill_test_runs_issue_id_issues_id_fk": { + "name": "company_skill_test_runs_issue_id_issues_id_fk", + "tableFrom": "company_skill_test_runs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skill_versions": { + "name": "company_skill_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_skill_id": { + "name": "company_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_name": { + "name": "release_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skill_versions_skill_revision_idx": { + "name": "company_skill_versions_skill_revision_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_skill_release_idx": { + "name": "company_skill_versions_skill_release_idx", + "columns": [ + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"company_skill_versions\".\"release_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skill_versions_company_skill_created_idx": { + "name": "company_skill_versions_company_skill_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skill_versions_company_id_companies_id_fk": { + "name": "company_skill_versions_company_id_companies_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_company_skill_id_company_skills_id_fk": { + "name": "company_skill_versions_company_skill_id_company_skills_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "company_skills", + "columnsFrom": [ + "company_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "company_skill_versions_author_agent_id_agents_id_fk": { + "name": "company_skill_versions_author_agent_id_agents_id_fk", + "tableFrom": "company_skill_versions", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_skills": { + "name": "company_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "markdown": { + "name": "markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "source_locator": { + "name": "source_locator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_ref": { + "name": "source_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trust_level": { + "name": "trust_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown_only'" + }, + "compatibility": { + "name": "compatibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compatible'" + }, + "file_inventory": { + "name": "file_inventory", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tagline": { + "name": "tagline", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "sharing_scope": { + "name": "sharing_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "public_share_token": { + "name": "public_share_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from_skill_id": { + "name": "forked_from_skill_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "forked_from_company_id": { + "name": "forked_from_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "star_count": { + "name": "star_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "install_count": { + "name": "install_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fork_count": { + "name": "fork_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_version_id": { + "name": "current_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_skills_company_key_idx": { + "name": "company_skills_company_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_name_idx": { + "name": "company_skills_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_folder_idx": { + "name": "company_skills_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_categories_idx": { + "name": "company_skills_company_categories_idx", + "columns": [ + { + "expression": "categories", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "company_skills_company_sharing_scope_idx": { + "name": "company_skills_company_sharing_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sharing_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_current_version_idx": { + "name": "company_skills_company_current_version_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_skills_company_forked_from_idx": { + "name": "company_skills_company_forked_from_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "forked_from_skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_skills_company_id_companies_id_fk": { + "name": "company_skills_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "company_skills_folder_id_folders_id_fk": { + "name": "company_skills_folder_id_folders_id_fk", + "tableFrom": "company_skills", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_skill_id_company_skills_id_fk": { + "name": "company_skills_forked_from_skill_id_company_skills_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skills", + "columnsFrom": [ + "forked_from_skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_forked_from_company_id_companies_id_fk": { + "name": "company_skills_forked_from_company_id_companies_id_fk", + "tableFrom": "company_skills", + "tableTo": "companies", + "columnsFrom": [ + "forked_from_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "company_skills_current_version_id_company_skill_versions_id_fk": { + "name": "company_skills_current_version_id_company_skill_versions_id_fk", + "tableFrom": "company_skills", + "tableTo": "company_skill_versions", + "columnsFrom": [ + "current_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_transfer_runs": { + "name": "company_transfer_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "actor_key": { + "name": "actor_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "container_ref": { + "name": "container_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manifest_sha256": { + "name": "manifest_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest": { + "name": "manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "blob_count": { + "name": "blob_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "completed_parts": { + "name": "completed_parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_transfer_runs_company_idx": { + "name": "company_transfer_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_idempotency_direction_idx": { + "name": "company_transfer_runs_idempotency_direction_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_transfer_runs_actor_status_idx": { + "name": "company_transfer_runs_actor_status_idx", + "columns": [ + { + "expression": "actor_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_transfer_runs_company_id_companies_id_fk": { + "name": "company_transfer_runs_company_id_companies_id_fk", + "tableFrom": "company_transfer_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.company_user_sidebar_preferences": { + "name": "company_user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_order": { + "name": "project_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "company_user_sidebar_preferences_company_idx": { + "name": "company_user_sidebar_preferences_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_user_idx": { + "name": "company_user_sidebar_preferences_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "company_user_sidebar_preferences_company_user_uq": { + "name": "company_user_sidebar_preferences_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "company_user_sidebar_preferences_company_id_companies_id_fk": { + "name": "company_user_sidebar_preferences_company_id_companies_id_fk", + "tableFrom": "company_user_sidebar_preferences", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.completion_contracts": { + "name": "completion_contracts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completion_authority": { + "name": "completion_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "incomplete_criteria_policy": { + "name": "incomplete_criteria_policy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contract_json": { + "name": "contract_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "supersedes_contract_id": { + "name": "supersedes_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "completion_contracts_issue_revision_uq": { + "name": "completion_contracts_issue_revision_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "completion_contracts_issue_hash_uq": { + "name": "completion_contracts_issue_hash_uq", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "completion_contracts_company_id_companies_id_fk": { + "name": "completion_contracts_company_id_companies_id_fk", + "tableFrom": "completion_contracts", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_issue_company_fk": { + "name": "completion_contracts_issue_company_fk", + "tableFrom": "completion_contracts", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "completion_contracts_supersedes_owner_fk": { + "name": "completion_contracts_supersedes_owner_fk", + "tableFrom": "completion_contracts", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "completion_contracts_company_issue_id_uq": { + "name": "completion_contracts_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cost_events": { + "name": "cost_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "billing_type": { + "name": "billing_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "cost_status": { + "name": "cost_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'reported'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cached_input_tokens": { + "name": "cached_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cost_events_company_occurred_idx": { + "name": "cost_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_agent_occurred_idx": { + "name": "cost_events_company_agent_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_provider_occurred_idx": { + "name": "cost_events_company_provider_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_biller_occurred_idx": { + "name": "cost_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cost_events_company_heartbeat_run_idx": { + "name": "cost_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cost_events_company_id_companies_id_fk": { + "name": "cost_events_company_id_companies_id_fk", + "tableFrom": "cost_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_agent_id_agents_id_fk": { + "name": "cost_events_agent_id_agents_id_fk", + "tableFrom": "cost_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_issue_id_issues_id_fk": { + "name": "cost_events_issue_id_issues_id_fk", + "tableFrom": "cost_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cost_events_project_id_projects_id_fk": { + "name": "cost_events_project_id_projects_id_fk", + "tableFrom": "cost_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_goal_id_goals_id_fk": { + "name": "cost_events_goal_id_goals_id_fk", + "tableFrom": "cost_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cost_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "cost_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "cost_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_archive_notification_outbox": { + "name": "decision_archive_notification_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_archive_notification_outbox_uq": { + "name": "decision_archive_notification_outbox_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archive_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_archive_notification_outbox_pending_idx": { + "name": "decision_archive_notification_outbox_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_archive_notification_outbox_company_id_companies_id_fk": { + "name": "decision_archive_notification_outbox_company_id_companies_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_archive_notification_outbox_origin_agent_id_agents_id_fk": { + "name": "decision_archive_notification_outbox_origin_agent_id_agents_id_fk", + "tableFrom": "decision_archive_notification_outbox", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_archive_notification_outbox_status_check": { + "name": "decision_archive_notification_outbox_status_check", + "value": "\"decision_archive_notification_outbox\".\"status\" IN ('pending', 'delivering', 'delivered')" + } + }, + "isRLSEnabled": false + }, + "public.decision_queue_items": { + "name": "decision_queue_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_type": { + "name": "added_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_agent_id": { + "name": "added_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by_run_id": { + "name": "added_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "added_by_agent_api_key_id": { + "name": "added_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queue_items_queue_source_uq": { + "name": "decision_queue_items_queue_source_uq", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queue_items_company_source_idx": { + "name": "decision_queue_items_company_source_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queue_items_company_id_companies_id_fk": { + "name": "decision_queue_items_company_id_companies_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_id_agents_id_fk": { + "name": "decision_queue_items_added_by_agent_id_agents_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agents", + "columnsFrom": [ + "added_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queue_items_added_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "added_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queue_items_added_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queue_items", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "added_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queue_items_queue_company_fk": { + "name": "decision_queue_items_queue_company_fk", + "tableFrom": "decision_queue_items", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id", + "company_id" + ], + "columnsTo": [ + "id", + "company_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_queue_items_actor_check": { + "name": "decision_queue_items_actor_check", + "value": "(\n (\"decision_queue_items\".\"added_by_type\" = 'agent' AND \"decision_queue_items\".\"added_by_agent_id\" IS NOT NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'user' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NOT NULL)\n OR (\"decision_queue_items\".\"added_by_type\" = 'system' AND \"decision_queue_items\".\"added_by_agent_id\" IS NULL AND \"decision_queue_items\".\"added_by_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_queues": { + "name": "decision_queues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_type": { + "name": "created_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_api_key_id": { + "name": "created_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retention_days": { + "name": "retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seed_rules": { + "name": "seed_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "seed_rules_enabled": { + "name": "seed_rules_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_queues_company_key_uq": { + "name": "decision_queues_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_queues_company_updated_idx": { + "name": "decision_queues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_queues_company_id_companies_id_fk": { + "name": "decision_queues_company_id_companies_id_fk", + "tableFrom": "decision_queues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_id_agents_id_fk": { + "name": "decision_queues_created_by_agent_id_agents_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_queues_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_queues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_queues_created_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_queues", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "created_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "decision_queues_id_company_uq": { + "name": "decision_queues_id_company_uq", + "nullsNotDistinct": false, + "columns": [ + "id", + "company_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "decision_queues_creator_check": { + "name": "decision_queues_creator_check", + "value": "(\n (\"decision_queues\".\"created_by_type\" = 'agent' AND \"decision_queues\".\"created_by_agent_id\" IS NOT NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'user' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NOT NULL)\n OR (\"decision_queues\".\"created_by_type\" = 'system' AND \"decision_queues\".\"created_by_agent_id\" IS NULL AND \"decision_queues\".\"created_by_user_id\" IS NULL)\n )" + }, + "decision_queues_retention_days_check": { + "name": "decision_queues_retention_days_check", + "value": "\"decision_queues\".\"retention_days\" IS NULL OR (\"decision_queues\".\"retention_days\" >= 1 AND \"decision_queues\".\"retention_days\" <= 3650)" + } + }, + "isRLSEnabled": false + }, + "public.decision_retention": { + "name": "decision_retention", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_activity_at": { + "name": "source_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "keep": { + "name": "keep", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_reason": { + "name": "archived_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_type": { + "name": "archived_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "archive_version": { + "name": "archive_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_retention_company_source_uq": { + "name": "decision_retention_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_retention_company_archived_idx": { + "name": "decision_retention_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_retention_company_id_companies_id_fk": { + "name": "decision_retention_company_id_companies_id_fk", + "tableFrom": "decision_retention", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_retention_archived_by_agent_id_agents_id_fk": { + "name": "decision_retention_archived_by_agent_id_agents_id_fk", + "tableFrom": "decision_retention", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_retention_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_retention_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_retention", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_retention_archive_actor_check": { + "name": "decision_retention_archive_actor_check", + "value": "(\n (\"decision_retention\".\"archived_at\" IS NULL AND \"decision_retention\".\"archived_by_type\" IS NULL AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'system' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'agent' AND \"decision_retention\".\"archived_by_agent_id\" IS NOT NULL AND \"decision_retention\".\"archived_by_user_id\" IS NULL)\n OR (\"decision_retention\".\"archived_at\" IS NOT NULL AND \"decision_retention\".\"archived_by_type\" = 'user' AND \"decision_retention\".\"archived_by_agent_id\" IS NULL AND \"decision_retention\".\"archived_by_user_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage": { + "name": "decision_triage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decide_by": { + "name": "decide_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decide_by_date": { + "name": "decide_by_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "set_by_type": { + "name": "set_by_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "set_by_agent_id": { + "name": "set_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_user_id": { + "name": "set_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "set_by_run_id": { + "name": "set_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "set_by_agent_api_key_id": { + "name": "set_by_agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_company_source_uq": { + "name": "decision_triage_company_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_company_decide_by_idx": { + "name": "decision_triage_company_decide_by_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decide_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_company_id_companies_id_fk": { + "name": "decision_triage_company_id_companies_id_fk", + "tableFrom": "decision_triage", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_id_agents_id_fk": { + "name": "decision_triage_set_by_agent_id_agents_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agents", + "columnsFrom": [ + "set_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_set_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "set_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_set_by_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "set_by_agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_actor_check": { + "name": "decision_triage_actor_check", + "value": "(\n (\"decision_triage\".\"set_by_type\" = 'agent' AND \"decision_triage\".\"set_by_agent_id\" IS NOT NULL AND \"decision_triage\".\"set_by_user_id\" IS NULL)\n OR (\"decision_triage\".\"set_by_type\" = 'user' AND \"decision_triage\".\"set_by_agent_id\" IS NULL AND \"decision_triage\".\"set_by_user_id\" IS NOT NULL)\n )" + }, + "decision_triage_decide_by_check": { + "name": "decision_triage_decide_by_check", + "value": "(\n (\"decision_triage\".\"decide_by\" IS NULL AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" IN ('today', 'this_week', 'whenever') AND \"decision_triage\".\"decide_by_date\" IS NULL)\n OR (\"decision_triage\".\"decide_by\" = 'date' AND \"decision_triage\".\"decide_by_date\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_triage_events": { + "name": "decision_triage_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_id": { + "name": "queue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_run_id": { + "name": "actor_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_api_key_id": { + "name": "agent_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_triage_events_company_source_created_idx": { + "name": "decision_triage_events_company_source_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_triage_events_queue_created_idx": { + "name": "decision_triage_events_queue_created_idx", + "columns": [ + { + "expression": "queue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_triage_events_company_id_companies_id_fk": { + "name": "decision_triage_events_company_id_companies_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_triage_events_queue_id_decision_queues_id_fk": { + "name": "decision_triage_events_queue_id_decision_queues_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "decision_queues", + "columnsFrom": [ + "queue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_agent_id_agents_id_fk": { + "name": "decision_triage_events_actor_agent_id_agents_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_actor_run_id_heartbeat_runs_id_fk": { + "name": "decision_triage_events_actor_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "actor_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk": { + "name": "decision_triage_events_agent_api_key_id_agent_api_keys_id_fk", + "tableFrom": "decision_triage_events", + "tableTo": "agent_api_keys", + "columnsFrom": [ + "agent_api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "decision_triage_events_actor_check": { + "name": "decision_triage_events_actor_check", + "value": "(\n (\"decision_triage_events\".\"actor_type\" = 'agent' AND \"decision_triage_events\".\"actor_agent_id\" IS NOT NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'user' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NOT NULL)\n OR (\"decision_triage_events\".\"actor_type\" = 'system' AND \"decision_triage_events\".\"actor_agent_id\" IS NULL AND \"decision_triage_events\".\"actor_user_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.decision_training_examples": { + "name": "decision_training_examples", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cutoff_at": { + "name": "cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notes_history": { + "name": "notes_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "decision_outcome": { + "name": "decision_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retention_policy": { + "name": "retention_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scrub_deleted_comments_v1'" + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_training_examples_company_created_at_idx": { + "name": "decision_training_examples_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_issue_idx": { + "name": "decision_training_examples_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_training_examples_source_author_uq": { + "name": "decision_training_examples_source_author_uq", + "columns": [ + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_training_examples_company_id_companies_id_fk": { + "name": "decision_training_examples_company_id_companies_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_training_examples_issue_id_issues_id_fk": { + "name": "decision_training_examples_issue_id_issues_id_fk", + "tableFrom": "decision_training_examples", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_bundles": { + "name": "decision_bundles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decision_bundles_company_created_at_idx": { + "name": "decision_bundles_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_bundles_company_id_companies_id_fk": { + "name": "decision_bundles_company_id_companies_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_agent_id_agents_id_fk": { + "name": "decision_bundles_origin_agent_id_agents_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_issue_id_issues_id_fk": { + "name": "decision_bundles_origin_issue_id_issues_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_bundles_origin_run_id_heartbeat_runs_id_fk": { + "name": "decision_bundles_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decision_bundles", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_effect_executions": { + "name": "decision_effect_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_index": { + "name": "effect_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_type": { + "name": "effect_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claimed'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activity_log_id": { + "name": "activity_log_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "decision_effect_executions_decision_effect_uq": { + "name": "decision_effect_executions_decision_effect_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effect_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_effect_executions_target_issue_idx": { + "name": "decision_effect_executions_target_issue_idx", + "columns": [ + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_effect_executions_decision_id_decisions_id_fk": { + "name": "decision_effect_executions_decision_id_decisions_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_effect_executions_target_issue_id_issues_id_fk": { + "name": "decision_effect_executions_target_issue_id_issues_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decision_effect_executions_activity_log_id_activity_log_id_fk": { + "name": "decision_effect_executions_activity_log_id_activity_log_id_fk", + "tableFrom": "decision_effect_executions", + "tableTo": "activity_log", + "columnsFrom": [ + "activity_log_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decision_target_issues": { + "name": "decision_target_issues", + "schema": "", + "columns": { + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "decision_target_issues_decision_idx": { + "name": "decision_target_issues_decision_idx", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decision_target_issues_issue_idx": { + "name": "decision_target_issues_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decision_target_issues_decision_id_decisions_id_fk": { + "name": "decision_target_issues_decision_id_decisions_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_issue_id_issues_id_fk": { + "name": "decision_target_issues_issue_id_issues_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "decision_target_issues_company_id_companies_id_fk": { + "name": "decision_target_issues_company_id_companies_id_fk", + "tableFrom": "decision_target_issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "decision_target_issues_decision_id_issue_id_pk": { + "name": "decision_target_issues_decision_id_issue_id_pk", + "columns": [ + "decision_id", + "issue_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decisions": { + "name": "decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "bundle_id": { + "name": "bundle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_agent_id": { + "name": "origin_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_issue_id": { + "name": "origin_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rule_key": { + "name": "rule_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inputs": { + "name": "inputs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "execution_status": { + "name": "execution_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chosen_option_id": { + "name": "chosen_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_values": { + "name": "input_values", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_spec": { + "name": "signed_spec", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_snapshots": { + "name": "target_snapshots", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "decisions_company_status_expires_at_idx": { + "name": "decisions_company_status_expires_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_bundle_idx": { + "name": "decisions_bundle_idx", + "columns": [ + { + "expression": "bundle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_origin_issue_idx": { + "name": "decisions_origin_issue_idx", + "columns": [ + { + "expression": "origin_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "decisions_company_idempotency_uq": { + "name": "decisions_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"decisions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "decisions_company_id_companies_id_fk": { + "name": "decisions_company_id_companies_id_fk", + "tableFrom": "decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_bundle_id_decision_bundles_id_fk": { + "name": "decisions_bundle_id_decision_bundles_id_fk", + "tableFrom": "decisions", + "tableTo": "decision_bundles", + "columnsFrom": [ + "bundle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "decisions_origin_agent_id_agents_id_fk": { + "name": "decisions_origin_agent_id_agents_id_fk", + "tableFrom": "decisions", + "tableTo": "agents", + "columnsFrom": [ + "origin_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_issue_id_issues_id_fk": { + "name": "decisions_origin_issue_id_issues_id_fk", + "tableFrom": "decisions", + "tableTo": "issues", + "columnsFrom": [ + "origin_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decisions_origin_run_id_heartbeat_runs_id_fk": { + "name": "decisions_origin_run_id_heartbeat_runs_id_fk", + "tableFrom": "decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "origin_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_anchor_snapshots": { + "name": "document_annotation_anchor_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_revision_id": { + "name": "from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_revision_number": { + "name": "from_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_revision_id": { + "name": "to_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_revision_number": { + "name": "to_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_anchor": { + "name": "previous_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "next_anchor": { + "name": "next_anchor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_anchor_snapshots_company_thread_created_at_idx": { + "name": "document_annotation_anchor_snapshots_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_anchor_snapshots_company_document_revision_idx": { + "name": "document_annotation_anchor_snapshots_company_document_revision_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_anchor_snapshots_company_id_companies_id_fk": { + "name": "document_annotation_anchor_snapshots_company_id_companies_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_anchor_snapshots_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_document_id_documents_id_fk": { + "name": "document_annotation_anchor_snapshots_document_id_documents_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_from_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk": { + "name": "document_annotation_anchor_snapshots_to_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_anchor_snapshots", + "tableTo": "document_revisions", + "columnsFrom": [ + "to_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_annotation_comments": { + "name": "document_annotation_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_comments_company_thread_created_at_idx": { + "name": "document_annotation_comments_company_thread_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_issue_created_at_idx": { + "name": "document_annotation_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_routine_created_at_idx": { + "name": "document_annotation_comments_company_routine_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_case_created_at_idx": { + "name": "document_annotation_comments_company_case_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_company_document_created_at_idx": { + "name": "document_annotation_comments_company_document_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_issue_comment_idx": { + "name": "document_annotation_comments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_comments_body_search_idx": { + "name": "document_annotation_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_comments_company_id_companies_id_fk": { + "name": "document_annotation_comments_company_id_companies_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_comments_thread_id_document_annotation_threads_id_fk": { + "name": "document_annotation_comments_thread_id_document_annotation_threads_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "document_annotation_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_id_issues_id_fk": { + "name": "document_annotation_comments_issue_id_issues_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_routine_id_routines_id_fk": { + "name": "document_annotation_comments_routine_id_routines_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_case_id_cases_id_fk": { + "name": "document_annotation_comments_case_id_cases_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_document_id_documents_id_fk": { + "name": "document_annotation_comments_document_id_documents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_comments_author_agent_id_agents_id_fk": { + "name": "document_annotation_comments_author_agent_id_agents_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_annotation_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_comments_issue_comment_id_issue_comments_id_fk": { + "name": "document_annotation_comments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "document_annotation_comments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_comments_exactly_one_owner_chk": { + "name": "document_annotation_comments_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_comments\".\"issue_id\", \"document_annotation_comments\".\"routine_id\", \"document_annotation_comments\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_annotation_threads": { + "name": "document_annotation_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "anchor_state": { + "name": "anchor_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "original_revision_id": { + "name": "original_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_revision_number": { + "name": "original_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_revision_id": { + "name": "current_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current_revision_number": { + "name": "current_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "selected_text": { + "name": "selected_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix_text": { + "name": "prefix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "suffix_text": { + "name": "suffix_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "normalized_start": { + "name": "normalized_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "normalized_end": { + "name": "normalized_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_start": { + "name": "markdown_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "markdown_end": { + "name": "markdown_end", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "anchor_confidence": { + "name": "anchor_confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "anchor_selector": { + "name": "anchor_selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_annotation_threads_company_document_status_idx": { + "name": "document_annotation_threads_company_document_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_issue_status_idx": { + "name": "document_annotation_threads_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_routine_status_idx": { + "name": "document_annotation_threads_company_routine_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_case_status_idx": { + "name": "document_annotation_threads_company_case_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_current_revision_open_idx": { + "name": "document_annotation_threads_company_current_revision_open_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "current_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_annotation_threads_company_anchor_state_idx": { + "name": "document_annotation_threads_company_anchor_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anchor_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_annotation_threads_company_id_companies_id_fk": { + "name": "document_annotation_threads_company_id_companies_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "document_annotation_threads_issue_id_issues_id_fk": { + "name": "document_annotation_threads_issue_id_issues_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_routine_id_routines_id_fk": { + "name": "document_annotation_threads_routine_id_routines_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_case_id_cases_id_fk": { + "name": "document_annotation_threads_case_id_cases_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_document_id_documents_id_fk": { + "name": "document_annotation_threads_document_id_documents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_annotation_threads_original_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_original_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "original_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_current_revision_id_document_revisions_id_fk": { + "name": "document_annotation_threads_current_revision_id_document_revisions_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "document_revisions", + "columnsFrom": [ + "current_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_created_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_created_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_annotation_threads_resolved_by_agent_id_agents_id_fk": { + "name": "document_annotation_threads_resolved_by_agent_id_agents_id_fk", + "tableFrom": "document_annotation_threads", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_annotation_threads_exactly_one_owner_chk": { + "name": "document_annotation_threads_exactly_one_owner_chk", + "value": "num_nonnulls(\"document_annotation_threads\".\"issue_id\", \"document_annotation_threads\".\"routine_id\", \"document_annotation_threads\".\"case_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.document_memberships": { + "name": "document_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_memberships_company_user_starred_idx": { + "name": "document_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_memberships_company_user_document_uq": { + "name": "document_memberships_company_user_document_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_memberships_company_id_companies_id_fk": { + "name": "document_memberships_company_id_companies_id_fk", + "tableFrom": "document_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_memberships_document_id_documents_id_fk": { + "name": "document_memberships_document_id_documents_id_fk", + "tableFrom": "document_memberships", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_revisions": { + "name": "document_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "document_revisions_document_revision_uq": { + "name": "document_revisions_document_revision_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "document_revisions_company_document_created_idx": { + "name": "document_revisions_company_document_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_revisions_company_id_companies_id_fk": { + "name": "document_revisions_company_id_companies_id_fk", + "tableFrom": "document_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_document_id_documents_id_fk": { + "name": "document_revisions_document_id_documents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_revisions_created_by_agent_id_agents_id_fk": { + "name": "document_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "document_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "document_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "document_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'markdown'" + }, + "latest_body": { + "name": "latest_body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "locked_by_agent_id": { + "name": "locked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "locked_by_user_id": { + "name": "locked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "documents_company_updated_idx": { + "name": "documents_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_company_created_idx": { + "name": "documents_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "documents_title_search_idx": { + "name": "documents_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "documents_latest_body_search_idx": { + "name": "documents_latest_body_search_idx", + "columns": [ + { + "expression": "latest_body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "documents_company_id_companies_id_fk": { + "name": "documents_company_id_companies_id_fk", + "tableFrom": "documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_created_by_agent_id_agents_id_fk": { + "name": "documents_created_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_updated_by_agent_id_agents_id_fk": { + "name": "documents_updated_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "documents_locked_by_agent_id_agents_id_fk": { + "name": "documents_locked_by_agent_id_agents_id_fk", + "tableFrom": "documents", + "tableTo": "agents", + "columnsFrom": [ + "locked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_setup_sessions": { + "name": "environment_custom_image_setup_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "promoted_template_id": { + "name": "promoted_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment_lease_id": { + "name": "environment_lease_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'starting'" + }, + "started_by_user_id": { + "name": "started_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_by_agent_id": { + "name": "started_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_template_ref": { + "name": "base_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_summary": { + "name": "connection_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "connection_secret_ref": { + "name": "connection_secret_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_setup_sessions_environment_status_idx": { + "name": "environment_custom_image_setup_sessions_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_environment_active_uq": { + "name": "environment_custom_image_setup_sessions_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_setup_sessions\".\"status\" IN ('starting', 'waiting_for_user', 'capturing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_template_idx": { + "name": "environment_custom_image_setup_sessions_template_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_promoted_template_idx": { + "name": "environment_custom_image_setup_sessions_promoted_template_idx", + "columns": [ + { + "expression": "promoted_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_expires_idx": { + "name": "environment_custom_image_setup_sessions_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_setup_sessions_provider_lease_idx": { + "name": "environment_custom_image_setup_sessions_provider_lease_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_setup_sessions_environment_id_environments_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_setup_sessions_promoted_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "promoted_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk": { + "name": "environment_custom_image_setup_sessions_environment_lease_id_environment_leases_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "environment_leases", + "columnsFrom": [ + "environment_lease_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_setup_sessions_started_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_setup_sessions", + "tableTo": "agents", + "columnsFrom": [ + "started_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_custom_image_templates": { + "name": "environment_custom_image_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_kind": { + "name": "template_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "template_ref": { + "name": "template_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_template_ref": { + "name": "source_template_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_environment_config_fingerprint": { + "name": "source_environment_config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "superseded_by_template_id": { + "name": "superseded_by_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_custom_image_templates_environment_status_idx": { + "name": "environment_custom_image_templates_environment_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_provider_status_idx": { + "name": "environment_custom_image_templates_environment_provider_status_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_environment_active_uq": { + "name": "environment_custom_image_templates_environment_active_uq", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environment_custom_image_templates\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_superseded_by_idx": { + "name": "environment_custom_image_templates_superseded_by_idx", + "columns": [ + { + "expression": "superseded_by_template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_custom_image_templates_last_used_idx": { + "name": "environment_custom_image_templates_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_custom_image_templates_environment_id_environments_id_fk": { + "name": "environment_custom_image_templates_environment_id_environments_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_custom_image_templates_created_by_agent_id_agents_id_fk": { + "name": "environment_custom_image_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk": { + "name": "environment_custom_image_templates_superseded_by_template_id_environment_custom_image_templates_id_fk", + "tableFrom": "environment_custom_image_templates", + "tableTo": "environment_custom_image_templates", + "columnsFrom": [ + "superseded_by_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_leases": { + "name": "environment_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "lease_policy": { + "name": "lease_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ephemeral'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_lease_id": { + "name": "provider_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acquired_at": { + "name": "acquired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_status": { + "name": "cleanup_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_leases_company_environment_status_idx": { + "name": "environment_leases_company_environment_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_execution_workspace_idx": { + "name": "environment_leases_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_issue_idx": { + "name": "environment_leases_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_heartbeat_run_idx": { + "name": "environment_leases_heartbeat_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_company_last_used_idx": { + "name": "environment_leases_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_leases_provider_lease_idx": { + "name": "environment_leases_provider_lease_idx", + "columns": [ + { + "expression": "provider_lease_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_leases_company_id_companies_id_fk": { + "name": "environment_leases_company_id_companies_id_fk", + "tableFrom": "environment_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_leases_environment_id_environments_id_fk": { + "name": "environment_leases_environment_id_environments_id_fk", + "tableFrom": "environment_leases", + "tableTo": "environments", + "columnsFrom": [ + "environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "environment_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "environment_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_issue_id_issues_id_fk": { + "name": "environment_leases_issue_id_issues_id_fk", + "tableFrom": "environment_leases", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "environment_leases_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "environment_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environments": { + "name": "environments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver": { + "name": "driver", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "env_vars": { + "name": "env_vars", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environments_status_idx": { + "name": "environments_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_local_driver_idx": { + "name": "environments_local_driver_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'local'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_managed_sandbox_idx": { + "name": "environments_managed_sandbox_idx", + "columns": [ + { + "expression": "driver", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"environments\".\"driver\" = 'sandbox' AND (\"environments\".\"metadata\" ->> 'managedByPaperclip')::boolean = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "environments_name_idx": { + "name": "environments_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspace_runtime_leases": { + "name": "execution_workspace_runtime_leases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_key": { + "name": "owner_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_issue_id": { + "name": "owner_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_action": { + "name": "last_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "renewed_at": { + "name": "renewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspace_runtime_leases_company_workspace_idx": { + "name": "execution_workspace_runtime_leases_company_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_company_owner_idx": { + "name": "execution_workspace_runtime_leases_company_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspace_runtime_leases_expires_at_idx": { + "name": "execution_workspace_runtime_leases_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspace_runtime_leases_company_id_companies_id_fk": { + "name": "execution_workspace_runtime_leases_company_id_companies_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk": { + "name": "execution_workspace_runtime_leases_owner_issue_id_issues_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "issues", + "columnsFrom": [ + "owner_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk": { + "name": "execution_workspace_runtime_leases_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk": { + "name": "execution_workspace_runtime_leases_owner_agent_id_agents_id_fk", + "tableFrom": "execution_workspace_runtime_leases", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "execution_workspace_runtime_leases_execution_workspace_id_unique": { + "name": "execution_workspace_runtime_leases_execution_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "execution_workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_workspaces": { + "name": "execution_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "strategy_type": { + "name": "strategy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_fs'" + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "derived_from_execution_workspace_id": { + "name": "derived_from_execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_eligible_at": { + "name": "cleanup_eligible_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cleanup_reason": { + "name": "cleanup_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_workspaces_company_project_status_idx": { + "name": "execution_workspaces_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_project_workspace_status_idx": { + "name": "execution_workspaces_company_project_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_source_issue_idx": { + "name": "execution_workspaces_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_last_used_idx": { + "name": "execution_workspaces_company_last_used_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_workspaces_company_branch_idx": { + "name": "execution_workspaces_company_branch_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_workspaces_company_id_companies_id_fk": { + "name": "execution_workspaces_company_id_companies_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_id_projects_id_fk": { + "name": "execution_workspaces_project_id_projects_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_workspaces_project_workspace_id_project_workspaces_id_fk": { + "name": "execution_workspaces_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_source_issue_id_issues_id_fk": { + "name": "execution_workspaces_source_issue_id_issues_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk": { + "name": "execution_workspaces_derived_from_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "execution_workspaces", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "derived_from_execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_object_mentions": { + "name": "external_object_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "property_key": { + "name": "property_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text_redacted": { + "name": "matched_text_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sanitized_display_url": { + "name": "sanitized_display_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity": { + "name": "canonical_identity", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_key": { + "name": "detector_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "created_by_plugin_id": { + "name": "created_by_plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_object_mentions_company_source_issue_idx": { + "name": "external_object_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_object_idx": { + "name": "external_object_mentions_company_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_provider_idx": { + "name": "external_object_mentions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_record_uq": { + "name": "external_object_mentions_company_source_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is not null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_object_mentions_company_source_null_record_uq": { + "name": "external_object_mentions_company_source_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "document_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "property_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"external_object_mentions\".\"source_record_id\" is null and \"external_object_mentions\".\"canonical_identity_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_object_mentions_company_id_companies_id_fk": { + "name": "external_object_mentions_company_id_companies_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_source_issue_id_issues_id_fk": { + "name": "external_object_mentions_source_issue_id_issues_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_object_mentions_object_id_external_objects_id_fk": { + "name": "external_object_mentions_object_id_external_objects_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "external_objects", + "columnsFrom": [ + "object_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "external_object_mentions_created_by_plugin_id_plugins_id_fk": { + "name": "external_object_mentions_created_by_plugin_id_plugins_id_fk", + "tableFrom": "external_object_mentions", + "tableTo": "plugins", + "columnsFrom": [ + "created_by_plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_objects": { + "name": "external_objects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_key": { + "name": "provider_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "object_type": { + "name": "object_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sanitized_canonical_url": { + "name": "sanitized_canonical_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_identity_hash": { + "name": "canonical_identity_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_key": { + "name": "display_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_key": { + "name": "icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_title": { + "name": "display_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_key": { + "name": "status_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_label": { + "name": "status_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_icon_key": { + "name": "status_icon_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_category": { + "name": "status_category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "status_tone": { + "name": "status_tone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'neutral'" + }, + "liveness": { + "name": "liveness", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "is_terminal": { + "name": "is_terminal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "remote_version": { + "name": "remote_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_resolved_at": { + "name": "last_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_changed_at": { + "name": "last_changed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_started_at": { + "name": "refresh_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_objects_company_provider_object_idx": { + "name": "external_objects_company_provider_object_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_provider_status_idx": { + "name": "external_objects_company_provider_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_refresh_idx": { + "name": "external_objects_company_refresh_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_external_id_uq": { + "name": "external_objects_company_external_id_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "external_objects_company_identity_uq": { + "name": "external_objects_company_identity_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "canonical_identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_objects_company_id_companies_id_fk": { + "name": "external_objects_company_id_companies_id_fk", + "tableFrom": "external_objects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "external_objects_plugin_id_plugins_id_fk": { + "name": "external_objects_plugin_id_plugins_id_fk", + "tableFrom": "external_objects", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_exports": { + "name": "feedback_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback_vote_id": { + "name": "feedback_vote_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_only'" + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "export_id": { + "name": "export_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-envelope-v2'" + }, + "bundle_version": { + "name": "bundle_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-bundle-v2'" + }, + "payload_version": { + "name": "payload_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip-feedback-v1'" + }, + "payload_digest": { + "name": "payload_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload_snapshot": { + "name": "payload_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "target_summary": { + "name": "target_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exported_at": { + "name": "exported_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_exports_feedback_vote_idx": { + "name": "feedback_exports_feedback_vote_idx", + "columns": [ + { + "expression": "feedback_vote_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_created_idx": { + "name": "feedback_exports_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_status_idx": { + "name": "feedback_exports_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_issue_idx": { + "name": "feedback_exports_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_project_idx": { + "name": "feedback_exports_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_exports_company_author_idx": { + "name": "feedback_exports_company_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_exports_company_id_companies_id_fk": { + "name": "feedback_exports_company_id_companies_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_exports_feedback_vote_id_feedback_votes_id_fk": { + "name": "feedback_exports_feedback_vote_id_feedback_votes_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "feedback_votes", + "columnsFrom": [ + "feedback_vote_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_issue_id_issues_id_fk": { + "name": "feedback_exports_issue_id_issues_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_exports_project_id_projects_id_fk": { + "name": "feedback_exports_project_id_projects_id_fk", + "tableFrom": "feedback_exports", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feedback_votes": { + "name": "feedback_votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vote": { + "name": "vote", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_with_labs": { + "name": "shared_with_labs", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consent_version": { + "name": "consent_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redaction_summary": { + "name": "redaction_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_votes_company_issue_idx": { + "name": "feedback_votes_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_issue_target_idx": { + "name": "feedback_votes_issue_target_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_author_idx": { + "name": "feedback_votes_author_idx", + "columns": [ + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_votes_company_target_author_idx": { + "name": "feedback_votes_company_target_author_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "feedback_votes_company_id_companies_id_fk": { + "name": "feedback_votes_company_id_companies_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "feedback_votes_issue_id_issues_id_fk": { + "name": "feedback_votes_issue_id_issues_id_fk", + "tableFrom": "feedback_votes", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finance_events": { + "name": "finance_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cost_event_id": { + "name": "cost_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_kind": { + "name": "event_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'debit'" + }, + "biller": { + "name": "biller", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_adapter_type": { + "name": "execution_adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing_tier": { + "name": "pricing_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "external_invoice_id": { + "name": "external_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_json": { + "name": "metadata_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "finance_events_company_occurred_idx": { + "name": "finance_events_company_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_biller_occurred_idx": { + "name": "finance_events_company_biller_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "biller", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_kind_occurred_idx": { + "name": "finance_events_company_kind_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_direction_occurred_idx": { + "name": "finance_events_company_direction_occurred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "direction", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_heartbeat_run_idx": { + "name": "finance_events_company_heartbeat_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "finance_events_company_cost_event_idx": { + "name": "finance_events_company_cost_event_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "finance_events_company_id_companies_id_fk": { + "name": "finance_events_company_id_companies_id_fk", + "tableFrom": "finance_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_agent_id_agents_id_fk": { + "name": "finance_events_agent_id_agents_id_fk", + "tableFrom": "finance_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_issue_id_issues_id_fk": { + "name": "finance_events_issue_id_issues_id_fk", + "tableFrom": "finance_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "finance_events_project_id_projects_id_fk": { + "name": "finance_events_project_id_projects_id_fk", + "tableFrom": "finance_events", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_goal_id_goals_id_fk": { + "name": "finance_events_goal_id_goals_id_fk", + "tableFrom": "finance_events", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "finance_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "finance_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "finance_events_cost_event_id_cost_events_id_fk": { + "name": "finance_events_cost_event_id_cost_events_id_fk", + "tableFrom": "finance_events", + "tableTo": "cost_events", + "columnsFrom": [ + "cost_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folders": { + "name": "folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_key": { + "name": "system_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "folders_company_kind_position_idx": { + "name": "folders_company_kind_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_root_slug_uq": { + "name": "folders_company_kind_root_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_slug_uq": { + "name": "folders_company_kind_parent_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"parent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_system_key_uq": { + "name": "folders_company_kind_system_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "system_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folders\".\"system_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folders_company_kind_parent_position_idx": { + "name": "folders_company_kind_parent_position_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folders_company_id_companies_id_fk": { + "name": "folders_company_id_companies_id_fk", + "tableFrom": "folders", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folders_parent_id_folders_id_fk": { + "name": "folders_parent_id_folders_id_fk", + "tableFrom": "folders", + "tableTo": "folders", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.goals": { + "name": "goals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'task'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planned'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "goals_company_idx": { + "name": "goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "goals_company_id_companies_id_fk": { + "name": "goals_company_id_companies_id_fk", + "tableFrom": "goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_parent_id_goals_id_fk": { + "name": "goals_parent_id_goals_id_fk", + "tableFrom": "goals", + "tableTo": "goals", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "goals_owner_agent_id_agents_id_fk": { + "name": "goals_owner_agent_id_agents_id_fk", + "tableFrom": "goals", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_events": { + "name": "heartbeat_run_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream": { + "name": "stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_instance_id": { + "name": "source_instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_event_id": { + "name": "source_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_seq": { + "name": "source_seq", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source_payload_sha256": { + "name": "source_payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "protocol_schema_version": { + "name": "protocol_schema_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_events_run_seq_uq": { + "name": "heartbeat_run_events_run_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_event_uq": { + "name": "heartbeat_run_events_run_source_event_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_event_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_run_source_seq_uq": { + "name": "heartbeat_run_events_run_source_seq_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"heartbeat_run_events\".\"source_instance_id\" is not null and \"heartbeat_run_events\".\"source_seq\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_run_idx": { + "name": "heartbeat_run_events_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_events_company_created_idx": { + "name": "heartbeat_run_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_events_company_id_companies_id_fk": { + "name": "heartbeat_run_events_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_events_agent_id_agents_id_fk": { + "name": "heartbeat_run_events_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_run_watchdog_decisions": { + "name": "heartbeat_run_watchdog_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "evaluation_issue_id": { + "name": "evaluation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_run_watchdog_decisions_company_run_created_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_run_watchdog_decisions_company_run_snooze_idx": { + "name": "heartbeat_run_watchdog_decisions_company_run_snooze_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snoozed_until", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_run_watchdog_decisions_company_id_companies_id_fk": { + "name": "heartbeat_run_watchdog_decisions_company_id_companies_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk": { + "name": "heartbeat_run_watchdog_decisions_evaluation_issue_id_issues_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "issues", + "columnsFrom": [ + "evaluation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_agent_id_agents_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_run_watchdog_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_run_watchdog_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.heartbeat_runs": { + "name": "heartbeat_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_source": { + "name": "invocation_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'on_demand'" + }, + "trigger_detail": { + "name": "trigger_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wakeup_request_id": { + "name": "wakeup_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "signal": { + "name": "signal", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "usage_json": { + "name": "usage_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runtime_mode": { + "name": "runtime_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "runtime_mode_resolver_version": { + "name": "runtime_mode_resolver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_reason": { + "name": "runtime_mode_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_mode_resolved_at": { + "name": "runtime_mode_resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runner_profile_json": { + "name": "runner_profile_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "runner_instance_id": { + "name": "runner_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_session_id": { + "name": "native_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "native_issue_id": { + "name": "native_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "driver_kind": { + "name": "driver_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "driver_version": { + "name": "driver_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completion_contract_sha256": { + "name": "completion_contract_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_event_seq": { + "name": "next_event_seq", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "native_phase": { + "name": "native_phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "native_phase_updated_at": { + "name": "native_phase_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "session_id_before": { + "name": "session_id_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id_after": { + "name": "session_id_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_run_id": { + "name": "external_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_pid": { + "name": "process_pid", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_group_id": { + "name": "process_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "process_started_at": { + "name": "process_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_at": { + "name": "last_output_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_output_seq": { + "name": "last_output_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_output_stream": { + "name": "last_output_stream", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_output_bytes": { + "name": "last_output_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "retry_of_run_id": { + "name": "retry_of_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "process_loss_retry_count": { + "name": "process_loss_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_at": { + "name": "scheduled_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scheduled_retry_attempt": { + "name": "scheduled_retry_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_retry_reason": { + "name": "scheduled_retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_comment_status": { + "name": "issue_comment_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_applicable'" + }, + "issue_comment_satisfied_by_comment_id": { + "name": "issue_comment_satisfied_by_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_comment_retry_queued_at": { + "name": "issue_comment_retry_queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "liveness_state": { + "name": "liveness_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "liveness_reason": { + "name": "liveness_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "continuation_attempt": { + "name": "continuation_attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_useful_action_at": { + "name": "last_useful_action_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context_snapshot": { + "name": "context_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "heartbeat_runs_company_agent_started_idx": { + "name": "heartbeat_runs_company_agent_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_responsible_user_idx": { + "name": "heartbeat_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_liveness_idx": { + "name": "heartbeat_runs_company_liveness_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "liveness_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_last_output_idx": { + "name": "heartbeat_runs_company_status_last_output_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_output_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_status_process_started_idx": { + "name": "heartbeat_runs_company_status_process_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "process_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_created_at_desc_idx": { + "name": "heartbeat_runs_company_created_at_desc_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_issue_created_idx": { + "name": "heartbeat_runs_company_ctx_issue_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'issueId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_task_created_idx": { + "name": "heartbeat_runs_company_ctx_task_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "heartbeat_runs_company_ctx_taskkey_created_idx": { + "name": "heartbeat_runs_company_ctx_taskkey_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"context_snapshot\" ->> 'taskKey')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "heartbeat_runs_company_id_companies_id_fk": { + "name": "heartbeat_runs_company_id_companies_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_agent_id_agents_id_fk": { + "name": "heartbeat_runs_agent_id_agents_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk": { + "name": "heartbeat_runs_wakeup_request_id_agent_wakeup_requests_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "agent_wakeup_requests", + "columnsFrom": [ + "wakeup_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk": { + "name": "heartbeat_runs_retry_of_run_id_heartbeat_runs_id_fk", + "tableFrom": "heartbeat_runs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "retry_of_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "heartbeat_runs_company_native_issue_id_uq": { + "name": "heartbeat_runs_company_native_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id" + ] + }, + "heartbeat_runs_company_native_issue_contract_id_uq": { + "name": "heartbeat_runs_company_native_issue_contract_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inbox_dismissals": { + "name": "inbox_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "item_key": { + "name": "item_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'dismiss'" + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "snoozed_until": { + "name": "snoozed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_dismissals_company_user_idx": { + "name": "inbox_dismissals_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_item_idx": { + "name": "inbox_dismissals_company_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_dismissals_company_user_item_idx": { + "name": "inbox_dismissals_company_user_item_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inbox_dismissals_company_id_companies_id_fk": { + "name": "inbox_dismissals_company_id_companies_id_fk", + "tableFrom": "inbox_dismissals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_delegations": { + "name": "connection_grant_delegations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_delegations_company_agent_idx": { + "name": "connection_grant_delegations_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_delegations_grant_agent_uq": { + "name": "connection_grant_delegations_grant_agent_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_delegations_company_id_companies_id_fk": { + "name": "connection_grant_delegations_company_id_companies_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_agent_id_agents_id_fk": { + "name": "connection_grant_delegations_agent_id_agents_id_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_delegations_company_grant_fk": { + "name": "connection_grant_delegations_company_grant_fk", + "tableFrom": "connection_grant_delegations", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.connection_grant_members": { + "name": "connection_grant_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grant_members_company_subject_idx": { + "name": "connection_grant_members_company_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grant_members_grant_subject_uq": { + "name": "connection_grant_members_grant_subject_uq", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grant_members_company_id_companies_id_fk": { + "name": "connection_grant_members_company_id_companies_id_fk", + "tableFrom": "connection_grant_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grant_members_company_grant_fk": { + "name": "connection_grant_members_company_grant_fk", + "tableFrom": "connection_grant_members", + "tableTo": "connection_grants", + "columnsFrom": [ + "company_id", + "grant_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "connection_grant_members_subject_type_check": { + "name": "connection_grant_members_subject_type_check", + "value": "\"connection_grant_members\".\"subject_type\" in ('user')" + } + }, + "isRLSEnabled": false + }, + "public.connection_grants": { + "name": "connection_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_tenant": { + "name": "provider_tenant", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_by_agent_id": { + "name": "revoked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_by_user_id": { + "name": "revoked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_grants_company_connection_idx": { + "name": "connection_grants_company_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_subject_user_idx": { + "name": "connection_grants_subject_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_user_uq": { + "name": "connection_grants_user_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_grants_default_uq": { + "name": "connection_grants_default_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"connection_grants\".\"is_default\" = true and \"connection_grants\".\"kind\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_grants_company_id_companies_id_fk": { + "name": "connection_grants_company_id_companies_id_fk", + "tableFrom": "connection_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_grants_created_by_agent_id_agents_id_fk": { + "name": "connection_grants_created_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_revoked_by_agent_id_agents_id_fk": { + "name": "connection_grants_revoked_by_agent_id_agents_id_fk", + "tableFrom": "connection_grants", + "tableTo": "agents", + "columnsFrom": [ + "revoked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_grants_company_connection_fk": { + "name": "connection_grants_company_connection_fk", + "tableFrom": "connection_grants", + "tableTo": "tool_connections", + "columnsFrom": [ + "company_id", + "connection_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "connection_grants_company_id_uq": { + "name": "connection_grants_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "connection_grants_kind_check": { + "name": "connection_grants_kind_check", + "value": "\"connection_grants\".\"kind\" in ('organization', 'user')" + }, + "connection_grants_status_check": { + "name": "connection_grants_status_check", + "value": "\"connection_grants\".\"status\" in ('active', 'revoked', 'expired', 'needs_reauthorization')" + }, + "connection_grants_credential_source_one_of_check": { + "name": "connection_grants_credential_source_one_of_check", + "value": "\"connection_grants\".\"external_credential\" is null or jsonb_array_length(\"connection_grants\".\"credential_secret_refs\") = 0" + }, + "connection_grants_subject_check": { + "name": "connection_grants_subject_check", + "value": "(\"connection_grants\".\"kind\" = 'user' and \"connection_grants\".\"subject_user_id\" is not null) or (\"connection_grants\".\"kind\" = 'organization' and \"connection_grants\".\"subject_user_id\" is null)" + }, + "connection_grants_default_check": { + "name": "connection_grants_default_check", + "value": "\"connection_grants\".\"is_default\" = false or \"connection_grants\".\"kind\" = 'organization'" + } + }, + "isRLSEnabled": false + }, + "public.connection_token_issuances": { + "name": "connection_token_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scope": { + "name": "requested_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "issued_scope": { + "name": "issued_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "ttl_seconds": { + "name": "ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "connection_token_issuances_company_created_idx": { + "name": "connection_token_issuances_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_connection_created_idx": { + "name": "connection_token_issuances_connection_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_agent_connection_idx": { + "name": "connection_token_issuances_agent_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "connection_token_issuances_run_idx": { + "name": "connection_token_issuances_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "connection_token_issuances_company_id_companies_id_fk": { + "name": "connection_token_issuances_company_id_companies_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_application_id_tool_applications_id_fk": { + "name": "connection_token_issuances_application_id_tool_applications_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_connection_id_tool_connections_id_fk": { + "name": "connection_token_issuances_connection_id_tool_connections_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_agent_id_agents_id_fk": { + "name": "connection_token_issuances_agent_id_agents_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "connection_token_issuances_run_id_heartbeat_runs_id_fk": { + "name": "connection_token_issuances_run_id_heartbeat_runs_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_issue_id_issues_id_fk": { + "name": "connection_token_issuances_issue_id_issues_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "connection_token_issuances_project_id_projects_id_fk": { + "name": "connection_token_issuances_project_id_projects_id_fk", + "tableFrom": "connection_token_issuances", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_settings": { + "name": "instance_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "singleton_key": { + "name": "singleton_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "default_environment_id": { + "name": "default_environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "general": { + "name": "general", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "experimental": { + "name": "experimental", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_settings_singleton_key_idx": { + "name": "instance_settings_singleton_key_idx", + "columns": [ + { + "expression": "singleton_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "instance_settings_default_environment_id_environments_id_fk": { + "name": "instance_settings_default_environment_id_environments_id_fk", + "tableFrom": "instance_settings", + "tableTo": "environments", + "columnsFrom": [ + "default_environment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.instance_user_roles": { + "name": "instance_user_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'instance_admin'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "instance_user_roles_user_role_unique_idx": { + "name": "instance_user_roles_user_role_unique_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "instance_user_roles_role_idx": { + "name": "instance_user_roles_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invites": { + "name": "invites", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invite_type": { + "name": "invite_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company_join'" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allowed_join_types": { + "name": "allowed_join_types", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'both'" + }, + "defaults_payload": { + "name": "defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "invited_by_user_id": { + "name": "invited_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invites_token_hash_unique_idx": { + "name": "invites_token_hash_unique_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invites_company_invite_state_idx": { + "name": "invites_company_invite_state_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invite_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invites_company_id_companies_id_fk": { + "name": "invites_company_id_companies_id_fk", + "tableFrom": "invites", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_approvals": { + "name": "issue_approvals", + "schema": "", + "columns": { + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "linked_by_agent_id": { + "name": "linked_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "linked_by_user_id": { + "name": "linked_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_approvals_issue_idx": { + "name": "issue_approvals_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_approval_idx": { + "name": "issue_approvals_approval_idx", + "columns": [ + { + "expression": "approval_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_approvals_company_idx": { + "name": "issue_approvals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_approvals_company_id_companies_id_fk": { + "name": "issue_approvals_company_id_companies_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_approvals_issue_id_issues_id_fk": { + "name": "issue_approvals_issue_id_issues_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_approval_id_approvals_id_fk": { + "name": "issue_approvals_approval_id_approvals_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_approvals_linked_by_agent_id_agents_id_fk": { + "name": "issue_approvals_linked_by_agent_id_agents_id_fk", + "tableFrom": "issue_approvals", + "tableTo": "agents", + "columnsFrom": [ + "linked_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_approvals_pk": { + "name": "issue_approvals_pk", + "columns": [ + "issue_id", + "approval_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_attachments": { + "name": "issue_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_comment_id": { + "name": "issue_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_attachments_company_issue_idx": { + "name": "issue_attachments_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_issue_comment_idx": { + "name": "issue_attachments_issue_comment_idx", + "columns": [ + { + "expression": "issue_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_attachments_asset_uq": { + "name": "issue_attachments_asset_uq", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_attachments_company_id_companies_id_fk": { + "name": "issue_attachments_company_id_companies_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_attachments_issue_id_issues_id_fk": { + "name": "issue_attachments_issue_id_issues_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_asset_id_assets_id_fk": { + "name": "issue_attachments_asset_id_assets_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_attachments_issue_comment_id_issue_comments_id_fk": { + "name": "issue_attachments_issue_comment_id_issue_comments_id_fk", + "tableFrom": "issue_attachments", + "tableTo": "issue_comments", + "columnsFrom": [ + "issue_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_comments": { + "name": "issue_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_agent_id": { + "name": "author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "author_user_id": { + "name": "author_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "on_behalf_of_user_id": { + "name": "on_behalf_of_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_type": { + "name": "author_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_agent_id": { + "name": "derived_author_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_created_by_run_id": { + "name": "derived_created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "derived_author_source": { + "name": "derived_author_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "presentation": { + "name": "presentation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_by_type": { + "name": "deleted_by_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_agent_id": { + "name": "deleted_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deleted_by_user_id": { + "name": "deleted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_by_run_id": { + "name": "deleted_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_comments_issue_idx": { + "name": "issue_comments_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_idx": { + "name": "issue_comments_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_issue_created_at_idx": { + "name": "issue_comments_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_company_author_issue_created_at_idx": { + "name": "issue_comments_company_author_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_comments_body_search_idx": { + "name": "issue_comments_body_search_idx", + "columns": [ + { + "expression": "body", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "issue_comments_company_id_companies_id_fk": { + "name": "issue_comments_company_id_companies_id_fk", + "tableFrom": "issue_comments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_issue_id_issues_id_fk": { + "name": "issue_comments_issue_id_issues_id_fk", + "tableFrom": "issue_comments", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_comments_author_agent_id_agents_id_fk": { + "name": "issue_comments_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_comments_on_behalf_of_user_id_user_id_fk": { + "name": "issue_comments_on_behalf_of_user_id_user_id_fk", + "tableFrom": "issue_comments", + "tableTo": "user", + "columnsFrom": [ + "on_behalf_of_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_author_agent_id_agents_id_fk": { + "name": "issue_comments_derived_author_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "derived_author_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_derived_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "derived_created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_agent_id_agents_id_fk": { + "name": "issue_comments_deleted_by_agent_id_agents_id_fk", + "tableFrom": "issue_comments", + "tableTo": "agents", + "columnsFrom": [ + "deleted_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_comments_deleted_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_comments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "deleted_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_create_idempotency_keys": { + "name": "issue_create_idempotency_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_create_idempotency_keys_company_key_uq": { + "name": "issue_create_idempotency_keys_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_issue_idx": { + "name": "issue_create_idempotency_keys_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_create_idempotency_keys_company_created_at_idx": { + "name": "issue_create_idempotency_keys_company_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_create_idempotency_keys_company_id_companies_id_fk": { + "name": "issue_create_idempotency_keys_company_id_companies_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_create_idempotency_keys_issue_id_issues_id_fk": { + "name": "issue_create_idempotency_keys_issue_id_issues_id_fk", + "tableFrom": "issue_create_idempotency_keys", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_documents": { + "name": "issue_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_documents_company_issue_key_uq": { + "name": "issue_documents_company_issue_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_document_uq": { + "name": "issue_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_documents_company_issue_updated_idx": { + "name": "issue_documents_company_issue_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_documents_company_id_companies_id_fk": { + "name": "issue_documents_company_id_companies_id_fk", + "tableFrom": "issue_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_documents_issue_id_issues_id_fk": { + "name": "issue_documents_issue_id_issues_id_fk", + "tableFrom": "issue_documents", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_documents_document_id_documents_id_fk": { + "name": "issue_documents_document_id_documents_id_fk", + "tableFrom": "issue_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_execution_decisions": { + "name": "issue_execution_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_type": { + "name": "stage_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_execution_decisions_company_issue_idx": { + "name": "issue_execution_decisions_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_execution_decisions_stage_idx": { + "name": "issue_execution_decisions_stage_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_execution_decisions_company_id_companies_id_fk": { + "name": "issue_execution_decisions_company_id_companies_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_issue_id_issues_id_fk": { + "name": "issue_execution_decisions_issue_id_issues_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_execution_decisions_actor_agent_id_agents_id_fk": { + "name": "issue_execution_decisions_actor_agent_id_agents_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_execution_decisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_execution_decisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_inbox_archives": { + "name": "issue_inbox_archives", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_by_actor_type": { + "name": "archived_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_by_run_id": { + "name": "archived_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_inbox_archives_company_issue_idx": { + "name": "issue_inbox_archives_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_user_idx": { + "name": "issue_inbox_archives_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_inbox_archives_company_issue_user_idx": { + "name": "issue_inbox_archives_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_inbox_archives_company_id_companies_id_fk": { + "name": "issue_inbox_archives_company_id_companies_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_inbox_archives_issue_id_issues_id_fk": { + "name": "issue_inbox_archives_issue_id_issues_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_agent_id_agents_id_fk": { + "name": "issue_inbox_archives_archived_by_agent_id_agents_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_inbox_archives_archived_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_inbox_archives", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "archived_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_inbox_archives_archived_by_actor_type_check": { + "name": "issue_inbox_archives_archived_by_actor_type_check", + "value": "\"issue_inbox_archives\".\"archived_by_actor_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.issue_labels": { + "name": "issue_labels", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label_id": { + "name": "label_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_labels_issue_idx": { + "name": "issue_labels_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_label_idx": { + "name": "issue_labels_label_idx", + "columns": [ + { + "expression": "label_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_labels_company_idx": { + "name": "issue_labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_labels_issue_id_issues_id_fk": { + "name": "issue_labels_issue_id_issues_id_fk", + "tableFrom": "issue_labels", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_label_id_labels_id_fk": { + "name": "issue_labels_label_id_labels_id_fk", + "tableFrom": "issue_labels", + "tableTo": "labels", + "columnsFrom": [ + "label_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_labels_company_id_companies_id_fk": { + "name": "issue_labels_company_id_companies_id_fk", + "tableFrom": "issue_labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "issue_labels_pk": { + "name": "issue_labels_pk", + "columns": [ + "issue_id", + "label_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_plan_decompositions": { + "name": "issue_plan_decompositions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_plan_revision_id": { + "name": "accepted_plan_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "accepted_interaction_id": { + "name": "accepted_interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'in_flight'" + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_child_count": { + "name": "requested_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "requested_children": { + "name": "requested_children", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "child_issue_ids": { + "name": "child_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_run_id": { + "name": "owner_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_plan_decompositions_company_source_status_idx": { + "name": "issue_plan_decompositions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_active_owner_idx": { + "name": "issue_plan_decompositions_active_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issue_plan_decompositions\".\"status\" = 'in_flight'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_plan_decompositions_source_revision_uq": { + "name": "issue_plan_decompositions_source_revision_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "accepted_plan_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_plan_decompositions_company_id_companies_id_fk": { + "name": "issue_plan_decompositions_company_id_companies_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_plan_decompositions_source_issue_id_issues_id_fk": { + "name": "issue_plan_decompositions_source_issue_id_issues_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk": { + "name": "issue_plan_decompositions_accepted_plan_revision_id_document_revisions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "document_revisions", + "columnsFrom": [ + "accepted_plan_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_plan_decompositions_accepted_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "accepted_interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_agent_id_agents_id_fk": { + "name": "issue_plan_decompositions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk": { + "name": "issue_plan_decompositions_owner_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_plan_decompositions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "owner_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_question_response_deliveries": { + "name": "issue_question_response_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_run_id": { + "name": "target_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_turn_id": { + "name": "target_turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_sha256": { + "name": "payload_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "delivery_mode": { + "name": "delivery_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_count": { + "name": "error_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_question_response_deliveries_interaction_uq": { + "name": "issue_question_response_deliveries_interaction_uq", + "columns": [ + { + "expression": "interaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_correlation_uq": { + "name": "issue_question_response_deliveries_correlation_uq", + "columns": [ + { + "expression": "correlation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_pending_idx": { + "name": "issue_question_response_deliveries_pending_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_question_response_deliveries_company_issue_idx": { + "name": "issue_question_response_deliveries_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_question_response_deliveries_company_id_companies_id_fk": { + "name": "issue_question_response_deliveries_company_id_companies_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_issue_id_issues_id_fk": { + "name": "issue_question_response_deliveries_issue_id_issues_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk": { + "name": "issue_question_response_deliveries_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk": { + "name": "issue_question_response_deliveries_target_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_question_response_deliveries", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "target_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "issue_question_response_deliveries_status_check": { + "name": "issue_question_response_deliveries_status_check", + "value": "\"issue_question_response_deliveries\".\"status\" IN ('pending', 'delivering', 'delivered', 'fallback_queued', 'failed')" + }, + "issue_question_response_deliveries_mode_check": { + "name": "issue_question_response_deliveries_mode_check", + "value": "\"issue_question_response_deliveries\".\"delivery_mode\" IS NULL OR \"issue_question_response_deliveries\".\"delivery_mode\" IN ('steered', 'coalesced', 'wake_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.issue_read_states": { + "name": "issue_read_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_read_states_company_issue_idx": { + "name": "issue_read_states_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_user_idx": { + "name": "issue_read_states_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_read_states_company_issue_user_idx": { + "name": "issue_read_states_company_issue_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_read_states_company_id_companies_id_fk": { + "name": "issue_read_states_company_id_companies_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_read_states_issue_id_issues_id_fk": { + "name": "issue_read_states_issue_id_issues_id_fk", + "tableFrom": "issue_read_states", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_recovery_actions": { + "name": "issue_recovery_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recovery_issue_id": { + "name": "recovery_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_owner_agent_id": { + "name": "previous_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "return_owner_agent_id": { + "name": "return_owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cause": { + "name": "cause", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "next_action": { + "name": "next_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wake_policy": { + "name": "wake_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_policy": { + "name": "monitor_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_note": { + "name": "resolution_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_recovery_actions_company_source_status_idx": { + "name": "issue_recovery_actions_company_source_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_owner_status_idx": { + "name": "issue_recovery_actions_company_owner_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_company_recovery_issue_idx": { + "name": "issue_recovery_actions_company_recovery_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recovery_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_source_uq": { + "name": "issue_recovery_actions_active_source_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_recovery_actions_active_fingerprint_uq": { + "name": "issue_recovery_actions_active_fingerprint_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cause", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_recovery_actions\".\"status\" in ('active', 'escalated')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_recovery_actions_company_id_companies_id_fk": { + "name": "issue_recovery_actions_company_id_companies_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_recovery_actions_source_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_source_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_recovery_actions_recovery_issue_id_issues_id_fk": { + "name": "issue_recovery_actions_recovery_issue_id_issues_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "issues", + "columnsFrom": [ + "recovery_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_previous_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_previous_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "previous_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_recovery_actions_return_owner_agent_id_agents_id_fk": { + "name": "issue_recovery_actions_return_owner_agent_id_agents_id_fk", + "tableFrom": "issue_recovery_actions", + "tableTo": "agents", + "columnsFrom": [ + "return_owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_reference_mentions": { + "name": "issue_reference_mentions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_issue_id": { + "name": "source_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_issue_id": { + "name": "target_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_kind": { + "name": "source_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_record_id": { + "name": "source_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "document_key": { + "name": "document_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_text": { + "name": "matched_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_reference_mentions_company_source_issue_idx": { + "name": "issue_reference_mentions_company_source_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_target_issue_idx": { + "name": "issue_reference_mentions_company_target_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_issue_pair_idx": { + "name": "issue_reference_mentions_company_issue_pair_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_record_uq": { + "name": "issue_reference_mentions_company_source_mention_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_record_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_reference_mentions_company_source_mention_null_record_uq": { + "name": "issue_reference_mentions_company_source_mention_null_record_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_reference_mentions\".\"source_record_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_reference_mentions_company_id_companies_id_fk": { + "name": "issue_reference_mentions_company_id_companies_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_reference_mentions_source_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_source_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "source_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_reference_mentions_target_issue_id_issues_id_fk": { + "name": "issue_reference_mentions_target_issue_id_issues_id_fk", + "tableFrom": "issue_reference_mentions", + "tableTo": "issues", + "columnsFrom": [ + "target_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_relations": { + "name": "issue_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "related_issue_id": { + "name": "related_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_relations_company_issue_idx": { + "name": "issue_relations_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_related_issue_idx": { + "name": "issue_relations_company_related_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_type_idx": { + "name": "issue_relations_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_relations_company_edge_uq": { + "name": "issue_relations_company_edge_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "related_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_relations_company_id_companies_id_fk": { + "name": "issue_relations_company_id_companies_id_fk", + "tableFrom": "issue_relations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_relations_issue_id_issues_id_fk": { + "name": "issue_relations_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_related_issue_id_issues_id_fk": { + "name": "issue_relations_related_issue_id_issues_id_fk", + "tableFrom": "issue_relations", + "tableTo": "issues", + "columnsFrom": [ + "related_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_relations_created_by_agent_id_agents_id_fk": { + "name": "issue_relations_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_relations", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_thread_interactions": { + "name": "issue_thread_interactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "continuation_policy": { + "name": "continuation_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'wake_assignee'" + }, + "requested_resolver_policy": { + "name": "requested_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "effective_resolver_policy": { + "name": "effective_resolver_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anyone'" + }, + "resolver_policy_provenance": { + "name": "resolver_policy_provenance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherited'" + }, + "effective_resolver_policy_source": { + "name": "effective_resolver_policy_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_comment_id": { + "name": "source_comment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_run_id": { + "name": "source_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_agent_id": { + "name": "addressee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "addressee_user_id": { + "name": "addressee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_run_id": { + "name": "resolved_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_thread_interactions_issue_idx": { + "name": "issue_thread_interactions_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_created_at_idx": { + "name": "issue_thread_interactions_company_issue_created_at_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_status_idx": { + "name": "issue_thread_interactions_company_issue_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_company_issue_idempotency_uq": { + "name": "issue_thread_interactions_company_issue_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_thread_interactions\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_source_comment_idx": { + "name": "issue_thread_interactions_source_comment_idx", + "columns": [ + { + "expression": "source_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_agent_idx": { + "name": "issue_thread_interactions_addressee_agent_idx", + "columns": [ + { + "expression": "addressee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_thread_interactions_addressee_user_idx": { + "name": "issue_thread_interactions_addressee_user_idx", + "columns": [ + { + "expression": "addressee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_thread_interactions_company_id_companies_id_fk": { + "name": "issue_thread_interactions_company_id_companies_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_issue_id_issues_id_fk": { + "name": "issue_thread_interactions_issue_id_issues_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_comment_id_issue_comments_id_fk": { + "name": "issue_thread_interactions_source_comment_id_issue_comments_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "issue_comments", + "columnsFrom": [ + "source_comment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_source_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "source_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_created_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_addressee_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_addressee_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "addressee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_agent_id_agents_id_fk": { + "name": "issue_thread_interactions_resolved_by_agent_id_agents_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_thread_interactions_resolved_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_thread_interactions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "resolved_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_hold_members": { + "name": "issue_tree_hold_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hold_id": { + "name": "hold_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depth": { + "name": "depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "issue_identifier": { + "name": "issue_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_status": { + "name": "issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_run_id": { + "name": "active_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "active_run_status": { + "name": "active_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skipped": { + "name": "skipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_hold_members_hold_issue_uq": { + "name": "issue_tree_hold_members_hold_issue_uq", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_company_issue_idx": { + "name": "issue_tree_hold_members_company_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_hold_members_hold_depth_idx": { + "name": "issue_tree_hold_members_hold_depth_idx", + "columns": [ + { + "expression": "hold_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "depth", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_hold_members_company_id_companies_id_fk": { + "name": "issue_tree_hold_members_company_id_companies_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk": { + "name": "issue_tree_hold_members_hold_id_issue_tree_holds_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issue_tree_holds", + "columnsFrom": [ + "hold_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_hold_members_parent_issue_id_issues_id_fk": { + "name": "issue_tree_hold_members_parent_issue_id_issues_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_assignee_agent_id_agents_id_fk": { + "name": "issue_tree_hold_members_assignee_agent_id_agents_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_hold_members_active_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_hold_members", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "active_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_tree_holds": { + "name": "issue_tree_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "root_issue_id": { + "name": "root_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_policy": { + "name": "release_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_at": { + "name": "released_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "released_by_actor_type": { + "name": "released_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_agent_id": { + "name": "released_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "released_by_user_id": { + "name": "released_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "released_by_run_id": { + "name": "released_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_reason": { + "name": "release_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_metadata": { + "name": "release_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_tree_holds_company_root_status_idx": { + "name": "issue_tree_holds_company_root_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_tree_holds_company_status_mode_idx": { + "name": "issue_tree_holds_company_status_mode_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_tree_holds_company_id_companies_id_fk": { + "name": "issue_tree_holds_company_id_companies_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_tree_holds_root_issue_id_issues_id_fk": { + "name": "issue_tree_holds_root_issue_id_issues_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "issues", + "columnsFrom": [ + "root_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_agent_id_agents_id_fk": { + "name": "issue_tree_holds_released_by_agent_id_agents_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "agents", + "columnsFrom": [ + "released_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_tree_holds_released_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_tree_holds", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "released_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_watchdogs": { + "name": "issue_watchdogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "watchdog_agent_id": { + "name": "watchdog_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instructions": { + "name": "instructions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "watchdog_issue_id": { + "name": "watchdog_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_observed_fingerprint": { + "name": "last_observed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_fingerprint": { + "name": "last_reviewed_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_observed_stop_snapshot": { + "name": "last_observed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_reviewed_stop_snapshot": { + "name": "last_reviewed_stop_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trigger_count": { + "name": "trigger_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_run_id": { + "name": "updated_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_watchdogs_company_issue_uq": { + "name": "issue_watchdogs_company_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_status_idx": { + "name": "issue_watchdogs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_agent_idx": { + "name": "issue_watchdogs_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_watchdogs_company_watchdog_issue_uq": { + "name": "issue_watchdogs_company_watchdog_issue_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "watchdog_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issue_watchdogs\".\"watchdog_issue_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_watchdogs_company_id_companies_id_fk": { + "name": "issue_watchdogs_company_id_companies_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_issue_id_issues_id_fk": { + "name": "issue_watchdogs_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_agent_id_agents_id_fk": { + "name": "issue_watchdogs_watchdog_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "watchdog_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_watchdogs_watchdog_issue_id_issues_id_fk": { + "name": "issue_watchdogs_watchdog_issue_id_issues_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "issues", + "columnsFrom": [ + "watchdog_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_created_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_agent_id_agents_id_fk": { + "name": "issue_watchdogs_updated_by_agent_id_agents_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_watchdogs_updated_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_watchdogs", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "updated_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issue_work_products": { + "name": "issue_work_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_service_id": { + "name": "runtime_service_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "review_state": { + "name": "review_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_work_products_company_issue_type_idx": { + "name": "issue_work_products_company_issue_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_execution_workspace_type_idx": { + "name": "issue_work_products_company_execution_workspace_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_provider_external_id_idx": { + "name": "issue_work_products_company_provider_external_id_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_work_products_company_updated_idx": { + "name": "issue_work_products_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issue_work_products_company_id_companies_id_fk": { + "name": "issue_work_products_company_id_companies_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issue_work_products_project_id_projects_id_fk": { + "name": "issue_work_products_project_id_projects_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_issue_id_issues_id_fk": { + "name": "issue_work_products_issue_id_issues_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "issue_work_products_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issue_work_products_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk": { + "name": "issue_work_products_runtime_service_id_workspace_runtime_services_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "workspace_runtime_services", + "columnsFrom": [ + "runtime_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issue_work_products_created_by_run_id_heartbeat_runs_id_fk": { + "name": "issue_work_products_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "issue_work_products", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.issues": { + "name": "issues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "status_version": { + "name": "status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_status_decision_id": { + "name": "last_status_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "work_mode": { + "name": "work_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "harness_kind": { + "name": "harness_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "review_policy": { + "name": "review_policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee_user_id": { + "name": "assignee_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checkout_run_id": { + "name": "checkout_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_run_id": { + "name": "execution_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_agent_name_key": { + "name": "execution_agent_name_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_locked_at": { + "name": "execution_locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin_fingerprint": { + "name": "origin_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "request_depth": { + "name": "request_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_code": { + "name": "billing_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_adapter_overrides": { + "name": "assignee_adapter_overrides", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_policy": { + "name": "execution_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "execution_state": { + "name": "execution_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "monitor_next_check_at": { + "name": "monitor_next_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_wake_requested_at": { + "name": "monitor_wake_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_last_triggered_at": { + "name": "monitor_last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "monitor_attempt_count": { + "name": "monitor_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "monitor_notes": { + "name": "monitor_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "monitor_scheduled_by": { + "name": "monitor_scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_preference": { + "name": "execution_workspace_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_settings": { + "name": "execution_workspace_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_trust": { + "name": "source_trust", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "unblock_descriptor": { + "name": "unblock_descriptor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "blocked_transition_at": { + "name": "blocked_transition_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_owner_notified_at": { + "name": "blocked_owner_notified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "hidden_at": { + "name": "hidden_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issues_company_status_idx": { + "name": "issues_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_harness_kind_idx": { + "name": "issues_company_harness_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "harness_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_status_idx": { + "name": "issues_company_assignee_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_assignee_user_status_idx": { + "name": "issues_company_assignee_user_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_responsible_user_idx": { + "name": "issues_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_parent_idx": { + "name": "issues_company_parent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_idx": { + "name": "issues_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_origin_idx": { + "name": "issues_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_project_workspace_idx": { + "name": "issues_company_project_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_execution_workspace_idx": { + "name": "issues_company_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_monitor_due_idx": { + "name": "issues_company_monitor_due_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "monitor_next_check_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_updated_idx": { + "name": "issues_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_created_idx": { + "name": "issues_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_open_normalized_title_created_idx": { + "name": "issues_open_normalized_title_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(regexp_replace(btrim(\"title\"), '\\s+', ' ', 'g'))", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"issues\".\"hidden_at\" is null and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_company_priority_idx": { + "name": "issues_company_priority_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_identifier_idx": { + "name": "issues_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_title_search_idx": { + "name": "issues_title_search_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_identifier_search_idx": { + "name": "issues_identifier_search_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_description_search_idx": { + "name": "issues_description_search_idx", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "issues_open_routine_execution_uq": { + "name": "issues_open_routine_execution_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'routine_execution'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"execution_run_id\" is not null\n and \"issues\".\"status\" in ('backlog', 'todo', 'in_progress', 'in_review', 'blocked')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_incident_uq": { + "name": "issues_active_liveness_recovery_incident_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_liveness_recovery_leaf_uq": { + "name": "issues_active_liveness_recovery_leaf_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'harness_liveness_escalation'\n and \"issues\".\"origin_fingerprint\" <> 'default'\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stale_run_evaluation_uq": { + "name": "issues_active_stale_run_evaluation_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stale_active_run_evaluation'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_task_watchdog_uq": { + "name": "issues_active_task_watchdog_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'task_watchdog'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_productivity_review_uq": { + "name": "issues_active_productivity_review_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'issue_productivity_review'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_active_stranded_issue_recovery_uq": { + "name": "issues_active_stranded_issue_recovery_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'stranded_issue_recovery'\n and \"issues\".\"origin_id\" is not null\n and \"issues\".\"hidden_at\" is null\n and \"issues\".\"status\" not in ('done', 'cancelled')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "issues_onboarding_first_task_uq": { + "name": "issues_onboarding_first_task_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"issues\".\"origin_kind\" = 'onboarding_first_task'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "issues_company_id_companies_id_fk": { + "name": "issues_company_id_companies_id_fk", + "tableFrom": "issues", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_id_projects_id_fk": { + "name": "issues_project_id_projects_id_fk", + "tableFrom": "issues", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_project_workspace_id_project_workspaces_id_fk": { + "name": "issues_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_goal_id_goals_id_fk": { + "name": "issues_goal_id_goals_id_fk", + "tableFrom": "issues", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_parent_id_issues_id_fk": { + "name": "issues_parent_id_issues_id_fk", + "tableFrom": "issues", + "tableTo": "issues", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_assignee_agent_id_agents_id_fk": { + "name": "issues_assignee_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_checkout_run_id_heartbeat_runs_id_fk": { + "name": "issues_checkout_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "checkout_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_execution_run_id_heartbeat_runs_id_fk": { + "name": "issues_execution_run_id_heartbeat_runs_id_fk", + "tableFrom": "issues", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "execution_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "issues_created_by_agent_id_agents_id_fk": { + "name": "issues_created_by_agent_id_agents_id_fk", + "tableFrom": "issues", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "issues_execution_workspace_id_execution_workspaces_id_fk": { + "name": "issues_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "issues", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "issues_company_id_uq": { + "name": "issues_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.join_requests": { + "name": "join_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "invite_id": { + "name": "invite_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_type": { + "name": "request_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending_approval'" + }, + "request_ip": { + "name": "request_ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requesting_user_id": { + "name": "requesting_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_email_snapshot": { + "name": "request_email_snapshot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_defaults_payload": { + "name": "agent_defaults_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "claim_secret_hash": { + "name": "claim_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_secret_expires_at": { + "name": "claim_secret_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_secret_consumed_at": { + "name": "claim_secret_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_agent_id": { + "name": "created_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_by_user_id": { + "name": "approved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rejected_by_user_id": { + "name": "rejected_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejected_at": { + "name": "rejected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "join_requests_invite_unique_idx": { + "name": "join_requests_invite_unique_idx", + "columns": [ + { + "expression": "invite_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_company_status_type_created_idx": { + "name": "join_requests_company_status_type_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_user_uq": { + "name": "join_requests_pending_human_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requesting_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"requesting_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "join_requests_pending_human_email_uq": { + "name": "join_requests_pending_human_email_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"request_email_snapshot\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"join_requests\".\"request_type\" = 'human' AND \"join_requests\".\"status\" = 'pending_approval' AND \"join_requests\".\"request_email_snapshot\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "join_requests_invite_id_invites_id_fk": { + "name": "join_requests_invite_id_invites_id_fk", + "tableFrom": "join_requests", + "tableTo": "invites", + "columnsFrom": [ + "invite_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_company_id_companies_id_fk": { + "name": "join_requests_company_id_companies_id_fk", + "tableFrom": "join_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "join_requests_created_agent_id_agents_id_fk": { + "name": "join_requests_created_agent_id_agents_id_fk", + "tableFrom": "join_requests", + "tableTo": "agents", + "columnsFrom": [ + "created_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.labels": { + "name": "labels", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "labels_company_idx": { + "name": "labels_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "labels_company_name_idx": { + "name": "labels_company_name_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "labels_company_id_companies_id_fk": { + "name": "labels_company_id_companies_id_fk", + "tableFrom": "labels", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.managed_agent_profiles": { + "name": "managed_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'anthropic_managed_agents'" + }, + "anthropic_agent_id": { + "name": "anthropic_agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beta_version": { + "name": "beta_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed-agents-2026-04-01'" + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-sonnet-5'" + }, + "default_max_list_cost_cents": { + "name": "default_max_list_cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "api_key_secret_id": { + "name": "api_key_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "managed_agent_profiles_company_idx": { + "name": "managed_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_key_uq": { + "name": "managed_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "managed_agent_profiles_company_resource_uq": { + "name": "managed_agent_profiles_company_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "anthropic_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "managed_agent_profiles_company_id_companies_id_fk": { + "name": "managed_agent_profiles_company_id_companies_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk": { + "name": "managed_agent_profiles_api_key_secret_id_company_secrets_id_fk", + "tableFrom": "managed_agent_profiles", + "tableTo": "company_secrets", + "columnsFrom": [ + "api_key_secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "managed_agent_profiles_service_check": { + "name": "managed_agent_profiles_service_check", + "value": "\"managed_agent_profiles\".\"service\" = 'anthropic_managed_agents'" + }, + "managed_agent_profiles_beta_check": { + "name": "managed_agent_profiles_beta_check", + "value": "\"managed_agent_profiles\".\"beta_version\" = 'managed-agents-2026-04-01'" + }, + "managed_agent_profiles_positive_budget_check": { + "name": "managed_agent_profiles_positive_budget_check", + "value": "\"managed_agent_profiles\".\"default_max_list_cost_cents\" > 0" + }, + "managed_agent_profiles_qualified_revision_check": { + "name": "managed_agent_profiles_qualified_revision_check", + "value": "(\"managed_agent_profiles\".\"qualified_at\" IS NULL AND \"managed_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"managed_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"managed_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"managed_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.native_run_finalizations": { + "name": "native_run_finalizations", + "schema": "", + "columns": { + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_owner": { + "name": "lease_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_detail": { + "name": "failure_detail", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "native_run_finalizations_company_id_companies_id_fk": { + "name": "native_run_finalizations_company_id_companies_id_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_issue_company_fk": { + "name": "native_run_finalizations_issue_company_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_run_owner_fk": { + "name": "native_run_finalizations_run_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_result_owner_fk": { + "name": "native_run_finalizations_result_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_assessment_owner_fk": { + "name": "native_run_finalizations_assessment_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_finalizations_decision_owner_fk": { + "name": "native_run_finalizations_decision_owner_fk", + "tableFrom": "native_run_finalizations", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_run_finalizations_assessment_requires_result_check": { + "name": "native_run_finalizations_assessment_requires_result_check", + "value": "\"native_run_finalizations\".\"assessment_id\" is null or \"native_run_finalizations\".\"result_id\" is not null" + }, + "native_run_finalizations_decision_requires_assessment_check": { + "name": "native_run_finalizations_decision_requires_assessment_check", + "value": "\"native_run_finalizations\".\"decision_id\" is null or \"native_run_finalizations\".\"assessment_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.native_run_results": { + "name": "native_run_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completion_contract_id": { + "name": "completion_contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "caller_result_id": { + "name": "caller_result_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caller_dedupe_key": { + "name": "caller_dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_fingerprint": { + "name": "server_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_status": { + "name": "schema_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rejection_code": { + "name": "rejection_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_json": { + "name": "result_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "canonical_sha256": { + "name": "canonical_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "native_run_results_run_fingerprint_uq": { + "name": "native_run_results_run_fingerprint_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "server_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_result_uq": { + "name": "native_run_results_run_caller_result_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_result_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_run_results_run_caller_dedupe_uq": { + "name": "native_run_results_run_caller_dedupe_uq", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "caller_dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_run_results_company_id_companies_id_fk": { + "name": "native_run_results_company_id_companies_id_fk", + "tableFrom": "native_run_results", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_issue_company_fk": { + "name": "native_run_results_issue_company_fk", + "tableFrom": "native_run_results", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_run_contract_owner_fk": { + "name": "native_run_results_run_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id", + "completion_contract_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "native_run_results_completion_contract_owner_fk": { + "name": "native_run_results_completion_contract_owner_fk", + "tableFrom": "native_run_results", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "completion_contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "native_run_results_company_issue_run_id_uq": { + "name": "native_run_results_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_automation_executions": { + "name": "pipeline_automation_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "automation_id": { + "name": "automation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "triggering_event_id": { + "name": "triggering_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_issue_id": { + "name": "execution_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_of_execution_id": { + "name": "retry_of_execution_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_automation_executions_idempotency_uq": { + "name": "pipeline_automation_executions_idempotency_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggering_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_company_case_idx": { + "name": "pipeline_automation_executions_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_routine_idx": { + "name": "pipeline_automation_executions_routine_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_execution_issue_idx": { + "name": "pipeline_automation_executions_execution_issue_idx", + "columns": [ + { + "expression": "execution_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_automation_executions_retry_of_execution_idx": { + "name": "pipeline_automation_executions_retry_of_execution_idx", + "columns": [ + { + "expression": "retry_of_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_automation_executions_company_id_companies_id_fk": { + "name": "pipeline_automation_executions_company_id_companies_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_case_id_pipeline_cases_id_fk": { + "name": "pipeline_automation_executions_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_routine_id_routines_id_fk": { + "name": "pipeline_automation_executions_routine_id_routines_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_automation_executions_execution_issue_id_issues_id_fk": { + "name": "pipeline_automation_executions_execution_issue_id_issues_id_fk", + "tableFrom": "pipeline_automation_executions", + "tableTo": "issues", + "columnsFrom": [ + "execution_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_automation_executions_status_check": { + "name": "pipeline_automation_executions_status_check", + "value": "\"pipeline_automation_executions\".\"status\" in ('succeeded', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_blockers": { + "name": "pipeline_case_blockers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "blocked_by_case_id": { + "name": "blocked_by_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_blockers_case_blocked_by_uq": { + "name": "pipeline_case_blockers_case_blocked_by_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_blocked_by_idx": { + "name": "pipeline_case_blockers_blocked_by_idx", + "columns": [ + { + "expression": "blocked_by_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_blockers_company_case_idx": { + "name": "pipeline_case_blockers_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_blockers_company_id_companies_id_fk": { + "name": "pipeline_case_blockers_company_id_companies_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_blockers_blocked_by_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_blockers", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "blocked_by_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_blockers_no_self_block_check": { + "name": "pipeline_case_blockers_no_self_block_check", + "value": "\"pipeline_case_blockers\".\"case_id\" <> \"pipeline_case_blockers\".\"blocked_by_case_id\"" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_documents": { + "name": "pipeline_case_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_documents_company_case_key_uq": { + "name": "pipeline_case_documents_company_case_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_document_uq": { + "name": "pipeline_case_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_documents_company_case_updated_idx": { + "name": "pipeline_case_documents_company_case_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_documents_company_id_companies_id_fk": { + "name": "pipeline_case_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_documents_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_documents_document_id_documents_id_fk": { + "name": "pipeline_case_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_case_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_case_events": { + "name": "pipeline_case_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_agent_id": { + "name": "actor_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_events_case_created_idx": { + "name": "pipeline_case_events_case_created_idx", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_events_company_case_idx": { + "name": "pipeline_case_events_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_events_company_id_companies_id_fk": { + "name": "pipeline_case_events_company_id_companies_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_events_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_events_actor_agent_id_agents_id_fk": { + "name": "pipeline_case_events_actor_agent_id_agents_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "agents", + "columnsFrom": [ + "actor_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_case_events_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_case_events_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_case_events", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_events_type_check": { + "name": "pipeline_case_events_type_check", + "value": "\"pipeline_case_events\".\"type\" in (\n 'ingested',\n 'updated',\n 'claimed',\n 'lease_released',\n 'lease_expired',\n 'transitioned',\n 'transition_forced',\n 'transition_suggested',\n 'suggestion_resolved',\n 'review_decided',\n 'conversation_opened',\n 'issue_linked',\n 'issue_unlinked',\n 'automation_executed',\n 'automation_failed',\n 'automation_retry_requested',\n 'automation_effects_retired',\n 'automation_retry_dispatched',\n 'blockers_set',\n 'blockers_resolved',\n 'children_terminal',\n 'upstream_drift',\n 'drift_acknowledged'\n )" + }, + "pipeline_case_events_actor_type_check": { + "name": "pipeline_case_events_actor_type_check", + "value": "\"pipeline_case_events\".\"actor_type\" in ('user', 'agent', 'system')" + }, + "pipeline_case_events_agent_run_check": { + "name": "pipeline_case_events_agent_run_check", + "value": "\"pipeline_case_events\".\"actor_type\" <> 'agent' or \"pipeline_case_events\".\"run_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_case_issue_links": { + "name": "pipeline_case_issue_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_case_issue_links_case_issue_uq": { + "name": "pipeline_case_issue_links_case_issue_uq", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_issue_idx": { + "name": "pipeline_case_issue_links_issue_idx", + "columns": [ + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_company_case_idx": { + "name": "pipeline_case_issue_links_company_case_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_case_issue_links_automation_attempt_idx": { + "name": "pipeline_case_issue_links_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_case_issue_links_company_id_companies_id_fk": { + "name": "pipeline_case_issue_links_company_id_companies_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_case_id_pipeline_cases_id_fk": { + "name": "pipeline_case_issue_links_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_case_issue_links_issue_id_issues_id_fk": { + "name": "pipeline_case_issue_links_issue_id_issues_id_fk", + "tableFrom": "pipeline_case_issue_links", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_case_issue_links_role_check": { + "name": "pipeline_case_issue_links_role_check", + "value": "\"pipeline_case_issue_links\".\"role\" in ('origin', 'conversation', 'work', 'automation')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_cases": { + "name": "pipeline_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "case_key": { + "name": "case_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fields": { + "name": "fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "workspace_ref": { + "name": "workspace_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_case_id": { + "name": "parent_case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_case_version": { + "name": "parent_case_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request_key": { + "name": "request_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "automation_attempt_id": { + "name": "automation_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_suggestion": { + "name": "pending_suggestion", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lease_owner_type": { + "name": "lease_owner_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_agent_id": { + "name": "lease_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_user_id": { + "name": "lease_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_kind": { + "name": "terminal_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "retired_by_attempt_id": { + "name": "retired_by_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retired_reason": { + "name": "retired_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hidden_from_board_at": { + "name": "hidden_from_board_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "child_count": { + "name": "child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "terminal_child_count": { + "name": "terminal_child_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "origin_run_id": { + "name": "origin_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_cases_pipeline_case_key_uq": { + "name": "pipeline_cases_pipeline_case_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "case_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_request_key_uq": { + "name": "pipeline_cases_parent_request_key_uq", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"pipeline_cases\".\"request_key\" is not null and \"pipeline_cases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_company_idx": { + "name": "pipeline_cases_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_pipeline_stage_idx": { + "name": "pipeline_cases_pipeline_stage_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_parent_idx": { + "name": "pipeline_cases_parent_idx", + "columns": [ + { + "expression": "parent_case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_automation_attempt_idx": { + "name": "pipeline_cases_automation_attempt_idx", + "columns": [ + { + "expression": "automation_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_retired_idx": { + "name": "pipeline_cases_retired_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retired_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_cases_lease_expires_idx": { + "name": "pipeline_cases_lease_expires_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"pipeline_cases\".\"lease_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_cases_company_id_companies_id_fk": { + "name": "pipeline_cases_company_id_companies_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_pipeline_id_pipelines_id_fk": { + "name": "pipeline_cases_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_cases_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_cases_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "pipeline_cases_parent_case_id_pipeline_cases_id_fk": { + "name": "pipeline_cases_parent_case_id_pipeline_cases_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "pipeline_cases", + "columnsFrom": [ + "parent_case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_lease_agent_id_agents_id_fk": { + "name": "pipeline_cases_lease_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "lease_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipeline_cases_created_by_agent_id_agents_id_fk": { + "name": "pipeline_cases_created_by_agent_id_agents_id_fk", + "tableFrom": "pipeline_cases", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_cases_terminal_kind_check": { + "name": "pipeline_cases_terminal_kind_check", + "value": "\"pipeline_cases\".\"terminal_kind\" is null or \"pipeline_cases\".\"terminal_kind\" in ('done', 'cancelled')" + }, + "pipeline_cases_lease_owner_type_check": { + "name": "pipeline_cases_lease_owner_type_check", + "value": "\"pipeline_cases\".\"lease_owner_type\" is null or \"pipeline_cases\".\"lease_owner_type\" in ('user', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_documents": { + "name": "pipeline_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_documents_company_pipeline_key_uq": { + "name": "pipeline_documents_company_pipeline_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_document_uq": { + "name": "pipeline_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_documents_company_pipeline_updated_idx": { + "name": "pipeline_documents_company_pipeline_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_documents_company_id_companies_id_fk": { + "name": "pipeline_documents_company_id_companies_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_pipeline_id_pipelines_id_fk": { + "name": "pipeline_documents_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_documents_document_id_documents_id_fk": { + "name": "pipeline_documents_document_id_documents_id_fk", + "tableFrom": "pipeline_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipeline_stages": { + "name": "pipeline_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_stages_pipeline_key_uq": { + "name": "pipeline_stages_pipeline_key_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_stages_pipeline_position_idx": { + "name": "pipeline_stages_pipeline_position_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_stages_pipeline_id_pipelines_id_fk": { + "name": "pipeline_stages_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_stages", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pipeline_stages_kind_check": { + "name": "pipeline_stages_kind_check", + "value": "\"pipeline_stages\".\"kind\" in ('working', 'review', 'done', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.pipeline_transitions": { + "name": "pipeline_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "pipeline_id": { + "name": "pipeline_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_stage_id": { + "name": "from_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_stage_id": { + "name": "to_stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipeline_transitions_pipeline_edge_uq": { + "name": "pipeline_transitions_pipeline_edge_uq", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_from_idx": { + "name": "pipeline_transitions_pipeline_from_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipeline_transitions_pipeline_to_idx": { + "name": "pipeline_transitions_pipeline_to_idx", + "columns": [ + { + "expression": "pipeline_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipeline_transitions_pipeline_id_pipelines_id_fk": { + "name": "pipeline_transitions_pipeline_id_pipelines_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipelines", + "columnsFrom": [ + "pipeline_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_from_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_from_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "from_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipeline_transitions_to_stage_id_pipeline_stages_id_fk": { + "name": "pipeline_transitions_to_stage_id_pipeline_stages_id_fk", + "tableFrom": "pipeline_transitions", + "tableTo": "pipeline_stages", + "columnsFrom": [ + "to_stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pipelines": { + "name": "pipelines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enforce_transitions": { + "name": "enforce_transitions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pipelines_company_key_uq": { + "name": "pipelines_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_idx": { + "name": "pipelines_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pipelines_company_project_idx": { + "name": "pipelines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pipelines_company_id_companies_id_fk": { + "name": "pipelines_company_id_companies_id_fk", + "tableFrom": "pipelines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pipelines_project_id_projects_id_fk": { + "name": "pipelines_project_id_projects_id_fk", + "tableFrom": "pipelines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "pipelines_created_by_agent_id_agents_id_fk": { + "name": "pipelines_created_by_agent_id_agents_id_fk", + "tableFrom": "pipelines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_company_settings": { + "name": "plugin_company_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings_json": { + "name": "settings_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_company_settings_company_idx": { + "name": "plugin_company_settings_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_plugin_idx": { + "name": "plugin_company_settings_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_company_settings_company_plugin_uq": { + "name": "plugin_company_settings_company_plugin_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_company_settings_company_id_companies_id_fk": { + "name": "plugin_company_settings_company_id_companies_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_company_settings_plugin_id_plugins_id_fk": { + "name": "plugin_company_settings_plugin_id_plugins_id_fk", + "tableFrom": "plugin_company_settings", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_config": { + "name": "plugin_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config_json": { + "name": "config_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_config_plugin_company_idx": { + "name": "plugin_config_plugin_company_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_config_company_id_companies_id_fk": { + "name": "plugin_config_company_id_companies_id_fk", + "tableFrom": "plugin_config", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_database_namespaces": { + "name": "plugin_database_namespaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_mode": { + "name": "namespace_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'schema'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_database_namespaces_plugin_idx": { + "name": "plugin_database_namespaces_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_namespace_idx": { + "name": "plugin_database_namespaces_namespace_idx", + "columns": [ + { + "expression": "namespace_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_database_namespaces_status_idx": { + "name": "plugin_database_namespaces_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_database_namespaces_plugin_id_plugins_id_fk": { + "name": "plugin_database_namespaces_plugin_id_plugins_id_fk", + "tableFrom": "plugin_database_namespaces", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_entities": { + "name": "plugin_entities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_entities_plugin_idx": { + "name": "plugin_entities_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_company_idx": { + "name": "plugin_entities_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_type_idx": { + "name": "plugin_entities_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_entities_scope_idx": { + "name": "plugin_entities_scope_idx", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_entities_plugin_id_plugins_id_fk": { + "name": "plugin_entities_plugin_id_plugins_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_entities_company_id_companies_id_fk": { + "name": "plugin_entities_company_id_companies_id_fk", + "tableFrom": "plugin_entities", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_entities_external_idx": { + "name": "plugin_entities_external_idx", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "plugin_id", + "entity_type", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_job_runs": { + "name": "plugin_job_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "logs": { + "name": "logs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_job_runs_job_idx": { + "name": "plugin_job_runs_job_idx", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_plugin_idx": { + "name": "plugin_job_runs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_company_idx": { + "name": "plugin_job_runs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_job_runs_status_idx": { + "name": "plugin_job_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_job_runs_job_id_plugin_jobs_id_fk": { + "name": "plugin_job_runs_job_id_plugin_jobs_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugin_jobs", + "columnsFrom": [ + "job_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_plugin_id_plugins_id_fk": { + "name": "plugin_job_runs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_job_runs_company_id_companies_id_fk": { + "name": "plugin_job_runs_company_id_companies_id_fk", + "tableFrom": "plugin_job_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_jobs": { + "name": "plugin_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_key": { + "name": "job_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule": { + "name": "schedule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_jobs_plugin_idx": { + "name": "plugin_jobs_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_next_run_idx": { + "name": "plugin_jobs_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_jobs_unique_idx": { + "name": "plugin_jobs_unique_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_jobs_plugin_id_plugins_id_fk": { + "name": "plugin_jobs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_jobs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_logs": { + "name": "plugin_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_logs_plugin_time_idx": { + "name": "plugin_logs_plugin_time_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_company_idx": { + "name": "plugin_logs_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_logs_level_idx": { + "name": "plugin_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_logs_plugin_id_plugins_id_fk": { + "name": "plugin_logs_plugin_id_plugins_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_logs_company_id_companies_id_fk": { + "name": "plugin_logs_company_id_companies_id_fk", + "tableFrom": "plugin_logs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_managed_resources": { + "name": "plugin_managed_resources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_kind": { + "name": "resource_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "defaults_json": { + "name": "defaults_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_managed_resources_company_idx": { + "name": "plugin_managed_resources_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_plugin_idx": { + "name": "plugin_managed_resources_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_resource_idx": { + "name": "plugin_managed_resources_resource_idx", + "columns": [ + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_managed_resources_company_plugin_resource_uq": { + "name": "plugin_managed_resources_company_plugin_resource_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_managed_resources_company_id_companies_id_fk": { + "name": "plugin_managed_resources_company_id_companies_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_managed_resources_plugin_id_plugins_id_fk": { + "name": "plugin_managed_resources_plugin_id_plugins_id_fk", + "tableFrom": "plugin_managed_resources", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_migrations": { + "name": "plugin_migrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace_name": { + "name": "namespace_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "migration_key": { + "name": "migration_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checksum": { + "name": "checksum", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "plugin_migrations_plugin_key_idx": { + "name": "plugin_migrations_plugin_key_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "migration_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_plugin_idx": { + "name": "plugin_migrations_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_migrations_status_idx": { + "name": "plugin_migrations_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_migrations_plugin_id_plugins_id_fk": { + "name": "plugin_migrations_plugin_id_plugins_id_fk", + "tableFrom": "plugin_migrations", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_state": { + "name": "plugin_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "state_key": { + "name": "state_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value_json": { + "name": "value_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_state_plugin_scope_idx": { + "name": "plugin_state_plugin_scope_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_state_plugin_id_plugins_id_fk": { + "name": "plugin_state_plugin_id_plugins_id_fk", + "tableFrom": "plugin_state", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "plugin_state_unique_entry_idx": { + "name": "plugin_state_unique_entry_idx", + "nullsNotDistinct": true, + "columns": [ + "plugin_id", + "scope_kind", + "scope_id", + "namespace", + "state_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugin_webhook_deliveries": { + "name": "plugin_webhook_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "webhook_key": { + "name": "webhook_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugin_webhook_deliveries_plugin_idx": { + "name": "plugin_webhook_deliveries_plugin_idx", + "columns": [ + { + "expression": "plugin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_company_idx": { + "name": "plugin_webhook_deliveries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_status_idx": { + "name": "plugin_webhook_deliveries_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugin_webhook_deliveries_key_idx": { + "name": "plugin_webhook_deliveries_key_idx", + "columns": [ + { + "expression": "webhook_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "plugin_webhook_deliveries_plugin_id_plugins_id_fk": { + "name": "plugin_webhook_deliveries_plugin_id_plugins_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_webhook_deliveries_company_id_companies_id_fk": { + "name": "plugin_webhook_deliveries_company_id_companies_id_fk", + "tableFrom": "plugin_webhook_deliveries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.plugins": { + "name": "plugins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plugin_key": { + "name": "plugin_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_version": { + "name": "api_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "manifest_json": { + "name": "manifest_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'installed'" + }, + "install_order": { + "name": "install_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "package_path": { + "name": "package_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "plugins_plugin_key_idx": { + "name": "plugins_plugin_key_idx", + "columns": [ + { + "expression": "plugin_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "plugins_status_idx": { + "name": "plugins_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_permission_grants": { + "name": "principal_permission_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "principal_type": { + "name": "principal_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_key": { + "name": "permission_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "granted_by_user_id": { + "name": "granted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_permission_grants_unique_idx": { + "name": "principal_permission_grants_unique_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "principal_permission_grants_company_permission_idx": { + "name": "principal_permission_grants_company_permission_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_permission_grants_company_id_companies_id_fk": { + "name": "principal_permission_grants_company_id_companies_id_fk", + "tableFrom": "principal_permission_grants", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_goals": { + "name": "project_goals", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_goals_project_idx": { + "name": "project_goals_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_goal_idx": { + "name": "project_goals_goal_idx", + "columns": [ + { + "expression": "goal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_goals_company_idx": { + "name": "project_goals_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_goals_project_id_projects_id_fk": { + "name": "project_goals_project_id_projects_id_fk", + "tableFrom": "project_goals", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_goal_id_goals_id_fk": { + "name": "project_goals_goal_id_goals_id_fk", + "tableFrom": "project_goals", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_goals_company_id_companies_id_fk": { + "name": "project_goals_company_id_companies_id_fk", + "tableFrom": "project_goals", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_goals_project_id_goal_id_pk": { + "name": "project_goals_project_id_goal_id_pk", + "columns": [ + "project_id", + "goal_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_memberships": { + "name": "project_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'joined'" + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_memberships_company_user_idx": { + "name": "project_memberships_company_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_starred_idx": { + "name": "project_memberships_company_user_starred_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_project_idx": { + "name": "project_memberships_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_memberships_company_user_project_uq": { + "name": "project_memberships_company_user_project_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_memberships_company_id_companies_id_fk": { + "name": "project_memberships_company_id_companies_id_fk", + "tableFrom": "project_memberships", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_memberships_project_id_projects_id_fk": { + "name": "project_memberships_project_id_projects_id_fk", + "tableFrom": "project_memberships", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project_workspaces": { + "name": "project_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_path'" + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_ref": { + "name": "repo_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_ref": { + "name": "default_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "setup_command": { + "name": "setup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cleanup_command": { + "name": "cleanup_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_provider": { + "name": "remote_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "remote_workspace_ref": { + "name": "remote_workspace_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_workspace_key": { + "name": "shared_workspace_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_primary": { + "name": "is_primary", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_workspaces_company_project_idx": { + "name": "project_workspaces_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_primary_idx": { + "name": "project_workspaces_project_primary_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_primary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_source_type_idx": { + "name": "project_workspaces_project_source_type_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_company_shared_key_idx": { + "name": "project_workspaces_company_shared_key_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shared_workspace_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_workspaces_project_remote_ref_idx": { + "name": "project_workspaces_project_remote_ref_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_workspace_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspaces_company_id_companies_id_fk": { + "name": "project_workspaces_company_id_companies_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "project_workspaces_project_id_projects_id_fk": { + "name": "project_workspaces_project_id_projects_id_fk", + "tableFrom": "project_workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'backlog'" + }, + "lead_agent_id": { + "name": "lead_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_date": { + "name": "target_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "pause_reason": { + "name": "pause_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_policy": { + "name": "execution_workspace_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_company_idx": { + "name": "projects_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_company_id_companies_id_fk": { + "name": "projects_company_id_companies_id_fk", + "tableFrom": "projects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_goal_id_goals_id_fk": { + "name": "projects_goal_id_goals_id_fk", + "tableFrom": "projects", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "projects_lead_agent_id_agents_id_fk": { + "name": "projects_lead_agent_id_agents_id_fk", + "tableFrom": "projects", + "tableTo": "agents", + "columnsFrom": [ + "lead_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_trace_records": { + "name": "provider_trace_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'capturing'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trace_ref": { + "name": "trace_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frame_count": { + "name": "frame_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "digest": { + "name": "digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by": { + "name": "requested_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_trace_records_run_unique": { + "name": "provider_trace_records_run_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_expiry_idx": { + "name": "provider_trace_records_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_trace_records_company_created_idx": { + "name": "provider_trace_records_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_trace_records_company_id_companies_id_fk": { + "name": "provider_trace_records_company_id_companies_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_trace_records_run_id_heartbeat_runs_id_fk": { + "name": "provider_trace_records_run_id_heartbeat_runs_id_fk", + "tableFrom": "provider_trace_records", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.remote_agent_profiles": { + "name": "remote_agent_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration": { + "name": "configuration", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retention_acknowledged": { + "name": "retention_acknowledged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualification": { + "name": "qualification", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "qualified_at": { + "name": "qualified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "qualified_revision": { + "name": "qualified_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "remote_agent_profiles_company_idx": { + "name": "remote_agent_profiles_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "remote_agent_profiles_company_key_uq": { + "name": "remote_agent_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "remote_agent_profiles_company_id_companies_id_fk": { + "name": "remote_agent_profiles_company_id_companies_id_fk", + "tableFrom": "remote_agent_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "remote_agent_profiles_service_check": { + "name": "remote_agent_profiles_service_check", + "value": "\"remote_agent_profiles\".\"service\" = 'aws_bedrock_agentcore_harness'" + }, + "remote_agent_profiles_qualified_revision_check": { + "name": "remote_agent_profiles_qualified_revision_check", + "value": "(\"remote_agent_profiles\".\"qualified_at\" IS NULL AND \"remote_agent_profiles\".\"qualified_revision\" IS NULL) OR (\"remote_agent_profiles\".\"qualified_at\" IS NOT NULL AND \"remote_agent_profiles\".\"qualification\" <> '{}'::jsonb AND \"remote_agent_profiles\".\"qualified_revision\" ~ '^sha256:[0-9a-f]{64}$')" + } + }, + "isRLSEnabled": false + }, + "public.routine_documents": { + "name": "routine_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_documents_company_routine_key_uq": { + "name": "routine_documents_company_routine_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_document_uq": { + "name": "routine_documents_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_documents_company_routine_updated_idx": { + "name": "routine_documents_company_routine_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_documents_company_id_companies_id_fk": { + "name": "routine_documents_company_id_companies_id_fk", + "tableFrom": "routine_documents", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routine_documents_routine_id_routines_id_fk": { + "name": "routine_documents_routine_id_routines_id_fk", + "tableFrom": "routine_documents", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_documents_document_id_documents_id_fk": { + "name": "routine_documents_document_id_documents_id_fk", + "tableFrom": "routine_documents", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_revisions": { + "name": "routine_revisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "revision_number": { + "name": "revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "restored_from_revision_id": { + "name": "restored_from_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_run_id": { + "name": "created_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_revisions_routine_revision_uq": { + "name": "routine_revisions_routine_revision_uq", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "revision_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_routine_created_idx": { + "name": "routine_revisions_company_routine_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_revisions_company_responsible_user_idx": { + "name": "routine_revisions_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_revisions_company_id_companies_id_fk": { + "name": "routine_revisions_company_id_companies_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_routine_id_routines_id_fk": { + "name": "routine_revisions_routine_id_routines_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_revisions_restored_from_revision_id_routine_revisions_id_fk": { + "name": "routine_revisions_restored_from_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "routine_revisions", + "columnsFrom": [ + "restored_from_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_agent_id_agents_id_fk": { + "name": "routine_revisions_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_revisions_created_by_run_id_heartbeat_runs_id_fk": { + "name": "routine_revisions_created_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "routine_revisions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "created_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_runs": { + "name": "routine_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "routine_revision_id": { + "name": "routine_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_payload": { + "name": "trigger_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "dispatch_fingerprint": { + "name": "dispatch_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linked_issue_id": { + "name": "linked_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "coalesced_into_run_id": { + "name": "coalesced_into_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_runs_company_routine_idx": { + "name": "routine_runs_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_revision_idx": { + "name": "routine_runs_revision_idx", + "columns": [ + { + "expression": "routine_revision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_company_responsible_user_idx": { + "name": "routine_runs_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idx": { + "name": "routine_runs_trigger_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_dispatch_fingerprint_idx": { + "name": "routine_runs_dispatch_fingerprint_idx", + "columns": [ + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatch_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_linked_issue_idx": { + "name": "routine_runs_linked_issue_idx", + "columns": [ + { + "expression": "linked_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_runs_trigger_idempotency_idx": { + "name": "routine_runs_trigger_idempotency_idx", + "columns": [ + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_runs_company_id_companies_id_fk": { + "name": "routine_runs_company_id_companies_id_fk", + "tableFrom": "routine_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_routine_id_routines_id_fk": { + "name": "routine_runs_routine_id_routines_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_runs_trigger_id_routine_triggers_id_fk": { + "name": "routine_runs_trigger_id_routine_triggers_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_triggers", + "columnsFrom": [ + "trigger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_routine_revision_id_routine_revisions_id_fk": { + "name": "routine_runs_routine_revision_id_routine_revisions_id_fk", + "tableFrom": "routine_runs", + "tableTo": "routine_revisions", + "columnsFrom": [ + "routine_revision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_runs_linked_issue_id_issues_id_fk": { + "name": "routine_runs_linked_issue_id_issues_id_fk", + "tableFrom": "routine_runs", + "tableTo": "issues", + "columnsFrom": [ + "linked_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routine_triggers": { + "name": "routine_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "routine_id": { + "name": "routine_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "signing_mode": { + "name": "signing_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "replay_window_sec": { + "name": "replay_window_sec", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_result": { + "name": "last_result", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routine_triggers_company_routine_idx": { + "name": "routine_triggers_company_routine_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "routine_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_company_kind_idx": { + "name": "routine_triggers_company_kind_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_next_run_idx": { + "name": "routine_triggers_next_run_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_idx": { + "name": "routine_triggers_public_id_idx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routine_triggers_public_id_uq": { + "name": "routine_triggers_public_id_uq", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routine_triggers_company_id_companies_id_fk": { + "name": "routine_triggers_company_id_companies_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_routine_id_routines_id_fk": { + "name": "routine_triggers_routine_id_routines_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "routines", + "columnsFrom": [ + "routine_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routine_triggers_secret_id_company_secrets_id_fk": { + "name": "routine_triggers_secret_id_company_secrets_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_created_by_agent_id_agents_id_fk": { + "name": "routine_triggers_created_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routine_triggers_updated_by_agent_id_agents_id_fk": { + "name": "routine_triggers_updated_by_agent_id_agents_id_fk", + "tableFrom": "routine_triggers", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routines": { + "name": "routines", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "goal_id": { + "name": "goal_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_issue_id": { + "name": "parent_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_agent_id": { + "name": "assignee_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "concurrency_policy": { + "name": "concurrency_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'coalesce_if_active'" + }, + "catch_up_policy": { + "name": "catch_up_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'skip_missed'" + }, + "activity_gate_policy": { + "name": "activity_gate_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'always'" + }, + "activity_gate_scope": { + "name": "activity_gate_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "origin_kind": { + "name": "origin_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "origin_id": { + "name": "origin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env": { + "name": "env", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "latest_revision_id": { + "name": "latest_revision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_revision_number": { + "name": "latest_revision_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_triggered_at": { + "name": "last_triggered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_enqueued_at": { + "name": "last_enqueued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routines_company_status_idx": { + "name": "routines_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_assignee_idx": { + "name": "routines_company_assignee_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assignee_agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_project_idx": { + "name": "routines_company_project_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_folder_idx": { + "name": "routines_company_folder_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_responsible_user_idx": { + "name": "routines_company_responsible_user_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "responsible_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routines_company_origin_idx": { + "name": "routines_company_origin_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "origin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routines_company_id_companies_id_fk": { + "name": "routines_company_id_companies_id_fk", + "tableFrom": "routines", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_project_id_projects_id_fk": { + "name": "routines_project_id_projects_id_fk", + "tableFrom": "routines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "routines_folder_id_folders_id_fk": { + "name": "routines_folder_id_folders_id_fk", + "tableFrom": "routines", + "tableTo": "folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_goal_id_goals_id_fk": { + "name": "routines_goal_id_goals_id_fk", + "tableFrom": "routines", + "tableTo": "goals", + "columnsFrom": [ + "goal_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_parent_issue_id_issues_id_fk": { + "name": "routines_parent_issue_id_issues_id_fk", + "tableFrom": "routines", + "tableTo": "issues", + "columnsFrom": [ + "parent_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_assignee_agent_id_agents_id_fk": { + "name": "routines_assignee_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "assignee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "routines_created_by_agent_id_agents_id_fk": { + "name": "routines_created_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "routines_updated_by_agent_id_agents_id_fk": { + "name": "routines_updated_by_agent_id_agents_id_fk", + "tableFrom": "routines", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_access_events": { + "name": "secret_access_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'company'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responsible_user_id": { + "name": "responsible_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_owner_user_id": { + "name": "credential_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_type": { + "name": "credential_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_subject_id": { + "name": "credential_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consumer_type": { + "name": "consumer_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "consumer_id": { + "name": "consumer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_access_events_company_created_idx": { + "name": "secret_access_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_secret_created_idx": { + "name": "secret_access_events_secret_created_idx", + "columns": [ + { + "expression": "secret_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_user_definition_created_idx": { + "name": "secret_access_events_user_definition_created_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_company_credential_owner_idx": { + "name": "secret_access_events_company_credential_owner_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_consumer_idx": { + "name": "secret_access_events_consumer_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "consumer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_access_events_run_idx": { + "name": "secret_access_events_run_idx", + "columns": [ + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_access_events_company_id_companies_id_fk": { + "name": "secret_access_events_company_id_companies_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "secret_access_events_secret_id_company_secrets_id_fk": { + "name": "secret_access_events_secret_id_company_secrets_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "company_secrets", + "columnsFrom": [ + "secret_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "secret_access_events_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_issue_id_issues_id_fk": { + "name": "secret_access_events_issue_id_issues_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "secret_access_events_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "secret_access_events_plugin_id_plugins_id_fk": { + "name": "secret_access_events_plugin_id_plugins_id_fk", + "tableFrom": "secret_access_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_run_steps": { + "name": "smoke_run_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scenario_step": { + "name": "scenario_step", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "screenshot_artifact_ref": { + "name": "screenshot_artifact_ref", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_run_steps_company_run_idx": { + "name": "smoke_run_steps_company_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_run_steps_company_path_idx": { + "name": "smoke_run_steps_company_path_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_run_steps_company_id_companies_id_fk": { + "name": "smoke_run_steps_company_id_companies_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smoke_run_steps_run_id_smoke_runs_id_fk": { + "name": "smoke_run_steps_run_id_smoke_runs_id_fk", + "tableFrom": "smoke_run_steps", + "tableTo": "smoke_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smoke_runs": { + "name": "smoke_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smoke_runs_company_started_idx": { + "name": "smoke_runs_company_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "smoke_runs_company_status_idx": { + "name": "smoke_runs_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smoke_runs_company_id_companies_id_fk": { + "name": "smoke_runs_company_id_companies_id_fk", + "tableFrom": "smoke_runs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_card_updates": { + "name": "status_card_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_issue_id": { + "name": "generation_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "changes": { + "name": "changes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cost_cents": { + "name": "cost_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "change_summary": { + "name": "change_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "status_card_updates_card_started_idx": { + "name": "status_card_updates_card_started_idx", + "columns": [ + { + "expression": "card_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_card_updates_generation_issue_idx": { + "name": "status_card_updates_generation_issue_idx", + "columns": [ + { + "expression": "generation_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_card_updates_card_id_status_cards_id_fk": { + "name": "status_card_updates_card_id_status_cards_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "status_cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_card_updates_generation_issue_id_issues_id_fk": { + "name": "status_card_updates_generation_issue_id_issues_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "issues", + "columnsFrom": [ + "generation_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_card_updates_run_id_heartbeat_runs_id_fk": { + "name": "status_card_updates_run_id_heartbeat_runs_id_fk", + "tableFrom": "status_card_updates", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_cards": { + "name": "status_cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_pinned": { + "name": "title_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "interest_prompt": { + "name": "interest_prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "query_version": { + "name": "query_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "query_compiled_at": { + "name": "query_compiled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "query_compiled_by_agent_id": { + "name": "query_compiled_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "refresh_policy": { + "name": "refresh_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'compiling'" + }, + "pending_change_count": { + "name": "pending_change_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_change_hash": { + "name": "pending_change_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_change_at": { + "name": "last_change_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "fingerprint": { + "name": "fingerprint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fingerprint_at": { + "name": "fingerprint_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "mentioned_issue_ids": { + "name": "mentioned_issue_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_update_run_kind": { + "name": "last_update_run_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_eval_at": { + "name": "next_eval_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_by_user_id": { + "name": "archived_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_by_agent_id": { + "name": "archived_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_cards_company_archived_idx": { + "name": "status_cards_company_archived_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_cards_company_next_eval_idx": { + "name": "status_cards_company_next_eval_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_eval_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_cards_company_id_companies_id_fk": { + "name": "status_cards_company_id_companies_id_fk", + "tableFrom": "status_cards", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_cards_created_by_agent_id_agents_id_fk": { + "name": "status_cards_created_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_query_compiled_by_agent_id_agents_id_fk": { + "name": "status_cards_query_compiled_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "query_compiled_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_agent_id_agents_id_fk": { + "name": "status_cards_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_document_id_documents_id_fk": { + "name": "status_cards_document_id_documents_id_fk", + "tableFrom": "status_cards", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_generating_issue_id_issues_id_fk": { + "name": "status_cards_generating_issue_id_issues_id_fk", + "tableFrom": "status_cards", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "status_cards_archived_by_agent_id_agents_id_fk": { + "name": "status_cards_archived_by_agent_id_agents_id_fk", + "tableFrom": "status_cards", + "tableTo": "agents", + "columnsFrom": [ + "archived_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decision_effects": { + "name": "status_decision_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "effect_kind": { + "name": "effect_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decision_effects_decision_ordinal_uq": { + "name": "status_decision_effects_decision_ordinal_uq", + "columns": [ + { + "expression": "decision_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decision_effects_company_idempotency_uq": { + "name": "status_decision_effects_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decision_effects_company_id_companies_id_fk": { + "name": "status_decision_effects_company_id_companies_id_fk", + "tableFrom": "status_decision_effects", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_issue_company_fk": { + "name": "status_decision_effects_issue_company_fk", + "tableFrom": "status_decision_effects", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decision_effects_decision_owner_fk": { + "name": "status_decision_effects_decision_owner_fk", + "tableFrom": "status_decision_effects", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.status_decisions": { + "name": "status_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assessment_id": { + "name": "assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_version": { + "name": "decision_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "decision_json": { + "name": "decision_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "decision_digest": { + "name": "decision_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "application_state": { + "name": "application_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'proposed'" + }, + "supersedes_decision_id": { + "name": "supersedes_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "status_decisions_company_issue_version_uq": { + "name": "status_decisions_company_issue_version_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_assessment_uq": { + "name": "status_decisions_company_assessment_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assessment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "status_decisions_company_issue_digest_uq": { + "name": "status_decisions_company_issue_digest_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "decision_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "status_decisions_company_id_companies_id_fk": { + "name": "status_decisions_company_id_companies_id_fk", + "tableFrom": "status_decisions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_issue_company_fk": { + "name": "status_decisions_issue_company_fk", + "tableFrom": "status_decisions", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_assessment_owner_fk": { + "name": "status_decisions_assessment_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_decisions_supersedes_owner_fk": { + "name": "status_decisions_supersedes_owner_fk", + "tableFrom": "status_decisions", + "tableTo": "status_decisions", + "columnsFrom": [ + "company_id", + "issue_id", + "supersedes_decision_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "status_decisions_company_issue_id_uq": { + "name": "status_decisions_company_issue_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "id" + ] + }, + "status_decisions_company_issue_run_assessment_id_uq": { + "name": "status_decisions_company_issue_run_assessment_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "assessment_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summary_slots": { + "name": "summary_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_kind": { + "name": "scope_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generating_issue_id": { + "name": "generating_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_generated_at": { + "name": "last_generated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_generated_by_agent_id": { + "name": "last_generated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summary_slots_document_uq": { + "name": "summary_slots_document_uq", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_scope_idx": { + "name": "summary_slots_company_scope_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_generating_issue_idx": { + "name": "summary_slots_company_generating_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generating_issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "summary_slots_company_updated_idx": { + "name": "summary_slots_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "summary_slots_company_id_companies_id_fk": { + "name": "summary_slots_company_id_companies_id_fk", + "tableFrom": "summary_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "summary_slots_document_id_documents_id_fk": { + "name": "summary_slots_document_id_documents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_generating_issue_id_issues_id_fk": { + "name": "summary_slots_generating_issue_id_issues_id_fk", + "tableFrom": "summary_slots", + "tableTo": "issues", + "columnsFrom": [ + "generating_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "summary_slots_last_generated_by_agent_id_agents_id_fk": { + "name": "summary_slots_last_generated_by_agent_id_agents_id_fk", + "tableFrom": "summary_slots", + "tableTo": "agents", + "columnsFrom": [ + "last_generated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "summary_slots_company_scope_slot_uq": { + "name": "summary_slots_company_scope_slot_uq", + "nullsNotDistinct": true, + "columns": [ + "company_id", + "scope_kind", + "scope_id", + "slot_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_access_audit_events": { + "name": "tool_access_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_access_audit_company_created_idx": { + "name": "tool_access_audit_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_connection_idx": { + "name": "tool_access_audit_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_access_audit_gateway_idx": { + "name": "tool_access_audit_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_access_audit_events_company_id_companies_id_fk": { + "name": "tool_access_audit_events_company_id_companies_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_access_audit_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_access_audit_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_connection_id_tool_connections_id_fk": { + "name": "tool_access_audit_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_access_audit_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_access_audit_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_action_requests": { + "name": "tool_action_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_id": { + "name": "approval_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "canonical_arguments_hash": { + "name": "canonical_arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_arguments_summary": { + "name": "canonical_arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "signed_arguments": { + "name": "signed_arguments", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_markdown": { + "name": "preview_markdown", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_agent_id": { + "name": "requested_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_by_agent_id": { + "name": "resolved_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "resolved_by_user_id": { + "name": "resolved_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by_agent_id": { + "name": "decided_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_action_requests_company_status_idx": { + "name": "tool_action_requests_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_invocation_idx": { + "name": "tool_action_requests_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_action_requests_issue_idx": { + "name": "tool_action_requests_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_action_requests_company_id_companies_id_fk": { + "name": "tool_action_requests_company_id_companies_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_invocation_id_tool_invocations_id_fk": { + "name": "tool_action_requests_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_action_requests_issue_id_issues_id_fk": { + "name": "tool_action_requests_issue_id_issues_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_interaction_id_issue_thread_interactions_id_fk": { + "name": "tool_action_requests_interaction_id_issue_thread_interactions_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "issue_thread_interactions", + "columnsFrom": [ + "interaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_approval_id_approvals_id_fk": { + "name": "tool_action_requests_approval_id_approvals_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "approvals", + "columnsFrom": [ + "approval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_requested_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_requested_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "requested_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_resolved_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_resolved_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "resolved_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_action_requests_decided_by_agent_id_agents_id_fk": { + "name": "tool_action_requests_decided_by_agent_id_agents_id_fk", + "tableFrom": "tool_action_requests", + "tableTo": "agents", + "columnsFrom": [ + "decided_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_applications": { + "name": "tool_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "plugin_id": { + "name": "plugin_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_applications_company_idx": { + "name": "tool_applications_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_status_idx": { + "name": "tool_applications_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_name_uq": { + "name": "tool_applications_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_applications_company_key_uq": { + "name": "tool_applications_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_applications_company_id_companies_id_fk": { + "name": "tool_applications_company_id_companies_id_fk", + "tableFrom": "tool_applications", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_applications_plugin_id_plugins_id_fk": { + "name": "tool_applications_plugin_id_plugins_id_fk", + "tableFrom": "tool_applications", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_applications_owner_agent_id_agents_id_fk": { + "name": "tool_applications_owner_agent_id_agents_id_fk", + "tableFrom": "tool_applications", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_call_events": { + "name": "tool_call_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invocation_id": { + "name": "invocation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action_request_id": { + "name": "action_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "runtime_slot_id": { + "name": "runtime_slot_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reason_code": { + "name": "reason_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_summary": { + "name": "request_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redaction_plan": { + "name": "redaction_plan", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rate_limit_state": { + "name": "rate_limit_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_call_events_company_created_idx": { + "name": "tool_call_events_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_run_idx": { + "name": "tool_call_events_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_issue_idx": { + "name": "tool_call_events_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_invocation_idx": { + "name": "tool_call_events_invocation_idx", + "columns": [ + { + "expression": "invocation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_call_events_gateway_idx": { + "name": "tool_call_events_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_call_events_company_id_companies_id_fk": { + "name": "tool_call_events_company_id_companies_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_call_events_agent_id_agents_id_fk": { + "name": "tool_call_events_agent_id_agents_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_run_id_heartbeat_runs_id_fk": { + "name": "tool_call_events_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_issue_id_issues_id_fk": { + "name": "tool_call_events_issue_id_issues_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_call_events_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_call_events_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_application_id_tool_applications_id_fk": { + "name": "tool_call_events_application_id_tool_applications_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_connection_id_tool_connections_id_fk": { + "name": "tool_call_events_connection_id_tool_connections_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_call_events_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_invocation_id_tool_invocations_id_fk": { + "name": "tool_call_events_invocation_id_tool_invocations_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_invocations", + "columnsFrom": [ + "invocation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_action_request_id_tool_action_requests_id_fk": { + "name": "tool_call_events_action_request_id_tool_action_requests_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_action_requests", + "columnsFrom": [ + "action_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk": { + "name": "tool_call_events_runtime_slot_id_tool_runtime_slots_id_fk", + "tableFrom": "tool_call_events", + "tableTo": "tool_runtime_slots", + "columnsFrom": [ + "runtime_slot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_catalog_entries": { + "name": "tool_catalog_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entry_kind": { + "name": "entry_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'tool'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "output_schema": { + "name": "output_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "annotations": { + "name": "annotations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "is_read_only": { + "name": "is_read_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_write": { + "name": "is_write", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_destructive": { + "name": "is_destructive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version_hash": { + "name": "version_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_hash": { + "name": "schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_agent_id": { + "name": "reviewed_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reviewed_by_user_id": { + "name": "reviewed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quarantined_at": { + "name": "quarantined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "quarantine_reason": { + "name": "quarantine_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_catalog_entries_company_idx": { + "name": "tool_catalog_entries_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_application_idx": { + "name": "tool_catalog_entries_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_idx": { + "name": "tool_catalog_entries_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_company_status_idx": { + "name": "tool_catalog_entries_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_catalog_entries_connection_name_uq": { + "name": "tool_catalog_entries_connection_name_uq", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_catalog_entries_company_id_companies_id_fk": { + "name": "tool_catalog_entries_company_id_companies_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_application_id_tool_applications_id_fk": { + "name": "tool_catalog_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_connection_id_tool_connections_id_fk": { + "name": "tool_catalog_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk": { + "name": "tool_catalog_entries_reviewed_by_agent_id_agents_id_fk", + "tableFrom": "tool_catalog_entries", + "tableTo": "agents", + "columnsFrom": [ + "reviewed_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_connection_installs": { + "name": "tool_connection_installs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connection_installs_company_target_idx": { + "name": "tool_connection_installs_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_connection_idx": { + "name": "tool_connection_installs_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connection_installs_target_uq": { + "name": "tool_connection_installs_target_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connection_installs_company_id_companies_id_fk": { + "name": "tool_connection_installs_company_id_companies_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_connection_id_tool_connections_id_fk": { + "name": "tool_connection_installs_connection_id_tool_connections_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connection_installs_created_by_agent_id_agents_id_fk": { + "name": "tool_connection_installs_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connection_installs", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tool_connection_installs_target_type_check": { + "name": "tool_connection_installs_target_type_check", + "value": "\"tool_connection_installs\".\"target_type\" in ('company', 'agent')" + } + }, + "isRLSEnabled": false + }, + "public.tool_connections": { + "name": "tool_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uid": { + "name": "uid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connection_kind": { + "name": "connection_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'managed'" + }, + "ownership": { + "name": "ownership", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'customer'" + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "credential_source": { + "name": "credential_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_vault'" + }, + "external_credential": { + "name": "external_credential", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_policy": { + "name": "credential_policy", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shared'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "transport_config": { + "name": "transport_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "credential_secret_refs": { + "name": "credential_secret_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_checked_at": { + "name": "health_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_health_at": { + "name": "last_health_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_catalog_refresh_at": { + "name": "last_catalog_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_connections_company_idx": { + "name": "tool_connections_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_application_idx": { + "name": "tool_connections_application_idx", + "columns": [ + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_enabled_idx": { + "name": "tool_connections_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_connections_company_uid_uq": { + "name": "tool_connections_company_uid_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_connections_company_id_companies_id_fk": { + "name": "tool_connections_company_id_companies_id_fk", + "tableFrom": "tool_connections", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_connections_application_id_tool_applications_id_fk": { + "name": "tool_connections_application_id_tool_applications_id_fk", + "tableFrom": "tool_connections", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tool_connections_created_by_agent_id_agents_id_fk": { + "name": "tool_connections_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_connections", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tool_connections_company_id_uq": { + "name": "tool_connections_company_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "tool_connections_ownership_check": { + "name": "tool_connections_ownership_check", + "value": "\"tool_connections\".\"ownership\" in ('platform_shared', 'platform_provisioned', 'customer', 'dcr')" + }, + "tool_connections_transport_check": { + "name": "tool_connections_transport_check", + "value": "\"tool_connections\".\"transport\" in ('mcp_remote', 'rest_api', 'local_stdio')" + }, + "tool_connections_auth_kind_check": { + "name": "tool_connections_auth_kind_check", + "value": "\"tool_connections\".\"auth_kind\" in ('oauth', 'api_key', 'none')" + }, + "tool_connections_credential_source_check": { + "name": "tool_connections_credential_source_check", + "value": "\"tool_connections\".\"credential_source\" in ('paperclip_vault', 'vercel_connect')" + }, + "tool_connections_credential_source_one_of_check": { + "name": "tool_connections_credential_source_one_of_check", + "value": "(\n (\"tool_connections\".\"credential_source\" = 'paperclip_vault' and \"tool_connections\".\"external_credential\" is null)\n or\n (\"tool_connections\".\"credential_source\" = 'vercel_connect' and \"tool_connections\".\"external_credential\" is not null and jsonb_array_length(\"tool_connections\".\"credential_refs\") = 0 and jsonb_array_length(\"tool_connections\".\"credential_secret_refs\") = 0)\n )" + }, + "tool_connections_credential_policy_check": { + "name": "tool_connections_credential_policy_check", + "value": "\"tool_connections\".\"credential_policy\" in ('shared', 'per_user', 'per_user_with_fallback')" + } + }, + "isRLSEnabled": false + }, + "public.tool_gateway_rate_limit_counters": { + "name": "tool_gateway_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_ms": { + "name": "window_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_rate_limit_counters_company_idx": { + "name": "tool_gateway_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_rate_limit_counters_window_uq": { + "name": "tool_gateway_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_gateway_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_gateway_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_gateway_sessions": { + "name": "tool_gateway_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_gateway_sessions_token_hash_uq": { + "name": "tool_gateway_sessions_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_agent_idx": { + "name": "tool_gateway_sessions_company_agent_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_company_expires_idx": { + "name": "tool_gateway_sessions_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_run_idx": { + "name": "tool_gateway_sessions_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_issue_idx": { + "name": "tool_gateway_sessions_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_gateway_sessions_gateway_idx": { + "name": "tool_gateway_sessions_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_gateway_sessions_company_id_companies_id_fk": { + "name": "tool_gateway_sessions_company_id_companies_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_agent_id_agents_id_fk": { + "name": "tool_gateway_sessions_agent_id_agents_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_run_id_heartbeat_runs_id_fk": { + "name": "tool_gateway_sessions_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_gateway_sessions_issue_id_issues_id_fk": { + "name": "tool_gateway_sessions_issue_id_issues_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_project_id_projects_id_fk": { + "name": "tool_gateway_sessions_project_id_projects_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_gateway_sessions_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_gateway_sessions_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_gateway_sessions", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_invocations": { + "name": "tool_invocations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_token_id": { + "name": "gateway_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_type": { + "name": "client_subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_subject_id": { + "name": "client_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_session_id": { + "name": "mcp_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_version_hash": { + "name": "catalog_version_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "catalog_schema_hash": { + "name": "catalog_schema_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "application_key": { + "name": "application_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upstream_tool_name": { + "name": "upstream_tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "arguments_hash": { + "name": "arguments_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "arguments_summary": { + "name": "arguments_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "policy_decision": { + "name": "policy_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_policy_ids": { + "name": "matched_policy_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "policy_explanation": { + "name": "policy_explanation", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_scope_summary": { + "name": "credential_scope_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "header_policy_summary": { + "name": "header_policy_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "approval_state": { + "name": "approval_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upstream_request_id": { + "name": "upstream_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_hash": { + "name": "result_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result_size_bytes": { + "name": "result_size_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_artifact_id": { + "name": "result_artifact_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_invocations_company_created_idx": { + "name": "tool_invocations_company_created_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_run_idx": { + "name": "tool_invocations_run_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_issue_idx": { + "name": "tool_invocations_issue_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_gateway_idx": { + "name": "tool_invocations_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_invocations_company_idempotency_uq": { + "name": "tool_invocations_company_idempotency_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_invocations_company_id_companies_id_fk": { + "name": "tool_invocations_company_id_companies_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_invocations_agent_id_agents_id_fk": { + "name": "tool_invocations_agent_id_agents_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_issue_id_issues_id_fk": { + "name": "tool_invocations_issue_id_issues_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_run_id_heartbeat_runs_id_fk": { + "name": "tool_invocations_run_id_heartbeat_runs_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_invocations_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk": { + "name": "tool_invocations_gateway_token_id_tool_mcp_gateway_tokens_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_mcp_gateway_tokens", + "columnsFrom": [ + "gateway_token_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_application_id_tool_applications_id_fk": { + "name": "tool_invocations_application_id_tool_applications_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_connection_id_tool_connections_id_fk": { + "name": "tool_invocations_connection_id_tool_connections_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_invocations_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_invocations", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateway_tokens": { + "name": "tool_mcp_gateway_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_id": { + "name": "gateway_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_client'" + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_label": { + "name": "client_label", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "owner_note": { + "name": "owner_note", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "allowed_actions": { + "name": "allowed_actions", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"tools/list\",\"tools/call\"]'::jsonb" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expiry_override_reason": { + "name": "expiry_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_user_id": { + "name": "expiry_override_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_override_by_agent_id": { + "name": "expiry_override_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expiry_override_at": { + "name": "expiry_override_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateway_tokens_token_hash_uq": { + "name": "tool_mcp_gateway_tokens_token_hash_uq", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_gateway_idx": { + "name": "tool_mcp_gateway_tokens_gateway_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "gateway_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_subject_idx": { + "name": "tool_mcp_gateway_tokens_subject_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateway_tokens_company_expires_idx": { + "name": "tool_mcp_gateway_tokens_company_expires_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateway_tokens_company_id_companies_id_fk": { + "name": "tool_mcp_gateway_tokens_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk": { + "name": "tool_mcp_gateway_tokens_gateway_id_tool_mcp_gateways_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "tool_mcp_gateways", + "columnsFrom": [ + "gateway_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_expiry_override_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "expiry_override_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateway_tokens_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateway_tokens", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_mcp_gateways": { + "name": "tool_mcp_gateways", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "gateway_public_id": { + "name": "gateway_public_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gw_' || replace(gen_random_uuid()::text, '-', '')" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_slug": { + "name": "display_slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_profile_mode": { + "name": "default_profile_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'gateway_only'" + }, + "context_scope_type": { + "name": "context_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "context_scope_id": { + "name": "context_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_issue_id": { + "name": "approval_issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "auth_config": { + "name": "auth_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"bearer\":{\"enabled\":true,\"tokenPrefix\":\"pcgw\",\"defaultTtlSeconds\":7776000,\"requireFiniteExpiry\":true,\"longLivedTokenRequiresOverride\":true},\"oauth\":{\"enabled\":false,\"reservedFor\":\"v1_5\",\"dynamicClientRegistration\":false,\"authorizationCodePkce\":false}}'::jsonb" + }, + "header_policy": { + "name": "header_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"callerPassthrough\":{\"enabled\":false,\"allowedHeaders\":[]},\"staticHeaders\":[],\"generatedMetadata\":{\"enabled\":false,\"allowedHeaders\":[]},\"responseHeaders\":{\"forwardMcpRequiredHeaders\":true,\"forwardSafeCacheHeaders\":true}}'::jsonb" + }, + "metadata_policy": { + "name": "metadata_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"version\":1,\"forwardCompanyId\":false,\"forwardGatewayId\":false,\"forwardProjectId\":false,\"forwardIssueId\":false,\"forwardAgentId\":false,\"forwardRunId\":false,\"forwardCorrelationId\":true}'::jsonb" + }, + "on_demand_tools_config": { + "name": "on_demand_tools_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"searchToolName\":\"search_tools\",\"runToolName\":\"run_tool\"}'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_mcp_gateways_company_idx": { + "name": "tool_mcp_gateways_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_status_idx": { + "name": "tool_mcp_gateways_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_profile_idx": { + "name": "tool_mcp_gateways_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_public_id_uq": { + "name": "tool_mcp_gateways_public_id_uq", + "columns": [ + { + "expression": "gateway_public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_slug_uq": { + "name": "tool_mcp_gateways_company_slug_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_mcp_gateways_company_name_uq": { + "name": "tool_mcp_gateways_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_mcp_gateways_company_id_companies_id_fk": { + "name": "tool_mcp_gateways_company_id_companies_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_mcp_gateways_profile_id_tool_profiles_id_fk": { + "name": "tool_mcp_gateways_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "tool_mcp_gateways_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_project_id_projects_id_fk": { + "name": "tool_mcp_gateways_project_id_projects_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_approval_issue_id_issues_id_fk": { + "name": "tool_mcp_gateways_approval_issue_id_issues_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "issues", + "columnsFrom": [ + "approval_issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_mcp_gateways_created_by_agent_id_agents_id_fk": { + "name": "tool_mcp_gateways_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_mcp_gateways", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_oauth_states": { + "name": "tool_oauth_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_actor_type": { + "name": "created_by_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_actor_id": { + "name": "created_by_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_session_id": { + "name": "created_by_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interaction_id": { + "name": "interaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_oauth_states_company_idx": { + "name": "tool_oauth_states_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_connection_idx": { + "name": "tool_oauth_states_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_actor_idx": { + "name": "tool_oauth_states_actor_idx", + "columns": [ + { + "expression": "created_by_actor_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_oauth_states_expires_at_idx": { + "name": "tool_oauth_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_oauth_states_company_id_companies_id_fk": { + "name": "tool_oauth_states_company_id_companies_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_oauth_states_connection_id_tool_connections_id_fk": { + "name": "tool_oauth_states_connection_id_tool_connections_id_fk", + "tableFrom": "tool_oauth_states", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_policies": { + "name": "tool_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy_type": { + "name": "policy_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "selectors": { + "name": "selectors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_policies_company_enabled_idx": { + "name": "tool_policies_company_enabled_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_type_idx": { + "name": "tool_policies_company_type_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_policies_company_name_uq": { + "name": "tool_policies_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_policies_company_id_companies_id_fk": { + "name": "tool_policies_company_id_companies_id_fk", + "tableFrom": "tool_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_policies_created_by_agent_id_agents_id_fk": { + "name": "tool_policies_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_policies", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_bindings": { + "name": "tool_profile_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_bindings_company_target_idx": { + "name": "tool_profile_bindings_company_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_bindings_target_profile_uq": { + "name": "tool_profile_bindings_target_profile_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_bindings_company_id_companies_id_fk": { + "name": "tool_profile_bindings_company_id_companies_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_bindings_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_bindings_created_by_agent_id_agents_id_fk": { + "name": "tool_profile_bindings_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_profile_bindings", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profile_entries": { + "name": "tool_profile_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selector_type": { + "name": "selector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'include'" + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "catalog_entry_id": { + "name": "catalog_entry_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "risk_level": { + "name": "risk_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profile_entries_company_profile_idx": { + "name": "tool_profile_entries_company_profile_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_application_idx": { + "name": "tool_profile_entries_application_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_connection_idx": { + "name": "tool_profile_entries_connection_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profile_entries_catalog_entry_idx": { + "name": "tool_profile_entries_catalog_entry_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "catalog_entry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profile_entries_company_id_companies_id_fk": { + "name": "tool_profile_entries_company_id_companies_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_profile_id_tool_profiles_id_fk": { + "name": "tool_profile_entries_profile_id_tool_profiles_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_application_id_tool_applications_id_fk": { + "name": "tool_profile_entries_application_id_tool_applications_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_connection_id_tool_connections_id_fk": { + "name": "tool_profile_entries_connection_id_tool_connections_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk": { + "name": "tool_profile_entries_catalog_entry_id_tool_catalog_entries_id_fk", + "tableFrom": "tool_profile_entries", + "tableTo": "tool_catalog_entries", + "columnsFrom": [ + "catalog_entry_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_profiles": { + "name": "tool_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_key": { + "name": "profile_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "default_action": { + "name": "default_action", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deny'" + }, + "new_tools_reviewed_at": { + "name": "new_tools_reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_profiles_company_status_idx": { + "name": "tool_profiles_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_key_uq": { + "name": "tool_profiles_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_profiles_company_name_uq": { + "name": "tool_profiles_company_name_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_profiles_company_id_companies_id_fk": { + "name": "tool_profiles_company_id_companies_id_fk", + "tableFrom": "tool_profiles", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_rate_limit_counters": { + "name": "tool_rate_limit_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "counter_key": { + "name": "counter_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_kind": { + "name": "window_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_start_at": { + "name": "window_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_rate_limit_counters_company_idx": { + "name": "tool_rate_limit_counters_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_rate_limit_counters_window_uq": { + "name": "tool_rate_limit_counters_window_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "counter_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_rate_limit_counters_company_id_companies_id_fk": { + "name": "tool_rate_limit_counters_company_id_companies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_rate_limit_counters_policy_id_tool_policies_id_fk": { + "name": "tool_rate_limit_counters_policy_id_tool_policies_id_fk", + "tableFrom": "tool_rate_limit_counters", + "tableTo": "tool_policies", + "columnsFrom": [ + "policy_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_metric_counters": { + "name": "tool_runtime_metric_counters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric": { + "name": "metric", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket_start_at": { + "name": "bucket_start_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_metric_counters_company_metric_idx": { + "name": "tool_runtime_metric_counters_company_metric_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_metric_counters_bucket_uq": { + "name": "tool_runtime_metric_counters_bucket_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "metric", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bucket_start_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_metric_counters_company_id_companies_id_fk": { + "name": "tool_runtime_metric_counters_company_id_companies_id_fk", + "tableFrom": "tool_runtime_metric_counters", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_runtime_slots": { + "name": "tool_runtime_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "application_id": { + "name": "application_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connection_id": { + "name": "connection_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_scope_type": { + "name": "owner_scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'connection'" + }, + "owner_scope_id": { + "name": "owner_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "runtime_kind": { + "name": "runtime_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_stdio'" + }, + "slot_key": { + "name": "slot_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stopped'" + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_scope": { + "name": "workspace_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_scope_hash": { + "name": "credential_scope_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "process_id": { + "name": "process_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "command_template_key": { + "name": "command_template_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unchecked'" + }, + "health_message": { + "name": "health_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_health_check_at": { + "name": "last_health_check_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "idle_deadline_at": { + "name": "idle_deadline_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_runtime_slots_company_idx": { + "name": "tool_runtime_slots_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_connection_idx": { + "name": "tool_runtime_slots_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_execution_workspace_idx": { + "name": "tool_runtime_slots_execution_workspace_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_runtime_slots_slot_key_uq": { + "name": "tool_runtime_slots_slot_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_runtime_slots_company_id_companies_id_fk": { + "name": "tool_runtime_slots_company_id_companies_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_application_id_tool_applications_id_fk": { + "name": "tool_runtime_slots_application_id_tool_applications_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_applications", + "columnsFrom": [ + "application_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_connection_id_tool_connections_id_fk": { + "name": "tool_runtime_slots_connection_id_tool_connections_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "tool_connections", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk": { + "name": "tool_runtime_slots_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk": { + "name": "tool_runtime_slots_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tool_runtime_slots_issue_id_issues_id_fk": { + "name": "tool_runtime_slots_issue_id_issues_id_fk", + "tableFrom": "tool_runtime_slots", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tool_stdio_command_templates": { + "name": "tool_stdio_command_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "template_key": { + "name": "template_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "env_keys": { + "name": "env_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tool_stdio_command_templates_company_idx": { + "name": "tool_stdio_command_templates_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_status_idx": { + "name": "tool_stdio_command_templates_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tool_stdio_command_templates_company_key_uq": { + "name": "tool_stdio_command_templates_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tool_stdio_command_templates_company_id_companies_id_fk": { + "name": "tool_stdio_command_templates_company_id_companies_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tool_stdio_command_templates_created_by_agent_id_agents_id_fk": { + "name": "tool_stdio_command_templates_created_by_agent_id_agents_id_fk", + "tableFrom": "tool_stdio_command_templates", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_inbox_agent_policies": { + "name": "user_inbox_agent_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "allowed_agent_ids": { + "name": "allowed_agent_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_inbox_agent_policies_company_user_uq": { + "name": "user_inbox_agent_policies_company_user_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_inbox_agent_policies_allowed_agent_ids_idx": { + "name": "user_inbox_agent_policies_allowed_agent_ids_idx", + "columns": [ + { + "expression": "allowed_agent_ids", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "user_inbox_agent_policies_company_id_companies_id_fk": { + "name": "user_inbox_agent_policies_company_id_companies_id_fk", + "tableFrom": "user_inbox_agent_policies", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_inbox_agent_policies_mode_check": { + "name": "user_inbox_agent_policies_mode_check", + "value": "\"user_inbox_agent_policies\".\"mode\" in ('open', 'allowlist', 'disabled')" + } + }, + "isRLSEnabled": false + }, + "public.user_secret_declarations": { + "name": "user_secret_declarations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_secret_definition_id": { + "name": "user_secret_definition_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_path": { + "name": "config_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version_selector": { + "name": "version_selector", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'latest'" + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_missing_override": { + "name": "allow_missing_override", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_declarations_company_idx": { + "name": "user_secret_declarations_company_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_definition_idx": { + "name": "user_secret_declarations_definition_idx", + "columns": [ + { + "expression": "user_secret_definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_idx": { + "name": "user_secret_declarations_target_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_company_required_idx": { + "name": "user_secret_declarations_company_required_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "required", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_target_path_uq": { + "name": "user_secret_declarations_target_path_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_declarations_required_override_idx": { + "name": "user_secret_declarations_required_override_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "allow_missing_override", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_secret_declarations\".\"allow_missing_override\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_declarations_company_id_companies_id_fk": { + "name": "user_secret_declarations_company_id_companies_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk": { + "name": "user_secret_declarations_user_secret_definition_id_user_secret_definitions_id_fk", + "tableFrom": "user_secret_declarations", + "tableTo": "user_secret_definitions", + "columnsFrom": [ + "user_secret_definition_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_secret_definitions": { + "name": "user_secret_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local_encrypted'" + }, + "managed_mode": { + "name": "managed_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paperclip_managed'" + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "usage_guidance": { + "name": "usage_guidance", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_agent_id": { + "name": "created_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_agent_id": { + "name": "updated_by_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_secret_definitions_company_status_idx": { + "name": "user_secret_definitions_company_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_provider_idx": { + "name": "user_secret_definitions_company_provider_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_provider_config_idx": { + "name": "user_secret_definitions_provider_config_idx", + "columns": [ + { + "expression": "provider_config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_secret_definitions_company_key_uq": { + "name": "user_secret_definitions_company_key_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_secret_definitions\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_secret_definitions_company_id_companies_id_fk": { + "name": "user_secret_definitions_company_id_companies_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk": { + "name": "user_secret_definitions_provider_config_id_company_secret_provider_configs_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "company_secret_provider_configs", + "columnsFrom": [ + "provider_config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_created_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_created_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "created_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_secret_definitions_updated_by_agent_id_agents_id_fk": { + "name": "user_secret_definitions_updated_by_agent_id_agents_id_fk", + "tableFrom": "user_secret_definitions", + "tableTo": "agents", + "columnsFrom": [ + "updated_by_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_sidebar_preferences": { + "name": "user_sidebar_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "company_order": { + "name": "company_order", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_sidebar_preferences_user_uq": { + "name": "user_sidebar_preferences_user_uq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.work_assessments": { + "name": "work_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contract_id": { + "name": "contract_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "result_id": { + "name": "result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "trigger_kind": { + "name": "trigger_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger_ref": { + "name": "trigger_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_capability": { + "name": "trigger_capability", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_actor_company_id": { + "name": "trigger_actor_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prior_issue_status": { + "name": "prior_issue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prior_status_version": { + "name": "prior_status_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "prior_decision_id": { + "name": "prior_decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "policy_version": { + "name": "policy_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assessment_json": { + "name": "assessment_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "input_digest": { + "name": "input_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "supersedes_assessment_id": { + "name": "supersedes_assessment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "work_assessments_company_issue_input_uq": { + "name": "work_assessments_company_issue_input_uq", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "input_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "work_assessments_company_id_companies_id_fk": { + "name": "work_assessments_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_trigger_actor_company_id_companies_id_fk": { + "name": "work_assessments_trigger_actor_company_id_companies_id_fk", + "tableFrom": "work_assessments", + "tableTo": "companies", + "columnsFrom": [ + "trigger_actor_company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_issue_company_fk": { + "name": "work_assessments_issue_company_fk", + "tableFrom": "work_assessments", + "tableTo": "issues", + "columnsFrom": [ + "company_id", + "issue_id" + ], + "columnsTo": [ + "company_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_run_owner_fk": { + "name": "work_assessments_run_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id" + ], + "columnsTo": [ + "company_id", + "native_issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_contract_owner_fk": { + "name": "work_assessments_contract_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "completion_contracts", + "columnsFrom": [ + "company_id", + "issue_id", + "contract_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_result_owner_fk": { + "name": "work_assessments_result_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "native_run_results", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "result_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "work_assessments_supersedes_owner_fk": { + "name": "work_assessments_supersedes_owner_fk", + "tableFrom": "work_assessments", + "tableTo": "work_assessments", + "columnsFrom": [ + "company_id", + "issue_id", + "run_id", + "supersedes_assessment_id" + ], + "columnsTo": [ + "company_id", + "issue_id", + "run_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "work_assessments_company_issue_run_id_uq": { + "name": "work_assessments_company_issue_run_id_uq", + "nullsNotDistinct": false, + "columns": [ + "company_id", + "issue_id", + "run_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "work_assessments_trigger_actor_company_check": { + "name": "work_assessments_trigger_actor_company_check", + "value": "\"work_assessments\".\"trigger_actor_company_id\" = \"work_assessments\".\"company_id\"" + } + }, + "isRLSEnabled": false + }, + "public.workspace_operations": { + "name": "workspace_operations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "heartbeat_run_id": { + "name": "heartbeat_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "log_store": { + "name": "log_store", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_ref": { + "name": "log_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_bytes": { + "name": "log_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "log_sha256": { + "name": "log_sha256", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "log_compressed": { + "name": "log_compressed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stdout_excerpt": { + "name": "stdout_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stderr_excerpt": { + "name": "stderr_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operations_company_run_started_idx": { + "name": "workspace_operations_company_run_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "heartbeat_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_started_idx": { + "name": "workspace_operations_company_workspace_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operations_company_workspace_issue_started_idx": { + "name": "workspace_operations_company_workspace_issue_started_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_operations_company_id_companies_id_fk": { + "name": "workspace_operations_company_id_companies_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_operations_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_operations_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk": { + "name": "workspace_operations_heartbeat_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "heartbeat_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_operations_issue_id_issues_id_fk": { + "name": "workspace_operations_issue_id_issues_id_fk", + "tableFrom": "workspace_operations", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_runtime_services": { + "name": "workspace_runtime_services", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "company_id": { + "name": "company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "project_workspace_id": { + "name": "project_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "execution_workspace_id": { + "name": "execution_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reuse_key": { + "name": "reuse_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwd": { + "name": "cwd", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_agent_id": { + "name": "owner_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "started_by_run_id": { + "name": "started_by_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stop_policy": { + "name": "stop_policy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure": { + "name": "exposure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "exposure_handle": { + "name": "exposure_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backend_url": { + "name": "backend_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "health_status": { + "name": "health_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_runtime_services_company_workspace_status_idx": { + "name": "workspace_runtime_services_company_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_execution_workspace_status_idx": { + "name": "workspace_runtime_services_company_execution_workspace_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_project_status_idx": { + "name": "workspace_runtime_services_company_project_status_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_run_idx": { + "name": "workspace_runtime_services_run_idx", + "columns": [ + { + "expression": "started_by_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_runtime_services_company_updated_idx": { + "name": "workspace_runtime_services_company_updated_idx", + "columns": [ + { + "expression": "company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_runtime_services_company_id_companies_id_fk": { + "name": "workspace_runtime_services_company_id_companies_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "companies", + "columnsFrom": [ + "company_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_id_projects_id_fk": { + "name": "workspace_runtime_services_project_id_projects_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk": { + "name": "workspace_runtime_services_project_workspace_id_project_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "project_workspaces", + "columnsFrom": [ + "project_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk": { + "name": "workspace_runtime_services_execution_workspace_id_execution_workspaces_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "execution_workspaces", + "columnsFrom": [ + "execution_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_issue_id_issues_id_fk": { + "name": "workspace_runtime_services_issue_id_issues_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "issues", + "columnsFrom": [ + "issue_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_owner_agent_id_agents_id_fk": { + "name": "workspace_runtime_services_owner_agent_id_agents_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "agents", + "columnsFrom": [ + "owner_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk": { + "name": "workspace_runtime_services_started_by_run_id_heartbeat_runs_id_fk", + "tableFrom": "workspace_runtime_services", + "tableTo": "heartbeat_runs", + "columnsFrom": [ + "started_by_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 73c8e90ec6..9f9aac6377 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -1646,6 +1646,13 @@ "when": 1788283761224, "tag": "0236_remove_cheap_model_profiles", "breakpoints": true + }, + { + "idx": 237, + "version": "7", + "when": 1788296836732, + "tag": "0237_clammy_colonel_america", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index c679a984c1..73cd94e1a9 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -129,6 +129,8 @@ export { activityLog } from "./activity_log.js"; export { companySecretProviderConfigs } from "./company_secret_provider_configs.js"; export { userSecretDefinitions } from "./user_secret_definitions.js"; export { companySecrets } from "./company_secrets.js"; +export { managedAgentProfiles } from "./managed_agent_profiles.js"; +export { remoteAgentProfiles } from "./remote_agent_profiles.js"; export { companySecretVersions } from "./company_secret_versions.js"; export { companySecretBindings } from "./company_secret_bindings.js"; export { companySecretProposals } from "./company_secret_proposals.js"; diff --git a/packages/db/src/schema/managed_agent_profiles.ts b/packages/db/src/schema/managed_agent_profiles.ts new file mode 100644 index 0000000000..73d1dfcf44 --- /dev/null +++ b/packages/db/src/schema/managed_agent_profiles.ts @@ -0,0 +1,80 @@ +import { sql } from "drizzle-orm"; +import { + boolean, + check, + index, + integer, + jsonb, + pgTable, + text, + timestamp, + uniqueIndex, + uuid, +} from "drizzle-orm/pg-core"; + +import { companies } from "./companies.js"; +import { companySecrets } from "./company_secrets.js"; + +/** + * Company-scoped, non-secret snapshots of qualified Claude Managed Agent + * resources. Credentials remain in the company secret store and are linked by + * id only. Runtime inputs copy the immutable public resource identity from this + * row rather than accepting it from an agent's editable adapter config. + */ +export const managedAgentProfiles = pgTable( + "managed_agent_profiles", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + profileKey: text("profile_key").notNull(), + displayName: text("display_name").notNull(), + service: text("service").notNull().default("anthropic_managed_agents"), + anthropicAgentId: text("anthropic_agent_id").notNull(), + agentVersion: text("agent_version").notNull(), + environmentId: text("environment_id").notNull(), + betaVersion: text("beta_version").notNull().default("managed-agents-2026-04-01"), + defaultModel: text("default_model").notNull().default("claude-sonnet-5"), + defaultMaxListCostCents: integer("default_max_list_cost_cents").notNull().default(100), + apiKeySecretId: uuid("api_key_secret_id") + .notNull() + .references(() => companySecrets.id, { onDelete: "restrict" }), + enabled: boolean("enabled").notNull().default(false), + retentionAcknowledged: boolean("retention_acknowledged").notNull().default(false), + qualification: jsonb("qualification").$type>().notNull().default({}), + qualifiedAt: timestamp("qualified_at", { withTimezone: true }), + qualifiedRevision: text("qualified_revision"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyIdx: index("managed_agent_profiles_company_idx").on(table.companyId), + companyKeyUq: uniqueIndex("managed_agent_profiles_company_key_uq").on( + table.companyId, + table.profileKey, + ), + resourceUq: uniqueIndex("managed_agent_profiles_company_resource_uq").on( + table.companyId, + table.anthropicAgentId, + table.agentVersion, + table.environmentId, + ), + serviceCheck: check( + "managed_agent_profiles_service_check", + sql`${table.service} = 'anthropic_managed_agents'`, + ), + betaCheck: check( + "managed_agent_profiles_beta_check", + sql`${table.betaVersion} = 'managed-agents-2026-04-01'`, + ), + positiveBudgetCheck: check( + "managed_agent_profiles_positive_budget_check", + sql`${table.defaultMaxListCostCents} > 0`, + ), + qualifiedRevisionCheck: check( + "managed_agent_profiles_qualified_revision_check", + sql`(${table.qualifiedAt} IS NULL AND ${table.qualifiedRevision} IS NULL) OR (${table.qualifiedAt} IS NOT NULL AND ${table.qualification} <> '{}'::jsonb AND ${table.qualifiedRevision} ~ '^sha256:[0-9a-f]{64}$')`, + ), + }), +); diff --git a/packages/db/src/schema/remote_agent_profiles.ts b/packages/db/src/schema/remote_agent_profiles.ts new file mode 100644 index 0000000000..8e397e8111 --- /dev/null +++ b/packages/db/src/schema/remote_agent_profiles.ts @@ -0,0 +1,55 @@ +import { sql } from "drizzle-orm"; +import { + boolean, + check, + index, + jsonb, + pgTable, + text, + timestamp, + uniqueIndex, + uuid, +} from "drizzle-orm/pg-core"; + +import { companies } from "./companies.js"; + +/** + * Company-scoped, non-secret snapshots of qualified remote runner resources. + * AWS AgentCore authentication uses the runner environment's workload identity; + * the profile stores no credential reference or credential material. + */ +export const remoteAgentProfiles = pgTable( + "remote_agent_profiles", + { + id: uuid("id").primaryKey().defaultRandom(), + companyId: uuid("company_id") + .notNull() + .references(() => companies.id, { onDelete: "cascade" }), + profileKey: text("profile_key").notNull(), + displayName: text("display_name").notNull(), + service: text("service").notNull(), + configuration: jsonb("configuration").$type>().notNull().default({}), + enabled: boolean("enabled").notNull().default(false), + retentionAcknowledged: boolean("retention_acknowledged").notNull().default(false), + qualification: jsonb("qualification").$type>().notNull().default({}), + qualifiedAt: timestamp("qualified_at", { withTimezone: true }), + qualifiedRevision: text("qualified_revision"), + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), + }, + (table) => ({ + companyIdx: index("remote_agent_profiles_company_idx").on(table.companyId), + companyKeyUq: uniqueIndex("remote_agent_profiles_company_key_uq").on( + table.companyId, + table.profileKey, + ), + serviceCheck: check( + "remote_agent_profiles_service_check", + sql`${table.service} = 'aws_bedrock_agentcore_harness'`, + ), + qualifiedRevisionCheck: check( + "remote_agent_profiles_qualified_revision_check", + sql`(${table.qualifiedAt} IS NULL AND ${table.qualifiedRevision} IS NULL) OR (${table.qualifiedAt} IS NOT NULL AND ${table.qualification} <> '{}'::jsonb AND ${table.qualifiedRevision} ~ '^sha256:[0-9a-f]{64}$')`, + ), + }), +); diff --git a/packages/paperclip-runner/README.md b/packages/paperclip-runner/README.md index 4dd853f39f..16767bb64d 100644 --- a/packages/paperclip-runner/README.md +++ b/packages/paperclip-runner/README.md @@ -8,7 +8,8 @@ and conformance oracle. The package includes one coherent set of capabilities: PRP v1 validation and replay, a supervised local runner with a scripted fake harness, durable -WebSocket delivery and recovery, a skillless Codex app-server driver, live +WebSocket delivery and recovery, qualified Codex, OpenCode, ACPX, Claude +Managed, and AWS AgentCore drivers, live session and issue-thread surfaces, a public browser/React SDK, a standalone adapter demo, and a deterministic mock control plane. None of these surfaces imports or starts Paperclip's server, UI, CLI, or production database. @@ -41,12 +42,20 @@ route/service authorities; it does not copy those rules into this package. ## Quick start The package also builds `paperclip-runner-acpx-sidecar`. This bounded v2 -stdin/stdout bridge admits the qualified Codex ACPX profile only. It validates -the exact model, session identity, tool catalog, structured input, and terminal -settlement at the process boundary. Runnerd and the server do not select this -sidecar in this slice. Other ACPX agents remain unavailable. +stdin/stdout bridge admits the pinned Claude and Codex ACPX profiles. It +validates the exact model, session identity, tool catalog, structured input, +and terminal settlement at the process boundary. Pi remains unavailable. -The Rust core includes a bounded client for this sidecar protocol. It enforces +Runnerd selects only qualified provider profiles. Claude Managed and AWS +AgentCore receive immutable company-profile snapshots with explicit retention, +spend, and invocation limits. No provider process receives a Paperclip API +credential or unrestricted server environment. + +Claude Managed resolves its API key from the company secret bound to the +selected profile. AWS AgentCore uses workload identity only; long-lived static +AWS access keys are intentionally removed from the runner environment. + +The Rust core includes a bounded client for the sidecar protocol. It enforces request identity, event order, frame and queue limits, timeouts, redacted diagnostics, and process-group cleanup. This transport remains package-local. It does not change runnerd provider selection in this slice. @@ -74,14 +83,13 @@ are canonicalized, and consumers must not reinterpret the display value as file-access authority. Operational semantic-result and terminal events remain reserved for the stateful adapter rather than being duplicated. -The package-local ACPX provider reducer preserves that order while it tracks one +The ACPX provider reducer preserves that order while it tracks one active turn, bounded assistant text, semantic results, and pending tool or input correlations. Terminal events flush the final assistant message first and clear -unresolved turn-scoped requests. This reducer still does not select ACPX in -runnerd. +unresolved turn-scoped requests. The package-local session bootstrap starts the bounded sidecar transport, -verifies the Codex-only capability handshake and effective model, opens one +verifies the qualified capability handshake and effective model, opens one identity-bound session, and confirms its run attachment. Any failed bootstrap terminates the process; session shutdown preserves persistent provider state. The session can then start one immutable-workspace turn, request interruption, diff --git a/packages/paperclip-runner/runner/Cargo.lock b/packages/paperclip-runner/runner/Cargo.lock index 654e59b517..f362913723 100644 --- a/packages/paperclip-runner/runner/Cargo.lock +++ b/packages/paperclip-runner/runner/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -20,7 +20,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -66,12 +66,503 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "aws-config" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a767267da9e2c2e189b2f9df8b5657e850ecf5352644734ba130d4a57095cf1b" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 1.5.0", + "time", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "aws-credential-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93964ffdaf57857f544be3666a5f57570bb699e934700f11b49708f61bb556e" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "aws-runtime" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9007227e10b5fed2f3e0a2beff489211e2b5604c400b7a9d5d81ca9d64c24bb" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 0.2.12", + "http 1.5.0", + "http-body 0.4.6", + "http-body 1.1.0", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-bedrockagentcore" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8306a5554b2039a8dc884f83064ae23a8369a8abccef57bb063258c5ad0fea35" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.5.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.144.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30dc8bf6baaf7d46336a0ca2c69f223d9b90d7a801fb3e28f7ea17b00dc6b1de" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.5.0", + "http-body 1.1.0", + "lru", + "percent-encoding", + "regex-lite", + "sha2 0.11.0", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.113.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68182ecb449f7537db0f4d5d25917789cf41e32074a9fe47b6a0b847fe1d2032" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "http 1.5.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723c2234ad7511ceef63eab016b7ba6ff7c55590fefb96fa8467af014a07309f" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint", + "form_urlencoded", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.5.0", + "p256", + "percent-encoding", + "sha2 0.11.0", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02e407fb3b54891734224b9ffac8a71fdd35f542500fa1af95754a6b2beb316" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67ecd999972b58e67cab052f5129906c08c25883bd0788ceefc55ef97d61307" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 1.5.0", + "http-body 1.1.0", + "http-body-util", + "md-5", + "pin-project-lite", + "sha1 0.11.0", + "sha2 0.11.0", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6de526c7b567420a31bc283657a7921b45c4cafe0827fdf2490713dcc770c28f" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37843d9add67c3aff5856f409c6dc315d3cdff60f9c0cb5b670dab1e9920306d" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.5.0", + "http-body 1.1.0", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebfd138fac0337cee7516c352757ea73b9f2266e57d0bcb5bc70e9547e45aef1" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2", + "http 1.5.0", + "hyper", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc65a121adb4b33729919fcfa14fa36fb33c1555a8f06bb0e2188dbfdc1d9ef" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e86338c869539a581bf161247762a6e87f92c5c075060057b5ed6d06632ed0c" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512346c7212ab7436df2d77a16d976a468ae44a418835511d2a69269810aaf62" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "aws-smithy-xml", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b82e438d30e02a825d363bd639a9efaed68a8089d86101054b0081e7e0d3e606" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.5.0", + "http-body 0.4.6", + "http-body 1.1.0", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "954c563ce84507722d2679f07a35d21b9c6466b3872d513020d0281fc8112ac9" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api-macros", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.5.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-runtime-api-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221eaa237ddf1ca79b60d1372aad77e47f9c0ea5b3ce5099da8c61d027dc77b3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "aws-smithy-schema" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56e0a4e53127a632224e43633b0fe045fa9e1e3cfc68b9830f1115e103f910" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.5.0", +] + +[[package]] +name = "aws-smithy-types" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce83ce9abbb198d25bc7131e468d0f9fe1257125e58c39f3f9fc9f5098c9647" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.5.0", + "http-body 0.4.6", + "http-body 1.1.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce84f71c72fee2cbbadde6e7d082f5fb466e3a84733855295fa7aafd1b31b7d8" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec1cd5469f328c782dc3e33d4153cf118a54e33cbb3356d60d16f89883e1f94" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bit-set" version = "0.8.0" @@ -102,6 +593,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "borrow-or-share" version = "0.2.4" @@ -126,6 +626,16 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "cc" version = "1.4.4" @@ -133,6 +643,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -142,16 +654,60 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "core-foundation" version = "0.10.1" @@ -177,6 +733,46 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc-fast" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" +dependencies = [ + "digest 0.10.7", + "spin", +] + +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -188,6 +784,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -197,23 +802,62 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "data-encoding" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "displaydoc" version = "0.2.7" @@ -225,6 +869,52 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "email_address" version = "0.2.9" @@ -240,6 +930,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "fancy-regex" version = "0.19.0" @@ -251,6 +951,22 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.11" @@ -268,12 +984,27 @@ dependencies = [ "serde", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fraction" version = "0.16.0" @@ -284,6 +1015,61 @@ dependencies = [ "num-bigint", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -292,6 +1078,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -301,8 +1088,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -314,11 +1103,25 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + [[package]] name = "ghash" version = "0.5.1" @@ -329,6 +1132,36 @@ dependencies = [ "polyval", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.5.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -346,13 +1179,39 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", ] [[package]] @@ -365,12 +1224,116 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http 1.5.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http 1.5.0", + "http-body 1.1.0", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http 1.5.0", + "http-body 1.1.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http 1.5.0", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http 1.5.0", + "http-body 1.1.0", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "icu_collections" version = "2.3.0" @@ -475,6 +1438,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "2.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07aa2048142242915a31d35844fb311e0e53fcca590c3a0a40dcf1b841fa09eb" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.4" @@ -484,12 +1457,28 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.104" @@ -497,6 +1486,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -558,6 +1548,12 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.3" @@ -579,6 +1575,31 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "lru" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d317b4b9eb398e6acce275758ec6125535505e7a146fb1a9b8bda2451b0ff4c" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", +] + [[package]] name = "memchr" version = "2.8.3" @@ -591,6 +1612,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "num" version = "0.4.3" @@ -630,6 +1662,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" version = "0.1.47" @@ -693,23 +1731,47 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.9", +] + [[package]] name = "paperclip-runner-core" version = "0.0.0" dependencies = [ "aes-gcm", + "aws-config", + "aws-sdk-bedrockagentcore", + "aws-sdk-s3", + "aws-sdk-sts", + "aws-smithy-types", + "aws-types", + "base64", "getrandom 0.3.4", - "hmac", + "hmac 0.12.1", "jsonschema", "num-bigint", "num-traits", + "reqwest", + "rustix", "rustls", "rustls-native-certs", "rustls-pemfile", "serde", "serde_json", - "sha2", + "sha1 0.10.7", + "sha2 0.10.9", + "tokio", "tungstenite", + "uuid", ] [[package]] @@ -735,12 +1797,49 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + [[package]] name = "polyval" version = "0.6.2" @@ -748,7 +1847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -762,6 +1861,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -771,6 +1876,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -780,6 +1894,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -795,6 +1965,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.9.5" @@ -805,6 +1981,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -833,6 +2020,21 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -902,12 +2104,68 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.5.0", + "http-body 1.1.0", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -922,12 +2180,41 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ + "aws-lc-rs", "once_cell", "ring", "rustls-pki-types", @@ -963,6 +2250,7 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ + "web-time", "zeroize", ] @@ -972,6 +2260,7 @@ version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -983,6 +2272,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "schannel" version = "0.1.29" @@ -998,6 +2293,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" @@ -1021,6 +2330,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.229" @@ -1064,6 +2379,18 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.7" @@ -1071,8 +2398,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1082,8 +2420,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -1092,12 +2441,54 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "smallvec" version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1153,6 +2544,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -1184,6 +2584,36 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.4" @@ -1194,6 +2624,141 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.5.0", + "http-body 1.1.0", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tungstenite" version = "0.28.0" @@ -1202,14 +2767,14 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.5.0", "httparse", "log", - "rand", + "rand 0.9.5", "rustls", "rustls-native-certs", "rustls-pki-types", - "sha1", + "sha1 0.10.7", "thiserror", "utf-8", ] @@ -1238,7 +2803,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -1248,6 +2813,24 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -1260,6 +2843,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "uuid-simd" version = "0.8.0" @@ -1282,6 +2876,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -1310,6 +2913,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.127" @@ -1342,6 +2955,35 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -1442,6 +3084,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yoke" version = "0.8.3" diff --git a/packages/paperclip-runner/runner/Cargo.toml b/packages/paperclip-runner/runner/Cargo.toml index 8178d281c2..102af24584 100644 --- a/packages/paperclip-runner/runner/Cargo.toml +++ b/packages/paperclip-runner/runner/Cargo.toml @@ -10,6 +10,13 @@ publish = false [workspace.dependencies] aes-gcm = "0.10" +aws-config = { version = "1.11", default-features = false, features = ["behavior-version-latest", "default-https-client", "rt-tokio"] } +aws-sdk-bedrockagentcore = { version = "1.63", default-features = false, features = ["default-https-client", "rt-tokio"] } +aws-sdk-s3 = { version = "1.112", default-features = false, features = ["default-https-client", "rt-tokio"] } +aws-sdk-sts = { version = "1.112", default-features = false, features = ["default-https-client", "rt-tokio", "sigv4a"] } +aws-smithy-types = "1.6" +aws-types = "1.3" +base64 = "0.22" getrandom = "0.3" hmac = "0.12" jsonschema = { version = "0.50", default-features = false } @@ -21,4 +28,9 @@ sha2 = "0.10" rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } rustls-native-certs = "0.8" rustls-pemfile = "2.2" +reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } +rustix = { version = "1.1", features = ["fs"] } +sha1 = "0.10" tungstenite = { version = "0.28", default-features = false, features = ["handshake", "rustls-tls-native-roots"] } +tokio = { version = "1.50", features = ["rt-multi-thread", "sync", "time"] } +uuid = { version = "1.22", features = ["v4"] } diff --git a/packages/paperclip-runner/runner/crates/runner-core/Cargo.toml b/packages/paperclip-runner/runner/crates/runner-core/Cargo.toml index fe670825c0..fb24ea90d9 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/Cargo.toml +++ b/packages/paperclip-runner/runner/crates/runner-core/Cargo.toml @@ -7,6 +7,13 @@ publish.workspace = true [dependencies] aes-gcm.workspace = true +aws-config.workspace = true +aws-sdk-bedrockagentcore.workspace = true +aws-sdk-s3.workspace = true +aws-sdk-sts.workspace = true +aws-smithy-types.workspace = true +aws-types.workspace = true +base64.workspace = true getrandom.workspace = true hmac.workspace = true jsonschema.workspace = true @@ -18,7 +25,12 @@ sha2.workspace = true rustls.workspace = true rustls-native-certs.workspace = true rustls-pemfile.workspace = true +reqwest.workspace = true +rustix.workspace = true +sha1.workspace = true tungstenite.workspace = true +tokio.workspace = true +uuid.workspace = true [[bin]] name = "conformance-tracer" diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs index 48fb05dbfc..99f5a88e9c 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_provider_backend.rs @@ -1,35 +1,39 @@ -use std::collections::{HashSet, VecDeque}; -use std::fs::{self, DirBuilder}; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::fs::{self, DirBuilder, File}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::time::Duration; -#[cfg(unix)] -use std::fs::File; #[cfg(unix)] use std::os::unix::fs::{DirBuilderExt, PermissionsExt}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; +#[cfg(test)] +use sha2::{Digest, Sha256}; use crate::acpx_provider_session::{ AcpxPermissionMode, AcpxProviderSession, AcpxProviderSessionConfig, AcpxProviderSessionIdentity, }; use crate::acpx_sidecar_transport::AcpxSidecarTransportConfig; +#[cfg(test)] +use crate::durable::QualifiedLaunchArtifact; use crate::durable::{ - create_private_temporary_file, open_private_regular_file, verify_private_directory, Command, - CommandExecution, CommandExecutor, DurableRunnerConfig, DurableRunnerError, EventPriority, - PolledEvent, + create_private_temporary_file, open_private_regular_file, verify_private_directory, + AcpxLaunchProfile, Command, CommandExecution, CommandExecutor, DurableRunnerConfig, + DurableRunnerError, EventPriority, PolledEvent, }; +use crate::process_supervisor::{VerifiedProcessArgument, VerifiedProcessLaunch}; use crate::provider_bridge::{ authorized_tool_catalog_digest, AuthorizedToolSet, ToolResult, TOOL_SET_SCHEMA, }; use crate::provider_events::{ project_acpx_state_event, AcpxEventProjectionContext, NormalizedProviderEvent, }; +use crate::qualified_launch::verify_launch_artifact; pub const ACPX_PROVIDER_STATE_FILE: &str = "acpx-provider-state.json"; -const ACPX_PROVIDER_STATE_SCHEMA: &str = "paperclip.runner.acpx-provider-state.v1"; +const ACPX_PROVIDER_STATE_SCHEMA: &str = "paperclip.runner.acpx-provider-state.v2"; const MAX_PROVIDER_STATE_BYTES: u64 = 16 * 1024 * 1024; const MAX_PENDING_EVENTS: usize = 8_320; const MAX_EVENTS_PER_POLL: usize = 128; @@ -150,15 +154,12 @@ impl AcpxProviderDescriptor { &self, tool_set: AuthorizedToolSet, expected_identity: Option, + launch_profile: Option<&AcpxLaunchProfile>, ) -> Result { secure_directory(&self.runtime_directory, "ACPX runtime")?; + let transport = self.verified_transport(launch_profile)?; Ok(AcpxProviderSessionConfig { - transport: AcpxSidecarTransportConfig { - command: self.sidecar_command.clone(), - args: self.sidecar_args.clone(), - request_timeout: Duration::from_secs(30), - shutdown_grace: Duration::from_secs(2), - }, + transport, agent: self.agent.clone(), model: self.model.clone(), run_id: self.run_id.clone(), @@ -174,6 +175,69 @@ impl AcpxProviderDescriptor { }) } + fn verified_transport( + &self, + launch_profile: Option<&AcpxLaunchProfile>, + ) -> Result { + let launch_profile = launch_profile.ok_or_else(|| { + DurableRunnerError::invalid( + "ACPX runner startup omitted its qualified sidecar launch profile", + ) + })?; + if self.sidecar_command != launch_profile.command + || self.sidecar_args != launch_profile.args + { + return Err(DurableRunnerError::invalid( + "ACPX descriptor sidecar launch does not match the runner-owned qualified profile", + )); + } + + let mut verified = HashMap::new(); + for artifact in &launch_profile.artifacts { + if verified.contains_key(&artifact.path) { + return Err(DurableRunnerError::invalid( + "ACPX runner launch profile repeats an artifact path", + )); + } + let snapshot = verify_launch_artifact(artifact, "ACPX")?; + verified.insert(artifact.path.clone(), snapshot); + } + let command = verified + .get(&launch_profile.command) + .cloned() + .ok_or_else(|| { + DurableRunnerError::invalid( + "ACPX runner launch profile does not authenticate its command", + ) + })?; + let verified_args = launch_profile + .args + .iter() + .map(|argument| { + let path = Path::new(argument); + if !path.is_absolute() { + return Ok(VerifiedProcessArgument::Literal(argument.clone())); + } + verified + .get(path) + .cloned() + .map(VerifiedProcessArgument::Artifact) + .ok_or_else(|| { + DurableRunnerError::invalid( + "ACPX runner launch profile does not authenticate an absolute argument", + ) + }) + }) + .collect::, _>>()?; + Ok(AcpxSidecarTransportConfig { + command: launch_profile.command.clone(), + args: launch_profile.args.clone(), + verified_launch: Some(VerifiedProcessLaunch::new(command, verified_args)), + request_timeout: Duration::from_secs(30), + shutdown_grace: Duration::from_secs(2), + }) + } + fn public_descriptor(&self, identity: Option<&AcpxProviderSessionIdentity>) -> Value { json!({ "provider": "acpx", @@ -199,6 +263,7 @@ impl AcpxProviderDescriptor { #[serde(rename_all = "camelCase", deny_unknown_fields)] struct AcpxDurableState { schema: String, + launch_profile_digest: String, lifecycle: String, descriptor: AcpxProviderDescriptor, tool_set: AuthorizedToolSet, @@ -215,9 +280,14 @@ struct AcpxDurableState { } impl AcpxDurableState { - fn new(descriptor: AcpxProviderDescriptor, tool_set: AuthorizedToolSet) -> Self { + fn new( + descriptor: AcpxProviderDescriptor, + tool_set: AuthorizedToolSet, + launch_profile_digest: String, + ) -> Self { Self { schema: ACPX_PROVIDER_STATE_SCHEMA.to_owned(), + launch_profile_digest, lifecycle: "prepared".to_owned(), descriptor, tool_set, @@ -229,10 +299,21 @@ impl AcpxDurableState { } } - fn validate(&self, context: &AcpxEventProjectionContext) -> Result<(), DurableRunnerError> { + fn validate( + &self, + context: &AcpxEventProjectionContext, + expected_launch_profile_digest: &str, + ) -> Result<(), DurableRunnerError> { self.descriptor.validate(context)?; + if self.launch_profile_digest != expected_launch_profile_digest { + return Err(DurableRunnerError::invalid( + "ACPX durable launch profile digest does not match runner startup", + )); + } let mut ids = HashSet::new(); if self.schema != ACPX_PROVIDER_STATE_SCHEMA + || self.launch_profile_digest.len() != 71 + || !self.launch_profile_digest.starts_with("sha256:") || !matches!( self.lifecycle.as_str(), "prepared" @@ -303,6 +384,8 @@ pub struct AcpxCommandExecutor { state: Option, session: Option, restore_checked: bool, + restore_error: Option, + launch_profile: Option, } impl AcpxCommandExecutor { @@ -318,6 +401,8 @@ impl AcpxCommandExecutor { state: None, session: None, restore_checked: false, + restore_error: None, + launch_profile: config.acpx_launch_profile.clone(), } } @@ -325,11 +410,37 @@ impl AcpxCommandExecutor { self.state_dir.join(ACPX_PROVIDER_STATE_FILE) } + fn launch_profile_digest(&self) -> Result { + self.launch_profile + .as_ref() + .ok_or_else(|| { + DurableRunnerError::invalid( + "ACPX runner startup omitted its qualified sidecar launch profile", + ) + })? + .canonical_digest() + } + fn restore(&mut self) -> Result<(), DurableRunnerError> { if self.restore_checked { return Ok(()); } - self.restore_checked = true; + if let Some(error) = self.restore_error.as_ref() { + return Err(error.clone()); + } + match self.restore_once() { + Ok(()) => { + self.restore_checked = true; + Ok(()) + } + Err(error) => { + self.restore_error = Some(error.clone()); + Err(error) + } + } + } + + fn restore_once(&mut self) -> Result<(), DurableRunnerError> { let path = self.state_path(); let mut file = match open_private_regular_file(&path) { Ok(file) => file, @@ -360,7 +471,8 @@ impl AcpxCommandExecutor { let state: AcpxDurableState = serde_json::from_slice(&bytes).map_err(|error| { DurableRunnerError::invalid(format!("ACPX provider state is malformed: {error}")) })?; - state.validate(&self.context)?; + let launch_profile_digest = self.launch_profile_digest()?; + state.validate(&self.context, &launch_profile_digest)?; self.state = Some(state); self.restore_session_if_needed() } @@ -380,12 +492,7 @@ impl AcpxCommandExecutor { } let unsafe_active = matches!(state.lifecycle.as_str(), "turn_starting" | "turn_active"); let previous_turn = state.active_turn_id.clone(); - let session = self.start_session(true)?; if unsafe_active { - let mut session = session; - let shutdown_failed = session - .shutdown("fail-closed durable recovery of an active ACPX turn") - .is_err(); let state = self .state .as_mut() @@ -401,7 +508,7 @@ impl AcpxCommandExecutor { "status": "failed", "providerTerminalObserved": false, "code": "acpx_active_turn_recovery_closed", - "providerShutdownFailed": shutdown_failed, + "providerShutdownFailed": false, }), })?; state.push(NormalizedProviderEvent { @@ -417,6 +524,7 @@ impl AcpxCommandExecutor { self.save_state()?; return Ok(()); } + let session = self.start_session(true)?; let identity = session.identity().clone(); let process_id = session.process_id(); let state = self @@ -439,9 +547,11 @@ impl AcpxCommandExecutor { .as_ref() .ok_or_else(|| DurableRunnerError::invalid("ACPX provider has not been prepared"))?; let expected = recovering.then(|| state.identity.clone()).flatten(); - let config = state - .descriptor - .session_config(state.tool_set.clone(), expected)?; + let config = state.descriptor.session_config( + state.tool_set.clone(), + expected, + self.launch_profile.as_ref(), + )?; let mut session = AcpxProviderSession::start(&config).map_err(|error| { DurableRunnerError::invalid(format!("failed to start ACPX provider: {error}")) })?; @@ -459,7 +569,8 @@ impl AcpxCommandExecutor { .state .as_ref() .ok_or_else(|| DurableRunnerError::invalid("ACPX provider state is unavailable"))?; - state.validate(&self.context)?; + let launch_profile_digest = self.launch_profile_digest()?; + state.validate(&self.context, &launch_profile_digest)?; secure_directory(&self.state_dir, "provider state")?; let path = self.state_path(); let bytes = serde_json::to_vec_pretty(state).map_err(|error| { @@ -505,6 +616,7 @@ impl AcpxCommandExecutor { })?; descriptor.validate(&self.context)?; let tool_set = authorized_tool_set(payload)?; + let launch_profile_digest = self.launch_profile_digest()?; if let Some(state) = self.state.as_ref() { if state.descriptor != descriptor || state.tool_set != tool_set { return Err(DurableRunnerError::invalid( @@ -517,7 +629,11 @@ impl AcpxCommandExecutor { )); } } else { - self.state = Some(AcpxDurableState::new(descriptor, tool_set)); + self.state = Some(AcpxDurableState::new( + descriptor, + tool_set, + launch_profile_digest, + )); self.save_state()?; } Ok(CommandExecution::result(json!({ @@ -1014,6 +1130,66 @@ fn secure_directory(path: &Path, label: &str) -> Result<(), DurableRunnerError> #[cfg(test)] mod tests { use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + fn temporary_directory(label: &str) -> PathBuf { + let nonce = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let directory = std::env::temp_dir().join(format!( + "paperclip-acpx-backend-{label}-{}-{nonce}", + std::process::id() + )); + fs::create_dir_all(&directory).unwrap(); + #[cfg(unix)] + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + directory + } + + fn write_artifact(path: &Path, contents: &[u8], executable: bool) { + fs::write(path, contents).unwrap(); + #[cfg(unix)] + fs::set_permissions( + path, + fs::Permissions::from_mode(if executable { 0o700 } else { 0o600 }), + ) + .unwrap(); + } + + fn artifact(path: &Path) -> QualifiedLaunchArtifact { + QualifiedLaunchArtifact { + path: path.to_owned(), + sha256: format!("sha256:{:x}", Sha256::digest(fs::read(path).unwrap())), + } + } + + fn test_config( + state_dir: &Path, + launch_profile: Option, + ) -> DurableRunnerConfig { + DurableRunnerConfig { + connect_url: "ws://127.0.0.1/runner".to_owned(), + ca_bundle_path: None, + state_dir: state_dir.to_owned(), + runner_instance_id: "runner-1".to_owned(), + environment_lease_id: "lease-1".to_owned(), + run_id: "run-1".to_owned(), + normalized_session_id: "session-1".to_owned(), + turn_id: "turn-1".to_owned(), + item_id: "item-1".to_owned(), + runner_version: "0.0.0".to_owned(), + runner_digest: "sha256:test".to_owned(), + acpx_launch_profile: launch_profile, + opencode_launch_profile: None, + max_outbox_bytes: 1024 * 1024, + p0_reserve_bytes: 64 * 1024, + max_frame_bytes: 1024 * 1024, + reconnect_delay: Duration::from_millis(1), + reconnect_grace: None, + max_runtime: Duration::from_secs(60), + } + } fn context() -> AcpxEventProjectionContext { AcpxEventProjectionContext { @@ -1086,4 +1262,185 @@ mod tests { let pi: AcpxProviderDescriptor = serde_json::from_value(pi).unwrap(); assert!(pi.validate(&context()).is_err()); } + + #[test] + fn binds_sidecar_paths_arguments_and_contents_to_the_runner_profile() { + let directory = temporary_directory("launch-binding"); + let command = directory.join("node"); + let sidecar = directory.join("sidecar.js"); + write_artifact(&command, b"qualified node", true); + write_artifact(&sidecar, b"qualified sidecar", false); + let args = vec![sidecar.to_string_lossy().into_owned()]; + let profile = AcpxLaunchProfile { + authority_digest: format!("sha256:{}", "d".repeat(64)), + command: command.clone(), + args: args.clone(), + artifacts: vec![artifact(&command), artifact(&sidecar)], + }; + let mut value = descriptor("codex"); + value["sidecarCommand"] = json!(command); + value["sidecarArgs"] = json!(args); + let descriptor: AcpxProviderDescriptor = serde_json::from_value(value).unwrap(); + let transport = descriptor.verified_transport(Some(&profile)).unwrap(); + assert_eq!(transport.command, profile.command); + assert_eq!(transport.args[0], sidecar.to_string_lossy()); + assert!(transport.verified_launch.is_some()); + + let mut drifted_path = descriptor.clone(); + drifted_path.sidecar_command = directory.join("other-node"); + assert!(drifted_path.verified_transport(Some(&profile)).is_err()); + let mut drifted_args = descriptor.clone(); + drifted_args.sidecar_args.push("--untrusted".to_owned()); + assert!(drifted_args.verified_transport(Some(&profile)).is_err()); + + write_artifact(&sidecar, b"modified sidecar", false); + assert!(descriptor.verified_transport(Some(&profile)).is_err()); + fs::remove_dir_all(directory).unwrap(); + } + + #[cfg(unix)] + #[test] + fn rejects_symlinked_launch_artifacts() { + use std::os::unix::fs::symlink; + + let directory = temporary_directory("launch-symlink"); + let command = directory.join("node"); + let command_link = directory.join("node-link"); + write_artifact(&command, b"qualified node", true); + symlink(&command, &command_link).unwrap(); + let profile = AcpxLaunchProfile { + authority_digest: format!("sha256:{}", "d".repeat(64)), + command: command_link.clone(), + args: Vec::new(), + artifacts: vec![QualifiedLaunchArtifact { + path: command_link.clone(), + sha256: artifact(&command).sha256, + }], + }; + let mut value = descriptor("codex"); + value["sidecarCommand"] = json!(command_link); + value["sidecarArgs"] = json!([]); + let descriptor: AcpxProviderDescriptor = serde_json::from_value(value).unwrap(); + assert!(descriptor.verified_transport(Some(&profile)).is_err()); + fs::remove_dir_all(directory).unwrap(); + } + + #[cfg(unix)] + #[test] + fn active_turn_recovery_closes_without_starting_the_provider() { + let directory = temporary_directory("active-recovery"); + let runtime = directory.join("runtime"); + let workspace = directory.join("workspace"); + fs::create_dir_all(&runtime).unwrap(); + fs::create_dir_all(&workspace).unwrap(); + fs::set_permissions(&runtime, fs::Permissions::from_mode(0o700)).unwrap(); + fs::set_permissions(&workspace, fs::Permissions::from_mode(0o700)).unwrap(); + let marker = directory.join("provider-started"); + let command = directory.join("sidecar"); + write_artifact( + &command, + format!("#!/bin/sh\ntouch '{}'\n", marker.display()).as_bytes(), + true, + ); + let launch_profile = AcpxLaunchProfile { + authority_digest: format!("sha256:{}", "d".repeat(64)), + command: command.clone(), + args: Vec::new(), + artifacts: vec![artifact(&command)], + }; + let mut value = descriptor("codex"); + value["sidecarCommand"] = json!(command); + value["sidecarArgs"] = json!([]); + value["runtimeDirectory"] = json!(runtime); + value["cwd"] = json!(workspace); + let descriptor: AcpxProviderDescriptor = serde_json::from_value(value).unwrap(); + let identity = AcpxProviderSessionIdentity { + kind: "acpx".to_owned(), + normalized_session_id: "session-1".to_owned(), + acpx_record_id: "record-1".to_owned(), + backend_session_id: "backend-1".to_owned(), + agent_session_id: "agent-1".to_owned(), + profile_digest: descriptor.command_digest.clone(), + workspace_digest: format!("sha256:{}", "a".repeat(64)), + requested_model: descriptor.model.clone(), + effective_model: descriptor.model.clone(), + permission_mode: Some(descriptor.permission_mode), + }; + let operations = Vec::new(); + let tool_set = AuthorizedToolSet { + schema: TOOL_SET_SCHEMA.to_owned(), + schema_version: 1, + catalog_digest: authorized_tool_catalog_digest(&operations).unwrap(), + operations, + }; + let launch_profile_digest = launch_profile.canonical_digest().unwrap(); + let mut state = AcpxDurableState::new(descriptor, tool_set, launch_profile_digest); + state.lifecycle = "turn_active".to_owned(); + state.identity = Some(identity); + state.active_turn_id = Some("turn-1".to_owned()); + let config = test_config(&directory, Some(launch_profile)); + let mut original = AcpxCommandExecutor::with_runner_config(&directory, &config); + original.state = Some(state); + original.save_state().unwrap(); + drop(original); + + let mut drifted_config = config.clone(); + drifted_config + .acpx_launch_profile + .as_mut() + .unwrap() + .authority_digest = format!("sha256:{}", "e".repeat(64)); + let mut drifted = AcpxCommandExecutor::with_runner_config(&directory, &drifted_config); + let drift_error = drifted + .execute(&Command { + schema: "paperclip.prp.command.v1".to_owned(), + command_id: "command-drift".to_owned(), + controller_seq: 1, + command_type: "session.snapshot".to_owned(), + issued_at: "2026-09-01T00:00:00.000Z".to_owned(), + deadline_at: None, + precondition: None, + payload: json!({}), + }) + .unwrap_err(); + assert!(drift_error + .to_string() + .contains("launch profile digest does not match runner startup")); + let retry_error = drifted + .execute(&Command { + schema: "paperclip.prp.command.v1".to_owned(), + command_id: "command-drift-retry".to_owned(), + controller_seq: 2, + command_type: "session.snapshot".to_owned(), + issued_at: "2026-09-01T00:00:01.000Z".to_owned(), + deadline_at: None, + precondition: None, + payload: json!({}), + }) + .unwrap_err(); + assert!(retry_error + .to_string() + .contains("launch profile digest does not match runner startup")); + assert!(!marker.exists()); + + let mut recovered = AcpxCommandExecutor::with_runner_config(&directory, &config); + let snapshot = recovered + .execute(&Command { + schema: "paperclip.prp.command.v1".to_owned(), + command_id: "command-1".to_owned(), + controller_seq: 1, + command_type: "session.snapshot".to_owned(), + issued_at: "2026-09-01T00:00:00.000Z".to_owned(), + deadline_at: None, + precondition: None, + payload: json!({}), + }) + .unwrap(); + assert_eq!(snapshot.result["status"], "closed"); + assert!(!marker.exists()); + let events = recovered.poll_events().unwrap(); + assert_eq!(events[0].event_type, "turn.failed"); + assert_eq!(events[1].event_type, "run.terminal"); + fs::remove_dir_all(directory).unwrap(); + } } diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs index c037207d04..8d4a0ea456 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs @@ -11,7 +11,9 @@ use crate::generated_acpx_sidecar_contract::{ GENERATED_ACPX_SIDECAR_PROTOCOL_VERSION, }; use crate::local_runner::LocalRunnerError; -use crate::process_supervisor::{BoundedLogBuffer, ProcessOutput, SupervisedProcess}; +use crate::process_supervisor::{ + BoundedLogBuffer, ProcessOutput, SupervisedProcess, VerifiedProcessLaunch, +}; use crate::stable_identity::{is_stable_id, DURABLE_STABLE_ID_CHARS, SHORT_STABLE_ID_CHARS}; pub const ACPX_SIDECAR_MAX_FRAME_BYTES: usize = 1024 * 1024; @@ -23,13 +25,16 @@ const MAX_JSON_SAFE_INTEGER: u64 = 9_007_199_254_740_991; pub struct AcpxSidecarTransportConfig { pub command: PathBuf, pub args: Vec, + pub verified_launch: Option, pub request_timeout: Duration, pub shutdown_grace: Duration, } impl AcpxSidecarTransportConfig { pub fn validate(&self) -> Result<(), LocalRunnerError> { - if !self.command.is_absolute() || !self.command.is_file() { + if !self.command.is_absolute() + || (self.verified_launch.is_none() && !self.command.is_file()) + { return Err(LocalRunnerError::invalid( "ACPX sidecar command must be an existing absolute file", )); @@ -124,13 +129,22 @@ impl AcpxSidecarTransport { environment_keys: &[&str], ) -> Result { config.validate()?; - let process = SupervisedProcess::spawn_with_environment_keys( - &config.command, - &config.args, - config.shutdown_grace, - ACPX_SIDECAR_MAX_FRAME_BYTES, - environment_keys, - )?; + let process = if let Some(launch) = config.verified_launch.as_ref() { + SupervisedProcess::spawn_verified_with_environment_keys( + launch, + config.shutdown_grace, + ACPX_SIDECAR_MAX_FRAME_BYTES, + environment_keys, + )? + } else { + SupervisedProcess::spawn_with_environment_keys( + &config.command, + &config.args, + config.shutdown_grace, + ACPX_SIDECAR_MAX_FRAME_BYTES, + environment_keys, + )? + }; Ok(Self { process, request_timeout: config.request_timeout, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/aws_agentcore_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/aws_agentcore_provider.rs new file mode 100644 index 0000000000..0536b243a7 --- /dev/null +++ b/packages/paperclip-runner/runner/crates/runner-core/src/aws_agentcore_provider.rs @@ -0,0 +1,3354 @@ +//! Amazon Bedrock AgentCore Harness provider. +//! +//! The Harness owns the remote model loop. Paperclip supplies only caller-side +//! inline functions, then executes those functions through the durable PRP +//! bridge. No Paperclip credential or callback address enters AgentCore. + +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::fs; +use std::future::Future; +use std::path::{Path, PathBuf}; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError, SyncSender}; +use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant}; + +use aws_config::sts::AssumeRoleProvider; +use aws_sdk_bedrockagentcore::error::SdkError; +use aws_sdk_bedrockagentcore::types::{ + HarnessContentBlock, HarnessContentBlockDelta, HarnessContentBlockStart, + HarnessConversationRole, HarnessInlineFunctionConfig, HarnessMessage, HarnessSkill, + HarnessSkillS3Source, HarnessSystemContentBlock, HarnessTool, HarnessToolConfiguration, + HarnessToolResultBlock, HarnessToolResultContentBlock, HarnessToolType, HarnessToolUseBlock, + HarnessToolUseStatus, HarnessToolUseType, InvokeHarnessStreamOutput, +}; +use aws_smithy_types::byte_stream::ByteStream; +use aws_smithy_types::error::metadata::ProvideErrorMetadata; +use aws_smithy_types::{Document, Number}; +use aws_types::region::Region; +use jsonschema::validator_for; +use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; +use uuid::Uuid; + +use crate::local_runner::LocalRunnerError; +use crate::managed_provider::{ + AwsAgentCoreProviderConfig, Provider, ProviderEvent, ProviderKind, ProviderRuntimeIdentity, +}; +use crate::provider_bridge::{AuthorizedTool, ToolResult}; + +const MAX_AGENTCORE_TOOLS: usize = 64; +// Harness invocations can spend substantial time starting a new runtime before +// response headers (and therefore the event stream) are available. Allow a +// bounded cold start, but leave enough of the eval's outer deadline for the +// durable runner to classify the failure and service runner.shutdown cleanly. +const AGENTCORE_INVOCATION_DELIVERY_TIMEOUT: Duration = Duration::from_secs(120); +// InvokeHarness accepts at most 50 HarnessSkill entries. Paperclip reserves one +// for the generated instruction companion, leaving at most 49 assigned skills. +const MAX_CONTEXT_SKILL_SOURCES: usize = 50; +const MAX_CONTEXT_UPLOAD_FILES: usize = 10_000; +const MAX_CONTEXT_UPLOAD_BYTES: usize = 64 * 1024 * 1024; +const MAX_MEMORY_HISTORY_PAGES: usize = 1_000; +const MAX_MEMORY_HISTORY_EVENTS: usize = 100_000; +const MAX_INTERRUPT_DRAIN_EVENTS: usize = 256; +#[cfg(not(test))] +const AGENTCORE_INTERRUPT_USAGE_RECONCILIATION_TIMEOUT: Duration = Duration::from_secs(2); +#[cfg(test)] +const AGENTCORE_INTERRUPT_USAGE_RECONCILIATION_TIMEOUT: Duration = Duration::from_millis(75); +pub(crate) const AGENTCORE_USAGE_RECONCILIATION_OBSERVED: &str = "authoritative_metadata_observed"; +pub(crate) const AGENTCORE_USAGE_RECONCILIATION_PENDING: &str = + "latest_cumulative_estimate_pending_metadata"; +pub(crate) const AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE: &str = + "interrupted_invocation_charged_to_session_ceiling"; +pub(crate) const AGENTCORE_USAGE_RECONCILIATION_FIELD: &str = "usageReconciliation"; +pub(crate) const AGENTCORE_PENDING_INVOCATION_FIELD: &str = "pendingInvocationId"; +pub(crate) const AGENTCORE_PENDING_CEILING_FIELD: &str = "pendingEstimatedCeilingUsd"; +pub(crate) const AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD: &str = "conservativeCostFloorUsd"; +#[cfg(test)] +const AGENTCORE_INLINE_TOOL_ALLOWLIST: &str = "@*/pc_*"; +#[derive(Clone, Debug)] +struct RemoteToolUse { + remote_name: String, + operation_id: String, + input: Value, +} + +fn restored_usage_snapshot(snapshot: Option<&Value>) -> Result { + let Some(snapshot) = snapshot else { + return Ok(json!({ + "inputTokens": 0, + "outputTokens": 0, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 0, + "requestCount": 0, + "estimatedCostUsd": 0.0, + "costSource": "paperclip_estimate" + })); + }; + let object = snapshot.as_object().ok_or_else(|| { + LocalRunnerError::invalid("AgentCore durable usage snapshot must be an object") + })?; + for field in [ + "inputTokens", + "outputTokens", + "cacheReadInputTokens", + "cacheWriteInputTokens", + "requestCount", + ] { + if object.get(field).and_then(Value::as_u64).is_none() { + return Err(LocalRunnerError::invalid(format!( + "AgentCore durable usage snapshot has invalid {field}" + ))); + } + } + let estimated = object + .get("estimatedCostUsd") + .and_then(Value::as_f64) + .filter(|value| value.is_finite() && *value >= 0.0) + .ok_or_else(|| { + LocalRunnerError::invalid( + "AgentCore durable usage snapshot has invalid estimatedCostUsd", + ) + })?; + if object.get("costSource").and_then(Value::as_str) != Some("paperclip_estimate") { + return Err(LocalRunnerError::invalid( + "AgentCore durable usage snapshot has invalid costSource", + )); + } + let pending_ceiling = match object.get(AGENTCORE_PENDING_CEILING_FIELD) { + None => None, + Some(value) => Some( + value + .as_f64() + .filter(|value| value.is_finite() && *value > 0.0) + .ok_or_else(|| { + LocalRunnerError::invalid( + "AgentCore durable usage snapshot has invalid pending estimated ceiling", + ) + })?, + ), + }; + let conservative_floor = match object.get(AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD) { + None => None, + Some(value) => Some( + value + .as_f64() + .filter(|value| value.is_finite() && *value > 0.0) + .ok_or_else(|| { + LocalRunnerError::invalid( + "AgentCore durable usage snapshot has invalid conservative cost floor", + ) + })?, + ), + }; + if conservative_floor.is_some_and(|floor| estimated < floor) { + return Err(LocalRunnerError::invalid( + "AgentCore durable usage snapshot undercuts its conservative cost floor", + )); + } + match ( + object.get(AGENTCORE_USAGE_RECONCILIATION_FIELD), + object.get(AGENTCORE_PENDING_INVOCATION_FIELD), + pending_ceiling, + ) { + (None, None, None) if conservative_floor.is_none() => {} + (Some(reconciliation), None, None) + if reconciliation.as_str() == Some(AGENTCORE_USAGE_RECONCILIATION_OBSERVED) => {} + (Some(reconciliation), None, None) + if reconciliation.as_str() == Some(AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE) + && conservative_floor.is_some() => {} + (Some(reconciliation), Some(invocation_id), _) + if reconciliation.as_str() == Some(AGENTCORE_USAGE_RECONCILIATION_PENDING) + && invocation_id.as_str().is_some_and(|invocation_id| { + !invocation_id.is_empty() + && invocation_id.len() <= 512 + && !invocation_id.chars().any(char::is_control) + }) => {} + _ => { + return Err(LocalRunnerError::invalid( + "AgentCore durable usage snapshot has invalid pending reconciliation state", + )); + } + } + let mut restored = snapshot.clone(); + restored["estimatedCostUsd"] = json!(estimated); + Ok(restored) +} + +#[derive(Clone, Debug)] +struct NetworkEvent { + invocation_id: String, + kind: NetworkEventKind, +} + +impl NetworkEvent { + fn new(invocation_id: &str, kind: NetworkEventKind) -> Self { + Self { + invocation_id: invocation_id.to_owned(), + kind, + } + } +} + +#[derive(Clone, Debug)] +enum NetworkEventKind { + TextDelta(String), + ReasoningProgress, + ToolUse { + call_id: String, + remote_name: String, + input: Value, + }, + Usage { + input_tokens: i64, + output_tokens: i64, + cache_read_input_tokens: i64, + cache_write_input_tokens: i64, + latency_ms: i64, + }, + Stop(String), + InvocationComplete, + MemoryCursor(String), + Failure(String), +} + +enum NetworkCommand { + Invoke { + messages: Vec, + tools: Vec, + allowed_tools: Vec, + invocation_id: String, + reply: mpsc::Sender>, + }, + StopRuntime { + token: String, + reply: mpsc::Sender>, + }, + DeleteMemory { + reply: mpsc::Sender>, + }, + Shutdown, +} + +struct NetworkWorker { + commands: SyncSender, + events: Receiver, + join: Option>, +} + +fn stop_runtime_target(config: &AwsAgentCoreProviderConfig) -> (&str, &str) { + ( + config.agent_runtime_arn.as_str(), + config.endpoint_qualifier.as_str(), + ) +} + +impl NetworkWorker { + fn start( + config: AwsAgentCoreProviderConfig, + session_id: String, + actor_id: String, + ) -> Result { + let (command_tx, command_rx) = mpsc::sync_channel(32); + let (event_tx, event_rx) = mpsc::sync_channel(256); + let (ready_tx, ready_rx) = mpsc::channel(); + let join = thread::Builder::new() + .name("paperclip-aws-agentcore-network".to_owned()) + .spawn(move || { + network_loop(config, session_id, actor_id, command_rx, event_tx, ready_tx) + }) + .map_err(|error| { + LocalRunnerError::invalid(format!( + "failed to start AgentCore network worker: {error}" + )) + })?; + ready_rx + .recv_timeout(Duration::from_secs(30)) + .map_err(|_| LocalRunnerError::invalid("AgentCore credential setup timed out"))? + .map_err(LocalRunnerError::invalid)?; + Ok(Self { + commands: command_tx, + events: event_rx, + join: Some(join), + }) + } + + fn invoke( + &self, + messages: Vec, + tools: Vec, + allowed_tools: Vec, + invocation_id: String, + ) -> Result<(), LocalRunnerError> { + let (reply_tx, reply_rx) = mpsc::channel(); + self.commands + .send(NetworkCommand::Invoke { + messages, + tools, + allowed_tools, + invocation_id, + reply: reply_tx, + }) + .map_err(|_| LocalRunnerError::invalid("AgentCore network worker stopped"))?; + reply_rx + .recv_timeout(AGENTCORE_INVOCATION_DELIVERY_TIMEOUT) + .map_err(|_| { + LocalRunnerError::invalid( + "AgentCore invocation delivery is ambiguous and requires Memory reconciliation", + ) + })? + .map_err(LocalRunnerError::invalid) + } + + fn stop_runtime(&self, token: String) -> Result<(), LocalRunnerError> { + let (reply_tx, reply_rx) = mpsc::channel(); + self.commands + .send(NetworkCommand::StopRuntime { + token, + reply: reply_tx, + }) + .map_err(|_| LocalRunnerError::invalid("AgentCore network worker stopped"))?; + reply_rx + .recv_timeout(Duration::from_secs(45)) + .map_err(|_| LocalRunnerError::invalid("AgentCore runtime stop timed out"))? + .map_err(LocalRunnerError::invalid) + } + + fn delete_memory(&self) -> Result<(), LocalRunnerError> { + let (reply_tx, reply_rx) = mpsc::channel(); + self.commands + .send(NetworkCommand::DeleteMemory { reply: reply_tx }) + .map_err(|_| LocalRunnerError::invalid("AgentCore network worker stopped"))?; + reply_rx + .recv_timeout(Duration::from_secs(60)) + .map_err(|_| LocalRunnerError::invalid("AgentCore Memory purge timed out"))? + .map_err(LocalRunnerError::invalid) + } + + fn try_event(&self) -> Option { + self.events.try_recv().ok() + } + + fn receive_event_until(&self, deadline: Instant) -> Option { + let remaining = deadline.saturating_duration_since(Instant::now()); + if remaining.is_zero() { + return None; + } + match self.events.recv_timeout(remaining) { + Ok(event) => Some(event), + Err(RecvTimeoutError::Timeout | RecvTimeoutError::Disconnected) => None, + } + } + + fn shutdown(&mut self) { + let _ = self.commands.send(NetworkCommand::Shutdown); + if let Some(join) = self.join.take() { + let _ = join.join(); + } + } +} + +impl Drop for NetworkWorker { + fn drop(&mut self) { + self.shutdown(); + } +} + +fn context_text<'a>(value: &'a Value, pointer: &str) -> Result<&'a str, String> { + value + .pointer(pointer) + .and_then(Value::as_str) + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| format!("runtimeContext {pointer} is missing")) +} + +fn collect_context_files(root: &Path) -> Result)>, String> { + fn visit( + root: &Path, + current: &Path, + files: &mut Vec<(PathBuf, Vec)>, + total: &mut usize, + ) -> Result<(), String> { + let mut entries = fs::read_dir(current) + .map_err(|error| format!("failed to read runtime context asset: {error}"))? + .collect::, _>>() + .map_err(|error| format!("failed to enumerate runtime context asset: {error}"))?; + entries.sort_by_key(|entry| entry.file_name()); + for entry in entries { + let path = entry.path(); + let metadata = fs::symlink_metadata(&path) + .map_err(|error| format!("failed to inspect runtime context asset: {error}"))?; + if metadata.file_type().is_symlink() { + return Err("AgentCore context bundles may not contain symlinks".to_owned()); + } + if metadata.is_dir() { + visit(root, &path, files, total)?; + } else if metadata.is_file() { + let relative = path + .strip_prefix(root) + .map_err(|_| "runtime context asset escaped its root".to_owned())? + .to_path_buf(); + let bytes = fs::read(&path) + .map_err(|error| format!("failed to read runtime context file: {error}"))?; + *total = total.saturating_add(bytes.len()); + if *total > MAX_CONTEXT_UPLOAD_BYTES { + return Err("AgentCore context upload exceeded its size limit".to_owned()); + } + if files.len() >= MAX_CONTEXT_UPLOAD_FILES { + return Err("AgentCore context upload exceeded its file limit".to_owned()); + } + files.push((relative, bytes)); + } + } + Ok(()) + } + let metadata = fs::symlink_metadata(root) + .map_err(|error| format!("runtime context asset is unavailable: {error}"))?; + if metadata.file_type().is_symlink() || !metadata.is_dir() { + return Err("runtime context asset root must be a real directory".to_owned()); + } + let mut files = Vec::new(); + let mut total = 0; + visit(root, root, &mut files, &mut total)?; + Ok(files) +} + +async fn upload_context_directory( + client: &aws_sdk_s3::Client, + config: &AwsAgentCoreProviderConfig, + digest: &str, + files: Vec<(PathBuf, Vec)>, + generated_skill: Option, +) -> Result { + let prefix = config.context_prefix.trim_matches('/'); + let asset_prefix = format!("{prefix}/assets/{digest}"); + let mut files = files; + if let Some(skill) = generated_skill { + files.push((PathBuf::from("SKILL.md"), skill.into_bytes())); + } + for (relative, bytes) in files { + let relative = relative + .components() + .map(|component| component.as_os_str().to_string_lossy()) + .collect::>() + .join("/"); + if relative.is_empty() + || relative + .split('/') + .any(|part| part.is_empty() || part == "." || part == "..") + { + return Err("runtime context upload path is unsafe".to_owned()); + } + let key = format!("{asset_prefix}/{relative}"); + let content_digest = format!("{:x}", Sha256::digest(&bytes)); + if let Ok(existing) = client + .head_object() + .bucket(&config.context_bucket) + .key(&key) + .send() + .await + { + let metadata_digest = existing + .metadata() + .and_then(|metadata| metadata.get("paperclip-sha256")) + .map(String::as_str); + if existing.content_length() != Some(bytes.len() as i64) + || metadata_digest != Some(content_digest.as_str()) + || existing.server_side_encryption() + != Some(&aws_sdk_s3::types::ServerSideEncryption::AwsKms) + || existing.ssekms_key_id() != Some(config.context_kms_key_arn.as_str()) + { + return Err(format!( + "AgentCore context S3 asset verification failed: {}", + sha_hex(&key, 16) + )); + } + continue; + } + client + .put_object() + .bucket(&config.context_bucket) + .key(&key) + .server_side_encryption(aws_sdk_s3::types::ServerSideEncryption::AwsKms) + .ssekms_key_id(&config.context_kms_key_arn) + .metadata("paperclip-sha256", content_digest) + .body(ByteStream::from(bytes)) + .send() + .await + .map_err(|error| { + format!( + "AgentCore context S3 upload failed: {}", + redact_aws_error(&error.to_string()) + ) + })?; + } + let source = HarnessSkillS3Source::builder() + .uri(format!("s3://{}/{asset_prefix}/", config.context_bucket)) + .build() + .map_err(|_| "failed to build AgentCore S3 skill source".to_owned())?; + Ok(HarnessSkill::S3(source)) +} + +#[derive(Debug)] +struct AgentCoreContextAsset { + digest: String, + files: Vec<(PathBuf, Vec)>, + generated_skill: Option, +} + +fn validate_agentcore_context_aggregate( + asset_count: usize, + file_count: usize, + byte_count: usize, +) -> Result<(), String> { + if asset_count > MAX_CONTEXT_SKILL_SOURCES { + return Err("AgentCore context exceeds the Harness skill-source limit".to_owned()); + } + if file_count > MAX_CONTEXT_UPLOAD_FILES { + return Err("AgentCore context exceeds the aggregate file limit".to_owned()); + } + if byte_count > MAX_CONTEXT_UPLOAD_BYTES { + return Err("AgentCore context exceeds the aggregate byte limit".to_owned()); + } + Ok(()) +} + +fn agentcore_context_totals(assets: &[AgentCoreContextAsset]) -> (usize, usize) { + assets.iter().fold((0_usize, 0_usize), |totals, asset| { + let generated = asset.generated_skill.as_ref(); + let file_count = totals + .0 + .saturating_add(asset.files.len()) + .saturating_add(usize::from(generated.is_some())); + let byte_count = asset + .files + .iter() + .fold(totals.1, |sum, (_, bytes)| sum.saturating_add(bytes.len())) + .saturating_add(generated.map_or(0, String::len)); + (file_count, byte_count) + }) +} + +fn prepare_agentcore_runtime_context_assets( + config: &AwsAgentCoreProviderConfig, +) -> Result, String> { + let context = config.runtime_context.as_ref().ok_or_else(|| { + "AgentCore requires paperclip.native-execution-input.v3 runtimeContext".to_owned() + })?; + let instruction_digest = context_text(context, "/instructions/bundle/digest")?; + let instruction_root = Path::new(context_text(context, "/instructions/bundle/rootPath")?); + let entry_path = context_text(context, "/instructions/entryPath")?; + let instruction_files = collect_context_files(instruction_root)? + .into_iter() + .map(|(path, bytes)| (PathBuf::from("instructions").join(path), bytes)) + .collect(); + let instruction_companion = format!( + "---\nname: paperclip-instructions-{}\ndescription: Paperclip agent instruction sibling bundle\n---\nRead `instructions/{entry_path}` and its sibling files as read-only context.\n", + &instruction_digest[..12] + ); + let instruction_asset_digest = sha_hex( + &format!("{instruction_digest}\0{entry_path}\0{instruction_companion}"), + 64, + ); + let mut assets = vec![AgentCoreContextAsset { + digest: instruction_asset_digest, + files: instruction_files, + generated_skill: Some(instruction_companion), + }]; + let (file_count, byte_count) = agentcore_context_totals(&assets); + validate_agentcore_context_aggregate(assets.len(), file_count, byte_count)?; + let assigned = context + .get("skills") + .and_then(Value::as_array) + .ok_or_else(|| "runtimeContext.skills must be an array".to_owned())?; + validate_agentcore_context_aggregate(assets.len().saturating_add(assigned.len()), 0, 0)?; + for skill in assigned { + let digest = context_text(skill, "/bundle/digest")?; + let root = Path::new(context_text(skill, "/bundle/rootPath")?); + let files = collect_context_files(root)?; + if !files.iter().any(|(path, _)| path == Path::new("SKILL.md")) { + return Err("AgentCore custom skill bundle is missing SKILL.md".to_owned()); + } + assets.push(AgentCoreContextAsset { + digest: digest.to_owned(), + files, + generated_skill: None, + }); + let (file_count, byte_count) = agentcore_context_totals(&assets); + validate_agentcore_context_aggregate(assets.len(), file_count, byte_count)?; + } + Ok(assets) +} + +async fn upload_agentcore_runtime_context( + client: &aws_sdk_s3::Client, + config: &AwsAgentCoreProviderConfig, +) -> Result, String> { + let mut skills = Vec::new(); + for asset in prepare_agentcore_runtime_context_assets(config)? { + skills.push( + upload_context_directory( + client, + config, + &asset.digest, + asset.files, + asset.generated_skill, + ) + .await?, + ); + } + Ok(skills) +} + +fn agentcore_system_instructions(config: &AwsAgentCoreProviderConfig) -> Result { + let Some(context) = config.runtime_context.as_ref() else { + return Ok(config.instructions.clone()); + }; + let instruction_root = context_text(context, "/instructions/bundle/rootPath")?; + let local_directive = format!("Read-only instruction sibling root: {instruction_root}"); + config + .instructions + .strip_suffix(&local_directive) + .map(|prefix| format!( + "{prefix}Read-only instruction siblings are in the attached Paperclip HarnessSkill under `instructions/`." + )) + .ok_or_else(|| "AgentCore instruction-root directive is missing or inconsistent".to_owned()) +} + +fn network_loop( + config: AwsAgentCoreProviderConfig, + session_id: String, + actor_id: String, + commands: Receiver, + events: SyncSender, + ready: mpsc::Sender>, +) { + let runtime = match tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + { + Ok(runtime) => runtime, + Err(error) => { + let _ = ready.send(Err(format!( + "failed to create AgentCore async runtime: {error}" + ))); + return; + } + }; + let clients = runtime.block_on(async { + let region = Region::new(config.region.clone()); + let base = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(region.clone()) + .load() + .await; + let assumed = AssumeRoleProvider::builder(config.invocation_role_arn.clone()) + .session_name(format!( + "paperclip-{}", + &session_id[session_id.len().saturating_sub(24)..] + )) + .configure(&base) + .build() + .await; + let shared = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(region) + .credentials_provider(assumed) + .load() + .await; + let s3 = aws_sdk_s3::Client::new(&shared); + let skills = upload_agentcore_runtime_context(&s3, &config).await?; + let system_instructions = agentcore_system_instructions(&config)?; + Ok::<_, String>(( + aws_sdk_bedrockagentcore::Client::new(&shared), + config, + skills, + system_instructions, + )) + }); + let (client, config, skills, system_instructions) = match clients { + Ok(clients) => clients, + Err(error) => { + let _ = ready.send(Err(error)); + return; + } + }; + let _ = ready.send(Ok(())); + while let Ok(command) = commands.recv() { + match command { + NetworkCommand::Invoke { + messages, + tools, + allowed_tools, + invocation_id, + reply, + } => { + let client = client.clone(); + let config = config.clone(); + let session_id = session_id.clone(); + let actor_id = actor_id.clone(); + let events = events.clone(); + let skills = skills.clone(); + let system_instructions = system_instructions.clone(); + runtime.spawn(async move { + // The qualified Harness version is the immutable model + // authority. Supplying a redundant invocation override + // changes AgentCore's authorization path and can require + // caller-side Marketplace permissions, bypassing the + // execution role qualified during provisioning. + let response = client + .invoke_harness() + .harness_arn(config.harness_arn.clone()) + .qualifier(config.endpoint_qualifier.clone()) + .runtime_session_id(session_id.clone()) + .runtime_user_id(actor_id.clone()) + .actor_id(actor_id.clone()) + .set_messages(Some(messages)) + .set_tools(Some(tools)) + // Invocation-scoped inline functions live behind an + // AgentCore server namespace. Unqualified names are + // silently withheld, so admit each authorized tool as + // `@*/` and nothing else. + .set_allowed_tools(Some(allowed_tools)) + .set_system_prompt(Some(vec![HarnessSystemContentBlock::Text( + system_instructions, + )])) + .set_skills(Some(skills)) + .max_iterations(config.max_iterations as i32) + .max_tokens(config.max_output_tokens as i32) + .timeout_seconds(config.timeout_seconds as i32) + .trace_parent(format!( + "00-{}-{}-01", + sha_hex(&invocation_id, 32), + sha_hex(&(invocation_id.clone() + "span"), 16) + )) + .send() + .await; + let mut response = match response { + Ok(value) => value, + Err(error) => { + let detail = classify_aws_sdk_error(&error); + let _ = reply.send(Err(detail.clone())); + let _ = events.send(NetworkEvent::new( + &invocation_id, + NetworkEventKind::Failure(detail), + )); + return; + } + }; + let _ = reply.send(Ok(())); + let mut tool_blocks: BTreeMap = BTreeMap::new(); + loop { + match response.stream.recv().await { + Ok(Some(event)) => normalize_stream_event( + event, + &mut tool_blocks, + &events, + &invocation_id, + ), + Ok(None) => break, + Err(error) => { + let _ = events.send(NetworkEvent::new( + &invocation_id, + NetworkEventKind::Failure(classify_aws_sdk_error(&error)), + )); + break; + } + } + } + match latest_memory_event_id(&client, &config, &session_id, &actor_id).await { + Ok(Some(event_id)) => { + let _ = events.send(NetworkEvent::new( + &invocation_id, + NetworkEventKind::MemoryCursor(event_id), + )); + } + Ok(None) => {} + Err(detail) => { + let _ = events.send(NetworkEvent::new( + &invocation_id, + NetworkEventKind::Failure(detail), + )); + return; + } + } + let _ = events.send(NetworkEvent::new( + &invocation_id, + NetworkEventKind::InvocationComplete, + )); + }); + } + NetworkCommand::StopRuntime { token, reply } => { + let result = runtime.block_on(async { + let (runtime_arn, qualifier) = stop_runtime_target(&config); + match client + .stop_runtime_session() + .runtime_session_id(session_id.clone()) + .agent_runtime_arn(runtime_arn) + .qualifier(qualifier) + .client_token(token) + .send() + .await + { + Ok(_) => Ok(()), + Err(error) if is_resource_not_found(&error.to_string()) => Ok(()), + Err(error) => Err(redact_aws_error(&error.to_string())), + } + }); + let _ = reply.send(result); + } + NetworkCommand::DeleteMemory { reply } => { + let result = runtime.block_on(delete_all_memory_events( + &client, + &config, + &session_id, + &actor_id, + )); + let _ = reply.send(result); + } + NetworkCommand::Shutdown => break, + } + } +} + +fn normalize_stream_event( + event: InvokeHarnessStreamOutput, + tool_blocks: &mut BTreeMap, + events: &SyncSender, + invocation_id: &str, +) { + match event { + InvokeHarnessStreamOutput::ContentBlockStart(value) => { + if let Some(HarnessContentBlockStart::ToolUse(start)) = value.start() { + tool_blocks.insert( + value.content_block_index(), + ( + start.tool_use_id().to_owned(), + start.name().to_owned(), + String::new(), + ), + ); + } + } + InvokeHarnessStreamOutput::ContentBlockDelta(value) => match value.delta() { + Some(HarnessContentBlockDelta::Text(text)) => { + let _ = events.send(NetworkEvent::new( + invocation_id, + NetworkEventKind::TextDelta(text.clone()), + )); + } + Some(HarnessContentBlockDelta::ReasoningContent(_)) => { + let _ = events.send(NetworkEvent::new( + invocation_id, + NetworkEventKind::ReasoningProgress, + )); + } + Some(HarnessContentBlockDelta::ToolUse(delta)) => { + if let Some((_, _, input)) = tool_blocks.get_mut(&value.content_block_index()) { + input.push_str(delta.input()); + } + } + _ => {} + }, + InvokeHarnessStreamOutput::ContentBlockStop(value) => { + if let Some((call_id, remote_name, input)) = + tool_blocks.remove(&value.content_block_index()) + { + let parsed = if input.trim().is_empty() { + Ok(json!({})) + } else { + serde_json::from_str::(&input) + }; + match parsed { + Ok(input) => { + let _ = events.send(NetworkEvent::new( + invocation_id, + NetworkEventKind::ToolUse { + call_id, + remote_name, + input, + }, + )); + } + Err(_) => { + let _ = events.send(NetworkEvent::new( + invocation_id, + NetworkEventKind::Failure( + "AgentCore emitted malformed inline-tool JSON".to_owned(), + ), + )); + } + } + } + } + InvokeHarnessStreamOutput::Metadata(value) => { + let usage = value.usage(); + let metrics = value.metrics(); + let _ = events.send(NetworkEvent::new( + invocation_id, + NetworkEventKind::Usage { + input_tokens: usage.map(|v| v.input_tokens() as i64).unwrap_or(0), + output_tokens: usage.map(|v| v.output_tokens() as i64).unwrap_or(0), + cache_read_input_tokens: usage + .and_then(|v| v.cache_read_input_tokens()) + .unwrap_or(0) as i64, + cache_write_input_tokens: usage + .and_then(|v| v.cache_write_input_tokens()) + .unwrap_or(0) as i64, + latency_ms: metrics.map(|v| v.latency_ms()).unwrap_or(0), + }, + )); + } + InvokeHarnessStreamOutput::MessageStop(value) => { + let _ = events.send(NetworkEvent::new( + invocation_id, + NetworkEventKind::Stop(value.stop_reason().as_str().to_owned()), + )); + } + InvokeHarnessStreamOutput::MessageStart(_) => {} + _ => { + let _ = events.send(NetworkEvent::new( + invocation_id, + NetworkEventKind::Failure( + "AgentCore SDK did not recognize an EventStream record".to_owned(), + ), + )); + } + } +} + +fn observe_memory_history_page( + pages: &mut usize, + events: &mut usize, + page_events: usize, + operation: &str, +) -> Result<(), String> { + *pages = pages.saturating_add(1); + *events = events.saturating_add(page_events); + if *pages > MAX_MEMORY_HISTORY_PAGES { + return Err(format!( + "AgentCore Memory {operation} exceeded its page bound" + )); + } + if *events > MAX_MEMORY_HISTORY_EVENTS { + return Err(format!( + "AgentCore Memory {operation} exceeded its event bound" + )); + } + Ok(()) +} + +async fn purge_memory_event_ids( + mut list_first_page: List, + mut delete: Delete, +) -> Result<(), String> +where + List: FnMut() -> ListFuture, + ListFuture: Future, String>>, + Delete: FnMut(String) -> DeleteFuture, + DeleteFuture: Future>, +{ + let mut pages = 0_usize; + let mut event_count = 0_usize; + let mut deleted_ids = HashSet::new(); + loop { + let ids = list_first_page().await?; + observe_memory_history_page(&mut pages, &mut event_count, ids.len(), "purge")?; + if ids.is_empty() { + return Ok(()); + } + for event_id in ids { + if !deleted_ids.insert(event_id.clone()) { + return Err("AgentCore Memory purge made no progress after deletion".to_owned()); + } + delete(event_id).await?; + } + } +} + +async fn delete_all_memory_events( + client: &aws_sdk_bedrockagentcore::Client, + config: &AwsAgentCoreProviderConfig, + session_id: &str, + actor_id: &str, +) -> Result<(), String> { + let list_client = client.clone(); + let list_memory_id = config.memory_id.clone(); + let list_session_id = session_id.to_owned(); + let list_actor_id = actor_id.to_owned(); + let delete_client = client.clone(); + let delete_memory_id = config.memory_id.clone(); + let delete_session_id = session_id.to_owned(); + let delete_actor_id = actor_id.to_owned(); + purge_memory_event_ids( + move || { + let client = list_client.clone(); + let memory_id = list_memory_id.clone(); + let session_id = list_session_id.clone(); + let actor_id = list_actor_id.clone(); + async move { + client + .list_events() + .memory_id(memory_id) + .session_id(session_id) + .actor_id(actor_id) + .include_payloads(false) + .max_results(100) + .send() + .await + .map(|page| { + page.events() + .iter() + .map(|event| event.event_id().to_owned()) + .collect() + }) + .map_err(|error| redact_aws_error(&error.to_string())) + } + }, + move |event_id| { + let client = delete_client.clone(); + let memory_id = delete_memory_id.clone(); + let session_id = delete_session_id.clone(); + let actor_id = delete_actor_id.clone(); + async move { + match client + .delete_event() + .memory_id(memory_id) + .session_id(session_id) + .actor_id(actor_id) + .event_id(event_id) + .send() + .await + { + Ok(_) => Ok(()), + Err(error) if is_resource_not_found(&error.to_string()) => Ok(()), + Err(error) => Err(redact_aws_error(&error.to_string())), + } + } + }, + ) + .await +} + +async fn latest_memory_event_id( + client: &aws_sdk_bedrockagentcore::Client, + config: &AwsAgentCoreProviderConfig, + session_id: &str, + actor_id: &str, +) -> Result, String> { + let mut next_token: Option = None; + let mut latest: Option<(i64, String)> = None; + let mut page_count = 0_usize; + let mut event_count = 0_usize; + loop { + let page = client + .list_events() + .memory_id(config.memory_id.clone()) + .session_id(session_id) + .actor_id(actor_id) + .include_payloads(false) + .max_results(100) + .set_next_token(next_token.take()) + .send() + .await + .map_err(|error| redact_aws_error(&error.to_string()))?; + observe_memory_history_page( + &mut page_count, + &mut event_count, + page.events().len(), + "history scan", + )?; + for event in page.events() { + let candidate = (event.event_timestamp().secs(), event.event_id().to_owned()); + if latest.as_ref().is_none_or(|current| candidate > *current) { + latest = Some(candidate); + } + } + next_token = page.next_token().map(str::to_owned); + if next_token.is_none() { + break; + } + } + Ok(latest.map(|(_, event_id)| event_id)) +} + +pub struct AwsAgentCoreHarnessProvider { + config: AwsAgentCoreProviderConfig, + session_id: String, + actor_id: String, + worker: NetworkWorker, + tools: Vec, + allowed_tools: Vec, + remote_to_canonical: BTreeMap, + input_schemas: BTreeMap, + pending: BTreeMap, + delivered_results: BTreeMap, + queue: VecDeque, + current_turn_id: Option, + current_text: String, + invocation_counter: u64, + active_invocation_id: Option, + durable_cursor: Option, + usage: Value, + pending_stop_reason: Option, + invocation_usage_observed: bool, + invocation_budget_reached: bool, + max_estimated_cost_usd: f64, +} + +impl AwsAgentCoreHarnessProvider { + pub fn start( + config: &AwsAgentCoreProviderConfig, + tools: Vec, + resume_session_id: Option<&str>, + resume_event_cursor: Option<&str>, + resume_usage: Option<&Value>, + ) -> Result { + validate_config(config)?; + let usage = restored_usage_snapshot(resume_usage)?; + let session_id = resume_session_id + .map(str::to_owned) + .unwrap_or_else(new_runtime_session_id); + let actor_id = format!("paperclip-{}", sha_hex(&session_id, 32)); + let worker = NetworkWorker::start(config.clone(), session_id.clone(), actor_id.clone())?; + let (encoded, allowed, reverse, schemas) = encode_tools(&tools)?; + Ok(Self { + config: config.clone(), + session_id, + actor_id, + worker, + tools: encoded, + allowed_tools: allowed, + remote_to_canonical: reverse, + input_schemas: schemas, + pending: BTreeMap::new(), + delivered_results: BTreeMap::new(), + queue: VecDeque::new(), + current_turn_id: None, + current_text: String::new(), + invocation_counter: 0, + active_invocation_id: None, + durable_cursor: resume_event_cursor.map(str::to_owned), + usage, + pending_stop_reason: None, + invocation_usage_observed: false, + invocation_budget_reached: false, + max_estimated_cost_usd: config.max_estimated_session_cost_usd, + }) + } + + fn invoke(&mut self, messages: Vec) -> Result { + if self.pending_stop_reason.is_some() { + return Err(LocalRunnerError::invalid( + "AgentCore prior invocation has not reached its metadata boundary", + )); + } + if self.active_invocation_id.is_some() { + return Err(LocalRunnerError::invalid( + "AgentCore prior invocation is still active", + )); + } + self.require_reconciled_interrupt_usage()?; + self.require_available_budget()?; + self.invocation_counter = self.invocation_counter.saturating_add(1); + self.invocation_usage_observed = false; + self.invocation_budget_reached = false; + // A runner restart cannot recover the in-memory counter from an + // AgentCore Memory event id. Include fresh entropy so a resumed + // session never aliases a prior invocation even when its durable + // counter restarts from zero. + let invocation_id = format!( + "{}-{}-{}", + self.session_id, + self.invocation_counter, + Uuid::new_v4() + ); + self.durable_cursor = Some(invocation_id.clone()); + // Delivery can time out after AgentCore has accepted the invocation. + // Retain its identity before crossing that ambiguity boundary so a + // subsequent interrupt can reconcile (or durably fail closed on) the + // matching authoritative usage metadata. + self.active_invocation_id = Some(invocation_id.clone()); + self.worker.invoke( + messages, + self.tools.clone(), + self.allowed_tools.clone(), + invocation_id.clone(), + )?; + Ok(json!({ "runtimeSessionId": self.session_id, "invocationId": invocation_id })) + } + + fn record_usage( + &mut self, + input_tokens: i64, + output_tokens: i64, + cache_read_input_tokens: i64, + cache_write_input_tokens: i64, + latency_ms: i64, + enforce_budget: bool, + ) -> ProviderEvent { + let prior_input = self + .usage + .get("inputTokens") + .and_then(Value::as_i64) + .unwrap_or(0); + let prior_output = self + .usage + .get("outputTokens") + .and_then(Value::as_i64) + .unwrap_or(0); + let prior_cache_read = self + .usage + .get("cacheReadInputTokens") + .and_then(Value::as_i64) + .unwrap_or(0); + let prior_cache_write = self + .usage + .get("cacheWriteInputTokens") + .and_then(Value::as_i64) + .unwrap_or(0); + let total_input = prior_input.saturating_add(input_tokens); + let total_output = prior_output.saturating_add(output_tokens); + let total_cache_read = prior_cache_read.saturating_add(cache_read_input_tokens); + let total_cache_write = prior_cache_write.saturating_add(cache_write_input_tokens); + let requests = self + .usage + .get("requestCount") + .and_then(Value::as_u64) + .unwrap_or(0) + .saturating_add(1); + let model_estimate = estimate_model_token_cost_usd( + &self.config.model, + total_input, + total_output, + total_cache_read, + total_cache_write, + ); + let conservative_floor = self + .usage + .get(AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD) + .and_then(Value::as_f64); + let estimate = match (model_estimate, conservative_floor) { + (Some(estimate), Some(floor)) => Some(estimate.max(floor)), + (Some(estimate), None) => Some(estimate), + (None, Some(floor)) => Some(floor), + (None, None) => None, + }; + let mut usage = json!({ + "inputTokens": total_input, + "outputTokens": total_output, + "cacheReadInputTokens": total_cache_read, + "cacheWriteInputTokens": total_cache_write, + "requestCount": requests, + "latencyMs": latency_ms, + "estimatedCostUsd": estimate, + "estimateScope": "bedrock_model_tokens_only", + "costSource": "paperclip_estimate", + "estimatedCeilingUsd": self.max_estimated_cost_usd, + }); + if let Some(floor) = conservative_floor { + usage[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD] = json!(floor); + usage[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE); + usage["tokenCountsLowerBound"] = json!(true); + } + self.usage = usage; + if enforce_budget && estimate.is_some_and(|value| value >= self.max_estimated_cost_usd) { + self.invocation_budget_reached = true; + self.queue.push_back(ProviderEvent::Notification { + method: "provider/budgetReached".to_owned(), + params: json!({ "turnId": self.current_turn_id, "status": "limit_reached", "stopReason": "estimated_session_cost", "estimatedCostUsd": estimate, "costSource": "paperclip_estimate" }), + }); + } + ProviderEvent::Notification { + method: "thread/tokenUsage/updated".to_owned(), + params: self.usage.clone(), + } + } + + fn pending_usage_reconciliation_invocation_id(&self) -> Option { + (self + .usage + .get(AGENTCORE_USAGE_RECONCILIATION_FIELD) + .and_then(Value::as_str) + == Some(AGENTCORE_USAGE_RECONCILIATION_PENDING)) + .then(|| { + self.usage + .get(AGENTCORE_PENDING_INVOCATION_FIELD) + .and_then(Value::as_str) + .map(str::to_owned) + }) + .flatten() + } + + fn mark_usage_reconciliation_pending(&mut self, invocation_id: &str) { + self.usage[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_PENDING); + self.usage[AGENTCORE_PENDING_INVOCATION_FIELD] = json!(invocation_id); + self.usage[AGENTCORE_PENDING_CEILING_FIELD] = json!(self.max_estimated_cost_usd); + } + + fn mark_usage_reconciliation_observed(&mut self) { + self.usage[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_OBSERVED); + if let Some(usage) = self.usage.as_object_mut() { + usage.remove(AGENTCORE_PENDING_INVOCATION_FIELD); + usage.remove(AGENTCORE_PENDING_CEILING_FIELD); + } + } + + fn reconcile_pending_usage_to_ceiling(&mut self) { + if self.pending_usage_reconciliation_invocation_id().is_none() { + return; + } + let pending_ceiling = self + .usage + .get(AGENTCORE_PENDING_CEILING_FIELD) + .and_then(Value::as_f64) + // Snapshots from the brief fail-closed-only implementation did + // not persist the ceiling. Charging the current ceiling is the + // safe backward-compatible fallback. + .unwrap_or(self.max_estimated_cost_usd); + let existing_estimate = self + .usage + .get("estimatedCostUsd") + .and_then(Value::as_f64) + .unwrap_or(0.0); + let existing_floor = self + .usage + .get(AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD) + .and_then(Value::as_f64) + .unwrap_or(0.0); + let conservative_floor = pending_ceiling.max(existing_estimate).max(existing_floor); + let request_count = self + .usage + .get("requestCount") + .and_then(Value::as_u64) + .unwrap_or(0) + .saturating_add(1); + self.usage["requestCount"] = json!(request_count); + self.usage["estimatedCostUsd"] = json!(conservative_floor); + self.usage["estimatedCeilingUsd"] = json!(self.max_estimated_cost_usd); + self.usage["estimateScope"] = + json!("bedrock_model_tokens_with_interrupted_invocation_cost_floor"); + self.usage["tokenCountsLowerBound"] = json!(true); + self.usage[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD] = json!(conservative_floor); + self.usage[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE); + if let Some(usage) = self.usage.as_object_mut() { + usage.remove(AGENTCORE_PENDING_INVOCATION_FIELD); + usage.remove(AGENTCORE_PENDING_CEILING_FIELD); + } + } + + fn require_reconciled_interrupt_usage(&self) -> Result<(), LocalRunnerError> { + if self.pending_usage_reconciliation_invocation_id().is_some() { + return Err(LocalRunnerError::invalid( + "AgentCore usage reconciliation remains pending after an interrupted invocation", + )); + } + Ok(()) + } + + fn require_available_budget(&self) -> Result<(), LocalRunnerError> { + if self + .usage + .get("estimatedCostUsd") + .and_then(Value::as_f64) + .unwrap_or(0.0) + >= self.max_estimated_cost_usd + { + return Err(LocalRunnerError::invalid("AgentCore estimated session spend ceiling reached; raise it explicitly before continuing")); + } + Ok(()) + } + + fn reconcile_interrupted_usage(&mut self) { + if self.invocation_usage_observed { + return; + } + let Some(active_invocation_id) = self.active_invocation_id.clone() else { + return; + }; + let deadline = Instant::now() + AGENTCORE_INTERRUPT_USAGE_RECONCILIATION_TIMEOUT; + for _ in 0..MAX_INTERRUPT_DRAIN_EVENTS { + let Some(event) = self.worker.receive_event_until(deadline) else { + break; + }; + if event.invocation_id != active_invocation_id { + continue; + } + if let NetworkEventKind::Usage { + input_tokens, + output_tokens, + cache_read_input_tokens, + cache_write_input_tokens, + latency_ms, + } = event.kind + { + if !self.invocation_usage_observed { + self.record_usage( + input_tokens, + output_tokens, + cache_read_input_tokens, + cache_write_input_tokens, + latency_ms, + false, + ); + self.invocation_usage_observed = true; + break; + } + } + } + } + + fn settle_interrupted_turn(&mut self, turn_id: &str, interrupted_invocation_id: Option<&str>) { + let usage_observed = self.invocation_usage_observed; + if usage_observed { + self.mark_usage_reconciliation_observed(); + } else { + if let Some(invocation_id) = interrupted_invocation_id { + self.mark_usage_reconciliation_pending(invocation_id); + } + } + self.active_invocation_id = None; + self.pending_stop_reason = None; + self.invocation_usage_observed = false; + self.invocation_budget_reached = false; + self.pending.clear(); + self.delivered_results.clear(); + self.queue.retain(|event| { + !matches!( + event, + ProviderEvent::Notification { method, .. } + if method == "provider/budgetReached" + ) + }); + + self.queue.push_back(ProviderEvent::Notification { + method: "thread/tokenUsage/updated".to_owned(), + params: self.usage.clone(), + }); + if !self.current_text.is_empty() { + self.queue.push_back(ProviderEvent::Notification { + method: "item/completed".to_owned(), + params: json!({ "turnId": turn_id, "item": { "id": format!("aws-message-{}", self.invocation_counter), "type": "agentMessage", "text": self.current_text, "authoritative": true } }), + }); + } + self.current_text.clear(); + self.current_turn_id.take(); + self.queue.push_back(ProviderEvent::Notification { + method: "turn/completed".to_owned(), + params: json!({ "turnId": turn_id, "turn": { "id": turn_id, "status": "interrupted" }, "stopReason": "interrupted" }), + }); + } +} + +impl Provider for AwsAgentCoreHarnessProvider { + fn kind(&self) -> ProviderKind { + ProviderKind::AwsAgentcore + } + + fn runtime_identity(&self) -> ProviderRuntimeIdentity { + ProviderRuntimeIdentity::RemoteService { + service: "aws_bedrock_agentcore_harness".to_owned(), + provider_session_id: self.session_id.clone(), + process_id: None, + } + } + + fn session_identity(&self) -> &str { + &self.session_id + } + fn provider_session_id(&self) -> Option<&str> { + Some(&self.session_id) + } + fn durable_event_cursor(&self) -> Option<&str> { + self.durable_cursor.as_deref() + } + + fn model_request_count(&self) -> Option { + self.usage.get("requestCount").and_then(Value::as_u64) + } + + fn usage_snapshot(&self) -> Option { + Some(self.usage.clone()) + } + + fn restore_active_turn(&mut self, turn_id: &str) -> Result<(), LocalRunnerError> { + match self.current_turn_id.as_deref() { + None => self.current_turn_id = Some(turn_id.to_owned()), + Some(current) if current == turn_id => {} + Some(_) => { + return Err(LocalRunnerError::invalid( + "AgentCore active turn does not match durable recovery state", + )) + } + } + Ok(()) + } + + fn restore_pending_tool_call( + &mut self, + call_id: &str, + operation_id: &str, + input: &Value, + ) -> Result<(), LocalRunnerError> { + let remote_name = self + .remote_to_canonical + .iter() + .find_map(|(remote, canonical)| (canonical == operation_id).then(|| remote.clone())) + .ok_or_else(|| { + LocalRunnerError::invalid( + "AgentCore durable tool call is not in the authorized tool catalog", + ) + })?; + let restored = RemoteToolUse { + remote_name, + operation_id: operation_id.to_owned(), + input: input.clone(), + }; + match self.pending.get(call_id) { + Some(current) + if current.operation_id != restored.operation_id + || current.input != restored.input => + { + Err(LocalRunnerError::invalid( + "AgentCore pending tool call conflicts with durable recovery state", + )) + } + _ => { + self.pending.insert(call_id.to_owned(), restored); + Ok(()) + } + } + } + + fn configure_tools(&mut self, tools: Vec) -> Result<(), LocalRunnerError> { + if self.current_turn_id.is_some() { + return Err(LocalRunnerError::invalid( + "cannot replace AgentCore tools while a turn is active", + )); + } + let (encoded, allowed, reverse, schemas) = encode_tools(&tools)?; + self.tools = encoded; + self.allowed_tools = allowed; + self.remote_to_canonical = reverse; + self.input_schemas = schemas; + Ok(()) + } + + fn increase_budget(&mut self, value: f64) -> Result { + if !value.is_finite() || value <= self.max_estimated_cost_usd { + return Err(LocalRunnerError::invalid( + "AgentCore estimated spend ceiling may only be raised monotonically", + )); + } + self.max_estimated_cost_usd = value; + Ok(json!({ "maxEstimatedSessionCostUsd": value, "costSource": "paperclip_estimate" })) + } + + fn destroy_session(&mut self) -> Result<(), LocalRunnerError> { + self.worker.stop_runtime(format!( + "paperclip-delete-{}", + sha_hex(&self.session_id, 32) + ))?; + self.worker.delete_memory()?; + self.worker.shutdown(); + Ok(()) + } + + fn preflight_turn(&mut self) -> Result<(), LocalRunnerError> { + // Reconciliation is an idempotent next-turn boundary operation. It + // never drains or publishes the prior EventStream after its terminal; + // the durable cost floor is checkpointed before admission instead. + self.reconcile_pending_usage_to_ceiling(); + self.require_reconciled_interrupt_usage()?; + self.require_available_budget() + } + + fn start_turn( + &mut self, + message: &str, + _cwd: &str, + turn_id: &str, + ) -> Result { + if self.current_turn_id.is_some() { + return Err(LocalRunnerError::invalid("AgentCore turn already active")); + } + self.preflight_turn()?; + self.current_turn_id = Some(turn_id.to_owned()); + self.current_text.clear(); + self.queue.push_back(ProviderEvent::Notification { + method: "turn/started".to_owned(), + params: json!({ "turnId": turn_id, "turn": { "id": turn_id, "status": "inProgress" } }), + }); + self.invoke(vec![user_text_message(message)?]) + } + + fn interrupt_turn(&mut self, turn_id: &str) -> Result { + if self.current_turn_id.as_deref() != Some(turn_id) { + return Err(LocalRunnerError::invalid( + "AgentCore interrupt does not match active turn", + )); + } + self.worker.stop_runtime(format!( + "paperclip-interrupt-{}", + sha_hex(&(self.session_id.clone() + turn_id), 32) + ))?; + let interrupted_invocation_id = self.active_invocation_id.clone(); + self.reconcile_interrupted_usage(); + self.settle_interrupted_turn(turn_id, interrupted_invocation_id.as_deref()); + Ok(json!({ "runtimeSessionId": self.session_id, "stopped": true, "terminalQueued": true })) + } + + fn read(&mut self) -> Result { + Ok(json!({ + "runtimeSessionId": self.session_id, + "actorId": self.actor_id, + "harnessArn": self.config.harness_arn, + "harnessVersion": self.config.harness_version, + "endpointArn": self.config.endpoint_arn, + "usage": self.usage, + })) + } + + fn poll(&mut self) -> Result, LocalRunnerError> { + if let Some(event) = self.queue.pop_front() { + return Ok(Some(event)); + } + let Some(event) = self.worker.try_event() else { + return Ok(None); + }; + let NetworkEvent { + invocation_id, + kind, + } = event; + if self.active_invocation_id.as_deref() != Some(invocation_id.as_str()) { + // Once the interrupted terminal is queued, no record from its + // truncated EventStream may escape after the durable terminal or + // mutate accounting. A timed-out usage snapshot stays fail closed + // for the lifetime of this remote session. + return Ok(None); + } + match kind { + NetworkEventKind::TextDelta(delta) => { + self.current_text.push_str(&delta); + Ok(Some(ProviderEvent::Notification { + method: "item/delta".to_owned(), + params: json!({ "turnId": self.current_turn_id, "itemId": format!("aws-message-{}", self.invocation_counter), "delta": delta, "authoritative": true }), + })) + } + NetworkEventKind::ReasoningProgress => Ok(Some(ProviderEvent::Notification { + method: "item/started".to_owned(), + params: json!({ "turnId": self.current_turn_id, "item": { "id": format!("aws-progress-{}", self.invocation_counter), "type": "progress", "phase": "thinking" } }), + })), + NetworkEventKind::ToolUse { + call_id, + remote_name, + input, + } => { + let operation_id = self + .remote_to_canonical + .get(&remote_name) + .ok_or_else(|| { + LocalRunnerError::invalid( + "AgentCore requested an unauthorized inline function", + ) + })? + .clone(); + let schema = self.input_schemas.get(&operation_id).ok_or_else(|| { + LocalRunnerError::invalid("AgentCore tool has no durable input schema") + })?; + if !validator_for(schema) + .map_err(|_| { + LocalRunnerError::invalid( + "authorized Paperclip tool has invalid JSON Schema", + ) + })? + .is_valid(&input) + { + return Err(LocalRunnerError::invalid( + "AgentCore inline-function arguments failed schema validation", + )); + } + if let Some(previous) = self.pending.get(&call_id) { + if previous.operation_id != operation_id || previous.input != input { + return Err(LocalRunnerError::invalid( + "AgentCore reused a tool-use ID with conflicting content", + )); + } + return Ok(None); + } + self.pending.insert( + call_id.clone(), + RemoteToolUse { + remote_name, + operation_id: operation_id.clone(), + input: input.clone(), + }, + ); + Ok(Some(ProviderEvent::ToolCall { + call_id, + operation_id, + input, + })) + } + NetworkEventKind::Usage { + input_tokens, + output_tokens, + cache_read_input_tokens, + cache_write_input_tokens, + latency_ms, + } => { + self.invocation_usage_observed = true; + Ok(Some(self.record_usage( + input_tokens, + output_tokens, + cache_read_input_tokens, + cache_write_input_tokens, + latency_ms, + true, + ))) + } + NetworkEventKind::Stop(reason) => { + if self.pending_stop_reason.replace(reason).is_some() { + return Err(LocalRunnerError::invalid( + "AgentCore emitted more than one stop reason for an invocation", + )); + } + Ok(None) + } + NetworkEventKind::InvocationComplete => { + self.active_invocation_id = None; + let reason = self.pending_stop_reason.take().ok_or_else(|| { + LocalRunnerError::invalid( + "AgentCore invocation ended without an authoritative stop reason", + ) + })?; + if !self.invocation_usage_observed { + return Err(LocalRunnerError::invalid( + "AgentCore invocation ended before usage metadata was observed", + )); + } + if self.invocation_budget_reached { + self.current_turn_id.take(); + return Ok(None); + } + match reason.as_str() { + "tool_use" | "tool_result" | "partial_turn" => { + Ok(Some(ProviderEvent::Notification { + method: "provider/waitingForToolResult".to_owned(), + params: json!({ "turnId": self.current_turn_id, "runtimeSessionId": self.session_id }), + })) + } + "end_turn" | "stop_sequence" | "interrupted" => { + let turn_id = self.current_turn_id.take(); + let status = if reason == "interrupted" { + "interrupted" + } else { + "completed" + }; + if !self.current_text.is_empty() { + self.queue.push_back(ProviderEvent::Notification { + method: "item/completed".to_owned(), + params: json!({ "turnId": turn_id, "item": { "id": format!("aws-message-{}", self.invocation_counter), "type": "agentMessage", "text": self.current_text, "authoritative": true } }), + }); + } + self.queue.push_back(ProviderEvent::Notification { + method: "turn/completed".to_owned(), + params: json!({ "turnId": turn_id, "turn": { "id": turn_id, "status": status }, "stopReason": reason }), + }); + Ok(self.queue.pop_front()) + } + "max_iterations_exceeded" + | "max_output_tokens_exceeded" + | "max_tokens" + | "timeout_exceeded" + | "model_context_window_exceeded" => Ok(Some(ProviderEvent::Notification { + method: "provider/budgetReached".to_owned(), + params: json!({ "turnId": self.current_turn_id, "status": "limit_reached", "stopReason": reason, "costSource": "paperclip_estimate" }), + })), + "content_filtered" => Err(LocalRunnerError::invalid( + "AgentCore model output was filtered", + )), + "malformed_model_output" | "malformed_tool_use" => Err( + LocalRunnerError::invalid("AgentCore returned malformed model output"), + ), + _ => Err(LocalRunnerError::invalid( + "AgentCore returned an unknown stop reason", + )), + } + } + NetworkEventKind::MemoryCursor(event_id) => { + self.durable_cursor = Some(event_id); + Ok(None) + } + NetworkEventKind::Failure(detail) => { + self.active_invocation_id = None; + Err(LocalRunnerError::invalid(format!( + "AgentCore transport failed: {detail}" + ))) + } + } + } + + fn deliver_tool_result(&mut self, result: &ToolResult) -> Result<(), LocalRunnerError> { + let pending = self + .pending + .get(&result.call_id) + .ok_or_else(|| { + LocalRunnerError::invalid("AgentCore tool result does not match a pending tool use") + })? + .clone(); + if pending.operation_id != result.operation_id { + return Err(LocalRunnerError::invalid( + "AgentCore tool result operation does not match pending tool use", + )); + } + if let Some(previous) = self.delivered_results.get(&result.call_id) { + if previous != result { + return Err(LocalRunnerError::invalid( + "AgentCore received conflicting results for one tool-use ID", + )); + } + return Ok(()); + } + self.delivered_results + .insert(result.call_id.clone(), result.clone()); + if self.delivered_results.len() < self.pending.len() { + return Ok(()); + } + + // AgentCore requires every toolUse from the assistant message and all + // matching user toolResults in one continuation. This also guarantees + // that parallel governed mutations cannot advance separate model turns. + let mut assistant_builder = + HarnessMessage::builder().role(HarnessConversationRole::Assistant); + let mut user_builder = HarnessMessage::builder().role(HarnessConversationRole::User); + for (call_id, pending) in &self.pending { + let delivered = self.delivered_results.get(call_id).ok_or_else(|| { + LocalRunnerError::invalid("AgentCore tool-result batch is incomplete") + })?; + let tool_use = HarnessToolUseBlock::builder() + .name(pending.remote_name.clone()) + .tool_use_id(call_id.clone()) + .input(json_to_document(&pending.input)?) + .r#type(HarnessToolUseType::ToolUse) + .build() + .map_err(|_| { + LocalRunnerError::invalid("failed to build AgentCore tool-use continuation") + })?; + let tool_result = HarnessToolResultBlock::builder() + .tool_use_id(call_id.clone()) + // Although the AgentCore data-plane model advertises JSON + // result blocks, the managed Harness runtime currently + // rejects them with `content_type= | unsupported type`. + // Preserve the complete structured result as compact JSON in + // the supported text variant. + .content(encode_tool_result_content(&delivered.result)?) + .status(if delivered.is_error { + HarnessToolUseStatus::Error + } else { + HarnessToolUseStatus::Success + }) + .r#type(HarnessToolUseType::ToolUse) + .build() + .map_err(|_| { + LocalRunnerError::invalid("failed to build AgentCore tool-result continuation") + })?; + assistant_builder = assistant_builder.content(HarnessContentBlock::ToolUse(tool_use)); + user_builder = user_builder.content(HarnessContentBlock::ToolResult(tool_result)); + } + let assistant = assistant_builder.build().map_err(|_| { + LocalRunnerError::invalid("failed to build AgentCore assistant continuation") + })?; + let user = user_builder.build().map_err(|_| { + LocalRunnerError::invalid("failed to build AgentCore user continuation") + })?; + // The durable runner records ToolResult before calling this method. A + // transport ambiguity therefore never repeats the Paperclip mutation; + // it fails closed with the last authoritative Memory cursor preserved + // for the control plane's reconciliation workflow. + self.invoke(vec![assistant, user])?; + self.pending.clear(); + self.delivered_results.clear(); + Ok(()) + } + + fn shutdown(&mut self) -> Result<(), LocalRunnerError> { + self.worker.stop_runtime(format!( + "paperclip-suspend-{}", + sha_hex(&self.session_id, 32) + ))?; + self.worker.shutdown(); + Ok(()) + } +} + +fn user_text_message(message: &str) -> Result { + HarnessMessage::builder() + .role(HarnessConversationRole::User) + .content(HarnessContentBlock::Text(message.to_owned())) + .build() + .map_err(|_| LocalRunnerError::invalid("failed to build AgentCore user message")) +} + +fn validate_config(config: &AwsAgentCoreProviderConfig) -> Result<(), LocalRunnerError> { + if [ + config.model.as_str(), + config.profile_id.as_str(), + config.region.as_str(), + config.account_id.as_str(), + config.harness_arn.as_str(), + config.harness_version.as_str(), + config.endpoint_arn.as_str(), + config.endpoint_qualifier.as_str(), + config.agent_runtime_arn.as_str(), + config.memory_arn.as_str(), + config.memory_id.as_str(), + config.invocation_role_arn.as_str(), + config.context_bucket.as_str(), + config.context_prefix.as_str(), + config.context_kms_key_arn.as_str(), + config.qualification_revision.as_str(), + config.instructions.as_str(), + ] + .iter() + .any(|value| value.trim().is_empty()) + { + return Err(LocalRunnerError::invalid( + "AgentCore profile fields must be non-empty", + )); + } + if config.context_prefix.starts_with('/') + || config + .context_prefix + .split('/') + .any(|part| part.is_empty() || part == "." || part == "..") + || !config.context_kms_key_arn.starts_with("arn:aws:kms:") + { + return Err(LocalRunnerError::invalid( + "AgentCore context S3 qualification is unsafe", + )); + } + if config.event_expiry_days != 90 { + return Err(LocalRunnerError::invalid( + "AgentCore profile must use the qualified 90-day Memory expiry", + )); + } + if config.max_iterations == 0 + || config.max_iterations > 8 + || config.max_output_tokens == 0 + || config.max_output_tokens > 4096 + || config.timeout_seconds == 0 + || config.timeout_seconds > 300 + { + return Err(LocalRunnerError::invalid( + "AgentCore invocation limits exceed the qualified profile", + )); + } + if !config.max_estimated_session_cost_usd.is_finite() + || config.max_estimated_session_cost_usd <= 0.0 + { + return Err(LocalRunnerError::invalid( + "AgentCore estimated spend ceiling must be positive", + )); + } + Ok(()) +} + +fn encode_tools( + tools: &[AuthorizedTool], +) -> Result< + ( + Vec, + Vec, + BTreeMap, + BTreeMap, + ), + LocalRunnerError, +> { + if tools.len() > MAX_AGENTCORE_TOOLS { + return Err(LocalRunnerError::invalid( + "AgentCore supports at most 64 inline functions", + )); + } + let mut encoded = Vec::with_capacity(tools.len()); + let mut allowed = Vec::with_capacity(tools.len()); + let mut reverse = BTreeMap::new(); + let mut schemas = BTreeMap::new(); + for tool in tools { + validator_for(&tool.input_schema).map_err(|_| { + LocalRunnerError::invalid("authorized Paperclip tool has invalid JSON Schema") + })?; + let remote_name = remote_tool_name(&tool.operation_id); + if reverse + .insert(remote_name.clone(), tool.operation_id.clone()) + .is_some() + { + return Err(LocalRunnerError::invalid( + "AgentCore inline-function name collision", + )); + } + schemas.insert(tool.operation_id.clone(), tool.input_schema.clone()); + let inline = HarnessInlineFunctionConfig::builder() + .description(tool.description.clone()) + .input_schema(json_to_document(&tool.input_schema)?) + .build() + .map_err(|_| LocalRunnerError::invalid("failed to build AgentCore inline function"))?; + let harness_tool = HarnessTool::builder() + .r#type(HarnessToolType::InlineFunction) + .name(remote_name.clone()) + .config(HarnessToolConfiguration::InlineFunction(inline)) + .build() + .map_err(|_| LocalRunnerError::invalid("failed to build AgentCore tool"))?; + allowed.push(format!("@*/{remote_name}")); + encoded.push(harness_tool); + } + Ok((encoded, allowed, reverse, schemas)) +} + +fn remote_tool_name(operation_id: &str) -> String { + let mut slug = operation_id + .chars() + .map(|ch| { + if ch.is_ascii_alphanumeric() { + ch.to_ascii_lowercase() + } else { + '_' + } + }) + .collect::(); + while slug.contains("__") { + slug = slug.replace("__", "_"); + } + slug = slug.trim_matches('_').to_owned(); + if slug.is_empty() { + slug = "paperclip_tool".to_owned(); + } + slug.truncate(48); + format!("pc_{}_{}", slug, sha_hex(operation_id, 12)) +} + +fn new_runtime_session_id() -> String { + format!("paperclip-{}-{}", Uuid::new_v4(), Uuid::new_v4()) +} + +fn estimate_model_token_cost_usd( + model: &str, + input_tokens: i64, + output_tokens: i64, + cache_read_input_tokens: i64, + cache_write_input_tokens: i64, +) -> Option { + if model != "global.anthropic.claude-sonnet-4-6" { + return None; + } + // Qualified 2026-08-21 Bedrock global standard-tier rates per million + // tokens. Runtime, Memory, CloudWatch, network, and tax are deliberately + // excluded and the receipt names that scope explicitly. + Some( + input_tokens.max(0) as f64 * 3.00 / 1_000_000.0 + + output_tokens.max(0) as f64 * 15.00 / 1_000_000.0 + + cache_read_input_tokens.max(0) as f64 * 0.30 / 1_000_000.0 + + cache_write_input_tokens.max(0) as f64 * 3.75 / 1_000_000.0, + ) +} + +fn sha_hex(value: &str, length: usize) -> String { + let digest = format!("{:x}", Sha256::digest(value.as_bytes())); + digest[..length.min(digest.len())].to_owned() +} + +fn redact_aws_error(value: &str) -> String { + // Smithy errors can embed a serialized request and signed headers. Retain + // only a closed, useful classification; never attempt field-by-field + // scrubbing of an open-ended provider error object. + let lower = value.to_ascii_lowercase(); + if lower.contains("accessdenied") + || lower.contains("access denied") + || lower.contains("unauthorized") + { + "AWS AgentCore access denied".to_owned() + } else if lower.contains("resourcenotfound") || lower.contains("not found") { + "AWS AgentCore resource not found".to_owned() + } else if lower.contains("throttl") || lower.contains("too many requests") { + "AWS AgentCore request throttled".to_owned() + } else if lower.contains("conflict") { + "AWS AgentCore resource conflict".to_owned() + } else if lower.contains("timeout") || lower.contains("timed out") { + "AWS AgentCore request timed out".to_owned() + } else if lower.contains("validation") { + "AWS AgentCore request validation failed".to_owned() + } else { + "AWS AgentCore request failed".to_owned() + } +} + +fn classify_aws_error_code(code: &str) -> String { + match code { + "AccessDeniedException" | "UnauthorizedException" => { + "AWS AgentCore access denied".to_owned() + } + "ResourceNotFoundException" => "AWS AgentCore resource not found".to_owned(), + "ThrottlingException" | "TooManyRequestsException" => { + "AWS AgentCore request throttled".to_owned() + } + "ConflictException" => "AWS AgentCore resource conflict".to_owned(), + "RequestTimeoutException" | "TimeoutException" => { + "AWS AgentCore request timed out".to_owned() + } + "ValidationException" => "AWS AgentCore request validation failed".to_owned(), + "ServiceUnavailableException" | "InternalServerException" => { + "AWS AgentCore service unavailable".to_owned() + } + _ => "AWS AgentCore request failed".to_owned(), + } +} + +fn classify_aws_sdk_error(error: &SdkError) -> String +where + E: ProvideErrorMetadata, +{ + if let Some(code) = error + .as_service_error() + .and_then(ProvideErrorMetadata::code) + { + return classify_aws_error_code(code); + } + match error { + SdkError::ConstructionFailure(_) => "AWS AgentCore request construction failed".to_owned(), + SdkError::TimeoutError(_) => "AWS AgentCore request timed out".to_owned(), + SdkError::DispatchFailure(context) if context.is_timeout() => { + "AWS AgentCore request dispatch timed out".to_owned() + } + SdkError::DispatchFailure(context) if context.is_io() => { + "AWS AgentCore request network I/O failed".to_owned() + } + SdkError::DispatchFailure(context) if context.is_user() => { + "AWS AgentCore request was rejected before dispatch".to_owned() + } + SdkError::DispatchFailure(_) => { + "AWS AgentCore credential or transport setup failed".to_owned() + } + SdkError::ResponseError(_) => "AWS AgentCore response decoding failed".to_owned(), + SdkError::ServiceError(_) => redact_aws_error(&error.to_string()), + _ => "AWS AgentCore request failed".to_owned(), + } +} + +fn is_resource_not_found(value: &str) -> bool { + let lower = value.to_ascii_lowercase(); + lower.contains("resourcenotfound") + || lower.contains("not found") + || lower.contains("status code: 404") +} + +fn json_to_document(value: &Value) -> Result { + Ok(match value { + Value::Null => Document::Null, + Value::Bool(value) => Document::Bool(*value), + Value::String(value) => Document::String(value.clone()), + Value::Array(values) => Document::Array( + values + .iter() + .map(json_to_document) + .collect::, _>>()?, + ), + Value::Object(values) => Document::Object( + values + .iter() + .map(|(key, value)| Ok((key.clone(), json_to_document(value)?))) + .collect::, LocalRunnerError>>()?, + ), + Value::Number(value) => { + if let Some(value) = value.as_u64() { + Document::Number(Number::PosInt(value)) + } else if let Some(value) = value.as_i64() { + Document::Number(Number::NegInt(value)) + } else if let Some(value) = value.as_f64() { + Document::Number(Number::Float(value)) + } else { + return Err(LocalRunnerError::invalid( + "JSON number cannot be represented in AgentCore", + )); + } + } + }) +} + +fn encode_tool_result_content( + value: &Value, +) -> Result { + serde_json::to_string(value) + .map(HarnessToolResultContentBlock::Text) + .map_err(|_| LocalRunnerError::invalid("failed to serialize AgentCore tool result")) +} + +#[cfg(test)] +mod tests { + use super::*; + use aws_sdk_bedrockagentcore::types::{ + HarnessContentBlockDeltaEvent, HarnessContentBlockStartEvent, HarnessContentBlockStopEvent, + HarnessToolUseBlockDelta, HarnessToolUseBlockStart, + }; + use std::sync::{Arc, Mutex}; + + fn config() -> AwsAgentCoreProviderConfig { + AwsAgentCoreProviderConfig { + model: "global.anthropic.claude-sonnet-4-6".to_owned(), + profile_id: "profile-test".to_owned(), + region: "us-east-1".to_owned(), + account_id: "123456789012".to_owned(), + harness_arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/test".to_owned(), + harness_version: "1".to_owned(), + endpoint_arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/test" + .to_owned(), + endpoint_qualifier: "1".to_owned(), + agent_runtime_arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/test" + .to_owned(), + memory_arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/test".to_owned(), + memory_id: "memory-test".to_owned(), + invocation_role_arn: "arn:aws:iam::123456789012:role/paperclip-agentcore".to_owned(), + context_bucket: "paperclip-context-test".to_owned(), + context_prefix: "companies/company-test/profiles/profile-test".to_owned(), + context_kms_key_arn: "arn:aws:kms:us-east-1:123456789012:key/test".to_owned(), + qualification_revision: "aws-agentcore-harness-v1".to_owned(), + event_expiry_days: 90, + max_estimated_session_cost_usd: 1.0, + max_iterations: 8, + max_output_tokens: 4096, + timeout_seconds: 300, + instructions: "Paperclip test instructions".to_owned(), + runtime_context: None, + } + } + + fn invocation_event(kind: NetworkEventKind) -> NetworkEvent { + NetworkEvent::new("invocation-1", kind) + } + + #[test] + fn stop_runtime_targets_the_runtime_arn_and_endpoint_qualifier() { + let config = config(); + let (runtime_arn, qualifier) = stop_runtime_target(&config); + assert_eq!(runtime_arn, config.agent_runtime_arn); + assert_ne!(runtime_arn, config.harness_arn); + assert_eq!(qualifier, config.endpoint_qualifier); + } + + fn provider_with_events( + events: Vec, + usage: Value, + ) -> AwsAgentCoreHarnessProvider { + let (commands, command_rx) = mpsc::sync_channel(1); + drop(command_rx); + let (event_tx, event_rx) = mpsc::sync_channel(events.len().max(1)); + for event in events { + event_tx.send(event).unwrap(); + } + drop(event_tx); + provider_with_worker( + NetworkWorker { + commands, + events: event_rx, + join: None, + }, + usage, + ) + } + + fn provider_with_interrupt_stream( + interrupt_events: Vec, + events_before_stop_reply: bool, + usage: Value, + ) -> AwsAgentCoreHarnessProvider { + provider_with_delayed_interrupt_stream( + interrupt_events, + events_before_stop_reply, + Duration::from_millis(10), + usage, + ) + } + + fn provider_with_delayed_interrupt_stream( + interrupt_events: Vec, + events_before_stop_reply: bool, + post_stop_delay: Duration, + usage: Value, + ) -> AwsAgentCoreHarnessProvider { + let (commands, command_rx) = mpsc::sync_channel(4); + let (event_tx, event_rx) = mpsc::sync_channel(interrupt_events.len().max(1)); + let join = thread::spawn(move || { + while let Ok(command) = command_rx.recv() { + match command { + NetworkCommand::StopRuntime { reply, .. } => { + if events_before_stop_reply { + for event in &interrupt_events { + event_tx.send(event.clone()).unwrap(); + } + } + reply.send(Ok(())).unwrap(); + if !events_before_stop_reply { + thread::sleep(post_stop_delay); + for event in &interrupt_events { + event_tx.send(event.clone()).unwrap(); + } + } + } + NetworkCommand::Shutdown => break, + NetworkCommand::Invoke { reply, .. } => { + reply + .send(Err("unexpected test invocation".to_owned())) + .unwrap(); + } + NetworkCommand::DeleteMemory { reply } => { + reply + .send(Err("unexpected test deletion".to_owned())) + .unwrap(); + } + } + } + }); + provider_with_worker( + NetworkWorker { + commands, + events: event_rx, + join: Some(join), + }, + usage, + ) + } + + fn provider_with_worker(worker: NetworkWorker, usage: Value) -> AwsAgentCoreHarnessProvider { + AwsAgentCoreHarnessProvider { + config: config(), + session_id: "paperclip-test-session".to_owned(), + actor_id: "paperclip-test-actor".to_owned(), + worker, + tools: Vec::new(), + allowed_tools: Vec::new(), + remote_to_canonical: BTreeMap::new(), + input_schemas: BTreeMap::new(), + pending: BTreeMap::new(), + delivered_results: BTreeMap::new(), + queue: VecDeque::new(), + current_turn_id: Some("turn-1".to_owned()), + current_text: String::new(), + invocation_counter: 1, + active_invocation_id: Some("invocation-1".to_owned()), + durable_cursor: None, + usage, + pending_stop_reason: None, + invocation_usage_observed: false, + invocation_budget_reached: false, + max_estimated_cost_usd: 1.0, + } + } + + #[test] + fn runtime_session_ids_are_valid_and_unique() { + let first = new_runtime_session_id(); + let second = new_runtime_session_id(); + assert!(first.len() >= 33); + assert_ne!(first, second); + } + + #[test] + fn durable_usage_restore_preserves_accumulated_spend_and_tokens() { + let usage = json!({ + "inputTokens": 123, + "outputTokens": 45, + "cacheReadInputTokens": 67, + "cacheWriteInputTokens": 8, + "requestCount": 4, + "estimatedCostUsd": 0.73, + "costSource": "paperclip_estimate", + "estimateScope": "bedrock_model_tokens_only" + }); + assert_eq!(restored_usage_snapshot(Some(&usage)).unwrap(), usage); + let mut conservative = usage.clone(); + conservative[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE); + conservative[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD] = json!(0.73); + assert_eq!( + restored_usage_snapshot(Some(&conservative)).unwrap(), + conservative + ); + } + + #[test] + fn durable_usage_restore_rejects_missing_or_untrusted_spend_state() { + assert!(restored_usage_snapshot(Some(&json!({ + "inputTokens": 123, + "outputTokens": 45, + "cacheReadInputTokens": 67, + "cacheWriteInputTokens": 8, + "requestCount": 4, + "estimatedCostUsd": -1.0, + "costSource": "paperclip_estimate" + }))) + .is_err()); + assert!(restored_usage_snapshot(Some(&json!({ + "inputTokens": 123, + "outputTokens": 45, + "cacheReadInputTokens": 67, + "cacheWriteInputTokens": 8, + "requestCount": 4, + "estimatedCostUsd": 0.73, + "costSource": "paperclip_estimate", + "pendingEstimatedCeilingUsd": 1.0 + }))) + .is_err()); + assert!(restored_usage_snapshot(Some(&json!({ + "inputTokens": 123, + "outputTokens": 45, + "cacheReadInputTokens": 67, + "cacheWriteInputTokens": 8, + "requestCount": 4, + "estimatedCostUsd": 0.73, + "costSource": "paperclip_estimate", + "usageReconciliation": AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE, + "conservativeCostFloorUsd": 0.74 + }))) + .is_err()); + assert!(restored_usage_snapshot(Some(&json!({ + "inputTokens": 123, + "outputTokens": 45, + "cacheReadInputTokens": 67, + "cacheWriteInputTokens": 8, + "requestCount": 4, + "estimatedCostUsd": 0.73, + "costSource": "provider_claim" + }))) + .is_err()); + assert!(restored_usage_snapshot(Some(&json!({ + "inputTokens": 123, + "outputTokens": 45, + "cacheReadInputTokens": 67, + "cacheWriteInputTokens": 8, + "requestCount": 4, + "estimatedCostUsd": 0.73, + "costSource": "paperclip_estimate", + "usageReconciliation": "unexpected_state", + "pendingInvocationId": "invocation-1" + }))) + .is_err()); + } + + #[test] + fn stop_waits_for_usage_metadata_before_emitting_turn_terminal() { + let mut provider = provider_with_events( + vec![ + invocation_event(NetworkEventKind::Stop("end_turn".to_owned())), + invocation_event(NetworkEventKind::Usage { + input_tokens: 3_000, + output_tokens: 0, + cache_read_input_tokens: 4_000, + cache_write_input_tokens: 1_000, + latency_ms: 12, + }), + invocation_event(NetworkEventKind::InvocationComplete), + ], + restored_usage_snapshot(None).unwrap(), + ); + assert!(provider.poll().unwrap().is_none()); + let usage = provider.poll().unwrap().unwrap(); + assert!(matches!( + usage, + ProviderEvent::Notification { ref method, .. } + if method == "thread/tokenUsage/updated" + )); + assert_eq!(provider.usage["requestCount"], 1); + assert!( + (provider.usage["estimatedCostUsd"].as_f64().unwrap() - 0.01395).abs() < 0.000_000_001 + ); + let terminal = provider.poll().unwrap().unwrap(); + assert!(matches!( + terminal, + ProviderEvent::Notification { ref method, .. } if method == "turn/completed" + )); + } + + #[test] + fn mid_stream_interrupt_waits_for_late_usage_and_suppresses_truncated_completion() { + let late_usage = NetworkEventKind::Usage { + input_tokens: 2_000, + output_tokens: 100, + cache_read_input_tokens: 500, + cache_write_input_tokens: 0, + latency_ms: 25, + }; + let mut provider = provider_with_interrupt_stream( + vec![ + invocation_event(NetworkEventKind::TextDelta( + "must not escape after stop".to_owned(), + )), + invocation_event(late_usage.clone()), + invocation_event(late_usage), + invocation_event(NetworkEventKind::Stop("end_turn".to_owned())), + invocation_event(NetworkEventKind::Failure( + "expected truncated event stream".to_owned(), + )), + invocation_event(NetworkEventKind::InvocationComplete), + ], + false, + restored_usage_snapshot(None).unwrap(), + ); + + let response = provider.interrupt_turn("turn-1").unwrap(); + assert_eq!(response["terminalQueued"], true); + assert!(provider.current_turn_id.is_none()); + + let snapshot = provider.poll().unwrap().unwrap(); + match snapshot { + ProviderEvent::Notification { method, params } => { + assert_eq!(method, "thread/tokenUsage/updated"); + assert_eq!(params["requestCount"], 1); + assert_eq!(params["inputTokens"], 2_000); + assert_eq!( + params["usageReconciliation"], + "authoritative_metadata_observed" + ); + } + other => panic!("unexpected interrupted usage snapshot: {other:?}"), + } + let terminal = provider.poll().unwrap().unwrap(); + match terminal { + ProviderEvent::Notification { method, params } => { + assert_eq!(method, "turn/completed"); + assert_eq!(params.pointer("/turn/status"), Some(&json!("interrupted"))); + assert_eq!(params["stopReason"], "interrupted"); + } + other => panic!("unexpected interrupted terminal: {other:?}"), + } + + thread::sleep(Duration::from_millis(20)); + let mut late_usage_updates = 0; + let mut extra_terminals = 0; + for _ in 0..10 { + match provider.poll().unwrap() { + Some(ProviderEvent::Notification { method, .. }) + if method == "thread/tokenUsage/updated" => + { + late_usage_updates += 1; + } + Some(ProviderEvent::Notification { method, .. }) if method == "turn/completed" => { + extra_terminals += 1; + } + Some(_) | None => {} + } + } + assert_eq!(late_usage_updates, 0); + assert_eq!(extra_terminals, 0); + assert_eq!(provider.usage["requestCount"], 1); + assert_eq!(provider.usage["inputTokens"], 2_000); + assert!(provider.current_text.is_empty()); + } + + #[test] + fn interrupt_usage_timeout_is_durably_charged_before_next_turn_admission() { + let mut provider = provider_with_interrupt_stream( + Vec::new(), + false, + restored_usage_snapshot(None).unwrap(), + ); + + provider.interrupt_turn("turn-1").unwrap(); + let snapshot = provider.usage_snapshot().unwrap(); + assert_eq!( + snapshot[AGENTCORE_USAGE_RECONCILIATION_FIELD], + AGENTCORE_USAGE_RECONCILIATION_PENDING + ); + assert_eq!(snapshot[AGENTCORE_PENDING_INVOCATION_FIELD], "invocation-1"); + assert_eq!(snapshot[AGENTCORE_PENDING_CEILING_FIELD], 1.0); + assert_eq!(restored_usage_snapshot(Some(&snapshot)).unwrap(), snapshot); + + match provider.poll().unwrap().unwrap() { + ProviderEvent::Notification { method, params } => { + assert_eq!(method, "thread/tokenUsage/updated"); + assert_eq!(params, snapshot); + } + other => panic!("unexpected pending usage snapshot: {other:?}"), + } + assert!(matches!( + provider.poll().unwrap().unwrap(), + ProviderEvent::Notification { ref method, .. } if method == "turn/completed" + )); + + let error = provider.preflight_turn().unwrap_err(); + assert!(error + .to_string() + .contains("estimated session spend ceiling reached")); + assert!(provider.active_invocation_id.is_none()); + assert!(provider.current_turn_id.is_none()); + assert_eq!(provider.usage["requestCount"], 1); + assert_eq!(provider.usage["estimatedCostUsd"], 1.0); + assert_eq!(provider.usage[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD], 1.0); + assert_eq!( + provider.usage[AGENTCORE_USAGE_RECONCILIATION_FIELD], + AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE + ); + assert!(provider + .usage + .get(AGENTCORE_PENDING_INVOCATION_FIELD) + .is_none()); + assert!(provider.poll().unwrap().is_none()); + let settled = provider.usage_snapshot().unwrap(); + assert_eq!(restored_usage_snapshot(Some(&settled)).unwrap(), settled); + + assert!(provider.preflight_turn().is_err()); + assert_eq!(provider.usage["requestCount"], 1); + + provider.increase_budget(2.0).unwrap(); + provider.preflight_turn().unwrap(); + assert_eq!(provider.usage["estimatedCostUsd"], 1.0); + provider.record_usage(10, 5, 0, 0, 3, true); + assert_eq!(provider.usage["requestCount"], 2); + assert_eq!(provider.usage["estimatedCostUsd"], 1.0); + assert_eq!(provider.usage[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD], 1.0); + + let mut recovered = provider_with_events(Vec::new(), snapshot); + recovered.active_invocation_id = None; + recovered.current_turn_id = None; + let error = recovered.preflight_turn().unwrap_err(); + assert!(error + .to_string() + .contains("estimated session spend ceiling reached")); + assert_eq!(recovered.usage["requestCount"], 1); + assert_eq!(recovered.usage["estimatedCostUsd"], 1.0); + recovered.increase_budget(2.0).unwrap(); + recovered.preflight_turn().unwrap(); + } + + #[test] + fn ambiguous_invoke_delivery_retains_identity_for_interrupt_reconciliation() { + let mut provider = provider_with_interrupt_stream( + Vec::new(), + false, + restored_usage_snapshot(None).unwrap(), + ); + provider.current_turn_id = None; + provider.active_invocation_id = None; + + let error = provider + .start_turn("ambiguous delivery", "", "turn-ambiguous") + .unwrap_err(); + assert!(error.to_string().contains("unexpected test invocation")); + let ambiguous_invocation_id = provider + .active_invocation_id + .clone() + .expect("ambiguous invocation identity is retained"); + assert_eq!( + provider.durable_cursor.as_deref(), + Some(ambiguous_invocation_id.as_str()) + ); + + provider.interrupt_turn("turn-ambiguous").unwrap(); + let snapshot = provider.usage_snapshot().unwrap(); + assert_eq!( + snapshot[AGENTCORE_USAGE_RECONCILIATION_FIELD], + AGENTCORE_USAGE_RECONCILIATION_PENDING + ); + assert_eq!( + snapshot[AGENTCORE_PENDING_INVOCATION_FIELD], + ambiguous_invocation_id + ); + + let restored = restored_usage_snapshot(Some(&snapshot)).unwrap(); + let mut recovered = provider_with_events(Vec::new(), restored); + recovered.active_invocation_id = None; + recovered.current_turn_id = None; + let error = recovered.preflight_turn().unwrap_err(); + assert!(error + .to_string() + .contains("estimated session spend ceiling reached")); + assert_eq!( + recovered.usage[AGENTCORE_USAGE_RECONCILIATION_FIELD], + AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE + ); + assert_eq!(recovered.usage["requestCount"], 1); + } + + #[test] + fn late_usage_is_suppressed_then_next_turn_boundary_charges_the_ceiling() { + let mut provider = provider_with_delayed_interrupt_stream( + vec![ + invocation_event(NetworkEventKind::Usage { + input_tokens: 900, + output_tokens: 90, + cache_read_input_tokens: 30, + cache_write_input_tokens: 10, + latency_ms: 14, + }), + invocation_event(NetworkEventKind::InvocationComplete), + ], + false, + Duration::from_millis(150), + restored_usage_snapshot(None).unwrap(), + ); + + provider.interrupt_turn("turn-1").unwrap(); + let pending_snapshot = provider.usage_snapshot().unwrap(); + assert_eq!( + pending_snapshot[AGENTCORE_USAGE_RECONCILIATION_FIELD], + AGENTCORE_USAGE_RECONCILIATION_PENDING + ); + assert!(matches!( + provider.poll().unwrap().unwrap(), + ProviderEvent::Notification { ref method, .. } + if method == "thread/tokenUsage/updated" + )); + assert!(matches!( + provider.poll().unwrap().unwrap(), + ProviderEvent::Notification { ref method, .. } if method == "turn/completed" + )); + + thread::sleep(Duration::from_millis(125)); + assert!(provider.poll().unwrap().is_none()); + assert!(provider.poll().unwrap().is_none()); + assert_eq!(provider.usage, pending_snapshot); + assert_eq!(provider.usage["requestCount"], 0); + assert_eq!(provider.usage["inputTokens"], 0); + + let error = provider.preflight_turn().unwrap_err(); + assert!(error + .to_string() + .contains("estimated session spend ceiling reached")); + assert_eq!(provider.usage["requestCount"], 1); + assert_eq!(provider.usage["estimatedCostUsd"], 1.0); + assert_eq!( + provider.usage[AGENTCORE_USAGE_RECONCILIATION_FIELD], + AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE + ); + assert!(provider.poll().unwrap().is_none()); + let restored = restored_usage_snapshot(Some(&pending_snapshot)).unwrap(); + let mut recovered = provider_with_events(Vec::new(), restored); + recovered.active_invocation_id = None; + recovered.current_turn_id = None; + assert!(recovered.preflight_turn().is_err()); + assert_eq!(recovered.usage["requestCount"], 1); + assert_eq!(recovered.usage["estimatedCostUsd"], 1.0); + } + + #[test] + fn interrupted_usage_metadata_is_reconciled_at_most_once() { + let usage = NetworkEventKind::Usage { + input_tokens: 400, + output_tokens: 50, + cache_read_input_tokens: 20, + cache_write_input_tokens: 10, + latency_ms: 8, + }; + let mut provider = provider_with_interrupt_stream( + vec![ + invocation_event(usage.clone()), + invocation_event(NetworkEventKind::InvocationComplete), + ], + false, + restored_usage_snapshot(None).unwrap(), + ); + provider.record_usage(400, 50, 20, 10, 8, true); + provider.invocation_usage_observed = true; + + provider.interrupt_turn("turn-1").unwrap(); + let snapshot = provider.poll().unwrap().unwrap(); + match snapshot { + ProviderEvent::Notification { method, params } => { + assert_eq!(method, "thread/tokenUsage/updated"); + assert_eq!(params["requestCount"], 1); + assert_eq!( + params["usageReconciliation"], + "authoritative_metadata_observed" + ); + } + other => panic!("unexpected interrupted usage snapshot: {other:?}"), + } + assert!(matches!( + provider.poll().unwrap().unwrap(), + ProviderEvent::Notification { ref method, .. } if method == "turn/completed" + )); + + thread::sleep(Duration::from_millis(20)); + let mut late_usage_updates = 0; + for _ in 0..4 { + match provider.poll().unwrap() { + Some(ProviderEvent::Notification { method, .. }) + if method == "thread/tokenUsage/updated" => + { + late_usage_updates += 1; + } + Some(_) | None => {} + } + } + assert_eq!(late_usage_updates, 0); + assert_eq!(provider.usage["requestCount"], 1); + assert_eq!(provider.usage["inputTokens"], 400); + } + + #[test] + fn interrupt_drains_queued_metadata_into_the_preterminal_snapshot() { + let mut provider = provider_with_interrupt_stream( + vec![ + invocation_event(NetworkEventKind::TextDelta( + "queued output is suppressed".to_owned(), + )), + invocation_event(NetworkEventKind::Usage { + input_tokens: 700, + output_tokens: 80, + cache_read_input_tokens: 30, + cache_write_input_tokens: 10, + latency_ms: 9, + }), + invocation_event(NetworkEventKind::Failure( + "expected truncated event stream".to_owned(), + )), + ], + true, + restored_usage_snapshot(None).unwrap(), + ); + + provider.interrupt_turn("turn-1").unwrap(); + match provider.poll().unwrap().unwrap() { + ProviderEvent::Notification { method, params } => { + assert_eq!(method, "thread/tokenUsage/updated"); + assert_eq!(params["requestCount"], 1); + assert_eq!(params["inputTokens"], 700); + assert_eq!( + params["usageReconciliation"], + "authoritative_metadata_observed" + ); + } + other => panic!("unexpected interrupted usage snapshot: {other:?}"), + } + match provider.poll().unwrap().unwrap() { + ProviderEvent::Notification { method, params } => { + assert_eq!(method, "turn/completed"); + assert_eq!(params.pointer("/turn/status"), Some(&json!("interrupted"))); + } + other => panic!("unexpected interrupted terminal: {other:?}"), + } + assert!(provider.poll().unwrap().is_none()); + assert!(provider.current_text.is_empty()); + } + + #[test] + fn restored_cumulative_spend_is_enforced_before_another_invocation() { + let usage = json!({ + "inputTokens": 100, + "outputTokens": 100, + "cacheReadInputTokens": 0, + "cacheWriteInputTokens": 0, + "requestCount": 2, + "estimatedCostUsd": 1.0, + "costSource": "paperclip_estimate" + }); + let mut provider = provider_with_events(Vec::new(), usage); + assert!(provider.invoke(Vec::new()).is_err()); + } + + #[test] + fn memory_purge_restarts_from_the_first_page_after_mutating_deletes() { + let remaining = Arc::new(Mutex::new( + (0..250) + .map(|index| format!("event-{index}")) + .collect::>(), + )); + let listed = Arc::new(Mutex::new(0_usize)); + let list_remaining = Arc::clone(&remaining); + let list_count = Arc::clone(&listed); + let delete_remaining = Arc::clone(&remaining); + tokio::runtime::Runtime::new() + .unwrap() + .block_on(purge_memory_event_ids( + move || { + let ids = list_remaining + .lock() + .unwrap() + .iter() + .take(100) + .cloned() + .collect::>(); + *list_count.lock().unwrap() += 1; + async move { Ok(ids) } + }, + move |event_id| { + let remaining = Arc::clone(&delete_remaining); + async move { + let mut values = remaining.lock().unwrap(); + let index = values.iter().position(|value| value == &event_id).unwrap(); + values.remove(index); + Ok(()) + } + }, + )) + .unwrap(); + assert!(remaining.lock().unwrap().is_empty()); + assert_eq!(*listed.lock().unwrap(), 4); + } + + #[test] + fn memory_history_scan_fails_closed_at_the_page_bound() { + let mut pages = 0; + let mut events = 0; + for _ in 0..MAX_MEMORY_HISTORY_PAGES { + observe_memory_history_page(&mut pages, &mut events, 1, "history scan").unwrap(); + } + assert!(observe_memory_history_page(&mut pages, &mut events, 1, "history scan").is_err()); + } + + #[test] + fn agentcore_system_replaces_the_local_instruction_path_with_the_harness_skill() { + let mut config = config(); + let local_root = "/paperclip/runtime/instructions"; + config.instructions = format!( + "paperclip prompt\n\nAGENTS entry\n\nRead-only instruction sibling root: {local_root}" + ); + config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { "digest": "abc123", "rootPath": local_root } + }, + "skills": [] + })); + + let instructions = agentcore_system_instructions(&config).unwrap(); + assert!(instructions.starts_with("paperclip prompt\n\nAGENTS entry\n\n")); + assert!(instructions.ends_with("attached Paperclip HarnessSkill under `instructions/`.")); + assert!(!instructions.contains(local_root)); + } + + #[test] + fn agentcore_upload_plan_contains_instruction_siblings_and_complete_assigned_skill_trees() { + let root = std::env::temp_dir().join(format!( + "paperclip-agentcore-context-{}", + uuid::Uuid::new_v4() + )); + let instruction_root = root.join("instructions"); + let skill_root = root.join("reviewer"); + fs::create_dir_all(instruction_root.join("references")).unwrap(); + fs::create_dir_all(skill_root.join("references")).unwrap(); + fs::write(instruction_root.join("AGENTS.md"), "Follow the entry.\n").unwrap(); + fs::write( + instruction_root.join("references/policy.md"), + "Instruction sibling.\n", + ) + .unwrap(); + fs::write(skill_root.join("SKILL.md"), "# Reviewer\n").unwrap(); + fs::write( + skill_root.join("references/checklist.md"), + "- Verify tests\n", + ) + .unwrap(); + let mut config = config(); + config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { + "digest": "a".repeat(64), + "rootPath": instruction_root.display().to_string() + } + }, + "skills": [{ + "key": "company-1/reviewer", + "runtimeName": "reviewer", + "bundle": { + "digest": "b".repeat(64), + "rootPath": skill_root.display().to_string() + } + }] + })); + + let assets = prepare_agentcore_runtime_context_assets(&config).unwrap(); + + assert_eq!(assets.len(), 2); + assert_eq!(assets[0].digest.len(), 64); + assert!(assets[0] + .generated_skill + .as_ref() + .is_some_and(|skill| skill.contains("instructions/AGENTS.md"))); + assert!(assets[0] + .files + .iter() + .any(|(path, bytes)| path == Path::new("instructions/AGENTS.md") + && bytes == b"Follow the entry.\n")); + assert!(assets[0].files.iter().any(|(path, bytes)| path + == Path::new("instructions/references/policy.md") + && bytes == b"Instruction sibling.\n")); + assert_eq!(assets[1].digest, "b".repeat(64)); + assert!(assets[1].generated_skill.is_none()); + assert!(assets[1] + .files + .iter() + .any(|(path, bytes)| path == Path::new("SKILL.md") && bytes == b"# Reviewer\n")); + assert!(assets[1] + .files + .iter() + .any(|(path, bytes)| path == Path::new("references/checklist.md") + && bytes == b"- Verify tests\n")); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn agentcore_context_aggregate_bounds_are_closed_at_provider_limits() { + let assets = vec![ + AgentCoreContextAsset { + digest: "a".repeat(64), + files: vec![(PathBuf::from("instructions/AGENTS.md"), vec![0; 3])], + generated_skill: Some("companion".to_owned()), + }, + AgentCoreContextAsset { + digest: "b".repeat(64), + files: vec![(PathBuf::from("SKILL.md"), vec![0; 5])], + generated_skill: None, + }, + ]; + assert_eq!(agentcore_context_totals(&assets), (3, 17)); + assert!(validate_agentcore_context_aggregate( + MAX_CONTEXT_SKILL_SOURCES, + MAX_CONTEXT_UPLOAD_FILES, + MAX_CONTEXT_UPLOAD_BYTES, + ) + .is_ok()); + assert!( + validate_agentcore_context_aggregate(MAX_CONTEXT_SKILL_SOURCES + 1, 0, 0,) + .unwrap_err() + .contains("skill-source limit") + ); + assert!( + validate_agentcore_context_aggregate(1, MAX_CONTEXT_UPLOAD_FILES + 1, 0,) + .unwrap_err() + .contains("aggregate file limit") + ); + assert!( + validate_agentcore_context_aggregate(1, 1, MAX_CONTEXT_UPLOAD_BYTES + 1,) + .unwrap_err() + .contains("aggregate byte limit") + ); + } + + #[test] + fn agentcore_rejects_too_many_assigned_skills_before_reading_any_skill_root() { + let root = std::env::temp_dir().join(format!( + "paperclip-agentcore-context-count-{}", + uuid::Uuid::new_v4() + )); + fs::create_dir_all(&root).unwrap(); + fs::write(root.join("AGENTS.md"), "Follow the entry.\n").unwrap(); + let assigned = (0..MAX_CONTEXT_SKILL_SOURCES) + .map(|index| { + json!({ + "key": format!("company-1/skill-{index}"), + "runtimeName": format!("skill-{index}"), + "bundle": { + "digest": format!("{index:064x}"), + "rootPath": root.join("does-not-exist").display().to_string() + } + }) + }) + .collect::>(); + let mut config = config(); + config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { + "digest": "a".repeat(64), + "rootPath": root.display().to_string() + } + }, + "skills": assigned + })); + + let error = prepare_agentcore_runtime_context_assets(&config).unwrap_err(); + assert!(error.contains("skill-source limit")); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn agentcore_rejects_unsafe_context_prefixes_before_cloud_access() { + for unsafe_prefix in [ + "/absolute", + "company//profile", + "company/../profile", + "company/./profile", + ] { + let mut config = config(); + config.context_prefix = unsafe_prefix.to_owned(); + assert!( + validate_config(&config).is_err(), + "prefix should fail: {unsafe_prefix}" + ); + } + } + + #[test] + fn tool_names_are_safe_stable_and_collision_resistant() { + let first = remote_tool_name("issues.comment:create"); + let second = remote_tool_name("issues.comment/create"); + assert!(first + .chars() + .all(|ch| ch.is_ascii_lowercase() || ch.is_ascii_digit() || ch == '_')); + assert_ne!(first, second); + assert_eq!(first, remote_tool_name("issues.comment:create")); + } + + #[test] + fn tool_allowlist_is_confined_to_the_paperclip_inline_namespace() { + let tool = AuthorizedTool { + operation_id: "get_task_context".to_owned(), + version: 1, + description: "Read the assigned task context".to_owned(), + input_schema: json!({"type":"object"}), + response_schema: json!({"type":"object"}), + }; + let (_, allowed, remote, _) = encode_tools(&[tool]).unwrap(); + assert_eq!(AGENTCORE_INLINE_TOOL_ALLOWLIST, "@*/pc_*"); + assert!(remote.keys().all(|name| name.starts_with("pc_"))); + assert_eq!(allowed.len(), 1); + assert!(allowed[0].starts_with("@*/pc_")); + } + + #[test] + fn rejects_more_than_sixty_four_tools() { + let tool = AuthorizedTool { + operation_id: "op".to_owned(), + version: 1, + description: "op".to_owned(), + input_schema: json!({"type":"object"}), + response_schema: json!({"type":"object"}), + }; + assert!(encode_tools(&vec![tool; 65]).is_err()); + } + + #[test] + fn redacts_credential_markers_from_remote_errors() { + let value = + redact_aws_error("Authorization: AWS_SESSION_TOKEN X-Amz-Signature=secret-value"); + assert!(!value.contains("Authorization")); + assert!(!value.contains("AWS_SESSION_TOKEN")); + assert!(!value.contains("secret-value")); + assert_eq!( + redact_aws_error("AccessDeniedException: signed request"), + "AWS AgentCore access denied" + ); + assert_eq!( + classify_aws_error_code("ValidationException"), + "AWS AgentCore request validation failed" + ); + assert_eq!( + classify_aws_error_code("UnrecognizedFutureError"), + "AWS AgentCore request failed" + ); + } + + #[test] + fn estimates_only_the_qualified_model_token_component() { + assert_eq!( + estimate_model_token_cost_usd("other-model", 1, 1, 0, 0), + None + ); + let estimate = estimate_model_token_cost_usd( + "global.anthropic.claude-sonnet-4-6", + 1_000_000, + 1_000_000, + 0, + 0, + ) + .unwrap(); + assert!((estimate - 18.0).abs() < f64::EPSILON); + let cached = estimate_model_token_cost_usd( + "global.anthropic.claude-sonnet-4-6", + 2_000_000, + 0, + 1_000_000, + 1_000_000, + ) + .unwrap(); + assert!((cached - 10.05).abs() < f64::EPSILON); + let mixed = estimate_model_token_cost_usd( + "global.anthropic.claude-sonnet-4-6", + 3_000, + 0, + 4_000, + 1_000, + ) + .unwrap(); + assert!((mixed - 0.01395).abs() < 0.000_000_001); + } + + #[test] + fn eventstream_text_delta_is_normalized_without_provider_objects() { + let (sender, receiver) = mpsc::sync_channel(4); + let mut blocks = BTreeMap::new(); + let event = HarnessContentBlockDeltaEvent::builder() + .content_block_index(0) + .delta(HarnessContentBlockDelta::Text("hello".to_owned())) + .build() + .unwrap(); + normalize_stream_event( + InvokeHarnessStreamOutput::ContentBlockDelta(event), + &mut blocks, + &sender, + "invocation-1", + ); + match receiver.try_recv().unwrap() { + NetworkEvent { + invocation_id, + kind: NetworkEventKind::TextDelta(value), + } => { + assert_eq!(invocation_id, "invocation-1"); + assert_eq!(value, "hello"); + } + other => panic!("unexpected normalized event: {other:?}"), + } + } + + #[test] + fn eventstream_tool_json_is_buffered_until_the_block_is_complete() { + let (sender, receiver) = mpsc::sync_channel(8); + let mut blocks = BTreeMap::new(); + let start = HarnessToolUseBlockStart::builder() + .tool_use_id("tool-use-1") + .name("pc_get_task_abc123") + .r#type(HarnessToolUseType::ToolUse) + .build() + .unwrap(); + normalize_stream_event( + InvokeHarnessStreamOutput::ContentBlockStart( + HarnessContentBlockStartEvent::builder() + .content_block_index(2) + .start(HarnessContentBlockStart::ToolUse(start)) + .build() + .unwrap(), + ), + &mut blocks, + &sender, + "invocation-1", + ); + for chunk in ["{\"issue", "Id\":\"MCK-1\"}"] { + normalize_stream_event( + InvokeHarnessStreamOutput::ContentBlockDelta( + HarnessContentBlockDeltaEvent::builder() + .content_block_index(2) + .delta(HarnessContentBlockDelta::ToolUse( + HarnessToolUseBlockDelta::builder() + .input(chunk) + .build() + .unwrap(), + )) + .build() + .unwrap(), + ), + &mut blocks, + &sender, + "invocation-1", + ); + } + assert!(receiver.try_recv().is_err()); + normalize_stream_event( + InvokeHarnessStreamOutput::ContentBlockStop( + HarnessContentBlockStopEvent::builder() + .content_block_index(2) + .build() + .unwrap(), + ), + &mut blocks, + &sender, + "invocation-1", + ); + match receiver.try_recv().unwrap() { + NetworkEvent { + invocation_id, + kind: + NetworkEventKind::ToolUse { + call_id, + remote_name, + input, + }, + } => { + assert_eq!(invocation_id, "invocation-1"); + assert_eq!(call_id, "tool-use-1"); + assert_eq!(remote_name, "pc_get_task_abc123"); + assert_eq!(input, json!({"issueId":"MCK-1"})); + } + other => panic!("unexpected normalized event: {other:?}"), + } + } + + #[test] + fn eventstream_empty_tool_input_is_normalized_to_an_empty_object() { + let (sender, receiver) = mpsc::sync_channel(4); + let mut blocks = BTreeMap::new(); + let start = HarnessToolUseBlockStart::builder() + .tool_use_id("tool-use-empty") + .name("pc_get_task_context_abc123") + .r#type(HarnessToolUseType::ToolUse) + .build() + .unwrap(); + normalize_stream_event( + InvokeHarnessStreamOutput::ContentBlockStart( + HarnessContentBlockStartEvent::builder() + .content_block_index(1) + .start(HarnessContentBlockStart::ToolUse(start)) + .build() + .unwrap(), + ), + &mut blocks, + &sender, + "invocation-1", + ); + normalize_stream_event( + InvokeHarnessStreamOutput::ContentBlockDelta( + HarnessContentBlockDeltaEvent::builder() + .content_block_index(1) + .delta(HarnessContentBlockDelta::ToolUse( + HarnessToolUseBlockDelta::builder() + .input("") + .build() + .unwrap(), + )) + .build() + .unwrap(), + ), + &mut blocks, + &sender, + "invocation-1", + ); + normalize_stream_event( + InvokeHarnessStreamOutput::ContentBlockStop( + HarnessContentBlockStopEvent::builder() + .content_block_index(1) + .build() + .unwrap(), + ), + &mut blocks, + &sender, + "invocation-1", + ); + match receiver.try_recv().unwrap() { + NetworkEvent { + invocation_id, + kind: + NetworkEventKind::ToolUse { + call_id, + remote_name, + input, + }, + } => { + assert_eq!(invocation_id, "invocation-1"); + assert_eq!(call_id, "tool-use-empty"); + assert_eq!(remote_name, "pc_get_task_context_abc123"); + assert_eq!(input, json!({})); + } + other => panic!("unexpected normalized event: {other:?}"), + } + } + + #[test] + fn tool_results_use_the_harness_supported_text_content_variant() { + let value = json!({"ok": true, "nested": {"value": 7}}); + let content = encode_tool_result_content(&value).unwrap(); + let text = content + .as_text() + .expect("AgentCore managed Harness requires text tool results"); + assert_eq!(serde_json::from_str::(text).unwrap(), value); + } +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs b/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs index 75bd23cae8..facba2c080 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/bin/paperclip-runnerd.rs @@ -3,7 +3,8 @@ use std::process::ExitCode; use std::time::Duration; use paperclip_runner_core::durable::{ - capture_bootstrap_ticket, run_durable_runner, DurableRunnerConfig, + capture_bootstrap_ticket, run_durable_runner, AcpxLaunchProfile, DurableRunnerConfig, + OpenCodeLaunchProfile, QualifiedLaunchArtifact, }; use paperclip_runner_core::local_runner::{run_local_runner, LocalRunnerError, RunnerConfig}; use paperclip_runner_core::native_provider_backend::NativeProviderCommandExecutor; @@ -52,6 +53,106 @@ fn optional_u64(args: &[String], name: &str) -> Result, LocalRunnerE .map_err(|error| LocalRunnerError::invalid(format!("invalid {name}: {error}"))) } +fn optional_value(args: &[String], name: &str) -> Result, LocalRunnerError> { + let Some(index) = args.iter().position(|argument| argument == name) else { + return Ok(None); + }; + args.get(index + 1) + .cloned() + .map(Some) + .ok_or_else(|| LocalRunnerError::invalid(format!("missing value for {name}"))) +} + +fn acpx_launch_profile(args: &[String]) -> Result, LocalRunnerError> { + let authority_digest = optional_value(args, "--acpx-launch-authority-digest")?; + let command = optional_value(args, "--acpx-sidecar-command")?; + let command_sha256 = optional_value(args, "--acpx-sidecar-command-sha256")?; + let sidecar = optional_value(args, "--acpx-sidecar-script")?; + let sidecar_sha256 = optional_value(args, "--acpx-sidecar-script-sha256")?; + match ( + authority_digest, + command, + command_sha256, + sidecar, + sidecar_sha256, + ) { + (None, None, None, None, None) => Ok(None), + ( + Some(authority_digest), + Some(command), + Some(command_sha256), + Some(sidecar), + Some(sidecar_sha256), + ) => { + let command = PathBuf::from(command); + let sidecar = PathBuf::from(sidecar); + Ok(Some(AcpxLaunchProfile { + authority_digest, + command: command.clone(), + args: vec![sidecar.to_string_lossy().into_owned()], + artifacts: vec![ + QualifiedLaunchArtifact { + path: command, + sha256: command_sha256, + }, + QualifiedLaunchArtifact { + path: sidecar, + sha256: sidecar_sha256, + }, + ], + })) + } + _ => Err(LocalRunnerError::invalid( + "ACPX sidecar launch profile requires its authority, command, script, and both SHA-256 digests", + )), + } +} + +fn opencode_launch_profile( + args: &[String], +) -> Result, LocalRunnerError> { + let command = optional_value(args, "--opencode-proxy-command")?; + let command_sha256 = optional_value(args, "--opencode-proxy-command-sha256")?; + let proxy_script = optional_value(args, "--opencode-proxy-script")?; + let proxy_script_sha256 = optional_value(args, "--opencode-proxy-script-sha256")?; + let executable = optional_value(args, "--opencode-executable")?; + let executable_sha256 = optional_value(args, "--opencode-executable-sha256")?; + match ( + command, + command_sha256, + proxy_script, + proxy_script_sha256, + executable, + executable_sha256, + ) { + (None, None, None, None, None, None) => Ok(None), + ( + Some(command), + Some(command_sha256), + Some(proxy_script), + Some(proxy_script_sha256), + Some(executable), + Some(executable_sha256), + ) => Ok(Some(OpenCodeLaunchProfile { + command: QualifiedLaunchArtifact { + path: PathBuf::from(command), + sha256: command_sha256, + }, + proxy_script: QualifiedLaunchArtifact { + path: PathBuf::from(proxy_script), + sha256: proxy_script_sha256, + }, + executable: QualifiedLaunchArtifact { + path: PathBuf::from(executable), + sha256: executable_sha256, + }, + })), + _ => Err(LocalRunnerError::invalid( + "OpenCode launch profile requires command, proxy, executable, and all SHA-256 digests", + )), + } +} + fn usize_value(args: &[String], name: &str, default: usize) -> Result { optional_u64(args, name)?.map_or(Ok(default), |value| { usize::try_from(value) @@ -122,6 +223,8 @@ fn run_durable(args: &[String]) -> Result<(), LocalRunnerError> { item_id: value(args, "--item-id")?, runner_version: value(args, "--runner-version")?, runner_digest: value(args, "--runner-digest")?, + acpx_launch_profile: acpx_launch_profile(args)?, + opencode_launch_profile: opencode_launch_profile(args)?, max_outbox_bytes: usize_value(args, "--max-outbox-bytes", 16 * 1024 * 1024)?, p0_reserve_bytes: usize_value(args, "--p0-reserve-bytes", 1024 * 1024)?, max_frame_bytes: usize_value(args, "--max-frame-bytes", 1024 * 1024)?, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/claude_managed_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/claude_managed_provider.rs new file mode 100644 index 0000000000..8d68b2212b --- /dev/null +++ b/packages/paperclip-runner/runner/crates/runner-core/src/claude_managed_provider.rs @@ -0,0 +1,4269 @@ +use std::collections::{BTreeMap, VecDeque}; +use std::error::Error; +use std::fmt::{self, Display, Formatter}; +use std::fs; +use std::io::{BufRead, BufReader, Read}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::{self, Receiver, SyncSender}; +use std::sync::Arc; +use std::thread::{self, JoinHandle}; +use std::time::Duration; + +use reqwest::blocking::Client; +use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE, RETRY_AFTER}; +use reqwest::Method; +use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; +use uuid::Uuid; + +use crate::local_runner::LocalRunnerError; +use crate::managed_provider::{ + ClaudeManagedProviderConfig, ClaudeManagedSkillRef, Provider, ProviderEvent, ProviderKind, + ProviderRuntimeIdentity, +}; +use crate::provider_bridge::{AuthorizedTool, ToolResult}; + +const ANTHROPIC_ORIGIN: &str = "https://api.anthropic.com"; +const ANTHROPIC_VERSION: &str = "2023-06-01"; +const QUALIFIED_BETA: &str = "managed-agents-2026-04-01"; +const MAX_REMOTE_EVENT_BYTES: usize = 4 * 1024 * 1024; +const MAX_REMOTE_RESPONSE_BYTES: usize = 8 * 1024 * 1024; +const MAX_REMOTE_TOOLS: usize = 128; +const MAX_HISTORY_EVENTS: usize = 10_000; +const MAX_HISTORY_PAGES: usize = 100; +// Managed Agents supports at most 500 deduplicated skills in a session. +// Paperclip's generated instruction companion consumes one of those slots. +const MAX_MANAGED_SKILL_ATTACHMENTS: usize = 500; +const MAX_SKILL_UPLOAD_FILES: usize = 10_000; +const MAX_SKILL_UPLOAD_BYTES: usize = 32 * 1024 * 1024; +const MAX_SKILL_RECONCILIATION_PAGES: usize = 100; +const SESSION_OWNERSHIP_METADATA_KEY: &str = "paperclip_ownership"; + +#[derive(Clone, Debug)] +pub struct ClaudeManagedProviderStartError { + cause: LocalRunnerError, + cleanup_inventory: Option>, + durable_skills: Option>, + recovery_session_id: Option, +} + +impl ClaudeManagedProviderStartError { + fn new(cause: LocalRunnerError) -> Self { + Self { + cause, + cleanup_inventory: None, + durable_skills: None, + recovery_session_id: None, + } + } + + fn fresh_failure( + cause: LocalRunnerError, + cleanup_inventory: Vec, + ) -> Self { + Self { + cause, + cleanup_inventory: Some(cleanup_inventory), + durable_skills: None, + recovery_session_id: None, + } + } + + fn retryable_fresh_failure( + cause: LocalRunnerError, + durable_skills: Vec, + recovery_session_id: Option, + ) -> Self { + Self { + cause, + cleanup_inventory: None, + durable_skills: Some(durable_skills), + recovery_session_id, + } + } + + pub fn cleanup_inventory(&self) -> Option<&[ClaudeManagedSkillRef]> { + self.cleanup_inventory.as_deref() + } + + pub fn durable_skills(&self) -> Option<&[ClaudeManagedSkillRef]> { + self.durable_skills.as_deref() + } + + pub fn recovery_session_id(&self) -> Option<&str> { + self.recovery_session_id.as_deref() + } +} + +impl Display for ClaudeManagedProviderStartError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + self.cause.fmt(formatter) + } +} + +impl Error for ClaudeManagedProviderStartError {} + +#[derive(Debug)] +struct ClaudeManagedBootstrapError { + cause: LocalRunnerError, + session_id: Option, + session_create_ambiguous: bool, +} + +impl ClaudeManagedBootstrapError { + fn after_session(cause: LocalRunnerError, session_id: &str) -> Self { + Self { + cause, + session_id: Some(session_id.to_owned()), + session_create_ambiguous: false, + } + } + + fn ambiguous_create(cause: LocalRunnerError) -> Self { + Self { + cause, + session_id: None, + session_create_ambiguous: true, + } + } +} + +impl From for ClaudeManagedBootstrapError { + fn from(cause: LocalRunnerError) -> Self { + Self { + cause, + session_id: None, + session_create_ambiguous: false, + } + } +} + +struct SensitiveApiKey(String); + +impl Drop for SensitiveApiKey { + fn drop(&mut self) { + // Rust strings cannot guarantee compiler-proof zeroization, but clearing + // prevents this long-lived owner from retaining the credential. + self.0.clear(); + } +} + +enum NetworkCommand { + Request { + method: Method, + path: String, + body: Option, + retry: bool, + reply: mpsc::Sender>, + }, + OpenStream { + session_id: String, + reply: mpsc::Sender>, + }, + Stop, +} + +enum NetworkEvent { + Remote(Value), + RecoverableFailure(String), +} + +struct NetworkWorker { + commands: SyncSender, + events: Receiver, + stop: Arc, + join: Option>, +} + +impl NetworkWorker { + fn start(api_key: SensitiveApiKey, beta: &str) -> Result { + Self::start_at(api_key, beta, ANTHROPIC_ORIGIN, true) + } + + fn start_at( + api_key: SensitiveApiKey, + beta: &str, + origin: &str, + require_https: bool, + ) -> Result { + let (command_tx, command_rx) = mpsc::sync_channel::(32); + let (event_tx, event_rx) = mpsc::sync_channel::(256); + let stop = Arc::new(AtomicBool::new(false)); + let worker_stop = Arc::clone(&stop); + let beta = beta.to_owned(); + let origin = origin.trim_end_matches('/').to_owned(); + let join = thread::Builder::new() + .name("paperclip-claude-managed-network".to_owned()) + .spawn(move || { + network_loop( + api_key, + beta, + origin, + require_https, + command_rx, + event_tx, + worker_stop, + ) + }) + .map_err(|error| { + LocalRunnerError::invalid(format!( + "failed to start Anthropic network worker: {error}" + )) + })?; + Ok(Self { + commands: command_tx, + events: event_rx, + stop, + join: Some(join), + }) + } + + fn request( + &self, + method: Method, + path: String, + body: Option, + ) -> Result { + let (reply_tx, reply_rx) = mpsc::channel(); + self.commands + .send(NetworkCommand::Request { + method, + path, + body, + retry: true, + reply: reply_tx, + }) + .map_err(|_| LocalRunnerError::invalid("Anthropic network worker stopped"))?; + reply_rx + .recv_timeout(Duration::from_secs(35)) + .map_err(|_| LocalRunnerError::invalid("Anthropic request timed out"))? + .map_err(LocalRunnerError::invalid) + } + + fn request_once( + &self, + method: Method, + path: String, + body: Option, + ) -> Result { + let (reply_tx, reply_rx) = mpsc::channel(); + self.commands + .send(NetworkCommand::Request { + method, + path, + body, + retry: false, + reply: reply_tx, + }) + .map_err(|_| LocalRunnerError::invalid("Anthropic network worker stopped"))?; + reply_rx + .recv_timeout(Duration::from_secs(35)) + .map_err(|_| LocalRunnerError::invalid("Anthropic request timed out"))? + .map_err(LocalRunnerError::invalid) + } + + fn open_stream(&self, session_id: &str) -> Result<(), LocalRunnerError> { + let (reply_tx, reply_rx) = mpsc::channel(); + self.commands + .send(NetworkCommand::OpenStream { + session_id: session_id.to_owned(), + reply: reply_tx, + }) + .map_err(|_| LocalRunnerError::invalid("Anthropic network worker stopped"))?; + reply_rx + .recv_timeout(Duration::from_secs(15)) + .map_err(|_| LocalRunnerError::invalid("Anthropic event stream startup timed out"))? + .map_err(LocalRunnerError::invalid) + } + + fn try_event(&self) -> Option { + self.events.try_recv().ok() + } + + fn stop(&mut self) { + self.stop.store(true, Ordering::Release); + let _ = self.commands.send(NetworkCommand::Stop); + if let Some(join) = self.join.take() { + let _ = join.join(); + } + } +} + +impl Drop for NetworkWorker { + fn drop(&mut self) { + self.stop(); + } +} + +fn runtime_array<'a>(context: &'a Value, field: &str) -> Result<&'a Vec, LocalRunnerError> { + context.get(field).and_then(Value::as_array).ok_or_else(|| { + LocalRunnerError::invalid(format!("runtimeContext.{field} must be an array")) + }) +} + +fn runtime_text<'a>(value: &'a Value, pointer: &str) -> Result<&'a str, LocalRunnerError> { + value + .pointer(pointer) + .and_then(Value::as_str) + .filter(|value| !value.trim().is_empty()) + .ok_or_else(|| LocalRunnerError::invalid(format!("runtimeContext {pointer} is missing"))) +} + +fn collect_bundle_files(root: &Path) -> Result)>, LocalRunnerError> { + fn visit( + root: &Path, + current: &Path, + files: &mut Vec<(PathBuf, Vec)>, + total: &mut usize, + ) -> Result<(), LocalRunnerError> { + let mut entries = fs::read_dir(current) + .map_err(|error| { + LocalRunnerError::invalid(format!("failed to read runtime asset: {error}")) + })? + .collect::, _>>() + .map_err(|error| { + LocalRunnerError::invalid(format!("failed to enumerate runtime asset: {error}")) + })?; + entries.sort_by_key(|entry| entry.file_name()); + for entry in entries { + let path = entry.path(); + let metadata = fs::symlink_metadata(&path).map_err(|error| { + LocalRunnerError::invalid(format!("failed to inspect runtime asset: {error}")) + })?; + if metadata.file_type().is_symlink() { + return Err(LocalRunnerError::invalid( + "managed skill bundles may not contain symlinks", + )); + } + if metadata.is_dir() { + visit(root, &path, files, total)?; + } else if metadata.is_file() { + let relative = path + .strip_prefix(root) + .map_err(|_| LocalRunnerError::invalid("runtime asset escaped its root"))? + .to_path_buf(); + let bytes = fs::read(&path).map_err(|error| { + LocalRunnerError::invalid(format!("failed to read runtime asset file: {error}")) + })?; + *total = total.saturating_add(bytes.len()); + if *total > MAX_SKILL_UPLOAD_BYTES { + return Err(LocalRunnerError::invalid( + "managed skill upload exceeded its size limit", + )); + } + if files.len() >= MAX_SKILL_UPLOAD_FILES { + return Err(LocalRunnerError::invalid( + "managed skill upload exceeded its file limit", + )); + } + files.push((relative, bytes)); + } + } + Ok(()) + } + let metadata = fs::symlink_metadata(root).map_err(|error| { + LocalRunnerError::invalid(format!("runtime asset is unavailable: {error}")) + })?; + if !metadata.is_dir() || metadata.file_type().is_symlink() { + return Err(LocalRunnerError::invalid( + "runtime asset root must be a real directory", + )); + } + let mut files = Vec::new(); + let mut total = 0; + visit(root, root, &mut files, &mut total)?; + Ok(files) +} + +fn multipart_field(body: &mut Vec, boundary: &str, name: &str, value: &str) { + body.extend_from_slice( + format!( + "--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\n\r\n{value}\r\n" + ) + .as_bytes(), + ); +} + +fn multipart_file(body: &mut Vec, boundary: &str, filename: &str, bytes: &[u8]) { + body.extend_from_slice(format!("--{boundary}\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"{filename}\"\r\nContent-Type: application/octet-stream\r\n\r\n").as_bytes()); + body.extend_from_slice(bytes); + body.extend_from_slice(b"\r\n"); +} + +struct ManagedSkillUploadPlan { + title: String, + top_level: String, + files: Vec<(PathBuf, Vec)>, + generated_skill: Option, +} + +fn validate_managed_skill_upload_aggregate( + attachment_count: usize, + file_count: usize, + byte_count: usize, +) -> Result<(), LocalRunnerError> { + if attachment_count > MAX_MANAGED_SKILL_ATTACHMENTS { + return Err(LocalRunnerError::invalid( + "managed runtime context exceeds the provider skill limit", + )); + } + if file_count > MAX_SKILL_UPLOAD_FILES { + return Err(LocalRunnerError::invalid( + "managed runtime context exceeds the aggregate file limit", + )); + } + if byte_count > MAX_SKILL_UPLOAD_BYTES { + return Err(LocalRunnerError::invalid( + "managed runtime context exceeds the aggregate byte limit", + )); + } + Ok(()) +} + +fn managed_skill_upload_totals(plans: &[ManagedSkillUploadPlan]) -> (usize, usize) { + plans.iter().fold((0_usize, 0_usize), |totals, plan| { + let generated = plan.generated_skill.as_ref(); + let file_count = totals + .0 + .saturating_add(plan.files.len()) + .saturating_add(usize::from(generated.is_some())); + let byte_count = plan + .files + .iter() + .fold(totals.1, |sum, (_, bytes)| sum.saturating_add(bytes.len())) + .saturating_add(generated.map_or(0, String::len)); + (file_count, byte_count) + }) +} + +fn managed_skills_client(api_key: &str, require_https: bool) -> Result { + let mut headers = HeaderMap::new(); + headers.insert( + "x-api-key", + HeaderValue::from_str(api_key).map_err(|_| { + LocalRunnerError::invalid("Anthropic API key contains invalid header bytes") + })?, + ); + headers.insert( + "anthropic-version", + HeaderValue::from_static(ANTHROPIC_VERSION), + ); + let mut client_builder = Client::builder() + .default_headers(headers) + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(60)) + .https_only(require_https); + if !require_https { + client_builder = client_builder.no_proxy(); + } + client_builder + .build() + .map_err(|_| LocalRunnerError::invalid("failed to construct Anthropic skills client")) +} + +fn managed_skill_ref(value: &Value) -> Result { + let skill_id = value + .get("id") + .and_then(Value::as_str) + .filter(|value| valid_managed_skill_id(value)) + .ok_or_else(|| LocalRunnerError::invalid("Anthropic skill response omitted valid id"))?; + let version = value + .get("latest_version_id") + .or_else(|| value.get("latest_version")) + .and_then(Value::as_str) + .filter(|value| valid_managed_skill_id(value)) + .ok_or_else(|| { + LocalRunnerError::invalid("Anthropic skill response omitted immutable version") + })?; + Ok(ClaudeManagedSkillRef { + skill_id: skill_id.to_owned(), + version: version.to_owned(), + }) +} + +fn reconcile_managed_skill( + client: &Client, + origin: &str, + display_name: &str, +) -> Result, LocalRunnerError> { + let mut page = None::; + let mut matches = Vec::new(); + for _ in 0..MAX_SKILL_RECONCILIATION_PAGES { + let mut path = "/v1/skills?source=custom&limit=1000".to_owned(); + if let Some(page) = page.as_deref() { + path.push_str("&page="); + path.push_str(&percent_encode_query(page)); + } + let value = bounded_request(client, origin, Method::GET, &path, None, true) + .map_err(LocalRunnerError::invalid)?; + let data = value.get("data").and_then(Value::as_array).ok_or_else(|| { + LocalRunnerError::invalid("Anthropic skill list response omitted data") + })?; + for skill in data { + let remote_name = skill + .get("display_name") + .or_else(|| skill.get("display_title")) + .and_then(Value::as_str); + if remote_name == Some(display_name) { + matches.push(managed_skill_ref(skill)?); + } + } + page = value + .get("next_page") + .and_then(Value::as_str) + .filter(|page| !page.is_empty()) + .map(str::to_owned); + if page.is_none() { + matches.sort_by(|left, right| left.skill_id.cmp(&right.skill_id)); + matches.dedup_by(|left, right| left.skill_id == right.skill_id); + let Some(chosen) = matches.first().cloned() else { + return Ok(None); + }; + if matches.len() > 1 { + delete_managed_skills_with_client(client, origin, &matches[1..])?; + } + return Ok(Some(chosen)); + } + } + Err(LocalRunnerError::invalid( + "Anthropic skill reconciliation exceeded the bounded page limit", + )) +} + +fn delete_managed_skills_with_client( + client: &Client, + origin: &str, + skills: &[ClaudeManagedSkillRef], +) -> Result<(), LocalRunnerError> { + for skill in skills { + bounded_request( + client, + origin, + Method::DELETE, + &format!("/v1/skills/{}", percent_encode_query(&skill.skill_id)), + None, + true, + ) + .map_err(LocalRunnerError::invalid)?; + } + Ok(()) +} + +fn delete_managed_skills_at( + api_key: &str, + origin: &str, + require_https: bool, + skills: &[ClaudeManagedSkillRef], +) -> Result<(), LocalRunnerError> { + if skills.is_empty() { + return Ok(()); + } + let client = managed_skills_client(api_key, require_https)?; + delete_managed_skills_with_client(&client, origin.trim_end_matches('/'), skills) +} + +fn delete_managed_session_at( + api_key: &str, + beta: &str, + origin: &str, + require_https: bool, + session_id: &str, +) -> Result<(), LocalRunnerError> { + let mut headers = HeaderMap::new(); + headers.insert( + "x-api-key", + HeaderValue::from_str(api_key).map_err(|_| { + LocalRunnerError::invalid("Anthropic API key contains invalid header bytes") + })?, + ); + headers.insert( + "anthropic-version", + HeaderValue::from_static(ANTHROPIC_VERSION), + ); + headers.insert( + "anthropic-beta", + HeaderValue::from_str(beta) + .map_err(|_| LocalRunnerError::invalid("Anthropic beta version is invalid"))?, + ); + let mut builder = Client::builder() + .default_headers(headers) + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(30)) + .https_only(require_https); + if !require_https { + builder = builder.no_proxy(); + } + let client = builder + .build() + .map_err(|_| LocalRunnerError::invalid("failed to construct Anthropic cleanup client"))?; + bounded_request( + &client, + origin.trim_end_matches('/'), + Method::DELETE, + &format!( + "/v1/sessions/{}?beta=true", + percent_encode_query(session_id) + ), + None, + true, + ) + .map(|_| ()) + .map_err(LocalRunnerError::invalid) +} + +fn handle_fresh_bootstrap_failure( + api_key: &str, + beta: &str, + origin: &str, + require_https: bool, + skills: Vec, + error: ClaudeManagedBootstrapError, +) -> ClaudeManagedProviderStartError { + if error.session_create_ambiguous { + return ClaudeManagedProviderStartError::retryable_fresh_failure(error.cause, skills, None); + } + if let Some(session_id) = error.session_id.as_deref() { + if let Err(cleanup_error) = + delete_managed_session_at(api_key, beta, origin, require_https, session_id) + { + return ClaudeManagedProviderStartError::retryable_fresh_failure( + LocalRunnerError::invalid(format!( + "{}; cleanup of created session failed: {cleanup_error}", + error.cause + )), + skills, + Some(session_id.to_owned()), + ); + } + } + match delete_managed_skills_at(api_key, origin, require_https, &skills) { + Ok(()) => ClaudeManagedProviderStartError::fresh_failure(error.cause, Vec::new()), + Err(cleanup_error) => ClaudeManagedProviderStartError::fresh_failure( + LocalRunnerError::invalid(format!( + "{}; cleanup of uploaded skills failed: {cleanup_error}", + error.cause + )), + skills, + ), + } +} + +fn upload_managed_skill( + client: &Client, + origin: &str, + ownership_scope: &str, + title: &str, + top_level: &str, + mut files: Vec<(PathBuf, Vec)>, + generated_skill: Option, +) -> Result { + if let Some(skill) = generated_skill { + files.push((PathBuf::from("SKILL.md"), skill.into_bytes())); + } + if !files.iter().any(|(path, _)| path == Path::new("SKILL.md")) { + return Err(LocalRunnerError::invalid( + "managed custom skill bundle is missing SKILL.md", + )); + } + files.sort_by(|left, right| left.0.cmp(&right.0)); + let mut content_identity = Sha256::new(); + content_identity.update(b"paperclip-managed-skill-v1\0"); + content_identity.update(title.as_bytes()); + content_identity.update([0]); + content_identity.update(top_level.as_bytes()); + for (path, bytes) in &files { + let relative = path + .components() + .map(|part| part.as_os_str().to_string_lossy()) + .collect::>() + .join("/"); + content_identity.update((relative.len() as u64).to_be_bytes()); + content_identity.update(relative.as_bytes()); + content_identity.update((bytes.len() as u64).to_be_bytes()); + content_identity.update(bytes); + } + let content_identity = format!("{:x}", content_identity.finalize()); + // Never bind to a mutable latest-version lookup discovered by predictable + // name. Every materialization creates a new custom skill and pins the exact + // immutable version ID returned by that creation response. + let ownership_identity = format!("{:x}", Sha256::digest(ownership_scope.as_bytes())); + let display_name = format!( + "pc-{}-{}", + &ownership_identity[..24], + &content_identity[..24] + ); + if let Some(existing) = reconcile_managed_skill(client, origin, &display_name)? { + return Ok(existing); + } + let upload_nonce = Uuid::new_v4().simple().to_string(); + let boundary = format!("paperclip-{}-{upload_nonce}", &content_identity[..24]); + let mut body = Vec::new(); + multipart_field(&mut body, &boundary, "display_name", &display_name); + for (path, bytes) in files { + let relative = path + .components() + .map(|part| part.as_os_str().to_string_lossy()) + .collect::>() + .join("/"); + multipart_file( + &mut body, + &boundary, + &format!("{top_level}/{relative}"), + &bytes, + ); + } + body.extend_from_slice(format!("--{boundary}--\r\n").as_bytes()); + let response = client + .post(format!("{origin}/v1/skills")) + .header( + CONTENT_TYPE, + format!("multipart/form-data; boundary={boundary}"), + ) + .body(body) + .send(); + let response = match response { + Ok(response) => response, + Err(_) => { + return reconcile_managed_skill(client, origin, &display_name)?.ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic skill creation is ambiguous and reconciliation found no owned skill", + ) + }) + } + }; + if !response.status().is_success() { + let status = response.status().as_u16(); + if status == 429 || response.status().is_server_error() { + if let Some(reconciled) = reconcile_managed_skill(client, origin, &display_name)? { + return Ok(reconciled); + } + } + return Err(LocalRunnerError::invalid(format!( + "Anthropic skill upload failed with HTTP {status}" + ))); + } + let parsed = response + .json::() + .map_err(|_| LocalRunnerError::invalid("Anthropic skill upload returned invalid JSON")) + .and_then(|value| managed_skill_ref(&value)); + match parsed { + Ok(skill) => Ok(skill), + Err(response_error) => { + reconcile_managed_skill(client, origin, &display_name)?.ok_or(response_error) + } + } +} + +fn upload_managed_runtime_skills_at( + api_key: &str, + config: &ClaudeManagedProviderConfig, + ownership_scope: &str, + origin: &str, + require_https: bool, +) -> Result, ClaudeManagedProviderStartError> { + let fresh_error = |error| ClaudeManagedProviderStartError::fresh_failure(error, Vec::new()); + let Some(context) = config.runtime_context.as_ref() else { + return Ok(Vec::new()); + }; + let instruction_root = + Path::new(runtime_text(context, "/instructions/bundle/rootPath").map_err(fresh_error)?); + let instruction_digest = + runtime_text(context, "/instructions/bundle/digest").map_err(fresh_error)?; + let entry_path = runtime_text(context, "/instructions/entryPath").map_err(fresh_error)?; + let instruction_files = collect_bundle_files(instruction_root) + .map_err(fresh_error)? + .into_iter() + .map(|(path, bytes)| (PathBuf::from("instructions").join(path), bytes)) + .collect(); + let instruction_identity = format!( + "{:x}", + Sha256::digest(format!("{instruction_digest}\0{entry_path}").as_bytes()) + ); + let instruction_name = format!("paperclip-instructions-{}", &instruction_identity[..12]); + let companion = format!( + "---\nname: {instruction_name}\ndescription: Paperclip agent instruction sibling bundle\n---\nRead `instructions/{entry_path}` and its sibling files when the system instructions require them. Treat all files as read-only.\n" + ); + let assigned = runtime_array(context, "skills").map_err(fresh_error)?; + validate_managed_skill_upload_aggregate(assigned.len().saturating_add(1), 0, 0) + .map_err(fresh_error)?; + let mut plans = vec![ManagedSkillUploadPlan { + title: format!( + "pc:{}:{}:instructions:{}", + config.profile_id, config.agent_version, instruction_identity + ), + top_level: instruction_name, + files: instruction_files, + generated_skill: Some(companion), + }]; + let (file_count, byte_count) = managed_skill_upload_totals(&plans); + validate_managed_skill_upload_aggregate(plans.len(), file_count, byte_count) + .map_err(fresh_error)?; + + for skill in assigned { + let key = skill.get("key").and_then(Value::as_str).unwrap_or("skill"); + let runtime_name = runtime_text(skill, "/runtimeName").map_err(fresh_error)?; + let digest = runtime_text(skill, "/bundle/digest").map_err(fresh_error)?; + let root = Path::new(runtime_text(skill, "/bundle/rootPath").map_err(fresh_error)?); + plans.push(ManagedSkillUploadPlan { + title: format!( + "pc:{}:{}:{}:{}", + config.profile_id, + config.agent_version, + &format!("{:x}", Sha256::digest(key.as_bytes()))[..12], + digest + ), + top_level: runtime_name.to_owned(), + files: collect_bundle_files(root).map_err(fresh_error)?, + generated_skill: None, + }); + let (file_count, byte_count) = managed_skill_upload_totals(&plans); + validate_managed_skill_upload_aggregate(plans.len(), file_count, byte_count) + .map_err(fresh_error)?; + } + + // Construct the client only after the complete upload plan has passed all + // provider and aggregate bounds, so validation cannot leave partial remote + // materialization behind. + let client = managed_skills_client(api_key, require_https).map_err(fresh_error)?; + let mut attachments = Vec::with_capacity(plans.len()); + for plan in plans { + let uploaded = upload_managed_skill( + &client, + origin.trim_end_matches('/'), + ownership_scope, + &plan.title, + &plan.top_level, + plan.files, + plan.generated_skill, + ); + match uploaded { + Ok(skill) => attachments.push(skill), + Err(upload_error) => { + return match delete_managed_skills_with_client( + &client, + origin.trim_end_matches('/'), + &attachments, + ) { + Ok(()) => Err(ClaudeManagedProviderStartError::fresh_failure( + upload_error, + Vec::new(), + )), + Err(cleanup_error) => Err(ClaudeManagedProviderStartError::fresh_failure( + LocalRunnerError::invalid(format!( + "{upload_error}; cleanup of known uploaded skills failed: {cleanup_error}" + )), + attachments, + )), + }; + } + } + } + Ok(attachments) +} + +fn upload_managed_runtime_skills( + api_key: &str, + config: &ClaudeManagedProviderConfig, + ownership_scope: &str, +) -> Result, ClaudeManagedProviderStartError> { + upload_managed_runtime_skills_at(api_key, config, ownership_scope, ANTHROPIC_ORIGIN, true) +} + +fn valid_managed_skill_id(value: &str) -> bool { + !value.is_empty() + && value.len() <= 512 + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-')) +} + +fn validate_managed_skill_refs( + config: &ClaudeManagedProviderConfig, + skills: &[ClaudeManagedSkillRef], +) -> Result<(), LocalRunnerError> { + let expected = match config.runtime_context.as_ref() { + None => 0, + Some(context) => runtime_array(context, "skills")? + .len() + .checked_add(1) + .ok_or_else(|| LocalRunnerError::invalid("managed skill count overflowed"))?, + }; + validate_managed_skill_upload_aggregate(expected, 0, 0)?; + let mut unique_ids = std::collections::BTreeSet::new(); + if skills.len() != expected + || skills.iter().any(|skill| { + !valid_managed_skill_id(&skill.skill_id) + || !valid_managed_skill_id(&skill.version) + || !unique_ids.insert(skill.skill_id.as_str()) + }) + { + return Err(LocalRunnerError::invalid( + "persisted Claude managed skill ownership is malformed or incomplete", + )); + } + Ok(()) +} + +fn managed_skill_attachments(skills: &[ClaudeManagedSkillRef]) -> Vec { + skills + .iter() + .map(|skill| { + json!({ + "type": "custom", + "skill_id": skill.skill_id, + "version": skill.version, + }) + }) + .collect() +} + +fn managed_system_instructions( + config: &ClaudeManagedProviderConfig, +) -> Result { + let Some(context) = config.runtime_context.as_ref() else { + return Ok(config.instructions.clone()); + }; + let instruction_root = runtime_text(context, "/instructions/bundle/rootPath")?; + let instruction_digest = runtime_text(context, "/instructions/bundle/digest")?; + let entry_path = runtime_text(context, "/instructions/entryPath")?; + let instruction_identity = format!( + "{:x}", + Sha256::digest(format!("{instruction_digest}\0{entry_path}").as_bytes()) + ); + let instruction_name = format!("paperclip-instructions-{}", &instruction_identity[..12]); + let local_directive = format!("Read-only instruction sibling root: {instruction_root}"); + let replacement = format!( + "Read-only instruction siblings are in the attached `{instruction_name}` skill under `instructions/`." + ); + config + .instructions + .strip_suffix(&local_directive) + .map(|prefix| format!("{prefix}{replacement}")) + .ok_or_else(|| { + LocalRunnerError::invalid( + "Claude Managed instruction-root directive is missing or inconsistent", + ) + }) +} + +fn managed_agent_overrides( + config: &ClaudeManagedProviderConfig, + system_instructions: &str, + mut custom_tools: Vec, + skills: &[ClaudeManagedSkillRef], +) -> Value { + let mut tools = vec![json!({ + "type": "agent_toolset_20260401", + "default_config": { "enabled": false }, + "configs": [{ "name": "read", "enabled": true }] + })]; + tools.append(&mut custom_tools); + json!({ + "model": { "id": config.model }, + "system": system_instructions, + "tools": tools, + "mcp_servers": [], + "skills": managed_skill_attachments(skills), + }) +} + +fn managed_agent_update(mut custom_tools: Vec) -> Value { + let mut tools = vec![json!({ + "type": "agent_toolset_20260401", + "default_config": { "enabled": false }, + "configs": [{ "name": "read", "enabled": true }] + })]; + tools.append(&mut custom_tools); + json!({ + "tools": tools, + "mcp_servers": [], + }) +} + +fn network_loop( + api_key: SensitiveApiKey, + beta: String, + origin: String, + require_https: bool, + commands: Receiver, + events: SyncSender, + stop: Arc, +) { + let mut headers = HeaderMap::new(); + let Ok(key) = HeaderValue::from_str(&api_key.0) else { + let _ = events.send(NetworkEvent::RecoverableFailure( + "Anthropic API key contains invalid header bytes".to_owned(), + )); + return; + }; + headers.insert("x-api-key", key); + headers.insert( + "anthropic-version", + HeaderValue::from_static(ANTHROPIC_VERSION), + ); + let Ok(beta_header) = HeaderValue::from_str(&beta) else { + let _ = events.send(NetworkEvent::RecoverableFailure( + "Anthropic beta version is invalid".to_owned(), + )); + return; + }; + headers.insert("anthropic-beta", beta_header); + headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + let mut client_builder = Client::builder() + .default_headers(headers) + .connect_timeout(Duration::from_secs(10)) + .timeout(Duration::from_secs(30)) + .https_only(require_https); + if !require_https { + client_builder = client_builder.no_proxy(); + } + let Ok(client) = client_builder.build() else { + let _ = events.send(NetworkEvent::RecoverableFailure( + "failed to construct Anthropic HTTPS client".to_owned(), + )); + return; + }; + while let Ok(command) = commands.recv() { + match command { + NetworkCommand::Request { + method, + path, + body, + retry, + reply, + } => { + let result = bounded_request(&client, &origin, method, &path, body, retry); + let _ = reply.send(result); + } + NetworkCommand::OpenStream { session_id, reply } => { + let stream_client = client.clone(); + let stream_origin = origin.clone(); + let stream_events = events.clone(); + let stream_stop = Arc::clone(&stop); + thread::spawn(move || { + stream_remote_events( + stream_client, + stream_origin, + session_id, + stream_events, + stream_stop, + reply, + ) + }); + } + NetworkCommand::Stop => break, + } + } + stop.store(true, Ordering::Release); + drop(api_key); +} + +fn bounded_request( + client: &Client, + origin: &str, + method: Method, + path: &str, + body: Option, + retry: bool, +) -> Result { + let mut backoff = Duration::from_millis(250); + let attempts = if retry { 4 } else { 1 }; + for attempt in 0..attempts { + let mut request = client.request(method.clone(), format!("{origin}{path}")); + if let Some(body) = body.as_ref() { + request = request.json(body); + } + let mut response = match request.send() { + Ok(response) => response, + Err(_) if retry && attempt + 1 < attempts => { + thread::sleep(backoff); + backoff = (backoff * 2).min(Duration::from_secs(4)); + continue; + } + Err(_) => return Err("Anthropic request transport failed".to_owned()), + }; + let status = response.status(); + if !status.is_success() { + if method == Method::DELETE && status.as_u16() == 404 { + return Ok(json!({ "deleted": true, "alreadyMissing": true })); + } + if retry + && (status.as_u16() == 429 || status.is_server_error()) + && attempt + 1 < attempts + { + let wait = response + .headers() + .get(RETRY_AFTER) + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.parse::().ok()) + .map(Duration::from_secs) + .unwrap_or(backoff) + .min(Duration::from_secs(8)); + thread::sleep(wait); + backoff = (backoff * 2).min(Duration::from_secs(4)); + continue; + } + return Err(format!( + "Anthropic request failed with HTTP {}", + status.as_u16() + )); + } + let mut bytes = Vec::new(); + response + .by_ref() + .take((MAX_REMOTE_RESPONSE_BYTES + 1) as u64) + .read_to_end(&mut bytes) + .map_err(|_| "Anthropic response read failed".to_owned())?; + if bytes.len() > MAX_REMOTE_RESPONSE_BYTES { + return Err("Anthropic response exceeded the PRP payload limit".to_owned()); + } + if bytes.is_empty() { + return Ok(json!({})); + } + return serde_json::from_slice(&bytes) + .map_err(|_| "Anthropic response was not valid JSON".to_owned()); + } + Err("Anthropic request retries exhausted".to_owned()) +} + +fn stream_remote_events( + client: Client, + origin: String, + session_id: String, + events: SyncSender, + stop: Arc, + ready: mpsc::Sender>, +) { + let mut ready = Some(ready); + while !stop.load(Ordering::Acquire) { + let response = client + .get(format!("{origin}/v1/sessions/{session_id}/events/stream?event_deltas%5B%5D=agent.message&event_deltas%5B%5D=agent.thinking&beta=true")) + .send(); + let Ok(response) = response else { + if let Some(ready) = ready.take() { + let _ = ready.send(Err("Anthropic event stream connection failed".to_owned())); + return; + } + return; + }; + if !response.status().is_success() { + if let Some(ready) = ready.take() { + let _ = ready.send(Err(format!( + "Anthropic event stream failed with HTTP {}", + response.status().as_u16() + ))); + return; + } + return; + } + if let Some(ready) = ready.take() { + let _ = ready.send(Ok(())); + } + let reader = BufReader::new(response); + let mut data = String::new(); + let mut reader = reader; + loop { + if stop.load(Ordering::Acquire) { + return; + } + let mut line = String::new(); + let read = match reader + .by_ref() + .take((MAX_REMOTE_EVENT_BYTES + 1) as u64) + .read_line(&mut line) + { + Ok(read) => read, + Err(_) => break, + }; + if read == 0 { + break; + } + if line.len() > MAX_REMOTE_EVENT_BYTES { + let _ = events.try_send(NetworkEvent::RecoverableFailure( + "Anthropic event exceeded the PRP payload limit".to_owned(), + )); + return; + } + let line = line.trim_end_matches(['\r', '\n']); + if line.is_empty() { + if !data.is_empty() { + if data.len() > MAX_REMOTE_EVENT_BYTES { + let _ = events.try_send(NetworkEvent::RecoverableFailure( + "Anthropic event exceeded the PRP payload limit".to_owned(), + )); + return; + } + match serde_json::from_str::(&data) { + Ok(value) => { + let _ = events.try_send(NetworkEvent::Remote(value)); + } + Err(_) => { + let _ = events.try_send(NetworkEvent::RecoverableFailure( + "Anthropic event stream emitted malformed JSON".to_owned(), + )); + return; + } + } + data.clear(); + } + } else if let Some(chunk) = line.strip_prefix("data:") { + if !data.is_empty() { + data.push('\n'); + } + data.push_str(chunk.trim_start()); + if data.len() > MAX_REMOTE_EVENT_BYTES { + let _ = events.try_send(NetworkEvent::RecoverableFailure( + "Anthropic event exceeded the PRP payload limit".to_owned(), + )); + return; + } + } + } + if !stop.load(Ordering::Acquire) { + let _ = events.try_send(NetworkEvent::RecoverableFailure( + "Anthropic event stream disconnected".to_owned(), + )); + return; + } + } +} + +pub struct ClaudeManagedProvider { + session_id: String, + worker: NetworkWorker, + remote_to_canonical: BTreeMap, + operation_input_schemas: BTreeMap, + pending_remote_calls: BTreeMap, + seen_event_fingerprints: BTreeMap, + durable_event_cursor: Option, + replay_queue: VecDeque, + normalized_queue: VecDeque, + interrupted_turn: Option, + current_turn_id: Option, + model_request_count: u64, + latest_usage_snapshot: Option, + current_budget_cents: u64, + reconnect_backoff: Duration, + config: ClaudeManagedProviderConfig, + system_instructions: String, + managed_skills: Vec, +} + +impl ClaudeManagedProvider { + pub fn start( + config: &ClaudeManagedProviderConfig, + tools: Vec, + ownership_scope: &str, + resume_session_id: Option<&str>, + resume_event_cursor: Option<&str>, + resume_model_request_count: u64, + resume_managed_skills: Option<&[ClaudeManagedSkillRef]>, + pending_cleanup_skills: Option<&[ClaudeManagedSkillRef]>, + ) -> Result { + validate_config(config).map_err(ClaudeManagedProviderStartError::new)?; + let api_key = std::env::var("ANTHROPIC_API_KEY").map_err(|_| { + ClaudeManagedProviderStartError::new(LocalRunnerError::invalid( + "ANTHROPIC_API_KEY is required for Claude Agent", + )) + })?; + if resume_session_id.is_some() && pending_cleanup_skills.is_some() { + return Err(ClaudeManagedProviderStartError::new( + LocalRunnerError::invalid( + "Claude managed recovery cannot overlap incomplete skill cleanup", + ), + )); + } + if let Some(skills) = pending_cleanup_skills.filter(|skills| !skills.is_empty()) { + if let Err(error) = delete_managed_skills_at(&api_key, ANTHROPIC_ORIGIN, true, skills) { + return Err(ClaudeManagedProviderStartError::fresh_failure( + LocalRunnerError::invalid(format!( + "failed to finish prior Claude managed skill cleanup: {error}" + )), + skills.to_vec(), + )); + } + } + let managed_skills = match (resume_session_id, resume_managed_skills) { + (Some(_), Some(skills)) => { + validate_managed_skill_refs(config, skills) + .map_err(ClaudeManagedProviderStartError::new)?; + skills.to_vec() + } + (Some(_), None) => { + return Err(ClaudeManagedProviderStartError::new( + LocalRunnerError::invalid( + "Claude managed recovery requires durable owned skill references", + ), + )) + } + (None, None) => upload_managed_runtime_skills(&api_key, config, ownership_scope)?, + (None, Some(skills)) => { + validate_managed_skill_refs(config, skills) + .map_err(ClaudeManagedProviderStartError::new)?; + skills.to_vec() + } + }; + validate_managed_skill_refs(config, &managed_skills) + .map_err(ClaudeManagedProviderStartError::new)?; + let fresh_skills = resume_session_id.is_none().then(|| managed_skills.clone()); + let result = Self::start_with_worker( + config, + tools, + ownership_scope, + resume_session_id, + resume_event_cursor, + resume_model_request_count, + managed_skills, + SensitiveApiKey(api_key.clone()), + NetworkWorker::start, + ); + match (result, fresh_skills) { + (Ok(provider), _) => { + std::env::remove_var("ANTHROPIC_API_KEY"); + Ok(provider) + } + (Err(error), None) => Err(ClaudeManagedProviderStartError::new(error.cause)), + (Err(error), Some(skills)) => Err(handle_fresh_bootstrap_failure( + &api_key, + &config.beta_version, + ANTHROPIC_ORIGIN, + true, + skills, + error, + )), + } + } + + fn start_with_worker( + config: &ClaudeManagedProviderConfig, + tools: Vec, + ownership_scope: &str, + resume_session_id: Option<&str>, + resume_event_cursor: Option<&str>, + resume_model_request_count: u64, + managed_skills: Vec, + api_key: SensitiveApiKey, + start_worker: F, + ) -> Result + where + F: FnOnce(SensitiveApiKey, &str) -> Result, + { + validate_config(config)?; + let system_instructions = managed_system_instructions(config)?; + let worker = start_worker(api_key, &config.beta_version)?; + let (tool_payload, reverse, operation_input_schemas) = encode_tools(&tools)?; + let agent_overrides = managed_agent_overrides( + config, + &system_instructions, + tool_payload.clone(), + &managed_skills, + ); + let mut replay_queue = VecDeque::new(); + let session_id = if let Some(session_id) = resume_session_id { + let session = worker.request( + Method::GET, + format!("/v1/sessions/{session_id}?beta=true"), + None, + )?; + verify_remote_session(config, &session)?; + worker.request( + Method::POST, + format!("/v1/sessions/{session_id}?beta=true"), + Some(json!({ "agent": managed_agent_update(tool_payload) })), + )?; + session_id.to_owned() + } else { + let version = Value::from(agent_version(config)?); + let ownership_marker = session_ownership_marker(ownership_scope); + let preexisting = reconcile_managed_session(&worker, config, &ownership_marker) + .map_err(ClaudeManagedBootstrapError::ambiguous_create)?; + if let Some(session_id) = preexisting { + session_id + } else { + // Session creation is not idempotent. A deterministic metadata + // marker and bounded list reconciliation recover an accepted + // request whose response is lost without creating a duplicate. + let created = worker.request_once( + Method::POST, + "/v1/sessions?beta=true".to_owned(), + Some(json!({ + "agent": { + "type": "agent_with_overrides", + "id": config.anthropic_agent_id, + "version": version, + "model": agent_overrides["model"].clone(), + "system": agent_overrides["system"].clone(), + "tools": agent_overrides["tools"].clone(), + "mcp_servers": agent_overrides["mcp_servers"].clone(), + "skills": agent_overrides["skills"].clone() + }, + "environment_id": config.environment_id, + "budget": { + "type": "limit", + "max_list_cost": { + "currency": "USD", + "amount": spend_cap_cents(config.max_session_list_cost_usd)?.to_string() + } + }, + "metadata": { + SESSION_OWNERSHIP_METADATA_KEY: ownership_marker + } + })), + ); + match created { + Ok(session) => { + match required_text( + &session, + "id", + "Anthropic session create omitted id", + ) { + Ok(session_id) => session_id.to_owned(), + Err(response_error) => { + match reconcile_managed_session( + &worker, + config, + &ownership_marker, + ) { + Ok(Some(session_id)) => session_id, + Ok(None) => { + return Err( + ClaudeManagedBootstrapError::ambiguous_create( + response_error, + ), + ) + } + Err(reconcile_error) => { + return Err( + ClaudeManagedBootstrapError::ambiguous_create( + LocalRunnerError::invalid(format!( + "{response_error}; session reconciliation failed: {reconcile_error}" + )), + ), + ) + } + } + } + } + } + Err(create_error) => match reconcile_managed_session( + &worker, + config, + &ownership_marker, + ) { + Ok(Some(session_id)) => session_id, + Ok(None) => { + return Err(ClaudeManagedBootstrapError::ambiguous_create( + create_error, + )) + } + Err(reconcile_error) => { + return Err(ClaudeManagedBootstrapError::ambiguous_create( + LocalRunnerError::invalid(format!( + "{create_error}; session reconciliation failed: {reconcile_error}" + )), + )) + } + }, + } + } + }; + worker + .open_stream(&session_id) + .map_err(|error| ClaudeManagedBootstrapError::after_session(error, &session_id))?; + // Streams never replay. Open first, then overlap a durable history read + // and deduplicate by event id so no recovery-gap event can be missed. + if resume_session_id.is_some() { + replay_queue.extend( + fetch_event_history(&worker, &session_id, resume_event_cursor).map_err( + |error| ClaudeManagedBootstrapError::after_session(error, &session_id), + )?, + ); + } + Ok(Self { + session_id, + worker, + remote_to_canonical: reverse, + operation_input_schemas, + pending_remote_calls: BTreeMap::new(), + seen_event_fingerprints: BTreeMap::new(), + durable_event_cursor: resume_event_cursor.map(str::to_owned), + replay_queue, + normalized_queue: VecDeque::new(), + interrupted_turn: None, + current_turn_id: None, + model_request_count: resume_model_request_count, + latest_usage_snapshot: None, + current_budget_cents: spend_cap_cents(config.max_session_list_cost_usd)?, + reconnect_backoff: Duration::from_millis(250), + config: config.clone(), + system_instructions, + managed_skills, + }) + } + + fn send_events(&self, events: Value) -> Result { + // Event POSTs are not blindly retried: a lost response is ambiguous. + // Custom-tool delivery reconciles requires_action below; user/interrupt + // ambiguity fails recoverably so durable history can be examined. + self.worker.request_once( + Method::POST, + format!("/v1/sessions/{}/events?beta=true", self.session_id), + Some(json!({ "events": events })), + ) + } + + fn normalize_remote_event( + &mut self, + value: Value, + ) -> Result, LocalRunnerError> { + // Managed Agents emits event objects directly. `event_start.event` is + // the descriptor for a provisional item, not an outer event envelope. + let event = &value; + if let Some(id) = event.get("id").and_then(Value::as_str) { + let fingerprint = format!( + "{:x}", + Sha256::digest(serde_json::to_vec(event).map_err( + |_| LocalRunnerError::invalid("Anthropic event could not be fingerprinted") + )?) + ); + if let Some(prior) = self.seen_event_fingerprints.get(id) { + if prior == &fingerprint { + return Ok(None); + } + return Err(LocalRunnerError::invalid( + "Anthropic reused a remote event ID with conflicting content", + )); + } + self.seen_event_fingerprints + .insert(id.to_owned(), fingerprint); + self.durable_event_cursor = Some(id.to_owned()); + } + let event_type = event + .get("type") + .and_then(Value::as_str) + .unwrap_or_default(); + match event_type { + "event_start" => { + let preview = event.get("event").unwrap_or(&Value::Null); + let preview_type = preview + .get("type") + .and_then(Value::as_str) + .unwrap_or_default(); + let preview_id = preview + .get("id") + .and_then(Value::as_str) + .unwrap_or("remote-preview"); + match preview_type { + "agent.message" => Ok(Some(ProviderEvent::Notification { + method: "item/started".to_owned(), + params: json!({ "turnId": self.current_turn_id, "item": { "id": preview_id, "type": "agentMessage", "provisional": true } }), + })), + "agent.thinking" => Ok(Some(ProviderEvent::Notification { + method: "item/started".to_owned(), + params: json!({ "turnId": self.current_turn_id, "item": { "id": preview_id, "type": "progress", "phase": "thinking" } }), + })), + _ => Ok(None), + } + } + "event_delta" => Ok(Some(ProviderEvent::Notification { + method: "item/agentMessage/delta".to_owned(), + params: json!({ + "turnId": self.current_turn_id, + "itemId": event.get("event_id"), + "delta": event.pointer("/delta/content/text").and_then(Value::as_str).unwrap_or_default(), + "provisional": true, + }), + })), + "agent.custom_tool_use" => { + let call_id = required_text(event, "id", "Anthropic custom tool event omitted id")? + .to_owned(); + let remote_name = event + .get("name") + .or_else(|| event.get("tool_name")) + .and_then(Value::as_str) + .ok_or_else(|| { + LocalRunnerError::invalid("Anthropic custom tool event omitted name") + })?; + let operation_id = self + .remote_to_canonical + .get(remote_name) + .ok_or_else(|| { + LocalRunnerError::invalid("Anthropic requested an unauthorized custom tool") + })? + .clone(); + let input = event.get("input").cloned().unwrap_or_else(|| json!({})); + let schema = self + .operation_input_schemas + .get(&operation_id) + .ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic requested a tool without a durable input schema", + ) + })?; + let validator = jsonschema::validator_for(schema).map_err(|_| { + LocalRunnerError::invalid( + "authorized Paperclip tool has an invalid JSON Schema", + ) + })?; + if !validator.is_valid(&input) { + return Err(LocalRunnerError::invalid( + "Anthropic custom tool arguments failed schema validation", + )); + } + self.pending_remote_calls + .insert(call_id.clone(), operation_id.clone()); + Ok(Some(ProviderEvent::ToolCall { + call_id, + operation_id, + input, + })) + } + "agent.message" => Ok(Some(ProviderEvent::Notification { + method: "item/completed".to_owned(), + params: json!({ "turnId": self.current_turn_id, "item": { "id": event.get("id"), "type": "agentMessage", "text": extract_text(event), "authoritative": true }, "remoteEventId": event.get("id") }), + })), + "agent.thinking" => Ok(Some(ProviderEvent::Notification { + method: "item/started".to_owned(), + params: json!({ "turnId": self.current_turn_id, "item": { "id": event.get("id"), "type": "progress", "phase": "thinking" }, "remoteEventId": event.get("id") }), + })), + "span.model_request_end" => { + self.model_request_count = self.model_request_count.saturating_add(1); + Ok(None) + } + "session.usage" => { + let mut params = event.clone(); + if let Some(usage) = params.get_mut("usage").and_then(Value::as_object_mut) { + usage.insert( + "requestCount".to_owned(), + Value::from(self.model_request_count), + ); + self.latest_usage_snapshot = Some(Value::Object(usage.clone())); + } + Ok(Some(ProviderEvent::Notification { + method: "thread/tokenUsage/updated".to_owned(), + params, + })) + } + "session.status_idle" | "session.status" => { + let status = event + .get("status") + .and_then(Value::as_str) + .unwrap_or("idle"); + let stop_reason = event.get("stop_reason").or_else(|| event.get("stopReason")); + let reason = stop_reason + .and_then(|value| { + value + .get("type") + .and_then(Value::as_str) + .or_else(|| value.as_str()) + }) + .unwrap_or_default(); + if status == "terminated" { + return Err(LocalRunnerError::invalid( + "Anthropic managed session terminated", + )); + } + match reason { + "requires_action" => Ok(Some(ProviderEvent::Notification { + method: "provider/waitingForToolResult".to_owned(), + params: json!({ + "turnId": self.current_turn_id, + "pendingRemoteEventIds": stop_reason.and_then(|value| value.get("event_ids")).cloned().unwrap_or_else(|| json!([])), + }), + })), + "end_turn" => { + let usage = self.fetch_final_usage()?; + let turn_id = self.current_turn_id.take(); + let interrupted = self.interrupted_turn.take().is_some(); + let terminal = ProviderEvent::Notification { + method: "turn/completed".to_owned(), + params: json!({ "turnId": turn_id, "turn": { "id": turn_id, "status": if interrupted { "interrupted" } else { "completed" } }, "remoteEventId": event.get("id") }), + }; + self.normalized_queue.push_back(terminal); + Ok(Some(ProviderEvent::Notification { + method: "thread/tokenUsage/updated".to_owned(), + params: usage, + })) + } + "retries_exhausted" => { + let turn_id = self.current_turn_id.take(); + Ok(Some(ProviderEvent::Notification { + method: "turn/completed".to_owned(), + params: json!({ + "turnId": turn_id, + "turn": { "id": turn_id, "status": "failed" }, + "error": { "code": "provider_retries_exhausted", "retryable": true }, + "remoteEventId": event.get("id"), + }), + })) + } + "budget_reached" => Ok(Some(ProviderEvent::Notification { + method: "provider/budgetReached".to_owned(), + params: json!({ + "turnId": self.current_turn_id, + "status": "budget_reached", + "stopReason": "provider_quota", + "remoteEventId": event.get("id"), + }), + })), + "" => Ok(None), + _ => Err(LocalRunnerError::invalid( + "Anthropic managed session returned an unknown stop reason", + )), + } + } + "session.status_running" => Ok(None), + "session.status_rescheduled" => Ok(Some(ProviderEvent::Notification { + method: "provider/reconnecting".to_owned(), + params: json!({ "service": "anthropic_managed_agents", "reason": "remote_rescheduled" }), + })), + "session.status_terminated" => Err(LocalRunnerError::invalid( + "Anthropic managed session terminated and cannot be resumed", + )), + "session.error" => Err(LocalRunnerError::invalid( + "Anthropic managed session reported an error", + )), + _ => Ok(None), + } + } +} + +impl Provider for ClaudeManagedProvider { + fn kind(&self) -> ProviderKind { + ProviderKind::ClaudeManaged + } + + fn runtime_identity(&self) -> ProviderRuntimeIdentity { + ProviderRuntimeIdentity::RemoteService { + service: "anthropic_managed_agents".to_owned(), + provider_session_id: self.session_id.clone(), + process_id: None, + } + } + + fn session_identity(&self) -> &str { + &self.session_id + } + fn provider_session_id(&self) -> Option<&str> { + Some(&self.session_id) + } + fn durable_event_cursor(&self) -> Option<&str> { + self.durable_event_cursor.as_deref() + } + + fn model_request_count(&self) -> Option { + Some(self.model_request_count) + } + + fn claude_managed_skills(&self) -> Option<&[ClaudeManagedSkillRef]> { + Some(&self.managed_skills) + } + + fn restore_active_turn(&mut self, turn_id: &str) -> Result<(), LocalRunnerError> { + match self.current_turn_id.as_deref() { + None => self.current_turn_id = Some(turn_id.to_owned()), + Some(current) if current == turn_id => {} + Some(_) => { + return Err(LocalRunnerError::invalid( + "Anthropic active turn does not match durable recovery state", + )) + } + } + Ok(()) + } + + fn restore_pending_tool_call( + &mut self, + call_id: &str, + operation_id: &str, + _input: &Value, + ) -> Result<(), LocalRunnerError> { + match self.pending_remote_calls.get(call_id) { + Some(current) if current != operation_id => Err(LocalRunnerError::invalid( + "Anthropic pending tool call conflicts with durable recovery state", + )), + _ => { + self.pending_remote_calls + .insert(call_id.to_owned(), operation_id.to_owned()); + Ok(()) + } + } + } + + fn configure_tools(&mut self, tools: Vec) -> Result<(), LocalRunnerError> { + let (payload, reverse, operation_input_schemas) = encode_tools(&tools)?; + self.worker.request( + Method::POST, + format!("/v1/sessions/{}?beta=true", self.session_id), + Some(json!({ "agent": managed_agent_update(payload) })), + )?; + self.remote_to_canonical = reverse; + self.operation_input_schemas = operation_input_schemas; + Ok(()) + } + + fn increase_budget(&mut self, max_list_cost_usd: f64) -> Result { + let cents = spend_cap_cents(max_list_cost_usd)?; + if cents <= self.current_budget_cents { + return Err(LocalRunnerError::invalid( + "Claude Agent spend ceiling may only be raised monotonically", + )); + } + let response = self.worker.request( + Method::POST, + format!("/v1/sessions/{}?beta=true", self.session_id), + Some(json!({ + "budget": { + "type": "limit", + "max_list_cost": { "currency": "USD", "amount": cents.to_string() } + } + })), + )?; + self.current_budget_cents = cents; + Ok(response) + } + + fn destroy_session(&mut self) -> Result<(), LocalRunnerError> { + self.worker.request( + Method::DELETE, + format!("/v1/sessions/{}?beta=true", self.session_id), + None, + )?; + for skill in &self.managed_skills { + self.worker.request( + Method::DELETE, + format!("/v1/skills/{}", percent_encode_query(&skill.skill_id)), + None, + )?; + } + self.worker.stop(); + Ok(()) + } + + fn start_turn( + &mut self, + message: &str, + _cwd: &str, + turn_id: &str, + ) -> Result { + let response = self.send_events( + json!([{ "type": "user.message", "content": [{ "type": "text", "text": message }] }]), + )?; + self.current_turn_id = Some(turn_id.to_owned()); + self.normalized_queue.push_back(ProviderEvent::Notification { + method: "turn/started".to_owned(), + params: json!({ "turnId": turn_id, "turn": { "id": turn_id, "status": "inProgress" } }), + }); + Ok(response) + } + + fn interrupt_turn(&mut self, turn_id: &str) -> Result { + self.interrupted_turn = Some(turn_id.to_owned()); + self.send_events(json!([{ "type": "user.interrupt" }])) + } + + fn read(&mut self) -> Result { + let session = self.worker.request( + Method::GET, + format!("/v1/sessions/{}?beta=true", self.session_id), + None, + )?; + let events = self.worker.request( + Method::GET, + format!("/v1/sessions/{}/events?beta=true", self.session_id), + None, + )?; + Ok(json!({ "session": session, "events": events })) + } + + fn poll(&mut self) -> Result, LocalRunnerError> { + if let Some(event) = self.normalized_queue.pop_front() { + return Ok(Some(event)); + } + if let Some(event) = self.replay_queue.pop_front() { + return self.normalize_remote_event(event); + } + match self.worker.try_event() { + Some(NetworkEvent::Remote(value)) => { + self.reconnect_backoff = Duration::from_millis(250); + self.normalize_remote_event(value) + } + Some(NetworkEvent::RecoverableFailure(detail)) => { + if let Ok(history) = fetch_event_history( + &self.worker, + &self.session_id, + self.durable_event_cursor.as_deref(), + ) { + self.replay_queue.extend(history); + } + // Durable history is authoritative. Reconcile it before opening + // a fresh preview stream so the reconnect gap cannot reorder + // provisional content ahead of persisted events. + thread::sleep(self.reconnect_backoff); + self.worker.open_stream(&self.session_id)?; + self.reconnect_backoff = (self.reconnect_backoff * 2).min(Duration::from_secs(8)); + Ok(Some(ProviderEvent::Notification { + method: "provider/reconnecting".to_owned(), + params: json!({ "service": "anthropic_managed_agents", "detail": detail }), + })) + } + None => Ok(None), + } + } + + fn deliver_tool_result(&mut self, result: &ToolResult) -> Result<(), LocalRunnerError> { + match self.pending_remote_calls.get(&result.call_id) { + Some(expected) if expected != &result.operation_id => { + return Err(LocalRunnerError::invalid( + "Claude Agent tool result operation does not match pending tool use", + )); + } + None => { + // A duplicate PRP result may arrive after runner recovery. + // Managed Agents does not retain custom-tool-result events in + // normal history, so the session's requires_action IDs are the + // authoritative delivery receipt. + if !self.remote_call_still_pending(&result.call_id)? { + return Ok(()); + } + } + Some(_) => {} + } + let event = json!({ + "type": "user.custom_tool_result", + "custom_tool_use_id": result.call_id, + "content": [{ "type": "text", "text": serde_json::to_string(&result.result).unwrap_or_else(|_| "null".to_owned()) }], + "is_error": result.is_error + }); + let event_path = format!("/v1/sessions/{}/events?beta=true", self.session_id); + let body = json!({ "events": [event.clone()] }); + for attempt in 0..2 { + if self + .worker + .request_once(Method::POST, event_path.clone(), Some(body.clone())) + .is_ok() + { + self.pending_remote_calls.remove(&result.call_id); + return Ok(()); + } + if !self.remote_call_still_pending(&result.call_id)? { + self.pending_remote_calls.remove(&result.call_id); + return Ok(()); + } + if attempt == 1 { + return Err(LocalRunnerError::invalid( + "Anthropic custom tool result delivery remains ambiguous", + )); + } + } + unreachable!("bounded tool-result delivery loop always returns") + } + + fn shutdown(&mut self) -> Result<(), LocalRunnerError> { + self.worker.stop(); + Ok(()) + } +} + +impl ClaudeManagedProvider { + fn fetch_final_usage(&mut self) -> Result { + let session = self.worker.request( + Method::GET, + format!("/v1/sessions/{}?beta=true", self.session_id), + None, + )?; + let mut usage = session + .get("usage") + .and_then(Value::as_object) + .cloned() + .ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic session terminal state omitted cumulative usage", + ) + })?; + + for field in ["input_tokens", "output_tokens", "cache_read_input_tokens"] { + if usage.get(field).and_then(Value::as_u64).is_none() { + return Err(LocalRunnerError::invalid(format!( + "Anthropic session cumulative usage omitted valid {field}" + ))); + } + } + let active_seconds = usage + .get("active_seconds") + .and_then(Value::as_f64) + .filter(|value| value.is_finite() && *value >= 0.0) + .ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic session cumulative usage omitted valid active_seconds", + ) + })?; + let cost_currency = usage + .get("list_cost") + .and_then(|value| value.get("currency")) + .and_then(Value::as_str); + let cost_amount = usage + .get("list_cost") + .and_then(|value| value.get("amount")) + .and_then(Value::as_str) + .and_then(|value| value.parse::().ok()) + .filter(|value| value.is_finite() && *value >= 0.0); + if cost_currency != Some("USD") || cost_amount.is_none() { + return Err(LocalRunnerError::invalid( + "Anthropic session cumulative usage omitted valid USD list cost", + )); + } + + if let Some(previous) = self.latest_usage_snapshot.as_ref() { + for field in ["input_tokens", "output_tokens", "cache_read_input_tokens"] { + if let Some(previous_value) = previous.get(field).and_then(Value::as_u64) { + let final_value = usage.get(field).and_then(Value::as_u64).unwrap_or_default(); + if final_value < previous_value { + return Err(LocalRunnerError::invalid( + "Anthropic final cumulative usage regressed from its streamed snapshot", + )); + } + } + } + if let Some(previous_active_seconds) = previous + .get("active_seconds") + .and_then(Value::as_f64) + .filter(|value| value.is_finite() && *value >= 0.0) + { + if active_seconds < previous_active_seconds { + return Err(LocalRunnerError::invalid( + "Anthropic final cumulative usage regressed from its streamed snapshot", + )); + } + } + if let Some(previous_cost) = previous + .get("list_cost") + .and_then(|value| value.get("amount")) + .and_then(Value::as_str) + .and_then(|value| value.parse::().ok()) + .filter(|value| value.is_finite() && *value >= 0.0) + { + if cost_amount.unwrap_or_default() < previous_cost { + return Err(LocalRunnerError::invalid( + "Anthropic final cumulative usage regressed from its streamed snapshot", + )); + } + } + } + + usage.insert( + "requestCount".to_owned(), + Value::from(self.model_request_count), + ); + self.latest_usage_snapshot = Some(Value::Object(usage.clone())); + Ok(json!({ + "type": "session.usage", + "session_id": self.session_id, + "usage": usage, + })) + } + + fn remote_call_still_pending(&self, call_id: &str) -> Result { + let events = fetch_event_history(&self.worker, &self.session_id, None)?; + let mut saw_pending_receipt = false; + let mut still_pending = None; + + for event in events { + match event.get("type").and_then(Value::as_str) { + Some("session.status_idle") => { + let stop_reason = event.get("stop_reason").ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic durable idle status omitted its stop reason", + ) + })?; + match stop_reason.get("type").and_then(Value::as_str) { + Some("requires_action") => { + let ids = stop_reason + .get("event_ids") + .and_then(Value::as_array) + .ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic durable requires_action status omitted event IDs", + ) + })?; + if !ids.iter().all(|id| id.as_str().is_some()) { + return Err(LocalRunnerError::invalid( + "Anthropic durable requires_action status contained an invalid event ID", + )); + } + if ids.iter().any(|id| id.as_str() == Some(call_id)) { + saw_pending_receipt = true; + still_pending = Some(true); + } else if saw_pending_receipt { + // A later, authoritative pending set that no longer + // contains this call proves that its result arrived. + still_pending = Some(false); + } + } + Some("end_turn") if saw_pending_receipt => still_pending = Some(false), + Some("end_turn") => {} + Some(_) | None if saw_pending_receipt => { + return Err(LocalRunnerError::invalid( + "Anthropic custom tool result delivery could not be reconciled from durable status", + )); + } + Some(_) | None => {} + } + } + Some("session.status_running") if saw_pending_receipt => { + still_pending = Some(false); + } + _ => {} + } + } + + still_pending.ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic custom tool result delivery could not be proven from durable event history", + ) + }) + } +} + +fn validate_config(config: &ClaudeManagedProviderConfig) -> Result<(), LocalRunnerError> { + if config.beta_version != QUALIFIED_BETA { + return Err(LocalRunnerError::invalid( + "unsupported Anthropic Managed Agents beta version", + )); + } + if [ + config.model.as_str(), + config.profile_id.as_str(), + config.anthropic_agent_id.as_str(), + config.agent_version.as_str(), + config.environment_id.as_str(), + config.instructions.as_str(), + ] + .iter() + .any(|value| value.trim().is_empty()) + { + return Err(LocalRunnerError::invalid( + "Claude Agent profile fields must be non-empty", + )); + } + if !config.max_session_list_cost_usd.is_finite() || config.max_session_list_cost_usd <= 0.0 { + return Err(LocalRunnerError::invalid( + "Claude Agent spend ceiling must be positive", + )); + } + agent_version(config)?; + spend_cap_cents(config.max_session_list_cost_usd)?; + Ok(()) +} + +fn agent_version(config: &ClaudeManagedProviderConfig) -> Result { + let version = config.agent_version.parse::().map_err(|_| { + LocalRunnerError::invalid( + "Claude Agent version must be a canonical positive 32-bit integer", + ) + })?; + if version == 0 || version > i32::MAX as u32 || version.to_string() != config.agent_version { + return Err(LocalRunnerError::invalid( + "Claude Agent version must be a canonical positive 32-bit integer", + )); + } + Ok(version) +} + +fn spend_cap_cents(value: f64) -> Result { + let cents = value * 100.0; + if !cents.is_finite() + || cents < 1.0 + || cents > u64::MAX as f64 + || (cents - cents.round()).abs() > 0.000_001 + { + return Err(LocalRunnerError::invalid( + "Claude Agent spend ceiling must be expressible as whole US cents", + )); + } + Ok(cents.round() as u64) +} + +fn fetch_event_history( + worker: &NetworkWorker, + session_id: &str, + after_event_id: Option<&str>, +) -> Result, LocalRunnerError> { + let mut page: Option = None; + let mut events = Vec::new(); + for _ in 0..MAX_HISTORY_PAGES { + let page_query = page + .as_ref() + .map(|value| format!("&page={}", percent_encode_query(value))) + .unwrap_or_default(); + let response = worker.request( + Method::GET, + format!("/v1/sessions/{session_id}/events?beta=true&limit=100{page_query}"), + None, + )?; + events.extend(extract_event_list(response.clone())); + if events.len() > MAX_HISTORY_EVENTS { + return Err(LocalRunnerError::invalid( + "Anthropic event history exceeded the recovery bound", + )); + } + page = response + .get("next_page") + .and_then(Value::as_str) + .map(str::to_owned); + if page.is_none() { + break; + } + } + if page.is_some() { + return Err(LocalRunnerError::invalid( + "Anthropic event history pagination exceeded the recovery bound", + )); + } + if let Some(cursor) = after_event_id { + let position = events + .iter() + .position(|value| value.get("id").and_then(Value::as_str) == Some(cursor)) + .ok_or_else(|| { + LocalRunnerError::invalid( + "Anthropic recovery cursor was not present in durable event history", + ) + })?; + Ok(events.into_iter().skip(position + 1).collect()) + } else { + Ok(events) + } +} + +fn percent_encode_query(value: &str) -> String { + value + .bytes() + .flat_map(|byte| { + if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b'~') { + vec![byte as char] + } else { + format!("%{byte:02X}").chars().collect() + } + }) + .collect() +} + +fn encode_tools( + tools: &[AuthorizedTool], +) -> Result< + ( + Vec, + BTreeMap, + BTreeMap, + ), + LocalRunnerError, +> { + if tools.len() > MAX_REMOTE_TOOLS { + return Err(LocalRunnerError::invalid( + "Anthropic custom-tool catalog exceeds 128 tools", + )); + } + let mut reverse = BTreeMap::new(); + let mut operation_input_schemas = BTreeMap::new(); + let payload = tools + .iter() + .map(|tool| { + jsonschema::validator_for(&tool.input_schema).map_err(|_| { + LocalRunnerError::invalid(format!( + "Paperclip tool {} has an invalid JSON Schema", + tool.operation_id + )) + })?; + let remote_name = remote_tool_name(&tool.operation_id); + if reverse + .insert(remote_name.clone(), tool.operation_id.clone()) + .is_some() + { + return Err(LocalRunnerError::invalid( + "Anthropic custom-tool name collision", + )); + } + operation_input_schemas.insert(tool.operation_id.clone(), tool.input_schema.clone()); + Ok(json!({ + "type": "custom", + "name": remote_name, + "description": tool.description, + "input_schema": tool.input_schema + })) + }) + .collect::, _>>()?; + Ok((payload, reverse, operation_input_schemas)) +} + +fn remote_tool_name(operation_id: &str) -> String { + let slug = operation_id + .chars() + .map(|character| { + if character.is_ascii_alphanumeric() || matches!(character, '_' | '-') { + character + } else { + '_' + } + }) + .take(96) + .collect::(); + let digest = format!("{:x}", Sha256::digest(operation_id.as_bytes())); + format!("pc_{slug}_{}", &digest[..16]) +} + +fn required_text<'a>( + value: &'a Value, + field: &str, + message: &str, +) -> Result<&'a str, LocalRunnerError> { + value + .get(field) + .and_then(Value::as_str) + .filter(|text| !text.is_empty()) + .ok_or_else(|| LocalRunnerError::invalid(message)) +} + +fn verify_remote_session( + config: &ClaudeManagedProviderConfig, + session: &Value, +) -> Result<(), LocalRunnerError> { + let expected_budget = spend_cap_cents(config.max_session_list_cost_usd)?.to_string(); + let actual_budget = session + .pointer("/budget/max_list_cost/amount") + .and_then(|value| { + value + .as_str() + .map(str::to_owned) + .or_else(|| value.as_u64().map(|number| number.to_string())) + }); + if session.get("environment_id").and_then(Value::as_str) != Some(config.environment_id.as_str()) + || session + .pointer("/agent/model/id") + .and_then(Value::as_str) + .or_else(|| session.pointer("/agent/model").and_then(Value::as_str)) + != Some(config.model.as_str()) + || session.pointer("/agent/id").and_then(Value::as_str) + != Some(config.anthropic_agent_id.as_str()) + || session + .pointer("/agent/version") + .and_then(|value| { + value + .as_u64() + .map(|number| number.to_string()) + .or_else(|| value.as_str().map(str::to_owned)) + }) + .as_deref() + != Some(config.agent_version.as_str()) + || actual_budget.as_deref() != Some(expected_budget.as_str()) + { + return Err(LocalRunnerError::invalid( + "Anthropic session identity does not match its immutable Paperclip profile", + )); + } + Ok(()) +} + +fn session_ownership_marker(ownership_scope: &str) -> String { + format!("paperclip-{:x}", Sha256::digest(ownership_scope.as_bytes())) +} + +fn session_matches_ownership( + session: &Value, + config: &ClaudeManagedProviderConfig, + ownership_marker: &str, +) -> bool { + session + .pointer(&format!("/metadata/{SESSION_OWNERSHIP_METADATA_KEY}")) + .and_then(Value::as_str) + == Some(ownership_marker) + && session.get("environment_id").and_then(Value::as_str) + == Some(config.environment_id.as_str()) + && session.pointer("/agent/id").and_then(Value::as_str) + == Some(config.anthropic_agent_id.as_str()) + && session + .pointer("/agent/version") + .and_then(|value| { + value + .as_u64() + .map(|number| number.to_string()) + .or_else(|| value.as_str().map(str::to_owned)) + }) + .as_deref() + == Some(config.agent_version.as_str()) +} + +fn reconcile_managed_session( + worker: &NetworkWorker, + config: &ClaudeManagedProviderConfig, + ownership_marker: &str, +) -> Result, LocalRunnerError> { + let version = agent_version(config)?; + let mut page = None::; + let mut matches = Vec::new(); + for _ in 0..MAX_HISTORY_PAGES { + let mut path = format!( + "/v1/sessions?beta=true&agent_id={}&agent_version={version}&include_archived=true&limit=100", + percent_encode_query(&config.anthropic_agent_id) + ); + if let Some(page) = page.as_deref() { + path.push_str("&page="); + path.push_str(&percent_encode_query(page)); + } + let value = worker.request(Method::GET, path, None)?; + let data = value.get("data").and_then(Value::as_array).ok_or_else(|| { + LocalRunnerError::invalid("Anthropic session list response omitted data") + })?; + for session in data { + if session_matches_ownership(session, config, ownership_marker) { + verify_remote_session(config, session)?; + let session_id = + required_text(session, "id", "Anthropic reconciled session omitted id")?; + if !valid_managed_skill_id(session_id) { + return Err(LocalRunnerError::invalid( + "Anthropic reconciled session returned an invalid id", + )); + } + matches.push(session_id.to_owned()); + } + } + page = value + .get("next_page") + .and_then(Value::as_str) + .filter(|page| !page.is_empty()) + .map(str::to_owned); + if page.is_none() { + break; + } + } + if page.is_some() { + return Err(LocalRunnerError::invalid( + "Anthropic session reconciliation exceeded the bounded page limit", + )); + } + matches.sort(); + matches.dedup(); + match matches.as_slice() { + [] => Ok(None), + [session_id] => Ok(Some(session_id.clone())), + duplicates => { + for session_id in duplicates { + worker.request( + Method::DELETE, + format!( + "/v1/sessions/{}?beta=true", + percent_encode_query(session_id) + ), + None, + )?; + } + Err(LocalRunnerError::invalid( + "Anthropic session reconciliation removed duplicate Paperclip-owned sessions", + )) + } + } +} + +fn extract_text(event: &Value) -> String { + if let Some(text) = event.get("text").and_then(Value::as_str) { + return text.to_owned(); + } + event + .get("content") + .and_then(Value::as_array) + .map(|items| { + items + .iter() + .filter_map(|item| item.get("text").and_then(Value::as_str)) + .collect::>() + .join("") + }) + .unwrap_or_default() +} + +fn extract_event_list(value: Value) -> Vec { + value + .get("data") + .or_else(|| value.get("events")) + .and_then(Value::as_array) + .cloned() + .unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{ErrorKind, Read, Write}; + use std::net::{TcpListener, TcpStream}; + use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering}; + use std::sync::Mutex; + use std::time::Instant; + + #[derive(Clone, Debug)] + struct CapturedRequest { + method: String, + path: String, + headers: String, + body: String, + } + + struct FakeAnthropicService { + origin: String, + requests: Arc>>, + stop: Arc, + join: Option>, + } + + struct ScriptedAnthropicService { + origin: String, + requests: Arc>>, + stop: Arc, + join: Option>, + } + + impl ScriptedAnthropicService { + fn start(handler: F) -> Self + where + F: Fn(&CapturedRequest) -> (String, Value) + Send + Sync + 'static, + { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + listener.set_nonblocking(true).unwrap(); + let origin = format!("http://{}", listener.local_addr().unwrap()); + let requests = Arc::new(Mutex::new(Vec::new())); + let captured = Arc::clone(&requests); + let stop = Arc::new(AtomicBool::new(false)); + let server_stop = Arc::clone(&stop); + let handler = Arc::new(handler); + let join = thread::spawn(move || { + while !server_stop.load(Ordering::Acquire) { + match listener.accept() { + Ok((mut socket, _)) => { + let Ok(request) = read_http_request(&mut socket) else { + continue; + }; + captured.lock().unwrap().push(request.clone()); + let (status, body) = handler(&request); + send_json_response(&mut socket, &status, &body); + } + Err(error) if error.kind() == ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(2)); + } + Err(_) => break, + } + } + }); + Self { + origin, + requests, + stop, + join: Some(join), + } + } + + fn captured(&self) -> Vec { + self.requests.lock().unwrap().clone() + } + } + + impl Drop for ScriptedAnthropicService { + fn drop(&mut self) { + self.stop.store(true, Ordering::Release); + if let Some(join) = self.join.take() { + let _ = join.join(); + } + } + } + + impl FakeAnthropicService { + fn start(stream_events: Vec, history_events: Vec) -> Self { + Self::start_with_options( + stream_events, + history_events, + 0, + Some(fake_cumulative_usage()), + ) + } + + fn start_with_lost_tool_result_responses( + stream_events: Vec, + history_events: Vec, + lost_tool_result_responses: usize, + ) -> Self { + Self::start_with_options( + stream_events, + history_events, + lost_tool_result_responses, + Some(fake_cumulative_usage()), + ) + } + + fn start_with_session_usage( + stream_events: Vec, + history_events: Vec, + session_usage: Option, + ) -> Self { + Self::start_with_options(stream_events, history_events, 0, session_usage) + } + + fn start_with_options( + stream_events: Vec, + history_events: Vec, + lost_tool_result_responses: usize, + session_usage: Option, + ) -> Self { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + listener.set_nonblocking(true).unwrap(); + let origin = format!("http://{}", listener.local_addr().unwrap()); + let requests = Arc::new(Mutex::new(Vec::new())); + let captured = Arc::clone(&requests); + let stop = Arc::new(AtomicBool::new(false)); + let server_stop = Arc::clone(&stop); + let stream_count = Arc::new(AtomicUsize::new(0)); + let skill_count = Arc::new(AtomicUsize::new(0)); + let lost_tool_result_responses = Arc::new(AtomicUsize::new(lost_tool_result_responses)); + let join = thread::spawn(move || { + while !server_stop.load(Ordering::Acquire) { + match listener.accept() { + Ok((mut socket, _)) => { + let Ok(request) = read_http_request(&mut socket) else { + continue; + }; + captured.lock().unwrap().push(request.clone()); + respond_to_fake_request( + &mut socket, + &request, + &stream_events, + &history_events, + &stream_count, + &skill_count, + &lost_tool_result_responses, + session_usage.as_ref(), + ); + } + Err(error) if error.kind() == ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(2)); + } + Err(_) => break, + } + } + }); + Self { + origin, + requests, + stop, + join: Some(join), + } + } + + fn captured(&self) -> Vec { + self.requests.lock().unwrap().clone() + } + } + + impl Drop for FakeAnthropicService { + fn drop(&mut self) { + self.stop.store(true, Ordering::Release); + if let Some(join) = self.join.take() { + let _ = join.join(); + } + } + } + + fn read_http_request(socket: &mut TcpStream) -> Result { + socket.set_read_timeout(Some(Duration::from_secs(2)))?; + let mut bytes = Vec::new(); + let mut buffer = [0_u8; 4096]; + let header_end = loop { + let read = socket.read(&mut buffer)?; + if read == 0 { + return Err(std::io::Error::new( + ErrorKind::UnexpectedEof, + "request ended before headers", + )); + } + bytes.extend_from_slice(&buffer[..read]); + if let Some(position) = bytes.windows(4).position(|window| window == b"\r\n\r\n") { + break position + 4; + } + if bytes.len() > MAX_REMOTE_RESPONSE_BYTES { + return Err(std::io::Error::new( + ErrorKind::InvalidData, + "test request too large", + )); + } + }; + let headers = String::from_utf8_lossy(&bytes[..header_end]).into_owned(); + let content_length = headers + .lines() + .find_map(|line| { + let (name, value) = line.split_once(':')?; + name.eq_ignore_ascii_case("content-length") + .then(|| value.trim().parse::().ok()) + .flatten() + }) + .unwrap_or(0); + while bytes.len() < header_end + content_length { + let read = socket.read(&mut buffer)?; + if read == 0 { + break; + } + bytes.extend_from_slice(&buffer[..read]); + } + let request_line = headers.lines().next().unwrap_or_default(); + let mut request_parts = request_line.split_whitespace(); + Ok(CapturedRequest { + method: request_parts.next().unwrap_or_default().to_owned(), + path: request_parts.next().unwrap_or_default().to_owned(), + headers, + body: String::from_utf8_lossy( + &bytes[header_end..bytes.len().min(header_end + content_length)], + ) + .into_owned(), + }) + } + + fn send_json_response(socket: &mut TcpStream, status: &str, value: &Value) { + let body = serde_json::to_string(value).unwrap(); + let _ = write!(socket, "HTTP/1.1 {status}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len()); + let _ = socket.flush(); + } + + fn respond_to_fake_request( + socket: &mut TcpStream, + request: &CapturedRequest, + stream_events: &[Value], + history_events: &[Value], + stream_count: &AtomicUsize, + skill_count: &AtomicUsize, + lost_tool_result_responses: &AtomicUsize, + session_usage: Option<&Value>, + ) { + if request.method == "GET" && request.path.starts_with("/v1/skills?") { + return send_json_response(socket, "200 OK", &json!({ "data": [] })); + } + if request.method == "POST" && request.path == "/v1/skills" { + let sequence = skill_count.fetch_add(1, AtomicOrdering::SeqCst) + 1; + return send_json_response( + socket, + "200 OK", + &json!({ + "id": format!("skill_test_{sequence}"), + "latest_version_id": format!("skver_test_{sequence}") + }), + ); + } + if request.method == "POST" && request.path.starts_with("/v1/sessions?") { + let body = serde_json::from_str::(&request.body).unwrap_or(Value::Null); + let Some(agent) = body.get("agent").and_then(Value::as_object) else { + return send_json_response( + socket, + "400 Bad Request", + &json!({ "type": "invalid_request_error" }), + ); + }; + let mut keys = agent.keys().map(String::as_str).collect::>(); + keys.sort_unstable(); + if keys + != [ + "id", + "mcp_servers", + "model", + "skills", + "system", + "tools", + "type", + "version", + ] + || agent.get("mcp_servers") != Some(&json!([])) + || body + .pointer(&format!("/metadata/{SESSION_OWNERSHIP_METADATA_KEY}")) + .and_then(Value::as_str) + .is_none_or(|marker| !marker.starts_with("paperclip-")) + { + return send_json_response( + socket, + "400 Bad Request", + &json!({ "type": "invalid_request_error" }), + ); + } + return send_json_response(socket, "200 OK", &json!({ "id": "session_test" })); + } + if request.method == "GET" && request.path.starts_with("/v1/sessions?") { + return send_json_response(socket, "200 OK", &json!({ "data": [], "next_page": null })); + } + if request.method == "GET" && request.path.contains("/events/stream") { + if stream_count.fetch_add(1, AtomicOrdering::SeqCst) > 0 { + return send_json_response( + socket, + "503 Service Unavailable", + &json!({ "type": "error" }), + ); + } + let body = stream_events + .iter() + .map(|event| format!("data: {}\n\n", serde_json::to_string(event).unwrap())) + .collect::(); + let _ = write!(socket, "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", body.len()); + let _ = socket.flush(); + return; + } + if request.method == "GET" && request.path.contains("/events?") { + return send_json_response( + socket, + "200 OK", + &json!({ "data": history_events, "next_page": null }), + ); + } + if request.method == "GET" && request.path.starts_with("/v1/sessions/session_test?") { + let mut session = json!({ + "id": "session_test", + "status": "idle", + "environment_id": "env_test", + "agent": { "id": "agent_test", "version": 3, "model": { "id": "claude-sonnet-5" } }, + "budget": { "type": "limit", "max_list_cost": { "currency": "USD", "amount": "100" } } + }); + if let Some(usage) = session_usage { + session["usage"] = usage.clone(); + } + return send_json_response(socket, "200 OK", &session); + } + if request.method == "POST" + && request.path.contains("/events?") + && request.body.contains("user.custom_tool_result") + && lost_tool_result_responses + .fetch_update( + AtomicOrdering::SeqCst, + AtomicOrdering::SeqCst, + |remaining| remaining.checked_sub(1), + ) + .is_ok() + { + // Simulate an accepted request whose response was lost. The caller + // must use durable session events to decide whether retry is safe. + return; + } + if request.method == "POST" && request.path.starts_with("/v1/sessions/session_test?") { + let body = serde_json::from_str::(&request.body).unwrap_or(Value::Null); + if let Some(agent) = body.get("agent").and_then(Value::as_object) { + let mut keys = agent.keys().map(String::as_str).collect::>(); + keys.sort_unstable(); + if keys != ["mcp_servers", "tools"] { + return send_json_response( + socket, + "400 Bad Request", + &json!({ "type": "invalid_request_error" }), + ); + } + } + return send_json_response(socket, "200 OK", &json!({ "accepted": true })); + } + send_json_response(socket, "200 OK", &json!({ "accepted": true })); + } + + fn fake_cumulative_usage() -> Value { + json!({ + "input_tokens": 12, + "output_tokens": 6, + "cache_read_input_tokens": 3, + "active_seconds": 2.0, + "list_cost": { "currency": "USD", "amount": "9" } + }) + } + + fn config() -> ClaudeManagedProviderConfig { + ClaudeManagedProviderConfig { + model: "claude-sonnet-5".to_owned(), + profile_id: "profile_test".to_owned(), + anthropic_agent_id: "agent_test".to_owned(), + agent_version: "3".to_owned(), + environment_id: "env_test".to_owned(), + beta_version: QUALIFIED_BETA.to_owned(), + max_session_list_cost_usd: 1.0, + instructions: "Paperclip test system instructions".to_owned(), + runtime_context: None, + } + } + + fn runtime_config_with_one_skill() -> (PathBuf, ClaudeManagedProviderConfig) { + let root = std::env::temp_dir().join(format!( + "paperclip-claude-managed-failure-test-{}", + Uuid::new_v4() + )); + let instruction_root = root.join("instructions"); + let skill_root = root.join("skill"); + fs::create_dir_all(&instruction_root).unwrap(); + fs::create_dir_all(&skill_root).unwrap(); + fs::write( + instruction_root.join("AGENTS.md"), + "Failure boundary test.\n", + ) + .unwrap(); + fs::write(skill_root.join("SKILL.md"), "# Failure boundary skill\n").unwrap(); + let mut config = config(); + config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { + "digest": "failure-instructions", + "rootPath": instruction_root.display().to_string() + } + }, + "skills": [{ + "key": "company/failure-boundary", + "runtimeName": "failure-boundary", + "bundle": { + "digest": "failure-skill", + "rootPath": skill_root.display().to_string() + } + }] + })); + (root, config) + } + + fn owned_test_skills() -> Vec { + vec![ + ClaudeManagedSkillRef { + skill_id: "skill_owned_1".to_owned(), + version: "version_1".to_owned(), + }, + ClaudeManagedSkillRef { + skill_id: "skill_owned_2".to_owned(), + version: "version_2".to_owned(), + }, + ] + } + + fn tool() -> AuthorizedTool { + AuthorizedTool { + operation_id: "issues.comment:create".to_owned(), + version: 1, + description: "Create an issue comment.".to_owned(), + input_schema: json!({ + "type": "object", + "required": ["body"], + "properties": { "body": { "type": "string" } }, + "additionalProperties": false + }), + response_schema: json!({ "type": "object" }), + } + } + + fn start_test_provider( + service: &FakeAnthropicService, + tools: Vec, + resume_session_id: Option<&str>, + cursor: Option<&str>, + ) -> Result { + let origin = service.origin.clone(); + ClaudeManagedProvider::start_with_worker( + &config(), + tools, + "test-scope", + resume_session_id, + cursor, + 0, + Vec::new(), + SensitiveApiKey("anthropic-test-secret".to_owned()), + move |key, beta| NetworkWorker::start_at(key, beta, &origin, false), + ) + .map_err(|error| error.cause) + } + + #[test] + fn tool_names_are_anthropic_safe_stable_and_collision_resistant() { + let first = remote_tool_name("issues.comment:create"); + let second = remote_tool_name("issues.comment/create"); + assert!(first.len() <= 128); + assert!(first + .chars() + .all(|value| value.is_ascii_alphanumeric() || matches!(value, '_' | '-'))); + assert_ne!(first, second); + assert_eq!(first, remote_tool_name("issues.comment:create")); + } + + #[test] + fn managed_profile_requires_a_canonical_numeric_agent_version() { + for invalid in ["latest", "0", "01", "2147483648"] { + let mut config = config(); + config.agent_version = invalid.to_owned(); + assert!(validate_config(&config) + .unwrap_err() + .to_string() + .contains("canonical positive 32-bit integer")); + } + } + + #[test] + fn managed_overrides_pin_inline_tools_and_never_embed_remote_mcp_credentials() { + let overrides = managed_agent_overrides( + &config(), + "Paperclip test system instructions", + vec![json!({ "type": "custom", "name": "pc_finish" })], + &[ClaudeManagedSkillRef { + skill_id: "skill_1".to_owned(), + version: "7".to_owned(), + }], + ); + assert_eq!( + overrides.get("system"), + Some(&json!("Paperclip test system instructions")) + ); + assert_eq!(overrides.pointer("/skills/0/version"), Some(&json!("7"))); + assert_eq!(overrides.get("mcp_servers"), Some(&json!([]))); + assert!(!serde_json::to_string(&overrides) + .unwrap() + .contains("paperclip_capability")); + assert!(overrides + .get("tools") + .and_then(Value::as_array) + .is_some_and(|tools| tools + .iter() + .any(|tool| tool.get("type") == Some(&json!("agent_toolset_20260401"))))); + assert!(serde_json::to_string(&config()) + .unwrap() + .find("paperclip_capability") + .is_none()); + } + + #[test] + fn managed_system_replaces_the_local_instruction_path_with_the_companion_skill() { + let mut config = config(); + let local_root = "/paperclip/runtime/instructions"; + config.instructions = format!( + "paperclip prompt\n\nAGENTS entry\n\nRead-only instruction sibling root: {local_root}" + ); + config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { "digest": "abc123", "rootPath": local_root } + }, + "skills": [] + })); + + let instructions = managed_system_instructions(&config).unwrap(); + assert!(instructions.starts_with("paperclip prompt\n\nAGENTS entry\n\n")); + assert!(instructions.contains("attached `paperclip-instructions-")); + assert!(instructions.ends_with("skill under `instructions/`.")); + assert!(!instructions.contains(local_root)); + } + + #[test] + fn managed_skill_uploads_include_instruction_siblings_and_complete_assigned_skill_trees() { + let service = FakeAnthropicService::start(vec![], vec![]); + let root = std::env::temp_dir().join(format!( + "paperclip-claude-managed-context-{}", + uuid::Uuid::new_v4() + )); + let instruction_root = root.join("instructions"); + let skill_root = root.join("reviewer"); + fs::create_dir_all(instruction_root.join("references")).unwrap(); + fs::create_dir_all(skill_root.join("references")).unwrap(); + fs::write( + instruction_root.join("AGENTS.md"), + "Follow the entry instructions.\n", + ) + .unwrap(); + fs::write( + instruction_root.join("references/policy.md"), + "Instruction sibling policy.\n", + ) + .unwrap(); + fs::write( + skill_root.join("SKILL.md"), + "# Reviewer\nUse the checklist.\n", + ) + .unwrap(); + fs::write( + skill_root.join("references/checklist.md"), + "- Verify the tests\n", + ) + .unwrap(); + let mut config = config(); + config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { + "digest": "instruction-digest", + "rootPath": instruction_root.display().to_string() + } + }, + "skills": [{ + "key": "company-1/reviewer", + "runtimeName": "reviewer", + "bundle": { + "digest": "skill-digest", + "rootPath": skill_root.display().to_string() + } + }] + })); + + let attached = upload_managed_runtime_skills_at( + "anthropic-test-secret", + &config, + "test-scope", + &service.origin, + false, + ) + .unwrap(); + + assert_eq!(attached.len(), 2); + assert_eq!(attached[0].skill_id, "skill_test_1"); + assert_eq!(attached[0].version, "skver_test_1"); + assert_eq!(attached[1].skill_id, "skill_test_2"); + assert_eq!(attached[1].version, "skver_test_2"); + let requests = service.captured(); + assert_eq!( + requests + .iter() + .filter(|request| request.method == "GET" && request.path.starts_with("/v1/skills?")) + .count(), + 2 + ); + let uploads = requests + .iter() + .filter(|request| request.method == "POST" && request.path == "/v1/skills") + .collect::>(); + assert_eq!(uploads.len(), 2); + assert!(uploads.iter().all(|request| request + .headers + .to_ascii_lowercase() + .contains("x-api-key: anthropic-test-secret"))); + let instruction_upload = uploads + .iter() + .find(|request| request.body.contains("Follow the entry instructions.")) + .unwrap(); + assert!(instruction_upload + .body + .contains("/instructions/AGENTS.md\"")); + assert!(instruction_upload + .body + .contains("/instructions/references/policy.md\"")); + assert!(instruction_upload + .body + .contains("Instruction sibling policy.")); + assert!(instruction_upload.body.contains("/SKILL.md\"")); + let assigned_upload = uploads + .iter() + .find(|request| request.body.contains("# Reviewer")) + .unwrap(); + assert!(assigned_upload.body.contains("reviewer/SKILL.md\"")); + assert!(assigned_upload + .body + .contains("reviewer/references/checklist.md\"")); + assert!(assigned_upload.body.contains("- Verify the tests")); + assert!(uploads + .iter() + .all(|request| !request.body.contains("anthropic-test-secret"))); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn managed_skill_aggregate_bounds_are_closed_at_provider_limits() { + let plans = vec![ + ManagedSkillUploadPlan { + title: "instructions".to_owned(), + top_level: "instructions".to_owned(), + files: vec![(PathBuf::from("instructions/AGENTS.md"), vec![0; 3])], + generated_skill: Some("companion".to_owned()), + }, + ManagedSkillUploadPlan { + title: "skill".to_owned(), + top_level: "skill".to_owned(), + files: vec![(PathBuf::from("SKILL.md"), vec![0; 5])], + generated_skill: None, + }, + ]; + assert_eq!(managed_skill_upload_totals(&plans), (3, 17)); + assert!(validate_managed_skill_upload_aggregate( + MAX_MANAGED_SKILL_ATTACHMENTS, + MAX_SKILL_UPLOAD_FILES, + MAX_SKILL_UPLOAD_BYTES, + ) + .is_ok()); + assert!( + validate_managed_skill_upload_aggregate(MAX_MANAGED_SKILL_ATTACHMENTS + 1, 0, 0,) + .unwrap_err() + .to_string() + .contains("provider skill limit") + ); + assert!( + validate_managed_skill_upload_aggregate(1, MAX_SKILL_UPLOAD_FILES + 1, 0,) + .unwrap_err() + .to_string() + .contains("aggregate file limit") + ); + assert!( + validate_managed_skill_upload_aggregate(1, 1, MAX_SKILL_UPLOAD_BYTES + 1,) + .unwrap_err() + .to_string() + .contains("aggregate byte limit") + ); + } + + #[test] + fn managed_skill_count_is_rejected_before_any_remote_upload() { + let service = FakeAnthropicService::start(vec![], vec![]); + let root = std::env::temp_dir().join(format!( + "paperclip-claude-managed-context-count-{}", + uuid::Uuid::new_v4() + )); + fs::create_dir_all(&root).unwrap(); + fs::write(root.join("AGENTS.md"), "Follow the entry.\n").unwrap(); + let assigned = (0..MAX_MANAGED_SKILL_ATTACHMENTS) + .map(|index| { + json!({ + "key": format!("company-1/skill-{index}"), + "runtimeName": format!("skill-{index}"), + "bundle": { + "digest": format!("{index:064x}"), + "rootPath": root.join("does-not-exist").display().to_string() + } + }) + }) + .collect::>(); + let mut config = config(); + config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { + "digest": "a".repeat(64), + "rootPath": root.display().to_string() + } + }, + "skills": assigned + })); + + let error = upload_managed_runtime_skills_at( + "anthropic-test-secret", + &config, + "test-scope", + &service.origin, + false, + ) + .unwrap_err(); + assert!(error.to_string().contains("provider skill limit")); + assert!(service.captured().is_empty()); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn managed_skill_creation_ignores_mutable_latest_mappings_and_avoids_title_collisions() { + let service = FakeAnthropicService::start(vec![], vec![]); + let client = Client::builder().no_proxy().build().unwrap(); + let files = vec![( + PathBuf::from("SKILL.md"), + b"# Verified\nImmutable content.\n".to_vec(), + )]; + let first = upload_managed_skill( + &client, + &service.origin, + "test-scope-one", + "predictable-title", + "verified", + files.clone(), + None, + ) + .unwrap(); + let second = upload_managed_skill( + &client, + &service.origin, + "test-scope-two", + "predictable-title", + "verified", + files, + None, + ) + .unwrap(); + assert_eq!(first.version, "skver_test_1"); + assert_eq!(second.version, "skver_test_2"); + let requests = service.captured(); + assert_eq!( + requests + .iter() + .filter(|request| request.method == "GET") + .count(), + 2 + ); + let uploads = requests + .iter() + .filter(|request| request.method == "POST" && request.path == "/v1/skills") + .collect::>(); + assert_eq!(uploads.len(), 2); + let name = |body: &str| { + body.split("name=\"display_name\"\r\n\r\n") + .nth(1) + .and_then(|value| value.split("\r\n").next()) + .unwrap() + .to_owned() + }; + assert_ne!(name(&uploads[0].body), name(&uploads[1].body)); + assert!(uploads.iter().all(|request| !request + .headers + .to_ascii_lowercase() + .contains("skills-2025-10-02"))); + } + + #[test] + fn upload_n_failure_deletes_every_known_uploaded_skill() { + let upload_count = Arc::new(AtomicUsize::new(0)); + let observed_upload_count = Arc::clone(&upload_count); + let service = ScriptedAnthropicService::start(move |request| { + if request.method == "GET" && request.path.starts_with("/v1/skills?") { + return ( + "200 OK".to_owned(), + json!({ "data": [], "next_page": null }), + ); + } + if request.method == "POST" && request.path == "/v1/skills" { + let upload = observed_upload_count.fetch_add(1, AtomicOrdering::SeqCst) + 1; + if upload == 1 { + return ( + "200 OK".to_owned(), + json!({ "id": "skill_uploaded_1", "latest_version_id": "version_1" }), + ); + } + return ("400 Bad Request".to_owned(), json!({ "type": "error" })); + } + if request.method == "DELETE" && request.path == "/v1/skills/skill_uploaded_1" { + return ("200 OK".to_owned(), json!({ "deleted": true })); + } + ( + "500 Internal Server Error".to_owned(), + json!({ "type": "error" }), + ) + }); + let (root, config) = runtime_config_with_one_skill(); + + let error = upload_managed_runtime_skills_at( + "anthropic-test-secret", + &config, + "upload-n-scope", + &service.origin, + false, + ) + .unwrap_err(); + + assert_eq!(error.cleanup_inventory(), Some(&[][..])); + assert!(error.durable_skills().is_none()); + let requests = service.captured(); + assert_eq!( + requests + .iter() + .filter(|request| request.method == "POST" && request.path == "/v1/skills") + .count(), + 2 + ); + assert!(requests.iter().any(|request| { + request.method == "DELETE" && request.path == "/v1/skills/skill_uploaded_1" + })); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn local_bootstrap_failure_deletes_all_uploaded_skills() { + let service = ScriptedAnthropicService::start(|request| { + if request.method == "DELETE" && request.path.starts_with("/v1/skills/") { + ("200 OK".to_owned(), json!({ "deleted": true })) + } else { + ( + "500 Internal Server Error".to_owned(), + json!({ "type": "error" }), + ) + } + }); + let skills = owned_test_skills(); + let error = handle_fresh_bootstrap_failure( + "anthropic-test-secret", + QUALIFIED_BETA, + &service.origin, + false, + skills.clone(), + LocalRunnerError::invalid("injected local bootstrap failure").into(), + ); + + assert_eq!(error.cleanup_inventory(), Some(&[][..])); + assert!(error.durable_skills().is_none()); + let deleted = service + .captured() + .into_iter() + .filter(|request| request.method == "DELETE") + .map(|request| request.path) + .collect::>(); + assert_eq!( + deleted, + vec![ + "/v1/skills/skill_owned_1".to_owned(), + "/v1/skills/skill_owned_2".to_owned() + ] + ); + } + + #[test] + fn failed_skill_cleanup_returns_a_retryable_cleanup_inventory() { + let service = ScriptedAnthropicService::start(|_| { + ( + "500 Internal Server Error".to_owned(), + json!({ "type": "error" }), + ) + }); + let skills = owned_test_skills(); + let error = handle_fresh_bootstrap_failure( + "anthropic-test-secret", + QUALIFIED_BETA, + &service.origin, + false, + skills.clone(), + LocalRunnerError::invalid("injected local bootstrap failure").into(), + ); + + assert_eq!(error.cleanup_inventory(), Some(skills.as_slice())); + assert!(error.durable_skills().is_none()); + assert!(error + .to_string() + .contains("cleanup of uploaded skills failed")); + } + + #[test] + fn stream_bootstrap_failure_deletes_session_before_its_skills() { + let service = ScriptedAnthropicService::start(|request| { + if request.method == "DELETE" { + ("200 OK".to_owned(), json!({ "deleted": true })) + } else { + ( + "500 Internal Server Error".to_owned(), + json!({ "type": "error" }), + ) + } + }); + let skills = owned_test_skills(); + let error = handle_fresh_bootstrap_failure( + "anthropic-test-secret", + QUALIFIED_BETA, + &service.origin, + false, + skills, + ClaudeManagedBootstrapError::after_session( + LocalRunnerError::invalid("injected stream bootstrap failure"), + "session_owned_1", + ), + ); + + assert_eq!(error.cleanup_inventory(), Some(&[][..])); + assert!(error.durable_skills().is_none()); + let deleted = service + .captured() + .into_iter() + .filter(|request| request.method == "DELETE") + .map(|request| request.path) + .collect::>(); + assert_eq!( + deleted, + vec![ + "/v1/sessions/session_owned_1?beta=true".to_owned(), + "/v1/skills/skill_owned_1".to_owned(), + "/v1/skills/skill_owned_2".to_owned() + ] + ); + } + + #[test] + fn failed_session_cleanup_checkpoints_session_and_skills_without_deleting_skills() { + let service = ScriptedAnthropicService::start(|_| { + ( + "500 Internal Server Error".to_owned(), + json!({ "type": "error" }), + ) + }); + let skills = owned_test_skills(); + let error = handle_fresh_bootstrap_failure( + "anthropic-test-secret", + QUALIFIED_BETA, + &service.origin, + false, + skills.clone(), + ClaudeManagedBootstrapError::after_session( + LocalRunnerError::invalid("injected stream bootstrap failure"), + "session_owned_1", + ), + ); + + assert_eq!(error.durable_skills(), Some(skills.as_slice())); + assert_eq!(error.recovery_session_id(), Some("session_owned_1")); + assert!(error.cleanup_inventory().is_none()); + assert!(service + .captured() + .iter() + .all(|request| !request.path.starts_with("/v1/skills/"))); + } + + #[test] + fn ambiguous_session_create_checkpoints_skills_for_metadata_reconciliation() { + let service = ScriptedAnthropicService::start(|_| { + ( + "500 Internal Server Error".to_owned(), + json!({ "type": "error" }), + ) + }); + let skills = owned_test_skills(); + let error = handle_fresh_bootstrap_failure( + "anthropic-test-secret", + QUALIFIED_BETA, + &service.origin, + false, + skills.clone(), + ClaudeManagedBootstrapError::ambiguous_create(LocalRunnerError::invalid( + "injected ambiguous session create", + )), + ); + + assert_eq!(error.durable_skills(), Some(skills.as_slice())); + assert!(error.recovery_session_id().is_none()); + assert!(error.cleanup_inventory().is_none()); + assert!(service.captured().is_empty()); + } + + #[test] + fn accepted_session_with_lost_response_is_reconciled_by_exact_metadata() { + let list_count = Arc::new(AtomicUsize::new(0)); + let observed_list_count = Arc::clone(&list_count); + let ownership_marker = session_ownership_marker("accepted-session-scope"); + let listed_marker = ownership_marker.clone(); + let service = ScriptedAnthropicService::start(move |request| { + if request.method == "GET" && request.path.starts_with("/v1/sessions?") { + let list = observed_list_count.fetch_add(1, AtomicOrdering::SeqCst); + if list == 0 { + return ( + "200 OK".to_owned(), + json!({ "data": [], "next_page": null }), + ); + } + return ( + "200 OK".to_owned(), + json!({ + "data": [{ + "id": "session_reconciled", + "environment_id": "env_test", + "agent": { + "id": "agent_test", + "version": 3, + "model": { "id": "claude-sonnet-5" } + }, + "budget": { + "max_list_cost": { "amount": "100" } + }, + "metadata": { + SESSION_OWNERSHIP_METADATA_KEY: listed_marker + } + }], + "next_page": null + }), + ); + } + if request.method == "POST" && request.path.starts_with("/v1/sessions?") { + return ( + "500 Internal Server Error".to_owned(), + json!({ "type": "lost_response" }), + ); + } + if request.method == "GET" && request.path.contains("/events/stream") { + return ("200 OK".to_owned(), json!({ "stream": "ready" })); + } + ( + "500 Internal Server Error".to_owned(), + json!({ "type": "error" }), + ) + }); + let origin = service.origin.clone(); + + let provider = ClaudeManagedProvider::start_with_worker( + &config(), + vec![], + "accepted-session-scope", + None, + None, + 0, + vec![], + SensitiveApiKey("anthropic-test-secret".to_owned()), + move |key, beta| NetworkWorker::start_at(key, beta, &origin, false), + ) + .unwrap(); + + assert_eq!(provider.session_identity(), "session_reconciled"); + let requests = service.captured(); + assert_eq!( + requests + .iter() + .filter( + |request| request.method == "POST" && request.path.starts_with("/v1/sessions?") + ) + .count(), + 1 + ); + let create = requests + .iter() + .find(|request| request.method == "POST" && request.path.starts_with("/v1/sessions?")) + .unwrap(); + let body: Value = serde_json::from_str(&create.body).unwrap(); + assert_eq!( + body.pointer(&format!("/metadata/{SESSION_OWNERSHIP_METADATA_KEY}")), + Some(&json!(ownership_marker)) + ); + } + + #[test] + fn managed_skills_upload_once_reuse_on_cold_recovery_and_delete_with_session() { + let create_service = FakeAnthropicService::start(vec![], vec![]); + let root = std::env::temp_dir().join(format!( + "paperclip-claude-managed-lifecycle-{}", + uuid::Uuid::new_v4() + )); + let instruction_root = root.join("instructions"); + let skill_root = root.join("reviewer"); + fs::create_dir_all(&instruction_root).unwrap(); + fs::create_dir_all(&skill_root).unwrap(); + fs::write( + instruction_root.join("AGENTS.md"), + "Follow these instructions.\n", + ) + .unwrap(); + fs::write( + skill_root.join("SKILL.md"), + "# Reviewer\nReview the work.\n", + ) + .unwrap(); + let mut managed_config = config(); + managed_config.instructions = format!( + "Paperclip test system instructions\n\nRead-only instruction sibling root: {}", + instruction_root.display() + ); + managed_config.runtime_context = Some(json!({ + "instructions": { + "entryPath": "AGENTS.md", + "bundle": { + "digest": "instruction-digest", + "rootPath": instruction_root.display().to_string() + } + }, + "skills": [{ + "key": "company-1/reviewer", + "runtimeName": "reviewer", + "bundle": { + "digest": "reviewer-digest", + "rootPath": skill_root.display().to_string() + } + }] + })); + + let owned_skills = upload_managed_runtime_skills_at( + "anthropic-test-secret", + &managed_config, + "test-scope", + &create_service.origin, + false, + ) + .unwrap(); + validate_managed_skill_refs(&managed_config, &owned_skills).unwrap(); + let create_origin = create_service.origin.clone(); + let mut created = ClaudeManagedProvider::start_with_worker( + &managed_config, + vec![], + "test-scope", + None, + None, + 0, + owned_skills.clone(), + SensitiveApiKey("anthropic-test-secret".to_owned()), + move |key, beta| NetworkWorker::start_at(key, beta, &create_origin, false), + ) + .unwrap(); + assert_eq!( + created.claude_managed_skills(), + Some(owned_skills.as_slice()) + ); + created.shutdown().unwrap(); + drop(created); + + let recover_service = FakeAnthropicService::start(vec![], vec![]); + let recover_origin = recover_service.origin.clone(); + let mut recovered = ClaudeManagedProvider::start_with_worker( + &managed_config, + vec![], + "test-scope", + Some("session_test"), + None, + 0, + owned_skills.clone(), + SensitiveApiKey("anthropic-test-secret".to_owned()), + move |key, beta| NetworkWorker::start_at(key, beta, &recover_origin, false), + ) + .unwrap(); + recovered.destroy_session().unwrap(); + + let create_requests = create_service.captured(); + let recover_requests = recover_service.captured(); + assert_eq!( + create_requests + .iter() + .filter(|request| request.method == "POST" && request.path == "/v1/skills") + .count(), + owned_skills.len() + ); + assert_eq!( + recover_requests + .iter() + .filter(|request| request.method == "POST" && request.path == "/v1/skills") + .count(), + 0 + ); + assert_eq!( + create_requests + .iter() + .filter( + |request| request.method == "POST" && request.path.starts_with("/v1/sessions?") + ) + .count(), + 1 + ); + let create_body: Value = serde_json::from_str( + &create_requests + .iter() + .find(|request| { + request.method == "POST" && request.path.starts_with("/v1/sessions?") + }) + .unwrap() + .body, + ) + .unwrap(); + assert_eq!( + create_body.pointer("/agent/skills"), + Some(&Value::Array(managed_skill_attachments(&owned_skills))) + ); + let deletes = recover_requests + .iter() + .filter(|request| request.method == "DELETE") + .map(|request| request.path.as_str()) + .collect::>(); + assert_eq!( + deletes, + vec![ + "/v1/sessions/session_test?beta=true", + "/v1/skills/skill_test_1", + "/v1/skills/skill_test_2", + ] + ); + fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn persisted_managed_skill_refs_are_strictly_bounded_and_complete() { + let config = config(); + assert!(validate_managed_skill_refs( + &config, + &[ClaudeManagedSkillRef { + skill_id: "skill_caller_supplied".to_owned(), + version: "skver_caller_supplied".to_owned(), + }], + ) + .is_err()); + + let mut runtime_config = config; + runtime_config.runtime_context = Some(json!({ "skills": [] })); + for invalid in [ + vec![], + vec![ClaudeManagedSkillRef { + skill_id: "../unowned".to_owned(), + version: "skver_1".to_owned(), + }], + vec![ClaudeManagedSkillRef { + skill_id: "skill_1".to_owned(), + version: "latest/value".to_owned(), + }], + ] { + assert!(validate_managed_skill_refs(&runtime_config, &invalid).is_err()); + } + assert!(validate_managed_skill_refs( + &runtime_config, + &[ClaudeManagedSkillRef { + skill_id: "skill_1".to_owned(), + version: "skver_1".to_owned(), + }], + ) + .is_ok()); + } + + #[test] + fn rejects_oversized_remote_catalogs() { + let tools = (0..129) + .map(|index| AuthorizedTool { + operation_id: format!("operation_{index}"), + version: 1, + description: "test".to_owned(), + input_schema: json!({"type": "object"}), + response_schema: json!({"type": "object"}), + }) + .collect::>(); + assert!(encode_tools(&tools) + .unwrap_err() + .to_string() + .contains("128")); + } + + #[test] + fn managed_transport_streams_reconciles_tools_and_accounts_without_leaking_auth() { + let service = FakeAnthropicService::start( + vec![ + json!({ "type": "event_start", "event": { "type": "agent.message", "id": "sevt_message" } }), + json!({ "type": "event_delta", "event_id": "sevt_message", "delta": { "type": "content_delta", "index": 0, "content": { "type": "text", "text": "draft" } } }), + json!({ "type": "agent.message", "id": "sevt_message", "content": [{ "type": "text", "text": "Authoritative final" }] }), + json!({ "type": "agent.message", "id": "sevt_message", "content": [{ "type": "text", "text": "Authoritative final" }] }), + json!({ "type": "agent.custom_tool_use", "id": "sevt_tool", "name": remote_tool_name("issues.comment:create"), "input": { "body": "Ship it" } }), + json!({ "type": "session.status_idle", "id": "sevt_wait", "status": "idle", "stop_reason": { "type": "requires_action", "event_ids": ["sevt_tool"] } }), + json!({ "type": "span.model_request_end", "id": "sevt_request", "model_usage": { "input_tokens": 10, "output_tokens": 4 } }), + json!({ "type": "session.usage", "id": "sevt_usage", "usage": { "input_tokens": 10, "output_tokens": 4, "cache_read_input_tokens": 2, "active_seconds": 1.5, "list_cost": { "currency": "USD", "amount": "7" } } }), + json!({ "type": "session.status_idle", "id": "sevt_end", "status": "idle", "stop_reason": { "type": "end_turn", "event_ids": [] } }), + ], + vec![ + json!({ "type": "agent.custom_tool_use", "id": "sevt_tool", "name": remote_tool_name("issues.comment:create"), "input": { "body": "Ship it" } }), + json!({ "type": "session.status_idle", "id": "sevt_wait", "status": "idle", "stop_reason": { "type": "requires_action", "event_ids": ["sevt_tool"] } }), + json!({ "type": "session.status_running", "id": "sevt_continue", "status": "running" }), + ], + ); + let mut provider = start_test_provider(&service, vec![tool()], None, None).unwrap(); + provider.start_turn("Hello", ".", "turn_test").unwrap(); + + let mut methods = Vec::new(); + let mut saw_tool = false; + let deadline = Instant::now() + Duration::from_secs(3); + while Instant::now() < deadline { + match provider.poll().unwrap() { + Some(ProviderEvent::ToolCall { + call_id, + operation_id, + input, + }) => { + assert_eq!(call_id, "sevt_tool"); + assert_eq!(operation_id, "issues.comment:create"); + assert_eq!(input, json!({ "body": "Ship it" })); + let result = ToolResult { + call_id, + operation_id, + result: json!({ "ok": true }), + is_error: false, + }; + provider.deliver_tool_result(&result).unwrap(); + // A replayed PRP command after the accepted delivery must + // reconcile against requires_action instead of POSTing the + // result a second time. + provider.deliver_tool_result(&result).unwrap(); + saw_tool = true; + } + Some(ProviderEvent::Notification { method, params }) => { + if method == "item/completed" { + assert_eq!( + params.pointer("/item/text"), + Some(&json!("Authoritative final")) + ); + } + methods.push(method); + if methods.iter().any(|value| value == "turn/completed") { + break; + } + } + Some(_) | None => thread::sleep(Duration::from_millis(2)), + } + } + assert!(saw_tool); + assert!(methods + .iter() + .any(|value| value == "item/agentMessage/delta")); + assert_eq!( + methods + .iter() + .filter(|value| value.as_str() == "item/completed") + .count(), + 1 + ); + assert!(methods + .iter() + .any(|value| value == "thread/tokenUsage/updated")); + assert!(methods.iter().any(|value| value == "turn/completed")); + + let requests = service.captured(); + let create = requests + .iter() + .find(|request| request.method == "POST" && request.path.starts_with("/v1/sessions?")) + .unwrap(); + assert!(create + .headers + .to_ascii_lowercase() + .contains("x-api-key: anthropic-test-secret")); + assert!(create.headers.contains(QUALIFIED_BETA)); + let body: Value = serde_json::from_str(&create.body).unwrap(); + assert_eq!( + body.pointer("/agent/model/id"), + Some(&json!("claude-sonnet-5")) + ); + assert_eq!(body.pointer("/agent/version"), Some(&json!(3))); + assert_eq!( + body.pointer("/budget/max_list_cost/amount"), + Some(&json!("100")) + ); + let serialized = serde_json::to_string( + &requests + .iter() + .map(|request| (&request.path, &request.body)) + .collect::>(), + ) + .unwrap(); + assert!(!serialized.contains("anthropic-test-secret")); + assert!(!serialized.contains("PAPERCLIP_API_KEY")); + let result_post = requests + .iter() + .filter(|request| request.body.contains("user.custom_tool_result")) + .collect::>(); + assert_eq!(result_post.len(), 1); + let result_post = result_post[0]; + assert!(result_post.body.contains("sevt_tool")); + } + + #[test] + fn terminal_fetches_cumulative_usage_when_no_periodic_snapshot_arrives() { + let service = FakeAnthropicService::start( + vec![ + json!({ "type": "span.model_request_end", "id": "sevt_request" }), + json!({ "type": "session.status_idle", "id": "sevt_end", "status": "idle", "stop_reason": { "type": "end_turn", "event_ids": [] } }), + ], + vec![], + ); + let mut provider = start_test_provider(&service, vec![], None, None).unwrap(); + provider.start_turn("Hello", ".", "turn_test").unwrap(); + + let mut methods = Vec::new(); + let mut final_usage = None; + let deadline = Instant::now() + Duration::from_secs(2); + while Instant::now() < deadline { + match provider.poll().unwrap() { + Some(ProviderEvent::Notification { method, params }) => { + if method == "thread/tokenUsage/updated" { + final_usage = Some(params); + } + methods.push(method); + if methods + .last() + .is_some_and(|method| method == "turn/completed") + { + break; + } + } + Some(_) | None => thread::sleep(Duration::from_millis(2)), + } + } + + assert_eq!( + methods + .iter() + .rev() + .take(2) + .map(String::as_str) + .collect::>(), + vec!["turn/completed", "thread/tokenUsage/updated"] + ); + let final_usage = final_usage.unwrap(); + assert_eq!(final_usage.pointer("/usage/input_tokens"), Some(&json!(12))); + assert_eq!(final_usage.pointer("/usage/requestCount"), Some(&json!(1))); + } + + #[test] + fn terminal_replaces_a_stale_periodic_snapshot_with_current_cumulative_usage() { + let service = FakeAnthropicService::start( + vec![ + json!({ "type": "span.model_request_end", "id": "sevt_request" }), + json!({ "type": "session.usage", "id": "sevt_stale_usage", "usage": { "input_tokens": 1, "output_tokens": 1, "cache_read_input_tokens": 0, "active_seconds": 0.5, "list_cost": { "currency": "USD", "amount": "1" } } }), + json!({ "type": "session.status_idle", "id": "sevt_end", "status": "idle", "stop_reason": { "type": "end_turn", "event_ids": [] } }), + ], + vec![], + ); + let mut provider = start_test_provider(&service, vec![], None, None).unwrap(); + provider.start_turn("Hello", ".", "turn_test").unwrap(); + + let mut usage_values = Vec::new(); + let mut methods = Vec::new(); + let deadline = Instant::now() + Duration::from_secs(2); + while Instant::now() < deadline { + match provider.poll().unwrap() { + Some(ProviderEvent::Notification { method, params }) => { + if method == "thread/tokenUsage/updated" { + usage_values.push( + params + .pointer("/usage/input_tokens") + .and_then(Value::as_u64) + .unwrap(), + ); + } + methods.push(method); + if methods + .last() + .is_some_and(|method| method == "turn/completed") + { + break; + } + } + Some(_) | None => thread::sleep(Duration::from_millis(2)), + } + } + + assert_eq!(usage_values, vec![1, 12]); + assert_eq!(methods.last().map(String::as_str), Some("turn/completed")); + } + + #[test] + fn terminal_fails_closed_when_current_cumulative_usage_is_missing() { + let service = FakeAnthropicService::start_with_session_usage( + vec![json!({ + "type": "session.status_idle", + "id": "sevt_end", + "status": "idle", + "stop_reason": { "type": "end_turn", "event_ids": [] } + })], + vec![], + None, + ); + let mut provider = start_test_provider(&service, vec![], None, None).unwrap(); + provider.start_turn("Hello", ".", "turn_test").unwrap(); + + let deadline = Instant::now() + Duration::from_secs(2); + loop { + assert!(Instant::now() < deadline, "terminal event was not observed"); + match provider.poll() { + Err(error) => { + assert!(error.to_string().contains("omitted cumulative usage")); + break; + } + _ => thread::sleep(Duration::from_millis(2)), + } + } + } + + #[test] + fn lost_tool_result_response_uses_durable_running_status_as_delivery_receipt() { + let service = FakeAnthropicService::start_with_lost_tool_result_responses( + vec![], + vec![ + json!({ "type": "agent.custom_tool_use", "id": "sevt_tool", "name": remote_tool_name("issues.comment:create"), "input": { "body": "Ship it" } }), + json!({ "type": "session.status_idle", "id": "sevt_wait", "status": "idle", "stop_reason": { "type": "requires_action", "event_ids": ["sevt_tool"] } }), + json!({ "type": "session.status_running", "id": "sevt_continue", "status": "running" }), + ], + 1, + ); + let mut provider = start_test_provider(&service, vec![tool()], None, None).unwrap(); + provider + .pending_remote_calls + .insert("sevt_tool".to_owned(), "issues.comment:create".to_owned()); + + provider + .deliver_tool_result(&ToolResult { + call_id: "sevt_tool".to_owned(), + operation_id: "issues.comment:create".to_owned(), + result: json!({ "ok": true }), + is_error: false, + }) + .unwrap(); + + assert!(!provider.pending_remote_calls.contains_key("sevt_tool")); + let requests = service.captured(); + assert_eq!( + requests + .iter() + .filter(|request| request.body.contains("user.custom_tool_result")) + .count(), + 1 + ); + assert!(requests.iter().any(|request| { + request.method == "GET" + && request.path.contains("/events?") + && !request.path.contains("/stream") + })); + assert!(!requests.iter().any(|request| { + request.method == "GET" && request.path.starts_with("/v1/sessions/session_test?") + })); + } + + #[test] + fn lost_tool_result_response_retries_when_durable_status_is_still_pending() { + let service = FakeAnthropicService::start_with_lost_tool_result_responses( + vec![], + vec![ + json!({ "type": "agent.custom_tool_use", "id": "sevt_tool", "name": remote_tool_name("issues.comment:create"), "input": { "body": "Ship it" } }), + json!({ "type": "session.status_idle", "id": "sevt_wait", "status": "idle", "stop_reason": { "type": "requires_action", "event_ids": ["sevt_tool"] } }), + ], + 1, + ); + let mut provider = start_test_provider(&service, vec![tool()], None, None).unwrap(); + provider + .pending_remote_calls + .insert("sevt_tool".to_owned(), "issues.comment:create".to_owned()); + + provider + .deliver_tool_result(&ToolResult { + call_id: "sevt_tool".to_owned(), + operation_id: "issues.comment:create".to_owned(), + result: json!({ "ok": true }), + is_error: false, + }) + .unwrap(); + + assert_eq!( + service + .captured() + .iter() + .filter(|request| request.body.contains("user.custom_tool_result")) + .count(), + 2 + ); + } + + #[test] + fn tool_result_recovery_fails_closed_without_a_linked_pending_receipt() { + let service = FakeAnthropicService::start_with_lost_tool_result_responses( + vec![], + vec![json!({ + "type": "session.status_idle", + "id": "sevt_unrelated_end", + "status": "idle", + "stop_reason": { "type": "end_turn", "event_ids": [] } + })], + 1, + ); + let mut provider = start_test_provider(&service, vec![tool()], None, None).unwrap(); + provider + .pending_remote_calls + .insert("sevt_tool".to_owned(), "issues.comment:create".to_owned()); + + let error = provider + .deliver_tool_result(&ToolResult { + call_id: "sevt_tool".to_owned(), + operation_id: "issues.comment:create".to_owned(), + result: json!({ "ok": true }), + is_error: false, + }) + .unwrap_err(); + assert!(error.to_string().contains("could not be proven")); + assert!(provider.pending_remote_calls.contains_key("sevt_tool")); + } + + #[test] + fn recovery_opens_stream_then_replays_only_after_the_durable_cursor() { + let service = FakeAnthropicService::start( + vec![], + vec![ + json!({ "type": "agent.message", "id": "sevt_old", "content": [{ "type": "text", "text": "old" }] }), + json!({ "type": "agent.message", "id": "sevt_new", "content": [{ "type": "text", "text": "recovered" }] }), + ], + ); + let mut provider = + start_test_provider(&service, vec![], Some("session_test"), Some("sevt_old")).unwrap(); + match provider.poll().unwrap().unwrap() { + ProviderEvent::Notification { method, params } => { + assert_eq!(method, "item/completed"); + assert_eq!(params.pointer("/item/text"), Some(&json!("recovered"))); + } + _ => panic!("expected recovered message"), + } + let paths = service + .captured() + .into_iter() + .map(|request| request.path) + .collect::>(); + let stream_index = paths + .iter() + .position(|path| path.contains("/events/stream")) + .unwrap(); + let history_index = paths + .iter() + .position(|path| path.contains("/events?") && !path.contains("/stream")) + .unwrap(); + assert!(stream_index < history_index); + } + + #[test] + fn malformed_tool_arguments_fail_closed_before_paperclip_execution() { + let service = FakeAnthropicService::start( + vec![json!({ + "type": "agent.custom_tool_use", + "id": "sevt_bad_tool", + "name": remote_tool_name("issues.comment:create"), + "input": { "body": 42 } + })], + vec![], + ); + let mut provider = start_test_provider(&service, vec![tool()], None, None).unwrap(); + provider.start_turn("Bad call", ".", "turn_bad").unwrap(); + let deadline = Instant::now() + Duration::from_secs(2); + loop { + assert!( + Instant::now() < deadline, + "malformed tool event was not observed" + ); + match provider.poll() { + Err(error) => { + assert!(error.to_string().contains("schema validation")); + break; + } + _ => thread::sleep(Duration::from_millis(2)), + } + } + } + + #[test] + fn budget_increase_is_monotonic_and_remote_deletion_is_explicit() { + let service = FakeAnthropicService::start(vec![], vec![]); + let mut provider = start_test_provider(&service, vec![], None, None).unwrap(); + assert!(provider + .increase_budget(0.99) + .unwrap_err() + .to_string() + .contains("monotonically")); + provider.increase_budget(2.0).unwrap(); + provider.destroy_session().unwrap(); + let requests = service.captured(); + let update = requests + .iter() + .find(|request| { + request.method == "POST" + && request.path.starts_with("/v1/sessions/session_test?") + && request.body.contains("max_list_cost") + }) + .unwrap(); + let body: Value = serde_json::from_str(&update.body).unwrap(); + assert_eq!( + body.pointer("/budget/max_list_cost/amount"), + Some(&json!("200")) + ); + assert!(requests.iter().any(|request| { + request.method == "DELETE" && request.path.starts_with("/v1/sessions/session_test?") + })); + } + + #[test] + fn conflicting_duplicate_remote_event_ids_fail_closed() { + let service = FakeAnthropicService::start(vec![], vec![]); + let mut provider = start_test_provider(&service, vec![], None, None).unwrap(); + assert!(provider + .normalize_remote_event(json!({ + "type": "agent.message", + "id": "sevt_conflict", + "content": [{ "type": "text", "text": "first" }] + })) + .unwrap() + .is_some()); + assert!(provider + .normalize_remote_event(json!({ + "type": "agent.message", + "id": "sevt_conflict", + "content": [{ "type": "text", "text": "different" }] + })) + .unwrap_err() + .to_string() + .contains("conflicting content")); + } +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs index e6a9367725..bf91cd95b3 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/codex_provider.rs @@ -10,16 +10,33 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use sha2::{Digest, Sha256}; -use crate::durable::redact_text; +use crate::durable::{redact_text, OpenCodeLaunchProfile}; use crate::local_runner::LocalRunnerError; -use crate::process_supervisor::SupervisedProcess; +use crate::process_supervisor::{ + SupervisedProcess, VerifiedProcessArgument, VerifiedProcessLaunch, +}; use crate::provider_bridge::{AuthorizedTool, DurableReplayFilter, ToolResult}; use crate::provider_events::normalized_codex_terminal_event_type; +use crate::qualified_launch::verify_launch_artifact; pub const CODEX_APP_SERVER_MAX_FRAME_BYTES: usize = 4 * 1024 * 1024; +const QUALIFIED_OPENCODE_VERSION: &str = "1.18.17"; const DEFAULT_PROVIDER_TRACE_MAX_BYTES: usize = 64 * 1024 * 1024; const MAX_BUFFERED_MESSAGES: usize = 1_024; const MAX_BUFFERED_MESSAGE_BYTES: usize = 16 * 1024 * 1024; +const OPENCODE_PROVIDER_ENVIRONMENT_KEYS: &[&str] = &[ + "OPENROUTER_API_KEY", + "PAPERCLIP_NATIVE_MCP_NAME", + "PAPERCLIP_NATIVE_MCP_URL", + "PAPERCLIP_NATIVE_MCP_TOKEN", + "PAPERCLIP_OPENCODE_PERMISSION_MODE", + "PAPERCLIP_OPENCODE_RUNTIME_DIR", + "PAPERCLIP_RUNNER_INSTANCE_ID", + "PAPERCLIP_RUN_ID", + "PAPERCLIP_NORMALIZED_SESSION_ID", + "PAPERCLIP_NATIVE_RUNTIME_CONTEXT_PATH", +]; +const TRUSTED_OPENCODE_EXECUTABLE_ARG: &str = "--paperclip-trusted-opencode-executable"; const MAX_INSTRUCTIONS_BYTES: usize = 1024 * 1024; const MAX_PENDING_TOOL_REQUESTS: usize = 4_096; const MAX_PENDING_TOOL_REQUEST_BYTES: usize = 16 * 1024 * 1024; @@ -269,6 +286,11 @@ impl CodexProviderConfig { "Codex providerVersion is empty or oversized", )); } + if self.provider == "opencode" && self.provider_version != QUALIFIED_OPENCODE_VERSION { + return Err(LocalRunnerError::invalid(format!( + "OpenCode providerVersion must equal the qualified {QUALIFIED_OPENCODE_VERSION} release", + ))); + } if self.command.as_os_str().is_empty() { return Err(LocalRunnerError::invalid("Codex command is required")); } @@ -493,6 +515,7 @@ pub struct CodexProvider { quarantined: bool, trace: Option, last_trace_frame_id: Option, + opencode_launch_profile: Option, } impl CodexProvider { @@ -500,7 +523,7 @@ impl CodexProvider { config: &CodexProviderConfig, resume_thread_id: Option<&str>, ) -> Result { - Self::start_with_tools_for_generation(config, std::iter::empty(), resume_thread_id, 1) + Self::start_with_tools_for_generation(config, std::iter::empty(), resume_thread_id, 1, None) } pub fn start_with_tools( @@ -508,7 +531,7 @@ impl CodexProvider { authorized_tools: impl IntoIterator, resume_thread_id: Option<&str>, ) -> Result { - Self::start_with_tools_for_generation(config, authorized_tools, resume_thread_id, 1) + Self::start_with_tools_for_generation(config, authorized_tools, resume_thread_id, 1, None) } pub(crate) fn start_with_tools_for_generation( @@ -516,6 +539,7 @@ impl CodexProvider { authorized_tools: impl IntoIterator, resume_thread_id: Option<&str>, process_generation: u64, + opencode_launch_profile: Option<&OpenCodeLaunchProfile>, ) -> Result { config.validate()?; if process_generation == 0 { @@ -542,35 +566,61 @@ impl CodexProvider { "RUST_BACKTRACE", ]; let provider_environment_keys = if config.provider == "opencode" { - vec![ - "OPENROUTER_API_KEY", - "PAPERCLIP_NATIVE_MCP_NAME", - "PAPERCLIP_NATIVE_MCP_URL", - "PAPERCLIP_NATIVE_MCP_TOKEN", - "PAPERCLIP_OPENCODE_COMMAND", - "PAPERCLIP_OPENCODE_PERMISSION_MODE", - "PAPERCLIP_OPENCODE_RUNTIME_DIR", - "PAPERCLIP_RUNNER_INSTANCE_ID", - "PAPERCLIP_RUN_ID", - "PAPERCLIP_NORMALIZED_SESSION_ID", - "PAPERCLIP_NATIVE_RUNTIME_CONTEXT_PATH", - ] + OPENCODE_PROVIDER_ENVIRONMENT_KEYS } else { - vec!["CODEX_HOME", "OPENAI_API_KEY", "CODEX_API_KEY"] + &["CODEX_HOME", "OPENAI_API_KEY", "CODEX_API_KEY"][..] }; let environment_keys = common_environment_keys .iter() .copied() - .chain(provider_environment_keys) + .chain(provider_environment_keys.iter().copied()) .collect::>(); - let mut provider = Self { - process: SupervisedProcess::spawn_with_environment_keys( + let process = if config.provider == "opencode" { + let profile = opencode_launch_profile.ok_or_else(|| { + LocalRunnerError::invalid( + "OpenCode runner startup omitted its qualified launch profile", + ) + })?; + let proxy_script = profile.proxy_script.path.to_string_lossy(); + if config.command != profile.command.path + || config.args.as_slice() != [proxy_script.as_ref()] + { + return Err(LocalRunnerError::invalid( + "OpenCode launch does not match the runner-owned qualified profile", + )); + } + let command = verify_launch_artifact(&profile.command, "OpenCode proxy command") + .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; + let proxy = verify_launch_artifact(&profile.proxy_script, "OpenCode proxy script") + .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; + let executable = + verify_launch_artifact(&profile.executable, "OpenCode provider executable") + .map_err(|error| LocalRunnerError::invalid(error.to_string()))?; + let launch = VerifiedProcessLaunch::new( + command, + vec![ + VerifiedProcessArgument::Artifact(proxy), + VerifiedProcessArgument::Literal(TRUSTED_OPENCODE_EXECUTABLE_ARG.to_owned()), + VerifiedProcessArgument::ExecutableArtifact(executable), + ], + ); + SupervisedProcess::spawn_verified_with_environment_keys( + &launch, + Duration::from_secs(2), + CODEX_APP_SERVER_MAX_FRAME_BYTES, + &environment_keys, + )? + } else { + SupervisedProcess::spawn_with_environment_keys( &config.command, &config.args, Duration::from_secs(2), CODEX_APP_SERVER_MAX_FRAME_BYTES, &environment_keys, - )?, + )? + }; + let mut provider = Self { + process, config: config.clone(), authorized_tools, next_request_id: 1, @@ -599,6 +649,7 @@ impl CodexProvider { quarantined: false, trace: ProviderTraceSink::from_environment(), last_trace_frame_id: None, + opencode_launch_profile: opencode_launch_profile.cloned(), }; let initialized = provider.request( "initialize", @@ -798,6 +849,7 @@ impl CodexProvider { authorized_tools, Some(&thread_id), next_generation, + self.opencode_launch_profile.as_ref(), )?; replacement.durable_tool_call_replays = durable_tool_call_replays; if replacement.active_provider_turn_id.is_some() { @@ -2334,7 +2386,7 @@ mod tests { let mut config = CodexProviderConfig { provider: "opencode".to_owned(), driver: "opencode_server".to_owned(), - provider_version: "1.18.17".to_owned(), + provider_version: QUALIFIED_OPENCODE_VERSION.to_owned(), command: PathBuf::from("node"), args: Vec::new(), cwd: std::env::current_dir() @@ -2347,6 +2399,10 @@ mod tests { approval_policy: "never".to_owned(), }; config.validate().unwrap(); + config.provider_version = "1.18.18".to_owned(); + let error = config.validate().unwrap_err(); + assert!(error.to_string().contains(QUALIFIED_OPENCODE_VERSION)); + config.provider_version = QUALIFIED_OPENCODE_VERSION.to_owned(); config.driver = "codex_app_server".to_owned(); assert!(config.validate().is_err()); config.driver = "opencode_server".to_owned(); @@ -2354,6 +2410,11 @@ mod tests { assert!(config.validate().is_err()); } + #[test] + fn does_not_forward_an_ambient_opencode_command_override() { + assert!(!OPENCODE_PROVIDER_ENVIRONMENT_KEYS.contains(&"PAPERCLIP_OPENCODE_COMMAND")); + } + #[test] fn converts_codex_questions_and_responses_without_provider_leakage() { let (request_id, question_set, labels) = codex_question_set( diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs index 539ef5e944..e48f214afa 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/mod.rs @@ -7,6 +7,8 @@ use std::fmt::{self, Display, Formatter}; use std::path::PathBuf; use std::time::Duration; +use sha2::{Digest, Sha256}; + use crate::stable_identity::{is_stable_id, DURABLE_STABLE_ID_CHARS, SHORT_STABLE_ID_CHARS}; pub use runner::{run_durable_runner, CommandExecution, CommandExecutor, PolledEvent}; @@ -95,6 +97,74 @@ pub fn capture_bootstrap_ticket() -> Result, DurableRunn BootstrapTicket::new(value).map(Some) } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct QualifiedLaunchArtifact { + pub path: PathBuf, + pub sha256: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct AcpxLaunchProfile { + pub authority_digest: String, + pub command: PathBuf, + pub args: Vec, + pub artifacts: Vec, +} + +impl AcpxLaunchProfile { + pub fn canonical_digest(&self) -> Result { + fn update(hasher: &mut Sha256, value: &[u8]) { + hasher.update((value.len() as u64).to_be_bytes()); + hasher.update(value); + } + + let mut artifacts = self.artifacts.iter().collect::>(); + artifacts.sort_by(|left, right| left.path.cmp(&right.path)); + let mut digest = Sha256::new(); + update(&mut digest, b"paperclip.runner.acpx-launch-profile.v1"); + update(&mut digest, self.authority_digest.as_bytes()); + update( + &mut digest, + self.command + .to_str() + .ok_or_else(|| { + DurableRunnerError::invalid( + "ACPX launch profile command path must be valid UTF-8", + ) + })? + .as_bytes(), + ); + update(&mut digest, &(self.args.len() as u64).to_be_bytes()); + for argument in &self.args { + update(&mut digest, argument.as_bytes()); + } + update(&mut digest, &(artifacts.len() as u64).to_be_bytes()); + for artifact in artifacts { + update( + &mut digest, + artifact + .path + .to_str() + .ok_or_else(|| { + DurableRunnerError::invalid( + "ACPX launch artifact paths must be valid UTF-8", + ) + })? + .as_bytes(), + ); + update(&mut digest, artifact.sha256.as_bytes()); + } + Ok(format!("sha256:{:x}", digest.finalize())) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OpenCodeLaunchProfile { + pub command: QualifiedLaunchArtifact, + pub proxy_script: QualifiedLaunchArtifact, + pub executable: QualifiedLaunchArtifact, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct DurableRunnerConfig { pub connect_url: String, @@ -108,6 +178,8 @@ pub struct DurableRunnerConfig { pub item_id: String, pub runner_version: String, pub runner_digest: String, + pub acpx_launch_profile: Option, + pub opencode_launch_profile: Option, pub max_outbox_bytes: usize, pub p0_reserve_bytes: usize, pub max_frame_bytes: usize, @@ -184,6 +256,67 @@ impl DurableRunnerConfig { "durable runner max runtime must not exceed seven days", )); } + if let Some(profile) = self.acpx_launch_profile.as_ref() { + if profile.authority_digest.len() != 71 + || !profile.authority_digest.starts_with("sha256:") + || !profile.authority_digest[7..] + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + || !profile.command.is_absolute() + || profile.command.to_str().is_none() + || profile.args.len() > 32 + || profile.args.iter().any(|argument| { + argument.is_empty() || argument.len() > 4_096 || argument.contains('\0') + }) + || profile.artifacts.is_empty() + || profile.artifacts.len() > 8 + || profile.artifacts.iter().any(|artifact| { + !artifact.path.is_absolute() + || artifact.path.to_str().is_none() + || artifact.sha256.len() != 71 + || !artifact.sha256.starts_with("sha256:") + || !artifact.sha256[7..] + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + }) + || !profile + .artifacts + .iter() + .any(|artifact| artifact.path == profile.command) + || profile + .artifacts + .iter() + .enumerate() + .any(|(index, artifact)| { + profile.artifacts[..index] + .iter() + .any(|prior| prior.path == artifact.path) + }) + { + return Err(DurableRunnerError::invalid( + "ACPX runner launch profile is malformed", + )); + } + } + if let Some(profile) = self.opencode_launch_profile.as_ref() { + let artifacts = [&profile.command, &profile.proxy_script, &profile.executable]; + if artifacts.iter().any(|artifact| { + !artifact.path.is_absolute() + || artifact.path.to_str().is_none() + || artifact.sha256.len() != 71 + || !artifact.sha256.starts_with("sha256:") + || !artifact.sha256[7..] + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + }) || profile.command.path == profile.proxy_script.path + || profile.command.path == profile.executable.path + || profile.proxy_script.path == profile.executable.path + { + return Err(DurableRunnerError::invalid( + "OpenCode runner launch profile is malformed", + )); + } + } Ok(()) } } @@ -205,6 +338,8 @@ mod tests { item_id: "item-1".to_owned(), runner_version: "1.0.0".to_owned(), runner_digest: "sha256:digest".to_owned(), + acpx_launch_profile: None, + opencode_launch_profile: None, max_outbox_bytes: 1024 * 1024, p0_reserve_bytes: 64 * 1024, max_frame_bytes: 64 * 1024, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs index 190bd62185..bd59e77132 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/runner.rs @@ -616,6 +616,8 @@ mod tests { item_id: "item_1".to_owned(), runner_version: "0.0.0".to_owned(), runner_digest: "sha256:test".to_owned(), + acpx_launch_profile: None, + opencode_launch_profile: None, max_outbox_bytes: 64 * 1024, p0_reserve_bytes: 4096, max_frame_bytes: 64 * 1024, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs index febfef7cbe..e7aa7caa46 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/state.rs @@ -1207,6 +1207,8 @@ mod tests { item_id: "item_1".to_owned(), runner_version: "0.0.0".to_owned(), runner_digest: "sha256:test".to_owned(), + acpx_launch_profile: None, + opencode_launch_profile: None, max_outbox_bytes: 16_384, p0_reserve_bytes: 4096, max_frame_bytes: 65_536, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/durable/transport.rs b/packages/paperclip-runner/runner/crates/runner-core/src/durable/transport.rs index 6e42875ce6..4827c7cc8a 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/durable/transport.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/durable/transport.rs @@ -1627,6 +1627,8 @@ mod tests { item_id: "item_1".to_owned(), runner_version: "0.0.0".to_owned(), runner_digest: "sha256:test".to_owned(), + acpx_launch_profile: None, + opencode_launch_profile: None, max_outbox_bytes: 64 * 1024, p0_reserve_bytes: 4096, max_frame_bytes: 64 * 1024, diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/lib.rs b/packages/paperclip-runner/runner/crates/runner-core/src/lib.rs index 2252dde13b..475119d34b 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/lib.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/lib.rs @@ -7,16 +7,21 @@ pub mod acpx_provider_checkpoint; pub mod acpx_provider_session; pub mod acpx_provider_state; pub mod acpx_sidecar_transport; +pub mod aws_agentcore_provider; +pub mod claude_managed_provider; pub mod codex_provider; pub mod durable; pub mod fake_harness; pub mod generated_acpx_sidecar_contract; pub mod local_runner; +pub mod managed_provider; +pub mod managed_provider_backend; pub mod native_provider_backend; pub mod process_supervisor; pub mod provider_backend; pub mod provider_bridge; pub mod provider_events; +pub mod qualified_launch; pub mod question_response; pub mod replay; mod stable_identity; diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/managed_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/src/managed_provider.rs new file mode 100644 index 0000000000..7d430abee9 --- /dev/null +++ b/packages/paperclip-runner/runner/crates/runner-core/src/managed_provider.rs @@ -0,0 +1,159 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use crate::local_runner::LocalRunnerError; +use crate::provider_bridge::{AuthorizedTool, ToolResult}; + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ProviderKind { + ClaudeManaged, + AwsAgentcore, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct ClaudeManagedSkillRef { + pub skill_id: String, + pub version: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct ClaudeManagedProviderConfig { + pub model: String, + pub profile_id: String, + pub anthropic_agent_id: String, + pub agent_version: String, + pub environment_id: String, + pub beta_version: String, + pub max_session_list_cost_usd: f64, + pub instructions: String, + #[serde(default)] + pub runtime_context: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct AwsAgentCoreProviderConfig { + pub model: String, + pub profile_id: String, + pub region: String, + pub account_id: String, + pub harness_arn: String, + pub harness_version: String, + pub endpoint_arn: String, + pub endpoint_qualifier: String, + pub agent_runtime_arn: String, + pub memory_arn: String, + pub memory_id: String, + pub invocation_role_arn: String, + pub context_bucket: String, + pub context_prefix: String, + pub context_kms_key_arn: String, + pub qualification_revision: String, + pub event_expiry_days: u16, + pub max_estimated_session_cost_usd: f64, + pub max_iterations: u32, + pub max_output_tokens: u32, + pub timeout_seconds: u32, + pub instructions: String, + #[serde(default)] + pub runtime_context: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "camelCase", tag = "executionKind")] +pub enum ProviderRuntimeIdentity { + #[serde(rename = "remote_service")] + RemoteService { + service: String, + provider_session_id: String, + process_id: Option, + }, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ProviderEvent { + ToolCall { + call_id: String, + operation_id: String, + input: Value, + }, + Notification { + method: String, + params: Value, + }, + SemanticResult { + result: Value, + item_id: Option, + }, + RuntimeRequest { + request_id: String, + request_kind: String, + title: String, + details: Value, + }, + Exited, +} + +pub trait Provider { + fn kind(&self) -> ProviderKind; + fn runtime_identity(&self) -> ProviderRuntimeIdentity; + fn session_identity(&self) -> &str; + fn provider_session_id(&self) -> Option<&str>; + fn durable_event_cursor(&self) -> Option<&str> { + None + } + fn model_request_count(&self) -> Option { + None + } + fn usage_snapshot(&self) -> Option { + None + } + fn claude_managed_skills(&self) -> Option<&[ClaudeManagedSkillRef]> { + None + } + fn restore_active_turn(&mut self, _turn_id: &str) -> Result<(), LocalRunnerError> { + Err(LocalRunnerError::invalid( + "provider does not support active-turn recovery", + )) + } + fn restore_pending_tool_call( + &mut self, + _call_id: &str, + _operation_id: &str, + _input: &Value, + ) -> Result<(), LocalRunnerError> { + Err(LocalRunnerError::invalid( + "provider does not support pending tool-call recovery", + )) + } + fn configure_tools(&mut self, _tools: Vec) -> Result<(), LocalRunnerError> { + Ok(()) + } + fn increase_budget(&mut self, _maximum_cost_usd: f64) -> Result { + Err(LocalRunnerError::invalid( + "provider does not support a remote session budget", + )) + } + fn destroy_session(&mut self) -> Result<(), LocalRunnerError> { + Err(LocalRunnerError::invalid( + "provider does not support remote session deletion", + )) + } + fn preflight_turn(&mut self) -> Result<(), LocalRunnerError> { + Ok(()) + } + fn start_turn( + &mut self, + message: &str, + cwd: &str, + turn_id: &str, + ) -> Result; + fn interrupt_turn(&mut self, turn_id: &str) -> Result; + fn read(&mut self) -> Result; + fn poll(&mut self) -> Result, LocalRunnerError>; + fn deliver_tool_result(&mut self, result: &ToolResult) -> Result<(), LocalRunnerError>; + fn shutdown(&mut self) -> Result<(), LocalRunnerError>; +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/managed_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/managed_provider_backend.rs new file mode 100644 index 0000000000..69ec37b5c7 --- /dev/null +++ b/packages/paperclip-runner/runner/crates/runner-core/src/managed_provider_backend.rs @@ -0,0 +1,2964 @@ +use std::collections::{BTreeMap, HashSet, VecDeque}; +use std::fs::{self, DirBuilder}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +#[cfg(unix)] +use std::fs::File; +#[cfg(unix)] +use std::os::unix::fs::{DirBuilderExt, PermissionsExt}; + +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use uuid::Uuid; + +use crate::aws_agentcore_provider::{ + AwsAgentCoreHarnessProvider, AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD, + AGENTCORE_PENDING_CEILING_FIELD, AGENTCORE_PENDING_INVOCATION_FIELD, + AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE, AGENTCORE_USAGE_RECONCILIATION_FIELD, + AGENTCORE_USAGE_RECONCILIATION_OBSERVED, AGENTCORE_USAGE_RECONCILIATION_PENDING, +}; +use crate::claude_managed_provider::ClaudeManagedProvider; +use crate::durable::{ + create_private_temporary_file, open_private_regular_file, sanitize_value, + verify_private_directory, Command, CommandExecution, CommandExecutor, DurableRunnerConfig, + DurableRunnerError, EventPriority, PolledEvent, +}; +use crate::managed_provider::{ + AwsAgentCoreProviderConfig, ClaudeManagedProviderConfig, ClaudeManagedSkillRef, Provider, + ProviderEvent, ProviderRuntimeIdentity, +}; +use crate::provider_bridge::{ + authorized_tool_catalog_digest, semantic_value_digest, AuthorizedTool, AuthorizedToolSet, + PendingToolCall, ToolResult, MAX_PENDING_CALLS, TOOL_SET_SCHEMA, +}; +use crate::provider_events::{normalize_codex_notification, NormalizedProviderEvent}; + +pub const MANAGED_PROVIDER_STATE_FILE: &str = "managed-provider-state.json"; +const MANAGED_PROVIDER_STATE_SCHEMA: &str = "paperclip.runner.managed-provider-state.v1"; +const MAX_PROVIDER_STATE_BYTES: u64 = 16 * 1024 * 1024; +const MAX_PENDING_EVENTS: usize = 8_320; +const MAX_EVENTS_PER_POLL: usize = 128; +const MAX_INSTRUCTIONS_BYTES: usize = 1024 * 1024; +const QUALIFIED_CLAUDE_MODEL: &str = "claude-sonnet-5"; +const QUALIFIED_CLAUDE_BETA: &str = "managed-agents-2026-04-01"; +const QUALIFIED_AGENTCORE_MODEL: &str = "global.anthropic.claude-sonnet-4-6"; +const QUALIFIED_AGENTCORE_REVISION: &str = "aws-agentcore-harness-v1"; + +fn initial_event_sequence() -> u64 { + 1 +} + +fn event_id(sequence: u64) -> String { + format!("managed_provider_{sequence:016}") +} + +fn event_sequence(value: &str) -> Option { + let sequence = value.strip_prefix("managed_provider_")?.parse().ok()?; + (event_id(sequence) == value).then_some(sequence) +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct CompletionContractBinding { + revision: String, + criterion_ids: Vec, +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +enum ManagedProviderKind { + ClaudeManaged, + AwsAgentcore, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(tag = "kind", content = "config", rename_all = "snake_case")] +enum ManagedProviderDescriptor { + ClaudeManaged(ClaudeManagedProviderConfig), + AwsAgentcore(AwsAgentCoreProviderConfig), +} + +impl ManagedProviderDescriptor { + fn parse(value: Value) -> Result { + let mut object = value.as_object().cloned().ok_or_else(|| { + DurableRunnerError::invalid("managed run.prepare provider must be an object") + })?; + let kind = object + .remove("kind") + .and_then(|value| value.as_str().map(str::to_owned)) + .ok_or_else(|| DurableRunnerError::invalid("managed provider kind is required"))?; + let value = Value::Object(object); + match kind.as_str() { + "claude_managed" => serde_json::from_value(value) + .map(Self::ClaudeManaged) + .map_err(|error| { + DurableRunnerError::invalid(format!( + "Claude Managed provider descriptor is invalid: {error}" + )) + }), + "aws_agentcore" => serde_json::from_value(value) + .map(Self::AwsAgentcore) + .map_err(|error| { + DurableRunnerError::invalid(format!( + "AWS AgentCore provider descriptor is invalid: {error}" + )) + }), + _ => Err(DurableRunnerError::invalid( + "managed provider kind must be claude_managed or aws_agentcore", + )), + } + } + + fn kind(&self) -> ManagedProviderKind { + match self { + Self::ClaudeManaged(_) => ManagedProviderKind::ClaudeManaged, + Self::AwsAgentcore(_) => ManagedProviderKind::AwsAgentcore, + } + } + + fn provider_label(&self) -> &'static str { + match self { + Self::ClaudeManaged(_) => "claude_managed", + Self::AwsAgentcore(_) => "aws_agentcore", + } + } + + fn display_name(&self) -> &'static str { + match self { + Self::ClaudeManaged(_) => "Claude Managed Agent", + Self::AwsAgentcore(_) => "AWS AgentCore Harness", + } + } + + fn driver(&self) -> &'static str { + match self { + Self::ClaudeManaged(_) => "claude_managed_agents_api", + Self::AwsAgentcore(_) => "aws_agentcore_harness_api", + } + } + + fn version(&self) -> &str { + match self { + Self::ClaudeManaged(config) => &config.beta_version, + Self::AwsAgentcore(config) => &config.qualification_revision, + } + } + + fn model(&self) -> &str { + match self { + Self::ClaudeManaged(config) => &config.model, + Self::AwsAgentcore(config) => &config.model, + } + } + + fn validate(&self) -> Result<(), DurableRunnerError> { + let valid_text = |value: &str, limit: usize| { + !value.trim().is_empty() && value.len() <= limit && !value.chars().any(char::is_control) + }; + match self { + Self::ClaudeManaged(config) => { + if config.model != QUALIFIED_CLAUDE_MODEL + || config.beta_version != QUALIFIED_CLAUDE_BETA + || ![ + config.profile_id.as_str(), + config.anthropic_agent_id.as_str(), + config.agent_version.as_str(), + config.environment_id.as_str(), + ] + .iter() + .all(|value| valid_text(value, 512)) + || config.instructions.is_empty() + || config.instructions.len() > MAX_INSTRUCTIONS_BYTES + || config.instructions.contains('\0') + || !config.max_session_list_cost_usd.is_finite() + || config.max_session_list_cost_usd < 0.01 + || ((config.max_session_list_cost_usd * 100.0).round() + - config.max_session_list_cost_usd * 100.0) + .abs() + > 0.000_001 + || config + .runtime_context + .as_ref() + .is_some_and(|value| !value.is_object()) + { + return Err(DurableRunnerError::invalid( + "Claude Managed provider does not match the qualified immutable profile", + )); + } + } + Self::AwsAgentcore(config) => { + let arn_prefix = format!( + "arn:aws:bedrock-agentcore:{}:{}:", + config.region, config.account_id + ); + let role_prefix = format!("arn:aws:iam::{}:role/", config.account_id); + if config.model != QUALIFIED_AGENTCORE_MODEL + || config.qualification_revision != QUALIFIED_AGENTCORE_REVISION + || ![ + config.profile_id.as_str(), + config.region.as_str(), + config.account_id.as_str(), + config.harness_version.as_str(), + config.endpoint_qualifier.as_str(), + config.memory_id.as_str(), + config.context_bucket.as_str(), + ] + .iter() + .all(|value| valid_text(value, 512)) + || !config.harness_arn.starts_with(&arn_prefix) + || !config.endpoint_arn.starts_with(&arn_prefix) + || !config.agent_runtime_arn.starts_with(&arn_prefix) + || !config.memory_arn.starts_with(&arn_prefix) + || !config.invocation_role_arn.starts_with(&role_prefix) + || !config.context_kms_key_arn.starts_with(&format!( + "arn:aws:kms:{}:{}:key/", + config.region, config.account_id + )) + || config.context_prefix.starts_with('/') + || config + .context_prefix + .split('/') + .any(|part| part.is_empty() || part == "." || part == "..") + || config.event_expiry_days != 90 + || config.max_iterations == 0 + || config.max_iterations > 8 + || config.max_output_tokens == 0 + || config.max_output_tokens > 4096 + || config.timeout_seconds == 0 + || config.timeout_seconds > 300 + || !config.max_estimated_session_cost_usd.is_finite() + || config.max_estimated_session_cost_usd <= 0.0 + || config.instructions.is_empty() + || config.instructions.len() > MAX_INSTRUCTIONS_BYTES + || config.instructions.contains('\0') + || config + .runtime_context + .as_ref() + .is_some_and(|value| !value.is_object()) + { + return Err(DurableRunnerError::invalid( + "AWS AgentCore provider does not match the qualified immutable profile", + )); + } + } + } + Ok(()) + } + + fn set_budget(&mut self, value: f64) { + match self { + Self::ClaudeManaged(config) => config.max_session_list_cost_usd = value, + Self::AwsAgentcore(config) => config.max_estimated_session_cost_usd = value, + } + } +} + +#[derive(Debug)] +struct ManagedProviderStartError { + error: DurableRunnerError, + claude_skill_cleanup: Option>, + claude_durable_skills: Option>, + recovery_session_id: Option, +} + +impl ManagedProviderStartError { + fn plain(error: DurableRunnerError) -> Self { + Self { + error, + claude_skill_cleanup: None, + claude_durable_skills: None, + recovery_session_id: None, + } + } +} + +trait ManagedProviderFactory { + fn start( + &self, + descriptor: &ManagedProviderDescriptor, + tools: Vec, + ownership_scope: &str, + resume_session_id: Option<&str>, + resume_event_cursor: Option<&str>, + resume_model_request_count: u64, + resume_usage: Option<&Value>, + resume_claude_managed_skills: Option<&[ClaudeManagedSkillRef]>, + pending_claude_skill_cleanup: Option<&[ClaudeManagedSkillRef]>, + ) -> Result, ManagedProviderStartError>; +} + +struct DefaultManagedProviderFactory; + +impl ManagedProviderFactory for DefaultManagedProviderFactory { + fn start( + &self, + descriptor: &ManagedProviderDescriptor, + tools: Vec, + ownership_scope: &str, + resume_session_id: Option<&str>, + resume_event_cursor: Option<&str>, + resume_model_request_count: u64, + resume_usage: Option<&Value>, + resume_claude_managed_skills: Option<&[ClaudeManagedSkillRef]>, + pending_claude_skill_cleanup: Option<&[ClaudeManagedSkillRef]>, + ) -> Result, ManagedProviderStartError> { + match descriptor { + ManagedProviderDescriptor::ClaudeManaged(config) => ClaudeManagedProvider::start( + config, + tools, + ownership_scope, + resume_session_id, + resume_event_cursor, + resume_model_request_count, + resume_claude_managed_skills, + pending_claude_skill_cleanup, + ) + .map(|provider| Box::new(provider) as Box) + .map_err(|error| { + let claude_skill_cleanup = error.cleanup_inventory().map(<[_]>::to_vec); + let claude_durable_skills = error.durable_skills().map(<[_]>::to_vec); + let recovery_session_id = error.recovery_session_id().map(str::to_owned); + ManagedProviderStartError { + error: DurableRunnerError::invalid(format!( + "failed to start Claude Managed provider: {error}" + )), + claude_skill_cleanup, + claude_durable_skills, + recovery_session_id, + } + }), + ManagedProviderDescriptor::AwsAgentcore(config) => AwsAgentCoreHarnessProvider::start( + config, + tools, + resume_session_id, + resume_event_cursor, + resume_usage, + ) + .map(|provider| Box::new(provider) as Box) + .map_err(|error| { + ManagedProviderStartError::plain(DurableRunnerError::invalid(format!( + "failed to start AWS AgentCore provider: {error}" + ))) + }), + } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct ManagedDurableState { + schema: String, + run_id: String, + normalized_session_id: String, + lifecycle: String, + descriptor: ManagedProviderDescriptor, + tool_set: AuthorizedToolSet, + #[serde(default)] + completion_contract: Option, + #[serde(default)] + provider_session_id: Option, + #[serde(default)] + durable_event_cursor: Option, + #[serde(default)] + model_request_count: u64, + #[serde(default)] + provider_usage: Option, + #[serde(default)] + claude_managed_skills: Option>, + #[serde(default)] + claude_managed_skill_cleanup: Option>, + #[serde(default)] + active_turn_id: Option, + #[serde(default)] + last_agent_message: Option, + #[serde(default)] + pending_tool_calls: BTreeMap, + #[serde(default)] + ambiguous_tool_deliveries: BTreeMap, + #[serde(default)] + pending_events: VecDeque, + #[serde(default = "initial_event_sequence")] + next_event_sequence: u64, +} + +fn valid_agentcore_usage_snapshot(value: &Value) -> bool { + let Some(object) = value.as_object() else { + return false; + }; + let pending_ceiling = match object.get(AGENTCORE_PENDING_CEILING_FIELD) { + None => None, + Some(value) => match value + .as_f64() + .filter(|value| value.is_finite() && *value > 0.0) + { + Some(value) => Some(value), + None => return false, + }, + }; + let conservative_floor = match object.get(AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD) { + None => None, + Some(value) => match value + .as_f64() + .filter(|value| value.is_finite() && *value > 0.0) + { + Some(value) => Some(value), + None => return false, + }, + }; + let reconciliation_valid = match ( + object.get(AGENTCORE_USAGE_RECONCILIATION_FIELD), + object.get(AGENTCORE_PENDING_INVOCATION_FIELD), + pending_ceiling, + ) { + (None, None, None) => conservative_floor.is_none(), + (Some(reconciliation), None, None) => { + reconciliation.as_str() == Some(AGENTCORE_USAGE_RECONCILIATION_OBSERVED) + || (reconciliation.as_str() == Some(AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE) + && conservative_floor.is_some()) + } + (Some(reconciliation), Some(invocation_id), _) => { + reconciliation.as_str() == Some(AGENTCORE_USAGE_RECONCILIATION_PENDING) + && invocation_id.as_str().is_some_and(|invocation_id| { + !invocation_id.is_empty() + && invocation_id.len() <= 512 + && !invocation_id.chars().any(char::is_control) + }) + } + _ => false, + }; + let estimated_cost = object + .get("estimatedCostUsd") + .and_then(Value::as_f64) + .filter(|value| value.is_finite() && *value >= 0.0); + reconciliation_valid + && [ + "inputTokens", + "outputTokens", + "cacheReadInputTokens", + "cacheWriteInputTokens", + "requestCount", + ] + .iter() + .all(|field| object.get(*field).and_then(Value::as_u64).is_some()) + && estimated_cost.is_some() + && conservative_floor + .zip(estimated_cost) + .is_none_or(|(floor, estimated)| estimated >= floor) + && object.get("costSource").and_then(Value::as_str) == Some("paperclip_estimate") +} + +fn valid_claude_managed_skill_ref(value: &ClaudeManagedSkillRef) -> bool { + let valid_id = |text: &str, limit: usize| { + !text.is_empty() + && text.len() <= limit + && text + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-')) + }; + valid_id(&value.skill_id, 512) && valid_id(&value.version, 512) +} + +fn expected_claude_managed_skill_count(config: &ClaudeManagedProviderConfig) -> Option { + match config.runtime_context.as_ref() { + None => Some(0), + Some(context) => context + .get("skills") + .and_then(Value::as_array) + .and_then(|skills| skills.len().checked_add(1)), + } +} + +impl ManagedDurableState { + fn new( + run_id: String, + normalized_session_id: String, + descriptor: ManagedProviderDescriptor, + tool_set: AuthorizedToolSet, + completion_contract: Option, + ) -> Self { + Self { + schema: MANAGED_PROVIDER_STATE_SCHEMA.to_owned(), + run_id, + normalized_session_id, + lifecycle: "prepared".to_owned(), + descriptor, + tool_set, + completion_contract, + provider_session_id: None, + durable_event_cursor: None, + model_request_count: 0, + provider_usage: None, + claude_managed_skills: None, + claude_managed_skill_cleanup: None, + active_turn_id: None, + last_agent_message: None, + pending_tool_calls: BTreeMap::new(), + ambiguous_tool_deliveries: BTreeMap::new(), + pending_events: VecDeque::new(), + next_event_sequence: initial_event_sequence(), + } + } + + fn validate(&self, config: &DurableRunnerConfig) -> Result<(), DurableRunnerError> { + self.descriptor.validate()?; + let mut event_ids = HashSet::new(); + let valid_skill_inventory = |skills: &[ClaudeManagedSkillRef], maximum: usize| { + let mut ids = HashSet::new(); + !skills.is_empty() + && skills.len() <= maximum + && skills.iter().all(|skill| { + valid_claude_managed_skill_ref(skill) && ids.insert(skill.skill_id.as_str()) + }) + }; + let valid_claude_managed_skills = match &self.descriptor { + ManagedProviderDescriptor::AwsAgentcore(_) => { + self.claude_managed_skills.is_none() && self.claude_managed_skill_cleanup.is_none() + } + ManagedProviderDescriptor::ClaudeManaged(descriptor) => { + let persisted_resources_required = self.provider_session_id.is_some(); + let expected = expected_claude_managed_skill_count(descriptor); + let owned_valid = match self.claude_managed_skills.as_ref() { + None => !persisted_resources_required, + Some(skills) => { + let mut ids = HashSet::new(); + expected == Some(skills.len()) + && skills.iter().all(|skill| { + valid_claude_managed_skill_ref(skill) + && ids.insert(skill.skill_id.as_str()) + }) + } + }; + let cleanup_valid = match self.claude_managed_skill_cleanup.as_ref() { + None => true, + Some(skills) => { + expected.is_some_and(|expected| valid_skill_inventory(skills, expected)) + } + }; + owned_valid + && cleanup_valid + && !(self.claude_managed_skills.is_some() + && self.claude_managed_skill_cleanup.is_some()) + && !(self.provider_session_id.is_some() + && self.claude_managed_skill_cleanup.is_some()) + } + }; + if self.schema != MANAGED_PROVIDER_STATE_SCHEMA + || self.run_id != config.run_id + || self.normalized_session_id != config.normalized_session_id + || !matches!( + self.lifecycle.as_str(), + "prepared" + | "session_opening" + | "session_open" + | "turn_starting" + | "turn_active" + | "suspended" + | "failed" + | "closed" + ) + || self.provider_session_id.as_ref().is_some_and(|value| { + value.is_empty() || value.len() > 512 || value.chars().any(char::is_control) + }) + || self.active_turn_id.as_ref().is_some_and(|value| { + value.is_empty() || value.len() > 512 || value.chars().any(char::is_control) + }) + || (matches!(self.lifecycle.as_str(), "turn_starting" | "turn_active") + != self.active_turn_id.is_some()) + || (matches!(self.lifecycle.as_str(), "session_open" | "suspended") + && self.provider_session_id.is_none()) + || !valid_claude_managed_skills + || match self.descriptor.kind() { + ManagedProviderKind::ClaudeManaged => self.provider_usage.is_some(), + ManagedProviderKind::AwsAgentcore => { + if matches!(self.lifecycle.as_str(), "prepared" | "session_opening") { + self.provider_usage + .as_ref() + .is_some_and(|value| !valid_agentcore_usage_snapshot(value)) + } else { + self.provider_usage + .as_ref() + .is_none_or(|value| !valid_agentcore_usage_snapshot(value)) + } + } + } + || self + .last_agent_message + .as_ref() + .is_some_and(|value| value.is_empty() || value.len() > 1_000_000) + || self.pending_tool_calls.len() > MAX_PENDING_CALLS + || self.ambiguous_tool_deliveries.len() > MAX_PENDING_CALLS + || self.next_event_sequence == 0 + || self.pending_events.len() > MAX_PENDING_EVENTS + || self.pending_events.iter().any(|event| { + event_sequence(&event.executor_event_id) + .is_none_or(|sequence| sequence >= self.next_event_sequence) + || !event_ids.insert(event.executor_event_id.as_str()) + || event.event_type.is_empty() + || event.event_type.len() > 160 + || !event.payload.is_object() + }) + { + return Err(DurableRunnerError::invalid( + "managed provider state is malformed or conflicts with runner identity", + )); + } + if let Some(contract) = self.completion_contract.as_ref() { + if contract.revision.is_empty() + || contract.revision.len() > 120 + || contract.criterion_ids.is_empty() + || contract.criterion_ids.len() > 256 + || contract.criterion_ids.iter().any(|criterion| { + criterion.is_empty() + || criterion.len() > 240 + || criterion.chars().any(char::is_control) + }) + { + return Err(DurableRunnerError::invalid( + "managed completion contract is malformed", + )); + } + } + for (call_id, call) in &self.pending_tool_calls { + if call_id != &call.call_id + || call_id.is_empty() + || call_id.len() > 512 + || call.operation_id.is_empty() + || call.operation_id.len() > 512 + || !call.input.is_object() + { + return Err(DurableRunnerError::invalid( + "managed pending tool call is malformed", + )); + } + } + for (call_id, result) in &self.ambiguous_tool_deliveries { + if call_id != &result.call_id || !self.pending_tool_calls.contains_key(call_id) { + return Err(DurableRunnerError::invalid( + "managed ambiguous tool delivery is inconsistent", + )); + } + } + Ok(()) + } + + fn push(&mut self, event: NormalizedProviderEvent) -> Result<(), DurableRunnerError> { + if self.pending_events.len() >= MAX_PENDING_EVENTS { + return Err(DurableRunnerError::invalid( + "managed provider event backlog exceeds its durable limit", + )); + } + let sequence = self.next_event_sequence; + self.next_event_sequence = sequence.checked_add(1).ok_or_else(|| { + DurableRunnerError::invalid("managed provider event sequence exhausted") + })?; + self.pending_events.push_back(PolledEvent { + executor_event_id: event_id(sequence), + event_type: event.event_type, + priority: event.priority, + payload: event.payload, + }); + Ok(()) + } +} + +pub struct ManagedProviderCommandExecutor { + state_dir: PathBuf, + config: DurableRunnerConfig, + state: Option, + provider: Option>, + restore_checked: bool, + factory: Box, +} + +impl ManagedProviderCommandExecutor { + pub fn with_runner_config(state_dir: impl Into, config: &DurableRunnerConfig) -> Self { + Self { + state_dir: state_dir.into(), + config: config.clone(), + state: None, + provider: None, + restore_checked: false, + factory: Box::new(DefaultManagedProviderFactory), + } + } + + #[cfg(test)] + fn with_factory( + state_dir: impl Into, + config: &DurableRunnerConfig, + factory: Box, + ) -> Self { + Self { + factory, + ..Self::with_runner_config(state_dir, config) + } + } + + pub fn state_path(&self) -> PathBuf { + self.state_dir.join(MANAGED_PROVIDER_STATE_FILE) + } + + fn restore(&mut self) -> Result<(), DurableRunnerError> { + if self.restore_checked { + return Ok(()); + } + self.restore_checked = true; + let path = self.state_path(); + let mut file = match open_private_regular_file(&path) { + Ok(file) => file, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()), + Err(error) => { + return Err(DurableRunnerError::invalid(format!( + "failed to open private managed provider state: {error}" + ))) + } + }; + let length = file + .metadata() + .map_err(|error| { + DurableRunnerError::invalid(format!( + "failed to inspect managed provider state: {error}" + )) + })? + .len(); + if length > MAX_PROVIDER_STATE_BYTES { + return Err(DurableRunnerError::invalid( + "managed provider state exceeds the 16 MiB limit", + )); + } + let mut bytes = Vec::with_capacity(length as usize); + file.read_to_end(&mut bytes).map_err(|error| { + DurableRunnerError::invalid(format!("failed to read managed provider state: {error}")) + })?; + let state: ManagedDurableState = serde_json::from_slice(&bytes).map_err(|error| { + DurableRunnerError::invalid(format!("managed provider state is malformed: {error}")) + })?; + state.validate(&self.config)?; + self.state = Some(state); + self.restore_provider_if_needed() + } + + fn restore_provider_if_needed(&mut self) -> Result<(), DurableRunnerError> { + if self.provider.is_some() { + return Ok(()); + } + let Some(state) = self.state.as_ref() else { + return Ok(()); + }; + if !matches!( + state.lifecycle.as_str(), + "session_opening" | "session_open" | "turn_starting" | "turn_active" | "suspended" + ) { + return Ok(()); + } + if !state.ambiguous_tool_deliveries.is_empty() { + return Err(DurableRunnerError::invalid( + "managed tool-result delivery is ambiguous; recovery refuses to redeliver it", + )); + } + let active = state.active_turn_id.clone(); + if active.is_some() && state.descriptor.kind() == ManagedProviderKind::AwsAgentcore { + let state = self + .state + .as_mut() + .expect("managed state remains present during recovery"); + let prior_turn = state.active_turn_id.take(); + state.lifecycle = "failed".to_owned(); + state.push(NormalizedProviderEvent { + event_type: "turn.failed".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "provider": "aws_agentcore", + "providerTurnId": prior_turn, + "status": "failed", + "providerTerminalObserved": false, + "code": "agentcore_active_turn_recovery_requires_review", + }), + })?; + let terminal = terminal_events(state, "turn.failed"); + for event in terminal { + state.push(event)?; + } + self.save_state()?; + return Ok(()); + } + let recovering_remote = self + .state + .as_ref() + .and_then(|state| state.provider_session_id.as_ref()) + .is_some(); + let mut provider = match self.start_provider(recovering_remote) { + Ok(provider) => provider, + Err(start_error) => { + if let Some(skills) = start_error.claude_durable_skills { + let state = self + .state + .as_mut() + .expect("managed state remains present during recovery"); + state.claude_managed_skills = Some(skills); + state.claude_managed_skill_cleanup = None; + state.provider_session_id = start_error.recovery_session_id; + self.save_state()?; + } else if let Some(inventory) = start_error.claude_skill_cleanup { + let state = self + .state + .as_mut() + .expect("managed state remains present during recovery"); + state.claude_managed_skills = None; + state.claude_managed_skill_cleanup = + (!inventory.is_empty()).then_some(inventory); + self.save_state()?; + } + return Err(start_error.error); + } + }; + if let Some(state) = self.state.as_mut() { + state.claude_managed_skill_cleanup = None; + } + if let Some(turn_id) = active.as_deref() { + provider.restore_active_turn(turn_id).map_err(|error| { + DurableRunnerError::invalid(format!( + "failed to restore managed active turn: {error}" + )) + })?; + } + let session_id = provider.session_identity().to_owned(); + let runtime = provider.runtime_identity(); + let state = self + .state + .as_mut() + .expect("managed state remains present during recovery"); + state.provider_session_id = Some(session_id); + state.lifecycle = if active.is_some() { + "turn_active".to_owned() + } else { + "session_open".to_owned() + }; + state.push(NormalizedProviderEvent { + event_type: "session.resumed".to_owned(), + priority: EventPriority::P0, + payload: session_event_payload(&state.descriptor, &runtime), + })?; + self.provider = Some(provider); + self.refresh_provider_checkpoint(); + self.save_state() + } + + fn start_provider( + &self, + recovering: bool, + ) -> Result, ManagedProviderStartError> { + let state = self.state.as_ref().ok_or_else(|| { + ManagedProviderStartError::plain(DurableRunnerError::invalid( + "managed provider has not been prepared", + )) + })?; + let ownership_scope = format!("{}:{}", state.run_id, state.normalized_session_id); + let mut provider = self.factory.start( + &state.descriptor, + state.tool_set.operations.clone(), + &ownership_scope, + recovering + .then(|| state.provider_session_id.as_deref()) + .flatten(), + recovering + .then(|| state.durable_event_cursor.as_deref()) + .flatten(), + state.model_request_count, + state.provider_usage.as_ref(), + state.claude_managed_skills.as_deref(), + state.claude_managed_skill_cleanup.as_deref(), + )?; + if recovering { + if let Some(expected) = state.provider_session_id.as_deref() { + if provider.session_identity() != expected { + let _ = provider.shutdown(); + return Err(ManagedProviderStartError::plain( + DurableRunnerError::invalid( + "managed provider resumed a different remote session", + ), + )); + } + } + for call in state.pending_tool_calls.values() { + provider + .restore_pending_tool_call(&call.call_id, &call.operation_id, &call.input) + .map_err(|error| { + ManagedProviderStartError::plain(DurableRunnerError::invalid(format!( + "failed to restore managed tool call: {error}" + ))) + })?; + } + } + Ok(provider) + } + + fn refresh_provider_checkpoint(&mut self) { + let Some(provider) = self.provider.as_ref() else { + return; + }; + let Some(state) = self.state.as_mut() else { + return; + }; + state.provider_session_id = provider.provider_session_id().map(str::to_owned); + state.durable_event_cursor = provider.durable_event_cursor().map(str::to_owned); + if let Some(count) = provider.model_request_count() { + state.model_request_count = count; + } + if let Some(usage) = provider.usage_snapshot() { + state.provider_usage = Some(usage); + } + if let Some(skills) = provider.claude_managed_skills() { + state.claude_managed_skills = Some(skills.to_vec()); + } + } + + fn save_state(&self) -> Result<(), DurableRunnerError> { + let state = self + .state + .as_ref() + .ok_or_else(|| DurableRunnerError::invalid("managed provider state is unavailable"))?; + state.validate(&self.config)?; + secure_directory(&self.state_dir, "managed provider state")?; + let path = self.state_path(); + let bytes = serde_json::to_vec_pretty(state).map_err(|error| { + DurableRunnerError::invalid(format!( + "failed to serialize managed provider state: {error}" + )) + })?; + if bytes.len() as u64 > MAX_PROVIDER_STATE_BYTES { + return Err(DurableRunnerError::invalid( + "managed provider state exceeds the 16 MiB limit", + )); + } + let (temporary, mut file) = create_private_temporary_file(&path)?; + let result = (|| -> std::io::Result<()> { + file.write_all(&bytes)?; + file.sync_all()?; + drop(file); + fs::rename(&temporary, &path)?; + #[cfg(unix)] + File::open(&self.state_dir)?.sync_all()?; + Ok(()) + })(); + if let Err(error) = result { + let _ = fs::remove_file(&temporary); + return Err(DurableRunnerError::invalid(format!( + "failed to atomically replace managed provider state: {error}" + ))); + } + #[cfg(unix)] + fs::set_permissions(&path, fs::Permissions::from_mode(0o600)).map_err(|error| { + DurableRunnerError::invalid(format!( + "failed to protect managed provider state: {error}" + )) + })?; + Ok(()) + } + + fn prepare(&mut self, payload: &Value) -> Result { + let descriptor = ManagedProviderDescriptor::parse( + payload + .get("provider") + .cloned() + .ok_or_else(|| DurableRunnerError::invalid("run.prepare requires provider"))?, + )?; + descriptor.validate()?; + let tool_set = authorized_tool_set(payload)?; + let completion_contract = completion_contract(payload)?; + if let Some(state) = self.state.as_ref() { + if state.descriptor != descriptor + || state.tool_set != tool_set + || state.completion_contract != completion_contract + { + return Err(DurableRunnerError::invalid( + "managed provider profile, tools, or completion contract changed across the durable run", + )); + } + if state.lifecycle == "closed" { + return Err(DurableRunnerError::invalid( + "managed provider session is already closed", + )); + } + } else { + self.state = Some(ManagedDurableState::new( + self.config.run_id.clone(), + self.config.normalized_session_id.clone(), + descriptor, + tool_set, + completion_contract, + )); + self.save_state()?; + } + let state = self + .state + .as_ref() + .expect("managed state exists after prepare"); + Ok(CommandExecution::result(json!({ + "status": "prepared", + "provider": state.descriptor.provider_label(), + "driver": state.descriptor.driver(), + }))) + } + + fn open_session(&mut self) -> Result { + if self.provider.is_none() { + let kind = self + .state + .as_ref() + .ok_or_else(|| { + DurableRunnerError::invalid("managed provider has not been prepared") + })? + .descriptor + .kind(); + let lifecycle = self + .state + .as_ref() + .expect("managed state exists") + .lifecycle + .clone(); + if lifecycle == "prepared" { + let state = self.state.as_mut().expect("managed state exists"); + state.lifecycle = "session_opening".to_owned(); + if kind == ManagedProviderKind::AwsAgentcore { + state.provider_session_id = + Some(format!("paperclip-{}-{}", Uuid::new_v4(), Uuid::new_v4())); + } + self.save_state()?; + } else if !matches!( + lifecycle.as_str(), + "session_opening" | "session_open" | "suspended" + ) { + return Err(DurableRunnerError::invalid( + "managed provider session cannot be opened from its current lifecycle", + )); + } + let recovering = self + .state + .as_ref() + .and_then(|state| state.provider_session_id.as_ref()) + .is_some(); + match self.start_provider(recovering) { + Ok(provider) => { + if let Some(state) = self.state.as_mut() { + state.claude_managed_skill_cleanup = None; + } + self.provider = Some(provider); + } + Err(start_error) => { + if let Some(skills) = start_error.claude_durable_skills { + let state = self.state.as_mut().expect("managed state exists"); + state.claude_managed_skills = Some(skills); + state.claude_managed_skill_cleanup = None; + state.provider_session_id = start_error.recovery_session_id; + self.save_state()?; + } else if let Some(inventory) = start_error.claude_skill_cleanup { + let state = self.state.as_mut().expect("managed state exists"); + state.claude_managed_skills = None; + state.claude_managed_skill_cleanup = + (!inventory.is_empty()).then_some(inventory); + self.save_state()?; + } + return Err(start_error.error); + } + } + } + let provider = self + .provider + .as_ref() + .expect("managed provider exists after open"); + let session_id = provider.session_identity().to_owned(); + let runtime = provider.runtime_identity(); + let resumed = self + .state + .as_ref() + .and_then(|state| state.provider_session_id.as_deref()) + .is_some_and(|expected| expected == session_id); + let state = self + .state + .as_mut() + .expect("managed state exists after open"); + state.provider_session_id = Some(session_id.clone()); + state.active_turn_id = None; + state.lifecycle = "session_open".to_owned(); + let payload = session_event_payload(&state.descriptor, &runtime); + let provider_label = state.descriptor.provider_label(); + let driver = state.descriptor.driver(); + let version = state.descriptor.version().to_owned(); + self.refresh_provider_checkpoint(); + self.save_state()?; + Ok(CommandExecution { + result: json!({ + "status": if resumed { "resumed" } else { "started" }, + "provider": provider_label, + "driver": driver, + "providerVersion": version, + "providerSessionId": session_id, + }), + events: vec![( + if resumed { + "session.resumed" + } else { + "session.started" + } + .to_owned(), + EventPriority::P0, + payload, + )], + }) + } + + fn start_turn(&mut self, payload: &Value) -> Result { + let text = payload + .get("text") + .and_then(Value::as_str) + .filter(|value| !value.is_empty() && value.len() <= MAX_INSTRUCTIONS_BYTES) + .ok_or_else(|| { + DurableRunnerError::invalid("turn.start payload.text is required and bounded") + })?; + if self.provider.is_none() { + self.open_session()?; + } + let state = self + .state + .as_ref() + .ok_or_else(|| DurableRunnerError::invalid("managed provider is not prepared"))?; + if state.lifecycle != "session_open" + || !state.pending_tool_calls.is_empty() + || !state.ambiguous_tool_deliveries.is_empty() + { + return Err(DurableRunnerError::invalid( + "managed provider cannot start a turn while prior work is unsettled", + )); + } + let preflight = self + .provider + .as_mut() + .expect("managed provider exists before turn preflight") + .preflight_turn(); + // A conservative AgentCore reconciliation must become durable even + // when the resulting ceiling gate rejects this turn. It emits no + // event after the prior terminal and is idempotent across restart. + self.refresh_provider_checkpoint(); + self.save_state()?; + preflight.map_err(|error| { + DurableRunnerError::invalid(format!("managed turn preflight failed: {error}")) + })?; + let state = self + .state + .as_mut() + .expect("managed state exists after turn preflight"); + let turn_id = self.config.turn_id.clone(); + state.lifecycle = "turn_starting".to_owned(); + state.active_turn_id = Some(turn_id.clone()); + state.last_agent_message = None; + self.save_state()?; + let response = self + .provider + .as_mut() + .expect("managed provider exists before turn start") + .start_turn(text, "", &turn_id) + .map_err(|error| { + DurableRunnerError::invalid(format!( + "managed turn start is ambiguous and recovery must reconcile it: {error}" + )) + })?; + let state = self + .state + .as_mut() + .expect("managed state exists after start"); + state.lifecycle = "turn_active".to_owned(); + self.refresh_provider_checkpoint(); + self.save_state()?; + Ok(CommandExecution::result(json!({ + "status": "started", + "providerTurnId": turn_id, + "providerResponse": sanitize_value(&response), + }))) + } + + fn interrupt_turn(&mut self, reason: &str) -> Result { + self.restore_provider_if_needed()?; + let turn_id = self + .state + .as_ref() + .and_then(|state| state.active_turn_id.clone()); + let Some(turn_id) = turn_id else { + return Ok(CommandExecution::result(json!({ + "status": "already_settled", + "reason": reason, + }))); + }; + self.provider + .as_mut() + .ok_or_else(|| { + DurableRunnerError::invalid( + "managed active turn has no recoverable provider connection", + ) + })? + .interrupt_turn(&turn_id) + .map_err(|error| { + DurableRunnerError::invalid(format!("managed turn interrupt is ambiguous: {error}")) + })?; + self.refresh_provider_checkpoint(); + self.save_state()?; + Ok(CommandExecution::result(json!({ + "status": "interrupt_requested", + "reason": reason, + "providerTurnId": turn_id, + }))) + } + + fn deliver_tool_result( + &mut self, + payload: &Value, + ) -> Result { + let result: ToolResult = serde_json::from_value(payload.clone()).map_err(|error| { + DurableRunnerError::invalid(format!("semantic tool result is invalid: {error}")) + })?; + let state = self + .state + .as_ref() + .ok_or_else(|| DurableRunnerError::invalid("managed provider is not prepared"))?; + let pending = state + .pending_tool_calls + .get(&result.call_id) + .ok_or_else(|| { + DurableRunnerError::invalid( + "managed tool result does not match a pending tool call", + ) + })?; + if pending.operation_id != result.operation_id { + return Err(DurableRunnerError::invalid( + "managed tool result operation conflicts with the pending tool call", + )); + } + if state + .ambiguous_tool_deliveries + .contains_key(&result.call_id) + { + return Err(DurableRunnerError::invalid( + "managed tool-result delivery is ambiguous; refusing duplicate delivery", + )); + } + self.state + .as_mut() + .expect("managed state exists") + .ambiguous_tool_deliveries + .insert(result.call_id.clone(), result.clone()); + self.save_state()?; + self.provider + .as_mut() + .ok_or_else(|| DurableRunnerError::invalid("managed provider is unavailable"))? + .deliver_tool_result(&result) + .map_err(|error| { + DurableRunnerError::invalid(format!( + "managed tool-result delivery is ambiguous: {error}" + )) + })?; + let state = self.state.as_mut().expect("managed state exists"); + state.pending_tool_calls.remove(&result.call_id); + state.ambiguous_tool_deliveries.remove(&result.call_id); + self.refresh_provider_checkpoint(); + self.save_state()?; + Ok(CommandExecution::result(json!({ + "status": "delivered", + "callId": result.call_id, + }))) + } + + fn change_budget(&mut self, payload: &Value) -> Result { + let value = payload + .get("maximumCostUsd") + .or_else(|| payload.get("maxSessionListCostUsd")) + .or_else(|| payload.get("maxEstimatedSessionCostUsd")) + .and_then(Value::as_f64) + .filter(|value| value.is_finite() && *value > 0.0) + .ok_or_else(|| { + DurableRunnerError::invalid("managed budget raise requires maximumCostUsd") + })?; + self.restore_provider_if_needed()?; + let response = self + .provider + .as_mut() + .ok_or_else(|| DurableRunnerError::invalid("managed provider is unavailable"))? + .increase_budget(value) + .map_err(|error| { + DurableRunnerError::invalid(format!("managed budget raise failed: {error}")) + })?; + self.state + .as_mut() + .expect("managed state exists") + .descriptor + .set_budget(value); + self.save_state()?; + Ok(CommandExecution::result(sanitize_value(&response))) + } + + fn snapshot(&self) -> Result { + let state = self + .state + .as_ref() + .ok_or_else(|| DurableRunnerError::invalid("managed provider is not prepared"))?; + Ok(CommandExecution::result(json!({ + "status": state.lifecycle, + "provider": state.descriptor.provider_label(), + "driver": state.descriptor.driver(), + "providerSessionId": state.provider_session_id, + "activeProviderTurnId": state.active_turn_id, + "durableEventCursor": state.durable_event_cursor, + }))) + } + + fn suspend(&mut self) -> Result { + if let Some(provider) = self.provider.as_mut() { + provider.shutdown().map_err(|error| { + DurableRunnerError::invalid(format!( + "failed to suspend managed provider connection: {error}" + )) + })?; + } + self.refresh_provider_checkpoint(); + self.provider = None; + if let Some(state) = self.state.as_mut() { + if state.active_turn_id.is_none() { + state.lifecycle = "suspended".to_owned(); + } + self.save_state()?; + } + Ok(CommandExecution::result(json!({"status": "completed"}))) + } + + fn close_session(&mut self, destroy: bool) -> Result { + if destroy { + self.restore_provider_if_needed()?; + self.provider + .as_mut() + .ok_or_else(|| DurableRunnerError::invalid("managed provider is unavailable"))? + .destroy_session() + .map_err(|error| { + DurableRunnerError::invalid(format!( + "managed remote session deletion failed: {error}" + )) + })?; + } else if let Some(provider) = self.provider.as_mut() { + provider.shutdown().map_err(|error| { + DurableRunnerError::invalid(format!("managed session close failed: {error}")) + })?; + } + self.provider = None; + let state = self + .state + .as_mut() + .ok_or_else(|| DurableRunnerError::invalid("managed provider is not prepared"))?; + state.lifecycle = "closed".to_owned(); + state.active_turn_id = None; + let session_id = state.provider_session_id.clone(); + let provider_label = state.descriptor.provider_label(); + self.save_state()?; + Ok(CommandExecution { + result: json!({ + "status": "closed", + "destroyed": destroy, + "providerSessionId": session_id, + }), + events: vec![( + "session.closed".to_owned(), + EventPriority::P0, + json!({ + "provider": provider_label, + "providerSessionId": session_id, + "remoteStateDestroyed": destroy, + }), + )], + }) + } + + fn poll_provider(&mut self) -> Result<(), DurableRunnerError> { + self.restore()?; + if self + .state + .as_ref() + .is_some_and(|state| !state.pending_events.is_empty()) + || self.provider.is_none() + { + return Ok(()); + } + for _ in 0..MAX_EVENTS_PER_POLL { + let event = match self + .provider + .as_mut() + .expect("managed provider remains present while polling") + .poll() + { + Ok(event) => event, + Err(error) => { + self.fail_provider(format!("managed provider failed: {error}"))?; + break; + } + }; + let Some(event) = event else { + break; + }; + self.project_event(event)?; + self.refresh_provider_checkpoint(); + self.save_state()?; + } + Ok(()) + } + + fn project_event(&mut self, event: ProviderEvent) -> Result<(), DurableRunnerError> { + let state = self + .state + .as_mut() + .expect("managed state exists while projecting provider events"); + match event { + ProviderEvent::ToolCall { + call_id, + operation_id, + input, + } => { + let pending = PendingToolCall { + call_id: call_id.clone(), + operation_id: operation_id.clone(), + input: input.clone(), + }; + if let Some(existing) = state.pending_tool_calls.get(&call_id) { + if existing != &pending { + return Err(DurableRunnerError::invalid( + "managed provider reused a tool-call ID with conflicting content", + )); + } + return Ok(()); + } + if state.pending_tool_calls.len() >= MAX_PENDING_CALLS { + return Err(DurableRunnerError::invalid( + "managed provider pending tool-call limit reached", + )); + } + state.pending_tool_calls.insert(call_id.clone(), pending); + state.push(semantic_input_event( + &self.config, + &call_id, + &operation_id, + &input, + ))?; + } + ProviderEvent::Notification { method, params } => { + if method == "item/completed" { + let item = params.get("item").unwrap_or(¶ms); + if item.get("type").and_then(Value::as_str) == Some("agentMessage") { + state.last_agent_message = item + .get("text") + .and_then(Value::as_str) + .filter(|value| !value.is_empty()) + .map(|value| value.chars().take(1_000_000).collect()); + } + } + if method == "thread/tokenUsage/updated" { + state.push(managed_usage_event(&state.descriptor, ¶ms))?; + state.model_request_count = + usage_request_count(¶ms).unwrap_or(state.model_request_count); + return Ok(()); + } + if method == "provider/budgetReached" { + state.push(NormalizedProviderEvent { + event_type: "provider.notice.recorded".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "schema": "paperclip.provider.notice.v1", + "noticeId": format!("{}-budget-limit", state.descriptor.provider_label()), + "severity": "error", + "category": "provider_limit", + "scope": "turn", + "recoverable": true, + "userActionable": true, + "summary": format!("{} reached its configured provider limit.", state.descriptor.display_name()), + "details": sanitize_value(¶ms), + }), + })?; + let prior_turn = state.active_turn_id.take(); + state.lifecycle = "session_open".to_owned(); + state.push(NormalizedProviderEvent { + event_type: "turn.failed".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "provider": state.descriptor.provider_label(), + "providerTurnId": prior_turn, + "status": "failed", + "stopReason": params.get("stopReason"), + "code": "provider_limit_reached", + }), + })?; + for event in terminal_events(state, "turn.failed") { + state.push(event)?; + } + return Ok(()); + } + if method == "provider/reconnecting" { + state.push(NormalizedProviderEvent { + event_type: "provider.notice.recorded".to_owned(), + priority: EventPriority::P1, + payload: json!({ + "schema": "paperclip.provider.notice.v1", + "noticeId": format!("{}-reconnecting", state.descriptor.provider_label()), + "severity": "warning", + "category": "reconnecting", + "scope": "session", + "recoverable": true, + "userActionable": false, + "summary": format!("{} is reconnecting.", state.descriptor.display_name()), + }), + })?; + return Ok(()); + } + let mut normalized = normalize_codex_notification(&method, ¶ms); + let terminal = normalized.iter().find_map(|event| { + matches!( + event.event_type.as_str(), + "turn.completed" | "turn.failed" | "turn.cancelled" | "turn.interrupted" + ) + .then(|| event.event_type.clone()) + }); + for event in &mut normalized { + if let Some(object) = event.payload.as_object_mut() { + object.insert( + "provider".to_owned(), + Value::String(state.descriptor.provider_label().to_owned()), + ); + } + } + for event in normalized { + state.push(event)?; + } + if let Some(event_type) = terminal { + state.active_turn_id = None; + state.lifecycle = "session_open".to_owned(); + for event in terminal_events(state, &event_type) { + state.push(event)?; + } + } + } + ProviderEvent::SemanticResult { result, .. } => { + state.push(NormalizedProviderEvent { + event_type: "run.result.proposed".to_owned(), + priority: EventPriority::P0, + payload: sanitize_value(&result), + })?; + } + ProviderEvent::RuntimeRequest { + request_id, + request_kind, + title, + details, + } => { + state.push(NormalizedProviderEvent { + event_type: "runtime_request.created".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "request": { + "schema": "paperclip.runtime_request.v2", + "requestKind": request_kind, + "requestId": request_id, + "turnId": self.config.turn_id, + "itemId": self.config.item_id, + "type": "input", + "status": "pending", + "prompt": title, + "input": sanitize_value(&details), + "origin": { + "adapter": state.descriptor.driver(), + "provider": state.descriptor.provider_label(), + "method": "remote_runtime_request", + }, + }, + }), + })?; + } + ProviderEvent::Exited => { + self.fail_provider("managed provider exited unexpectedly".to_owned())?; + } + } + Ok(()) + } + + fn fail_provider(&mut self, message: String) -> Result<(), DurableRunnerError> { + self.provider = None; + let state = self + .state + .as_mut() + .expect("managed state exists while failing provider"); + let active = state.active_turn_id.take(); + state.lifecycle = "failed".to_owned(); + state.push(NormalizedProviderEvent { + event_type: "session.failed".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "provider": state.descriptor.provider_label(), + "code": "managed_provider_failed", + "message": message, + }), + })?; + if active.is_some() { + state.push(NormalizedProviderEvent { + event_type: "turn.failed".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "provider": state.descriptor.provider_label(), + "providerTurnId": active, + "status": "failed", + "code": "managed_provider_failed", + }), + })?; + for event in terminal_events(state, "turn.failed") { + state.push(event)?; + } + } + self.save_state() + } +} + +impl CommandExecutor for ManagedProviderCommandExecutor { + fn execute(&mut self, command: &Command) -> Result { + self.restore()?; + match command.command_type.as_str() { + "run.prepare" => self.prepare(&command.payload), + "run.attach" => { + if self.state.is_none() && command.payload.get("provider").is_some() { + self.prepare(&command.payload)?; + } + let mut execution = self.open_session()?; + let provider = self + .state + .as_ref() + .expect("managed state exists after attach") + .descriptor + .provider_label(); + execution.events.push(( + "run.attached".to_owned(), + EventPriority::P0, + json!({"provider": provider}), + )); + Ok(execution) + } + "session.open" => self.open_session(), + "turn.start" => self.start_turn(&command.payload), + "turn.steer" => Ok(CommandExecution::result(json!({ + "status": "rejected", + "code": "provider_command_unavailable", + "message": "managed providers do not support active-turn steering", + }))), + "turn.interrupt" | "turn.stop" | "run.cancel" => { + self.interrupt_turn(&command.command_type) + } + "semantic_tool.result" => self.deliver_tool_result(&command.payload), + "provider.budget.raise" => self.change_budget(&command.payload), + "session.snapshot" => self.snapshot(), + "session.close" => self.close_session(false), + "session.destroy" => self.close_session(true), + "runner.suspend" | "runner.shutdown" => self.suspend(), + "runner.drain" => Ok(CommandExecution::result(json!({"status": "completed"}))), + _ => Ok(CommandExecution::result(json!({ + "status": "rejected", + "code": "provider_command_unavailable", + "message": "the managed provider does not implement this command", + }))), + } + } + + fn poll_events(&mut self) -> Result, DurableRunnerError> { + self.poll_provider()?; + Ok(self + .state + .as_ref() + .into_iter() + .flat_map(|state| state.pending_events.iter().take(MAX_EVENTS_PER_POLL)) + .cloned() + .collect()) + } + + fn acknowledge_events(&mut self, count: usize) -> Result<(), DurableRunnerError> { + if count == 0 { + return Ok(()); + } + let state = self + .state + .as_mut() + .ok_or_else(|| DurableRunnerError::invalid("managed provider state is unavailable"))?; + if count > state.pending_events.len() { + return Err(DurableRunnerError::invalid( + "managed event acknowledgement exceeded the pending prefix", + )); + } + state.pending_events.drain(..count); + self.save_state() + } + + fn shutdown(&mut self) -> Result<(), DurableRunnerError> { + if let Some(provider) = self.provider.as_mut() { + provider.shutdown().map_err(|error| { + DurableRunnerError::invalid(format!( + "failed to stop managed provider connection: {error}" + )) + })?; + } + self.provider = None; + Ok(()) + } +} + +fn completion_contract( + payload: &Value, +) -> Result, DurableRunnerError> { + let Some(value) = payload.get("completionContract") else { + return Ok(None); + }; + let binding: CompletionContractBinding = + serde_json::from_value(value.clone()).map_err(|error| { + DurableRunnerError::invalid(format!( + "run.prepare completionContract is invalid: {error}" + )) + })?; + if binding.revision.is_empty() + || binding.revision.len() > 120 + || binding.criterion_ids.is_empty() + || binding.criterion_ids.len() > 256 + || binding.criterion_ids.iter().any(|criterion| { + criterion.is_empty() || criterion.len() > 240 || criterion.chars().any(char::is_control) + }) + { + return Err(DurableRunnerError::invalid( + "run.prepare completionContract is malformed or oversized", + )); + } + Ok(Some(binding)) +} + +fn authorized_tool_set(payload: &Value) -> Result { + if let Some(value) = payload.get("authorizedTools") { + let tool_set: AuthorizedToolSet = + serde_json::from_value(value.clone()).map_err(|error| { + DurableRunnerError::invalid(format!( + "run.prepare authorizedTools is invalid: {error}" + )) + })?; + if tool_set.schema != TOOL_SET_SCHEMA + || tool_set.schema_version != 1 + || tool_set.operations.len() > MAX_PENDING_CALLS + || authorized_tool_catalog_digest(&tool_set.operations) + .map_err(|error| DurableRunnerError::invalid(error.to_string()))? + != tool_set.catalog_digest + { + return Err(DurableRunnerError::invalid( + "run.prepare authorizedTools failed its closed catalog contract", + )); + } + return Ok(tool_set); + } + let operations = Vec::new(); + let catalog_digest = authorized_tool_catalog_digest(&operations) + .map_err(|error| DurableRunnerError::invalid(error.to_string()))?; + Ok(AuthorizedToolSet { + schema: TOOL_SET_SCHEMA.to_owned(), + schema_version: 1, + catalog_digest, + operations, + }) +} + +fn semantic_input_event( + config: &DurableRunnerConfig, + call_id: &str, + operation_id: &str, + input: &Value, +) -> NormalizedProviderEvent { + let safe_input = sanitize_value(input); + NormalizedProviderEvent { + event_type: "semantic_tool.input".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "semantic_tool": { + "schema": "paperclip.prp.semantic_tool.v1", + "schemaVersion": 1, + "phase": "input", + "operationId": operation_id, + "callId": call_id, + "correlation": { + "runId": config.run_id, + "normalizedSessionId": config.normalized_session_id, + "turnId": config.turn_id, + "itemId": config.item_id, + }, + "idempotencyKey": Value::Null, + "content": { + "digest": semantic_value_digest(&safe_input), + "redactionDisposition": "digest_only", + "references": [], + }, + "input": safe_input, + }, + }), + } +} + +fn terminal_events(state: &ManagedDurableState, event_type: &str) -> Vec { + let Some(contract) = state.completion_contract.as_ref() else { + return Vec::new(); + }; + let succeeded = event_type == "turn.completed"; + let cancelled = matches!(event_type, "turn.cancelled" | "turn.interrupted"); + let disposition = if succeeded { "done" } else { "needs_review" }; + let provider = state.descriptor.provider_label(); + let display_name = state.descriptor.display_name(); + let summary = state.last_agent_message.clone().unwrap_or_else(|| { + if succeeded { + format!("{display_name} completed the requested work.") + } else if cancelled { + format!("The {display_name} run stopped before it completed.") + } else { + format!("The {display_name} run failed before it completed.") + } + }); + let evidence_ref = format!("provider:{provider}:agent-message"); + let criteria = contract + .criterion_ids + .iter() + .map(|criterion_id| { + json!({ + "criterionId": criterion_id, + "status": if succeeded { "satisfied" } else { "unknown" }, + "evidenceRefs": if succeeded { vec![evidence_ref.as_str()] } else { Vec::<&str>::new() }, + }) + }) + .collect::>(); + let result = json!({ + "schema": "paperclip.run_result.v1", + "reportedWorkDisposition": disposition, + "summary": summary, + "completionClaim": { + "contractRevision": contract.revision, + "objectiveSatisfied": succeeded, + "criteria": criteria, + "remainingWork": if succeeded { Vec::::new() } else { vec![json!({ + "description": format!("Review the stopped {display_name} run and continue the task."), + "blocksCompletion": true, + })] }, + }, + "evidence": if succeeded { vec![json!({ "ref": evidence_ref })] } else { Vec::::new() }, + "verification": [], + "attentionRequests": if succeeded { Vec::::new() } else { vec![json!({ + "kind": "review", + "summary": format!("Review the stopped {display_name} run before continuing."), + "ownerClass": "human", + })] }, + "artifacts": [], + }); + let turn_terminal_state = if succeeded { + "completed" + } else if event_type == "turn.interrupted" { + "interrupted" + } else if cancelled { + "cancelled" + } else { + "failed" + }; + vec![ + NormalizedProviderEvent { + event_type: "run.result.proposed".to_owned(), + priority: EventPriority::P0, + payload: result, + }, + NormalizedProviderEvent { + event_type: "run.terminal".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "schema": "paperclip.prp.terminal.v1", + "provider": provider, + "turnTerminalState": turn_terminal_state, + "runTerminalState": if succeeded { "succeeded" } else if cancelled { "cancelled" } else { "failed" }, + "reportedWorkDisposition": disposition, + }), + }, + ] +} + +fn usage_request_count(params: &Value) -> Option { + params + .pointer("/usage/requestCount") + .or_else(|| params.get("requestCount")) + .and_then(Value::as_u64) +} + +fn managed_usage_event( + descriptor: &ManagedProviderDescriptor, + params: &Value, +) -> NormalizedProviderEvent { + let usage = params.get("usage").unwrap_or(params); + let nonnegative_integer = + |value: Option<&Value>| value.and_then(Value::as_i64).unwrap_or(0).max(0); + let integer = |camel: &str, snake: &str| { + nonnegative_integer(usage.get(camel).or_else(|| usage.get(snake))) + }; + let cache_write_tokens = usage + .get("cache_creation") + .and_then(Value::as_object) + .map(|cache_creation| { + nonnegative_integer(cache_creation.get("ephemeral_1h_input_tokens")).saturating_add( + nonnegative_integer(cache_creation.get("ephemeral_5m_input_tokens")), + ) + }) + .unwrap_or_else(|| integer("cacheWriteInputTokens", "cache_write_input_tokens")); + let provider_cost_usd = usage + .get("estimatedCostUsd") + .and_then(Value::as_f64) + .or_else(|| { + usage + .pointer("/list_cost/amount") + .or_else(|| usage.pointer("/listCost/amount")) + .and_then(|value| value.as_str().and_then(|value| value.parse::().ok())) + .map(|cents| cents / 100.0) + }) + .unwrap_or(0.0) + .max(0.0); + let measurement = json!({ + "inputTokens": integer("inputTokens", "input_tokens"), + "outputTokens": integer("outputTokens", "output_tokens"), + "cacheReadTokens": integer("cacheReadInputTokens", "cache_read_input_tokens"), + "cacheWriteTokens": cache_write_tokens, + "activeSeconds": usage.get("activeSeconds").or_else(|| usage.get("active_seconds")).and_then(Value::as_f64).unwrap_or(0.0).max(0.0), + "requests": usage_request_count(params).unwrap_or(0), + "providerCostUsd": provider_cost_usd, + }); + NormalizedProviderEvent { + event_type: "usage.reported".to_owned(), + priority: EventPriority::P0, + payload: json!({ + "provider": descriptor.provider_label(), + "model": descriptor.model(), + "providerSessionId": Value::Null, + "providerRequestId": descriptor.kind().eq(&ManagedProviderKind::AwsAgentcore).then(|| params.get("invocationId")).flatten(), + "cumulative": measurement, + "runDeltaAvailable": false, + "runDelta": Value::Null, + "costSource": if descriptor.kind() == ManagedProviderKind::AwsAgentcore { "paperclip_estimate" } else { "provider_reported" }, + }), + } +} + +fn session_event_payload( + descriptor: &ManagedProviderDescriptor, + runtime: &ProviderRuntimeIdentity, +) -> Value { + let session_id = match runtime { + ProviderRuntimeIdentity::RemoteService { + provider_session_id, + .. + } => provider_session_id, + }; + json!({ + "provider": descriptor.provider_label(), + "driver": descriptor.driver(), + "providerDescriptor": { + "provider": descriptor.provider_label(), + "driver": descriptor.driver(), + "providerVersion": descriptor.version(), + "model": descriptor.model(), + "executionKind": "remote_service", + "providerSessionId": session_id, + }, + "runtimeIdentity": runtime, + "threadId": session_id, + "providerSessionId": session_id, + "sessionId": session_id, + "providerAccountSessionId": session_id, + "processId": Value::Null, + }) +} + +fn secure_directory(path: &Path, label: &str) -> Result<(), DurableRunnerError> { + let mut builder = DirBuilder::new(); + #[cfg(unix)] + builder.mode(0o700); + match builder.create(path) { + Ok(()) => {} + Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => {} + Err(error) => { + return Err(DurableRunnerError::invalid(format!( + "failed to create {label} directory: {error}" + ))) + } + } + verify_private_directory(path).map_err(|error| { + DurableRunnerError::invalid(format!("{label} directory is not private: {error}")) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::{Arc, Mutex}; + use std::time::Duration; + + struct FakeProvider { + session_id: String, + usage: Value, + max_estimated_cost_usd: f64, + } + + impl Provider for FakeProvider { + fn kind(&self) -> crate::managed_provider::ProviderKind { + crate::managed_provider::ProviderKind::AwsAgentcore + } + + fn runtime_identity(&self) -> ProviderRuntimeIdentity { + ProviderRuntimeIdentity::RemoteService { + service: "aws_bedrock_agentcore_harness".to_owned(), + provider_session_id: self.session_id.clone(), + process_id: None, + } + } + + fn session_identity(&self) -> &str { + &self.session_id + } + + fn provider_session_id(&self) -> Option<&str> { + Some(&self.session_id) + } + + fn model_request_count(&self) -> Option { + self.usage.get("requestCount").and_then(Value::as_u64) + } + + fn usage_snapshot(&self) -> Option { + Some(self.usage.clone()) + } + + fn increase_budget( + &mut self, + maximum_cost_usd: f64, + ) -> Result { + if maximum_cost_usd <= self.max_estimated_cost_usd { + return Err(crate::local_runner::LocalRunnerError::invalid( + "fake AgentCore budget must increase", + )); + } + self.max_estimated_cost_usd = maximum_cost_usd; + Ok(json!({ "maxEstimatedSessionCostUsd": maximum_cost_usd })) + } + + fn preflight_turn(&mut self) -> Result<(), crate::local_runner::LocalRunnerError> { + if self + .usage + .get(AGENTCORE_USAGE_RECONCILIATION_FIELD) + .and_then(Value::as_str) + == Some(AGENTCORE_USAGE_RECONCILIATION_PENDING) + { + let floor = self + .usage + .get(AGENTCORE_PENDING_CEILING_FIELD) + .and_then(Value::as_f64) + .unwrap_or(self.max_estimated_cost_usd) + .max( + self.usage + .get("estimatedCostUsd") + .and_then(Value::as_f64) + .unwrap_or(0.0), + ); + let requests = self + .usage + .get("requestCount") + .and_then(Value::as_u64) + .unwrap_or(0) + .saturating_add(1); + self.usage["requestCount"] = json!(requests); + self.usage["estimatedCostUsd"] = json!(floor); + self.usage[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD] = json!(floor); + self.usage[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE); + if let Some(usage) = self.usage.as_object_mut() { + usage.remove(AGENTCORE_PENDING_INVOCATION_FIELD); + usage.remove(AGENTCORE_PENDING_CEILING_FIELD); + } + } + if self + .usage + .get("estimatedCostUsd") + .and_then(Value::as_f64) + .unwrap_or(0.0) + >= self.max_estimated_cost_usd + { + return Err(crate::local_runner::LocalRunnerError::invalid( + "AgentCore estimated session spend ceiling reached; raise it explicitly before continuing", + )); + } + Ok(()) + } + + fn start_turn( + &mut self, + _message: &str, + _cwd: &str, + _turn_id: &str, + ) -> Result { + Ok(json!({"started": true})) + } + + fn interrupt_turn( + &mut self, + _turn_id: &str, + ) -> Result { + Ok(json!({"interrupted": true})) + } + + fn read(&mut self) -> Result { + Ok(json!({})) + } + + fn poll(&mut self) -> Result, crate::local_runner::LocalRunnerError> { + Ok(None) + } + + fn deliver_tool_result( + &mut self, + _result: &ToolResult, + ) -> Result<(), crate::local_runner::LocalRunnerError> { + Ok(()) + } + + fn shutdown(&mut self) -> Result<(), crate::local_runner::LocalRunnerError> { + Ok(()) + } + } + + struct FakeFactory { + observed_resume_usage: Arc>>>, + usage: Value, + } + + impl ManagedProviderFactory for FakeFactory { + fn start( + &self, + descriptor: &ManagedProviderDescriptor, + _tools: Vec, + _ownership_scope: &str, + resume_session_id: Option<&str>, + _resume_event_cursor: Option<&str>, + _resume_model_request_count: u64, + resume_usage: Option<&Value>, + _resume_claude_managed_skills: Option<&[ClaudeManagedSkillRef]>, + _pending_claude_skill_cleanup: Option<&[ClaudeManagedSkillRef]>, + ) -> Result, ManagedProviderStartError> { + self.observed_resume_usage + .lock() + .unwrap() + .push(resume_usage.cloned()); + Ok(Box::new(FakeProvider { + session_id: resume_session_id + .unwrap_or("managed-test-session") + .to_owned(), + usage: resume_usage.cloned().unwrap_or_else(|| self.usage.clone()), + max_estimated_cost_usd: match descriptor { + ManagedProviderDescriptor::AwsAgentcore(config) => { + config.max_estimated_session_cost_usd + } + ManagedProviderDescriptor::ClaudeManaged(_) => 1.0, + }, + })) + } + } + + struct FakeClaudeProvider { + session_id: String, + skills: Vec, + destroy_failures: Arc, + } + + impl Provider for FakeClaudeProvider { + fn kind(&self) -> crate::managed_provider::ProviderKind { + crate::managed_provider::ProviderKind::ClaudeManaged + } + + fn runtime_identity(&self) -> ProviderRuntimeIdentity { + ProviderRuntimeIdentity::RemoteService { + service: "anthropic_managed_agents".to_owned(), + provider_session_id: self.session_id.clone(), + process_id: None, + } + } + + fn session_identity(&self) -> &str { + &self.session_id + } + + fn provider_session_id(&self) -> Option<&str> { + Some(&self.session_id) + } + + fn claude_managed_skills(&self) -> Option<&[ClaudeManagedSkillRef]> { + Some(&self.skills) + } + + fn destroy_session(&mut self) -> Result<(), crate::local_runner::LocalRunnerError> { + if self + .destroy_failures + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| { + remaining.checked_sub(1) + }) + .is_ok() + { + return Err(crate::local_runner::LocalRunnerError::invalid( + "injected owned-resource deletion failure", + )); + } + Ok(()) + } + + fn start_turn( + &mut self, + _message: &str, + _cwd: &str, + _turn_id: &str, + ) -> Result { + Ok(json!({"started": true})) + } + + fn interrupt_turn( + &mut self, + _turn_id: &str, + ) -> Result { + Ok(json!({"interrupted": true})) + } + + fn read(&mut self) -> Result { + Ok(json!({})) + } + + fn poll(&mut self) -> Result, crate::local_runner::LocalRunnerError> { + Ok(None) + } + + fn deliver_tool_result( + &mut self, + _result: &ToolResult, + ) -> Result<(), crate::local_runner::LocalRunnerError> { + Ok(()) + } + + fn shutdown(&mut self) -> Result<(), crate::local_runner::LocalRunnerError> { + Ok(()) + } + } + + struct FakeClaudeFactory { + observed_resume_skills: Arc>>>>, + created_skills: Vec, + destroy_failures: Arc, + } + + impl ManagedProviderFactory for FakeClaudeFactory { + fn start( + &self, + _descriptor: &ManagedProviderDescriptor, + _tools: Vec, + _ownership_scope: &str, + resume_session_id: Option<&str>, + _resume_event_cursor: Option<&str>, + _resume_model_request_count: u64, + _resume_usage: Option<&Value>, + resume_claude_managed_skills: Option<&[ClaudeManagedSkillRef]>, + _pending_claude_skill_cleanup: Option<&[ClaudeManagedSkillRef]>, + ) -> Result, ManagedProviderStartError> { + self.observed_resume_skills + .lock() + .unwrap() + .push(resume_claude_managed_skills.map(<[_]>::to_vec)); + Ok(Box::new(FakeClaudeProvider { + session_id: resume_session_id.unwrap_or("claude-session-1").to_owned(), + skills: resume_claude_managed_skills + .map(<[_]>::to_vec) + .unwrap_or_else(|| self.created_skills.clone()), + destroy_failures: Arc::clone(&self.destroy_failures), + })) + } + } + + struct FailThenRecoverClaudeFactory { + calls: AtomicUsize, + skills: Vec, + recovery_session_id: Option, + observed_resume: Arc, Option>)>>>, + } + + impl ManagedProviderFactory for FailThenRecoverClaudeFactory { + fn start( + &self, + _descriptor: &ManagedProviderDescriptor, + _tools: Vec, + _ownership_scope: &str, + resume_session_id: Option<&str>, + _resume_event_cursor: Option<&str>, + _resume_model_request_count: u64, + _resume_usage: Option<&Value>, + resume_claude_managed_skills: Option<&[ClaudeManagedSkillRef]>, + _pending_claude_skill_cleanup: Option<&[ClaudeManagedSkillRef]>, + ) -> Result, ManagedProviderStartError> { + self.observed_resume.lock().unwrap().push(( + resume_session_id.map(str::to_owned), + resume_claude_managed_skills.map(<[_]>::to_vec), + )); + if self.calls.fetch_add(1, Ordering::SeqCst) == 0 { + return Err(ManagedProviderStartError { + error: DurableRunnerError::invalid("injected stream bootstrap failure"), + claude_skill_cleanup: None, + claude_durable_skills: Some(self.skills.clone()), + recovery_session_id: self.recovery_session_id.clone(), + }); + } + Ok(Box::new(FakeClaudeProvider { + session_id: resume_session_id + .unwrap_or("claude-checkpointed-session") + .to_owned(), + skills: resume_claude_managed_skills + .map(<[_]>::to_vec) + .unwrap_or_else(|| self.skills.clone()), + destroy_failures: Arc::new(AtomicUsize::new(0)), + })) + } + } + + fn test_config(state_dir: &Path) -> DurableRunnerConfig { + DurableRunnerConfig { + connect_url: "ws://127.0.0.1/runner".to_owned(), + ca_bundle_path: None, + state_dir: state_dir.to_owned(), + runner_instance_id: "runner-1".to_owned(), + environment_lease_id: "lease-1".to_owned(), + run_id: "run-1".to_owned(), + normalized_session_id: "session-1".to_owned(), + turn_id: "turn-1".to_owned(), + item_id: "item-1".to_owned(), + runner_version: "0.0.0".to_owned(), + runner_digest: "sha256:test".to_owned(), + acpx_launch_profile: None, + opencode_launch_profile: None, + max_outbox_bytes: 1024 * 1024, + p0_reserve_bytes: 64 * 1024, + max_frame_bytes: 1024 * 1024, + reconnect_delay: Duration::from_millis(1), + reconnect_grace: None, + max_runtime: Duration::from_secs(60), + } + } + + fn test_command(sequence: u64, command_type: &str, payload: Value) -> Command { + Command { + schema: "paperclip.prp.command.v1".to_owned(), + command_id: format!("command-{sequence}"), + controller_seq: sequence, + command_type: command_type.to_owned(), + issued_at: "2026-09-01T00:00:00.000Z".to_owned(), + deadline_at: None, + precondition: None, + payload, + } + } + + fn agentcore_prepare_payload() -> Value { + let operations = Vec::new(); + json!({ + "authorizedTools": { + "schema": TOOL_SET_SCHEMA, + "schemaVersion": 1, + "catalogDigest": authorized_tool_catalog_digest(&operations).unwrap(), + "operations": operations, + }, + "provider": { + "kind": "aws_agentcore", + "model": QUALIFIED_AGENTCORE_MODEL, + "profileId": "profile-1", + "region": "us-east-1", + "accountId": "123456789012", + "harnessArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/test", + "harnessVersion": "1", + "endpointArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/test", + "endpointQualifier": "1", + "agentRuntimeArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/test", + "memoryArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/test", + "memoryId": "memory-1", + "invocationRoleArn": "arn:aws:iam::123456789012:role/runner", + "contextBucket": "context-bucket", + "contextPrefix": "companies/company/profiles/profile", + "contextKmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/test", + "qualificationRevision": QUALIFIED_AGENTCORE_REVISION, + "eventExpiryDays": 90, + "maxEstimatedSessionCostUsd": 1.0, + "maxIterations": 8, + "maxOutputTokens": 4096, + "timeoutSeconds": 300, + "instructions": "Complete the supplied task.", + "runtimeContext": null, + }, + }) + } + + fn claude_prepare_payload() -> Value { + let operations = Vec::new(); + json!({ + "authorizedTools": { + "schema": TOOL_SET_SCHEMA, + "schemaVersion": 1, + "catalogDigest": authorized_tool_catalog_digest(&operations).unwrap(), + "operations": operations, + }, + "provider": { + "kind": "claude_managed", + "model": QUALIFIED_CLAUDE_MODEL, + "profileId": "profile-1", + "anthropicAgentId": "agent-1", + "agentVersion": "1", + "environmentId": "environment-1", + "betaVersion": QUALIFIED_CLAUDE_BETA, + "maxSessionListCostUsd": 1.0, + "instructions": "Complete the supplied task.", + "runtimeContext": { + "instructions": {}, + "skills": [{}] + }, + }, + }) + } + + #[test] + fn claude_usage_maps_nested_cache_creation_token_buckets() { + let descriptor = ManagedProviderDescriptor::ClaudeManaged(ClaudeManagedProviderConfig { + model: QUALIFIED_CLAUDE_MODEL.to_owned(), + profile_id: "profile-1".to_owned(), + anthropic_agent_id: "agent-1".to_owned(), + agent_version: "version-1".to_owned(), + environment_id: "environment-1".to_owned(), + beta_version: QUALIFIED_CLAUDE_BETA.to_owned(), + max_session_list_cost_usd: 1.0, + instructions: "Complete the supplied task.".to_owned(), + runtime_context: None, + }); + let event = managed_usage_event( + &descriptor, + &json!({ + "usage": { + "input_tokens": 21, + "output_tokens": 8, + "cache_read_input_tokens": 13, + "cache_creation": { + "ephemeral_1h_input_tokens": 34, + "ephemeral_5m_input_tokens": 55 + } + }, + "requestCount": 2 + }), + ); + + assert_eq!( + event.payload.pointer("/cumulative/cacheWriteTokens"), + Some(&json!(89)) + ); + } + + #[test] + fn agentcore_usage_snapshot_accepts_only_bounded_reconciliation_states() { + let usage = json!({ + "inputTokens": 12, + "outputTokens": 3, + "cacheReadInputTokens": 4, + "cacheWriteInputTokens": 5, + "requestCount": 2, + "estimatedCostUsd": 0.75, + "costSource": "paperclip_estimate" + }); + assert!(valid_agentcore_usage_snapshot(&usage)); + + let mut observed = usage.clone(); + observed[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_OBSERVED); + assert!(valid_agentcore_usage_snapshot(&observed)); + + let mut pending = usage.clone(); + pending[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_PENDING); + pending[AGENTCORE_PENDING_INVOCATION_FIELD] = json!("invocation-1"); + pending[AGENTCORE_PENDING_CEILING_FIELD] = json!(1.0); + assert!(valid_agentcore_usage_snapshot(&pending)); + + let mut conservative = usage.clone(); + conservative[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE); + conservative[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD] = json!(0.75); + assert!(valid_agentcore_usage_snapshot(&conservative)); + + let mut orphan_pending_ceiling = usage.clone(); + orphan_pending_ceiling[AGENTCORE_PENDING_CEILING_FIELD] = json!(1.0); + assert!(!valid_agentcore_usage_snapshot(&orphan_pending_ceiling)); + + let mut orphan_floor = usage.clone(); + orphan_floor[AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD] = json!(0.75); + assert!(!valid_agentcore_usage_snapshot(&orphan_floor)); + + conservative["estimatedCostUsd"] = json!(0.74); + assert!(!valid_agentcore_usage_snapshot(&conservative)); + conservative["estimatedCostUsd"] = json!(0.75); + conservative + .as_object_mut() + .unwrap() + .remove(AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD); + assert!(!valid_agentcore_usage_snapshot(&conservative)); + + pending[AGENTCORE_USAGE_RECONCILIATION_FIELD] = json!("unknown"); + assert!(!valid_agentcore_usage_snapshot(&pending)); + pending[AGENTCORE_USAGE_RECONCILIATION_FIELD] = + json!(AGENTCORE_USAGE_RECONCILIATION_PENDING); + pending + .as_object_mut() + .unwrap() + .remove(AGENTCORE_PENDING_INVOCATION_FIELD); + assert!(!valid_agentcore_usage_snapshot(&pending)); + } + + #[test] + fn durable_pending_agentcore_usage_is_charged_once_before_turn_admission() { + let directory = std::env::temp_dir().join(format!( + "paperclip-managed-provider-test-{}", + Uuid::new_v4() + )); + fs::create_dir_all(&directory).unwrap(); + #[cfg(unix)] + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + let config = test_config(&directory); + let usage = json!({ + "inputTokens": 12, + "outputTokens": 3, + "cacheReadInputTokens": 4, + "cacheWriteInputTokens": 5, + "requestCount": 2, + "estimatedCostUsd": 0.75, + "costSource": "paperclip_estimate", + "usageReconciliation": AGENTCORE_USAGE_RECONCILIATION_PENDING, + "pendingInvocationId": "invocation-before-restart", + "pendingEstimatedCeilingUsd": 1.0 + }); + let first_observed = Arc::new(Mutex::new(Vec::new())); + let mut first = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FakeFactory { + observed_resume_usage: Arc::clone(&first_observed), + usage: usage.clone(), + }), + ); + first + .execute(&test_command(1, "run.prepare", agentcore_prepare_payload())) + .unwrap(); + first + .execute(&test_command(2, "session.open", json!({}))) + .unwrap(); + first + .execute(&test_command(3, "runner.suspend", json!({}))) + .unwrap(); + drop(first); + + let recovered_observed = Arc::new(Mutex::new(Vec::new())); + let mut recovered = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FakeFactory { + observed_resume_usage: Arc::clone(&recovered_observed), + usage: json!({}), + }), + ); + recovered + .execute(&test_command(4, "session.open", json!({}))) + .unwrap(); + assert_eq!( + recovered_observed.lock().unwrap().as_slice(), + &[Some(usage)] + ); + let before_preflight: Value = + serde_json::from_slice(&fs::read(recovered.state_path()).unwrap()).unwrap(); + let error = recovered + .execute(&test_command( + 5, + "turn.start", + json!({ "text": "blocked until an explicit budget raise" }), + )) + .unwrap_err(); + assert!(error + .to_string() + .contains("estimated session spend ceiling reached")); + let persisted: Value = + serde_json::from_slice(&fs::read(recovered.state_path()).unwrap()).unwrap(); + assert_eq!(persisted["lifecycle"], "session_open"); + assert_eq!(persisted["activeTurnId"], Value::Null); + assert_eq!( + persisted["nextEventSequence"], + before_preflight["nextEventSequence"] + ); + assert_eq!( + persisted["pendingEvents"], + before_preflight["pendingEvents"] + ); + assert_eq!(persisted["modelRequestCount"], 3); + assert_eq!(persisted["providerUsage"]["requestCount"], 3); + assert_eq!(persisted["providerUsage"]["estimatedCostUsd"], 1.0); + assert_eq!( + persisted["providerUsage"][AGENTCORE_USAGE_RECONCILIATION_FIELD], + AGENTCORE_USAGE_RECONCILIATION_CONSERVATIVE + ); + assert_eq!( + persisted["providerUsage"][AGENTCORE_CONSERVATIVE_COST_FLOOR_FIELD], + 1.0 + ); + assert_eq!( + persisted["providerUsage"][AGENTCORE_PENDING_INVOCATION_FIELD], + Value::Null + ); + + recovered + .execute(&test_command( + 6, + "turn.start", + json!({ "text": "the same old cap still blocks" }), + )) + .unwrap_err(); + let persisted_again: Value = + serde_json::from_slice(&fs::read(recovered.state_path()).unwrap()).unwrap(); + assert_eq!(persisted_again["providerUsage"]["requestCount"], 3); + assert_eq!(persisted_again["modelRequestCount"], 3); + assert_eq!(persisted_again["providerUsage"], persisted["providerUsage"]); + drop(recovered); + + let raised_observed = Arc::new(Mutex::new(Vec::new())); + let mut raised = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FakeFactory { + observed_resume_usage: Arc::clone(&raised_observed), + usage: json!({}), + }), + ); + raised + .execute(&test_command( + 7, + "provider.budget.raise", + json!({ "maximumCostUsd": 2.0 }), + )) + .unwrap(); + assert_eq!( + raised_observed.lock().unwrap().as_slice(), + &[Some(persisted["providerUsage"].clone())] + ); + raised + .execute(&test_command( + 8, + "turn.start", + json!({ "text": "explicitly raised budget permits this turn" }), + )) + .unwrap(); + let admitted: Value = + serde_json::from_slice(&fs::read(raised.state_path()).unwrap()).unwrap(); + assert_eq!(admitted["lifecycle"], "turn_active"); + assert_eq!(admitted["activeTurnId"], "turn-1"); + assert_eq!(admitted["modelRequestCount"], 3); + assert_eq!(admitted["providerUsage"]["requestCount"], 3); + assert_eq!(admitted["providerUsage"]["estimatedCostUsd"], 1.0); + fs::remove_dir_all(directory).unwrap(); + } + + #[test] + fn durable_claude_skill_ownership_is_reused_on_cold_recovery() { + let directory = std::env::temp_dir().join(format!( + "paperclip-managed-provider-test-{}", + Uuid::new_v4() + )); + fs::create_dir_all(&directory).unwrap(); + #[cfg(unix)] + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + let config = test_config(&directory); + let skills = vec![ + ClaudeManagedSkillRef { + skill_id: "skill_instructions".to_owned(), + version: "skver_instructions".to_owned(), + }, + ClaudeManagedSkillRef { + skill_id: "skill_reviewer".to_owned(), + version: "skver_reviewer".to_owned(), + }, + ]; + let first_observed = Arc::new(Mutex::new(Vec::new())); + let mut first = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FakeClaudeFactory { + observed_resume_skills: Arc::clone(&first_observed), + created_skills: skills.clone(), + destroy_failures: Arc::new(AtomicUsize::new(0)), + }), + ); + first + .execute(&test_command(1, "run.prepare", claude_prepare_payload())) + .unwrap(); + first + .execute(&test_command(2, "session.open", json!({}))) + .unwrap(); + first + .execute(&test_command(3, "runner.suspend", json!({}))) + .unwrap(); + assert_eq!(first_observed.lock().unwrap().as_slice(), &[None]); + let persisted: Value = + serde_json::from_slice(&fs::read(first.state_path()).unwrap()).unwrap(); + assert_eq!( + persisted.get("claudeManagedSkills"), + Some(&serde_json::to_value(&skills).unwrap()) + ); + drop(first); + + let recovered_observed = Arc::new(Mutex::new(Vec::new())); + let mut recovered = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FakeClaudeFactory { + observed_resume_skills: Arc::clone(&recovered_observed), + created_skills: Vec::new(), + destroy_failures: Arc::new(AtomicUsize::new(0)), + }), + ); + recovered + .execute(&test_command(4, "session.destroy", json!({}))) + .unwrap(); + assert_eq!( + recovered_observed.lock().unwrap().as_slice(), + &[Some(skills)] + ); + fs::remove_dir_all(directory).unwrap(); + } + + #[test] + fn failed_fresh_bootstrap_checkpoints_session_and_skills_before_recovery() { + let directory = std::env::temp_dir().join(format!( + "paperclip-managed-provider-test-{}", + Uuid::new_v4() + )); + fs::create_dir_all(&directory).unwrap(); + #[cfg(unix)] + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + let config = test_config(&directory); + let skills = vec![ + ClaudeManagedSkillRef { + skill_id: "skill_instructions".to_owned(), + version: "skver_instructions".to_owned(), + }, + ClaudeManagedSkillRef { + skill_id: "skill_reviewer".to_owned(), + version: "skver_reviewer".to_owned(), + }, + ]; + let observed = Arc::new(Mutex::new(Vec::new())); + let mut executor = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FailThenRecoverClaudeFactory { + calls: AtomicUsize::new(0), + skills: skills.clone(), + recovery_session_id: Some("claude-checkpointed-session".to_owned()), + observed_resume: Arc::clone(&observed), + }), + ); + executor + .execute(&test_command(1, "run.prepare", claude_prepare_payload())) + .unwrap(); + + let error = executor + .execute(&test_command(2, "session.open", json!({}))) + .unwrap_err(); + assert!(error + .to_string() + .contains("injected stream bootstrap failure")); + let checkpoint: Value = + serde_json::from_slice(&fs::read(executor.state_path()).unwrap()).unwrap(); + assert_eq!( + checkpoint.get("providerSessionId"), + Some(&json!("claude-checkpointed-session")) + ); + assert_eq!( + checkpoint.get("claudeManagedSkills"), + Some(&serde_json::to_value(&skills).unwrap()) + ); + + executor + .execute(&test_command(3, "session.open", json!({}))) + .unwrap(); + assert_eq!( + observed.lock().unwrap().as_slice(), + &[ + (None, None), + (Some("claude-checkpointed-session".to_owned()), Some(skills)) + ] + ); + fs::remove_dir_all(directory).unwrap(); + } + + #[test] + fn ambiguous_fresh_create_checkpoints_skills_before_metadata_reconciliation() { + let directory = std::env::temp_dir().join(format!( + "paperclip-managed-provider-test-{}", + Uuid::new_v4() + )); + fs::create_dir_all(&directory).unwrap(); + #[cfg(unix)] + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + let config = test_config(&directory); + let skills = vec![ + ClaudeManagedSkillRef { + skill_id: "skill_instructions".to_owned(), + version: "skver_instructions".to_owned(), + }, + ClaudeManagedSkillRef { + skill_id: "skill_reviewer".to_owned(), + version: "skver_reviewer".to_owned(), + }, + ]; + let observed = Arc::new(Mutex::new(Vec::new())); + let mut executor = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FailThenRecoverClaudeFactory { + calls: AtomicUsize::new(0), + skills: skills.clone(), + recovery_session_id: None, + observed_resume: Arc::clone(&observed), + }), + ); + executor + .execute(&test_command(1, "run.prepare", claude_prepare_payload())) + .unwrap(); + executor + .execute(&test_command(2, "session.open", json!({}))) + .unwrap_err(); + + let checkpoint: Value = + serde_json::from_slice(&fs::read(executor.state_path()).unwrap()).unwrap(); + assert_eq!(checkpoint.get("providerSessionId"), Some(&Value::Null)); + assert_eq!( + checkpoint.get("claudeManagedSkills"), + Some(&serde_json::to_value(&skills).unwrap()) + ); + executor + .execute(&test_command(3, "session.open", json!({}))) + .unwrap(); + assert_eq!( + observed.lock().unwrap().as_slice(), + &[(None, None), (None, Some(skills))] + ); + fs::remove_dir_all(directory).unwrap(); + } + + #[test] + fn claude_skill_ids_cannot_be_supplied_by_the_prepare_caller() { + let directory = std::env::temp_dir().join(format!( + "paperclip-managed-provider-test-{}", + Uuid::new_v4() + )); + fs::create_dir_all(&directory).unwrap(); + #[cfg(unix)] + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + let config = test_config(&directory); + let observed = Arc::new(Mutex::new(Vec::new())); + let mut executor = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FakeClaudeFactory { + observed_resume_skills: observed, + created_skills: Vec::new(), + destroy_failures: Arc::new(AtomicUsize::new(0)), + }), + ); + let mut payload = claude_prepare_payload(); + payload["provider"]["claudeManagedSkills"] = json!([{ + "skillId": "skill_not_owned_by_paperclip", + "version": "skver_not_owned_by_paperclip" + }]); + let error = executor + .execute(&test_command(1, "run.prepare", payload)) + .unwrap_err(); + assert!(error + .to_string() + .contains("Claude Managed provider descriptor is invalid")); + fs::remove_dir_all(directory).unwrap(); + } + + #[test] + fn claude_destroy_reports_closed_only_after_all_owned_resources_are_deleted() { + let directory = std::env::temp_dir().join(format!( + "paperclip-managed-provider-test-{}", + Uuid::new_v4() + )); + fs::create_dir_all(&directory).unwrap(); + #[cfg(unix)] + fs::set_permissions(&directory, fs::Permissions::from_mode(0o700)).unwrap(); + let config = test_config(&directory); + let failures = Arc::new(AtomicUsize::new(1)); + let mut executor = ManagedProviderCommandExecutor::with_factory( + &directory, + &config, + Box::new(FakeClaudeFactory { + observed_resume_skills: Arc::new(Mutex::new(Vec::new())), + created_skills: vec![ + ClaudeManagedSkillRef { + skill_id: "skill_instructions".to_owned(), + version: "skver_instructions".to_owned(), + }, + ClaudeManagedSkillRef { + skill_id: "skill_reviewer".to_owned(), + version: "skver_reviewer".to_owned(), + }, + ], + destroy_failures: Arc::clone(&failures), + }), + ); + executor + .execute(&test_command(1, "run.prepare", claude_prepare_payload())) + .unwrap(); + executor + .execute(&test_command(2, "session.open", json!({}))) + .unwrap(); + + let error = executor + .execute(&test_command(3, "session.destroy", json!({}))) + .unwrap_err(); + assert!(error + .to_string() + .contains("managed remote session deletion failed")); + let persisted_after_failure: Value = + serde_json::from_slice(&fs::read(executor.state_path()).unwrap()).unwrap(); + assert_eq!( + persisted_after_failure.get("lifecycle"), + Some(&json!("session_open")) + ); + + let closed = executor + .execute(&test_command(4, "session.destroy", json!({}))) + .unwrap(); + assert_eq!(closed.events.len(), 1); + assert_eq!( + closed.events[0].2.get("remoteStateDestroyed"), + Some(&json!(true)) + ); + let persisted_after_success: Value = + serde_json::from_slice(&fs::read(executor.state_path()).unwrap()).unwrap(); + assert_eq!( + persisted_after_success.get("lifecycle"), + Some(&json!("closed")) + ); + fs::remove_dir_all(directory).unwrap(); + } +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/native_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/native_provider_backend.rs index c6f50f9949..040bdea015 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/native_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/native_provider_backend.rs @@ -7,11 +7,15 @@ use crate::durable::{ Command, CommandExecution, CommandExecutor, DurableRunnerConfig, DurableRunnerError, PolledEvent, }; +use crate::managed_provider_backend::{ + ManagedProviderCommandExecutor, MANAGED_PROVIDER_STATE_FILE, +}; use crate::provider_backend::{CodexCommandExecutor, CODEX_PROVIDER_STATE_FILE}; enum SelectedExecutor { LocalFacade(CodexCommandExecutor), Acpx(AcpxCommandExecutor), + Managed(ManagedProviderCommandExecutor), } impl CommandExecutor for SelectedExecutor { @@ -19,6 +23,7 @@ impl CommandExecutor for SelectedExecutor { match self { Self::LocalFacade(executor) => executor.execute(command), Self::Acpx(executor) => executor.execute(command), + Self::Managed(executor) => executor.execute(command), } } @@ -26,6 +31,7 @@ impl CommandExecutor for SelectedExecutor { match self { Self::LocalFacade(executor) => executor.poll_events(), Self::Acpx(executor) => executor.poll_events(), + Self::Managed(executor) => executor.poll_events(), } } @@ -33,6 +39,7 @@ impl CommandExecutor for SelectedExecutor { match self { Self::LocalFacade(executor) => executor.acknowledge_events(count), Self::Acpx(executor) => executor.acknowledge_events(count), + Self::Managed(executor) => executor.acknowledge_events(count), } } @@ -40,6 +47,7 @@ impl CommandExecutor for SelectedExecutor { match self { Self::LocalFacade(executor) => executor.shutdown(), Self::Acpx(executor) => executor.shutdown(), + Self::Managed(executor) => executor.shutdown(), } } } @@ -71,12 +79,22 @@ impl NativeProviderCommandExecutor { self.recovery_checked = true; let codex = self.state_dir.join(CODEX_PROVIDER_STATE_FILE).exists(); let acpx = self.state_dir.join(ACPX_PROVIDER_STATE_FILE).exists(); - if codex && acpx { + let managed = self.state_dir.join(MANAGED_PROVIDER_STATE_FILE).exists(); + if [codex, acpx, managed] + .into_iter() + .filter(|present| *present) + .count() + > 1 + { return Err(DurableRunnerError::invalid( - "runner state contains conflicting local provider authorities", + "runner state contains conflicting provider authorities", )); } - self.selected = if acpx { + self.selected = if managed { + Some(SelectedExecutor::Managed( + ManagedProviderCommandExecutor::with_runner_config(&self.state_dir, &self.config), + )) + } else if acpx { Some(SelectedExecutor::Acpx( AcpxCommandExecutor::with_runner_config(&self.state_dir, &self.config), )) @@ -107,6 +125,9 @@ impl NativeProviderCommandExecutor { &self.state_dir, &self.config, )), + "claude_managed" | "aws_agentcore" => SelectedExecutor::Managed( + ManagedProviderCommandExecutor::with_runner_config(&self.state_dir, &self.config), + ), _ => { return Err(DurableRunnerError::invalid(format!( "provider kind {kind} is not executable through the local runnerd boundary" diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs index 3e4f2866ef..6d4c96ec84 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/process_supervisor.rs @@ -1,20 +1,390 @@ use std::collections::VecDeque; -use std::io::{self, BufRead, BufReader, Write}; -use std::path::Path; +use std::fs::{self, File, OpenOptions}; +use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Write}; +use std::path::{Path, PathBuf}; use std::process::{Child, ChildStdin, Command, ExitStatus, Stdio}; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, SyncSender}; +use std::sync::Arc; use std::thread; use std::time::{Duration, Instant}; #[cfg(unix)] use std::os::unix::process::CommandExt; +#[cfg(any(target_os = "linux", target_os = "macos"))] +use std::os::fd::AsRawFd; + +#[cfg(target_os = "macos")] +use std::os::unix::fs::{DirBuilderExt, FileExt, MetadataExt, OpenOptionsExt, PermissionsExt}; + +#[cfg(target_os = "macos")] +use uuid::Uuid; + use serde::Serialize; +use sha2::{Digest, Sha256}; use crate::local_runner::LocalRunnerError; const PROCESS_OUTPUT_QUEUE_CAPACITY: usize = 256; +#[derive(Clone, Debug)] +pub struct VerifiedProcessArtifact { + display_path: PathBuf, + file: Arc, +} + +impl VerifiedProcessArtifact { + pub fn snapshot_verified( + display_path: PathBuf, + mut file: File, + expected_sha256: &str, + ) -> Result { + file.seek(SeekFrom::Start(0)).map_err(|error| { + LocalRunnerError::invalid(format!( + "failed to rewind verified process artifact {}: {error}", + display_path.display() + )) + })?; + #[cfg(target_os = "linux")] + let file = sealed_snapshot(&display_path, &mut file, expected_sha256)?; + #[cfg(target_os = "macos")] + let file = unlinked_snapshot(&display_path, &mut file, expected_sha256)?; + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + return Err(LocalRunnerError::invalid( + "verified process snapshots are supported only on Linux and macOS", + )); + Ok(Self { + display_path, + file: Arc::new(file), + }) + } +} + +#[cfg(target_os = "linux")] +fn sealed_snapshot( + display_path: &Path, + source: &mut File, + expected_sha256: &str, +) -> Result { + use rustix::fs::{MemfdFlags, Mode, SealFlags}; + + let flags = MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING | MemfdFlags::EXEC; + let fd = match rustix::fs::memfd_create("paperclip-verified-launch", flags) { + Ok(fd) => fd, + Err(rustix::io::Errno::INVAL) => rustix::fs::memfd_create( + "paperclip-verified-launch", + MemfdFlags::CLOEXEC | MemfdFlags::ALLOW_SEALING, + ) + .map_err(|error| snapshot_error(display_path, error))?, + Err(error) => return Err(snapshot_error(display_path, error)), + }; + let mut snapshot = File::from(fd); + copy_verified(source, &mut snapshot, display_path, expected_sha256)?; + snapshot + .flush() + .map_err(|error| snapshot_error(display_path, error))?; + rustix::fs::fchmod(&snapshot, Mode::RUSR | Mode::XUSR) + .map_err(|error| snapshot_error(display_path, error))?; + rustix::fs::fcntl_add_seals( + &snapshot, + SealFlags::WRITE | SealFlags::GROW | SealFlags::SHRINK | SealFlags::SEAL, + ) + .map_err(|error| snapshot_error(display_path, error))?; + snapshot + .seek(SeekFrom::Start(0)) + .map_err(|error| snapshot_error(display_path, error))?; + Ok(snapshot) +} + +#[cfg(target_os = "macos")] +fn unlinked_snapshot( + display_path: &Path, + source: &mut File, + expected_sha256: &str, +) -> Result { + let temporary_path = std::env::temp_dir().join(format!( + ".paperclip-verified-launch-{}", + Uuid::new_v4().simple() + )); + let mut writable = OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .mode(0o700) + .open(&temporary_path) + .map_err(|error| snapshot_error(display_path, error))?; + let result = (|| { + copy_verified(source, &mut writable, display_path, expected_sha256)?; + writable + .sync_all() + .map_err(|error| snapshot_error(display_path, error))?; + fs::set_permissions(&temporary_path, fs::Permissions::from_mode(0o500)) + .map_err(|error| snapshot_error(display_path, error))?; + let snapshot = + File::open(&temporary_path).map_err(|error| snapshot_error(display_path, error))?; + let written = writable + .metadata() + .map_err(|error| snapshot_error(display_path, error))?; + let opened = snapshot + .metadata() + .map_err(|error| snapshot_error(display_path, error))?; + if written.dev() != opened.dev() || written.ino() != opened.ino() { + return Err(LocalRunnerError::invalid(format!( + "verified process snapshot {} changed while it was reopened", + display_path.display() + ))); + } + fs::remove_file(&temporary_path).map_err(|error| snapshot_error(display_path, error))?; + drop(writable); + Ok(snapshot) + })(); + if result.is_err() { + let _ = fs::remove_file(temporary_path); + } + result +} + +fn copy_verified( + source: &mut File, + destination: &mut File, + display_path: &Path, + expected_sha256: &str, +) -> Result<(), LocalRunnerError> { + let mut digest = Sha256::new(); + let mut buffer = [0_u8; 64 * 1024]; + loop { + let count = source + .read(&mut buffer) + .map_err(|error| snapshot_error(display_path, error))?; + if count == 0 { + break; + } + digest.update(&buffer[..count]); + destination + .write_all(&buffer[..count]) + .map_err(|error| snapshot_error(display_path, error))?; + } + let actual = format!("sha256:{:x}", digest.finalize()); + if actual != expected_sha256 { + return Err(LocalRunnerError::invalid(format!( + "verified process artifact digest mismatch for {}", + display_path.display() + ))); + } + Ok(()) +} + +fn snapshot_error(display_path: &Path, error: impl std::fmt::Display) -> LocalRunnerError { + LocalRunnerError::invalid(format!( + "failed to create immutable process snapshot for {}: {error}", + display_path.display() + )) +} + +#[derive(Clone, Debug)] +pub enum VerifiedProcessArgument { + Literal(String), + Artifact(VerifiedProcessArtifact), + ExecutableArtifact(VerifiedProcessArtifact), +} + +#[derive(Clone, Debug)] +pub struct VerifiedProcessLaunch { + program: VerifiedProcessArtifact, + args: Vec, +} + +impl VerifiedProcessLaunch { + pub fn new(program: VerifiedProcessArtifact, args: Vec) -> Self { + Self { program, args } + } + + #[cfg(any(target_os = "linux", target_os = "macos"))] + fn inherited_command(&self) -> Result { + let mut inherited = Vec::with_capacity(self.args.len() + 1); + #[cfg(target_os = "linux")] + let (program_fd, program) = inherited_artifact(&self.program)?; + #[cfg(target_os = "linux")] + inherited.push(program_fd); + #[cfg(target_os = "macos")] + let program_snapshot = materialize_executable(&self.program)?; + #[cfg(target_os = "macos")] + let program = program_snapshot.path.clone(); + #[cfg(target_os = "macos")] + let mut temporary_executables = vec![program_snapshot]; + let mut args = Vec::with_capacity(self.args.len()); + for argument in &self.args { + match argument { + VerifiedProcessArgument::Literal(value) => args.push(value.clone()), + VerifiedProcessArgument::Artifact(artifact) => { + let (fd, path) = inherited_artifact(artifact)?; + inherited.push(fd); + args.push(path.to_string_lossy().into_owned()); + } + VerifiedProcessArgument::ExecutableArtifact(artifact) => { + #[cfg(target_os = "linux")] + { + let (fd, path) = inherited_artifact(artifact)?; + inherited.push(fd); + args.push(path.to_string_lossy().into_owned()); + } + #[cfg(target_os = "macos")] + { + let executable = materialize_executable(artifact)?; + args.push(executable.path.to_string_lossy().into_owned()); + temporary_executables.push(executable); + } + } + } + } + Ok(InheritedCommand { + program, + args, + _inherited: inherited, + #[cfg(target_os = "macos")] + temporary_executables, + }) + } +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +struct InheritedCommand { + program: PathBuf, + args: Vec, + _inherited: Vec, + #[cfg(target_os = "macos")] + temporary_executables: Vec, +} + +#[cfg(target_os = "macos")] +struct TemporaryExecutable { + path: PathBuf, + directory: PathBuf, + _file: File, +} + +#[cfg(target_os = "macos")] +impl Drop for TemporaryExecutable { + fn drop(&mut self) { + let _ = fs::remove_file(&self.path); + let _ = fs::remove_dir(&self.directory); + } +} + +#[cfg(target_os = "macos")] +fn materialize_executable( + artifact: &VerifiedProcessArtifact, +) -> Result { + let directory = std::env::temp_dir().join(format!( + ".paperclip-verified-executable-{}", + Uuid::new_v4().simple() + )); + let mut directory_builder = fs::DirBuilder::new(); + directory_builder.mode(0o700); + directory_builder + .create(&directory) + .map_err(|error| snapshot_error(&artifact.display_path, error))?; + let path = directory.join("launch"); + let writable = OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .mode(0o700) + .open(&path); + let mut writable = match writable { + Ok(file) => file, + Err(error) => { + let _ = fs::remove_dir(&directory); + return Err(snapshot_error(&artifact.display_path, error)); + } + }; + let result = (|| { + let length = artifact + .file + .metadata() + .map_err(|error| snapshot_error(&artifact.display_path, error))? + .len(); + let mut offset = 0_u64; + let mut buffer = [0_u8; 64 * 1024]; + while offset < length { + let count = artifact + .file + .read_at(&mut buffer, offset) + .map_err(|error| snapshot_error(&artifact.display_path, error))?; + if count == 0 { + return Err(LocalRunnerError::invalid(format!( + "immutable process snapshot for {} ended unexpectedly", + artifact.display_path.display() + ))); + } + writable + .write_all(&buffer[..count]) + .map_err(|error| snapshot_error(&artifact.display_path, error))?; + offset += count as u64; + } + writable + .sync_all() + .map_err(|error| snapshot_error(&artifact.display_path, error))?; + fs::set_permissions(&path, fs::Permissions::from_mode(0o500)) + .map_err(|error| snapshot_error(&artifact.display_path, error))?; + let file = + File::open(&path).map_err(|error| snapshot_error(&artifact.display_path, error))?; + let written = writable + .metadata() + .map_err(|error| snapshot_error(&artifact.display_path, error))?; + let opened = file + .metadata() + .map_err(|error| snapshot_error(&artifact.display_path, error))?; + if written.dev() != opened.dev() || written.ino() != opened.ino() { + return Err(LocalRunnerError::invalid(format!( + "private executable snapshot {} changed while it was reopened", + artifact.display_path.display() + ))); + } + drop(writable); + Ok(TemporaryExecutable { + path: path.clone(), + directory: directory.clone(), + _file: file, + }) + })(); + if result.is_err() { + let _ = fs::remove_file(path); + let _ = fs::remove_dir(directory); + } + result +} + +#[cfg(any(target_os = "linux", target_os = "macos"))] +fn inherited_artifact( + artifact: &VerifiedProcessArtifact, +) -> Result<(rustix::fd::OwnedFd, PathBuf), LocalRunnerError> { + let fd = rustix::io::dup(&*artifact.file).map_err(|error| { + LocalRunnerError::invalid(format!( + "failed to inherit verified process artifact {}: {error}", + artifact.display_path.display() + )) + })?; + let mut flags = rustix::io::fcntl_getfd(&fd).map_err(|error| { + LocalRunnerError::invalid(format!( + "failed to inspect inherited process artifact {}: {error}", + artifact.display_path.display() + )) + })?; + flags.remove(rustix::io::FdFlags::CLOEXEC); + rustix::io::fcntl_setfd(&fd, flags).map_err(|error| { + LocalRunnerError::invalid(format!( + "failed to inherit process artifact {} across exec: {error}", + artifact.display_path.display() + )) + })?; + #[cfg(target_os = "linux")] + let path = PathBuf::from(format!("/proc/self/fd/{}", fd.as_raw_fd())); + #[cfg(target_os = "macos")] + let path = PathBuf::from(format!("/dev/fd/{}", fd.as_raw_fd())); + Ok((fd, path)) +} + pub(crate) enum ProcessOutput { Stdout(String), Stderr(String), @@ -193,6 +563,8 @@ pub struct SupervisedProcess { process_group_id: u32, shutdown_grace: Duration, finished: bool, + #[cfg(target_os = "macos")] + _temporary_executables: Vec, } impl SupervisedProcess { @@ -211,6 +583,60 @@ impl SupervisedProcess { shutdown_grace: Duration, max_line_bytes: usize, additional_environment_keys: &[&str], + ) -> Result { + Self::spawn_command( + program, + args, + shutdown_grace, + max_line_bytes, + additional_environment_keys, + ) + } + + pub fn spawn_verified_with_environment_keys( + launch: &VerifiedProcessLaunch, + shutdown_grace: Duration, + max_line_bytes: usize, + additional_environment_keys: &[&str], + ) -> Result { + #[cfg(any(target_os = "linux", target_os = "macos"))] + { + let mut inherited = launch.inherited_command()?; + let mut result = Self::spawn_command( + &inherited.program, + &inherited.args, + shutdown_grace, + max_line_bytes, + additional_environment_keys, + ); + #[cfg(target_os = "macos")] + if let Ok(process) = result.as_mut() { + process._temporary_executables = + std::mem::take(&mut inherited.temporary_executables); + } + drop(inherited); + result + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + { + let _ = ( + launch, + shutdown_grace, + max_line_bytes, + additional_environment_keys, + ); + Err(LocalRunnerError::invalid( + "verified process launch is supported only on Linux and macOS", + )) + } + } + + fn spawn_command( + program: &Path, + args: &[String], + shutdown_grace: Duration, + max_line_bytes: usize, + additional_environment_keys: &[&str], ) -> Result { let mut command = Command::new(program); command @@ -273,6 +699,8 @@ impl SupervisedProcess { process_group_id, shutdown_grace, finished: false, + #[cfg(target_os = "macos")] + _temporary_executables: Vec::new(), }) } diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs index 0b34552365..a34e91dc2f 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/src/provider_backend.rs @@ -10,6 +10,7 @@ use std::os::unix::fs::PermissionsExt; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; use crate::codex_provider::{ CodexProvider, CodexProviderConfig, CodexProviderEvent, RejectedAcceptedTurn, @@ -18,7 +19,7 @@ use crate::codex_provider::{ use crate::durable::{ create_private_temporary_file, current_unix_ms, open_private_regular_file, sanitize_value, verify_private_directory, Command, CommandExecution, CommandExecutor, DurableRunnerConfig, - DurableRunnerError, EventPriority, PolledEvent, + DurableRunnerError, EventPriority, OpenCodeLaunchProfile, PolledEvent, }; use crate::provider_bridge::{ authorized_tool_catalog_digest, semantic_value_digest, AuthorizedToolSet, DurableReplayFilter, @@ -357,6 +358,8 @@ struct CodexProviderState { lifecycle: String, config: CodexProviderConfig, #[serde(default)] + opencode_launch_profile_digest: Option, + #[serde(default)] completion_contract: Option, #[serde(default)] tool_bridge: ProviderToolBridge, @@ -445,6 +448,7 @@ impl CodexProviderState { schema: PROVIDER_STATE_SCHEMA.to_owned(), lifecycle: "prepared".to_owned(), config, + opencode_launch_profile_digest: None, completion_contract, tool_bridge, thread_id, @@ -828,6 +832,8 @@ pub struct CodexCommandExecutor { provider: Option, event_identity: Option, restore_checked: bool, + restore_error: Option, + opencode_launch_profile: Option, } impl CodexCommandExecutor { @@ -838,15 +844,49 @@ impl CodexCommandExecutor { provider: None, event_identity: None, restore_checked: false, + restore_error: None, + opencode_launch_profile: None, } } pub fn with_runner_config(state_dir: impl Into, config: &DurableRunnerConfig) -> Self { let mut executor = Self::new(state_dir); executor.event_identity = Some(ProviderEventIdentity::from_config(config)); + executor.opencode_launch_profile = config.opencode_launch_profile.clone(); executor } + fn bind_opencode_launch_profile( + &self, + config: &CodexProviderConfig, + ) -> Result, DurableRunnerError> { + if config.provider != "opencode" { + return Ok(None); + } + let profile = self.opencode_launch_profile.as_ref().ok_or_else(|| { + DurableRunnerError::invalid( + "OpenCode runner startup omitted its qualified launch profile", + ) + })?; + let proxy_script = profile.proxy_script.path.to_string_lossy(); + if config.command != profile.command.path + || config.args.as_slice() != [proxy_script.as_ref()] + { + return Err(DurableRunnerError::invalid( + "OpenCode run.prepare launch does not match the runner-owned qualified profile", + )); + } + let mut digest = Sha256::new(); + digest.update(b"paperclip.runner.opencode-launch-profile.v1\0"); + for artifact in [&profile.command, &profile.proxy_script, &profile.executable] { + digest.update(artifact.path.to_string_lossy().as_bytes()); + digest.update(b"\0"); + digest.update(artifact.sha256.as_bytes()); + digest.update(b"\0"); + } + Ok(Some(format!("sha256:{:x}", digest.finalize()))) + } + fn state_path(&self) -> PathBuf { self.state_dir.join(CODEX_PROVIDER_STATE_FILE) } @@ -855,7 +895,22 @@ impl CodexCommandExecutor { if self.restore_checked { return Ok(()); } - self.restore_checked = true; + if let Some(error) = self.restore_error.as_ref() { + return Err(error.clone()); + } + match self.restore_once() { + Ok(()) => { + self.restore_checked = true; + Ok(()) + } + Err(error) => { + self.restore_error = Some(error.clone()); + Err(error) + } + } + } + + fn restore_once(&mut self) -> Result<(), DurableRunnerError> { let path = self.state_path(); let mut file = match open_private_regular_file(&path) { Ok(file) => file, @@ -887,6 +942,12 @@ impl CodexCommandExecutor { )) })?; state.validate()?; + let expected_launch_profile_digest = self.bind_opencode_launch_profile(&state.config)?; + if state.opencode_launch_profile_digest != expected_launch_profile_digest { + return Err(DurableRunnerError::invalid( + "OpenCode runner launch profile changed across durable recovery", + )); + } self.state = Some(state); self.restore_provider_if_needed() } @@ -939,6 +1000,7 @@ impl CodexCommandExecutor { state.tool_bridge.authorized_tools().cloned(), Some(&thread_id), process_generation, + self.opencode_launch_profile.as_ref(), ) .map_err(|error| { DurableRunnerError::invalid(format!( @@ -1257,6 +1319,7 @@ impl CodexCommandExecutor { .map_err(|error| DurableRunnerError::invalid(error.to_string()))?; let provider_name = config.provider.clone(); let driver = config.driver.clone(); + let opencode_launch_profile_digest = self.bind_opencode_launch_profile(&config)?; let completion_contract = completion_contract(payload)?; let tool_set = authorized_tool_set(payload)?; if let Some(state) = self.state.as_mut() { @@ -1265,6 +1328,11 @@ impl CodexCommandExecutor { "Codex provider or completion contract changed across the durable run", )); } + if state.opencode_launch_profile_digest != opencode_launch_profile_digest { + return Err(DurableRunnerError::invalid( + "OpenCode runner launch profile changed across the durable run", + )); + } if state.lifecycle == "closed" { return Err(DurableRunnerError::invalid( "Codex provider session is already closed", @@ -1292,11 +1360,9 @@ impl CodexCommandExecutor { tool_bridge.prepare(tool_set).map_err(|error| { DurableRunnerError::invalid(format!("run.prepare tool contract rejected: {error}")) })?; - self.state = Some(CodexProviderState::new( - config, - completion_contract, - tool_bridge, - )); + let mut state = CodexProviderState::new(config, completion_contract, tool_bridge); + state.opencode_launch_profile_digest = opencode_launch_profile_digest; + self.state = Some(state); self.save_state()?; } Ok(CommandExecution::result(json!({ @@ -1328,6 +1394,7 @@ impl CodexCommandExecutor { state.tool_bridge.authorized_tools().cloned(), state.thread_id.as_deref(), process_generation, + self.opencode_launch_profile.as_ref(), ) .map_err(|error| { DurableRunnerError::invalid(format!("failed to start Codex provider: {error}")) @@ -2740,6 +2807,7 @@ mod tests { instructions: String::new(), approval_policy: "never".to_owned(), }, + opencode_launch_profile_digest: None, completion_contract: None, tool_bridge: ProviderToolBridge::default(), thread_id: Some("thread-1".to_owned()), diff --git a/packages/paperclip-runner/runner/crates/runner-core/src/qualified_launch.rs b/packages/paperclip-runner/runner/crates/runner-core/src/qualified_launch.rs new file mode 100644 index 0000000000..c7f72d3152 --- /dev/null +++ b/packages/paperclip-runner/runner/crates/runner-core/src/qualified_launch.rs @@ -0,0 +1,70 @@ +use std::fs::{self, File}; + +#[cfg(unix)] +use std::os::unix::fs::{MetadataExt, PermissionsExt}; + +use crate::durable::{DurableRunnerError, QualifiedLaunchArtifact}; +use crate::process_supervisor::VerifiedProcessArtifact; + +pub fn verify_launch_artifact( + artifact: &QualifiedLaunchArtifact, + label: &str, +) -> Result { + let source_metadata = fs::symlink_metadata(&artifact.path).map_err(|error| { + DurableRunnerError::invalid(format!("failed to inspect qualified {label}: {error}")) + })?; + if source_metadata.file_type().is_symlink() || !source_metadata.is_file() { + return Err(DurableRunnerError::invalid(format!( + "qualified {label} must be a regular file, not a symlink" + ))); + } + #[cfg(unix)] + if source_metadata.permissions().mode() & 0o022 != 0 { + return Err(DurableRunnerError::invalid(format!( + "qualified {label} must not be group- or world-writable" + ))); + } + + let canonical = fs::canonicalize(&artifact.path).map_err(|error| { + DurableRunnerError::invalid(format!("failed to resolve qualified {label}: {error}")) + })?; + let canonical_metadata = fs::symlink_metadata(&canonical).map_err(|error| { + DurableRunnerError::invalid(format!( + "failed to inspect resolved qualified {label}: {error}" + )) + })?; + if canonical_metadata.file_type().is_symlink() || !canonical_metadata.is_file() { + return Err(DurableRunnerError::invalid(format!( + "resolved qualified {label} must be a regular file" + ))); + } + let file = File::open(&canonical).map_err(|error| { + DurableRunnerError::invalid(format!("failed to open qualified {label}: {error}")) + })?; + let opened_metadata = file.metadata().map_err(|error| { + DurableRunnerError::invalid(format!("failed to identify qualified {label}: {error}")) + })?; + if !same_file(&canonical_metadata, &opened_metadata) { + return Err(DurableRunnerError::invalid(format!( + "qualified {label} changed while it was opened" + ))); + } + VerifiedProcessArtifact::snapshot_verified(canonical, file, &artifact.sha256) + .map_err(|error| DurableRunnerError::invalid(error.to_string())) +} + +#[cfg(unix)] +fn same_file(left: &fs::Metadata, right: &fs::Metadata) -> bool { + left.dev() == right.dev() + && left.ino() == right.ino() + && left.len() == right.len() + && left.mtime() == right.mtime() + && left.mtime_nsec() == right.mtime_nsec() +} + +#[cfg(not(unix))] +fn same_file(left: &fs::Metadata, right: &fs::Metadata) -> bool { + left.len() == right.len() + && left.modified().ok() == right.modified().ok() + && left.is_file() == right.is_file() +} diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs index 688053d674..c5897506e0 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_checkpoint.rs @@ -38,6 +38,7 @@ fn config(directory: &std::path::Path) -> AcpxProviderSessionConfig { transport: AcpxSidecarTransportConfig { command: PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")), args: vec!["--mode".to_owned(), "suspend".to_owned()], + verified_launch: None, request_timeout: Duration::from_secs(1), shutdown_grace: Duration::from_millis(100), }, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs index a551825a7f..81d30d7376 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_resolutions.rs @@ -37,6 +37,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { transport: AcpxSidecarTransportConfig { command: PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")), args: vec!["--mode".to_owned(), mode.to_owned()], + verified_launch: None, request_timeout: Duration::from_secs(1), shutdown_grace: Duration::from_millis(100), }, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs index a57168370d..e0ae0f7f17 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_session.rs @@ -31,6 +31,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { transport: AcpxSidecarTransportConfig { command: PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")), args: vec!["--mode".to_owned(), mode.to_owned()], + verified_launch: None, request_timeout: Duration::from_secs(1), shutdown_grace: Duration::from_millis(100), }, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs index 444ee6ddd9..1054261f3e 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_suspend.rs @@ -13,6 +13,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { transport: AcpxSidecarTransportConfig { command: PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")), args: vec!["--mode".to_owned(), mode.to_owned()], + verified_launch: None, request_timeout: Duration::from_secs(1), shutdown_grace: Duration::from_millis(100), }, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs index c751b43b7a..35b8db1148 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_provider_turns.rs @@ -32,6 +32,7 @@ fn config(mode: &str) -> AcpxProviderSessionConfig { transport: AcpxSidecarTransportConfig { command: PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")), args: vec!["--mode".to_owned(), mode.to_owned()], + verified_launch: None, request_timeout: Duration::from_secs(1), shutdown_grace: Duration::from_millis(100), }, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs index 41d402c1d8..bbead687b5 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_transport.rs @@ -13,6 +13,7 @@ fn transport(mode: &str, timeout: Duration) -> AcpxSidecarTransport { AcpxSidecarTransport::start(&AcpxSidecarTransportConfig { command: PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")), args: vec!["--mode".to_owned(), mode.to_owned()], + verified_launch: None, request_timeout: timeout, shutdown_grace: Duration::from_millis(50), }) diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs index 0010ae6051..7765c626df 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/codex_provider.rs @@ -90,6 +90,8 @@ fn durable_config(directory: &Path) -> DurableRunnerConfig { item_id: "item-1".to_owned(), runner_version: "test-1".to_owned(), runner_digest: format!("sha256:{}", "a".repeat(64)), + acpx_launch_profile: None, + opencode_launch_profile: None, max_outbox_bytes: 16 * 1024 * 1024, p0_reserve_bytes: 1024 * 1024, max_frame_bytes: 1024 * 1024, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/durable_recovery.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/durable_recovery.rs index 30293f39e3..dbb7ee18f8 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/durable_recovery.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/durable_recovery.rs @@ -23,6 +23,8 @@ fn config(state_dir: PathBuf) -> DurableRunnerConfig { item_id: "item_1".to_owned(), runner_version: "0.0.0".to_owned(), runner_digest: "sha256:test".to_owned(), + acpx_launch_profile: None, + opencode_launch_profile: None, max_outbox_bytes: 16_384, p0_reserve_bytes: 4096, max_frame_bytes: 65_536, diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs index 3203da016f..815eedea69 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/native_provider_backend.rs @@ -5,10 +5,14 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; -use paperclip_runner_core::durable::{Command, CommandExecutor, DurableRunnerConfig}; +use paperclip_runner_core::durable::{ + AcpxLaunchProfile, Command, CommandExecutor, DurableRunnerConfig, OpenCodeLaunchProfile, + QualifiedLaunchArtifact, +}; use paperclip_runner_core::native_provider_backend::NativeProviderCommandExecutor; use paperclip_runner_core::provider_bridge::authorized_tool_catalog_digest; use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; const CODEX_ACPX_DIGEST: &str = "sha256:94049b3e3c3aee87de62703786e4fa81d031d7bd979f99bdf516d84f28791a79"; @@ -41,6 +45,8 @@ fn config(state_dir: &Path) -> DurableRunnerConfig { item_id: "item-1".to_owned(), runner_version: "0.0.0".to_owned(), runner_digest: "sha256:test".to_owned(), + acpx_launch_profile: None, + opencode_launch_profile: None, max_outbox_bytes: 1024 * 1024, p0_reserve_bytes: 64 * 1024, max_frame_bytes: 1024 * 1024, @@ -50,6 +56,66 @@ fn config(state_dir: &Path) -> DurableRunnerConfig { } } +fn acpx_config(state_dir: &Path, mode: &str) -> DurableRunnerConfig { + let mut config = config(state_dir); + let command = PathBuf::from(env!("CARGO_BIN_EXE_fake-acpx-sidecar")); + config.acpx_launch_profile = Some(AcpxLaunchProfile { + authority_digest: format!("sha256:{}", "d".repeat(64)), + command: command.clone(), + args: vec![ + "--mode".to_owned(), + mode.to_owned(), + "--profile-digest".to_owned(), + CODEX_ACPX_DIGEST.to_owned(), + ], + artifacts: vec![QualifiedLaunchArtifact { + sha256: format!("sha256:{:x}", Sha256::digest(fs::read(&command).unwrap())), + path: command, + }], + }); + config +} + +fn qualified_artifact(path: PathBuf) -> QualifiedLaunchArtifact { + QualifiedLaunchArtifact { + sha256: format!("sha256:{:x}", Sha256::digest(fs::read(&path).unwrap())), + path, + } +} + +fn opencode_config(state_dir: &Path) -> DurableRunnerConfig { + let command = state_dir.join("qualified-opencode-proxy-command"); + let proxy_script = state_dir.join("qualified-opencode-proxy-script"); + let executable = state_dir.join("qualified-opencode-executable"); + fs::write( + &command, + "#!/bin/sh\nproxy=\"$1\"\nshift\nexec /bin/sh \"$proxy\" \"$@\"\n", + ) + .unwrap(); + fs::write( + &proxy_script, + format!( + "#!/bin/sh\nexec '{}' --state-file '{}' --call-log '{}'\n", + env!("CARGO_BIN_EXE_fake-codex-app-server"), + state_dir.join("fake-opencode-state.json").display(), + state_dir.join("fake-opencode-calls.log").display(), + ), + ) + .unwrap(); + fs::write(&executable, "qualified OpenCode test executable\n").unwrap(); + #[cfg(unix)] + for path in [&command, &proxy_script, &executable] { + fs::set_permissions(path, fs::Permissions::from_mode(0o500)).unwrap(); + } + let mut config = config(state_dir); + config.opencode_launch_profile = Some(OpenCodeLaunchProfile { + command: qualified_artifact(command), + proxy_script: qualified_artifact(proxy_script), + executable: qualified_artifact(executable), + }); + config +} + fn command(sequence: u64, command_type: &str, payload: Value) -> Command { Command { schema: "paperclip.prp.command.v1".to_owned(), @@ -111,7 +177,7 @@ fn prepare_payload_with_mode(directory: &Path, agent: &str, mode: &str) -> Value #[test] fn preserves_acpx_semantic_disposition_in_the_run_terminal() { let directory = temporary_directory("acpx-blocked"); - let config = config(&directory); + let config = acpx_config(&directory, "turns-reserved-block-terminal"); let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config); executor @@ -158,13 +224,8 @@ fn opencode_prepare_payload(directory: &Path) -> Value { "provider": "opencode", "driver": "opencode_server", "providerVersion": "1.18.17", - "command": env!("CARGO_BIN_EXE_fake-codex-app-server"), - "args": [ - "--state-file", - directory.join("fake-opencode-state.json"), - "--call-log", - directory.join("fake-opencode-calls.log"), - ], + "command": directory.join("qualified-opencode-proxy-command"), + "args": [directory.join("qualified-opencode-proxy-script")], "cwd": directory, "model": "openrouter/model", "approvalPolicy": "never", @@ -173,10 +234,80 @@ fn opencode_prepare_payload(directory: &Path) -> Value { }) } +fn managed_prepare_payload(kind: &str) -> Value { + let operations = Vec::new(); + let provider = match kind { + "claude_managed" => json!({ + "kind": "claude_managed", + "model": "claude-sonnet-5", + "profileId": "profile-1", + "anthropicAgentId": "agent-1", + "agentVersion": "1", + "environmentId": "environment-1", + "betaVersion": "managed-agents-2026-04-01", + "maxSessionListCostUsd": 1.0, + "instructions": "Complete the supplied task.", + "runtimeContext": null, + }), + "aws_agentcore" => json!({ + "kind": "aws_agentcore", + "model": "global.anthropic.claude-sonnet-4-6", + "profileId": "profile-1", + "region": "us-east-1", + "accountId": "123456789012", + "harnessArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/test", + "harnessVersion": "1", + "endpointArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/test", + "endpointQualifier": "1", + "agentRuntimeArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/test", + "memoryArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/test", + "memoryId": "memory-1", + "invocationRoleArn": "arn:aws:iam::123456789012:role/runner", + "contextBucket": "context-bucket", + "contextPrefix": "companies/company/profiles/profile", + "contextKmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/test", + "qualificationRevision": "aws-agentcore-harness-v1", + "eventExpiryDays": 90, + "maxEstimatedSessionCostUsd": 1.0, + "maxIterations": 8, + "maxOutputTokens": 4096, + "timeoutSeconds": 300, + "instructions": "Complete the supplied task.", + "runtimeContext": null, + }), + _ => panic!("unsupported fixture"), + }; + json!({ + "authorizedTools": { + "schema": "paperclip.runner.authorized-tools.v1", + "schemaVersion": 1, + "catalogDigest": authorized_tool_catalog_digest(&operations).unwrap(), + "operations": operations, + }, + "provider": provider, + }) +} + +#[test] +fn preserves_managed_provider_descriptors_through_the_native_selector() { + for kind in ["claude_managed", "aws_agentcore"] { + let directory = temporary_directory(kind); + let config = config(&directory); + let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + let prepared = executor + .execute(&command(1, "run.prepare", managed_prepare_payload(kind))) + .unwrap(); + assert_eq!(prepared.result["provider"], kind); + assert!(directory.join("managed-provider-state.json").exists()); + executor.shutdown().unwrap(); + fs::remove_dir_all(directory).unwrap(); + } +} + #[test] fn executes_a_qualified_acpx_profile_through_the_native_selector() { let directory = temporary_directory("acpx"); - let config = config(&directory); + let config = acpx_config(&directory, "turns-reserved-result-terminal"); let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config); let prepared = executor @@ -223,7 +354,7 @@ fn executes_a_qualified_acpx_profile_through_the_native_selector() { #[test] fn executes_opencode_through_the_local_facade_without_codex_event_labels() { let directory = temporary_directory("opencode"); - let config = config(&directory); + let config = opencode_config(&directory); let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config); let prepared = executor @@ -288,6 +419,76 @@ fn executes_opencode_through_the_local_facade_without_codex_event_labels() { fs::remove_dir_all(directory).unwrap(); } +#[test] +fn rejects_a_mutable_opencode_command_outside_the_runner_launch_profile() { + let directory = temporary_directory("opencode-command-override"); + let config = opencode_config(&directory); + let mut payload = opencode_prepare_payload(&directory); + payload["provider"]["command"] = json!(env!("CARGO_BIN_EXE_fake-codex-app-server")); + let mut executor = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + + let error = executor + .execute(&command(1, "run.prepare", payload)) + .unwrap_err(); + assert!(error + .to_string() + .contains("does not match the runner-owned qualified profile")); + + executor.shutdown().unwrap(); + fs::remove_dir_all(directory).unwrap(); +} + +#[test] +fn rejects_opencode_launch_profile_drift_across_fresh_recovery() { + let directory = temporary_directory("opencode-profile-recovery"); + let config = opencode_config(&directory); + let mut first = NativeProviderCommandExecutor::with_runner_config(&directory, &config); + first + .execute(&command( + 1, + "run.prepare", + opencode_prepare_payload(&directory), + )) + .unwrap(); + first.shutdown().unwrap(); + drop(first); + + let mut changed = config.clone(); + changed + .opencode_launch_profile + .as_mut() + .unwrap() + .executable + .sha256 = format!("sha256:{}", "a".repeat(64)); + let mut recovered = NativeProviderCommandExecutor::with_runner_config(&directory, &changed); + let state_path = directory.join("codex-provider-state.json"); + let state_before_recovery = fs::read(&state_path).unwrap(); + let error = recovered + .execute(&command( + 2, + "run.prepare", + opencode_prepare_payload(&directory), + )) + .unwrap_err(); + assert!(error + .to_string() + .contains("launch profile changed across durable recovery")); + let second_error = recovered + .execute(&command( + 3, + "run.prepare", + opencode_prepare_payload(&directory), + )) + .unwrap_err(); + assert!(second_error + .to_string() + .contains("launch profile changed across durable recovery")); + assert_eq!(fs::read(&state_path).unwrap(), state_before_recovery); + + recovered.shutdown().unwrap(); + fs::remove_dir_all(directory).unwrap(); +} + #[test] fn rejects_pi_before_starting_a_sidecar() { let directory = temporary_directory("pi"); diff --git a/packages/paperclip-runner/runner/crates/runner-core/tests/process_supervisor.rs b/packages/paperclip-runner/runner/crates/runner-core/tests/process_supervisor.rs index 5353a40979..d3671ecccf 100644 --- a/packages/paperclip-runner/runner/crates/runner-core/tests/process_supervisor.rs +++ b/packages/paperclip-runner/runner/crates/runner-core/tests/process_supervisor.rs @@ -1,13 +1,19 @@ #![cfg(unix)] -use std::path::PathBuf; +use std::fs::{self, File}; +use std::io::Write; +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; use std::process::Command; use std::process::Stdio; use std::time::Duration; use paperclip_runner_core::local_runner::HarnessCommand; -use paperclip_runner_core::process_supervisor::SupervisedProcess; +use paperclip_runner_core::process_supervisor::{ + SupervisedProcess, VerifiedProcessArgument, VerifiedProcessArtifact, VerifiedProcessLaunch, +}; use serde_json::{json, Value}; +use sha2::{Digest, Sha256}; fn process_exists(pid: u64) -> bool { Command::new("kill") @@ -18,6 +24,90 @@ fn process_exists(pid: u64) -> bool { .is_ok_and(|status| status.success()) } +fn write_executable(path: &Path, contents: &str) { + let mut file = File::create(path).unwrap(); + file.write_all(contents.as_bytes()).unwrap(); + file.sync_all().unwrap(); + fs::set_permissions(path, fs::Permissions::from_mode(0o700)).unwrap(); +} + +fn sha256(contents: &str) -> String { + format!("sha256:{:x}", Sha256::digest(contents.as_bytes())) +} + +#[test] +fn verified_launch_uses_open_command_and_script_after_atomic_path_replacement() { + let directory = std::env::temp_dir().join(format!( + "paperclip-verified-launch-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + fs::create_dir(&directory).unwrap(); + let command = directory.join("command"); + let script = directory.join("script"); + let original_command = "#!/bin/sh\nprintf '%s\\n' old-command\nexec /bin/sh \"$1\"\n"; + let original_script = "#!/bin/sh\nprintf '%s\\n' old-script\n"; + write_executable(&command, original_command); + write_executable(&script, original_script); + + let launch = VerifiedProcessLaunch::new( + VerifiedProcessArtifact::snapshot_verified( + command.clone(), + File::open(&command).unwrap(), + &sha256(original_command), + ) + .unwrap(), + vec![VerifiedProcessArgument::Artifact( + VerifiedProcessArtifact::snapshot_verified( + script.clone(), + File::open(&script).unwrap(), + &sha256(original_script), + ) + .unwrap(), + )], + ); + + let replacement_command = directory.join("replacement-command"); + let replacement_script = directory.join("replacement-script"); + write_executable( + &replacement_command, + "#!/bin/sh\nprintf '%s\\n' replacement-command\nexec /bin/sh \"$1\"\n", + ); + write_executable( + &replacement_script, + "#!/bin/sh\nprintf '%s\\n' replacement-script\n", + ); + fs::rename(replacement_command, &command).unwrap(); + fs::rename(replacement_script, &script).unwrap(); + + let mut process = SupervisedProcess::spawn_verified_with_environment_keys( + &launch, + Duration::from_millis(50), + 1024, + &[], + ) + .unwrap(); + assert_eq!( + process + .receive_stdout_line(Duration::from_secs(1)) + .unwrap() + .as_deref(), + Some("old-command") + ); + assert_eq!( + process + .receive_stdout_line(Duration::from_secs(1)) + .unwrap() + .as_deref(), + Some("old-script") + ); + process.wait().unwrap(); + fs::remove_dir_all(directory).unwrap(); +} + fn spawn_linger_process() -> (SupervisedProcess, u32, u64) { let harness = PathBuf::from(env!("CARGO_BIN_EXE_fake-harness")); let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")) diff --git a/packages/paperclip-runner/src/backends/codex-native-backend.ts b/packages/paperclip-runner/src/backends/codex-native-backend.ts index e0646eeb60..5633afa988 100644 --- a/packages/paperclip-runner/src/backends/codex-native-backend.ts +++ b/packages/paperclip-runner/src/backends/codex-native-backend.ts @@ -32,7 +32,12 @@ export interface CodexNativeSessionBackendOptions { function transportDriverIdentity( input: NativeExecutionInput, ): { - kind: "codex_app_server" | "opencode_server" | "acpx_runtime"; + kind: + | "codex_app_server" + | "opencode_server" + | "claude_managed_agents_api" + | "aws_agentcore_harness_api" + | "acpx_runtime"; displayName: string; version: string; } { @@ -49,6 +54,18 @@ function transportDriverIdentity( displayName: "OpenCode server", version: "1.18.17", }; + case "claude_managed": + return { + kind: "claude_managed_agents_api", + displayName: "Claude Managed Agent", + version: input.provider.managedProfile.betaVersion, + }; + case "aws_agentcore": + return { + kind: "aws_agentcore_harness_api", + displayName: "AWS AgentCore Harness", + version: input.provider.agentCoreProfile.qualificationRevision, + }; case "acpx": if (input.provider.agent === "pi") { throw new Error( @@ -62,7 +79,7 @@ function transportDriverIdentity( }; default: throw new Error( - `Native backend for ${input.provider.kind} is not available through the local runnerd transport`, + "Native provider is not available through the local runnerd transport", ); } } @@ -76,7 +93,7 @@ function createTransportBackedNativeSessionBackend( return new HarnessDriverBackend(new CodexAppServerDriver({ ...(input.provider.model ? { model: input.provider.model } : {}), - // Runnerd owns provider permissions for the OpenCode/ACPX facades. Their + // Runnerd owns provider permissions for non-Codex facades. Their // Codex-compatible surface must never open a second approval channel. approvalPolicy: input.provider.kind === "codex" diff --git a/packages/paperclip-runner/src/backends/native-backend-factory.test.ts b/packages/paperclip-runner/src/backends/native-backend-factory.test.ts index 52385a5eb7..a921f40484 100644 --- a/packages/paperclip-runner/src/backends/native-backend-factory.test.ts +++ b/packages/paperclip-runner/src/backends/native-backend-factory.test.ts @@ -121,6 +121,71 @@ function opencodeExecution(): NativeExecutionInput { }; } +function managedExecution( + kind: "claude_managed" | "aws_agentcore", +): NativeExecutionInput { + if (kind === "claude_managed") { + return { + ...execution(), + session: { + normalizedSessionId: "session", + driverKind: "claude_managed_agents_api", + protocolVersion: 1, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, + }, + provider: { + kind, + model: "claude-sonnet-5", + maxSessionListCostUsd: 1, + managedProfile: { + profileId: "profile", + anthropicAgentId: "agent", + agentVersion: "1", + environmentId: "environment", + betaVersion: "managed-agents-2026-04-01", + }, + }, + }; + } + return { + ...execution(), + session: { + normalizedSessionId: "session", + driverKind: "aws_agentcore_harness_api", + protocolVersion: 1, + lifecyclePolicy: { mode: "per_turn", idleTimeoutMs: null }, + }, + provider: { + kind, + model: "global.anthropic.claude-sonnet-4-6", + maxEstimatedSessionCostUsd: 1, + invocationLimits: { + maxIterations: 8, + maxOutputTokens: 4096, + timeoutSeconds: 300, + }, + agentCoreProfile: { + profileId: "profile", + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/test", + harnessVersion: "1", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/test", + endpointQualifier: "1", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/test", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/test", + memoryId: "memory", + invocationRoleArn: "arn:aws:iam::123456789012:role/runner", + contextBucket: "context-bucket", + contextPrefix: "companies/company/profiles/profile", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/test", + qualificationRevision: "aws-agentcore-harness-v1", + eventExpiryDays: 90, + }, + }, + }; +} + describe("native backend factory", () => { it("constructs the Codex backend without starting its transport", async () => { const backend = createNativeSessionBackend(execution(), { @@ -165,6 +230,29 @@ describe("native backend factory", () => { }); }); + it.each([ + ["claude_managed" as const, "claude_managed_agents_api", "managed-agents-2026-04-01"], + ["aws_agentcore" as const, "aws_agentcore_harness_api", "aws-agentcore-harness-v1"], + ])("routes %s through runnerd", async (kind, name, version) => { + const backend = createNativeSessionBackend(managedExecution(kind), { + codexTransportFactory: () => { + throw new Error("descriptor must not launch the transport"); + }, + }); + + await expect(backend.descriptor()).resolves.toMatchObject({ + kind: "runner", + name, + version, + capabilities: { + steering: false, + resume: true, + interruption: true, + dynamicTools: true, + }, + }); + }); + it("constructs the OpenCode backend without starting its process", async () => { const backend = createNativeSessionBackend(opencodeExecution(), { opencodeRuntimeDirectory: "/runtime", diff --git a/packages/paperclip-runner/src/cli/opencode-app-server-proxy.test.ts b/packages/paperclip-runner/src/cli/opencode-app-server-proxy.test.ts index 2a5f028cd4..0c672b356a 100644 --- a/packages/paperclip-runner/src/cli/opencode-app-server-proxy.test.ts +++ b/packages/paperclip-runner/src/cli/opencode-app-server-proxy.test.ts @@ -1,7 +1,127 @@ import { describe, expect, it } from "vitest"; - +import { + chmodSync, + closeSync, + lstatSync, + mkdtempSync, + mkdirSync, + openSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { spawnSync } from "node:child_process"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + TRUSTED_OPENCODE_EXECUTABLE_ARG, + trustedOpenCodeLaunchBinding, + withoutAmbientOpenCodeCommand, +} from "./opencode-proxy-command.js"; import { openCodeProxyTaskEnvelope } from "./opencode-proxy-task-envelope.js"; +describe("OpenCode runnerd proxy executable", () => { + it("uses the runner-owned inherited executable descriptor", () => { + if (process.platform !== "linux") return; + expect(trustedOpenCodeLaunchBinding([ + TRUSTED_OPENCODE_EXECUTABLE_ARG, + "/proc/self/fd/7", + ])).toEqual({ command: "/proc/self/fd/7", commandFd: 7 }); + }); + + it("executes the inherited artifact through the nested child fd mapping", () => { + if (process.platform === "win32") return; + if (process.platform === "darwin") { + const root = mkdtempSync(join(tmpdir(), "paperclip-opencode-nested-")); + const directory = join( + root, + ".paperclip-verified-executable-0123456789abcdef0123456789abcdef", + ); + const command = join(directory, "launch"); + try { + mkdirSync(directory, { mode: 0o700 }); + writeFileSync(command, "#!/bin/sh\nprintf verified-nested-spawn\n"); + chmodSync(command, 0o500); + const binding = trustedOpenCodeLaunchBinding([ + TRUSTED_OPENCODE_EXECUTABLE_ARG, + command, + ]); + binding.commandLifecycle?.beforeSpawn(); + const child = spawnSync(binding.command, [], { encoding: "utf8" }); + expect(child.error).toBeUndefined(); + expect(child.status).toBe(0); + expect(child.stdout).toBe("verified-nested-spawn"); + binding.commandLifecycle?.afterSpawn(); + expect(() => lstatSync(command)).toThrow(); + expect(() => lstatSync(directory)).toThrow(); + binding.commandLifecycle?.beforeSpawn(); + const retriedChild = spawnSync(binding.command, [], { encoding: "utf8" }); + expect(retriedChild.error).toBeUndefined(); + expect(retriedChild.status).toBe(0); + expect(retriedChild.stdout).toBe("verified-nested-spawn"); + binding.commandLifecycle?.afterSpawn(); + expect(() => lstatSync(command)).toThrow(); + expect(() => lstatSync(directory)).toThrow(); + } finally { + rmSync(root, { recursive: true, force: true }); + } + return; + } + const parentFd = openSync(process.execPath, "r"); + try { + const binding = trustedOpenCodeLaunchBinding([ + TRUSTED_OPENCODE_EXECUTABLE_ARG, + `/proc/self/fd/${parentFd}`, + ]); + const stdio: Array<"ignore" | "pipe" | number> = [ + "ignore", + "pipe", + "pipe", + ]; + while (stdio.length <= binding.commandFd!) stdio.push("ignore"); + stdio[binding.commandFd!] = binding.commandFd!; + const child = spawnSync( + binding.command, + ["-e", "process.stdout.write('verified-nested-spawn')"], + { + encoding: "utf8", + stdio, + }, + ); + expect(child.error).toBeUndefined(); + expect(child.status).toBe(0); + expect(child.stdout).toBe("verified-nested-spawn"); + } finally { + closeSync(parentFd); + } + }); + + it("fails closed instead of accepting an ambient command fallback", () => { + expect(() => trustedOpenCodeLaunchBinding([])) + .toThrow("refusing ambient PATH or PAPERCLIP_OPENCODE_COMMAND fallback"); + expect(() => trustedOpenCodeLaunchBinding([ + TRUSTED_OPENCODE_EXECUTABLE_ARG, + "/tmp/unqualified-opencode", + ])).toThrow("runner-owned executable binding is unavailable"); + expect(() => trustedOpenCodeLaunchBinding([ + TRUSTED_OPENCODE_EXECUTABLE_ARG, + "/proc/self/fd/7", + "unexpected", + ])).toThrow("runner-owned executable binding is unavailable"); + }); + + it("removes the ambient override from the launched provider environment", () => { + const original = { + OPENROUTER_API_KEY: "secret", + PAPERCLIP_OPENCODE_COMMAND: "/tmp/unqualified-opencode", + }; + + expect(withoutAmbientOpenCodeCommand(original)).toEqual({ + OPENROUTER_API_KEY: "secret", + }); + expect(original.PAPERCLIP_OPENCODE_COMMAND).toBe("/tmp/unqualified-opencode"); + }); +}); + describe("OpenCode runnerd proxy task envelope", () => { it("uses the durable completion-contract binding instead of a demo revision", () => { expect(openCodeProxyTaskEnvelope({ diff --git a/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts b/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts index 980b2e265e..07102ebe55 100644 --- a/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts +++ b/packages/paperclip-runner/src/cli/opencode-app-server-proxy.ts @@ -1,7 +1,6 @@ #!/usr/bin/env node import { createInterface } from "node:readline"; import { readFileSync } from "node:fs"; -import { createRequire } from "node:module"; import { resolve } from "node:path"; import type { HarnessRuntimeRequestResolution, HarnessSession, PersistedHarnessSession } from "../contracts/harness-driver.js"; @@ -18,6 +17,10 @@ import { openCodeProxyCollaborationModes, } from "./opencode-proxy-collaboration-mode.js"; import { parseOpenCodeProxyPermissionMode } from "./opencode-proxy-permission-mode.js"; +import { + trustedOpenCodeLaunchBinding, + withoutAmbientOpenCodeCommand, +} from "./opencode-proxy-command.js"; type RpcMessage = { id?: string | number; method?: string; params?: unknown; result?: unknown; error?: unknown }; @@ -31,16 +34,7 @@ let cwd = ""; let activeModel = ""; let activeTurnId: string | null = null; const announcedTurnIds = new Set(); - -function openCodeCommand(): string { - const configured = process.env.PAPERCLIP_OPENCODE_COMMAND?.trim(); - if (configured && configured !== "opencode") return configured; - try { - return createRequire(import.meta.url).resolve("opencode-ai/bin/opencode.exe"); - } catch { - return configured || "opencode"; - } -} +const launchBinding = trustedOpenCodeLaunchBinding(process.argv.slice(2)); function send(value: unknown): void { process.stdout.write(`${JSON.stringify(value)}\n`); @@ -82,9 +76,11 @@ async function open(params: Record, resume: boolean): Promise= 3 && commandFd <= 255) { + return { command, commandFd }; + } + const validateMacSnapshot = (expected?: { dev: number; ino: number }) => { + let snapshotIsValid = false; + let metadata: ReturnType | undefined; + try { + metadata = lstatSync(command); + const directoryMetadata = lstatSync(dirname(command)); + const currentUid = process.getuid?.(); + snapshotIsValid = + metadata.isFile() && + !metadata.isSymbolicLink() && + metadata.nlink === 1 && + (metadata.mode & 0o777) === 0o500 && + directoryMetadata.isDirectory() && + !directoryMetadata.isSymbolicLink() && + (directoryMetadata.mode & 0o777) === 0o700 && + currentUid !== undefined && + metadata.uid === currentUid && + directoryMetadata.uid === currentUid && + (expected === undefined || + (metadata.dev === expected.dev && metadata.ino === expected.ino)); + } catch { + snapshotIsValid = false; + } + if (!snapshotIsValid) { + throw new Error( + `OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executable binding is unavailable; refusing ambient PATH or PAPERCLIP_OPENCODE_COMMAND fallback`, + ); + } + return metadata!; + }; + if ( + process.platform === "darwin" && + isAbsolute(command) && + basename(command) === "launch" && + /^\.paperclip-verified-executable-[0-9a-f]{32}$/.test( + basename(dirname(command)), + ) + ) { + const initialMetadata = validateMacSnapshot(); + let sourceFd: number; + try { + sourceFd = openSync(command, "r"); + } catch { + throw new Error( + `OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executable binding is unavailable; refusing ambient PATH or PAPERCLIP_OPENCODE_COMMAND fallback`, + ); + } + const sourceMetadata = fstatSync(sourceFd); + if ( + sourceMetadata.dev !== initialMetadata.dev || + sourceMetadata.ino !== initialMetadata.ino + ) { + closeSync(sourceFd); + throw new Error( + `OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executable binding is unavailable; refusing ambient PATH or PAPERCLIP_OPENCODE_COMMAND fallback`, + ); + } + try { + unlinkSync(command); + rmdirSync(dirname(command)); + } catch (error) { + closeSync(sourceFd); + throw error; + } + let materialized = false; + let materializedIdentity: { dev: number; ino: number } | undefined; + const materializeForSpawn = () => { + if (materialized) { + validateMacSnapshot(materializedIdentity); + return; + } + let destinationFd: number | undefined; + try { + mkdirSync(dirname(command), { mode: 0o700 }); + chmodSync(dirname(command), 0o700); + destinationFd = openSync(command, "wx", 0o700); + const buffer = Buffer.allocUnsafe(64 * 1024); + let sourceOffset = 0; + while (sourceOffset < sourceMetadata.size) { + const count = readSync( + sourceFd, + buffer, + 0, + Math.min(buffer.length, sourceMetadata.size - sourceOffset), + sourceOffset, + ); + if (count === 0) throw new Error("verified OpenCode snapshot ended early"); + let written = 0; + while (written < count) { + const writeCount = writeSync( + destinationFd, + buffer, + written, + count - written, + ); + if (writeCount === 0) { + throw new Error("verified OpenCode snapshot copy made no progress"); + } + written += writeCount; + } + sourceOffset += count; + } + fsyncSync(destinationFd); + chmodSync(command, 0o500); + const copied = fstatSync(destinationFd); + materializedIdentity = { dev: copied.dev, ino: copied.ino }; + closeSync(destinationFd); + destinationFd = undefined; + materialized = true; + validateMacSnapshot(materializedIdentity); + } catch (error) { + if (destinationFd !== undefined) closeSync(destinationFd); + try { + unlinkSync(command); + } catch { + // Best-effort cleanup; the private directory removal below remains + // fail-closed if another entry appeared. + } + try { + rmdirSync(dirname(command)); + } catch { + // Preserve the original materialization failure. + } + throw error; + } + }; + return { + command, + commandLifecycle: { + // Descriptor execution is unavailable on macOS. Same-UID filesystem + // attackers are outside the documented local-host trust boundary in + // docs/durable-recovery.md. Keep the verified source on an unlinked + // descriptor, rematerialize only at the syscall boundary (including + // retries), then remove the executable pathname immediately. + beforeSpawn: materializeForSpawn, + afterSpawn() { + validateMacSnapshot(materializedIdentity); + unlinkSync(command); + rmdirSync(dirname(command)); + materialized = false; + materializedIdentity = undefined; + }, + }, + }; + } + throw new Error( + `OpenCode ${QUALIFIED_OPENCODE_VERSION} runner-owned executable binding is unavailable; refusing ambient PATH or PAPERCLIP_OPENCODE_COMMAND fallback`, + ); +} diff --git a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts index 90995500e1..155e93b4ef 100644 --- a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts +++ b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.test.ts @@ -31,6 +31,115 @@ const identity: DurableRecoveryIdentity = { const expectedRunnerVersion = "0.3.0"; const expectedRunnerDigest = `sha256:${"a".repeat(64)}`; +it("pins the ACPX launch profile in runner startup arguments and restarts", () => { + const launches: RunnerProcessLaunchSpec[] = []; + const handle = spawnRunner({ + connection: { mode: "connect", connectUrl: "ws://127.0.0.1:43127" }, + stateDirectory: "/tmp/paperclip-runner-test", + identity, + ticket: "bootstrap-ticket", + maxOutboxBytes: 256 * 1024, + p0ReserveBytes: 64 * 1024, + runnerVersion: expectedRunnerVersion, + runnerDigest: expectedRunnerDigest, + acpxLaunchProfile: { + authorityDigest: `sha256:${"d".repeat(64)}`, + command: "/provider-pack/node", + commandSha256: `sha256:${"b".repeat(64)}`, + sidecarScript: "/provider-pack/acpx-sidecar.js", + sidecarScriptSha256: `sha256:${"c".repeat(64)}`, + }, + processLauncher: (spec) => { + launches.push(spec); + return { + child: { + pid: 42, + exitCode: null, + signalCode: null, + kill: () => true, + }, + completion: Promise.resolve({ + code: 0, + signal: null, + stdout: "", + stderr: "", + }), + }; + }, + }); + + handle.restart("replacement-ticket"); + expect(launches).toHaveLength(2); + for (const launch of launches) { + expect(launch.args).toContain("--acpx-launch-authority-digest"); + expect(launch.args).toContain("--acpx-sidecar-command"); + expect(launch.args).toContain(`sha256:${"d".repeat(64)}`); + expect(launch.args).toContain("/provider-pack/node"); + expect(launch.args).toContain(`sha256:${"b".repeat(64)}`); + expect(launch.args).toContain("/provider-pack/acpx-sidecar.js"); + expect(launch.args).toContain(`sha256:${"c".repeat(64)}`); + } +}); + +it("pins the OpenCode launch profile in runner startup arguments and restarts", () => { + const launches: RunnerProcessLaunchSpec[] = []; + const profile = { + command: "/provider-pack/node", + commandSha256: `sha256:${"b".repeat(64)}`, + proxyScript: "/provider-pack/opencode-proxy.js", + proxyScriptSha256: `sha256:${"c".repeat(64)}`, + executable: "/provider-pack/opencode.exe", + executableSha256: `sha256:${"d".repeat(64)}`, + }; + const handle = spawnRunner({ + connection: { mode: "connect", connectUrl: "ws://127.0.0.1:43127" }, + stateDirectory: "/tmp/paperclip-runner-test", + identity, + ticket: "bootstrap-ticket", + maxOutboxBytes: 256 * 1024, + p0ReserveBytes: 64 * 1024, + runnerVersion: expectedRunnerVersion, + runnerDigest: expectedRunnerDigest, + opencodeLaunchProfile: profile, + processLauncher: (spec) => { + launches.push(spec); + return { + child: { + pid: 42, + exitCode: null, + signalCode: null, + kill: () => true, + }, + completion: Promise.resolve({ + code: 0, + signal: null, + stdout: "", + stderr: "", + }), + }; + }, + }); + + handle.restart("replacement-ticket"); + expect(launches).toHaveLength(2); + for (const launch of launches) { + expect(launch.args).toEqual(expect.arrayContaining([ + "--opencode-proxy-command", + profile.command, + "--opencode-proxy-command-sha256", + profile.commandSha256, + "--opencode-proxy-script", + profile.proxyScript, + "--opencode-proxy-script-sha256", + profile.proxyScriptSha256, + "--opencode-executable", + profile.executable, + "--opencode-executable-sha256", + profile.executableSha256, + ])); + } +}); + it("preserves an explicit OpenCode permission mode at the runner spawn boundary", () => { const launches: RunnerProcessLaunchSpec[] = []; spawnRunner({ @@ -45,7 +154,6 @@ it("preserves an explicit OpenCode permission mode at the runner spawn boundary" environment: { PATH: "/bin", OPENROUTER_API_KEY: "provider-key", - PAPERCLIP_OPENCODE_COMMAND: "/provider-pack/opencode", PAPERCLIP_OPENCODE_PERMISSION_MODE: "deny", PAPERCLIP_OPENCODE_RUNTIME_DIR: "/runner/opencode", DATABASE_URL: "must-not-reach-runnerd", @@ -75,13 +183,64 @@ it("preserves an explicit OpenCode permission mode at the runner spawn boundary" expect(launches[0]!.environment).toMatchObject({ PATH: "/bin", OPENROUTER_API_KEY: "provider-key", - PAPERCLIP_OPENCODE_COMMAND: "/provider-pack/opencode", PAPERCLIP_OPENCODE_PERMISSION_MODE: "deny", PAPERCLIP_OPENCODE_RUNTIME_DIR: "/runner/opencode", }); expect(launches[0]!.environment.DATABASE_URL).toBeUndefined(); expect(launches[0]!.environment.PAPERCLIP_API_KEY).toBeUndefined(); expect(launches[0]!.environment.NODE_OPTIONS).toBeUndefined(); + expect(launches[0]!.environment.PAPERCLIP_OPENCODE_COMMAND).toBeUndefined(); +}); + +it("preserves file-backed AWS workload identity at the runner spawn boundary", () => { + const launches: RunnerProcessLaunchSpec[] = []; + spawnRunner({ + connection: { mode: "connect", connectUrl: "ws://127.0.0.1:43127" }, + stateDirectory: "/tmp/paperclip-runner-test", + identity, + ticket: "bootstrap-ticket", + maxOutboxBytes: 256 * 1024, + p0ReserveBytes: 64 * 1024, + runnerVersion: expectedRunnerVersion, + runnerDigest: expectedRunnerDigest, + environment: { + AWS_PROFILE: "host-profile", + AWS_CONFIG_FILE: "/host/home/.aws/config", + AWS_SHARED_CREDENTIALS_FILE: "/host/home/.aws/credentials", + AWS_CONTAINER_CREDENTIALS_FULL_URI: "http://127.0.0.1:9001/credentials", + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE: "/identity/container-token", + AWS_ACCESS_KEY_ID: "must-not-reach-runnerd", + AWS_SECRET_ACCESS_KEY: "must-not-reach-runnerd", + }, + processLauncher: (spec) => { + launches.push(spec); + return { + child: { + pid: 42, + exitCode: null, + signalCode: null, + kill: () => true, + }, + completion: Promise.resolve({ + code: 0, + signal: null, + stdout: "", + stderr: "", + }), + }; + }, + }); + + expect(launches).toHaveLength(1); + expect(launches[0]!.environment).toMatchObject({ + AWS_CONTAINER_CREDENTIALS_FULL_URI: "http://127.0.0.1:9001/credentials", + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE: "/identity/container-token", + }); + expect(launches[0]!.environment.AWS_ACCESS_KEY_ID).toBeUndefined(); + expect(launches[0]!.environment.AWS_SECRET_ACCESS_KEY).toBeUndefined(); + expect(launches[0]!.environment.AWS_PROFILE).toBeUndefined(); + expect(launches[0]!.environment.AWS_CONFIG_FILE).toBeUndefined(); + expect(launches[0]!.environment.AWS_SHARED_CREDENTIALS_FILE).toBeUndefined(); }); function domainDigest(domain: string, parts: readonly Buffer[]): Buffer { diff --git a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts index 408ba27940..62e66596fe 100644 --- a/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts +++ b/packages/paperclip-runner/src/control-plane/durable-prp-control-plane.ts @@ -1875,17 +1875,14 @@ const runnerExplicitProviderEnvironmentKeys = [ "OPENAI_API_KEY", "CODEX_API_KEY", "PAPERCLIP_ACPX_CODEX_AUTH_JSON_SECRET", - "AWS_PROFILE", "AWS_REGION", "AWS_DEFAULT_REGION", - "AWS_CONFIG_FILE", - "AWS_SHARED_CREDENTIALS_FILE", "AWS_WEB_IDENTITY_TOKEN_FILE", "AWS_ROLE_ARN", "AWS_ROLE_SESSION_NAME", "AWS_CONTAINER_CREDENTIALS_FULL_URI", "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", - "PAPERCLIP_OPENCODE_COMMAND", + "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", "PAPERCLIP_OPENCODE_PERMISSION_MODE", "PAPERCLIP_OPENCODE_RUNTIME_DIR", "PAPERCLIP_RUNNER_INSTANCE_ID", @@ -1941,6 +1938,21 @@ export function spawnRunner(options: { runnerBinaryPath?: string; runnerVersion: string; runnerDigest: string; + acpxLaunchProfile?: { + authorityDigest: string; + command: string; + commandSha256: string; + sidecarScript: string; + sidecarScriptSha256: string; + }; + opencodeLaunchProfile?: { + command: string; + commandSha256: string; + proxyScript: string; + proxyScriptSha256: string; + executable: string; + executableSha256: string; + }; environment?: NodeJS.ProcessEnv; processLauncher?: (spec: RunnerProcessLaunchSpec) => RunnerProcessHandle; }): RunnerProcessHandle { @@ -1984,6 +1996,36 @@ export function spawnRunner(options: { options.runnerVersion, "--runner-digest", options.runnerDigest, + ...(options.acpxLaunchProfile + ? [ + "--acpx-launch-authority-digest", + options.acpxLaunchProfile.authorityDigest, + "--acpx-sidecar-command", + options.acpxLaunchProfile.command, + "--acpx-sidecar-command-sha256", + options.acpxLaunchProfile.commandSha256, + "--acpx-sidecar-script", + options.acpxLaunchProfile.sidecarScript, + "--acpx-sidecar-script-sha256", + options.acpxLaunchProfile.sidecarScriptSha256, + ] + : []), + ...(options.opencodeLaunchProfile + ? [ + "--opencode-proxy-command", + options.opencodeLaunchProfile.command, + "--opencode-proxy-command-sha256", + options.opencodeLaunchProfile.commandSha256, + "--opencode-proxy-script", + options.opencodeLaunchProfile.proxyScript, + "--opencode-proxy-script-sha256", + options.opencodeLaunchProfile.proxyScriptSha256, + "--opencode-executable", + options.opencodeLaunchProfile.executable, + "--opencode-executable-sha256", + options.opencodeLaunchProfile.executableSha256, + ] + : []), "--fake-harness", fakeHarnessBinary, "--fake-harness-script", diff --git a/packages/paperclip-runner/src/drivers/claude-managed/environment.ts b/packages/paperclip-runner/src/drivers/claude-managed/environment.ts new file mode 100644 index 0000000000..4d0f01cb24 --- /dev/null +++ b/packages/paperclip-runner/src/drivers/claude-managed/environment.ts @@ -0,0 +1,80 @@ +/** + * Builds the complete environment visible to runnerd for Claude Managed + * execution. Remote providers receive governed inline tool definitions in the + * durable descriptor, never a Paperclip MCP URL or capability token. + */ +export function createSanitizedClaudeManagedEnvironment( + environment: NodeJS.ProcessEnv | undefined, +): NodeJS.ProcessEnv { + const source = environment ?? process.env; + const result: NodeJS.ProcessEnv = {}; + for (const key of [ + "PATH", + "HOME", + "LANG", + "LANGUAGE", + "LC_ALL", + "LC_CTYPE", + "TZ", + "TMPDIR", + "TEMP", + "TMP", + "SSL_CERT_FILE", + "SSL_CERT_DIR", + "HTTP_PROXY", + "HTTPS_PROXY", + "NO_PROXY", + "http_proxy", + "https_proxy", + "no_proxy", + "RUST_BACKTRACE", + "ANTHROPIC_API_KEY", + ] as const) { + if (typeof source[key] === "string") result[key] = source[key]; + } + return result; +} + +/** + * AgentCore uses workload identity from a private runner home. Long-lived AWS + * access keys, shared profiles, executable credential configuration, and + * Paperclip capability credentials are excluded. + */ +export function createSanitizedAwsAgentCoreEnvironment( + environment: NodeJS.ProcessEnv | undefined, + isolatedHome: string, +): NodeJS.ProcessEnv { + const source = environment ?? process.env; + const result: NodeJS.ProcessEnv = { HOME: isolatedHome }; + for (const key of [ + "PATH", + "LANG", + "LANGUAGE", + "LC_ALL", + "LC_CTYPE", + "TZ", + "TMPDIR", + "TEMP", + "TMP", + "SSL_CERT_FILE", + "SSL_CERT_DIR", + "HTTP_PROXY", + "HTTPS_PROXY", + "NO_PROXY", + "http_proxy", + "https_proxy", + "no_proxy", + "RUST_BACKTRACE", + "AWS_REGION", + "AWS_DEFAULT_REGION", + "AWS_WEB_IDENTITY_TOKEN_FILE", + "AWS_ROLE_ARN", + "AWS_ROLE_SESSION_NAME", + "AWS_CONTAINER_CREDENTIALS_FULL_URI", + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", + "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", + ] as const) { + if (typeof source[key] === "string") result[key] = source[key]; + } + return result; +} diff --git a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts index b5abaa4fb2..33a80fd8fe 100644 --- a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts +++ b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.test.ts @@ -862,11 +862,16 @@ describe("OpenCodeServerDriver", () => { roots.push(root, workspace); let failSessionCreate = true; const spawns: number[] = []; + const commandLifecycle: string[] = []; const driver = new OpenCodeServerDriver({ model: "openrouter/deepseek/deepseek-v4-flash-0731", runtimeDirectory: root, command: fixture, environment: { PATH: process.env.PATH, OPENROUTER_API_KEY: "fixture-key" }, + commandLifecycle: { + beforeSpawn: () => { commandLifecycle.push("before"); }, + afterSpawn: () => { commandLifecycle.push("after"); }, + }, fetch: async (input, init) => { if (failSessionCreate && String(input).endsWith("/session") && init?.method === "POST") { failSessionCreate = false; @@ -879,6 +884,7 @@ describe("OpenCodeServerDriver", () => { const session = await driver.openSession({ runId: "run-retry", normalizedSessionId: "retry", workingDirectory: workspace }); expect(session.ids().providerSessionId).toBe("ses_fake_1"); expect(spawns).toHaveLength(2); + expect(commandLifecycle).toEqual(["before", "after", "before", "after"]); await session.close({ reason: "test" }); }); diff --git a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts index c23dfc13c1..7c7ff4a89e 100644 --- a/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts +++ b/packages/paperclip-runner/src/drivers/opencode/opencode-server-driver.ts @@ -73,6 +73,13 @@ export interface OpenCodeServerDriverOptions { taskEnvelope?: CodexTaskEnvelope; runnerInstanceId?: string; command?: string; + /** Inherited runner-owned executable descriptor duplicated into the child. */ + commandFd?: number; + /** Runner-owned executable path lifecycle used only by the macOS proxy. */ + commandLifecycle?: { + beforeSpawn(): void; + afterSpawn(): void; + }; runtimeDirectory: string; systemInstructions?: string; runtimeContext?: NativeRuntimeContextSnapshot | null; @@ -1364,12 +1371,26 @@ async function startRuntime(input: { OPENCODE_SERVER_PASSWORD: password, }); const isolateProcessGroup = input.options.isolateProcessGroup ?? true; + const stdio: Array<"ignore" | "pipe" | number> = ["ignore", "ignore", "pipe"]; + if (input.options.commandFd !== undefined) { + while (stdio.length <= input.options.commandFd) stdio.push("ignore"); + stdio[input.options.commandFd] = input.options.commandFd; + } + input.options.commandLifecycle?.beforeSpawn(); const child = spawn(input.options.command ?? "opencode", ["serve", "--hostname", "127.0.0.1", "--port", String(port)], { cwd: input.cwd, env: environment, - stdio: ["ignore", "ignore", "pipe"], + stdio, detached: globalThis.process.platform !== "win32" && isolateProcessGroup, }); + if (child.pid !== undefined) { + try { + input.options.commandLifecycle?.afterSpawn(); + } catch (error) { + child.kill("SIGKILL"); + throw error; + } + } let diagnostics = ""; child.stderr?.on("data", (chunk) => { const raw = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)); diff --git a/packages/paperclip-runner/src/live/live-session.ts b/packages/paperclip-runner/src/live/live-session.ts index 396fae158c..7fbc78b57e 100644 --- a/packages/paperclip-runner/src/live/live-session.ts +++ b/packages/paperclip-runner/src/live/live-session.ts @@ -295,9 +295,11 @@ export interface CapabilityLiveSessionSnapshot { export interface CreateCapabilityLiveSessionInput { seed?: CapabilityFixtureSeed | CapabilityFixtureState; workingDirectory?: string; - provider?: "codex" | "opencode" | "acpx"; + provider?: "codex" | "opencode" | "claude_managed" | "aws_agentcore" | "acpx"; acpxAgent?: QualifiedAcpxAgent; requestedModel?: string; + managedProfile?: CapabilityLiveSessionConfigSnapshot["managedProfile"]; + agentCoreProfile?: CapabilityLiveSessionConfigSnapshot["agentCoreProfile"]; scenario?: CapabilitySemanticScenarioPolicy; capabilities?: string[]; explicitClaims?: string[]; @@ -798,6 +800,18 @@ export class CapabilityLiveSessionService { if (input.provider === "acpx" && input.acpxAgent === "pi") { throw new Error("The Pi ACPX profile is not available"); } + if (input.provider === "claude_managed" && !input.managedProfile) { + throw new Error("Claude Managed live sessions require a qualified managed profile"); + } + if (input.provider === "aws_agentcore" && !input.agentCoreProfile) { + throw new Error("AWS AgentCore live sessions require a qualified AgentCore profile"); + } + if ( + (input.provider === "claude_managed" || input.provider === "aws_agentcore") && + !input.requestedModel?.trim() + ) { + throw new Error("Managed live sessions require an explicit qualified model"); + } const port = new CapabilityMockControlPlaneAdapter(input.seed); const seedState = port.serialize(); await port.start(); @@ -840,14 +854,28 @@ export class CapabilityLiveSessionService { provider: input.provider ?? "codex", driver: input.provider === "opencode" ? "opencode_server" + : input.provider === "claude_managed" + ? "claude_managed_agents_api" + : input.provider === "aws_agentcore" + ? "aws_agentcore_harness_api" : input.provider === "acpx" ? "acpx_runtime" : "codex_app_server", providerVersion: input.provider === "opencode" ? "1.18.17" + : input.provider === "claude_managed" + ? input.managedProfile!.betaVersion + : input.provider === "aws_agentcore" + ? input.agentCoreProfile!.qualificationRevision : input.provider === "acpx" ? acpxProfile!.acpxVersion : null, ...(acpxProfile === null ? {} : { acpxAgent: acpxProfile.agent, acpxProfile: structuredClone(acpxProfile), }), + ...(input.managedProfile === undefined + ? {} + : { managedProfile: structuredClone(input.managedProfile) }), + ...(input.agentCoreProfile === undefined + ? {} + : { agentCoreProfile: structuredClone(input.agentCoreProfile) }), ...(input.requestedModel === undefined ? {} : { requestedModel: requireNonEmpty(input.requestedModel, "requested_model") }), @@ -988,15 +1016,18 @@ export class CapabilityLiveSessionService { // not disposable state. Suspending preserves its provider checkpoint and // authority binding so selecting it from history can restore it safely. await session.suspend("reset archived prior session"); - if (config.provider === "claude_managed" || config.provider === "aws_agentcore") { - throw new Error("managed provider sessions are readable but cannot start a replacement session in this release"); - } const seedPort = CapabilityMockControlPlaneAdapter.restore(config.seedState); return this.create({ seed: seedPort.snapshot(), workingDirectory: config.workingDirectory, provider: config.provider ?? "codex", ...(config.requestedModel === undefined ? {} : { requestedModel: config.requestedModel }), + ...(config.managedProfile === undefined + ? {} + : { managedProfile: config.managedProfile }), + ...(config.agentCoreProfile === undefined + ? {} + : { agentCoreProfile: config.agentCoreProfile }), scenario: config.scenario, capabilities: config.capabilities, explicitClaims: config.explicitClaims, @@ -1787,9 +1818,6 @@ export class CapabilityLiveSession { async #connect(resume: boolean): Promise { const provider = this.#config.provider ?? this.#transportOptions.provider ?? "codex"; - if (provider === "claude_managed" || provider === "aws_agentcore") { - throw new Error("managed provider sessions are readable but execution is deferred in this release"); - } this.#status = resume ? "restoring" : "starting"; const identityDigest = createHash("sha256").update(this.id).digest("hex").slice(0, 20); const providerRunBinding = this.#providerRunBinding ?? { @@ -1804,6 +1832,24 @@ export class CapabilityLiveSession { ...(provider === "acpx" && this.#config.acpxAgent ? { acpxAgent: this.#config.acpxAgent, } : {}), + ...(provider === "claude_managed" && this.#config.managedProfile + ? { + managedProfile: { + ...this.#config.managedProfile, + model: this.#config.requestedModel ?? "claude-sonnet-5", + }, + } + : {}), + ...(provider === "aws_agentcore" && this.#config.agentCoreProfile + ? { + agentCoreProfile: { + ...this.#config.agentCoreProfile, + model: + this.#config.requestedModel ?? + "global.anthropic.claude-sonnet-4-6", + }, + } + : {}), lifecyclePolicy: this.#config.lifecyclePolicy ?? { mode: "per_turn", idleTimeoutMs: null }, resumeActiveTurnId: resume ? this.#activeTurnId : null, stateDirectory: resolve(this.#config.workingDirectory, ".paperclip-runner-prp", identityDigest), diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts index e30bdd3190..2e4dd027ed 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.test.ts @@ -27,6 +27,7 @@ import { rehydrateRunnerdTurnNotification, rehydrateRunnerdUsageNotification, rehydrateRunnerdWorkspaceChangeNotification, + runnerdLaunchProfileInternals, resolveRunnerdAcpxPermissionMode, resolveRunnerdSessionIdentity, resolveSourceCodexHome, @@ -41,6 +42,48 @@ it("defaults runnerd ACPX permissions to approve reads", () => { expect(resolveRunnerdAcpxPermissionMode("deny-all")).toBe("deny-all"); }); +it("rejects caller-selected local ACPX artifacts even when they are self-hashed", async () => { + const directory = await mkdtemp(join(tmpdir(), "paperclip-acpx-authority-")); + const command = join(directory, "node"); + const sidecar = join(directory, "sidecar.js"); + await writeFile(command, "caller-selected command", { mode: 0o700 }); + await writeFile(sidecar, "caller-selected sidecar", { mode: 0o600 }); + const digest = (value: string) => + `sha256:${createHash("sha256").update(value).digest("hex")}`; + try { + expect(() => + runnerdLaunchProfileInternals.acpxRunnerLaunchProfile( + { + providerNodeCommand: command, + providerNodeCommandSha256: digest("caller-selected command"), + acpxSidecarPath: sidecar, + acpxSidecarSha256: digest("caller-selected sidecar"), + }, + command, + sidecar, + ), + ).toThrow("ACPX local launch must use build-owned artifacts"); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); + +it("requires a provider-pack authority for remote ACPX artifact hashes", () => { + expect(() => + runnerdLaunchProfileInternals.acpxRunnerLaunchProfile( + { + runnerFilesystemRoot: "/runner", + providerNodeCommand: "/provider-pack/node", + providerNodeCommandSha256: `sha256:${"a".repeat(64)}`, + acpxSidecarPath: "/provider-pack/acpx-sidecar.js", + acpxSidecarSha256: `sha256:${"b".repeat(64)}`, + }, + "/provider-pack/node", + "/provider-pack/acpx-sidecar.js", + ), + ).toThrow("omitted its provider-pack authority"); +}); + it("adds Codex-style turn updates only when collaboration instructions are enabled", () => { const base = "Base Paperclip instructions."; const enabled = withCodexCollaborationRuntimeInstructions(base, true); @@ -94,7 +137,6 @@ it("preserves OpenCode runtime bindings when a durable runner is respawned", () hasRuntimeContext: true, }); expect(environment).toMatchObject({ - PAPERCLIP_OPENCODE_COMMAND: "/provider-pack/opencode", PAPERCLIP_OPENCODE_PERMISSION_MODE: "deny", PAPERCLIP_OPENCODE_RUNTIME_DIR: "/isolated/session/opencode", PAPERCLIP_RUNNER_INSTANCE_ID: "runner-1", @@ -108,6 +150,7 @@ it("preserves OpenCode runtime bindings when a durable runner is respawned", () expect(environment.DATABASE_URL).toBeUndefined(); expect(environment.PAPERCLIP_API_KEY).toBeUndefined(); expect(environment.NODE_OPTIONS).toBeUndefined(); + expect(environment.PAPERCLIP_OPENCODE_COMMAND).toBeUndefined(); const defaultPermissionEnvironment = createCapabilityRunnerdProviderEnvironment({ @@ -168,6 +211,98 @@ it("passes the configured Codex API key only through the provider process enviro expect(environment.PAPERCLIP_API_KEY).toBeUndefined(); }); +it("passes only the Anthropic credential to Claude Managed runnerd", () => { + const environment = createCapabilityRunnerdProviderEnvironment({ + provider: "claude_managed", + options: { + provider: "claude_managed", + environment: { + PATH: "/bin", + ANTHROPIC_API_KEY: "anthropic-canary", + PAPERCLIP_NATIVE_MCP_NAME: "paperclip", + PAPERCLIP_NATIVE_MCP_URL: "https://paperclip.example/mcp", + PAPERCLIP_NATIVE_MCP_TOKEN: "must-not-reach-provider", + PAPERCLIP_API_KEY: "must-not-reach-provider", + DATABASE_URL: "must-not-reach-provider", + }, + }, + identity: { + runnerInstanceId: "runner-1", + environmentLeaseId: "lease-1", + runId: "run-1", + normalizedSessionId: "session-1", + turnId: "turn-1", + itemId: "item-1", + }, + codexHome: "/isolated/codex-home", + runtimeContextPath: "/isolated/runtime-context.json", + hasRuntimeContext: true, + }); + expect(environment).toMatchObject({ + PATH: "/bin", + ANTHROPIC_API_KEY: "anthropic-canary", + PAPERCLIP_RUNNER_INSTANCE_ID: "runner-1", + PAPERCLIP_RUN_ID: "run-1", + PAPERCLIP_NORMALIZED_SESSION_ID: "session-1", + }); + expect(environment.PAPERCLIP_NATIVE_MCP_NAME).toBeUndefined(); + expect(environment.PAPERCLIP_NATIVE_MCP_URL).toBeUndefined(); + expect(environment.PAPERCLIP_NATIVE_MCP_TOKEN).toBeUndefined(); + expect(environment.PAPERCLIP_API_KEY).toBeUndefined(); + expect(environment.DATABASE_URL).toBeUndefined(); +}); + +it("uses file-backed AWS workload identity without forwarding access keys or Paperclip tokens", () => { + const environment = createCapabilityRunnerdProviderEnvironment({ + provider: "aws_agentcore", + options: { + provider: "aws_agentcore", + environment: { + PATH: "/bin", + HOME: "/host/home", + AWS_PROFILE: "host-profile", + AWS_CONFIG_FILE: "/host/home/.aws/config", + AWS_SHARED_CREDENTIALS_FILE: "/host/home/.aws/credentials", + AWS_REGION: "us-east-1", + AWS_ROLE_ARN: "arn:aws:iam::123456789012:role/runner", + AWS_WEB_IDENTITY_TOKEN_FILE: "/identity/token", + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE: "/identity/container-token", + AWS_ACCESS_KEY_ID: "must-not-reach-provider", + AWS_SECRET_ACCESS_KEY: "must-not-reach-provider", + AWS_SESSION_TOKEN: "must-not-reach-provider", + PAPERCLIP_NATIVE_MCP_URL: "https://paperclip.example/mcp", + PAPERCLIP_NATIVE_MCP_TOKEN: "must-not-reach-provider", + }, + }, + identity: { + runnerInstanceId: "runner-1", + environmentLeaseId: "lease-1", + runId: "run-1", + normalizedSessionId: "session-1", + turnId: "turn-1", + itemId: "item-1", + }, + codexHome: "/isolated/codex-home", + runtimeContextPath: "/isolated/runtime-context.json", + hasRuntimeContext: false, + }); + expect(environment).toMatchObject({ + HOME: "/isolated/codex-home", + AWS_REGION: "us-east-1", + AWS_ROLE_ARN: "arn:aws:iam::123456789012:role/runner", + AWS_WEB_IDENTITY_TOKEN_FILE: "/identity/token", + AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE: "/identity/container-token", + }); + expect(environment.AWS_ACCESS_KEY_ID).toBeUndefined(); + expect(environment.AWS_SECRET_ACCESS_KEY).toBeUndefined(); + expect(environment.AWS_SESSION_TOKEN).toBeUndefined(); + expect(environment.AWS_PROFILE).toBeUndefined(); + expect(environment.AWS_CONFIG_FILE).toBeUndefined(); + expect(environment.AWS_SHARED_CREDENTIALS_FILE).toBeUndefined(); + expect(environment.PAPERCLIP_NATIVE_MCP_URL).toBeUndefined(); + expect(environment.PAPERCLIP_NATIVE_MCP_TOKEN).toBeUndefined(); +}); + it.each([ { agent: "pi" as const, diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index a15f73ca83..e0eb0242ce 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -41,6 +41,10 @@ import { type QualifiedAcpxAgent, } from "../drivers/acpx/qualified-profiles.js"; import { createSanitizedAcpxSpawnInput } from "../drivers/acpx/environment.js"; +import { + createSanitizedAwsAgentCoreEnvironment, + createSanitizedClaudeManagedEnvironment, +} from "../drivers/claude-managed/environment.js"; import type { NativeRuntimeContextSnapshot } from "../contracts/runtime-context.js"; import type { NativeAcpxPermissionMode, @@ -237,22 +241,64 @@ export interface CapabilityRunnerdProcessEvidence { } export interface CapabilityRunnerdCodexTransportOptions { - provider?: "codex" | "opencode" | "acpx"; + provider?: "codex" | "opencode" | "claude_managed" | "aws_agentcore" | "acpx"; opencodePermissionMode?: NativeOpenCodePermissionMode; acpxAgent?: QualifiedAcpxAgent; acpxPermissionMode?: NativeAcpxPermissionMode; acpxPermissionModePinned?: boolean; acpxSidecarPath?: string; + /** SHA-256 verified by the provider-pack authority before runner startup. */ + acpxSidecarSha256?: string; /** Node executable in the runner filesystem; required for remote JS providers. */ providerNodeCommand?: string; + /** SHA-256 verified by the provider-pack authority before runner startup. */ + providerNodeCommandSha256?: string; + /** Digest of the build-owned provider-pack manifest that authorized remote artifacts. */ + providerPackAuthorityDigest?: string; acpxRuntimeDirectory?: string; + managedProfile?: { + profileId: string; + anthropicAgentId: string; + agentVersion: string; + environmentId: string; + betaVersion: "managed-agents-2026-04-01"; + maxSessionListCostUsd: number; + model: string; + }; + agentCoreProfile?: { + profileId: string; + region: string; + accountId: string; + harnessArn: string; + harnessVersion: string; + endpointArn: string; + endpointQualifier: string; + agentRuntimeArn: string; + memoryArn: string; + memoryId: string; + invocationRoleArn: string; + contextBucket: string; + contextPrefix: string; + contextKmsKeyArn: string; + qualificationRevision: string; + eventExpiryDays: 90; + maxEstimatedSessionCostUsd: number; + maxIterations: number; + maxOutputTokens: number; + timeoutSeconds: number; + model: string; + }; runnerBinary?: string; codexCommand?: string; codexArgs?: string[]; /** Controller-visible Codex home used only to seed the isolated runner home. */ sourceCodexHome?: string | null; opencodeCommand?: string; + /** SHA-256 verified by the provider-pack authority before runner startup. */ + opencodeCommandSha256?: string; opencodeProxyPath?: string; + /** SHA-256 verified by the provider-pack authority before runner startup. */ + opencodeProxySha256?: string; opencodeRuntimeDirectory?: string; environment?: NodeJS.ProcessEnv; closeGraceMs?: number; @@ -768,6 +814,112 @@ function approvedRunnerArtifact( }; } +function acpxRunnerLaunchProfile( + options: CapabilityRunnerdCodexTransportOptions, + command: string, + sidecarScript: string, +): { + authorityDigest: string; + command: string; + commandSha256: string; + sidecarScript: string; + sidecarScriptSha256: string; +} { + const localDigest = (path: string) => + `sha256:${createHash("sha256").update(readFileSync(path)).digest("hex")}`; + if (!options.runnerFilesystemRoot) { + const buildCommand = process.execPath; + const buildSidecar = fileURLToPath( + new URL("../cli/acpx-runtime-sidecar.js", import.meta.url), + ); + if ( + options.providerNodeCommand !== undefined || + options.providerNodeCommandSha256 !== undefined || + options.acpxSidecarPath !== undefined || + options.acpxSidecarSha256 !== undefined || + options.providerPackAuthorityDigest !== undefined || + command !== buildCommand || + sidecarScript !== buildSidecar + ) { + throw new Error( + "runner_local_provider_artifact_incompatible: ACPX local launch must use build-owned artifacts", + ); + } + const commandSha256 = localDigest(buildCommand); + const sidecarScriptSha256 = localDigest(buildSidecar); + return { + authorityDigest: commandDigest({ + schema: "paperclip.runner.local-acpx-authority.v1", + commandSha256, + sidecarScriptSha256, + }), + command: buildCommand, + commandSha256, + sidecarScript: buildSidecar, + sidecarScriptSha256, + }; + } + const commandSha256 = options.providerNodeCommandSha256; + const sidecarScriptSha256 = options.acpxSidecarSha256; + const authorityDigest = options.providerPackAuthorityDigest; + if (!commandSha256 || !sidecarScriptSha256 || !authorityDigest) { + throw new Error( + "runner_remote_provider_artifact_incompatible: ACPX launch profile omitted its provider-pack authority or verified artifact digests", + ); + } + return { + authorityDigest, + command, + commandSha256, + sidecarScript, + sidecarScriptSha256, + }; +} + +function opencodeRunnerLaunchProfile( + options: CapabilityRunnerdCodexTransportOptions, + command: string, + proxyScript: string, + executable: string, +): { + command: string; + commandSha256: string; + proxyScript: string; + proxyScriptSha256: string; + executable: string; + executableSha256: string; +} { + const localDigest = (path: string) => + `sha256:${createHash("sha256").update(readFileSync(path)).digest("hex")}`; + const usesLocalBuildOwnedDefaults = + !options.runnerFilesystemRoot && + options.providerNodeCommand === undefined && + options.opencodeProxyPath === undefined && + options.opencodeCommand === undefined; + const commandSha256 = + options.providerNodeCommandSha256 ?? + (usesLocalBuildOwnedDefaults ? localDigest(command) : null); + const proxyScriptSha256 = + options.opencodeProxySha256 ?? + (usesLocalBuildOwnedDefaults ? localDigest(proxyScript) : null); + const executableSha256 = + options.opencodeCommandSha256 ?? + (usesLocalBuildOwnedDefaults ? localDigest(executable) : null); + if (!commandSha256 || !proxyScriptSha256 || !executableSha256) { + throw new Error( + "runner_remote_provider_artifact_incompatible: OpenCode launch profile omitted verified artifact digests", + ); + } + return { + command, + commandSha256, + proxyScript, + proxyScriptSha256, + executable, + executableSha256, + }; +} + function authorizedToolSet( tools: readonly Readonly>[], ): Record { @@ -833,7 +985,6 @@ export function createCapabilityRunnerdProviderEnvironment(input: { if (input.provider === "opencode") { return { ...createSanitizedOpenCodeRunnerEnvironment(input.options.environment), - PAPERCLIP_OPENCODE_COMMAND: input.options.opencodeCommand ?? "opencode", PAPERCLIP_OPENCODE_PERMISSION_MODE: input.options.opencodePermissionMode ?? "ask", PAPERCLIP_OPENCODE_RUNTIME_DIR: @@ -858,6 +1009,21 @@ export function createCapabilityRunnerdProviderEnvironment(input: { : {}), }; } + if (input.provider === "claude_managed") { + return { + ...createSanitizedClaudeManagedEnvironment(input.options.environment), + ...commonIdentity, + }; + } + if (input.provider === "aws_agentcore") { + return { + ...createSanitizedAwsAgentCoreEnvironment( + input.options.environment, + input.codexHome, + ), + ...commonIdentity, + }; + } const environment = createSanitizedCodexEnvironment({ ...input.options.environment, HOME: input.codexHome, @@ -1061,7 +1227,14 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { ).env : options.provider === "opencode" ? createSanitizedOpenCodeRunnerEnvironment(options.environment) - : createSanitizedCodexEnvironment(options.environment), + : options.provider === "claude_managed" + ? createSanitizedClaudeManagedEnvironment(options.environment) + : options.provider === "aws_agentcore" + ? createSanitizedAwsAgentCoreEnvironment( + options.environment, + resolve(this.#root, "codex-home"), + ) + : createSanitizedCodexEnvironment(options.environment), ).sort(), diagnostics: ["lab transport selected authenticated durable PRP"], }; @@ -1443,6 +1616,9 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { const codexHome = this.options.runnerFilesystemRoot ? resolve(this.options.runnerFilesystemRoot, "codex-home") : localCodexHome; + if (provider === "aws_agentcore") { + mkdirSync(codexHome, { recursive: true, mode: 0o700 }); + } if (provider === "codex") { await prepareIsolatedCodexHome({ context: sourceRuntimeContext, @@ -1466,6 +1642,28 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { fileURLToPath(new URL("../cli/acpx-runtime-sidecar.js", import.meta.url)); const providerNodeCommand = this.options.providerNodeCommand ?? process.execPath; + const opencodeExecutable = + provider === "opencode" + ? (this.options.opencodeCommand ?? + resolve(packageRoot, "node_modules/opencode-ai/bin/opencode.exe")) + : null; + const runnerAcpxLaunchProfile = + provider === "acpx" + ? acpxRunnerLaunchProfile( + this.options, + providerNodeCommand, + acpxSidecarPath, + ) + : undefined; + const runnerOpenCodeLaunchProfile = + provider === "opencode" + ? opencodeRunnerLaunchProfile( + this.options, + providerNodeCommand, + opencodeProxyPath, + opencodeExecutable!, + ) + : undefined; if ( this.options.runnerFilesystemRoot && (provider === "opencode" || provider === "acpx") @@ -1474,7 +1672,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { ["provider Node", providerNodeCommand], ["OpenCode proxy", opencodeProxyPath], ["ACPX sidecar", acpxSidecarPath], - ["OpenCode executable", this.options.opencodeCommand ?? "opencode"], + ["OpenCode executable", opencodeExecutable ?? "opencode"], ] as const; for (const [label, candidate] of providerPaths) { if ( @@ -1523,6 +1721,24 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { provider === "acpx" ? resolveQualifiedAcpxProfile(acpxAgent!, requestedModel) : null; + const managedProfile = this.options.managedProfile; + const agentCoreProfile = this.options.agentCoreProfile; + if (provider === "claude_managed") { + if (!managedProfile) { + throw new Error("Claude Managed runner transport requires a qualified managed profile"); + } + if (requestedModel !== managedProfile.model) { + throw new Error("Claude Managed requested model does not match its qualified profile"); + } + } + if (provider === "aws_agentcore") { + if (!agentCoreProfile) { + throw new Error("AWS AgentCore runner transport requires a qualified AgentCore profile"); + } + if (requestedModel !== agentCoreProfile.model) { + throw new Error("AWS AgentCore requested model does not match its qualified profile"); + } + } const completionContract = record(params.completionContract); core.queueCommand("run.prepare", { authorizedTools: this.#authorizedTools, @@ -1560,6 +1776,49 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { permissionModePinned: this.options.acpxPermissionModePinned ?? true, runtimeContext, } + : provider === "claude_managed" + ? { + kind: "claude_managed", + model: managedProfile!.model, + profileId: managedProfile!.profileId, + anthropicAgentId: managedProfile!.anthropicAgentId, + agentVersion: managedProfile!.agentVersion, + environmentId: managedProfile!.environmentId, + betaVersion: managedProfile!.betaVersion, + maxSessionListCostUsd: + managedProfile!.maxSessionListCostUsd, + instructions: baseInstructions, + runtimeContext, + } + : provider === "aws_agentcore" + ? { + kind: "aws_agentcore", + model: agentCoreProfile!.model, + profileId: agentCoreProfile!.profileId, + region: agentCoreProfile!.region, + accountId: agentCoreProfile!.accountId, + harnessArn: agentCoreProfile!.harnessArn, + harnessVersion: agentCoreProfile!.harnessVersion, + endpointArn: agentCoreProfile!.endpointArn, + endpointQualifier: agentCoreProfile!.endpointQualifier, + agentRuntimeArn: agentCoreProfile!.agentRuntimeArn, + memoryArn: agentCoreProfile!.memoryArn, + memoryId: agentCoreProfile!.memoryId, + invocationRoleArn: agentCoreProfile!.invocationRoleArn, + contextBucket: agentCoreProfile!.contextBucket, + contextPrefix: agentCoreProfile!.contextPrefix, + contextKmsKeyArn: agentCoreProfile!.contextKmsKeyArn, + qualificationRevision: + agentCoreProfile!.qualificationRevision, + eventExpiryDays: agentCoreProfile!.eventExpiryDays, + maxEstimatedSessionCostUsd: + agentCoreProfile!.maxEstimatedSessionCostUsd, + maxIterations: agentCoreProfile!.maxIterations, + maxOutputTokens: agentCoreProfile!.maxOutputTokens, + timeoutSeconds: agentCoreProfile!.timeoutSeconds, + instructions: baseInstructions, + runtimeContext, + } : { kind: provider, provider, @@ -1635,6 +1894,8 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { runnerBinaryPath, runnerVersion: runnerArtifact.version, runnerDigest: runnerArtifact.digest, + acpxLaunchProfile: runnerAcpxLaunchProfile, + opencodeLaunchProfile: runnerOpenCodeLaunchProfile, environment: withRunnerdProviderTrace( createCapabilityRunnerdProviderEnvironment({ provider, @@ -1683,6 +1944,10 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { modelProvider: provider === "opencode" && typeof params.model === "string" ? params.model.split("/", 1)[0] + : provider === "claude_managed" + ? "anthropic" + : provider === "aws_agentcore" + ? "aws" : provider === "acpx" ? acpxAgent === "pi" ? "openrouter" @@ -1740,6 +2005,9 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { const codexHome = this.options.runnerFilesystemRoot ? resolve(this.options.runnerFilesystemRoot, "codex-home") : localCodexHome; + if (provider === "aws_agentcore") { + mkdirSync(codexHome, { recursive: true, mode: 0o700 }); + } if (provider === "codex") { // The prior process consumed a sealed, immutable copy. Rebuild that // copy from the authoritative runtime snapshot before a new provider is @@ -1759,6 +2027,38 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { nativeMcp: nativeMcpLaunchBinding(this.options.environment), }); } + const opencodeProxyPath = + this.options.opencodeProxyPath ?? + fileURLToPath( + new URL("../cli/opencode-app-server-proxy.js", import.meta.url), + ); + const acpxSidecarPath = + this.options.acpxSidecarPath ?? + fileURLToPath(new URL("../cli/acpx-runtime-sidecar.js", import.meta.url)); + const providerNodeCommand = + this.options.providerNodeCommand ?? process.execPath; + const opencodeExecutable = + provider === "opencode" + ? (this.options.opencodeCommand ?? + resolve(packageRoot, "node_modules/opencode-ai/bin/opencode.exe")) + : null; + const runnerAcpxLaunchProfile = + provider === "acpx" + ? acpxRunnerLaunchProfile( + this.options, + providerNodeCommand, + acpxSidecarPath, + ) + : undefined; + const runnerOpenCodeLaunchProfile = + provider === "opencode" + ? opencodeRunnerLaunchProfile( + this.options, + providerNodeCommand, + opencodeProxyPath, + opencodeExecutable!, + ) + : undefined; const core = new DurablePrpControlPlane({ stateDirectory: controlPlaneDirectory, identity, @@ -1814,6 +2114,8 @@ class DurablePrpCodexTransport implements CodexAppServerTransport { runnerBinaryPath, runnerVersion: runnerArtifact.version, runnerDigest: runnerArtifact.digest, + acpxLaunchProfile: runnerAcpxLaunchProfile, + opencodeLaunchProfile: runnerOpenCodeLaunchProfile, environment: withRunnerdProviderTrace( createCapabilityRunnerdProviderEnvironment({ provider, @@ -2490,3 +2792,7 @@ export function createCapabilityRunnerdCodexTransport( export const createRunnerdCodexTransport = createCapabilityRunnerdCodexTransport; + +export const runnerdLaunchProfileInternals = Object.freeze({ + acpxRunnerLaunchProfile, +}); diff --git a/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs b/packages/paperclip-runner/test/fixtures/fake-opencode-server.mjs old mode 100644 new mode 100755 diff --git a/server/src/__tests__/adapter-registry.test.ts b/server/src/__tests__/adapter-registry.test.ts index 5d1ece56f6..c9e61f2e1f 100644 --- a/server/src/__tests__/adapter-registry.test.ts +++ b/server/src/__tests__/adapter-registry.test.ts @@ -231,7 +231,7 @@ describe("server adapter registry", () => { expect(adapter!.supportsLocalAgentJwt).toBe(true); }); - it("rejects an unsupported persisted runner provider before probing Codex", async () => { + it("rejects an incomplete managed runner provider before probing Codex", async () => { const adapter = requireServerAdapter("paperclip_runner"); expect(adapter.supportsInstructionsBundle).toBe(true); expect(adapter.instructionsPathKey).toBe("instructionsFilePath"); @@ -245,12 +245,35 @@ describe("server adapter registry", () => { adapterType: "paperclip_runner", status: "fail", checks: [{ - code: "paperclip_runner_provider_unsupported", + code: "paperclip_runner_claude_managed_profile_required", level: "error", }], }); }); + it.each([ + ["claude_managed", { + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, "claude_managed_profile_selected"], + ["aws_agentcore", { + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + }, "aws_agentcore_profile_selected"], + ] as const)("accepts a complete %s profile selection", async (provider, config, code) => { + const result = await requireServerAdapter("paperclip_runner").testEnvironment({ + companyId: "company-1", + adapterType: "paperclip_runner", + config: { provider, ...config }, + }); + + expect(result).toMatchObject({ + adapterType: "paperclip_runner", + status: "warn", + checks: expect.arrayContaining([expect.objectContaining({ code, level: "info" })]), + }); + }); + it.each([ ["claude", "claude-sonnet-5"], ["codex", "gpt-5.6-sol"], diff --git a/server/src/__tests__/adapter-routes.test.ts b/server/src/__tests__/adapter-routes.test.ts index fb6da3f455..d706891f9a 100644 --- a/server/src/__tests__/adapter-routes.test.ts +++ b/server/src/__tests__/adapter-routes.test.ts @@ -342,11 +342,13 @@ describe("adapter routes", () => { expect(res.body.fields).toEqual(expect.arrayContaining([ expect.objectContaining({ key: "provider", - options: [ + options: expect.arrayContaining([ expect.objectContaining({ value: "codex" }), expect.objectContaining({ value: "opencode" }), + expect.objectContaining({ value: "claude_managed" }), + expect.objectContaining({ value: "aws_agentcore" }), expect.objectContaining({ value: "acpx" }), - ], + ]), }), expect.objectContaining({ key: "codexPermissionMode", diff --git a/server/src/__tests__/agent-adapter-validation-routes.test.ts b/server/src/__tests__/agent-adapter-validation-routes.test.ts index a12f9a9bb4..a5aa160779 100644 --- a/server/src/__tests__/agent-adapter-validation-routes.test.ts +++ b/server/src/__tests__/agent-adapter-validation-routes.test.ts @@ -8,6 +8,8 @@ import type { ServerAdapterModule } from "../adapters/index.js"; const mockAgentService = vi.hoisted(() => ({ create: vi.fn(), getById: vi.fn(), + getConfigRevision: vi.fn(), + rollbackConfigRevision: vi.fn(), update: vi.fn(), })); @@ -67,6 +69,14 @@ const mockInstanceSettingsService = vi.hoisted(() => ({ getExperimental: vi.fn(async () => ({ enableNativeRunner: false })), })); +const mockManagedAgentProfileService = vi.hoisted(() => ({ + requireQualified: vi.fn(), +})); + +const mockRemoteAgentProfileService = vi.hoisted(() => ({ + requireQualified: vi.fn(), +})); + const mockLogActivity = vi.hoisted(() => vi.fn()); vi.mock("../services/index.js", () => ({ @@ -94,6 +104,14 @@ vi.mock("../services/secrets.js", () => ({ secretService: () => mockSecretService, })); +vi.mock("../services/managed-agent-profiles.js", () => ({ + managedAgentProfileService: () => mockManagedAgentProfileService, +})); + +vi.mock("../services/remote-agent-profiles.js", () => ({ + remoteAgentProfileService: () => mockRemoteAgentProfileService, +})); + function registerModuleMocks() { vi.doMock("../services/index.js", () => ({ agentService: () => mockAgentService, @@ -120,6 +138,14 @@ function registerModuleMocks() { secretService: () => mockSecretService, })); + vi.doMock("../services/managed-agent-profiles.js", () => ({ + managedAgentProfileService: () => mockManagedAgentProfileService, + })); + + vi.doMock("../services/remote-agent-profiles.js", () => ({ + remoteAgentProfileService: () => mockRemoteAgentProfileService, + })); + // The adapter registry reads the disabled set from this store. Mock it so a // test can declare an adapter disabled without writing to the real // ~/.paperclip/adapter-settings.json. @@ -236,6 +262,16 @@ describe("agent routes adapter validation", () => { mockLogActivity.mockResolvedValue(undefined); mockSecretService.syncEnvBindingsForTarget.mockResolvedValue(undefined); mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableNativeRunner: false }); + mockManagedAgentProfileService.requireQualified.mockResolvedValue({ + id: "managed-primary", + companyId: "company-1", + enabled: true, + }); + mockRemoteAgentProfileService.requireQualified.mockResolvedValue({ + id: "agentcore-primary", + companyId: "company-1", + enabled: true, + }); mockAgentInstructionsService.materializeManagedBundle.mockImplementation(async (agent: { adapterConfig: unknown }) => ({ adapterConfig: agent.adapterConfig, })); @@ -287,6 +323,7 @@ describe("agent routes adapter validation", () => { createdAt: new Date(), updatedAt: new Date(), }); + mockAgentService.getConfigRevision.mockResolvedValue(null); mockAgentService.update.mockImplementation(async (_id: string, patch: Record) => ({ ...(await mockAgentService.getById()), ...patch, @@ -603,7 +640,7 @@ describe("agent routes adapter validation", () => { ); }); - it("accepts qualified OpenCode and ACPX providers on fresh runner agents and hires", async () => { + it("accepts qualified local and managed providers on fresh runner agents and hires", async () => { mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableNativeRunner: true }); const app = await createApp(); const createResponse = await requestApp(app, (baseUrl) => @@ -631,12 +668,134 @@ describe("agent routes adapter validation", () => { }, }), ); + const managedResponse = await requestApp(app, (baseUrl) => + request(baseUrl) + .post("/api/companies/company-1/agents") + .send({ + name: "Native Claude Managed", + adapterType: "paperclip_runner", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + }), + ); + const agentCoreResponse = await requestApp(app, (baseUrl) => + request(baseUrl) + .post("/api/companies/company-1/agent-hires") + .send({ + name: "Native AgentCore", + adapterType: "paperclip_runner", + adapterConfig: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + }, + }), + ); expect(createResponse.status, JSON.stringify(createResponse.body)).toBe(201); expect(hireResponse.status, JSON.stringify(hireResponse.body)).toBe(201); - expect(mockAgentService.create).toHaveBeenCalledTimes(2); + expect(managedResponse.status, JSON.stringify(managedResponse.body)).toBe(201); + expect(agentCoreResponse.status, JSON.stringify(agentCoreResponse.body)).toBe(201); + expect(mockAgentService.create).toHaveBeenCalledTimes(4); + expect(mockManagedAgentProfileService.requireQualified).toHaveBeenCalledWith( + "company-1", + "managed-primary", + ); + expect(mockRemoteAgentProfileService.requireQualified).toHaveBeenCalledWith( + "company-1", + "agentcore-primary", + "aws_bedrock_agentcore_harness", + ); }); + it.each([ + [ + "nonexistent Claude profile", + "managed" as const, + { + provider: "claude_managed", + managedProfileId: "managed-missing", + managedAgentsRetentionAcknowledged: true, + }, + "not_found" as const, + 404, + ], + [ + "disabled Claude profile", + "managed" as const, + { + provider: "claude_managed", + managedProfileId: "managed-disabled", + managedAgentsRetentionAcknowledged: true, + }, + "disabled" as const, + 409, + ], + [ + "drifted AgentCore profile", + "remote" as const, + { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-drifted", + agentCoreRetentionAcknowledged: true, + }, + "drifted" as const, + 409, + ], + [ + "cross-company AgentCore profile", + "remote" as const, + { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-other-company", + agentCoreRetentionAcknowledged: true, + }, + "not_found" as const, + 404, + ], + ])( + "rejects a managed-provider selection with a %s", + async (_label, service, adapterConfig, failure, expectedStatus) => { + mockInstanceSettingsService.getExperimental.mockResolvedValue({ enableNativeRunner: true }); + const app = await createApp(); + const { conflict, notFound } = await import("../errors.js"); + const error = failure === "not_found" + ? notFound("Managed provider profile not found") + : conflict( + failure === "disabled" + ? "Managed provider profile is not enabled and qualified" + : "Managed provider profile configuration does not match its qualified revision", + ); + const profileService = service === "managed" + ? mockManagedAgentProfileService + : mockRemoteAgentProfileService; + profileService.requireQualified.mockRejectedValueOnce(error); + + const response = await requestApp(app, (baseUrl) => + request(baseUrl) + .post("/api/companies/company-1/agents") + .send({ + name: "Invalid Managed Selection", + adapterType: "paperclip_runner", + adapterConfig, + }), + ); + + expect(response.status, JSON.stringify(response.body)).toBe(expectedStatus); + expect(profileService.requireQualified).toHaveBeenCalledWith( + "company-1", + service === "managed" + ? adapterConfig.managedProfileId + : adapterConfig.agentCoreProfileId, + ...(service === "remote" ? ["aws_bedrock_agentcore_harness"] : []), + ); + expect(mockAgentService.create).not.toHaveBeenCalled(); + }, + ); + it("rejects provider changes but preserves edits to historical runner agents", async () => { const existing = await mockAgentService.getById(); mockAgentService.getById.mockResolvedValue({ @@ -663,6 +822,142 @@ describe("agent routes adapter validation", () => { }); }); + it.each([ + [ + "cleared managed profile", + { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + { managedProfileId: "" }, + "paperclip_runner_claude_managed_profile_required", + ], + [ + "withdrawn managed retention", + { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + { managedAgentsRetentionAcknowledged: false }, + "paperclip_runner_claude_managed_retention_required", + ], + [ + "unqualified managed model", + { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + { model: "claude-opus-5" }, + "paperclip_runner_claude_managed_model_unqualified", + ], + [ + "invalid managed spend cap", + { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + { maxSessionListCostUsd: 0 }, + "paperclip_runner_claude_managed_spend_cap_invalid", + ], + [ + "invalid Codex permission", + { provider: "codex", codexPermissionMode: "untrusted" }, + { codexPermissionMode: "unrestricted" }, + "runner_permission_mode_invalid", + ], + ])( + "rejects a same-provider Paperclip Runner edit with %s", + async (_label, existingAdapterConfig, adapterConfigPatch, expectedCode) => { + const existing = await mockAgentService.getById(); + mockAgentService.getById.mockResolvedValue({ + ...existing, + adapterType: "paperclip_runner", + adapterConfig: existingAdapterConfig, + }); + const app = await createApp(); + const response = await requestApp(app, (baseUrl) => + request(baseUrl) + .patch("/api/agents/11111111-1111-4111-8111-111111111111") + .send({ adapterConfig: adapterConfigPatch }), + ); + + expect(response.status, JSON.stringify(response.body)).toBe(422); + expect(response.body.details).toMatchObject({ code: expectedCode }); + expect(mockAgentService.update).not.toHaveBeenCalled(); + }, + ); + + it("validates a merged same-provider runner config and preserves omitted fields", async () => { + const existing = await mockAgentService.getById(); + mockAgentService.getById.mockResolvedValue({ + ...existing, + adapterType: "paperclip_runner", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + maxSessionListCostUsd: 1, + }, + }); + const app = await createApp(); + const response = await requestApp(app, (baseUrl) => + request(baseUrl) + .patch("/api/agents/11111111-1111-4111-8111-111111111111") + .send({ adapterConfig: { maxSessionListCostUsd: 2 } }), + ); + + expect(response.status, JSON.stringify(response.body)).toBe(200); + expect(mockAgentService.update).toHaveBeenCalledOnce(); + expect(mockAgentService.update.mock.calls[0]?.[1]).toMatchObject({ + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + maxSessionListCostUsd: 2, + }, + }); + }); + + it("rejects a same-provider rollback to an invalid runner config", async () => { + const existing = await mockAgentService.getById(); + mockAgentService.getById.mockResolvedValue({ + ...existing, + adapterType: "paperclip_runner", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + }); + mockAgentService.getConfigRevision.mockResolvedValue({ + afterConfig: { + adapterType: "paperclip_runner", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: false, + }, + runtimeConfig: {}, + }, + }); + const app = await createApp(); + const response = await requestApp(app, (baseUrl) => + request(baseUrl).post( + "/api/agents/11111111-1111-4111-8111-111111111111/config-revisions/33333333-3333-4333-8333-333333333333/rollback", + ), + ); + + expect(response.status, JSON.stringify(response.body)).toBe(422); + expect(response.body.details).toMatchObject({ + code: "paperclip_runner_claude_managed_retention_required", + }); + expect(mockAgentService.rollbackConfigRevision).not.toHaveBeenCalled(); + }); + it("keeps an existing paperclip_runner agent editable after the flag is disabled", async () => { const existing = await mockAgentService.getById(); mockAgentService.getById.mockResolvedValue({ diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index b23a397adb..3ea7f705ee 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -106,6 +106,14 @@ const instanceSettingsSvc = { getExperimental: vi.fn(async () => ({ enableNativeRunner: false })), }; +const managedAgentProfileSvc = { + requireQualified: vi.fn(), +}; + +const remoteAgentProfileSvc = { + requireQualified: vi.fn(), +}; + vi.mock("../services/companies.js", () => ({ companyService: () => companySvc, })); @@ -162,6 +170,14 @@ vi.mock("../services/instance-settings.js", () => ({ instanceSettingsService: () => instanceSettingsSvc, })); +vi.mock("../services/managed-agent-profiles.js", () => ({ + managedAgentProfileService: () => managedAgentProfileSvc, +})); + +vi.mock("../services/remote-agent-profiles.js", () => ({ + remoteAgentProfileService: () => remoteAgentProfileSvc, +})); + vi.mock("../routes/org-chart-svg.js", () => ({ renderOrgChartPng: vi.fn(async () => Buffer.from("png")), })); @@ -180,6 +196,16 @@ describe("company portability", () => { beforeEach(() => { vi.clearAllMocks(); instanceSettingsSvc.getExperimental.mockResolvedValue({ enableNativeRunner: false }); + managedAgentProfileSvc.requireQualified.mockResolvedValue({ + id: "managed-primary", + companyId: "company-1", + enabled: true, + }); + remoteAgentProfileSvc.requireQualified.mockResolvedValue({ + id: "agentcore-primary", + companyId: "company-1", + enabled: true, + }); secretSvc.create.mockResolvedValue({ id: "secret-created" }); secretSvc.remove.mockResolvedValue(true); secretSvc.normalizeAdapterConfigForPersistence.mockImplementation(async (_companyId, config) => config); @@ -5967,6 +5993,48 @@ describe("company portability", () => { adapterType: "paperclip_runner", adapterConfig: expect.objectContaining({ provider: "codex" }), })); + + await portability.importBundle({ + ...request, + adapterOverrides: { + claudecoder: { + adapterType: "paperclip_runner", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + }, + }, + }, "user-1"); + expect(managedAgentProfileSvc.requireQualified).toHaveBeenCalledWith( + "company-1", + "managed-primary", + ); + + const createCallsBeforeInvalidProfile = agentSvc.create.mock.calls.length; + const { notFound } = await import("../errors.js"); + managedAgentProfileSvc.requireQualified.mockRejectedValueOnce( + notFound("Managed Agent profile not found"), + ); + await expect(portability.importBundle({ + ...request, + adapterOverrides: { + claudecoder: { + adapterType: "paperclip_runner", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-other-company", + managedAgentsRetentionAcknowledged: true, + }, + }, + }, + }, "user-1")).rejects.toMatchObject({ status: 404 }); + expect(managedAgentProfileSvc.requireQualified).toHaveBeenLastCalledWith( + "company-1", + "managed-other-company", + ); + expect(agentSvc.create).toHaveBeenCalledTimes(createCallsBeforeInvalidProfile); }); }); diff --git a/server/src/__tests__/managed-agent-profile-routes-authz.test.ts b/server/src/__tests__/managed-agent-profile-routes-authz.test.ts new file mode 100644 index 0000000000..540f3f5b16 --- /dev/null +++ b/server/src/__tests__/managed-agent-profile-routes-authz.test.ts @@ -0,0 +1,165 @@ +import express from "express"; +import type { Db } from "@paperclipai/db"; +import request from "supertest"; +import { describe, it } from "vitest"; + +import { errorHandler } from "../middleware/index.js"; +import { managedAgentProfileRoutes } from "../routes/managed-agent-profiles.js"; +import { remoteAgentProfileRoutes } from "../routes/remote-agent-profiles.js"; + +const COMPANY_ID = "10000000-0000-4000-8000-000000000001"; +const OTHER_COMPANY_ID = "10000000-0000-4000-8000-000000000002"; + +const unusedDb = new Proxy({}, { + get() { + throw new Error("authorization failure unexpectedly accessed the database"); + }, +}) as unknown as Db; + +function createApp(actor: Express.Request["actor"]) { + const app = express(); + app.use(express.json()); + app.use((req, _res, next) => { + req.actor = actor; + next(); + }); + app.use("/api", managedAgentProfileRoutes(unusedDb)); + app.use("/api", remoteAgentProfileRoutes(unusedDb)); + app.use(errorHandler); + return app; +} + +function boardActor(role: "operator" | "viewer"): Express.Request["actor"] { + return { + type: "board", + source: "session", + userId: `${role}-1`, + userName: null, + userEmail: null, + isInstanceAdmin: false, + companyIds: [COMPANY_ID], + memberships: [{ companyId: COMPANY_ID, membershipRole: role, status: "active" }], + }; +} + +describe("managed provider profile route authorization", () => { + it("does not allow a board member to enumerate another company's profiles", async () => { + const app = createApp(boardActor("operator")); + + await request(app) + .get(`/api/companies/${OTHER_COMPANY_ID}/managed-agent-profiles`) + .expect(403); + await request(app) + .get(`/api/companies/${OTHER_COMPANY_ID}/remote-agent-profiles`) + .expect(403); + }); + + it("keeps profile management board-only", async () => { + const app = createApp({ + type: "agent", + source: "agent_key", + agentId: "agent-1", + companyId: COMPANY_ID, + }); + + await request(app) + .get(`/api/companies/${COMPANY_ID}/managed-agent-profiles`) + .expect(403); + await request(app) + .get(`/api/companies/${COMPANY_ID}/remote-agent-profiles`) + .expect(403); + }); + + it("allows viewer reads but blocks viewer writes before profile storage", async () => { + const app = createApp(boardActor("viewer")); + + await request(app) + .post(`/api/companies/${COMPANY_ID}/managed-agent-profiles`) + .send({}) + .expect(403); + await request(app) + .post(`/api/companies/${COMPANY_ID}/remote-agent-profiles`) + .send({}) + .expect(403); + }); + + it("does not let operator enablement substitute for qualification evidence", async () => { + const app = createApp(boardActor("operator")); + + await request(app) + .post(`/api/companies/${COMPANY_ID}/managed-agent-profiles`) + .send({ + profileKey: "managed", + displayName: "Managed", + anthropicAgentId: "agent-1", + agentVersion: "1", + environmentId: "environment-1", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: "20000000-0000-4000-8000-000000000002", + enabled: true, + retentionAcknowledged: true, + qualification: {}, + }) + .expect(422); + + await request(app) + .post(`/api/companies/${COMPANY_ID}/remote-agent-profiles`) + .send({ + profileKey: "agentcore", + displayName: "AgentCore", + service: "aws_bedrock_agentcore_harness", + configuration: { + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/example", + harnessVersion: "1", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/example", + endpointQualifier: "paperclip", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/example", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/example", + memoryId: "memory-example", + invocationRoleArn: "arn:aws:iam::123456789012:role/paperclip-runner", + contextBucket: "paperclip-runner-context", + contextPrefix: "profiles/example", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/example", + qualificationRevision: "aws-agentcore-harness-v1", + defaultModel: "global.anthropic.claude-sonnet-4-6", + eventExpiryDays: 90, + }, + enabled: true, + retentionAcknowledged: true, + qualification: { suite: "operator-says-pass" }, + }) + .expect(422); + }); + + it("routes Claude profiles only through the executable managed profile store", async () => { + const app = createApp(boardActor("operator")); + + await request(app) + .post(`/api/companies/${COMPANY_ID}/remote-agent-profiles`) + .send({ + profileKey: "wrong-store", + displayName: "Wrong Store", + service: "anthropic_managed_agents", + configuration: {}, + }) + .expect(422); + }); + + it("rejects obsolete AgentCore credential references before profile storage", async () => { + const app = createApp(boardActor("operator")); + + await request(app) + .post(`/api/companies/${COMPANY_ID}/remote-agent-profiles`) + .send({ + profileKey: "agentcore", + displayName: "AgentCore", + service: "aws_bedrock_agentcore_harness", + configuration: {}, + credentialSecretId: "20000000-0000-4000-8000-000000000002", + }) + .expect(422); + }); +}); diff --git a/server/src/__tests__/openapi-routes.test.ts b/server/src/__tests__/openapi-routes.test.ts index c494e85ae8..238816b7ed 100644 --- a/server/src/__tests__/openapi-routes.test.ts +++ b/server/src/__tests__/openapi-routes.test.ts @@ -45,12 +45,14 @@ const apiPrefixes: Record = { "issues.ts": "/api", "issue-tree-control.ts": "/api", "llms.ts": "/api", + "managed-agent-profiles.ts": "/api", "onboarding-seed.ts": "/api", "openapi.ts": "/api", "plugin-ui-static.ts": "/api", "plugins.ts": "/api", "projects.ts": "/api", "resource-memberships.ts": "/api", + "remote-agent-profiles.ts": "/api", "routines.ts": "/api", "secrets.ts": "/api", "sidebar-badges.ts": "/api", @@ -303,6 +305,23 @@ describe("openapi routes", () => { }); expect(spec.paths["/api/companies/{companyId}/cost-events"].post.responses["201"]).toBeDefined(); expect(spec.paths["/api/companies/{companyId}/cost-events"].post.responses["403"]).toBeDefined(); + expect(spec.paths["/api/companies/{companyId}/managed-agent-profiles"].post.security).toEqual([ + { BoardSessionAuth: [] }, + { BoardApiKeyAuth: [] }, + ]); + expect(spec.paths["/api/companies/{companyId}/remote-agent-profiles"].get.security).toEqual([ + { BoardSessionAuth: [] }, + { BoardApiKeyAuth: [] }, + ]); + const remoteAgentProfileBody = + spec.paths["/api/companies/{companyId}/remote-agent-profiles"].post.requestBody.content[ + "application/json" + ].schema; + expect(remoteAgentProfileBody.properties.service).toMatchObject({ + type: "string", + enum: ["aws_bedrock_agentcore_harness"], + }); + expect(remoteAgentProfileBody.properties.credentialSecretId).toBeUndefined(); expect(spec.paths["/api/instance/database-backups"].post.responses["201"]).toBeDefined(); expect(spec.paths["/api/invites/{token}/accept"].post.responses["202"]).toBeDefined(); expect(spec.paths["/api/board-api-keys"].post.responses["201"]).toBeDefined(); diff --git a/server/src/adapters/registry.ts b/server/src/adapters/registry.ts index a7e5214fc1..f303cce14d 100644 --- a/server/src/adapters/registry.ts +++ b/server/src/adapters/registry.ts @@ -370,9 +370,13 @@ const paperclipRunnerAdapter: ServerAdapterModule = { errorCode: "paperclip_runner_coordinator_required", provider: ctx.config.provider === "opencode" ? "opencode" + : ctx.config.provider === "claude_managed" + ? "anthropic" + : ctx.config.provider === "aws_agentcore" + ? "amazon-bedrock" : ctx.config.provider === "acpx" - ? "acpx" - : "codex", + ? "acpx" + : "codex", summary: message, }; }, @@ -410,6 +414,38 @@ const paperclipRunnerAdapter: ServerAdapterModule = { }], }; } + if (profile.provider === "claude_managed") { + return { + adapterType: "paperclip_runner", + status: "warn" as const, + testedAt: new Date().toISOString(), + checks: [{ + code: "claude_managed_profile_selected", + level: "info" as const, + message: `Claude Managed profile ${profile.managedProfileId} is selected with retention acknowledged. Its stored qualification, API-key binding, and spend ceiling are verified before the first turn.`, + }, { + code: "claude_managed_retention_notice", + level: "warn" as const, + message: "Claude Managed is a stateful beta service and is not eligible for ZDR or HIPAA modes.", + }], + }; + } + if (profile.provider === "aws_agentcore") { + return { + adapterType: "paperclip_runner", + status: "warn" as const, + testedAt: new Date().toISOString(), + checks: [{ + code: "aws_agentcore_profile_selected", + level: "info" as const, + message: `AWS AgentCore profile ${profile.agentCoreProfileId} is selected with retention acknowledged. Its stored qualification, invocation limits, and estimated spend ceiling are verified before the first turn.`, + }, { + code: "aws_agentcore_retention_notice", + level: "warn" as const, + message: "AgentCore Memory retains short-term events for 90 days; the spend ceiling is an estimate, not an AWS currency hard stop.", + }], + }; + } const result = profile.provider === "opencode" ? await openCodeTestEnvironment(context) : await codexTestEnvironment(context); @@ -422,22 +458,27 @@ const paperclipRunnerAdapter: ServerAdapterModule = { ...codexModels, { id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" }, { id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, + { id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" }, ], listModels: async () => [ ...await listCodexModels(), { id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" }, { id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, + { id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" }, ], refreshModels: async () => [ ...await refreshCodexModels(), { id: DEFAULT_OPENCODE_RUNNER_MODEL, label: "OpenRouter · DeepSeek V4 Flash 0731" }, { id: QUALIFIED_ACPX_RUNNER_MODELS.claude, label: "Claude Sonnet 5" }, + { id: "global.anthropic.claude-sonnet-4-6", label: "Amazon Bedrock · Claude Sonnet 4.6 (global)" }, ], supportsLocalAgentJwt: false, supportsInstructionsBundle: true, instructionsPathKey: "instructionsFilePath", requiresMaterializedRuntimeSkills: false, - getRuntimeCommandSpec: (config) => config.provider === "acpx" + getRuntimeCommandSpec: (config) => config.provider === "claude_managed" + || config.provider === "aws_agentcore" + || config.provider === "acpx" ? { command: "paperclip-runnerd", detectCommand: null, installCommand: null } : config.provider === "opencode" ? buildNpmRuntimeCommandSpec( @@ -447,7 +488,7 @@ const paperclipRunnerAdapter: ServerAdapterModule = { ) : buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex@0.148.0"), agentConfigurationDoc: - "# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, or a qualified Claude/Codex ACP agent through the Rust Paperclip runner and authenticated PRP transport. Pi is not available through the qualified ACPX profile.\n", + "# Paperclip Runner\n\nAdapter: paperclip_runner\n\nRuns Codex, OpenCode, Claude Managed, AWS AgentCore, or a qualified Claude/Codex ACP agent through the Rust Paperclip runner and authenticated PRP transport. Pi is not available through the qualified ACPX profile. Managed providers use company-scoped qualified profiles, explicit retention acknowledgement, and spend limits.\n", getConfigSchema: () => ({ fields: [ { @@ -458,9 +499,11 @@ const paperclipRunnerAdapter: ServerAdapterModule = { options: [ { value: "codex", label: "Codex" }, { value: "opencode", label: `OpenCode ${QUALIFIED_OPENCODE_RUNNER_VERSION}` }, + { value: "claude_managed", label: "Claude Managed" }, + { value: "aws_agentcore", label: "AWS AgentCore" }, { value: "acpx", label: "ACPX" }, ], - hint: "Select Codex, qualified OpenCode, or a qualified Claude/Codex ACPX profile.", + hint: "Select a local provider, company-qualified managed provider, or qualified Claude/Codex ACPX profile.", }, { key: "codexPermissionMode", @@ -516,6 +559,78 @@ const paperclipRunnerAdapter: ServerAdapterModule = { hint: "OpenCode uses provider/model form. ACPX models are pinned by the selected qualified agent profile.", meta: { visibleWhen: { key: "provider", value: "opencode" } }, }, + { + key: "managedProfileId", + label: "Managed Agent profile", + type: "text" as const, + required: true, + hint: "Company-scoped qualified profile ID or key.", + meta: { visibleWhen: { key: "provider", value: "claude_managed" } }, + }, + { + key: "maxSessionListCostUsd", + label: "Session spend ceiling (USD)", + type: "number" as const, + default: 1, + hint: "Hard ceiling for one Claude Managed session.", + meta: { visibleWhen: { key: "provider", value: "claude_managed" } }, + }, + { + key: "managedAgentsRetentionAcknowledged", + label: "Acknowledge managed retention", + type: "toggle" as const, + default: false, + hint: "Claude Managed is stateful beta and is not eligible for ZDR or HIPAA modes.", + meta: { visibleWhen: { key: "provider", value: "claude_managed" } }, + }, + { + key: "agentCoreProfileId", + label: "AgentCore profile", + type: "text" as const, + required: true, + hint: "Company-scoped qualified AgentCore profile ID or key.", + meta: { visibleWhen: { key: "provider", value: "aws_agentcore" } }, + }, + { + key: "maxEstimatedSessionCostUsd", + label: "Estimated session ceiling (USD)", + type: "number" as const, + default: 1, + hint: "Paperclip estimate; AWS does not provide a per-session currency hard stop.", + meta: { visibleWhen: { key: "provider", value: "aws_agentcore" } }, + }, + { + key: "maxIterations", + label: "Maximum iterations", + type: "number" as const, + default: 8, + hint: "Must be between 1 and the qualified maximum of 8.", + meta: { visibleWhen: { key: "provider", value: "aws_agentcore" } }, + }, + { + key: "maxOutputTokens", + label: "Maximum output tokens", + type: "number" as const, + default: 4_096, + hint: "Must be between 1 and the qualified maximum of 4096.", + meta: { visibleWhen: { key: "provider", value: "aws_agentcore" } }, + }, + { + key: "timeoutSeconds", + label: "Invocation timeout (seconds)", + type: "number" as const, + default: 300, + hint: "Must be between 1 and the qualified maximum of 300 seconds.", + meta: { visibleWhen: { key: "provider", value: "aws_agentcore" } }, + }, + { + key: "agentCoreRetentionAcknowledged", + label: "Acknowledge 90-day Memory retention", + type: "toggle" as const, + default: false, + hint: "The qualified AgentCore profile retains short-term Memory events for 90 days.", + meta: { visibleWhen: { key: "provider", value: "aws_agentcore" } }, + }, { key: "lifecycleMode", label: "Runner lifecycle", diff --git a/server/src/app.ts b/server/src/app.ts index 29cc528dad..851399c992 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -87,6 +87,8 @@ import { runtimeConnectionIntentRoutes, } from "./routes/connection-intents.js"; import { adapterRoutes } from "./routes/adapters.js"; +import { managedAgentProfileRoutes } from "./routes/managed-agent-profiles.js"; +import { remoteAgentProfileRoutes } from "./routes/remote-agent-profiles.js"; import { pluginUiStaticRoutes } from "./routes/plugin-ui-static.js"; import { readBrandedStaticIndexHtml } from "./static-index-html.js"; import { staticUiCacheControl } from "./static-ui-cache.js"; @@ -534,6 +536,8 @@ export async function createApp( api.use(boardChatRoutes(db, { deploymentMode: opts.deploymentMode })); api.use(approvalRoutes(db, { pluginWorkerManager: workerManager })); api.use(secretRoutes(db)); + api.use(managedAgentProfileRoutes(db)); + api.use(remoteAgentProfileRoutes(db)); const trustedLocalStdioRuntimeHost = process.env.PAPERCLIP_TRUSTED_MCP_RUNTIME_HOST ?? process.env.PAPERCLIP_TOOL_RUNTIME_TRUSTED_HOST diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 5030ec10ac..9cdf5e0b12 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -213,6 +213,8 @@ import { PaperclipRunnerProviderProfileError, resolvePaperclipRunnerProviderProfile, } from "../services/native-runtime/provider-profile.js"; +import { managedAgentProfileService } from "../services/managed-agent-profiles.js"; +import { remoteAgentProfileService } from "../services/remote-agent-profiles.js"; const AGENT_SKILL_ASSIGNMENT_MODES = ["add", "remove", "replace"] as const; @@ -1685,19 +1687,33 @@ export function agentRoutes( ); } - function assertFreshPaperclipRunnerProvider( + async function assertFreshPaperclipRunnerProvider( + companyId: string, adapterType: string, adapterConfig: Record, - ): void { + ): Promise { if (adapterType !== "paperclip_runner") return; + let profile; try { - resolvePaperclipRunnerProviderProfile(adapterConfig); + profile = resolvePaperclipRunnerProviderProfile(adapterConfig); } catch (error) { if (error instanceof PaperclipRunnerProviderProfileError) { throw unprocessable(error.message, { code: error.code }); } throw error; } + if (profile.provider === "claude_managed") { + await managedAgentProfileService(db).requireQualified( + companyId, + profile.managedProfileId, + ); + } else if (profile.provider === "aws_agentcore") { + await remoteAgentProfileService(db).requireQualified( + companyId, + profile.agentCoreProfileId, + "aws_bedrock_agentcore_harness", + ); + } } function assertProviderTraceSettingTransition( @@ -1905,6 +1921,7 @@ export function agentRoutes( }, ); await assertAdapterConfigConstraints( + input.companyId, input.adapterType, input.constraintAdapterConfig ? { ...input.constraintAdapterConfig, ...normalizedAdapterConfig } @@ -2000,11 +2017,12 @@ export function agentRoutes( } async function assertAdapterConfigConstraints( + companyId: string, adapterType: string | null | undefined, adapterConfig: Record, ) { if (adapterType === "paperclip_runner") { - assertFreshPaperclipRunnerProvider(adapterType, adapterConfig); + await assertFreshPaperclipRunnerProvider(companyId, adapterType, adapterConfig); return; } if (adapterType !== "opencode_local") return; @@ -3424,13 +3442,12 @@ export function agentRoutes( await assertSelectableAdapterType(rollbackAdapterType); } const rollbackAdapterConfig = asRecord(rollbackConfig.adapterConfig) ?? {}; - const existingAdapterConfig = asRecord(existing.adapterConfig) ?? {}; if ( rollbackAdapterType !== existing.adapterType || - (rollbackAdapterType === "paperclip_runner" && - rollbackAdapterConfig.provider !== existingAdapterConfig.provider) + rollbackAdapterType === "paperclip_runner" ) { - assertFreshPaperclipRunnerProvider( + await assertFreshPaperclipRunnerProvider( + existing.companyId, rollbackAdapterType, rollbackAdapterConfig, ); @@ -3536,7 +3553,8 @@ export function agentRoutes( hireInput.adapterType = await assertSelectableAdapterType(hireInput.adapterType); const rawHireAdapterConfig = (hireInput.adapterConfig ?? {}) as Record; assertProviderTraceSettingTransition(req, hireInput.runtimeConfig); - assertFreshPaperclipRunnerProvider( + await assertFreshPaperclipRunnerProvider( + companyId, hireInput.adapterType, rawHireAdapterConfig, ); @@ -3754,7 +3772,8 @@ export function agentRoutes( createInput.adapterType = await assertSelectableAdapterType(createInput.adapterType); const rawCreateAdapterConfig = (createInput.adapterConfig ?? {}) as Record; assertProviderTraceSettingTransition(req, createInput.runtimeConfig); - assertFreshPaperclipRunnerProvider( + await assertFreshPaperclipRunnerProvider( + companyId, createInput.adapterType, rawCreateAdapterConfig, ); @@ -4252,9 +4271,11 @@ export function agentRoutes( if ( changingAdapterType || (requestedAdapterType === "paperclip_runner" && - rawEffectiveAdapterConfig.provider !== existingRunnerProvider) + (requestedAdapterConfig !== null || + rawEffectiveAdapterConfig.provider !== existingRunnerProvider)) ) { - assertFreshPaperclipRunnerProvider( + await assertFreshPaperclipRunnerProvider( + existing.companyId, requestedAdapterType, rawEffectiveAdapterConfig, ); diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index 835c54bb76..fdf3e0889b 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -39,3 +39,5 @@ export { llmRoutes } from "./llms.js"; export { accessRoutes } from "./access.js"; export { instanceSettingsRoutes } from "./instance-settings.js"; export { instanceDatabaseBackupRoutes } from "./instance-database-backups.js"; +export { managedAgentProfileRoutes } from "./managed-agent-profiles.js"; +export { remoteAgentProfileRoutes } from "./remote-agent-profiles.js"; diff --git a/server/src/routes/managed-agent-profiles.ts b/server/src/routes/managed-agent-profiles.ts new file mode 100644 index 0000000000..64d899a8e0 --- /dev/null +++ b/server/src/routes/managed-agent-profiles.ts @@ -0,0 +1,79 @@ +import { Router } from "express"; +import type { Db } from "@paperclipai/db"; + +import { unprocessable } from "../errors.js"; +import { logActivity } from "../services/activity-log.js"; +import { + managedAgentProfileService, + type ManagedAgentProfileInput, +} from "../services/managed-agent-profiles.js"; +import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js"; + +function profileInput(value: unknown): ManagedAgentProfileInput { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw unprocessable("Managed Agent profile body is required"); + } + const body = value as Record; + return { + profileKey: String(body.profileKey ?? ""), + displayName: String(body.displayName ?? ""), + anthropicAgentId: String(body.anthropicAgentId ?? ""), + agentVersion: String(body.agentVersion ?? ""), + environmentId: String(body.environmentId ?? ""), + defaultModel: String(body.defaultModel ?? "claude-sonnet-5"), + defaultMaxListCostUsd: Number(body.defaultMaxListCostUsd ?? 1), + apiKeySecretId: String(body.apiKeySecretId ?? ""), + enabled: body.enabled === true, + retentionAcknowledged: body.retentionAcknowledged === true, + qualification: + body.qualification + && typeof body.qualification === "object" + && !Array.isArray(body.qualification) + ? body.qualification as Record + : {}, + }; +} + +export function managedAgentProfileRoutes(db: Db) { + const router = Router(); + const profiles = managedAgentProfileService(db); + + router.get("/companies/:companyId/managed-agent-profiles", async (req, res) => { + assertBoard(req); + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + res.json(await profiles.list(companyId)); + }); + + router.post("/companies/:companyId/managed-agent-profiles", async (req, res) => { + assertBoard(req); + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const profile = await profiles.upsert(companyId, profileInput(req.body)); + const actor = getActorInfo(req); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "managed_agent_profile.upserted", + entityType: "managed_agent_profile", + entityId: profile.id, + details: { + profileKey: profile.profileKey, + service: profile.service, + agentVersion: profile.agentVersion, + environmentId: profile.environmentId, + model: profile.defaultModel, + enabled: profile.enabled, + retentionAcknowledged: profile.retentionAcknowledged, + defaultMaxListCostCents: profile.defaultMaxListCostCents, + qualifiedRevision: profile.qualifiedRevision, + }, + }); + res.status(201).json(profile); + }); + + return router; +} diff --git a/server/src/routes/openapi.ts b/server/src/routes/openapi.ts index f73b47f6a1..61aa6bf713 100644 --- a/server/src/routes/openapi.ts +++ b/server/src/routes/openapi.ts @@ -875,6 +875,10 @@ const BOARD_ONLY_OPERATIONS = new Set([ "POST /api/companies/{companyId}/members/{memberId}/archive", "PATCH /api/companies/{companyId}/members/{memberId}/permissions", "GET /api/companies/{companyId}/user-directory", + "GET /api/companies/{companyId}/managed-agent-profiles", + "POST /api/companies/{companyId}/managed-agent-profiles", + "GET /api/companies/{companyId}/remote-agent-profiles", + "POST /api/companies/{companyId}/remote-agent-profiles", "POST /api/execution-workspaces/{id}/reconcile-branch", "POST /api/execution-workspaces/{id}/login-handoff", "GET /api/board-api-keys", @@ -4536,6 +4540,90 @@ registry.registerPath({ responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound }, }); +registry.registerPath({ + method: "get", + path: "/api/companies/{companyId}/managed-agent-profiles", + tags: ["agents"], + summary: "List Claude Managed Agent profiles for a company", + request: { params: z.object({ companyId: z.string() }) }, + responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden }, +}); + +registry.registerPath({ + method: "post", + path: "/api/companies/{companyId}/managed-agent-profiles", + tags: ["agents"], + summary: "Create or operator-attest a Claude Managed Agent profile", + request: { + params: z.object({ companyId: z.string() }), + body: jsonBody(z.object({ + profileKey: z.string(), + displayName: z.string(), + anthropicAgentId: z.string(), + agentVersion: z.string(), + environmentId: z.string(), + defaultModel: z.literal("claude-sonnet-5").optional(), + defaultMaxListCostUsd: z.number().positive().optional(), + apiKeySecretId: z.string(), + enabled: z.boolean().optional(), + retentionAcknowledged: z.boolean().optional(), + qualification: z.object({ + probedAt: z.string().datetime(), + betaVersion: z.literal("managed-agents-2026-04-01"), + environmentPolicy: z.literal("limited_no_hosts_no_packages"), + agentCapabilities: z.literal("no_tools_no_mcp_no_skills_no_multiagent"), + }).strict().optional(), + })), + }, + responses: { + 201: r.ok(), + 401: r.unauthorized, + 403: r.forbidden, + 409: r.conflict, + 422: r.unprocessable, + }, +}); + +registry.registerPath({ + method: "get", + path: "/api/companies/{companyId}/remote-agent-profiles", + tags: ["agents"], + summary: "List remote AgentCore profiles for a company", + request: { + params: z.object({ companyId: z.string() }), + query: z.object({ + service: z.literal("aws_bedrock_agentcore_harness").optional(), + }), + }, + responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden, 422: r.unprocessable }, +}); + +registry.registerPath({ + method: "post", + path: "/api/companies/{companyId}/remote-agent-profiles", + tags: ["agents"], + summary: "Create or operator-attest a remote AgentCore profile", + request: { + params: z.object({ companyId: z.string() }), + body: jsonBody(z.object({ + profileKey: z.string(), + displayName: z.string(), + service: z.literal("aws_bedrock_agentcore_harness"), + configuration: z.record(z.string(), z.unknown()), + enabled: z.boolean().optional(), + retentionAcknowledged: z.boolean().optional(), + qualification: z.object({ suite: z.literal("aws-agentcore-harness-v1") }).strict().optional(), + })), + }, + responses: { + 201: r.ok(), + 401: r.unauthorized, + 403: r.forbidden, + 409: r.conflict, + 422: r.unprocessable, + }, +}); + // ─── Heartbeat runs ────────────────────────────────────────────────────────── registry.registerPath({ diff --git a/server/src/routes/remote-agent-profiles.ts b/server/src/routes/remote-agent-profiles.ts new file mode 100644 index 0000000000..1c586be4a3 --- /dev/null +++ b/server/src/routes/remote-agent-profiles.ts @@ -0,0 +1,90 @@ +import { Router } from "express"; +import type { Db } from "@paperclipai/db"; + +import { unprocessable } from "../errors.js"; +import { logActivity } from "../services/activity-log.js"; +import { + remoteAgentProfileService, + type RemoteAgentProfileInput, + type RemoteAgentService, +} from "../services/remote-agent-profiles.js"; +import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js"; + +function remoteAgentService(value: unknown): RemoteAgentService { + if (value !== "aws_bedrock_agentcore_harness") { + throw unprocessable("Unsupported remote agent service"); + } + return value; +} + +function profileInput(value: unknown): RemoteAgentProfileInput { + if (!value || typeof value !== "object" || Array.isArray(value)) { + throw unprocessable("Remote Agent profile body is required"); + } + const body = value as Record; + if ("credentialSecretId" in body) { + throw unprocessable("AWS AgentCore profiles use workload identity, not a credential secret"); + } + return { + profileKey: String(body.profileKey ?? ""), + displayName: String(body.displayName ?? ""), + service: remoteAgentService(body.service), + configuration: + body.configuration + && typeof body.configuration === "object" + && !Array.isArray(body.configuration) + ? body.configuration as Record + : {}, + enabled: body.enabled === true, + retentionAcknowledged: body.retentionAcknowledged === true, + qualification: + body.qualification + && typeof body.qualification === "object" + && !Array.isArray(body.qualification) + ? body.qualification as Record + : {}, + }; +} + +export function remoteAgentProfileRoutes(db: Db) { + const router = Router(); + const profiles = remoteAgentProfileService(db); + + router.get("/companies/:companyId/remote-agent-profiles", async (req, res) => { + assertBoard(req); + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const service = req.query.service === undefined + ? undefined + : remoteAgentService(req.query.service); + res.json(await profiles.list(companyId, service)); + }); + + router.post("/companies/:companyId/remote-agent-profiles", async (req, res) => { + assertBoard(req); + const companyId = req.params.companyId as string; + assertCompanyAccess(req, companyId); + const profile = await profiles.upsert(companyId, profileInput(req.body)); + const actor = getActorInfo(req); + await logActivity(db, { + companyId, + actorType: actor.actorType, + actorId: actor.actorId, + agentId: actor.agentId, + runId: actor.runId, + action: "remote_agent_profile.upserted", + entityType: "remote_agent_profile", + entityId: profile.id, + details: { + profileKey: profile.profileKey, + service: profile.service, + enabled: profile.enabled, + retentionAcknowledged: profile.retentionAcknowledged, + qualifiedRevision: profile.qualifiedRevision, + }, + }); + res.status(201).json(profile); + }); + + return router; +} diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index 3bdeffea25..8007f9e3a0 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -109,6 +109,8 @@ import { PaperclipRunnerProviderProfileError, resolvePaperclipRunnerProviderProfile, } from "./native-runtime/provider-profile.js"; +import { managedAgentProfileService } from "./managed-agent-profiles.js"; +import { remoteAgentProfileService } from "./remote-agent-profiles.js"; const EXPORT_READ_CONCURRENCY = 8; const EXPORT_ISSUE_READ_CONCURRENCY = 2; @@ -3585,18 +3587,32 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { } async function assertImportAdapterConfigConstraints( + companyId: string, adapterType: string, adapterConfig: Record, ) { if (adapterType === "paperclip_runner") { + let profile; try { - resolvePaperclipRunnerProviderProfile(adapterConfig); + profile = resolvePaperclipRunnerProviderProfile(adapterConfig); } catch (error) { if (error instanceof PaperclipRunnerProviderProfileError) { throw unprocessable(error.message, { code: error.code }); } throw error; } + if (profile.provider === "claude_managed") { + await managedAgentProfileService(db).requireQualified( + companyId, + profile.managedProfileId, + ); + } else if (profile.provider === "aws_agentcore") { + await remoteAgentProfileService(db).requireQualified( + companyId, + profile.agentCoreProfileId, + "aws_bedrock_agentcore_harness", + ); + } return; } if (adapterType !== "opencode_local") return; @@ -3634,7 +3650,11 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { nextAdapterConfig, { strictMode: strictSecretsMode, adapterType: effectiveAdapterType }, ); - await assertImportAdapterConfigConstraints(effectiveAdapterType, normalizedAdapterConfig); + await assertImportAdapterConfigConstraints( + companyId, + effectiveAdapterType, + normalizedAdapterConfig, + ); return { adapterType: effectiveAdapterType, adapterConfig: normalizedAdapterConfig, diff --git a/server/src/services/heartbeat-runner-provider-config.test.ts b/server/src/services/heartbeat-runner-provider-config.test.ts index 0b80b98aa0..f1bde39aa3 100644 --- a/server/src/services/heartbeat-runner-provider-config.test.ts +++ b/server/src/services/heartbeat-runner-provider-config.test.ts @@ -1,8 +1,93 @@ import { describe, expect, it } from "vitest"; -import { resolvePaperclipRunnerNativeProviderInput } from "./native-runtime/provider-profile.js"; +import { + assertAgentCoreProfileRecoveryBinding, + assertManagedProfileRecoveryBinding, + resolvePaperclipRunnerNativeProviderInput, +} from "./native-runtime/provider-profile.js"; describe("Paperclip Runner native provider configuration", () => { + it("requires persisted Claude recovery to use the qualified identity and current profile secret", () => { + const stored = { + id: "00000000-0000-4000-8000-000000000001", + anthropicAgentId: "agent_remote", + agentVersion: "7", + environmentId: "env_remote", + betaVersion: "managed-agents-2026-04-01", + apiKeySecretId: "00000000-0000-4000-8000-000000000010", + }; + const snapshot = { + profileId: stored.id, + anthropicAgentId: stored.anthropicAgentId, + agentVersion: stored.agentVersion, + environmentId: stored.environmentId, + betaVersion: stored.betaVersion, + }; + expect(() => assertManagedProfileRecoveryBinding({ + adapterConfig: { + env: { ANTHROPIC_API_KEY: { secretId: stored.apiKeySecretId } }, + }, + snapshot, + stored, + })).not.toThrow(); + expect(() => assertManagedProfileRecoveryBinding({ + adapterConfig: { + env: { + ANTHROPIC_API_KEY: { + secretId: "00000000-0000-4000-8000-000000000099", + }, + }, + }, + snapshot, + stored, + })).toThrow("current API-key secret"); + expect(() => assertManagedProfileRecoveryBinding({ + adapterConfig: { + env: { ANTHROPIC_API_KEY: { secretId: stored.apiKeySecretId } }, + }, + snapshot: { ...snapshot, agentVersion: "8" }, + stored, + })).toThrow("no longer matches"); + }); + + it("requires persisted AgentCore recovery to use the same qualified identity", () => { + const configuration = { + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/h-1", + harnessVersion: "3", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/e-1", + endpointQualifier: "prod", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/r-1", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/m-1", + memoryId: "m-1", + invocationRoleArn: "arn:aws:iam::123456789012:role/invoke", + contextBucket: "paperclip-context", + contextPrefix: "runner/", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1", + qualificationRevision: "aws-agentcore-harness-v1", + eventExpiryDays: 90, + }; + const stored = { + id: "00000000-0000-4000-8000-000000000002", + configuration, + }; + const snapshot = { profileId: stored.id, ...configuration }; + expect(() => assertAgentCoreProfileRecoveryBinding({ + snapshot, + stored, + })).not.toThrow(); + expect(() => assertAgentCoreProfileRecoveryBinding({ + snapshot: { ...snapshot, harnessVersion: "4" }, + stored, + })).toThrow("no longer matches"); + expect(() => assertAgentCoreProfileRecoveryBinding({ + snapshot, + stored: { ...stored, id: "00000000-0000-4000-8000-000000000099" }, + })).toThrow("no longer matches"); + }); + + it("projects OpenCode identity, model, and permissions from adapter config", () => { expect( resolvePaperclipRunnerNativeProviderInput({ @@ -56,6 +141,204 @@ describe("Paperclip Runner native provider configuration", () => { }); }); + it("materializes a qualified Claude Managed profile without trusting editable resource IDs", () => { + expect(resolvePaperclipRunnerNativeProviderInput({ + backend: "claude_managed_agents_api", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + maxSessionListCostUsd: 0.75, + }, + managedProfile: { + id: "00000000-0000-4000-8000-000000000001", + profileKey: "managed-primary", + anthropicAgentId: "agent_remote", + agentVersion: "7", + environmentId: "env_remote", + betaVersion: "managed-agents-2026-04-01", + defaultModel: "claude-sonnet-5", + defaultMaxListCostCents: 100, + }, + })).toEqual({ + provider: "claude_managed", + model: "claude-sonnet-5", + managedProfile: { + profileId: "00000000-0000-4000-8000-000000000001", + anthropicAgentId: "agent_remote", + agentVersion: "7", + environmentId: "env_remote", + betaVersion: "managed-agents-2026-04-01", + }, + maxSessionListCostUsd: 0.75, + }); + }); + + it("materializes a qualified AgentCore profile with bounded invocation limits", () => { + expect(resolvePaperclipRunnerNativeProviderInput({ + backend: "aws_agentcore_harness_api", + adapterConfig: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + maxIterations: 8, + maxOutputTokens: 2_048, + timeoutSeconds: 30, + }, + agentCoreProfile: { + id: "00000000-0000-4000-8000-000000000002", + profileKey: "agentcore-primary", + configuration: { + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/h-1", + harnessVersion: "3", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/e-1", + endpointQualifier: "prod", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/r-1", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/m-1", + memoryId: "m-1", + invocationRoleArn: "arn:aws:iam::123456789012:role/invoke", + contextBucket: "paperclip-context", + contextPrefix: "runner/", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1", + qualificationRevision: "aws-agentcore-harness-v1", + defaultModel: "global.anthropic.claude-sonnet-4-6", + eventExpiryDays: 90, + defaultMaxEstimatedSessionCostUsd: 1.25, + }, + }, + })).toMatchObject({ + provider: "aws_agentcore", + model: "global.anthropic.claude-sonnet-4-6", + agentCoreProfile: { + profileId: "00000000-0000-4000-8000-000000000002", + eventExpiryDays: 90, + }, + maxEstimatedSessionCostUsd: 1.25, + invocationLimits: { + maxIterations: 8, + maxOutputTokens: 2_048, + timeoutSeconds: 30, + }, + }); + }); + + it.each([ + ["maxIterations", 0], + ["maxIterations", 9], + ["maxIterations", "8"], + ["maxOutputTokens", 4_097], + ["timeoutSeconds", 301], + ])("rejects an unsafe AgentCore %s override", (field, value) => { + expect(() => resolvePaperclipRunnerNativeProviderInput({ + backend: "aws_agentcore_harness_api", + adapterConfig: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + [field]: value, + }, + agentCoreProfile: { + id: "00000000-0000-4000-8000-000000000002", + profileKey: "agentcore-primary", + configuration: { + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/h-1", + harnessVersion: "3", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/e-1", + endpointQualifier: "prod", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/r-1", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/m-1", + memoryId: "m-1", + invocationRoleArn: "arn:aws:iam::123456789012:role/invoke", + contextBucket: "paperclip-context", + contextPrefix: "runner/", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1", + qualificationRevision: "aws-agentcore-harness-v1", + defaultModel: "global.anthropic.claude-sonnet-4-6", + eventExpiryDays: 90, + defaultMaxEstimatedSessionCostUsd: 1.25, + }, + }, + })).toThrow("must be an integer between"); + }); + + it("rejects managed providers without retention acknowledgement or a matching stored profile", () => { + expect(() => resolvePaperclipRunnerNativeProviderInput({ + backend: "claude_managed_agents_api", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + }, + })).toThrow("requires acknowledgement"); + + expect(() => resolvePaperclipRunnerNativeProviderInput({ + backend: "aws_agentcore_harness_api", + adapterConfig: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + }, + })).toThrow("does not match the adapter selection"); + }); + + it("rejects managed provider model overrides outside the runner allowlist", () => { + expect(() => resolvePaperclipRunnerNativeProviderInput({ + backend: "claude_managed_agents_api", + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + model: "claude-opus-5", + }, + managedProfile: { + id: "00000000-0000-4000-8000-000000000001", + profileKey: "managed-primary", + anthropicAgentId: "agent_remote", + agentVersion: "7", + environmentId: "env_remote", + betaVersion: "managed-agents-2026-04-01", + defaultModel: "claude-sonnet-5", + defaultMaxListCostCents: 100, + }, + })).toThrow("requires exact model claude-sonnet-5"); + + expect(() => resolvePaperclipRunnerNativeProviderInput({ + backend: "aws_agentcore_harness_api", + adapterConfig: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + model: "global.anthropic.claude-opus-5", + }, + agentCoreProfile: { + id: "00000000-0000-4000-8000-000000000002", + profileKey: "agentcore-primary", + configuration: { + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/h-1", + harnessVersion: "3", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/e-1", + endpointQualifier: "prod", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/r-1", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/m-1", + memoryId: "m-1", + invocationRoleArn: "arn:aws:iam::123456789012:role/invoke", + contextBucket: "paperclip-context", + contextPrefix: "runner/", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1", + qualificationRevision: "aws-agentcore-harness-v1", + defaultModel: "global.anthropic.claude-sonnet-4-6", + eventExpiryDays: 90, + defaultMaxEstimatedSessionCostUsd: 1.25, + }, + }, + })).toThrow("requires exact model global.anthropic.claude-sonnet-4-6"); + }); + it("fails closed when the persisted backend and current provider disagree", () => { expect(() => resolvePaperclipRunnerNativeProviderInput({ diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index f879cef7b9..38cb15ca0d 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -117,6 +117,8 @@ import { withQueuedCommentIdsInRunContext, } from "./issue-queued-comment-queue.js"; import { documentService } from "./documents.js"; +import { managedAgentProfileService } from "./managed-agent-profiles.js"; +import { remoteAgentProfileService } from "./remote-agent-profiles.js"; import { buildNativeProviderEnvironment, buildNativeExecutionInput, @@ -135,7 +137,11 @@ import { reconcileNativeFinalizations, resolveHeartbeatNativeRuntimeMode, } from "./native-runtime/index.js"; -import { resolvePaperclipRunnerNativeProviderInput } from "./native-runtime/provider-profile.js"; +import { + assertAgentCoreProfileRecoveryBinding, + assertManagedProfileRecoveryBinding, + resolvePaperclipRunnerNativeProviderInput, +} from "./native-runtime/provider-profile.js"; import type { NativeRunHistoricalSpan } from "./native-runtime/native-run-trace.js"; import { parseNativeExecutionInput, @@ -19758,6 +19764,30 @@ export function heartbeatService( throw new Error( "native_execution_input_persisted_binding_mismatch", ); + if (nativeExecution.provider.kind === "claude_managed") { + const recoveryProfile = + await managedAgentProfileService(db).requireQualified( + agent.companyId, + nativeExecution.provider.managedProfile.profileId, + ); + assertManagedProfileRecoveryBinding({ + adapterConfig: agent.adapterConfig, + snapshot: nativeExecution.provider.managedProfile, + stored: recoveryProfile, + }); + } + if (nativeExecution.provider.kind === "aws_agentcore") { + const recoveryProfile = + await remoteAgentProfileService(db).requireQualified( + agent.companyId, + nativeExecution.provider.agentCoreProfile.profileId, + "aws_bedrock_agentcore_harness", + ); + assertAgentCoreProfileRecoveryBinding({ + snapshot: nativeExecution.provider.agentCoreProfile, + stored: recoveryProfile, + }); + } } else { const interactionId = readNonEmptyString(context.interactionId); const interactionResponses = @@ -19769,6 +19799,50 @@ export function heartbeatService( agentId: agent.id, interactionIds: interactionId ? [interactionId] : [], }); + const runnerAdapterConfig = parseObject(agent.adapterConfig); + const managedProfile = + nativeRuntimeResolution.profile.backend === + "claude_managed_agents_api" + ? await managedAgentProfileService(db).requireQualified( + agent.companyId, + readNonEmptyString(runnerAdapterConfig.managedProfileId) ?? "", + ) + : null; + const agentCoreProfile = + nativeRuntimeResolution.profile.backend === + "aws_agentcore_harness_api" + ? await remoteAgentProfileService(db).requireQualified( + agent.companyId, + readNonEmptyString(runnerAdapterConfig.agentCoreProfileId) ?? "", + "aws_bedrock_agentcore_harness", + ) + : null; + if (managedProfile) { + const rawApiKeyBinding = parseObject( + runnerAdapterConfig.env, + ).ANTHROPIC_API_KEY; + const boundSecretId = + typeof rawApiKeyBinding === "object" + && rawApiKeyBinding !== null + ? readNonEmptyString( + (rawApiKeyBinding as Record).secretId, + ) + : null; + if (boundSecretId !== managedProfile.apiKeySecretId) { + throw new ConfigurationIncompleteFailure( + "configuration incomplete: Claude Managed profile API key is not bound at env.ANTHROPIC_API_KEY", + { + configurationIncomplete: { + reason: "managed_agent_profile_secret_binding_mismatch", + companyId: agent.companyId, + agentId: agent.id, + profileId: managedProfile.id, + requiredEnvKeys: ["ANTHROPIC_API_KEY"], + }, + }, + ); + } + } const executionMode = issueRef.workMode === "planning" && !acceptedPlanContinuationWake ? ("plan" as const) @@ -19840,6 +19914,8 @@ export function heartbeatService( ...resolvePaperclipRunnerNativeProviderInput({ backend: nativeRuntimeResolution.profile.backend, adapterConfig: agent.adapterConfig, + managedProfile, + agentCoreProfile, }), lifecyclePolicy: effectiveLifecyclePolicy, interactionResponses, diff --git a/server/src/services/index.ts b/server/src/services/index.ts index b36b803532..ecaf7a8e05 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -200,3 +200,13 @@ export { } from "./codex-auth-reconciliation.js"; export { reconcilePersistedRuntimeServicesOnStartup, restartDesiredRuntimeServicesOnStartup } from "./workspace-runtime.js"; export { createStorageServiceFromConfig, getStorageService } from "../storage/index.js"; +export { + managedAgentProfileService, + CLAUDE_MANAGED_BETA_VERSION, + type ManagedAgentProfileInput, +} from "./managed-agent-profiles.js"; +export { + remoteAgentProfileService, + type RemoteAgentProfileInput, + type RemoteAgentService, +} from "./remote-agent-profiles.js"; diff --git a/server/src/services/managed-agent-profiles.ts b/server/src/services/managed-agent-profiles.ts new file mode 100644 index 0000000000..0b8fe22d8c --- /dev/null +++ b/server/src/services/managed-agent-profiles.ts @@ -0,0 +1,254 @@ +import { and, asc, eq } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { companySecrets, managedAgentProfiles } from "@paperclipai/db"; + +import { conflict, notFound, unprocessable } from "../errors.js"; +import { + assertClaudeManagedQualification, + assertProfileMetadataContainsNoSecrets, + CLAUDE_MANAGED_QUALIFIED_MODEL, + computeQualifiedProfileRevision, + isQualifiedProfileRevision, +} from "./provider-profile-qualification.js"; + +export const CLAUDE_MANAGED_BETA_VERSION = "managed-agents-2026-04-01" as const; +const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; +const ANTHROPIC_AGENT_VERSION_RE = /^[1-9][0-9]*$/; +const ANTHROPIC_AGENT_VERSION_MAX = 2_147_483_647; + +export interface ManagedAgentProfileInput { + profileKey: string; + displayName: string; + anthropicAgentId: string; + agentVersion: string; + environmentId: string; + defaultModel: string; + defaultMaxListCostUsd: number; + apiKeySecretId: string; + enabled: boolean; + retentionAcknowledged: boolean; + qualification?: Record; +} + +function required(value: string, label: string): string { + const normalized = value.trim(); + if (!normalized) throw unprocessable(`${label} is required`); + return normalized; +} + +function toCents(value: number): number { + const cents = Math.round(value * 100); + if (!Number.isSafeInteger(cents) || cents <= 0) { + throw unprocessable("Managed Agent default spend ceiling must be positive"); + } + return cents; +} + +function requiredUuid(value: string, label: string): string { + const normalized = required(value, label); + if (!UUID_RE.test(normalized)) throw unprocessable(`${label} must be a UUID`); + return normalized; +} + +function requiredAgentVersion(value: string): string { + const normalized = required(value, "Agent version"); + const numeric = Number(normalized); + if ( + !ANTHROPIC_AGENT_VERSION_RE.test(normalized) + || !Number.isSafeInteger(numeric) + || numeric > ANTHROPIC_AGENT_VERSION_MAX + ) { + throw unprocessable("Agent version must be a canonical positive 32-bit integer"); + } + return normalized; +} + +interface ManagedAgentProfileRevisionInput { + anthropicAgentId: string; + agentVersion: string; + environmentId: string; + betaVersion: string; + retentionAcknowledged: boolean; + qualification: Record; +} + +export function computeManagedAgentProfileRevision( + input: ManagedAgentProfileRevisionInput, +): string { + return computeQualifiedProfileRevision({ + service: "anthropic_managed_agents", + anthropicAgentId: input.anthropicAgentId, + agentVersion: input.agentVersion, + environmentId: input.environmentId, + betaVersion: input.betaVersion, + retentionAcknowledged: input.retentionAcknowledged, + qualification: input.qualification, + }); +} + +function assertQualifiedRevisionUnchanged( + existing: typeof managedAgentProfiles.$inferSelect | null, + revision: string | null, +): void { + if (!existing?.qualifiedAt) return; + if (!revision || existing.qualifiedRevision !== revision) { + throw conflict("Qualified Managed Agent configuration revision is immutable; create a new profile key"); + } +} + +export function managedAgentProfileService(db: Db) { + async function list(companyId: string) { + return db + .select() + .from(managedAgentProfiles) + .where(eq(managedAgentProfiles.companyId, companyId)) + .orderBy(asc(managedAgentProfiles.displayName)); + } + + async function get(companyId: string, profileIdOrKey: string) { + const isUuid = UUID_RE.test(profileIdOrKey); + const rows = await db + .select() + .from(managedAgentProfiles) + .where(and( + eq(managedAgentProfiles.companyId, companyId), + isUuid + ? eq(managedAgentProfiles.id, profileIdOrKey) + : eq(managedAgentProfiles.profileKey, profileIdOrKey), + )) + .limit(1); + return rows[0] ?? null; + } + + async function getByProfileKey(companyId: string, profileKey: string) { + const rows = await db + .select() + .from(managedAgentProfiles) + .where(and( + eq(managedAgentProfiles.companyId, companyId), + eq(managedAgentProfiles.profileKey, profileKey), + )) + .limit(1); + return rows[0] ?? null; + } + + async function requireQualified(companyId: string, profileIdOrKey: string) { + const profile = await get(companyId, profileIdOrKey); + if (!profile) throw notFound("Managed Agent profile not found"); + if (!profile.enabled || !profile.retentionAcknowledged || !profile.qualifiedAt) { + throw conflict("Managed Agent profile is not enabled and qualified"); + } + if (profile.defaultModel !== CLAUDE_MANAGED_QUALIFIED_MODEL) { + throw conflict("Managed Agent profile model is not qualified"); + } + try { + requiredAgentVersion(profile.agentVersion); + } catch { + throw conflict("Managed Agent profile version is not qualified"); + } + try { + assertClaudeManagedQualification(profile.qualification, { required: true }); + } catch { + throw conflict("Managed Agent profile qualification attestation is invalid"); + } + const currentRevision = computeManagedAgentProfileRevision(profile); + if ( + !isQualifiedProfileRevision(profile.qualifiedRevision) + || profile.qualifiedRevision !== currentRevision + ) { + throw conflict("Managed Agent profile configuration does not match its qualified revision"); + } + return profile; + } + + async function upsert(companyId: string, input: ManagedAgentProfileInput) { + const profileKey = required(input.profileKey, "Profile key"); + if (UUID_RE.test(profileKey)) { + throw unprocessable("Profile key must not be UUID-shaped"); + } + const displayName = required(input.displayName, "Display name"); + const anthropicAgentId = required(input.anthropicAgentId, "Anthropic Agent ID"); + const agentVersion = requiredAgentVersion(input.agentVersion); + const environmentId = required(input.environmentId, "Anthropic Environment ID"); + const defaultModel = required(input.defaultModel, "Default model"); + if (defaultModel !== CLAUDE_MANAGED_QUALIFIED_MODEL) { + throw unprocessable(`Managed Agent model must be ${CLAUDE_MANAGED_QUALIFIED_MODEL}`); + } + const defaultMaxListCostCents = toCents(input.defaultMaxListCostUsd); + const apiKeySecretId = requiredUuid(input.apiKeySecretId, "Managed Agent API-key secret ID"); + const qualification = structuredClone(input.qualification ?? {}); + assertProfileMetadataContainsNoSecrets({ + profileKey, + displayName, + anthropicAgentId, + agentVersion, + environmentId, + defaultModel, + }, "Managed Agent profile"); + if (input.enabled && !input.retentionAcknowledged) { + throw unprocessable("Enabling Managed Agents requires the retention acknowledgement"); + } + const qualificationAttested = assertClaudeManagedQualification(qualification, { + required: input.enabled, + }); + + const existing = await getByProfileKey(companyId, profileKey); + const qualifiedRevision = qualificationAttested + ? computeManagedAgentProfileRevision({ + anthropicAgentId, + agentVersion, + environmentId, + betaVersion: CLAUDE_MANAGED_BETA_VERSION, + retentionAcknowledged: input.retentionAcknowledged, + qualification, + }) + : null; + assertQualifiedRevisionUnchanged(existing, qualifiedRevision); + + const secret = await db + .select({ id: companySecrets.id }) + .from(companySecrets) + .where(and( + eq(companySecrets.companyId, companyId), + eq(companySecrets.id, apiKeySecretId), + eq(companySecrets.scope, "company"), + eq(companySecrets.status, "active"), + )) + .limit(1); + if (!secret[0]) throw unprocessable("Managed Agent API-key secret reference is invalid"); + + const values = { + companyId, + profileKey, + displayName, + service: "anthropic_managed_agents", + anthropicAgentId, + agentVersion, + environmentId, + betaVersion: CLAUDE_MANAGED_BETA_VERSION, + defaultModel, + defaultMaxListCostCents, + apiKeySecretId, + enabled: input.enabled, + retentionAcknowledged: input.retentionAcknowledged, + qualification, + qualifiedAt: existing?.qualifiedAt ?? (input.enabled && qualificationAttested ? new Date() : null), + qualifiedRevision: + existing?.qualifiedAt || (input.enabled && qualificationAttested) + ? qualifiedRevision + : null, + updatedAt: new Date(), + } as const; + const [row] = await db + .insert(managedAgentProfiles) + .values(values) + .onConflictDoUpdate({ + target: [managedAgentProfiles.companyId, managedAgentProfiles.profileKey], + set: values, + }) + .returning(); + return row!; + } + + return { list, get, requireQualified, upsert }; +} diff --git a/server/src/services/native-runtime/native-codex-runner.integration.test.ts b/server/src/services/native-runtime/native-codex-runner.integration.test.ts index eb62b42473..4241625dc9 100644 --- a/server/src/services/native-runtime/native-codex-runner.integration.test.ts +++ b/server/src/services/native-runtime/native-codex-runner.integration.test.ts @@ -73,7 +73,10 @@ function ensureRunnerTestBinaries(): void { ], { cwd: runnerWorkspace, stdio: "inherit", - timeout: 180_000, + // A clean release build includes every qualified managed-provider SDK. + // Keep this below the CI job deadline while allowing that cold compile to + // finish on GitHub-hosted runners. + timeout: 600_000, }); } @@ -102,7 +105,7 @@ describeEmbeddedPostgres("native Codex server vertical slice", () => { setupRunnerPrpWebSocketServer(server, { apiUrl: `http://127.0.0.1:${address.port}`, }); - }, 240_000); + }, 660_000); afterAll(async () => { runnerPrpWebSocketInternals.resetForTests(); diff --git a/server/src/services/native-runtime/native-execution-input.ts b/server/src/services/native-runtime/native-execution-input.ts index 64988a37b4..04a7f9eea9 100644 --- a/server/src/services/native-runtime/native-execution-input.ts +++ b/server/src/services/native-runtime/native-execution-input.ts @@ -44,12 +44,26 @@ export function buildNativeExecutionInput(input: { branchName: string | null; }; normalizedSessionId: string | null; - provider?: "codex" | "opencode" | "acpx"; + provider?: "codex" | "opencode" | "claude_managed" | "aws_agentcore" | "acpx"; acpxAgent?: NativeAcpxAgent; codexApprovalPolicy?: NativeCodexApprovalPolicy; opencodePermissionMode?: NativeOpenCodePermissionMode; acpxPermissionMode?: NativeAcpxPermissionMode; model?: string | null; + managedProfile?: Extract< + NativeExecutionInputV4["provider"], + { kind: "claude_managed" } + >["managedProfile"]; + maxSessionListCostUsd?: number; + agentCoreProfile?: Extract< + NativeExecutionInputV4["provider"], + { kind: "aws_agentcore" } + >["agentCoreProfile"]; + maxEstimatedSessionCostUsd?: number; + invocationLimits?: Extract< + NativeExecutionInputV4["provider"], + { kind: "aws_agentcore" } + >["invocationLimits"]; lifecyclePolicy?: NativeExecutionInputV4["session"]["lifecyclePolicy"]; executionMode?: "default" | "plan"; planningContext?: NativePlanningContext | null; @@ -108,13 +122,32 @@ export function buildNativeExecutionInput(input: { normalizedSessionId: input.normalizedSessionId, driverKind: input.provider === "opencode" ? "opencode_server" + : input.provider === "claude_managed" + ? "claude_managed_agents_api" + : input.provider === "aws_agentcore" + ? "aws_agentcore_harness_api" : input.provider === "acpx" - ? "acpx_runtime" - : "codex_app_server", + ? "acpx_runtime" + : "codex_app_server", protocolVersion: 1, lifecyclePolicy: input.lifecyclePolicy ?? { mode: "per_turn", idleTimeoutMs: null }, }, - provider: input.provider === "acpx" + provider: input.provider === "claude_managed" + ? { + kind: "claude_managed", + model: input.model, + managedProfile: input.managedProfile, + maxSessionListCostUsd: input.maxSessionListCostUsd, + } + : input.provider === "aws_agentcore" + ? { + kind: "aws_agentcore", + model: input.model, + agentCoreProfile: input.agentCoreProfile, + maxEstimatedSessionCostUsd: input.maxEstimatedSessionCostUsd, + invocationLimits: input.invocationLimits, + } + : input.provider === "acpx" ? { kind: "acpx", agent: acpxProfile!.agent, diff --git a/server/src/services/native-runtime/native-session-executor.test.ts b/server/src/services/native-runtime/native-session-executor.test.ts index d0f02521e7..386753bf15 100644 --- a/server/src/services/native-runtime/native-session-executor.test.ts +++ b/server/src/services/native-runtime/native-session-executor.test.ts @@ -298,17 +298,36 @@ describe("remote provider pack manifest", () => { .update("\n") .update(payload.distDigest) .digest("hex")}`; - await writeFile( - join(root, "provider-pack.json"), - JSON.stringify({ + const writeManifest = async () => + writeFile( + join(root, "provider-pack.json"), + JSON.stringify({ schema: "paperclip-runner/remote-provider-pack/v1", digest: `sha256:${createHash("sha256").update(canonical(payload)).digest("hex")}`, payload, - }), - ); + }), + ); + await writeManifest(); expect(readRemoteProviderPackManifest(root).payload.pins.opencode).toBe( "1.18.17", ); + for (const [artifactName, substituteName] of [ + ["nodeCommand", "productionLock"], + ["opencodeExecutable", "opencodeCommand"], + ["opencodeProxy", "acpxSidecar"], + ["acpxSidecar", "opencodeProxy"], + ] as const) { + const original = payload.artifacts[artifactName]; + payload.artifacts[artifactName] = { + ...payload.artifacts[substituteName], + }; + await writeManifest(); + expect(() => readRemoteProviderPackManifest(root)).toThrow( + /path must be/, + ); + payload.artifacts[artifactName] = original; + } + await writeManifest(); await writeFile( join(root, "dist", "cli", "opencode-app-server-proxy.js"), "tampered\n", diff --git a/server/src/services/native-runtime/native-session-executor.ts b/server/src/services/native-runtime/native-session-executor.ts index da04207286..960176371b 100644 --- a/server/src/services/native-runtime/native-session-executor.ts +++ b/server/src/services/native-runtime/native-session-executor.ts @@ -2753,6 +2753,8 @@ export async function executePaperclipNativeSession(input: { if ( input.execution.provider.kind !== "codex" && input.execution.provider.kind !== "opencode" + && input.execution.provider.kind !== "claude_managed" + && input.execution.provider.kind !== "aws_agentcore" && input.execution.provider.kind !== "acpx" ) { throw new Error("paperclip_runner_provider_unsupported"); @@ -3962,6 +3964,14 @@ const REMOTE_PROVIDER_PACK_PROFILE_DIGESTS = { codex: "sha256:94049b3e3c3aee87de62703786e4fa81d031d7bd979f99bdf516d84f28791a79", } as const; +const REMOTE_PROVIDER_PACK_ARTIFACT_PATHS = { + nodeCommand: "node_modules/node/bin/node", + productionLock: "pnpm-lock.yaml", + opencodeCommand: "node_modules/.bin/opencode", + opencodeExecutable: "node_modules/opencode-ai/bin/opencode.exe", + opencodeProxy: "dist/cli/opencode-app-server-proxy.js", + acpxSidecar: "dist/cli/acpx-runtime-sidecar.js", +} as const; type RemoteProviderPackManifest = { schema: typeof REMOTE_PROVIDER_PACK_SCHEMA; @@ -4072,18 +4082,23 @@ export function readRemoteProviderPackManifest( ); } const artifactEntries = [ - ["provider Node", payload.artifacts?.nodeCommand], - ["production lockfile", payload.artifacts?.productionLock], - ["OpenCode command", payload.artifacts?.opencodeCommand], - ["OpenCode executable", payload.artifacts?.opencodeExecutable], - ["OpenCode proxy", payload.artifacts?.opencodeProxy], - ["ACPX sidecar", payload.artifacts?.acpxSidecar], + ["provider Node", payload.artifacts?.nodeCommand, REMOTE_PROVIDER_PACK_ARTIFACT_PATHS.nodeCommand], + ["production lockfile", payload.artifacts?.productionLock, REMOTE_PROVIDER_PACK_ARTIFACT_PATHS.productionLock], + ["OpenCode command", payload.artifacts?.opencodeCommand, REMOTE_PROVIDER_PACK_ARTIFACT_PATHS.opencodeCommand], + ["OpenCode executable", payload.artifacts?.opencodeExecutable, REMOTE_PROVIDER_PACK_ARTIFACT_PATHS.opencodeExecutable], + ["OpenCode proxy", payload.artifacts?.opencodeProxy, REMOTE_PROVIDER_PACK_ARTIFACT_PATHS.opencodeProxy], + ["ACPX sidecar", payload.artifacts?.acpxSidecar, REMOTE_PROVIDER_PACK_ARTIFACT_PATHS.acpxSidecar], ] as const; - for (const [label, artifact] of artifactEntries) { + for (const [label, artifact, expectedPath] of artifactEntries) { const artifactPath = providerPackRelativePath( artifact?.path, `${label} path`, ); + if (artifactPath !== expectedPath) { + throw new Error( + `runner_remote_provider_artifact_incompatible: ${label} path must be ${expectedPath}`, + ); + } if ( typeof artifact?.sha256 !== "string" || sha256File(resolve(packRoot, artifactPath)) !== artifact.sha256 @@ -6010,9 +6025,13 @@ export async function createRunnerdBackend(input: { ? "codex" : input.execution.provider.kind === "opencode" ? "opencode" - : input.execution.provider.kind === "acpx" - ? "acpx" - : undefined, + : input.execution.provider.kind === "claude_managed" + ? "claude_managed" + : input.execution.provider.kind === "aws_agentcore" + ? "aws_agentcore" + : input.execution.provider.kind === "acpx" + ? "acpx" + : undefined, ...(input.execution.provider.kind === "acpx" ? { acpxAgent: input.execution.provider.agent, @@ -6035,26 +6054,64 @@ export async function createRunnerdBackend(input: { opencodePermissionMode: input.execution.provider.permissionMode, } : {}), + ...(input.execution.provider.kind === "claude_managed" + ? { + managedProfile: { + ...input.execution.provider.managedProfile, + maxSessionListCostUsd: + input.execution.provider.maxSessionListCostUsd, + model: input.execution.provider.model, + }, + } + : {}), + ...(input.execution.provider.kind === "aws_agentcore" + ? { + agentCoreProfile: { + ...input.execution.provider.agentCoreProfile, + maxEstimatedSessionCostUsd: + input.execution.provider.maxEstimatedSessionCostUsd, + maxIterations: + input.execution.provider.invocationLimits.maxIterations, + maxOutputTokens: + input.execution.provider.invocationLimits.maxOutputTokens, + timeoutSeconds: + input.execution.provider.invocationLimits.timeoutSeconds, + model: input.execution.provider.model, + }, + } + : {}), ...(expectedProviderPackManifest && stagedRemoteProviderPackRoot ? { providerNodeCommand: posix.join( stagedRemoteProviderPackRoot, expectedProviderPackManifest.payload.artifacts.nodeCommand.path, ), + providerNodeCommandSha256: + expectedProviderPackManifest.payload.artifacts.nodeCommand.sha256, + providerPackAuthorityDigest: + expectedProviderPackManifest.digest, opencodeCommand: posix.join( stagedRemoteProviderPackRoot, expectedProviderPackManifest.payload.artifacts .opencodeExecutable.path, ), + opencodeCommandSha256: + expectedProviderPackManifest.payload.artifacts + .opencodeExecutable.sha256, opencodeProxyPath: posix.join( stagedRemoteProviderPackRoot, expectedProviderPackManifest.payload.artifacts.opencodeProxy .path, ), + opencodeProxySha256: + expectedProviderPackManifest.payload.artifacts.opencodeProxy + .sha256, acpxSidecarPath: posix.join( stagedRemoteProviderPackRoot, expectedProviderPackManifest.payload.artifacts.acpxSidecar.path, ), + acpxSidecarSha256: + expectedProviderPackManifest.payload.artifacts.acpxSidecar.sha256, } : {}), stateDirectory: root, diff --git a/server/src/services/native-runtime/native-session-resume.test.ts b/server/src/services/native-runtime/native-session-resume.test.ts index 206aaf3be6..ff05228863 100644 --- a/server/src/services/native-runtime/native-session-resume.test.ts +++ b/server/src/services/native-runtime/native-session-resume.test.ts @@ -221,6 +221,48 @@ describe("buildNativeExecutionInput wake projection", () => { model: "claude-sonnet-5", acpxPermissionMode: "deny-all", }); + const claudeManaged = buildNativeExecutionInput({ + ...common, + provider: "claude_managed", + model: "claude-sonnet-5", + managedProfile: { + profileId: "managed-profile", + anthropicAgentId: "agent-remote", + agentVersion: "7", + environmentId: "environment-remote", + betaVersion: "managed-agents-2026-04-01", + }, + maxSessionListCostUsd: 0.75, + }); + const agentCore = buildNativeExecutionInput({ + ...common, + provider: "aws_agentcore", + model: "global.anthropic.claude-sonnet-4-6", + agentCoreProfile: { + profileId: "agentcore-profile", + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/h-1", + harnessVersion: "3", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/e-1", + endpointQualifier: "prod", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/r-1", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/m-1", + memoryId: "m-1", + invocationRoleArn: "arn:aws:iam::123456789012:role/invoke", + contextBucket: "paperclip-context", + contextPrefix: "runner/", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/key-1", + qualificationRevision: "aws-agentcore-harness-v1", + eventExpiryDays: 90, + }, + maxEstimatedSessionCostUsd: 1.25, + invocationLimits: { + maxIterations: 8, + maxOutputTokens: 4_096, + timeoutSeconds: 300, + }, + }); const defaultOpenCode = buildNativeExecutionInput({ ...common, provider: "opencode", @@ -244,6 +286,25 @@ describe("buildNativeExecutionInput wake projection", () => { schema: "paperclip.native-execution-input.v4", provider: { kind: "acpx", permissionMode: "deny-all" }, }); + expect(claudeManaged).toMatchObject({ + session: { driverKind: "claude_managed_agents_api" }, + provider: { + kind: "claude_managed", + managedProfile: { profileId: "managed-profile" }, + maxSessionListCostUsd: 0.75, + }, + }); + expect(agentCore).toMatchObject({ + session: { driverKind: "aws_agentcore_harness_api" }, + provider: { + kind: "aws_agentcore", + agentCoreProfile: { + profileId: "agentcore-profile", + eventExpiryDays: 90, + }, + maxEstimatedSessionCostUsd: 1.25, + }, + }); expect(defaultOpenCode).toMatchObject({ provider: { kind: "opencode", permissionMode: "ask" }, }); @@ -254,7 +315,7 @@ describe("buildNativeExecutionInput wake projection", () => { permissionMode: "approve-reads", }, }); - expect(JSON.stringify([codex, opencode, acpx])) + expect(JSON.stringify([codex, opencode, claudeManaged, agentCore, acpx])) .not.toMatch(/OPENAI_API_KEY|ANTHROPIC_API_KEY|AWS_SECRET_ACCESS_KEY|PAPERCLIP_API_KEY/); }); diff --git a/server/src/services/native-runtime/provider-profile.ts b/server/src/services/native-runtime/provider-profile.ts index b5260dff6d..00f5a8b2f2 100644 --- a/server/src/services/native-runtime/provider-profile.ts +++ b/server/src/services/native-runtime/provider-profile.ts @@ -4,10 +4,15 @@ import { resolvePaperclipRunnerPermissionMode, type PaperclipRunnerProvider, } from "@paperclipai/adapter-utils"; +import { + AGENTCORE_QUALIFIED_MODEL, + CLAUDE_MANAGED_QUALIFIED_MODEL, +} from "../provider-profile-qualification.js"; export const QUALIFIED_OPENCODE_RUNNER_VERSION = "1.18.17" as const; export const DEFAULT_OPENCODE_RUNNER_MODEL = "openrouter/deepseek/deepseek-v4-flash-0731" as const; +export const CLAUDE_MANAGED_BETA_VERSION = "managed-agents-2026-04-01" as const; export const QUALIFIED_ACPX_RUNNER_MODELS = { claude: "claude-sonnet-5", @@ -28,6 +33,20 @@ export type PaperclipRunnerProviderProfile = backend: "opencode_server"; model: string; } + | { + provider: "claude_managed"; + backend: "claude_managed_agents_api"; + managedProfileId: string; + model: string | null; + maxSessionListCostUsd: number | null; + } + | { + provider: "aws_agentcore"; + backend: "aws_agentcore_harness_api"; + agentCoreProfileId: string; + model: string | null; + maxEstimatedSessionCostUsd: number | null; + } | { provider: "acpx"; backend: "acpx_runtime"; @@ -46,6 +65,46 @@ export type PaperclipRunnerNativeProviderInput = model: string; opencodePermissionMode: "allow" | "ask" | "deny"; } + | { + provider: "claude_managed"; + model: string; + managedProfile: { + profileId: string; + anthropicAgentId: string; + agentVersion: string; + environmentId: string; + betaVersion: typeof CLAUDE_MANAGED_BETA_VERSION; + }; + maxSessionListCostUsd: number; + } + | { + provider: "aws_agentcore"; + model: string; + agentCoreProfile: { + profileId: string; + region: string; + accountId: string; + harnessArn: string; + harnessVersion: string; + endpointArn: string; + endpointQualifier: string; + agentRuntimeArn: string; + memoryArn: string; + memoryId: string; + invocationRoleArn: string; + contextBucket: string; + contextPrefix: string; + contextKmsKeyArn: string; + qualificationRevision: string; + eventExpiryDays: 90; + }; + maxEstimatedSessionCostUsd: number; + invocationLimits: { + maxIterations: number; + maxOutputTokens: number; + timeoutSeconds: number; + }; + } | { provider: "acpx"; model: string; @@ -75,11 +134,47 @@ function optionalString(value: unknown): string | null { : null; } +function positiveNumberOrNull( + value: unknown, + code: string, + message: string, +): number | null { + if (value === undefined || value === null || value === "") return null; + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed <= 0) { + throw new PaperclipRunnerProviderProfileError(code, message); + } + return parsed; +} + +function boundedPositiveInteger( + value: unknown, + fallback: number, + maximum: number, + code: string, + label: string, +): number { + if (value === undefined || value === null || value === "") return fallback; + if ( + typeof value !== "number" + || !Number.isSafeInteger(value) + || value <= 0 + || value > maximum + ) { + throw new PaperclipRunnerProviderProfileError( + code, + `${label} must be an integer between 1 and ${maximum}.`, + ); + } + return value; +} + function assertPermissionMode( provider: PaperclipRunnerProvider, config: Record, ): void { const capability = PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES[provider]; + if (!capability.configurable) return; const configured = config[capability.configKey]; if ( configured !== undefined @@ -92,6 +187,108 @@ function assertPermissionMode( } } +/** + * Rebind a persisted Claude Managed run only to the still-qualified profile + * and the profile's current company secret. The secret itself may rotate, but + * the agent configuration must rotate its binding to the same profile-owned + * secret before recovery can continue. + */ +export function assertManagedProfileRecoveryBinding(input: { + adapterConfig: unknown; + snapshot: { + profileId: string; + anthropicAgentId: string; + agentVersion: string; + environmentId: string; + betaVersion: string; + }; + stored: { + id: string; + anthropicAgentId: string; + agentVersion: string; + environmentId: string; + betaVersion: string; + apiKeySecretId: string; + }; +}): void { + const { snapshot, stored } = input; + if ( + snapshot.profileId !== stored.id + || snapshot.anthropicAgentId !== stored.anthropicAgentId + || snapshot.agentVersion !== stored.agentVersion + || snapshot.environmentId !== stored.environmentId + || snapshot.betaVersion !== stored.betaVersion + ) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_recovery_identity_mismatch", + "The persisted Claude Managed identity no longer matches its qualified profile.", + ); + } + const rawBinding = asRecord(asRecord(input.adapterConfig).env).ANTHROPIC_API_KEY; + const boundSecretId = asRecord(rawBinding).secretId; + if (boundSecretId !== stored.apiKeySecretId) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_recovery_secret_mismatch", + "The persisted Claude Managed run is not bound to its profile's current API-key secret.", + ); + } +} + +/** Revalidate the operator's AWS profile revocation and immutable identity on recovery. */ +export function assertAgentCoreProfileRecoveryBinding(input: { + snapshot: { + profileId: string; + region: string; + accountId: string; + harnessArn: string; + harnessVersion: string; + endpointArn: string; + endpointQualifier: string; + agentRuntimeArn: string; + memoryArn: string; + memoryId: string; + invocationRoleArn: string; + contextBucket: string; + contextPrefix: string; + contextKmsKeyArn: string; + qualificationRevision: string; + eventExpiryDays: number; + }; + stored: { + id: string; + configuration: Record; + }; +}): void { + const { snapshot, stored } = input; + const configuration = asRecord(stored.configuration); + const fields = [ + "region", + "accountId", + "harnessArn", + "harnessVersion", + "endpointArn", + "endpointQualifier", + "agentRuntimeArn", + "memoryArn", + "memoryId", + "invocationRoleArn", + "contextBucket", + "contextPrefix", + "contextKmsKeyArn", + "qualificationRevision", + "eventExpiryDays", + ] as const; + if ( + snapshot.profileId !== stored.id + || fields.some((field) => snapshot[field] !== configuration[field]) + ) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_recovery_identity_mismatch", + "The persisted AWS AgentCore identity no longer matches its qualified profile.", + ); + } +} + /** * Resolve the immutable provider identity used for a fresh Paperclip Runner * selection. The persisted adapterConfig is the authority; runtimeConfig is @@ -106,7 +303,7 @@ export function resolvePaperclipRunnerProviderProfile( if (!isPaperclipRunnerProvider(candidate)) { throw new PaperclipRunnerProviderProfileError( "paperclip_runner_provider_unsupported", - "Paperclip Runner provider must be codex, opencode, or acpx.", + "Paperclip Runner provider must be Codex, OpenCode, Claude Managed, AWS AgentCore, or ACPX.", ); } @@ -134,6 +331,72 @@ export function resolvePaperclipRunnerProviderProfile( }; } + if (candidate === "claude_managed") { + const managedProfileId = optionalString(config.managedProfileId); + if (!managedProfileId) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_profile_required", + "Paperclip Runner Claude Managed requires a company managed-agent profile.", + ); + } + if (config.managedAgentsRetentionAcknowledged !== true) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_retention_required", + "Paperclip Runner Claude Managed requires acknowledgement of stateful beta retention.", + ); + } + if (model !== null && model !== CLAUDE_MANAGED_QUALIFIED_MODEL) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_model_unqualified", + `The Claude Managed profile requires exact model ${CLAUDE_MANAGED_QUALIFIED_MODEL}.`, + ); + } + return { + provider: "claude_managed", + backend: "claude_managed_agents_api", + managedProfileId, + model, + maxSessionListCostUsd: positiveNumberOrNull( + config.maxSessionListCostUsd, + "paperclip_runner_claude_managed_spend_cap_invalid", + "Paperclip Runner Claude Managed requires a positive session spend ceiling when overridden.", + ), + }; + } + + if (candidate === "aws_agentcore") { + const agentCoreProfileId = optionalString(config.agentCoreProfileId); + if (!agentCoreProfileId) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_profile_required", + "Paperclip Runner AWS AgentCore requires a company remote-agent profile.", + ); + } + if (config.agentCoreRetentionAcknowledged !== true) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_retention_required", + "Paperclip Runner AWS AgentCore requires acknowledgement of 90-day Memory retention.", + ); + } + if (model !== null && model !== AGENTCORE_QUALIFIED_MODEL) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_model_unqualified", + `The AWS AgentCore profile requires exact model ${AGENTCORE_QUALIFIED_MODEL}.`, + ); + } + return { + provider: "aws_agentcore", + backend: "aws_agentcore_harness_api", + agentCoreProfileId, + model, + maxEstimatedSessionCostUsd: positiveNumberOrNull( + config.maxEstimatedSessionCostUsd, + "paperclip_runner_aws_agentcore_spend_cap_invalid", + "Paperclip Runner AWS AgentCore requires a positive estimated session spend ceiling when overridden.", + ), + }; + } + const acpxAgent = config.acpxAgent; if (acpxAgent !== "claude" && acpxAgent !== "codex") { throw new PaperclipRunnerProviderProfileError( @@ -157,14 +420,29 @@ export function resolvePaperclipRunnerProviderProfile( } /** - * Project the operator-owned adapter configuration into the closed native - * execution descriptor. The selected backend must still match the provider; - * an adapter edit cannot silently change a run that already persisted its - * runtime driver. + * Project the operator-owned adapter configuration and a qualified stored + * profile into the closed native execution descriptor. Editable adapter + * configuration can select a stored profile, but it cannot replace that + * profile's immutable remote resource identity. */ export function resolvePaperclipRunnerNativeProviderInput(input: { backend: PaperclipRunnerProviderProfile["backend"]; adapterConfig: unknown; + managedProfile?: { + id: string; + profileKey: string; + anthropicAgentId: string; + agentVersion: string; + environmentId: string; + betaVersion: string; + defaultModel: string; + defaultMaxListCostCents: number; + } | null; + agentCoreProfile?: { + id: string; + profileKey: string; + configuration: Record; + } | null; }): PaperclipRunnerNativeProviderInput { const config = asRecord(input.adapterConfig); const profile = resolvePaperclipRunnerProviderProfile(config); @@ -195,6 +473,157 @@ export function resolvePaperclipRunnerNativeProviderInput(input: { ) as "approve-all" | "approve-reads" | "deny-all", }; } + if (profile.provider === "claude_managed") { + const stored = input.managedProfile; + if ( + !stored + || ( + profile.managedProfileId !== stored.id + && profile.managedProfileId !== stored.profileKey + ) + ) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_profile_mismatch", + "The qualified Claude Managed profile does not match the adapter selection.", + ); + } + if (stored.betaVersion !== CLAUDE_MANAGED_BETA_VERSION) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_beta_unqualified", + "The Claude Managed profile beta version is not qualified.", + ); + } + const model = profile.model ?? optionalString(stored.defaultModel); + const maxSessionListCostUsd = profile.maxSessionListCostUsd + ?? stored.defaultMaxListCostCents / 100; + if (!model) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_model_invalid", + "The Claude Managed profile requires a model.", + ); + } + if (model !== CLAUDE_MANAGED_QUALIFIED_MODEL) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_model_unqualified", + `The Claude Managed profile requires exact model ${CLAUDE_MANAGED_QUALIFIED_MODEL}.`, + ); + } + if (!Number.isFinite(maxSessionListCostUsd) || maxSessionListCostUsd <= 0) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_claude_managed_spend_cap_invalid", + "The Claude Managed profile requires a positive session spend ceiling.", + ); + } + return { + provider: "claude_managed", + model, + managedProfile: { + profileId: stored.id, + anthropicAgentId: stored.anthropicAgentId, + agentVersion: stored.agentVersion, + environmentId: stored.environmentId, + betaVersion: CLAUDE_MANAGED_BETA_VERSION, + }, + maxSessionListCostUsd, + }; + } + if (profile.provider === "aws_agentcore") { + const stored = input.agentCoreProfile; + if ( + !stored + || ( + profile.agentCoreProfileId !== stored.id + && profile.agentCoreProfileId !== stored.profileKey + ) + ) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_profile_mismatch", + "The qualified AWS AgentCore profile does not match the adapter selection.", + ); + } + const remote = asRecord(stored.configuration); + const required = (key: string): string => { + const value = optionalString(remote[key]); + if (!value) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_profile_invalid", + `The qualified AWS AgentCore profile is missing ${key}.`, + ); + } + return value; + }; + if (remote.eventExpiryDays !== 90) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_retention_unqualified", + "The qualified AWS AgentCore profile must retain Memory events for exactly 90 days.", + ); + } + const maxEstimatedSessionCostUsd = profile.maxEstimatedSessionCostUsd + ?? positiveNumberOrNull( + remote.defaultMaxEstimatedSessionCostUsd, + "paperclip_runner_aws_agentcore_spend_cap_invalid", + "The AWS AgentCore profile requires a positive estimated session spend ceiling.", + ); + if (maxEstimatedSessionCostUsd === null) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_spend_cap_invalid", + "The AWS AgentCore profile requires a positive estimated session spend ceiling.", + ); + } + const model = profile.model ?? required("defaultModel"); + if (model !== AGENTCORE_QUALIFIED_MODEL) { + throw new PaperclipRunnerProviderProfileError( + "paperclip_runner_aws_agentcore_model_unqualified", + `The AWS AgentCore profile requires exact model ${AGENTCORE_QUALIFIED_MODEL}.`, + ); + } + return { + provider: "aws_agentcore", + model, + agentCoreProfile: { + profileId: stored.id, + region: required("region"), + accountId: required("accountId"), + harnessArn: required("harnessArn"), + harnessVersion: required("harnessVersion"), + endpointArn: required("endpointArn"), + endpointQualifier: required("endpointQualifier"), + agentRuntimeArn: required("agentRuntimeArn"), + memoryArn: required("memoryArn"), + memoryId: required("memoryId"), + invocationRoleArn: required("invocationRoleArn"), + contextBucket: required("contextBucket"), + contextPrefix: required("contextPrefix"), + contextKmsKeyArn: required("contextKmsKeyArn"), + qualificationRevision: required("qualificationRevision"), + eventExpiryDays: 90, + }, + maxEstimatedSessionCostUsd, + invocationLimits: { + maxIterations: boundedPositiveInteger( + config.maxIterations, + 8, + 8, + "paperclip_runner_aws_agentcore_max_iterations_invalid", + "AWS AgentCore maxIterations", + ), + maxOutputTokens: boundedPositiveInteger( + config.maxOutputTokens, + 4_096, + 4_096, + "paperclip_runner_aws_agentcore_max_output_tokens_invalid", + "AWS AgentCore maxOutputTokens", + ), + timeoutSeconds: boundedPositiveInteger( + config.timeoutSeconds, + 300, + 300, + "paperclip_runner_aws_agentcore_timeout_invalid", + "AWS AgentCore timeoutSeconds", + ), + }, + }; + } return { provider: "codex", model: profile.model, diff --git a/server/src/services/native-runtime/runtime-mode.test.ts b/server/src/services/native-runtime/runtime-mode.test.ts index bf70ef0dc4..c7f64acca9 100644 --- a/server/src/services/native-runtime/runtime-mode.test.ts +++ b/server/src/services/native-runtime/runtime-mode.test.ts @@ -63,7 +63,7 @@ describe("resolveNativeRuntimeMode", () => { })); }); - it("admits fresh OpenCode and qualified ACPX profiles", () => { + it("admits fresh local, managed, and qualified ACPX profiles", () => { expect(resolveNativeRuntimeMode({ ...eligible, adapterConfig: { provider: "opencode", model: "openrouter/deepseek/deepseek-v4-flash-0731" }, @@ -71,6 +71,28 @@ describe("resolveNativeRuntimeMode", () => { kind: "native", profile: { backend: "opencode_server" }, }); + expect(resolveNativeRuntimeMode({ + ...eligible, + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + managedAgentsRetentionAcknowledged: true, + }, + })).toMatchObject({ + kind: "native", + profile: { backend: "claude_managed_agents_api" }, + }); + expect(resolveNativeRuntimeMode({ + ...eligible, + adapterConfig: { + provider: "aws_agentcore", + agentCoreProfileId: "agentcore-primary", + agentCoreRetentionAcknowledged: true, + }, + })).toMatchObject({ + kind: "native", + profile: { backend: "aws_agentcore_harness_api" }, + }); expect(resolveNativeRuntimeMode({ ...eligible, adapterConfig: { provider: "acpx", acpxAgent: "claude", model: "claude-sonnet-5" }, @@ -112,6 +134,27 @@ describe("resolveNativeRuntimeMode", () => { })); }); + it("rejects incomplete managed-provider selections before a run is persisted", () => { + expect(() => resolveNativeRuntimeMode({ + ...eligible, + adapterConfig: { + provider: "claude_managed", + managedProfileId: "managed-primary", + }, + })).toThrow(expect.objectContaining({ + code: "paperclip_runner_claude_managed_retention_required", + })); + expect(() => resolveNativeRuntimeMode({ + ...eligible, + adapterConfig: { + provider: "aws_agentcore", + agentCoreRetentionAcknowledged: true, + }, + })).toThrow(expect.objectContaining({ + code: "paperclip_runner_aws_agentcore_profile_required", + })); + }); + it("uses adapterConfig as the fresh provider authority", () => { expect(resolveNativeRuntimeMode({ ...eligible, @@ -269,6 +312,24 @@ describe("resolveNativeRuntimeMode", () => { })); }); + it.each([ + "claude_managed_agents_api", + "aws_agentcore_harness_api", + ] as const)("keeps a persisted %s recovery on its immutable driver", (driverKind) => { + expect(resolveHeartbeatNativeRuntimeMode({ + ...eligible, + enabled: false, + persisted: { + runtimeMode: "native", + runtimeModeReason: "eligible_opt_in", + runtimeModeResolvedAt: new Date(), + driverKind, + }, + })).toEqual(expect.objectContaining({ + profile: { mode: "native", backend: driverKind, protocolVersion: 1 }, + })); + }); + it("keeps a persisted ACPX recovery even when fresh selection is disabled", () => { expect(resolveHeartbeatNativeRuntimeMode({ ...eligible, diff --git a/server/src/services/native-runtime/runtime-mode.ts b/server/src/services/native-runtime/runtime-mode.ts index 7c8b4acd92..8e3ac18cad 100644 --- a/server/src/services/native-runtime/runtime-mode.ts +++ b/server/src/services/native-runtime/runtime-mode.ts @@ -45,7 +45,12 @@ export type NativeRuntimeResolution = reason: "eligible_opt_in"; profile: { mode: "native"; - backend: "codex_app_server" | "opencode_server" | "acpx_runtime"; + backend: + | "codex_app_server" + | "opencode_server" + | "claude_managed_agents_api" + | "aws_agentcore_harness_api" + | "acpx_runtime"; protocolVersion: 1; }; authorityDecision: NativeStatusDecision; @@ -233,9 +238,13 @@ export function resolveHeartbeatRuntimeMode(input: { reason: "explicit_paperclip_runner", provider: resolution.profile.backend === "opencode_server" ? "opencode" + : resolution.profile.backend === "claude_managed_agents_api" + ? "claude_managed" + : resolution.profile.backend === "aws_agentcore_harness_api" + ? "aws_agentcore" : resolution.profile.backend === "acpx_runtime" - ? "acpx" - : "codex", + ? "acpx" + : "codex", }; } @@ -279,9 +288,13 @@ export function resolveHeartbeatNativeRuntimeMode(input: { const driverKind = input.persisted.driverKind; const backend = driverKind === "opencode_server" ? "opencode_server" + : driverKind === "claude_managed_agents_api" + ? "claude_managed_agents_api" + : driverKind === "aws_agentcore_harness_api" + ? "aws_agentcore_harness_api" : driverKind === "acpx_runtime" - ? "acpx_runtime" - : driverKind === null + ? "acpx_runtime" + : driverKind === null || driverKind === undefined || driverKind === "codex" || driverKind === "codex_app_server" diff --git a/server/src/services/provider-profile-qualification.ts b/server/src/services/provider-profile-qualification.ts new file mode 100644 index 0000000000..e26d52d285 --- /dev/null +++ b/server/src/services/provider-profile-qualification.ts @@ -0,0 +1,166 @@ +import { createHash } from "node:crypto"; +import { isDeepStrictEqual } from "node:util"; + +import { unprocessable } from "../errors.js"; +import { sanitizeRecord } from "../redaction.js"; + +export const CLAUDE_MANAGED_QUALIFICATION = { + betaVersion: "managed-agents-2026-04-01", + environmentPolicy: "limited_no_hosts_no_packages", + agentCapabilities: "no_tools_no_mcp_no_skills_no_multiagent", +} as const; + +export const CLAUDE_MANAGED_QUALIFIED_MODEL = "claude-sonnet-5" as const; +export const AGENTCORE_QUALIFICATION_SUITE = "aws-agentcore-harness-v1" as const; +export const AGENTCORE_QUALIFIED_MODEL = "global.anthropic.claude-sonnet-4-6" as const; + +const REVISION_PREFIX = "sha256:"; +const SHA256_RE = /^sha256:[0-9a-f]{64}$/; +const ISO_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/; + +const SECRET_SHAPED_VALUE_PATTERNS = [ + /^\s*(?:bearer|basic)\s+\S+/i, + /^sk-[A-Za-z0-9_-]{8,}$/, + /^(?:gh[opusr]_[A-Za-z0-9]{12,}|xox[baprs]-\S+)$/, + /^AKIA[0-9A-Z]{16}$/, + /^[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}(?:\.[A-Za-z0-9_-]{8,})?$/, + /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, +] as const; + +function containsSecretShapedValue(value: unknown): boolean { + if (typeof value === "string") { + return SECRET_SHAPED_VALUE_PATTERNS.some((pattern) => pattern.test(value)); + } + if (Array.isArray(value)) return value.some(containsSecretShapedValue); + if (!value || typeof value !== "object") return false; + return Object.values(value as Record).some(containsSecretShapedValue); +} + +function normalizeKnownPublicValues(value: unknown): unknown { + if ( + value === CLAUDE_MANAGED_QUALIFIED_MODEL + || value === AGENTCORE_QUALIFIED_MODEL + ) { + return "paperclip-qualified-provider-model"; + } + if (Array.isArray(value)) return value.map(normalizeKnownPublicValues); + if (!value || typeof value !== "object") return value; + return Object.fromEntries( + Object.entries(value as Record) + .map(([key, entry]) => [key, normalizeKnownPublicValues(entry)]), + ); +} + +export function assertProfileMetadataContainsNoSecrets( + value: Record, + label: string, +): void { + const publicValueNormalized = normalizeKnownPublicValues(value) as Record; + if ( + !isDeepStrictEqual(sanitizeRecord(publicValueNormalized), publicValueNormalized) + || containsSecretShapedValue(value) + ) { + throw unprocessable(`${label} must not contain credential-shaped keys or values`); + } +} + +function assertExactKeys( + value: Record, + keys: readonly string[], + label: string, +): void { + const actual = Object.keys(value).sort(); + const expected = [...keys].sort(); + if (!isDeepStrictEqual(actual, expected)) { + throw unprocessable(`${label} must contain the exact qualification attestation fields`); + } +} + +function assertIsoTimestamp(value: unknown, label: string): void { + if ( + typeof value !== "string" + || !ISO_TIMESTAMP_RE.test(value) + || !Number.isFinite(Date.parse(value)) + ) { + throw unprocessable(`${label} must be an ISO-8601 UTC timestamp`); + } +} + +export function assertClaudeManagedQualification( + qualification: Record, + options: { required: boolean }, +): boolean { + if (Object.keys(qualification).length === 0) { + if (options.required) { + throw unprocessable("Claude Managed qualification attestation is required before enablement"); + } + return false; + } + assertProfileMetadataContainsNoSecrets(qualification, "Managed Agent qualification"); + assertExactKeys( + qualification, + ["probedAt", "betaVersion", "environmentPolicy", "agentCapabilities"], + "Claude Managed qualification", + ); + assertIsoTimestamp(qualification.probedAt, "qualification.probedAt"); + if ( + qualification.betaVersion !== CLAUDE_MANAGED_QUALIFICATION.betaVersion + || qualification.environmentPolicy !== CLAUDE_MANAGED_QUALIFICATION.environmentPolicy + || qualification.agentCapabilities !== CLAUDE_MANAGED_QUALIFICATION.agentCapabilities + ) { + throw unprocessable("Claude Managed qualification attestation does not match the qualified profile"); + } + return true; +} + +export function assertAgentCoreQualification( + configuration: Record, + qualification: Record, + options: { required: boolean }, +): boolean { + if (Object.keys(qualification).length === 0) { + if (options.required) { + throw unprocessable("AWS AgentCore qualification attestation is required before enablement"); + } + return false; + } + assertProfileMetadataContainsNoSecrets(qualification, "Remote Agent qualification"); + assertExactKeys(qualification, ["suite"], "AWS AgentCore qualification"); + if ( + configuration.qualificationRevision !== AGENTCORE_QUALIFICATION_SUITE + || qualification.suite !== AGENTCORE_QUALIFICATION_SUITE + ) { + throw unprocessable("AWS AgentCore qualification attestation does not match the qualified harness suite"); + } + return true; +} + +type CanonicalJson = null | boolean | number | string | CanonicalJson[] | { + [key: string]: CanonicalJson; +}; + +function canonicalize(value: unknown, label: string): CanonicalJson { + if (value === null || typeof value === "string" || typeof value === "boolean") return value; + if (typeof value === "number" && Number.isFinite(value)) return value; + if (Array.isArray(value)) return value.map((entry) => canonicalize(entry, label)); + if (value && typeof value === "object") { + return Object.fromEntries( + Object.entries(value as Record) + .filter(([, entry]) => entry !== undefined) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, entry]) => [key, canonicalize(entry, label)]), + ); + } + throw unprocessable(`${label} contains a value that cannot be revisioned`); +} + +export function computeQualifiedProfileRevision( + value: Record, +): string { + const canonical = JSON.stringify(canonicalize(value, "Provider profile")); + return `${REVISION_PREFIX}${createHash("sha256").update(canonical).digest("hex")}`; +} + +export function isQualifiedProfileRevision(value: unknown): value is string { + return typeof value === "string" && SHA256_RE.test(value); +} diff --git a/server/src/services/remote-agent-profiles.test.ts b/server/src/services/remote-agent-profiles.test.ts new file mode 100644 index 0000000000..3dcd77a129 --- /dev/null +++ b/server/src/services/remote-agent-profiles.test.ts @@ -0,0 +1,575 @@ +import type { Db } from "@paperclipai/db"; +import { describe, expect, it } from "vitest"; + +import { + computeManagedAgentProfileRevision, + managedAgentProfileService, +} from "./managed-agent-profiles.js"; +import { + computeRemoteAgentProfileRevision, + remoteAgentProfileService, + type RemoteAgentProfileInput, +} from "./remote-agent-profiles.js"; + +const COMPANY_ID = "10000000-0000-4000-8000-000000000001"; +const OTHER_COMPANY_SECRET_ID = "20000000-0000-4000-8000-000000000002"; + +const AWS_CONFIGURATION = { + region: "us-east-1", + accountId: "123456789012", + harnessArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/example", + harnessVersion: "1", + endpointArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:endpoint/example", + endpointQualifier: "paperclip", + agentRuntimeArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/example", + memoryArn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/example", + memoryId: "memory-example", + invocationRoleArn: "arn:aws:iam::123456789012:role/paperclip-runner", + contextBucket: "paperclip-runner-context", + contextPrefix: "profiles/example", + contextKmsKeyArn: "arn:aws:kms:us-east-1:123456789012:key/example", + qualificationRevision: "aws-agentcore-harness-v1", + defaultModel: "global.anthropic.claude-sonnet-4-6", + eventExpiryDays: 90, + defaultMaxEstimatedSessionCostUsd: 1, +} as const; + +function remoteInput( + overrides: Partial = {}, +): RemoteAgentProfileInput { + return { + profileKey: "agentcore", + displayName: "AgentCore", + service: "aws_bedrock_agentcore_harness", + configuration: { ...AWS_CONFIGURATION }, + enabled: false, + retentionAcknowledged: false, + qualification: { suite: "aws-agentcore-harness-v1" }, + ...overrides, + }; +} + +function dbReturningNoRows(): Db { + return { + select: () => ({ + from: () => ({ + where: () => ({ + limit: async () => [], + }), + }), + }), + } as unknown as Db; +} + +function dbReturningFirstRow(row: Record): Db { + return { + select: () => ({ + from: () => ({ + where: () => ({ + limit: async () => [row], + }), + }), + }), + } as unknown as Db; +} + +function dbForUpsert( + existing: Record | null, + secretId?: string, +): Db { + let selection = 0; + return { + select: () => ({ + from: () => ({ + where: () => ({ + limit: async () => { + selection += 1; + if (selection === 1) return existing ? [existing] : []; + return secretId ? [{ id: secretId }] : []; + }, + }), + }), + }), + insert: () => ({ + values: (values: Record) => ({ + onConflictDoUpdate: () => ({ + returning: async () => [{ ...(existing ?? {}), ...values }], + }), + }), + }), + } as unknown as Db; +} + +const unusedDb = new Proxy({}, { + get() { + throw new Error("validation unexpectedly accessed the database"); + }, +}) as unknown as Db; + +describe("remote agent profile metadata validation", () => { + it("rejects obsolete explicit credentials because AgentCore uses workload identity", async () => { + await expect( + remoteAgentProfileService(unusedDb).upsert(COMPANY_ID, { + ...remoteInput(), + credentialSecretId: OTHER_COMPANY_SECRET_ID, + } as unknown as RemoteAgentProfileInput), + ).rejects.toThrow("use workload identity"); + }); + + it("rejects Claude profiles before the remote AgentCore store is accessed", async () => { + await expect( + remoteAgentProfileService(unusedDb).upsert(COMPANY_ID, { + ...remoteInput(), + service: "anthropic_managed_agents", + } as unknown as RemoteAgentProfileInput), + ).rejects.toThrow("Unsupported remote agent service"); + }); + + it("rejects non-canonical Anthropic Agent versions before profile storage", async () => { + for (const agentVersion of ["latest", "0", "01", "2147483648"]) { + await expect( + managedAgentProfileService(unusedDb).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "agent-example", + agentVersion, + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: false, + retentionAcknowledged: false, + }), + ).rejects.toThrow("canonical positive 32-bit integer"); + } + }); + + it("rejects provider configuration keys outside the exact allowlist", async () => { + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ + configuration: { + ...AWS_CONFIGURATION, + customEndpointToken: "not-persistable", + }, + }), + ), + ).rejects.toThrow("Unsupported configuration field"); + }); + + it("rejects secret-shaped configuration values and qualification fields", async () => { + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ + configuration: { + ...AWS_CONFIGURATION, + qualificationRevision: "Bearer secret-value", + }, + }), + ), + ).rejects.toThrow("must not contain credential-shaped keys or values"); + + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ qualification: { apiToken: "secret-value" } }), + ), + ).rejects.toThrow("must not contain credential-shaped keys or values"); + + await expect( + managedAgentProfileService(unusedDb).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Bearer secret-value", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: false, + retentionAcknowledged: false, + }), + ).rejects.toThrow("must not contain credential-shaped keys or values"); + }); + + it("requires retention acknowledgement and a positive explicit spend ceiling", async () => { + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ enabled: true, retentionAcknowledged: false }), + ), + ).rejects.toThrow("requires retention acknowledgement"); + + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ + configuration: { + ...AWS_CONFIGURATION, + defaultMaxEstimatedSessionCostUsd: 0, + }, + }), + ), + ).rejects.toThrow("spend ceiling must be positive"); + + const { + defaultMaxEstimatedSessionCostUsd: _omittedSpendCeiling, + ...configurationWithoutSpendCeiling + } = AWS_CONFIGURATION; + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ configuration: configurationWithoutSpendCeiling }), + ), + ).rejects.toThrow("spend ceiling must be positive"); + + await expect( + managedAgentProfileService(unusedDb).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 0, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: false, + retentionAcknowledged: false, + }), + ).rejects.toThrow("spend ceiling must be positive"); + }); + + it("does not treat enablement or arbitrary metadata as qualification", async () => { + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ enabled: true, retentionAcknowledged: true, qualification: {} }), + ), + ).rejects.toThrow("qualification attestation is required"); + + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ + enabled: true, + retentionAcknowledged: true, + qualification: { suite: "operator-says-pass" }, + }), + ), + ).rejects.toThrow("does not match the qualified harness suite"); + + await expect( + managedAgentProfileService(unusedDb).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: true, + retentionAcknowledged: true, + qualification: {}, + }), + ).rejects.toThrow("qualification attestation is required"); + }); + + it("stores a qualified revision only after exact operator attestation", async () => { + const qualification = { + probedAt: "2026-08-01T00:00:00.000Z", + betaVersion: "managed-agents-2026-04-01", + environmentPolicy: "limited_no_hosts_no_packages", + agentCapabilities: "no_tools_no_mcp_no_skills_no_multiagent", + }; + const managed = await managedAgentProfileService( + dbForUpsert(null, OTHER_COMPANY_SECRET_ID), + ).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: true, + retentionAcknowledged: true, + qualification, + }); + expect(managed.qualifiedAt).toBeInstanceOf(Date); + expect(managed.qualifiedRevision).toMatch(/^sha256:[0-9a-f]{64}$/); + + const remote = await remoteAgentProfileService(dbForUpsert(null)).upsert( + COMPANY_ID, + remoteInput({ enabled: true, retentionAcknowledged: true }), + ); + expect(remote.qualifiedAt).toBeInstanceOf(Date); + expect(remote.qualifiedRevision).toMatch(/^sha256:[0-9a-f]{64}$/); + }); + + it("requires exact qualified models and non-UUID profile keys", async () => { + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ + configuration: { ...AWS_CONFIGURATION, defaultModel: "claude-sonnet-4-6" }, + }), + ), + ).rejects.toThrow("model must be global.anthropic.claude-sonnet-4-6"); + + await expect( + managedAgentProfileService(unusedDb).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-opus-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: false, + retentionAcknowledged: false, + }), + ).rejects.toThrow("model must be claude-sonnet-5"); + + await expect( + remoteAgentProfileService(unusedDb).upsert( + COMPANY_ID, + remoteInput({ profileKey: "30000000-0000-4000-8000-000000000003" }), + ), + ).rejects.toThrow("must not be UUID-shaped"); + }); + + it("rejects Claude credential references that do not resolve in the owning company", async () => { + await expect( + managedAgentProfileService(dbReturningNoRows()).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: false, + retentionAcknowledged: false, + qualification: {}, + }), + ).rejects.toThrow("API-key secret reference is invalid"); + }); + + it("does not allow a qualified AgentCore profile key to be repointed", async () => { + const qualification = { suite: "aws-agentcore-harness-v1" }; + const existing = { + id: "30000000-0000-4000-8000-000000000003", + companyId: COMPANY_ID, + profileKey: "agentcore", + displayName: "AgentCore", + service: "aws_bedrock_agentcore_harness", + configuration: { ...AWS_CONFIGURATION }, + enabled: true, + retentionAcknowledged: true, + qualification, + qualifiedAt: new Date("2026-08-01T00:00:00.000Z"), + qualifiedRevision: computeRemoteAgentProfileRevision({ + service: "aws_bedrock_agentcore_harness", + configuration: AWS_CONFIGURATION, + retentionAcknowledged: true, + qualification, + }), + createdAt: new Date("2026-08-01T00:00:00.000Z"), + updatedAt: new Date("2026-08-01T00:00:00.000Z"), + }; + + await expect( + remoteAgentProfileService(dbReturningFirstRow(existing)).upsert( + COMPANY_ID, + remoteInput({ + enabled: true, + retentionAcknowledged: true, + configuration: { ...AWS_CONFIGURATION, memoryId: "different-memory" }, + }), + ), + ).rejects.toThrow("configuration revision is immutable"); + }); + + it("does not allow qualified Claude resource identity or proof to drift", async () => { + const qualification = { + probedAt: "2026-08-01T00:00:00.000Z", + betaVersion: "managed-agents-2026-04-01", + environmentPolicy: "limited_no_hosts_no_packages", + agentCapabilities: "no_tools_no_mcp_no_skills_no_multiagent", + }; + const existing = { + id: "30000000-0000-4000-8000-000000000004", + companyId: COMPANY_ID, + profileKey: "managed", + displayName: "Managed Agent", + service: "anthropic_managed_agents", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + betaVersion: "managed-agents-2026-04-01", + defaultModel: "claude-sonnet-5", + defaultMaxListCostCents: 100, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: true, + retentionAcknowledged: true, + qualification, + qualifiedAt: new Date("2026-08-01T00:00:00.000Z"), + qualifiedRevision: computeManagedAgentProfileRevision({ + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + betaVersion: "managed-agents-2026-04-01", + retentionAcknowledged: true, + qualification, + }), + createdAt: new Date("2026-08-01T00:00:00.000Z"), + updatedAt: new Date("2026-08-01T00:00:00.000Z"), + }; + + await expect( + managedAgentProfileService(dbReturningFirstRow(existing)).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "different-agent", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 1, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: true, + retentionAcknowledged: true, + qualification, + }), + ).rejects.toThrow("configuration revision is immutable"); + }); + + it("allows credential rotation and mutable caps without invalidating qualified identity", async () => { + const qualification = { + probedAt: "2026-08-01T00:00:00.000Z", + betaVersion: "managed-agents-2026-04-01", + environmentPolicy: "limited_no_hosts_no_packages", + agentCapabilities: "no_tools_no_mcp_no_skills_no_multiagent", + }; + const rotatedSecretId = "20000000-0000-4000-8000-000000000003"; + const existingManaged = { + id: "30000000-0000-4000-8000-000000000004", + companyId: COMPANY_ID, + profileKey: "managed", + displayName: "Managed Agent", + service: "anthropic_managed_agents", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + betaVersion: "managed-agents-2026-04-01", + defaultModel: "claude-sonnet-5", + defaultMaxListCostCents: 100, + apiKeySecretId: OTHER_COMPANY_SECRET_ID, + enabled: true, + retentionAcknowledged: true, + qualification, + qualifiedAt: new Date("2026-08-01T00:00:00.000Z"), + qualifiedRevision: computeManagedAgentProfileRevision({ + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + betaVersion: "managed-agents-2026-04-01", + retentionAcknowledged: true, + qualification, + }), + createdAt: new Date("2026-08-01T00:00:00.000Z"), + updatedAt: new Date("2026-08-01T00:00:00.000Z"), + }; + const updatedManaged = await managedAgentProfileService( + dbForUpsert(existingManaged, rotatedSecretId), + ).upsert(COMPANY_ID, { + profileKey: "managed", + displayName: "Managed Agent", + anthropicAgentId: "agent-example", + agentVersion: "1", + environmentId: "environment-example", + defaultModel: "claude-sonnet-5", + defaultMaxListCostUsd: 2.5, + apiKeySecretId: rotatedSecretId, + enabled: true, + retentionAcknowledged: true, + qualification, + }); + expect(updatedManaged).toMatchObject({ + apiKeySecretId: rotatedSecretId, + defaultMaxListCostCents: 250, + qualifiedRevision: existingManaged.qualifiedRevision, + }); + + const awsQualification = { suite: "aws-agentcore-harness-v1" }; + const existingRemote = { + id: "30000000-0000-4000-8000-000000000005", + companyId: COMPANY_ID, + profileKey: "agentcore", + displayName: "AgentCore", + service: "aws_bedrock_agentcore_harness", + configuration: { ...AWS_CONFIGURATION }, + enabled: true, + retentionAcknowledged: true, + qualification: awsQualification, + qualifiedAt: new Date("2026-08-01T00:00:00.000Z"), + qualifiedRevision: computeRemoteAgentProfileRevision({ + service: "aws_bedrock_agentcore_harness", + configuration: AWS_CONFIGURATION, + retentionAcknowledged: true, + qualification: awsQualification, + }), + createdAt: new Date("2026-08-01T00:00:00.000Z"), + updatedAt: new Date("2026-08-01T00:00:00.000Z"), + }; + const updatedRemote = await remoteAgentProfileService( + dbForUpsert(existingRemote), + ).upsert(COMPANY_ID, remoteInput({ + enabled: true, + retentionAcknowledged: true, + configuration: { + ...AWS_CONFIGURATION, + defaultMaxEstimatedSessionCostUsd: 3, + }, + })); + expect(updatedRemote).toMatchObject({ + configuration: { + defaultModel: "global.anthropic.claude-sonnet-4-6", + defaultMaxEstimatedSessionCostUsd: 3, + }, + qualifiedRevision: existingRemote.qualifiedRevision, + }); + }); + + it("rejects runtime use when stored identity drifts from the qualified revision", async () => { + const qualification = { suite: "aws-agentcore-harness-v1" }; + const qualifiedRevision = computeRemoteAgentProfileRevision({ + service: "aws_bedrock_agentcore_harness", + configuration: AWS_CONFIGURATION, + retentionAcknowledged: true, + qualification, + }); + await expect( + remoteAgentProfileService(dbReturningFirstRow({ + id: "30000000-0000-4000-8000-000000000003", + companyId: COMPANY_ID, + profileKey: "agentcore", + displayName: "AgentCore", + service: "aws_bedrock_agentcore_harness", + configuration: { ...AWS_CONFIGURATION, memoryId: "tampered-memory" }, + enabled: true, + retentionAcknowledged: true, + qualification, + qualifiedAt: new Date("2026-08-01T00:00:00.000Z"), + qualifiedRevision, + createdAt: new Date("2026-08-01T00:00:00.000Z"), + updatedAt: new Date("2026-08-01T00:00:00.000Z"), + })).requireQualified(COMPANY_ID, "agentcore"), + ).rejects.toThrow("does not match its qualified revision"); + }); +}); diff --git a/server/src/services/remote-agent-profiles.ts b/server/src/services/remote-agent-profiles.ts new file mode 100644 index 0000000000..719cd244e1 --- /dev/null +++ b/server/src/services/remote-agent-profiles.ts @@ -0,0 +1,290 @@ +import { and, asc, eq } from "drizzle-orm"; +import type { Db } from "@paperclipai/db"; +import { remoteAgentProfiles } from "@paperclipai/db"; + +import { conflict, notFound, unprocessable } from "../errors.js"; +import { + AGENTCORE_QUALIFIED_MODEL, + AGENTCORE_QUALIFICATION_SUITE, + assertAgentCoreQualification, + assertProfileMetadataContainsNoSecrets, + computeQualifiedProfileRevision, + isQualifiedProfileRevision, +} from "./provider-profile-qualification.js"; + +export { assertProfileMetadataContainsNoSecrets } from "./provider-profile-qualification.js"; + +export type RemoteAgentService = "aws_bedrock_agentcore_harness"; +const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +export interface RemoteAgentProfileInput { + profileKey: string; + displayName: string; + service: RemoteAgentService; + configuration: Record; + enabled: boolean; + retentionAcknowledged: boolean; + qualification?: Record; +} + +function required(value: unknown, label: string): string { + if (typeof value !== "string" || value.trim().length === 0) { + throw unprocessable(`${label} is required`); + } + return value.trim(); +} + +const AGENTCORE_CONFIGURATION_KEYS = new Set([ + "region", + "accountId", + "harnessArn", + "harnessVersion", + "endpointArn", + "endpointQualifier", + "agentRuntimeArn", + "memoryArn", + "memoryId", + "invocationRoleArn", + "contextBucket", + "contextPrefix", + "contextKmsKeyArn", + "qualificationRevision", + "defaultModel", + "eventExpiryDays", + "defaultMaxEstimatedSessionCostUsd", +]); + +function validateConfiguration(service: RemoteAgentService, configuration: Record) { + if (service !== "aws_bedrock_agentcore_harness") { + throw unprocessable("Unsupported remote agent service"); + } + const requiredKeys = [ + "region", + "accountId", + "harnessArn", + "harnessVersion", + "endpointArn", + "endpointQualifier", + "agentRuntimeArn", + "memoryArn", + "memoryId", + "invocationRoleArn", + "contextBucket", + "contextPrefix", + "contextKmsKeyArn", + "qualificationRevision", + "defaultModel", + ]; + const unknownKeys = Object.keys(configuration).filter((key) => !AGENTCORE_CONFIGURATION_KEYS.has(key)); + if (unknownKeys.length > 0) { + throw unprocessable(`Unsupported configuration field: ${unknownKeys.sort()[0]}`); + } + assertProfileMetadataContainsNoSecrets(configuration, "Remote Agent configuration"); + for (const key of requiredKeys) required(configuration[key], `configuration.${key}`); + if (configuration.eventExpiryDays !== 90) { + throw unprocessable("AWS AgentCore profile requires a 90-day Memory expiry"); + } + if ( + configuration.qualificationRevision !== AGENTCORE_QUALIFICATION_SUITE + ) { + throw unprocessable("AWS AgentCore profile requires the qualified harness revision"); + } + if (configuration.defaultModel !== AGENTCORE_QUALIFIED_MODEL) { + throw unprocessable(`Remote Agent model must be ${AGENTCORE_QUALIFIED_MODEL}`); + } + if ( + typeof configuration.defaultMaxEstimatedSessionCostUsd !== "number" + || !Number.isFinite(configuration.defaultMaxEstimatedSessionCostUsd) + || configuration.defaultMaxEstimatedSessionCostUsd <= 0 + ) { + throw unprocessable("AWS AgentCore default estimated spend ceiling must be positive"); + } +} + +export function computeRemoteAgentProfileRevision(input: { + service: RemoteAgentService; + configuration: Record; + retentionAcknowledged: boolean; + qualification: Record; +}): string { + const immutableConfigurationKeys = [ + "region", + "accountId", + "harnessArn", + "harnessVersion", + "endpointArn", + "endpointQualifier", + "agentRuntimeArn", + "memoryArn", + "memoryId", + "invocationRoleArn", + "contextBucket", + "contextPrefix", + "contextKmsKeyArn", + "qualificationRevision", + "eventExpiryDays", + ]; + const immutableConfiguration = Object.fromEntries( + immutableConfigurationKeys.map((key) => [key, input.configuration[key]]), + ); + return computeQualifiedProfileRevision({ + service: input.service, + configuration: immutableConfiguration, + retentionAcknowledged: input.retentionAcknowledged, + qualification: input.qualification, + }); +} + +function assertQualifiedRevisionUnchanged( + existing: typeof remoteAgentProfiles.$inferSelect | null, + revision: string | null, +): void { + if (!existing?.qualifiedAt) return; + if (!revision || existing.qualifiedRevision !== revision) { + throw conflict("Qualified Remote Agent configuration revision is immutable; create a new profile key"); + } +} + +export function remoteAgentProfileService(db: Db) { + async function list(companyId: string, service?: RemoteAgentService) { + return db + .select() + .from(remoteAgentProfiles) + .where( + service + ? and(eq(remoteAgentProfiles.companyId, companyId), eq(remoteAgentProfiles.service, service)) + : eq(remoteAgentProfiles.companyId, companyId), + ) + .orderBy(asc(remoteAgentProfiles.displayName)); + } + + async function get(companyId: string, profileIdOrKey: string) { + const isUuid = UUID_RE.test(profileIdOrKey); + const rows = await db + .select() + .from(remoteAgentProfiles) + .where(and( + eq(remoteAgentProfiles.companyId, companyId), + isUuid + ? eq(remoteAgentProfiles.id, profileIdOrKey) + : eq(remoteAgentProfiles.profileKey, profileIdOrKey), + )) + .limit(1); + return rows[0] ?? null; + } + + async function getByProfileKey(companyId: string, profileKey: string) { + const rows = await db + .select() + .from(remoteAgentProfiles) + .where(and( + eq(remoteAgentProfiles.companyId, companyId), + eq(remoteAgentProfiles.profileKey, profileKey), + )) + .limit(1); + return rows[0] ?? null; + } + + async function requireQualified( + companyId: string, + profileIdOrKey: string, + service?: RemoteAgentService, + ) { + const profile = await get(companyId, profileIdOrKey); + if (!profile || (service && profile.service !== service)) { + throw notFound("Remote Agent profile not found"); + } + if (!profile.enabled || !profile.retentionAcknowledged || !profile.qualifiedAt) { + throw conflict("Remote Agent profile is not enabled and qualified"); + } + try { + validateConfiguration(profile.service as RemoteAgentService, profile.configuration); + } catch { + throw conflict("Remote Agent profile configuration is not qualified"); + } + try { + assertAgentCoreQualification(profile.configuration, profile.qualification, { required: true }); + } catch { + throw conflict("Remote Agent profile qualification attestation is invalid"); + } + const currentRevision = computeRemoteAgentProfileRevision({ + service: profile.service as RemoteAgentService, + configuration: profile.configuration, + retentionAcknowledged: profile.retentionAcknowledged, + qualification: profile.qualification, + }); + if ( + !isQualifiedProfileRevision(profile.qualifiedRevision) + || profile.qualifiedRevision !== currentRevision + ) { + throw conflict("Remote Agent profile configuration does not match its qualified revision"); + } + return profile; + } + + async function upsert(companyId: string, input: RemoteAgentProfileInput) { + if ("credentialSecretId" in (input as unknown as Record)) { + throw unprocessable("AWS AgentCore profiles use workload identity, not a credential secret"); + } + const profileKey = required(input.profileKey, "Profile key"); + if (UUID_RE.test(profileKey)) { + throw unprocessable("Profile key must not be UUID-shaped"); + } + const displayName = required(input.displayName, "Display name"); + const configuration = structuredClone(input.configuration); + const qualification = structuredClone(input.qualification ?? {}); + assertProfileMetadataContainsNoSecrets( + { profileKey, displayName }, + "Remote Agent profile", + ); + validateConfiguration(input.service, configuration); + if (input.enabled && !input.retentionAcknowledged) { + throw unprocessable("Enabling a remote agent requires retention acknowledgement"); + } + const qualificationAttested = assertAgentCoreQualification( + configuration, + qualification, + { required: input.enabled }, + ); + + const existing = await getByProfileKey(companyId, profileKey); + + const qualifiedRevision = qualificationAttested + ? computeRemoteAgentProfileRevision({ + service: input.service, + configuration, + retentionAcknowledged: input.retentionAcknowledged, + qualification, + }) + : null; + assertQualifiedRevisionUnchanged(existing, qualifiedRevision); + + const values = { + companyId, + profileKey, + displayName, + service: input.service, + configuration, + enabled: input.enabled, + retentionAcknowledged: input.retentionAcknowledged, + qualification, + qualifiedAt: existing?.qualifiedAt ?? (input.enabled && qualificationAttested ? new Date() : null), + qualifiedRevision: + existing?.qualifiedAt || (input.enabled && qualificationAttested) + ? qualifiedRevision + : null, + updatedAt: new Date(), + } as const; + const [row] = await db + .insert(remoteAgentProfiles) + .values(values) + .onConflictDoUpdate({ + target: [remoteAgentProfiles.companyId, remoteAgentProfiles.profileKey], + set: values, + }) + .returning(); + return row!; + } + + return { list, get, requireQualified, upsert }; +} diff --git a/ui/src/adapters/codex-local/config-fields.test.tsx b/ui/src/adapters/codex-local/config-fields.test.tsx index c6b6d44586..4feb79380a 100644 --- a/ui/src/adapters/codex-local/config-fields.test.tsx +++ b/ui/src/adapters/codex-local/config-fields.test.tsx @@ -25,7 +25,7 @@ function renderRunner(config: Record): string { } describe("Paperclip Runner Codex configuration", () => { - it("exposes the qualified provider choices without managed profiles", () => { + it("exposes all qualified provider choices", () => { const html = renderRunner({ provider: "codex" }); expect(html).toContain(''); @@ -34,8 +34,8 @@ describe("Paperclip Runner Codex configuration", () => { expect(html).toContain("Full auto (never ask)"); expect(html).toContain("Ask when requested"); expect(html).toContain("Ask for untrusted operations"); - expect(html).not.toContain("Claude Agent"); - expect(html).not.toContain("AWS AgentCore"); + expect(html).toContain("Claude Managed"); + expect(html).toContain("AWS AgentCore"); expect(html).not.toContain("Bypass sandbox"); }); diff --git a/ui/src/adapters/codex-local/config-fields.tsx b/ui/src/adapters/codex-local/config-fields.tsx index f2163b9407..920c00531f 100644 --- a/ui/src/adapters/codex-local/config-fields.tsx +++ b/ui/src/adapters/codex-local/config-fields.tsx @@ -35,6 +35,8 @@ const acpxRunnerModels = { claude: "claude-sonnet-5", codex: "gpt-5.6-sol", } as const; +const defaultClaudeManagedModel = "claude-sonnet-5"; +const defaultAwsAgentCoreModel = "global.anthropic.claude-sonnet-4-6"; export function CodexLocalConfigFields({ mode, @@ -67,7 +69,7 @@ export function CodexLocalConfigFields({ : "codex"; const runnerPermissionCapability = PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES[runnerProvider]; - const runnerPermissionMode = runnerManaged + const runnerPermissionMode = runnerManaged && runnerPermissionCapability.configurable ? resolvePaperclipRunnerPermissionMode( runnerProvider, isCreate @@ -84,6 +86,22 @@ export function CodexLocalConfigFields({ ), ) : runnerPermissionCapability.defaultMode; + const runnerSchemaValue = (key: string, fallback: unknown): unknown => + isCreate + ? values!.adapterSchemaValues?.[key] ?? fallback + : eff("adapterConfig", key, config[key] ?? fallback); + const updateRunnerSchemaValue = (key: string, value: unknown): void => { + if (isCreate) { + set!({ + adapterSchemaValues: { + ...values!.adapterSchemaValues, + [key]: value, + }, + }); + } else { + mark("adapterConfig", key, value); + } + }; const configuredAcpxAgent = runnerManaged && runnerProvider === "acpx" ? isCreate ? values!.adapterSchemaValues?.acpxAgent @@ -164,9 +182,13 @@ export function CodexLocalConfigFields({ : "codex"; const model = provider === "opencode" ? defaultOpenCodeRunnerModel - : provider === "acpx" - ? acpxRunnerModels.claude - : DEFAULT_CODEX_LOCAL_MODEL; + : provider === "claude_managed" + ? defaultClaudeManagedModel + : provider === "aws_agentcore" + ? defaultAwsAgentCoreModel + : provider === "acpx" + ? acpxRunnerModels.claude + : DEFAULT_CODEX_LOCAL_MODEL; if (isCreate) { set!({ model, @@ -187,10 +209,129 @@ export function CodexLocalConfigFields({ > + + )} + {runnerManaged && !runnerPermissionCapability.configurable && ( + +
+ Provider-managed +
+
+ )} + {runnerManaged && runnerProvider === "claude_managed" && ( + <> + + updateRunnerSchemaValue("managedProfileId", value.trim())} + immediate + className={inputClass} + placeholder="managed-primary" + /> + + + updateRunnerSchemaValue("maxSessionListCostUsd", value)} + immediate + className={inputClass} + /> + + updateRunnerSchemaValue("managedAgentsRetentionAcknowledged", value)} + /> + + )} + {runnerManaged && runnerProvider === "aws_agentcore" && ( + <> + + updateRunnerSchemaValue("agentCoreProfileId", value.trim())} + immediate + className={inputClass} + placeholder="agentcore-primary" + /> + + + updateRunnerSchemaValue("maxEstimatedSessionCostUsd", value)} + immediate + className={inputClass} + /> + + + updateRunnerSchemaValue("maxIterations", value)} + immediate + className={inputClass} + /> + + + updateRunnerSchemaValue("maxOutputTokens", value)} + immediate + className={inputClass} + /> + + + updateRunnerSchemaValue("timeoutSeconds", value)} + immediate + className={inputClass} + /> + + updateRunnerSchemaValue("agentCoreRetentionAcknowledged", value)} + /> + + )} {runnerManaged && runnerProvider === "acpx" && ( )} - {runnerManaged && ( + {runnerManaged && runnerPermissionCapability.configurable && (