|
| 1 | +/** |
| 2 | + * Controlled live proof for a configured ONEVibe + ONEComputer deployment. |
| 3 | + * It is intentionally never run as part of unit tests: it creates a real |
| 4 | + * disposable sandbox and requires server-side deployment credentials. |
| 5 | + */ |
| 6 | +const baseUrl = (process.env.ONEVIBE_E2E_URL ?? 'http://127.0.0.1:5173').replace(/\/$/, '') |
| 7 | +const timeoutMs = Math.max(60_000, Number(process.env.ONEVIBE_E2E_TIMEOUT_MS ?? 20 * 60_000)) |
| 8 | +const requireGateway = process.env.ONEVIBE_E2E_REQUIRE_GATEWAY === 'true' |
| 9 | +const requireVisual = process.env.ONEVIBE_E2E_REQUIRE_VISUAL !== 'false' |
| 10 | + |
| 11 | +type Snapshot = { |
| 12 | + id: string |
| 13 | + status: string |
| 14 | + securityContext?: { executionBoundary?: string; gatewayEnforced?: boolean; sandboxState?: string } |
| 15 | + events: Array<{ type: string; label?: string; payload: Record<string, unknown> }> |
| 16 | +} |
| 17 | + |
| 18 | +const request = async <T>(pathname: string, init?: RequestInit) => { |
| 19 | + let response: Response |
| 20 | + try { |
| 21 | + response = await fetch(`${baseUrl}${pathname}`, { ...init, headers: { 'Content-Type': 'application/json', ...init?.headers } }) |
| 22 | + } catch (error) { |
| 23 | + throw new Error(`Cannot reach ONEVibe at ${baseUrl}${pathname}: ${error instanceof Error ? error.message : 'network failure'}`) |
| 24 | + } |
| 25 | + const body = await response.json().catch(() => ({})) as T & { error?: string } |
| 26 | + if (!response.ok) throw new Error(`${pathname} returned ${response.status}${body.error ? `: ${body.error}` : ''}`) |
| 27 | + return body |
| 28 | +} |
| 29 | + |
| 30 | +const waitForTerminalSnapshot = async (taskId: string) => { |
| 31 | + const deadline = Date.now() + timeoutMs |
| 32 | + let latest: Snapshot | undefined |
| 33 | + while (Date.now() < deadline) { |
| 34 | + latest = await request<Snapshot>(`/api/tasks/${encodeURIComponent(taskId)}`) |
| 35 | + if (['completed', 'failed', 'cancelled'].includes(latest.status)) return latest |
| 36 | + await new Promise((resolve) => setTimeout(resolve, 2_000)) |
| 37 | + } |
| 38 | + throw new Error(`Task ${taskId} did not reach a terminal state within ${timeoutMs}ms (last state: ${latest?.status ?? 'unreadable'})`) |
| 39 | +} |
| 40 | + |
| 41 | +const main = async () => { |
| 42 | + const readiness = await request<{ providers: Array<{ id: string; available: boolean; detail: string }> }>('/api/runtime') |
| 43 | + const sandbox = readiness.providers.find((provider) => provider.id === 'onecomputer') |
| 44 | + if (!sandbox?.available) throw new Error(`ONEComputer is not available at ${baseUrl}: ${sandbox?.detail ?? 'runtime status unavailable'}`) |
| 45 | + const created = await request<{ id: string }>('/api/tasks', { |
| 46 | + method: 'POST', |
| 47 | + body: JSON.stringify({ |
| 48 | + prompt: 'Create a small accessible governed website with index.html and a concise README. Do not publish or call external services.', |
| 49 | + provider: 'onecomputer', |
| 50 | + mode: 'website', |
| 51 | + projectId: 'project_onevibe', |
| 52 | + references: [], attachments: [], skills: ['web_build', 'security_review'], |
| 53 | + }), |
| 54 | + }) |
| 55 | + const task = await waitForTerminalSnapshot(created.id) |
| 56 | + if (task.status !== 'completed') throw new Error(`ONEComputer task ${task.id} ended ${task.status}`) |
| 57 | + if (task.securityContext?.executionBoundary !== 'onecomputer_sandbox') throw new Error('Task did not record the ONEComputer sandbox execution boundary') |
| 58 | + if (requireGateway && task.securityContext?.gatewayEnforced !== true) throw new Error('Gateway attestation was required but not recorded') |
| 59 | + if (task.securityContext?.sandboxState !== 'destroyed') throw new Error(`Expected ephemeral sandbox destruction, found ${task.securityContext?.sandboxState ?? 'unknown'}`) |
| 60 | + if (!task.events.some((event) => event.label === 'ONEComputer sandbox ready')) throw new Error('Sandbox readiness event missing') |
| 61 | + if (requireVisual && !task.events.some((event) => event.payload.kind === 'visual_frame')) throw new Error('Required X11 visual evidence missing') |
| 62 | + const preview = await request<{ content: string }>(`/api/tasks/${encodeURIComponent(task.id)}/file?path=index.html`) |
| 63 | + if (!preview.content.includes('<')) throw new Error('Expected portable index.html was not extracted') |
| 64 | + const evidence = await request<{ valid: boolean }>(`/api/tasks/${encodeURIComponent(task.id)}/evidence`) |
| 65 | + if (!evidence.valid) throw new Error('Evidence chain verification failed') |
| 66 | + console.log(JSON.stringify({ taskId: task.id, status: task.status, gatewayEnforced: task.securityContext?.gatewayEnforced === true, visualEvidence: task.events.filter((event) => event.payload.kind === 'visual_frame').length, evidenceValid: evidence.valid }, null, 2)) |
| 67 | +} |
| 68 | + |
| 69 | +main().catch((error: unknown) => { |
| 70 | + console.error(error instanceof Error ? error.message : error) |
| 71 | + process.exitCode = 1 |
| 72 | +}) |
0 commit comments