feat(db): add cloud_workspaces for sandbox-backed workspaces

Closed
SA
3mo ago
+7071-14

First of five PRs toward spawning cloud workspaces from the desktop app. Schema only — no behaviour, nothing reads or writes this table yet.

cloud_workspaces

A cloud workspace is one workspace, one sandbox, and no v2_hosts row. Registering sandboxes as hosts would put them in the device picker and require each to hold an outbound relay socket, which fights the provider's wake-on-inbound sleep. Clients instead reach a sandbox directly at sandbox_url with a short-lived token brokered by the cloud (PR 2).

provider + provider_sandbox_id are unique together: the identifier is the provider's, not a machine_id we mint. (Blaxel happens to address sandboxes by name, so this is what its API calls name.)

sandbox_url is nullable because it doesn't exist until provisioning finishes — it moves in lockstep with status. It's named for what it is rather than Blaxel's "preview URL" vocabulary.

Status enum

Four states: provisioning · ready · failed · deleted.

Deliberately no sleep/wake states. Blaxel wakes a sandbox on the first inbound connection, so "asleep" is invisible to callers and is a status we could only ever report stale. ready means provisioned and addressable, awake or not.

Two pieces of cruft removed

  • sandboxStatusValues — an 11-state enum (pending · spawning · connecting · warming · syncing · ready · running · stale · snapshotting · stopped · failed) from the deleted sandbox_images era. Replaced by the four-state enum above.
  • Its one real consumer turned out to be zod.ts, which types v1 workspaces.config and references Modal sandbox ids — an even earlier provider attempt. I nearly deleted the file as dead; it isn't, until the v1 workspaces table drops. The status enum is now inlined there so enums.ts doesn't carry an 11-state enum for a single v1 consumer, and both retire together.

Verification

typecheck 0 · lint 0 · migration generated by drizzle-kit generate (not hand-edited) · journal ordering 0073 → 0074 confirmed increasing, so the timestamp gate can't silently skip it.

Not applied to any database. Regenerated three times as main took 0070–0073 while this stack was open.