/** * Durable storage for queued `run-code` sandbox executions. * * A row in `sandbox_executions` is one background code-execution request: the * raw user code plus enough identity (owner email, org, thread) to rebuild the * action bridge context in a fresh process, and the full result once an * executor finishes. This is what lets a long compute job survive the hosted * serverless wall: the enqueueing request returns immediately, and any later * invocation (self-dispatch processor, status poll, warm-instance sweep) can * claim and run the row under its own budget. * * Concurrency model (single-writer via claim token + lease): * - `claimSandboxExecution` atomically flips `queued → running` (or reclaims * a `running` row whose lease expired) with `UPDATE … WHERE status = …` * guards, so exactly one executor wins even when several invocations race. * - The winning executor holds a random `claim_token` and heartbeats * `lease_expires_at`; `finalizeSandboxExecution` only writes results when * the token still matches, so a displaced executor can never clobber the * reclaimer's result. * - A row whose lease expired with attempts remaining is re-claimable; once * attempts are exhausted `failExpiredSandboxExecution` marks it `failed` * instead of leaving it "running" forever. * * Schema notes: additive-only, portable across Postgres (Neon) and SQLite — * same `ensureTableExists`/dialect-branched DDL pattern as * `resources/store.ts`. Timestamps are epoch-ms in `BIGINT`/`INTEGER` columns. * Reads for user-facing surfaces are always owner-scoped * (`getSandboxExecutionForOwner`); the unscoped internal reader exists only * for the trusted executor/sweep paths. */ export type SandboxExecutionStatus = "queued" | "running" | "succeeded" | "failed" | "timed_out"; export interface SandboxExecutionRow { id: string; owner: string; orgId: string | null; threadId: string | null; runtime: string; code: string; status: SandboxExecutionStatus; timeoutMs: number; maxOutputChars: number; attemptCount: number; maxAttempts: number; claimToken: string | null; leaseExpiresAt: number | null; stdout: string; stderr: string; stdoutTruncated: boolean; stderrTruncated: boolean; exitCode: number | null; timedOut: boolean; error: string | null; bridgeToolsUsed: string[]; createdAt: number; startedAt: number | null; finishedAt: number | null; updatedAt: number; } export interface CreateSandboxExecutionInput { owner: string; orgId?: string | null; threadId?: string | null; code: string; timeoutMs: number; maxOutputChars: number; maxAttempts?: number; runtime?: string; } export interface FinalizeSandboxExecutionInput { status: Extract; stdout?: string; stderr?: string; exitCode?: number | null; timedOut?: boolean; error?: string | null; bridgeToolsUsed?: string[]; } /** Default retry budget: the initial attempt plus one lease-expiry retry. */ export declare const SANDBOX_EXECUTION_DEFAULT_MAX_ATTEMPTS = 2; /** * Hard per-stream storage cap. The per-row `max_output_chars` (derived from * the caller's `maxOutputChars`) is applied first; this bounds even that. */ export declare const SANDBOX_EXECUTION_MAX_STORED_OUTPUT_CHARS = 200000; /** Test-only: forget the memoized init so a fresh in-memory DB re-creates. */ export declare function resetSandboxExecutionsStoreForTests(): void; /** Enqueue a new execution row in `queued` state. */ export declare function createSandboxExecution(input: CreateSandboxExecutionInput): Promise; /** Owner-scoped read for user-facing status/result surfaces. */ export declare function getSandboxExecutionForOwner(id: string, owner: string): Promise; /** * Unscoped read for the trusted executor/sweep paths ONLY. Never expose this * through a user-facing action — use `getSandboxExecutionForOwner`. */ export declare function getSandboxExecutionInternal(id: string): Promise; /** * Atomically claim an execution for a single executor. Claims a `queued` row, * or reclaims a `running` row whose lease expired, as long as the attempt * budget is not exhausted. Returns the claimed row (with the new claim token) * or null when another executor holds it / it is terminal / attempts ran out. */ export declare function claimSandboxExecution(id: string, claimToken: string, leaseMs: number, now?: number): Promise; /** Heartbeat: extend the lease while the claimed execution is still running. */ export declare function renewSandboxExecutionLease(id: string, claimToken: string, leaseMs: number, now?: number): Promise; /** * Write the terminal result. Single-writer safe: only the executor whose * claim token still matches the row (i.e. it was not reclaimed after a lease * expiry) can finalize. Returns false when the row was already finalized or * reclaimed — the caller must discard its result. */ export declare function finalizeSandboxExecution(id: string, claimToken: string, input: FinalizeSandboxExecutionInput, now?: number): Promise; /** * Reap a `running` row whose lease expired after its attempt budget was * exhausted, so it never sits "running" forever. Atomic: guarded on the same * expiry + budget conditions, so a live executor (fresh lease) is untouched. */ export declare function failExpiredSandboxExecution(id: string, error: string, now?: number): Promise; export interface DueSandboxExecution { id: string; status: SandboxExecutionStatus; attemptCount: number; maxAttempts: number; leaseExpiresAt: number | null; } /** * List rows that need driving: `queued` rows that have sat unclaimed longer * than `queuedOlderThanMs` (their enqueue-time dispatch was likely lost), and * `running` rows whose lease expired (executor died). Used by the sweep and * the poll-time drain. Deliberately does NOT call `ensureTable()` — the sweep * must stay a zero-footprint no-op on deployments that never enqueue; callers * treat a missing-table error as "nothing due". */ export declare function listDueSandboxExecutions(options: { limit?: number; queuedOlderThanMs?: number; now?: number; }): Promise; //# sourceMappingURL=executions-store.d.ts.map