Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/v1-move-freeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@executor-js/sdk": patch
"@executor-js/api": patch
"@executor-js/cloud": patch
---

Hosts can pause every change to a workspace with `workspaceWriteGate`: connection and OAuth writes, OAuth completion (before the code is exchanged) and OAuth token refreshes are refused with the host's message, while reads and tool calls keep working. The OAuth callback popup now shows why a refusal the user can act on happened. Executor Cloud uses this to freeze an organization while it moves to Executor v2, refuses changes to it once it has moved, and opens a moved organization's dashboard in v2.
32 changes: 32 additions & 0 deletions apps/cloud/drizzle/0022_org_moved_to_v2_slug.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- `org_moved_to_v2` gains the v2 organization's slug (v2 writes it with
-- `moved`; the v1 dashboard redirects to `/org/<slug>` on v2) and a third
-- state, `rolled_back` (v2 gave the organization back; v1 serves it as if
-- there were no row). The constraint swap is safe online: the table holds at
-- most one row per organization that started a move.
ALTER TABLE "org_moved_to_v2" DROP CONSTRAINT "org_moved_to_v2_state_check";--> statement-breakpoint
ALTER TABLE "org_moved_to_v2" ADD COLUMN "v2_org_slug" text;--> statement-breakpoint
ALTER TABLE "org_moved_to_v2" ADD CONSTRAINT "org_moved_to_v2_state_check" CHECK ("org_moved_to_v2"."state" in ('moving', 'moved', 'rolled_back'));--> statement-breakpoint
-- v2 writes this table as `v1_import_writer`, with
-- INSERT INTO org_moved_to_v2 (org_id, state, v2_org_id, v2_org_slug) VALUES (...)
-- ON CONFLICT (org_id) DO UPDATE SET state = excluded.state,
-- v2_org_id = excluded.v2_org_id, v2_org_slug = excluded.v2_org_slug
-- which needs INSERT and UPDATE on the table and SELECT on the columns the
-- upsert names (the conflict target and the `excluded` reads). It gets nothing
-- else: no DELETE, no SELECT of `updated_at`, nothing on any other table, and
-- only USAGE on the schema, which PlanetScale does not give PUBLIC.
--
-- The grant runs only when the role already exists. When the role is created
-- after this migration has run, run these three statements once, after
-- `CREATE ROLE v1_import_writer ...`:
-- GRANT USAGE ON SCHEMA public TO v1_import_writer;
-- GRANT INSERT, UPDATE ON org_moved_to_v2 TO v1_import_writer;
-- GRANT SELECT (org_id, state, v2_org_id, v2_org_slug) ON org_moved_to_v2 TO v1_import_writer;
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'v1_import_writer') THEN
-- PlanetScale gives PUBLIC no USAGE on `public`, so the role needs its own.
GRANT USAGE ON SCHEMA public TO v1_import_writer;
GRANT INSERT, UPDATE ON org_moved_to_v2 TO v1_import_writer;
GRANT SELECT (org_id, state, v2_org_id, v2_org_slug) ON org_moved_to_v2 TO v1_import_writer;
END IF;
END $$;
Loading
Loading