- Add scopeType (organization|workspace) and organizationId columns to policies - Make workspaceId nullable for organization-scoped policies - Add check constraints for scope consistency - Add organization-level index on policies table - Add purpose column to execution interactions table - Add PolicyScopeTypeSchema to schema models - Update persistence test to include new required fields - Include database migration for all schema changes
Unified · Split
| @@ -0,0 +1,17 @@ | ||
| 1 | +ALTER TABLE "policies" ADD COLUMN "scope_type" text;--> statement-breakpoint | |
| 2 | +ALTER TABLE "policies" ADD COLUMN "organization_id" text;--> statement-breakpoint | |
| 3 | +UPDATE "policies" AS "pol" | |
| 4 | +SET | |
| 5 | + "scope_type" = 'workspace', | |
| 6 | + "organization_id" = "ws"."organization_id" | |
| 7 | +FROM "workspaces" AS "ws" | |
| 8 | +WHERE "pol"."workspace_id" = "ws"."id";--> statement-breakpoint | |
| 9 | +ALTER TABLE "policies" ALTER COLUMN "scope_type" SET NOT NULL;--> statement-breakpoint | |
| 10 | +ALTER TABLE "policies" ALTER COLUMN "organization_id" SET NOT NULL;--> statement-breakpoint | |
| 11 | +ALTER TABLE "policies" ALTER COLUMN "workspace_id" DROP NOT NULL;--> statement-breakpoint | |
| 12 | +ALTER TABLE "policies" ADD CONSTRAINT "policies_scope_type_check" CHECK ("scope_type" in ('organization', 'workspace'));--> statement-breakpoint | |
| 13 | +ALTER TABLE "policies" ADD CONSTRAINT "policies_scope_consistency_check" CHECK (("scope_type" = 'organization' and "workspace_id" is null) or ("scope_type" = 'workspace' and "workspace_id" is not null));--> statement-breakpoint | |
| 14 | +DROP INDEX IF EXISTS "policies_workspace_priority_idx";--> statement-breakpoint | |
| 15 | +CREATE INDEX "policies_organization_priority_idx" ON "policies" ("organization_id","priority" DESC,"updated_at","id");--> statement-breakpoint | |
| 16 | +CREATE INDEX "policies_workspace_priority_idx" ON "policies" ("workspace_id","priority" DESC,"updated_at","id");--> statement-breakpoint | |
| 17 | +ALTER TABLE "execution_interactions" ADD COLUMN "purpose" text NOT NULL DEFAULT 'elicitation'; | |
| @@ -196,6 +196,8 @@ describe("control-plane-persistence-drizzle", () => { · 195 unmodified lines | ||
| 196 | 196 | |
| 197 | 197 | yield* persistence.rows.policies.insert({ |
| 198 | 198 | id: PolicyIdSchema.make("pol_1"), |
| 199 | + scopeType: "workspace", | |
| 200 | + organizationId, | |
| 199 | 201 | workspaceId, |
| 200 | 202 | targetAccountId: null, |
| 201 | 203 | clientId: null, |
| @@ -428,7 +428,9 @@ export const policiesTable = pgTable( · 427 unmodified lines | ||
| 428 | 428 | tableNames.policies, |
| 429 | 429 | { |
| 430 | 430 | id: text("id").notNull().primaryKey(), |
| 431 | − workspaceId: text("workspace_id").notNull(), | |
| 431 | + scopeType: text("scope_type").notNull(), | |
| 432 | + organizationId: text("organization_id").notNull(), | |
| 433 | + workspaceId: text("workspace_id"), | |
| 432 | 434 | targetAccountId: text("target_account_id"), |
| 433 | 435 | clientId: text("client_id"), |
| 434 | 436 | resourceType: text("resource_type").notNull(), |
| @@ -443,12 +445,30 @@ export const policiesTable = pgTable( · 8 unmodified lines | ||
| 443 | 445 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), |
| 444 | 446 | }, |
| 445 | 447 | (table) => [ |
| 448 | + index("policies_organization_priority_idx").on( | |
| 449 | + table.organizationId, | |
| 450 | + table.priority.desc(), | |
| 451 | + table.updatedAt, | |
| 452 | + table.id, | |
| 453 | + ), | |
| 446 | 454 | index("policies_workspace_priority_idx").on( |
| 447 | 455 | table.workspaceId, |
| 448 | 456 | table.priority.desc(), |
| 449 | 457 | table.updatedAt, |
| 450 | 458 | table.id, |
| 451 | 459 | ), |
| 460 | + check( | |
| 461 | + "policies_scope_type_check", | |
| 462 | + sql`${table.scopeType} in ('organization', 'workspace')`, | |
| 463 | + ), | |
| 464 | + check( | |
| 465 | + "policies_scope_consistency_check", | |
| 466 | + sql`( | |
| 467 | + ${table.scopeType} = 'organization' and ${table.workspaceId} is null | |
| 468 | + ) or ( | |
| 469 | + ${table.scopeType} = 'workspace' and ${table.workspaceId} is not null | |
| 470 | + )`, | |
| 471 | + ), | |
| 452 | 472 | check( |
| 453 | 473 | "policies_resource_type_check", |
| 454 | 474 | sql`${table.resourceType} in ('all_tools', 'source', 'namespace', 'tool_path')`, |
| @@ -516,6 +536,7 @@ export const executionInteractionsTable = pgTable( · 61 unmodified lines | ||
| 516 | 536 | executionId: text("execution_id").notNull(), |
| 517 | 537 | status: text("status").notNull(), |
| 518 | 538 | kind: text("kind").notNull(), |
| 539 | + purpose: text("purpose").notNull(), | |
| 519 | 540 | payloadJson: text("payload_json").notNull(), |
| 520 | 541 | responseJson: text("response_json"), |
| 521 | 542 | createdAt: bigint("created_at", { mode: "number" }).notNull(), |
| @@ -65,6 +65,7 @@ const executionInteractionSchemaOverrides = { · 64 unmodified lines | ||
| 65 | 65 | id: ExecutionInteractionIdSchema, |
| 66 | 66 | executionId: ExecutionIdSchema, |
| 67 | 67 | status: ExecutionInteractionStatusSchema, |
| 68 | + purpose: Schema.String, | |
| 68 | 69 | responseJson: Schema.NullOr(Schema.String), |
| 69 | 70 | createdAt: TimestampMsSchema, |
| 70 | 71 | updatedAt: TimestampMsSchema, |
| @@ -7,7 +7,14 @@ import { Schema } from "effect"; · 6 unmodified lines | ||
| 7 | 7 | |
| 8 | 8 | import { policiesTable } from "../../persistence/schema"; |
| 9 | 9 | import { TimestampMsSchema } from "../common"; |
| 10 | −import { AccountIdSchema, PolicyIdSchema, WorkspaceIdSchema } from "../ids"; | |
| 10 | +import { | |
| 11 | + AccountIdSchema, | |
| 12 | + OrganizationIdSchema, | |
| 13 | + PolicyIdSchema, | |
| 14 | + WorkspaceIdSchema, | |
| 15 | +} from "../ids"; | |
| 16 | + | |
| 17 | +export const PolicyScopeTypeSchema = Schema.Literal("organization", "workspace"); | |
| 11 | 18 | |
| 12 | 19 | export const PolicyResourceTypeSchema = Schema.Literal( |
| 13 | 20 | "all_tools", |
| @@ -35,7 +42,9 @@ export const PolicyArgumentConditionSchema = Schema.Struct({ · 21 unmodified lines | ||
| 35 | 42 | |
| 36 | 43 | const policySchemaOverrides = { |
| 37 | 44 | id: PolicyIdSchema, |
| 38 | − workspaceId: WorkspaceIdSchema, | |
| 45 | + scopeType: PolicyScopeTypeSchema, | |
| 46 | + organizationId: OrganizationIdSchema, | |
| 47 | + workspaceId: Schema.NullOr(WorkspaceIdSchema), | |
| 39 | 48 | targetAccountId: Schema.NullOr(AccountIdSchema), |
| 40 | 49 | clientId: Schema.NullOr(Schema.String), |
| 41 | 50 | resourceType: PolicyResourceTypeSchema, |
| @@ -63,6 +72,7 @@ export type PolicyResourceType = typeof PolicyResourceTypeSchema.Type; · 21 unmodified lines | ||
| 63 | 72 | export type PolicyMatchType = typeof PolicyMatchTypeSchema.Type; |
| 64 | 73 | export type PolicyEffect = typeof PolicyEffectSchema.Type; |
| 65 | 74 | export type PolicyApprovalMode = typeof PolicyApprovalModeSchema.Type; |
| 75 | +export type PolicyScopeType = typeof PolicyScopeTypeSchema.Type; | |
| 66 | 76 | export type ArgumentConditionOperator = |
| 67 | 77 | typeof ArgumentConditionOperatorSchema.Type; |
| 68 | 78 | export type PolicyArgumentCondition = typeof PolicyArgumentConditionSchema.Type; |