-- Motion: the library, engagements, artifacts and the append-only score log. -- -- Hand-written, and it has to be. `motion_templates.origin_artifact_id` -- references `engagement_artifacts`, and `engagement_artifacts.template_id` -- references `motion_templates` — the promotion loop is a foreign key cycle, -- and there is no ordering of two CREATE TABLE statements that satisfies both. -- Drizzle emits every constraint with the table it belongs to and will not -- order this for you, so the second half of the cycle is added below as its own -- ALTER TABLE once both tables exist. Generated output for this schema fails on -- `relation "engagement_artifacts" does not exist`. CREATE TABLE "motion_templates" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "kind" text NOT NULL, "slug" text NOT NULL, "version" integer DEFAULT 1 NOT NULL, "title" text NOT NULL, "summary" text NOT NULL, "body" text NOT NULL, "fields" jsonb, "stage" text NOT NULL, "visibility" text DEFAULT 'private' NOT NULL, "owner_user_id" uuid, "supersedes_id" uuid, "origin_artifact_id" uuid, "is_system" boolean DEFAULT false NOT NULL, "usage_count" integer DEFAULT 0 NOT NULL, "archived_at" timestamp with time zone, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "motion_templates_slug_version_key" UNIQUE("slug","version"), CONSTRAINT "motion_templates_kind_check" CHECK ("motion_templates"."kind" IN ('discovery', 'qualification', 'poc', 'proposal', 'pricing', 'architecture', 'case_study', 'narrative', 'playbook')), CONSTRAINT "motion_templates_stage_check" CHECK ("motion_templates"."stage" IN ('qualification', 'legal', 'scoping', 'proposal', 'procurement', 'poc', 'deployment', 'expansion', 'closed_won', 'closed_lost')), CONSTRAINT "motion_templates_visibility_check" CHECK ("motion_templates"."visibility" IN ('private', 'shared')), CONSTRAINT "motion_templates_version_positive_check" CHECK ("motion_templates"."version" > 0), CONSTRAINT "motion_templates_private_has_owner_check" CHECK ("motion_templates"."visibility" <> 'private' OR "motion_templates"."owner_user_id" IS NOT NULL) ); --> statement-breakpoint CREATE TABLE "engagements" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "demand_deal_id" uuid NOT NULL, "playbook_template_id" uuid, "owner_user_id" uuid, "status" text DEFAULT 'open' NOT NULL, "summary" text, "opened_at" timestamp with time zone DEFAULT now() NOT NULL, "closed_at" timestamp with time zone, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "engagements_demand_deal_key" UNIQUE("demand_deal_id"), CONSTRAINT "engagements_status_check" CHECK ("engagements"."status" IN ('open', 'won', 'lost', 'paused')) ); --> statement-breakpoint CREATE TABLE "engagement_artifacts" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "engagement_id" uuid NOT NULL, "template_id" uuid, "kind" text NOT NULL, "stage" text NOT NULL, "title" text NOT NULL, "body" text NOT NULL, "fields" jsonb, "status" text DEFAULT 'draft' NOT NULL, "authored_by_user_id" uuid, "promoted_template_id" uuid, "archived_at" timestamp with time zone, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "engagement_artifacts_kind_check" CHECK ("engagement_artifacts"."kind" IN ('discovery', 'qualification', 'poc', 'proposal', 'pricing', 'architecture', 'case_study', 'narrative', 'playbook')), CONSTRAINT "engagement_artifacts_stage_check" CHECK ("engagement_artifacts"."stage" IN ('qualification', 'legal', 'scoping', 'proposal', 'procurement', 'poc', 'deployment', 'expansion', 'closed_won', 'closed_lost')), CONSTRAINT "engagement_artifacts_status_check" CHECK ("engagement_artifacts"."status" IN ('draft', 'review', 'final')) ); --> statement-breakpoint CREATE TABLE "qualification_scores" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "engagement_id" uuid NOT NULL, "framework_template_id" uuid, "dimensions" jsonb NOT NULL, "basis_points" integer NOT NULL, "band" text NOT NULL, "note" text, "scored_by_user_id" uuid, "scored_at" timestamp with time zone DEFAULT now() NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "qualification_scores_basis_points_check" CHECK ("qualification_scores"."basis_points" >= 0 AND "qualification_scores"."basis_points" <= 10000) ); --> statement-breakpoint ALTER TABLE "motion_templates" ADD CONSTRAINT "motion_templates_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "motion_templates" ADD CONSTRAINT "motion_templates_supersedes_id_motion_templates_id_fk" FOREIGN KEY ("supersedes_id") REFERENCES "public"."motion_templates"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "engagements" ADD CONSTRAINT "engagements_demand_deal_id_demand_deals_id_fk" FOREIGN KEY ("demand_deal_id") REFERENCES "public"."demand_deals"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "engagements" ADD CONSTRAINT "engagements_playbook_template_id_motion_templates_id_fk" FOREIGN KEY ("playbook_template_id") REFERENCES "public"."motion_templates"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "engagements" ADD CONSTRAINT "engagements_owner_user_id_users_id_fk" FOREIGN KEY ("owner_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "engagement_artifacts" ADD CONSTRAINT "engagement_artifacts_engagement_id_engagements_id_fk" FOREIGN KEY ("engagement_id") REFERENCES "public"."engagements"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "engagement_artifacts" ADD CONSTRAINT "engagement_artifacts_template_id_motion_templates_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."motion_templates"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "engagement_artifacts" ADD CONSTRAINT "engagement_artifacts_authored_by_user_id_users_id_fk" FOREIGN KEY ("authored_by_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "engagement_artifacts" ADD CONSTRAINT "engagement_artifacts_promoted_template_fk" FOREIGN KEY ("promoted_template_id") REFERENCES "public"."motion_templates"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -- The other half of the loop, and the reason this file is hand-written. ALTER TABLE "motion_templates" ADD CONSTRAINT "motion_templates_origin_artifact_fk" FOREIGN KEY ("origin_artifact_id") REFERENCES "public"."engagement_artifacts"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "qualification_scores" ADD CONSTRAINT "qualification_scores_engagement_id_engagements_id_fk" FOREIGN KEY ("engagement_id") REFERENCES "public"."engagements"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "qualification_scores" ADD CONSTRAINT "qualification_scores_framework_template_fk" FOREIGN KEY ("framework_template_id") REFERENCES "public"."motion_templates"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint CREATE INDEX "motion_templates_kind_stage_idx" ON "motion_templates" USING btree ("kind","stage");--> statement-breakpoint CREATE INDEX "motion_templates_visibility_kind_idx" ON "motion_templates" USING btree ("visibility","kind");--> statement-breakpoint CREATE INDEX "motion_templates_owner_idx" ON "motion_templates" USING btree ("owner_user_id");--> statement-breakpoint CREATE INDEX "engagements_status_idx" ON "engagements" USING btree ("status");--> statement-breakpoint CREATE INDEX "engagement_artifacts_engagement_stage_idx" ON "engagement_artifacts" USING btree ("engagement_id","stage");--> statement-breakpoint CREATE INDEX "qualification_scores_engagement_idx" ON "qualification_scores" USING btree ("engagement_id","scored_at" DESC);