CREATE TABLE "playbook_package_files" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "playbook_version_id" uuid NOT NULL, "path" text NOT NULL, "role" text NOT NULL, "content" "bytea" NOT NULL, "size_bytes" bigint NOT NULL, "sha256" text NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "playbook_package_files_version_path_uq" UNIQUE("playbook_version_id","path"), CONSTRAINT "playbook_package_files_path_check" CHECK (length("playbook_package_files"."path") between 1 and 512 and "playbook_package_files"."path" = btrim("playbook_package_files"."path") and "playbook_package_files"."path" !~ '(^/|\\|//|(^|/)\.\.?(/|$))'), CONSTRAINT "playbook_package_files_role_check" CHECK ("playbook_package_files"."role" in ('manifest', 'template', 'partial', 'documentation', 'changelog', 'example', 'evaluation', 'resource', 'run-pack-resource')), CONSTRAINT "playbook_package_files_size_check" CHECK ("playbook_package_files"."size_bytes" between 0 and 5242880 and octet_length("playbook_package_files"."content") = "playbook_package_files"."size_bytes"), CONSTRAINT "playbook_package_files_sha256_check" CHECK ("playbook_package_files"."sha256" ~ '^[0-9a-f]{64}$' and encode(digest("playbook_package_files"."content", 'sha256'), 'hex') = "playbook_package_files"."sha256") ); --> statement-breakpoint ALTER TABLE "playbook_versions" ADD COLUMN "draft_revision" integer DEFAULT 1 NOT NULL;--> statement-breakpoint ALTER TABLE "playbook_versions" ADD COLUMN "draft_digest" text DEFAULT repeat('0', 64) NOT NULL;--> statement-breakpoint ALTER TABLE "playbook_versions" ADD COLUMN "draft_validation_json" jsonb DEFAULT '{"valid":true,"issues":[]}'::jsonb NOT NULL;--> statement-breakpoint DROP TRIGGER "playbook_versions_published_immutable_trg" ON "playbook_versions";--> statement-breakpoint UPDATE "playbook_versions" SET "draft_digest" = "content_digest";--> statement-breakpoint CREATE TRIGGER playbook_versions_published_immutable_trg BEFORE UPDATE ON "playbook_versions" FOR EACH ROW WHEN (OLD."published_at" IS NOT NULL) EXECUTE FUNCTION devrunbook_reject_immutable_update();--> statement-breakpoint ALTER TABLE "playbook_package_files" ADD CONSTRAINT "playbook_package_files_version_fk" FOREIGN KEY ("playbook_version_id") REFERENCES "public"."playbook_versions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint CREATE INDEX "playbook_package_files_version_idx" ON "playbook_package_files" USING btree ("playbook_version_id");--> statement-breakpoint ALTER TABLE "playbook_versions" ADD CONSTRAINT "playbook_versions_draft_revision_check" CHECK ("playbook_versions"."draft_revision" > 0);--> statement-breakpoint ALTER TABLE "playbook_versions" ADD CONSTRAINT "playbook_versions_draft_digest_check" CHECK ("playbook_versions"."draft_digest" ~ '^[0-9a-f]{64}$');--> statement-breakpoint ALTER TABLE "playbook_versions" ADD CONSTRAINT "playbook_versions_draft_validation_check" CHECK (jsonb_typeof("playbook_versions"."draft_validation_json") = 'object');--> statement-breakpoint CREATE FUNCTION normalize_playbook_version_draft_digest() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.draft_digest = repeat('0', 64) THEN NEW.draft_digest := NEW.content_digest; END IF; RETURN NEW; END; $$;--> statement-breakpoint CREATE TRIGGER playbook_versions_normalize_draft_digest BEFORE INSERT ON playbook_versions FOR EACH ROW EXECUTE FUNCTION normalize_playbook_version_draft_digest();--> statement-breakpoint CREATE FUNCTION reject_published_playbook_file_mutation() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE target_version_id uuid; target_published_at timestamptz; BEGIN target_version_id := CASE WHEN TG_OP = 'DELETE' THEN OLD.playbook_version_id ELSE NEW.playbook_version_id END; SELECT published_at INTO target_published_at FROM playbook_versions WHERE id = target_version_id FOR SHARE; IF target_published_at IS NOT NULL THEN RAISE EXCEPTION 'published playbook package files are immutable' USING ERRCODE = '55000'; END IF; IF TG_OP = 'UPDATE' AND OLD.playbook_version_id <> NEW.playbook_version_id THEN SELECT published_at INTO target_published_at FROM playbook_versions WHERE id = OLD.playbook_version_id FOR SHARE; IF target_published_at IS NOT NULL THEN RAISE EXCEPTION 'published playbook package files are immutable' USING ERRCODE = '55000'; END IF; END IF; RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END; END; $$;--> statement-breakpoint CREATE TRIGGER playbook_package_files_immutable_when_published BEFORE INSERT OR UPDATE OR DELETE ON playbook_package_files FOR EACH ROW EXECUTE FUNCTION reject_published_playbook_file_mutation();