Files
orca/cloud/apps/sandbox-control/migrations/0008_environments.sql
T
Neil c1cf0cb760 ## What (#49)
Control-plane half of W1, org environment builds (Conductor "Cloud Computer" parity). The desktop settings UI, CLI and disk banner follow in a second PR.

- **Org environment**: repos (GitHub, up to 50, each with a default branch), an install script, env var names, and a machine size (Standard 4/8 or Large 8/16; Large bills 2× through the existing usage path). Members can read; owners and admins edit. Edits use optimistic concurrency.
- **Versioned builds** (`#N`), with status, live log stream (ndjson), stored log, failure step, duration and snapshot.
  - Each build boots a throwaway VM from the active golden image, clones every repo, and runs the install script once with `set -euo pipefail`.
  - It records commits and lockfile blob ids, checks that orcad survived, then saves a manual snapshot.
- **Failed builds never replace the active build.** Rollback is instant (`activate`). The newest 5 builds plus the active one keep their snapshots.
- **New workspaces boot from the active build** (or a pinned `environmentBuildId`). Existing workspaces keep their build, and destroying a workspace never deletes the shared image.
  - A first-boot prelude links the prebuilt repo as the project and fast-forwards the requested branch.
  - A post-checkout hook hard-links the installed dependency folders into each new Orca worktree (about 250 ms).
- **Rebuild prompt** (`/staleness`): reports a lockfile change, a moved default branch, a config edit, or a newer base image. It uses the GitHub compare API, or anonymous smart-HTTP plus raw blob hashing when the REST API refuses Worker IPs.
- **Repos with no commits** build and open; Orca adds an empty first commit.
- **Hardening from the review**:
  - Recovers a snapshot whose finalize was cut off (adopted by box name).
  - Bounded log reads on cancel/timeout.
  - Org secret values are redacted from live and stored logs.
  - Activation is guarded in SQL.
  - Build-number races return 409.
- Migration `0008_environments.sql`: number claimed in /tmp/vdrv/migrations.claims and already applied to dev D1. It is additive.

## Evidence (real Vercel VMs)

Full write-up: `.context/cloud/reports/w1-env-builds.md`.

- vercel/ms build: 15.9 s (boot 1.8 s, clone+install 6 s, snapshot 8 s).
- Private `nwparker/orca-sandbox-push-test` plus public `vercel/ms` in one environment on the shared dev preview: build succeeded in 18 s, and a workspace created from it was ready in 6.3 s.
- Create → orcad ready:

  | Boots from | p50 | p95 |
  |---|---|---|
  | Environment build (n=10, deps installed) | 4.37 s | 8.41 s |
  | Bare golden image (n=9) | 4.79 s | 7.61 s |

- Rollback, creating from build N-1, an empty repo (`rsc/empty`), and staleness `deps_changed` → Rebuild → `up_to_date` were all verified live.

## Tests

`cloud/apps/sandbox-control`: 99 vitest tests and typecheck pass. The new `test/environments.test.ts` covers RBAC, build/fail/keep-last-good, concurrent build 409, cancel, live stream, boot from active and pinned builds, rollback, prune, interrupted-snapshot recovery, and staleness.
2026-11-09 05:45:23 -08:00

74 lines
3.0 KiB
SQL

-- Org environments ("Cloud Computer"): repos, an install script and env var names, built into a
-- versioned snapshot new workspaces boot from. Builds never replace the active one unless they
-- succeed, and any kept build can be re-activated (rollback).
CREATE TABLE environments (
id TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
name TEXT NOT NULL,
-- JSON [{"url":"https://github.com/o/r.git","defaultBranch":"main"|null}]
repos TEXT NOT NULL,
install_script TEXT NOT NULL,
-- JSON ["NPM_TOKEN", ...]: org secrets with these names reach the install script.
env_var_names TEXT NOT NULL,
machine_size TEXT NOT NULL CHECK (machine_size IN ('standard', 'large')),
active_build_id TEXT,
-- Bumped on every config edit; a build records the revision it was made from.
revision INTEGER NOT NULL DEFAULT 1,
next_build_number INTEGER NOT NULL DEFAULT 1,
created_by TEXT NOT NULL,
updated_by TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
deleted_at INTEGER
);
CREATE UNIQUE INDEX environments_account_name ON environments (account_id, name) WHERE deleted_at IS NULL;
CREATE TABLE environment_builds (
id TEXT PRIMARY KEY,
environment_id TEXT NOT NULL REFERENCES environments (id),
account_id TEXT NOT NULL,
number INTEGER NOT NULL,
-- starting | running | snapshotting | succeeded | failed | canceled
status TEXT NOT NULL,
-- Frozen config the build ran with (repos, script, env var names, machine size, revision).
config TEXT NOT NULL,
base_snapshot_id TEXT NOT NULL,
provider_name TEXT NOT NULL UNIQUE,
session_id TEXT,
command_id TEXT,
snapshot_id TEXT,
-- JSON from the VM: per-repo commit + lockfile blob ids, disk use.
result TEXT,
-- Short failure code (`clone_failed`, `install_exit_2`, `timeout`, ...) and the step it hit.
error TEXT,
failed_step TEXT,
log_bytes INTEGER NOT NULL DEFAULT 0,
log_truncated INTEGER NOT NULL DEFAULT 0,
created_by TEXT NOT NULL,
created_at INTEGER NOT NULL,
started_at INTEGER,
-- When the install finished and the snapshot began (the build timeline's last step).
snapshot_started_at INTEGER,
finished_at INTEGER,
updated_at INTEGER NOT NULL,
generation INTEGER NOT NULL DEFAULT 0,
-- Old builds beyond the kept window lose their snapshot; the record (and log) stays.
snapshot_deleted_at INTEGER,
UNIQUE (environment_id, number)
);
CREATE INDEX environment_builds_env ON environment_builds (environment_id, number);
CREATE INDEX environment_builds_status ON environment_builds (status, updated_at);
-- Build output, stored once the build ends (live output streams straight from the VM).
CREATE TABLE environment_build_logs (
build_id TEXT NOT NULL REFERENCES environment_builds (id),
seq INTEGER NOT NULL,
data TEXT NOT NULL,
PRIMARY KEY (build_id, seq)
);
-- Which build a workspace sandbox booted from: a rebuild never touches live workspaces.
ALTER TABLE sandboxes ADD COLUMN environment_id TEXT;
ALTER TABLE sandboxes ADD COLUMN environment_build_id TEXT;
ALTER TABLE sandboxes ADD COLUMN source_snapshot_id TEXT;