History

add policy scope type, organization-level ownership, and interaction purpose

- 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

Rhys Sullivan committed 8 months ago

Commit b262ec01 parent 74ceb1c5 files · +54 −3Browse files

Unified · Split

5 files
  1. packages/control-plane/drizzle/20260309103000_policy_scope_and_interaction_purpose/migration.sql +17 −0
  2. packages/control-plane/src/persistence/control-plane-rows.test.ts +2 −0
  3. packages/control-plane/src/persistence/schema.ts +22 −1
  4. packages/control-plane/src/schema/models/execution.ts +1 −0
  5. packages/control-plane/src/schema/models/policy.ts +12 −2
packages/control-plane/drizzle/20260309103000_policy_scope_and_interaction_purpose/migration.sql+17−0
@@ -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';
packages/control-plane/src/persistence/control-plane-rows.test.ts+2−0
@@ -196,6 +196,8 @@ describe("control-plane-persistence-drizzle", () => { · 195 unmodified lines
196196
197197 yield* persistence.rows.policies.insert({
198198 id: PolicyIdSchema.make("pol_1"),
199+ scopeType: "workspace",
200+ organizationId,
199201 workspaceId,
200202 targetAccountId: null,
201203 clientId: null,
packages/control-plane/src/persistence/schema.ts+22−1
@@ -428,7 +428,9 @@ export const policiesTable = pgTable( · 427 unmodified lines
428428 tableNames.policies,
429429 {
430430 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"),
432434 targetAccountId: text("target_account_id"),
433435 clientId: text("client_id"),
434436 resourceType: text("resource_type").notNull(),
@@ -443,12 +445,30 @@ export const policiesTable = pgTable( · 8 unmodified lines
443445 updatedAt: bigint("updated_at", { mode: "number" }).notNull(),
444446 },
445447 (table) => [
448+ index("policies_organization_priority_idx").on(
449+ table.organizationId,
450+ table.priority.desc(),
451+ table.updatedAt,
452+ table.id,
453+ ),
446454 index("policies_workspace_priority_idx").on(
447455 table.workspaceId,
448456 table.priority.desc(),
449457 table.updatedAt,
450458 table.id,
451459 ),
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+ ),
452472 check(
453473 "policies_resource_type_check",
454474 sql`${table.resourceType} in ('all_tools', 'source', 'namespace', 'tool_path')`,
@@ -516,6 +536,7 @@ export const executionInteractionsTable = pgTable( · 61 unmodified lines
516536 executionId: text("execution_id").notNull(),
517537 status: text("status").notNull(),
518538 kind: text("kind").notNull(),
539+ purpose: text("purpose").notNull(),
519540 payloadJson: text("payload_json").notNull(),
520541 responseJson: text("response_json"),
521542 createdAt: bigint("created_at", { mode: "number" }).notNull(),
packages/control-plane/src/schema/models/execution.ts+1−0
@@ -65,6 +65,7 @@ const executionInteractionSchemaOverrides = { · 64 unmodified lines
6565 id: ExecutionInteractionIdSchema,
6666 executionId: ExecutionIdSchema,
6767 status: ExecutionInteractionStatusSchema,
68+ purpose: Schema.String,
6869 responseJson: Schema.NullOr(Schema.String),
6970 createdAt: TimestampMsSchema,
7071 updatedAt: TimestampMsSchema,
packages/control-plane/src/schema/models/policy.ts+12−2
@@ -7,7 +7,14 @@ import { Schema } from "effect"; · 6 unmodified lines
77
88 import { policiesTable } from "../../persistence/schema";
99 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");
1118
1219 export const PolicyResourceTypeSchema = Schema.Literal(
1320 "all_tools",
@@ -35,7 +42,9 @@ export const PolicyArgumentConditionSchema = Schema.Struct({ · 21 unmodified lines
3542
3643 const policySchemaOverrides = {
3744 id: PolicyIdSchema,
38− workspaceId: WorkspaceIdSchema,
45+ scopeType: PolicyScopeTypeSchema,
46+ organizationId: OrganizationIdSchema,
47+ workspaceId: Schema.NullOr(WorkspaceIdSchema),
3948 targetAccountId: Schema.NullOr(AccountIdSchema),
4049 clientId: Schema.NullOr(Schema.String),
4150 resourceType: PolicyResourceTypeSchema,
@@ -63,6 +72,7 @@ export type PolicyResourceType = typeof PolicyResourceTypeSchema.Type; · 21 unmodified lines
6372 export type PolicyMatchType = typeof PolicyMatchTypeSchema.Type;
6473 export type PolicyEffect = typeof PolicyEffectSchema.Type;
6574 export type PolicyApprovalMode = typeof PolicyApprovalModeSchema.Type;
75+export type PolicyScopeType = typeof PolicyScopeTypeSchema.Type;
6676 export type ArgumentConditionOperator =
6777 typeof ArgumentConditionOperatorSchema.Type;
6878 export type PolicyArgumentCondition = typeof PolicyArgumentConditionSchema.Type;