-- CreateEnum CREATE TYPE "GrantSubjectType" AS ENUM ('USER', 'AUTHENTICATED', 'PUBLIC'); -- CreateEnum CREATE TYPE "GrantRole" AS ENUM ('POND_ADMIN', 'EDITOR', 'READER'); -- CreateEnum CREATE TYPE "GrantScopeType" AS ENUM ('POND', 'LABEL', 'PAGE'); -- CreateEnum CREATE TYPE "GrantEffect" AS ENUM ('ALLOW', 'DENY'); -- DropIndex DROP INDEX "page_content_cache_search_vector_idx"; -- CreateTable CREATE TABLE "role_grants" ( "id" TEXT NOT NULL, "pond_id" TEXT NOT NULL, "subject_type" "GrantSubjectType" NOT NULL, "subject_id" TEXT, "role" "GrantRole" NOT NULL, "scope_type" "GrantScopeType" NOT NULL, "scope_id" TEXT, "effect" "GrantEffect" NOT NULL, "created_by" TEXT NOT NULL, "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "role_grants_pkey" PRIMARY KEY ("id") ); -- CreateIndex CREATE INDEX "role_grants_pond_id_idx" ON "role_grants"("pond_id"); -- CreateIndex CREATE UNIQUE INDEX "role_grants_pond_id_subject_type_subject_id_role_scope_type_key" ON "role_grants"("pond_id", "subject_type", "subject_id", "role", "scope_type", "scope_id"); -- AddForeignKey ALTER TABLE "role_grants" ADD CONSTRAINT "role_grants_pond_id_fkey" FOREIGN KEY ("pond_id") REFERENCES "ponds"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- Structural backstop (permissions.md, issue #51): a POND_ADMIN grant is only -- valid at pond scope for a specific user. The personal-pond single-admin rule -- needs the pond type and is enforced in the service, not here. ALTER TABLE "role_grants" ADD CONSTRAINT "role_grants_pond_admin_scope_check" CHECK ("role" <> 'POND_ADMIN' OR ("scope_type" = 'POND' AND "subject_type" = 'USER'));