From ad32d2270d018c62d44ad1a9f5ebc0773bdfc1b8 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 15 Aug 2026 19:20:37 +0000 Subject: [PATCH] fix(db): renumber board key scope migration Co-Authored-By: Paperclip --- .../src/board-api-key-scope-migration.test.ts | 2 +- .../migrations/0212_cultured_george_stacy.sql | 54 ---- .../src/migrations/0277_omniscient_darwin.sql | 52 +++ .../db/src/migrations/meta/0277_snapshot.json | 302 +++++++++++------- packages/db/src/migrations/meta/_journal.json | 4 +- 5 files changed, 242 insertions(+), 172 deletions(-) delete mode 100644 packages/db/src/migrations/0212_cultured_george_stacy.sql create mode 100644 packages/db/src/migrations/0277_omniscient_darwin.sql diff --git a/packages/db/src/board-api-key-scope-migration.test.ts b/packages/db/src/board-api-key-scope-migration.test.ts index 51dad5670e..1a56cc6792 100644 --- a/packages/db/src/board-api-key-scope-migration.test.ts +++ b/packages/db/src/board-api-key-scope-migration.test.ts @@ -6,7 +6,7 @@ import { startEmbeddedPostgresTestDatabase, } from "./test-embedded-postgres.js"; -const MIGRATION_FILE = "0212_cultured_george_stacy.sql"; +const MIGRATION_FILE = "0277_omniscient_darwin.sql"; const cleanups: Array<() => Promise> = []; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; diff --git a/packages/db/src/migrations/0212_cultured_george_stacy.sql b/packages/db/src/migrations/0212_cultured_george_stacy.sql deleted file mode 100644 index 303b313b7d..0000000000 --- a/packages/db/src/migrations/0212_cultured_george_stacy.sql +++ /dev/null @@ -1,54 +0,0 @@ -ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "scope_config" jsonb;--> statement-breakpoint -ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "token_prefix" text;--> statement-breakpoint -ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "legacy_unrestricted" boolean DEFAULT false NOT NULL;--> statement-breakpoint - --- Only rows present before this migration can enter the legacy-unrestricted state. --- Malformed non-null scope JSON is deliberately preserved so authentication fails closed. -UPDATE "board_api_keys" -SET "legacy_unrestricted" = true -WHERE "scope_config" IS NULL;--> statement-breakpoint - -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM pg_constraint - WHERE conname = 'board_api_keys_scope_legacy_check' - AND conrelid = 'board_api_keys'::regclass - ) THEN - ALTER TABLE "board_api_keys" - ADD CONSTRAINT "board_api_keys_scope_legacy_check" - CHECK ( - ("legacy_unrestricted" = true AND "scope_config" IS NULL) - OR - ("legacy_unrestricted" = false AND "scope_config" IS NOT NULL) - ); - END IF; -END $$;--> statement-breakpoint - --- Pending CLI auth challenges created before this migration remain nullable and --- cannot mint a key; every new challenge must persist a validated scope. -ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "requested_scope_config" jsonb; ---> statement-breakpoint -ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "pending_key_prefix" text; ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "board_api_key_authorization_events" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "board_api_key_id" uuid NOT NULL, - "owner_user_id" text NOT NULL, - "token_prefix" text, - "action" text NOT NULL, - "classification" text NOT NULL, - "authoritative_company_id" uuid, - "authoritative_resource_type" text, - "authoritative_resource_id" text, - "decision" text NOT NULL, - "reason" text NOT NULL, - "request_id" text, - "run_id" uuid, - "details" jsonb DEFAULT '{}'::jsonb NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL -);--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_key_created_idx" -ON "board_api_key_authorization_events" USING btree ("board_api_key_id", "created_at" DESC);--> statement-breakpoint -CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_company_created_idx" -ON "board_api_key_authorization_events" USING btree ("authoritative_company_id", "created_at" DESC); diff --git a/packages/db/src/migrations/0277_omniscient_darwin.sql b/packages/db/src/migrations/0277_omniscient_darwin.sql new file mode 100644 index 0000000000..89e9f36beb --- /dev/null +++ b/packages/db/src/migrations/0277_omniscient_darwin.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS "board_api_key_authorization_events" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "board_api_key_id" uuid NOT NULL, + "owner_user_id" text NOT NULL, + "token_prefix" text, + "action" text NOT NULL, + "classification" text NOT NULL, + "authoritative_company_id" uuid, + "authoritative_resource_type" text, + "authoritative_resource_id" text, + "decision" text NOT NULL, + "reason" text NOT NULL, + "request_id" text, + "run_id" uuid, + "details" jsonb DEFAULT '{}'::jsonb NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +);--> statement-breakpoint +ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "scope_config" jsonb;--> statement-breakpoint +ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "token_prefix" text;--> statement-breakpoint +ALTER TABLE "board_api_keys" ADD COLUMN IF NOT EXISTS "legacy_unrestricted" boolean DEFAULT false NOT NULL;--> statement-breakpoint + +-- Only rows present before this migration can enter the legacy-unrestricted +-- state. Malformed non-null scope JSON is deliberately preserved so +-- authentication fails closed. +UPDATE "board_api_keys" +SET "legacy_unrestricted" = true +WHERE "scope_config" IS NULL;--> statement-breakpoint + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'board_api_keys_scope_legacy_check' + AND conrelid = 'board_api_keys'::regclass + ) THEN + ALTER TABLE "board_api_keys" + ADD CONSTRAINT "board_api_keys_scope_legacy_check" + CHECK ( + ("legacy_unrestricted" = true AND "scope_config" IS NULL) + OR + ("legacy_unrestricted" = false AND "scope_config" IS NOT NULL) + ); + END IF; +END $$;--> statement-breakpoint + +-- Pending CLI auth challenges created before this migration remain nullable +-- and cannot mint a key; every new challenge must persist a validated scope. +ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "requested_scope_config" jsonb;--> statement-breakpoint +ALTER TABLE "cli_auth_challenges" ADD COLUMN IF NOT EXISTS "pending_key_prefix" text;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_key_created_idx" ON "board_api_key_authorization_events" USING btree ("board_api_key_id","created_at" DESC NULLS LAST);--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "board_api_key_authorization_events_company_created_idx" ON "board_api_key_authorization_events" USING btree ("authoritative_company_id","created_at" DESC NULLS LAST); diff --git a/packages/db/src/migrations/meta/0277_snapshot.json b/packages/db/src/migrations/meta/0277_snapshot.json index 84c859cb1a..da44b97b62 100644 --- a/packages/db/src/migrations/meta/0277_snapshot.json +++ b/packages/db/src/migrations/meta/0277_snapshot.json @@ -1,5 +1,5 @@ { - "id": "5f80a2a4-dcf6-48a0-be4b-f32dbb243e28", + "id": "582904aa-9302-4986-b589-2e9cfa7d7b03", "prevId": "9c7c9e05-e663-4a29-8b46-63733f62da9b", "version": "7", "dialect": "postgresql", @@ -2402,119 +2402,6 @@ }, "isRLSEnabled": false }, - "public.ai_provider_defaults": { - "name": "ai_provider_defaults", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "company_id": { - "name": "company_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "grant_id": { - "name": "grant_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "ai_provider_defaults_owner_provider_uq": { - "name": "ai_provider_defaults_owner_provider_uq", - "columns": [ - { - "expression": "company_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ai_provider_defaults_company_id_companies_id_fk": { - "name": "ai_provider_defaults_company_id_companies_id_fk", - "tableFrom": "ai_provider_defaults", - "tableTo": "companies", - "columnsFrom": [ - "company_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "ai_provider_defaults_company_grant_fk": { - "name": "ai_provider_defaults_company_grant_fk", - "tableFrom": "ai_provider_defaults", - "tableTo": "connection_grants", - "columnsFrom": [ - "company_id", - "grant_id" - ], - "columnsTo": [ - "company_id", - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "ai_provider_defaults_provider_check": { - "name": "ai_provider_defaults_provider_check", - "value": "\"ai_provider_defaults\".\"provider\" in ('anthropic','openai','openrouter','xai')" - } - }, - "isRLSEnabled": false - }, "public.approval_comments": { "name": "approval_comments", "schema": "", @@ -3307,6 +3194,155 @@ "checkConstraints": {}, "isRLSEnabled": false }, + "public.board_api_key_authorization_events": { + "name": "board_api_key_authorization_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "board_api_key_id": { + "name": "board_api_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authoritative_company_id": { + "name": "authoritative_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "authoritative_resource_type": { + "name": "authoritative_resource_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authoritative_resource_id": { + "name": "authoritative_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "board_api_key_authorization_events_key_created_idx": { + "name": "board_api_key_authorization_events_key_created_idx", + "columns": [ + { + "expression": "board_api_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "board_api_key_authorization_events_company_created_idx": { + "name": "board_api_key_authorization_events_company_created_idx", + "columns": [ + { + "expression": "authoritative_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, "public.board_api_keys": { "name": "board_api_keys", "schema": "", @@ -3336,6 +3372,25 @@ "primaryKey": false, "notNull": true }, + "scope_config": { + "name": "scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "legacy_unrestricted": { + "name": "legacy_unrestricted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, "last_used_at": { "name": "last_used_at", "type": "timestamp with time zone", @@ -3412,7 +3467,12 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {}, "policies": {}, - "checkConstraints": {}, + "checkConstraints": { + "board_api_keys_scope_legacy_check": { + "name": "board_api_keys_scope_legacy_check", + "value": "(\"board_api_keys\".\"legacy_unrestricted\" = true and \"board_api_keys\".\"scope_config\" is null) or (\"board_api_keys\".\"legacy_unrestricted\" = false and \"board_api_keys\".\"scope_config\" is not null)" + } + }, "isRLSEnabled": false }, "public.budget_incidents": { @@ -8487,12 +8547,24 @@ "primaryKey": false, "notNull": false }, + "requested_scope_config": { + "name": "requested_scope_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "pending_key_hash": { "name": "pending_key_hash", "type": "text", "primaryKey": false, "notNull": true }, + "pending_key_prefix": { + "name": "pending_key_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, "pending_key_name": { "name": "pending_key_name", "type": "text", diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 1fa240dd86..6a96b686e1 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -1930,8 +1930,8 @@ { "idx": 277, "version": "7", - "when": 1789260211664, - "tag": "0277_uneven_lady_deathstrike", + "when": 1789258667522, + "tag": "0277_omniscient_darwin", "breakpoints": true } ]