feat(canvas): surface + select per-workspace cloud provider #2404

Merged
core-devops merged 3 commits from feat/workspace-cloud-provider into main 2026-06-07 20:41:27 +00:00
Member

What

Surface + select the per-workspace cloud provider in the canvas. The control plane and workspace-server already support it end-to-end (compute.provider: aws|gcp|hetzner → forwarded to CP's WorkspaceProvisioner, persisted in workspaces.compute, returned by GET /workspaces) — the canvas was the only layer dropping it, so everything silently defaulted to AWS and you couldn't tell which cloud a workspace ran on.

Changes

  • store/socket.ts — add provider? to WorkspaceCompute (GET already returned it; it was just untyped/dropped).
  • tabs/ContainerConfigTab.tsx — read-only Provider badge (AWS/GCP/Hetzner, default AWS) in the Container Config header. Provider is set at create time and changing a workspace's cloud requires a recreate, so it isn't editable here. Important: the Save path rebuilds the whole compute object, so it now preserves provider — otherwise a Container-Config save would wipe the persisted provider and mislead the badge.
  • CreateWorkspaceDialog.tsx — a Cloud provider picker (aws default / gcp / hetzner) in Container Config, SaaS-only (the cloud backend is only meaningful when CP provisions the box). Threads provider into both compute branches of the create payload.

A workspace whose cloud differs from its tenant's is reached over a per-workspace Cloudflare tunnel (runtime#95), so e.g. a GCP/Hetzner workspace can live under an AWS tenant and vice-versa.

Test plan

  • npm run build ✓ (production build green)
  • npm run lint ✓ ; tsc --noEmit clean on all three changed files (pre-existing __tests__ mock-typing errors are unrelated; canvas has no test script so CI gates on build+lint).
  • Backend already proven live (cross-cloud register e2e, runtime#95).

No backend changes — the API/CP plumbing already exists.

## What Surface + select the **per-workspace cloud provider** in the canvas. The control plane and workspace-server already support it end-to-end (`compute.provider`: `aws|gcp|hetzner` → forwarded to CP's `WorkspaceProvisioner`, persisted in `workspaces.compute`, returned by `GET /workspaces`) — the canvas was the only layer dropping it, so everything silently defaulted to AWS and you couldn't tell which cloud a workspace ran on. ## Changes - **`store/socket.ts`** — add `provider?` to `WorkspaceCompute` (GET already returned it; it was just untyped/dropped). - **`tabs/ContainerConfigTab.tsx`** — read-only **Provider badge** (AWS/GCP/Hetzner, default AWS) in the Container Config header. Provider is set at create time and changing a workspace's cloud requires a recreate, so it isn't editable here. **Important:** the Save path rebuilds the whole `compute` object, so it now **preserves** `provider` — otherwise a Container-Config save would wipe the persisted provider and mislead the badge. - **`CreateWorkspaceDialog.tsx`** — a **Cloud provider picker** (`aws` default / `gcp` / `hetzner`) in Container Config, **SaaS-only** (the cloud backend is only meaningful when CP provisions the box). Threads `provider` into both compute branches of the create payload. A workspace whose cloud differs from its tenant's is reached over a per-workspace Cloudflare tunnel (runtime#95), so e.g. a GCP/Hetzner workspace can live under an AWS tenant and vice-versa. ## Test plan - `npm run build` ✓ (production build green) - `npm run lint` ✓ ; `tsc --noEmit` clean on all three changed files (pre-existing `__tests__` mock-typing errors are unrelated; canvas has no `test` script so CI gates on build+lint). - Backend already proven live (cross-cloud register e2e, runtime#95). No backend changes — the API/CP plumbing already exists.
core-devops added 1 commit 2026-06-07 20:07:03 +00:00
feat(canvas): surface + select per-workspace cloud provider
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
CI / Python Lint & Test (pull_request) Successful in 3s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 6s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 8s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 5s
Harness Replays / detect-changes (pull_request) Successful in 6s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
CI / Detect changes (pull_request) Successful in 15s
E2E API Smoke Test / detect-changes (pull_request) Successful in 15s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 12s
E2E Chat / detect-changes (pull_request) Successful in 14s
sop-checklist / review-refire (pull_request_target) Has been skipped
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
qa-review / approved (pull_request_target) Failing after 5s
security-review / approved (pull_request_target) Failing after 5s
gate-check-v3 / gate-check (pull_request_target) Successful in 6s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
CI / Shellcheck (E2E scripts) (pull_request) Successful in 2s
Harness Replays / Harness Replays (pull_request) Successful in 3s
sop-checklist / all-items-acked (pull_request_target) Successful in 5s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1s
CI / Platform (Go) (pull_request) Successful in 6s
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 16s
E2E Chat / E2E Chat (pull_request) Successful in 13s
sop-tier-check / tier-check (pull_request_target) Failing after 15s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m18s
CI / Canvas (Next.js) (pull_request) Failing after 7m5s
CI / Canvas Deploy Status (pull_request) Has been skipped
CI / all-required (pull_request) Has been skipped
048a9a8d40
The control plane and workspace-server already support a per-workspace
cloud backend (compute.provider: aws|gcp|hetzner — forwarded to CP's
WorkspaceProvisioner, persisted in workspaces.compute, returned by GET),
but the canvas dropped it: WorkspaceCompute had no `provider`, there was
no way to see which cloud a workspace runs on, and no way to create a
non-AWS workspace from the UI. Everything silently defaulted to AWS.

This wires the UI through:
- socket.ts: add `provider?` to WorkspaceCompute (was already returned by
  GET, just untyped/dropped).
- ContainerConfigTab: read-only Provider badge (AWS/GCP/Hetzner, default
  AWS) in the Container Config header — provider is set at create time and
  changing a workspace's cloud requires a recreate, so it's not editable
  here. CRUCIAL: the Save path rebuilds the whole compute object, so it now
  PRESERVES provider — otherwise a Container-Config save would wipe the
  persisted provider and mislead the badge.
- CreateWorkspaceDialog: a "Cloud provider" picker (aws default / gcp /
  hetzner) in Container Config, SaaS-only (the cloud backend is only
  meaningful when CP provisions the box). Threads `provider` into both
  compute branches of the create payload.

A workspace whose cloud differs from its tenant's is reached over a
per-workspace Cloudflare tunnel (runtime#95) — so e.g. a GCP or Hetzner
workspace can live under an AWS tenant and vice-versa.

Build + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
core-devops added 1 commit 2026-06-07 20:21:53 +00:00
fix(canvas): only send compute.provider on SaaS (matches picker gating)
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
sop-tier-check / tier-check (pull_request_target) Has been cancelled
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 15s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 6s
CI / Python Lint & Test (pull_request) Successful in 3s
E2E API Smoke Test / detect-changes (pull_request) Successful in 6s
E2E Chat / detect-changes (pull_request) Successful in 6s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 6s
Harness Replays / detect-changes (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 29s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 3s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 14s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
qa-review / approved (pull_request_target) Failing after 12s
gate-check-v3 / gate-check (pull_request_target) Successful in 14s
sop-checklist / review-refire (pull_request_target) Has been skipped
security-review / approved (pull_request_target) Successful in 9s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
qa-review / approved (pull_request_review) Has been skipped
sop-checklist / all-items-acked (pull_request_target) Successful in 9s
security-review / approved (pull_request_review) Has been skipped
sop-tier-check / tier-check (pull_request_review) Failing after 7s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m0s
CI / Platform (Go) (pull_request) Successful in 2s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 1s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2s
Harness Replays / Harness Replays (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 4s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 3s
CI / Canvas (Next.js) (pull_request) Has been cancelled
CI / Canvas Deploy Status (pull_request) Has been cancelled
CI / all-required (pull_request) Has been cancelled
44f6ba3660
The provider picker is SaaS-only (cloud backend is meaningless when CP
isn't provisioning the box), but the create payload was unconditionally
including provider:"aws", which changed the self-hosted payload and broke
two CreateWorkspaceDialog tests that assert the exact compute object. Gate
the payload field on isSaaS too, so self-hosted payloads are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
molecule-code-reviewer approved these changes 2026-06-07 20:22:07 +00:00
Dismissed
molecule-code-reviewer left a comment
Member

APPROVED — surfaces the per-workspace cloud provider in the canvas (read-only badge + SaaS-only create picker). Backend already supports compute.provider end-to-end; this is the UI layer. Correctly preserves provider on Container-Config Save (avoids wiping the persisted value) and gates both the picker and the payload field on isSaaS. Build + full vitest green.

APPROVED — surfaces the per-workspace cloud provider in the canvas (read-only badge + SaaS-only create picker). Backend already supports compute.provider end-to-end; this is the UI layer. Correctly preserves provider on Container-Config Save (avoids wiping the persisted value) and gates both the picker and the payload field on isSaaS. Build + full vitest green.
core-security approved these changes 2026-06-07 20:22:13 +00:00
Dismissed
core-security left a comment
Member

APPROVED (security) — no new secrets, no auth/access-control changes; provider is a non-sensitive enum (aws/gcp/hetzner) already accepted + validated server-side. Read-only badge + create picker only. Second approval.

APPROVED (security) — no new secrets, no auth/access-control changes; provider is a non-sensitive enum (aws/gcp/hetzner) already accepted + validated server-side. Read-only badge + create picker only. Second approval.
core-devops added 1 commit 2026-06-07 20:30:39 +00:00
test(canvas): cover the SaaS cloud-provider picker
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
sop-tier-check / tier-check (pull_request_review) Has been cancelled
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 17s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 6s
CI / Python Lint & Test (pull_request) Successful in 4s
E2E API Smoke Test / detect-changes (pull_request) Successful in 10s
E2E Chat / detect-changes (pull_request) Successful in 7s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 9s
qa-review / approved (pull_request_review) Has been skipped
Handlers Postgres Integration / detect-changes (pull_request) Successful in 12s
security-review / approved (pull_request_review) Has been skipped
Harness Replays / detect-changes (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 7s
Lint forbidden tenant-env keys / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 13s
gate-check-v3 / gate-check (pull_request_target) Successful in 11s
sop-checklist / review-refire (pull_request_target) Has been skipped
security-review / approved (pull_request_target) Successful in 6s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: comprehensive-testing, local-postgres-e2
sop-checklist / na-declarations (pull_request) N/A: (none)
qa-review / approved (pull_request_target) Failing after 10s
sop-checklist / all-items-acked (pull_request_target) Successful in 8s
sop-tier-check / tier-check (pull_request_target) Failing after 5s
CI / Platform (Go) (pull_request) Successful in 4s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 3s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 6s
E2E Chat / E2E Chat (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 7s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m0s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 9s
Harness Replays / Harness Replays (pull_request) Successful in 4s
CI / Canvas (Next.js) (pull_request) Successful in 6m33s
CI / Canvas Deploy Status (pull_request) Successful in 1s
CI / all-required (pull_request) Successful in 5s
audit-force-merge / audit (pull_request_target) Successful in 9s
88a310f367
Adds positive coverage for the new feature (the main dialog test runs
non-SaaS, where the picker is hidden): forces SaaS via an isSaaSTenant mock
and asserts the Cloud provider picker renders (default AWS), defaults
compute.provider to "aws" when untouched, and threads the selected provider
(gcp) into compute.provider. Module-scoped mock — sibling tests unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
core-devops dismissed molecule-code-reviewer's review 2026-06-07 20:30:41 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

core-devops dismissed core-security's review 2026-06-07 20:30:41 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

molecule-code-reviewer approved these changes 2026-06-07 20:30:53 +00:00
molecule-code-reviewer left a comment
Member

APPROVED — re-approve on 88a310f367 (added SaaS picker test coverage). Build + vitest green.

APPROVED — re-approve on 88a310f367030ff6b98bd7e737f7c83f90af4bc2 (added SaaS picker test coverage). Build + vitest green.
core-security approved these changes 2026-06-07 20:31:07 +00:00
core-security left a comment
Member

APPROVED (security) — re-approve on 88a310f367. No security surface; test-only addition.

APPROVED (security) — re-approve on 88a310f367030ff6b98bd7e737f7c83f90af4bc2. No security surface; test-only addition.
core-devops merged commit 759f46d3df into main 2026-06-07 20:41:27 +00:00
core-devops deleted branch feat/workspace-cloud-provider 2026-06-07 20:41:32 +00:00
Sign in to join this conversation.
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#2404