feat(db): add cloud_workspaces for sandbox-backed workspaces
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 deletedsandbox_imagesera. Replaced by the four-state enum above.- Its one real consumer turned out to be
zod.ts, which types v1workspaces.configand references Modal sandbox ids — an even earlier provider attempt. I nearly deleted the file as dead; it isn't, until the v1workspacestable drops. The status enum is now inlined there soenums.tsdoesn'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.