- 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 | yield* persistence.rows.policies.insert({ | 197 | yield* persistence.rows.policies.insert({ |
| 198 | id: PolicyIdSchema.make("pol_1"), | 198 | id: PolicyIdSchema.make("pol_1"), |
| 199 | scopeType: "workspace", | ||
| 200 | organizationId, | ||
| 199 | workspaceId, | 201 | workspaceId, |
| 200 | targetAccountId: null, | 202 | targetAccountId: null, |
| 201 | clientId: null, | 203 | clientId: null, |
| @@ -428,7 +428,9 @@ export const policiesTable = pgTable( · 427 unmodified lines | |||
| 428 | tableNames.policies, | 428 | tableNames.policies, |
| 429 | { | 429 | { |
| 430 | id: text("id").notNull().primaryKey(), | 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 | targetAccountId: text("target_account_id"), | 434 | targetAccountId: text("target_account_id"), |
| 433 | clientId: text("client_id"), | 435 | clientId: text("client_id"), |
| 434 | resourceType: text("resource_type").notNull(), | 436 | resourceType: text("resource_type").notNull(), |
| @@ -443,12 +445,30 @@ export const policiesTable = pgTable( · 8 unmodified lines | |||
| 443 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), | 445 | updatedAt: bigint("updated_at", { mode: "number" }).notNull(), |
| 444 | }, | 446 | }, |
| 445 | (table) => [ | 447 | (table) => [ |
| 448 | index("policies_organization_priority_idx").on( | ||
| 449 | table.organizationId, | ||
| 450 | table.priority.desc(), | ||
| 451 | table.updatedAt, | ||
| 452 | table.id, | ||
| 453 | ), | ||
| 446 | index("policies_workspace_priority_idx").on( | 454 | index("policies_workspace_priority_idx").on( |
| 447 | table.workspaceId, | 455 | table.workspaceId, |
| 448 | table.priority.desc(), | 456 | table.priority.desc(), |
| 449 | table.updatedAt, | 457 | table.updatedAt, |
| 450 | table.id, | 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 | check( | 472 | check( |
| 453 | "policies_resource_type_check", | 473 | "policies_resource_type_check", |
| 454 | sql`${table.resourceType} in ('all_tools', 'source', 'namespace', 'tool_path')`, | 474 | sql`${table.resourceType} in ('all_tools', 'source', 'namespace', 'tool_path')`, |
| @@ -516,6 +536,7 @@ export const executionInteractionsTable = pgTable( · 61 unmodified lines | |||
| 516 | executionId: text("execution_id").notNull(), | 536 | executionId: text("execution_id").notNull(), |
| 517 | status: text("status").notNull(), | 537 | status: text("status").notNull(), |
| 518 | kind: text("kind").notNull(), | 538 | kind: text("kind").notNull(), |
| 539 | purpose: text("purpose").notNull(), | ||
| 519 | payloadJson: text("payload_json").notNull(), | 540 | payloadJson: text("payload_json").notNull(), |
| 520 | responseJson: text("response_json"), | 541 | responseJson: text("response_json"), |
| 521 | createdAt: bigint("created_at", { mode: "number" }).notNull(), | 542 | createdAt: bigint("created_at", { mode: "number" }).notNull(), |
| @@ -65,6 +65,7 @@ const executionInteractionSchemaOverrides = { · 64 unmodified lines | |||
| 65 | id: ExecutionInteractionIdSchema, | 65 | id: ExecutionInteractionIdSchema, |
| 66 | executionId: ExecutionIdSchema, | 66 | executionId: ExecutionIdSchema, |
| 67 | status: ExecutionInteractionStatusSchema, | 67 | status: ExecutionInteractionStatusSchema, |
| 68 | purpose: Schema.String, | ||
| 68 | responseJson: Schema.NullOr(Schema.String), | 69 | responseJson: Schema.NullOr(Schema.String), |
| 69 | createdAt: TimestampMsSchema, | 70 | createdAt: TimestampMsSchema, |
| 70 | updatedAt: TimestampMsSchema, | 71 | updatedAt: TimestampMsSchema, |
| @@ -7,7 +7,14 @@ import { Schema } from "effect"; · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | import { policiesTable } from "../../persistence/schema"; | 8 | import { policiesTable } from "../../persistence/schema"; |
| 9 | import { TimestampMsSchema } from "../common"; | 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 | export const PolicyResourceTypeSchema = Schema.Literal( | 19 | export const PolicyResourceTypeSchema = Schema.Literal( |
| 13 | "all_tools", | 20 | "all_tools", |
| @@ -35,7 +42,9 @@ export const PolicyArgumentConditionSchema = Schema.Struct({ · 21 unmodified lines | |||
| 35 | 42 | ||
| 36 | const policySchemaOverrides = { | 43 | const policySchemaOverrides = { |
| 37 | id: PolicyIdSchema, | 44 | id: PolicyIdSchema, |
| 38 | workspaceId: WorkspaceIdSchema, | 45 | scopeType: PolicyScopeTypeSchema, |
| 46 | organizationId: OrganizationIdSchema, | ||
| 47 | workspaceId: Schema.NullOr(WorkspaceIdSchema), | ||
| 39 | targetAccountId: Schema.NullOr(AccountIdSchema), | 48 | targetAccountId: Schema.NullOr(AccountIdSchema), |
| 40 | clientId: Schema.NullOr(Schema.String), | 49 | clientId: Schema.NullOr(Schema.String), |
| 41 | resourceType: PolicyResourceTypeSchema, | 50 | resourceType: PolicyResourceTypeSchema, |
| @@ -63,6 +72,7 @@ export type PolicyResourceType = typeof PolicyResourceTypeSchema.Type; · 21 unmodified lines | |||
| 63 | export type PolicyMatchType = typeof PolicyMatchTypeSchema.Type; | 72 | export type PolicyMatchType = typeof PolicyMatchTypeSchema.Type; |
| 64 | export type PolicyEffect = typeof PolicyEffectSchema.Type; | 73 | export type PolicyEffect = typeof PolicyEffectSchema.Type; |
| 65 | export type PolicyApprovalMode = typeof PolicyApprovalModeSchema.Type; | 74 | export type PolicyApprovalMode = typeof PolicyApprovalModeSchema.Type; |
| 75 | export type PolicyScopeType = typeof PolicyScopeTypeSchema.Type; | ||
| 66 | export type ArgumentConditionOperator = | 76 | export type ArgumentConditionOperator = |
| 67 | typeof ArgumentConditionOperatorSchema.Type; | 77 | typeof ArgumentConditionOperatorSchema.Type; |
| 68 | export type PolicyArgumentCondition = typeof PolicyArgumentConditionSchema.Type; | 78 | export type PolicyArgumentCondition = typeof PolicyArgumentConditionSchema.Type; |