From a4d6079510673ba4edb63cbcfebf77a6c169e7df Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sun, 8 Nov 2026 22:26:16 -0800 Subject: [PATCH] fix(cloud): live Vercel Pro findings (bundle download, rehearsal cap) + results (#16) * fix(cloud): provision bundle download no longer overwrites its source; shortened session deadline for staging rehearsals * docs(cloud): live Vercel Pro results --- .../sandbox-control/image/provision-golden.sh | 7 ++++--- cloud/apps/sandbox-control/src/env.ts | 2 ++ cloud/apps/sandbox-control/src/index.ts | 4 +++- cloud/apps/sandbox-control/src/lifecycle.ts | 9 ++++++--- cloud/apps/sandbox-control/src/reconcile.ts | 2 +- cloud/docs/vercel-driver-design.md | 18 ++++++++++++++++++ 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/cloud/apps/sandbox-control/image/provision-golden.sh b/cloud/apps/sandbox-control/image/provision-golden.sh index c5cfa6e6a26..9d54d8c9ec7 100755 --- a/cloud/apps/sandbox-control/image/provision-golden.sh +++ b/cloud/apps/sandbox-control/image/provision-golden.sh @@ -28,9 +28,10 @@ fetch_verified() { # url sha256 dest } log 'orcad bundle' -fetch_verified "$ORCAD_BUNDLE_URL" "$ORCAD_BUNDLE_SHA256" /tmp/orcad.tgz -tar -xzf /tmp/orcad.tgz -C "$ORCA_HOME/runtime" -rm -f /tmp/orcad.tgz +bundle="$(mktemp -d)/orcad.tgz" +fetch_verified "$ORCAD_BUNDLE_URL" "$ORCAD_BUNDLE_SHA256" "$bundle" +tar -xzf "$bundle" -C "$ORCA_HOME/runtime" +rm -rf "$(dirname "$bundle")" test -s "$ORCA_HOME/runtime/orcad/orcad-server.js" node="$(find "$ORCA_HOME/runtime/runtimes" -type f -name node | head -n 1)" test -x "$node" diff --git a/cloud/apps/sandbox-control/src/env.ts b/cloud/apps/sandbox-control/src/env.ts index 2ce0646fd46..12ba480162e 100644 --- a/cloud/apps/sandbox-control/src/env.ts +++ b/cloud/apps/sandbox-control/src/env.ts @@ -30,4 +30,6 @@ export type Env = { /** Public URL of this Worker's /v1/egress/proxy; Vercel forwardURL rules point here. */ EGRESS_PROXY_URL?: string + /** Shortened session deadline (ms) for staging rollover rehearsals; refused when SANDBOX_ENV=prod. */ + SESSION_DEADLINE_MS?: string } diff --git a/cloud/apps/sandbox-control/src/index.ts b/cloud/apps/sandbox-control/src/index.ts index 9506b2a6981..27882421a65 100644 --- a/cloud/apps/sandbox-control/src/index.ts +++ b/cloud/apps/sandbox-control/src/index.ts @@ -77,7 +77,9 @@ function lifecycleDeps(env: Env, deps: Deps): LifecycleDeps | null { environment: env.SANDBOX_ENV, goldenSnapshotId: env.GOLDEN_SNAPSHOT_ID, defaultRegion: env.VERCEL_REGION ?? 'iad1', - egressProxyUrl: env.EGRESS_PROXY_URL + egressProxyUrl: env.EGRESS_PROXY_URL, + sessionDeadlineMs: + env.SESSION_DEADLINE_MS && env.SANDBOX_ENV !== 'prod' ? Number(env.SESSION_DEADLINE_MS) : undefined } } } diff --git a/cloud/apps/sandbox-control/src/lifecycle.ts b/cloud/apps/sandbox-control/src/lifecycle.ts index c360f450e05..9abed5636b9 100644 --- a/cloud/apps/sandbox-control/src/lifecycle.ts +++ b/cloud/apps/sandbox-control/src/lifecycle.ts @@ -35,6 +35,8 @@ export type LifecycleDeps = { goldenSnapshotId: string defaultRegion: string egressProxyUrl?: string + /** Staging rehearsals shorten the cap; production uses SESSION_DEADLINE_MS. */ + sessionDeadlineMs?: number } } @@ -80,12 +82,13 @@ function egressPolicy(deps: LifecycleDeps, row: SandboxRow) { return compileNetworkPolicy({ mode: 'allowlist', credentials }) } -export function sessionFields(state: ProviderSandboxState, now: number): SessionFields { +export function sessionFields(deps: LifecycleDeps, state: ProviderSandboxState): SessionFields { + const now = deps.now() const startedAt = state.session.startedAt ?? now return { session_id: state.session.id, session_started_at: startedAt, - session_deadline_at: startedAt + SESSION_DEADLINE_MS, + session_deadline_at: startedAt + (deps.config.sessionDeadlineMs ?? SESSION_DEADLINE_MS), lease_expires_at: startedAt + state.session.timeoutMs, usage_settled_at: startedAt } @@ -116,7 +119,7 @@ async function boot(deps: LifecycleDeps, row: SandboxRow, state: ProviderSandbox repo, githubPlaceholder: deps.config.egressProxyUrl ? githubPlaceholderToken(row) : undefined }) - const running = await claim(deps, row, { status: 'running', last_error: null, ...sessionFields(state, deps.now()) }) + const running = await claim(deps, row, { status: 'running', last_error: null, ...sessionFields(deps, state) }) return { row: running, connection } } diff --git a/cloud/apps/sandbox-control/src/reconcile.ts b/cloud/apps/sandbox-control/src/reconcile.ts index 88299ed3a21..81fde8737b7 100644 --- a/cloud/apps/sandbox-control/src/reconcile.ts +++ b/cloud/apps/sandbox-control/src/reconcile.ts @@ -85,7 +85,7 @@ async function reconcileRunning(deps: LifecycleDeps, row: SandboxRow, summary: R async function repairTransition(deps: LifecycleDeps, row: SandboxRow): Promise { const state = await deps.provider.getSandbox(row.provider_name, { resume: false }) if (state?.session.status === 'running') { - await transition(deps.db, row, { status: 'running', ...sessionFields(state, deps.now()) }, deps.now()) + await transition(deps.db, row, { status: 'running', ...sessionFields(deps, state) }, deps.now()) return } await markProviderStopped(deps, row) diff --git a/cloud/docs/vercel-driver-design.md b/cloud/docs/vercel-driver-design.md index 36fa3286751..911d92ab376 100644 --- a/cloud/docs/vercel-driver-design.md +++ b/cloud/docs/vercel-driver-design.md @@ -162,6 +162,24 @@ Compiled per sandbox by `compileNetworkPolicy()`: recipe adapter; start one scheduler per ready hosted runtime (stop on suspend/destroy); an orcad RPC exposing the idle-probe verdict + last PTY input for `readActivity`; header warning + mobile push for `warn`. +## Live results (2026-11-09, Vercel Pro team `lovecast`, project `orca-sandbox-control`, iad1) + +Driven by the real Worker handler in-process (real Vercel REST, node:sqlite D1, stub billing) and the real desktop +driver/scheduler. Base OS is Amazon Linux 2023 (dnf), not Ubuntu. +- Golden image: orcad built from public stablyai/orca@7c14832 in a 8 vCPU build sandbox in 98s (67 MB tgz); + provision 14.3s (gh 2.103.3, Claude Code 2.1.319, Codex 0.166.0; orcad loopback smoke passed); wipe ok; + snapshot 8.2s. Bug found and fixed: provision downloaded the bundle over its own source path. +- Snapshot → orcad ready (`POST /v1/sandboxes`, incl. Vercel create + orca-sandbox-start): 5.4 / 4.1 / 4.9 s; WS open. +- Suspend (drain + stop) 7.6s; resume → ready 6.0s; same endpoint and same orcad host key (no re-pair). +- Shortened cap (6 min): scheduler warned T−15, then forced rollover at the deadline in 17.3s (drain, stop, boot, + orcad ready), reconnect WS open, new deadline set. Destroy 0.4–0.7s; zero sandboxes/snapshots left. +- Egress: allowlist resolves npm (200), `example.com` does not resolve (curl rc 6). Policy update 0.25s. + Placeholder → injected fake GitHub credential reaches GitHub (curl/gh `token` scheme: 401 Bad credentials); + attacker token and placeholder-less git → 403 `orca-egress-denied`. Node fetch under a transform rule still hangs + on Pro; `orca-egress-probe` reports `egress_framing_hang` (npm, untransformed: ok). No real GitHub token was used, + so a successful authenticated push is still unverified. +- Not covered live: forwardURL egress proxy (needs a deployed Worker URL), agent auto-resume with a real chat. + ## What needs live access (not done) Vercel Pro team id + project id + a Sandbox-scoped token (as Worker secrets), wrangler auth for a dev D1, a