-- Extension UUID v7 (préférer pg_uuidv7 si dispo, sinon fallback Node). -- Note: pg_uuidv7 peut ne pas être disponible dans toutes les distributions Postgres 16. -- Cette migration tentera de créer l'extension, et si elle échoue, le fallback sera -- la génération côté Node via uuid@9+ (cf. ADR-0002). DO $$ BEGIN CREATE EXTENSION IF NOT EXISTS pg_uuidv7; EXCEPTION WHEN OTHERS THEN -- Extension non disponible, on créera une fonction fallback CREATE OR REPLACE FUNCTION uuidv7() RETURNS uuid AS $func$ BEGIN RAISE EXCEPTION 'uuidv7() requires pg_uuidv7 extension or Node-side generation'; END; $func$ LANGUAGE plpgsql; END $$; --> statement-breakpoint -- Trigger updated_at (bump à chaque UPDATE sur agents) CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; --> statement-breakpoint CREATE TABLE "agents" ( "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL, "name" text NOT NULL, "display_name" text NOT NULL, "role" text NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "agents_name_unique" UNIQUE("name"), CONSTRAINT "agents_name_check" CHECK ("agents"."name" ~ '^[a-z0-9][a-z0-9-]{0,63}$'), CONSTRAINT "agents_display_name_check" CHECK (length("agents"."display_name") BETWEEN 1 AND 128), CONSTRAINT "agents_role_check" CHECK ("agents"."role" IN ('admin', 'agent')) ); --> statement-breakpoint CREATE TRIGGER agents_set_updated_at BEFORE UPDATE ON agents FOR EACH ROW EXECUTE FUNCTION set_updated_at(); --> statement-breakpoint CREATE TABLE "api_tokens" ( "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL, "agent_id" uuid NOT NULL, "hash_argon2id" text NOT NULL, "prefix" text NOT NULL, "scopes" jsonb DEFAULT '{}'::jsonb NOT NULL, "status" text DEFAULT 'active' NOT NULL, "expires_at" timestamp with time zone, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "revoked_at" timestamp with time zone, CONSTRAINT "api_tokens_prefix_unique" UNIQUE("prefix"), CONSTRAINT "api_tokens_prefix_check" CHECK ("api_tokens"."prefix" ~ '^ah_live_[a-zA-Z0-9]{4}$'), CONSTRAINT "api_tokens_status_check" CHECK ("api_tokens"."status" IN ('active', 'rotating', 'revoked')), CONSTRAINT "api_tokens_revoked_at_check" CHECK ("api_tokens"."revoked_at" IS NULL OR "api_tokens"."status" = 'revoked'), CONSTRAINT "api_tokens_expires_at_check" CHECK ("api_tokens"."expires_at" IS NULL OR "api_tokens"."expires_at" > "api_tokens"."created_at") ); --> statement-breakpoint CREATE TABLE "audit_events" ( "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL, "type" text NOT NULL, "agent_id" uuid, "payload_hash" "bytea" NOT NULL, "ts" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "audit_events_type_check" CHECK ("audit_events"."type" IN ( 'login', 'token-issued', 'token-rotated', 'token-revoked', 'jwt-issued', 'agent-created', 'agent-deleted', 'room-created', 'room-deleted', 'message-sent' )), CONSTRAINT "audit_events_payload_hash_check" CHECK (length("audit_events"."payload_hash") = 32) ); --> statement-breakpoint CREATE TABLE "messages" ( "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL, "room_id" uuid NOT NULL, "author_agent_id" uuid NOT NULL, "body" text NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "messages_body_check" CHECK (length("messages"."body") BETWEEN 1 AND 16384) ); --> statement-breakpoint CREATE TABLE "room_members" ( "room_id" uuid NOT NULL, "agent_id" uuid NOT NULL, "joined_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "room_members_room_id_agent_id_pk" PRIMARY KEY("room_id","agent_id") ); --> statement-breakpoint CREATE TABLE "rooms" ( "id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL, "slug" text NOT NULL, "name" text NOT NULL, "created_by" uuid, "created_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "rooms_slug_unique" UNIQUE("slug"), CONSTRAINT "rooms_slug_check" CHECK ("rooms"."slug" ~ '^[a-z0-9][a-z0-9-]{0,63}$'), CONSTRAINT "rooms_name_check" CHECK (length("rooms"."name") BETWEEN 1 AND 128) ); --> statement-breakpoint ALTER TABLE "api_tokens" ADD CONSTRAINT "api_tokens_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "audit_events" ADD CONSTRAINT "audit_events_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint ALTER TABLE "messages" ADD CONSTRAINT "messages_room_id_rooms_id_fk" FOREIGN KEY ("room_id") REFERENCES "public"."rooms"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "messages" ADD CONSTRAINT "messages_author_agent_id_agents_id_fk" FOREIGN KEY ("author_agent_id") REFERENCES "public"."agents"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint ALTER TABLE "room_members" ADD CONSTRAINT "room_members_room_id_rooms_id_fk" FOREIGN KEY ("room_id") REFERENCES "public"."rooms"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "room_members" ADD CONSTRAINT "room_members_agent_id_agents_id_fk" FOREIGN KEY ("agent_id") REFERENCES "public"."agents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint ALTER TABLE "rooms" ADD CONSTRAINT "rooms_created_by_agents_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."agents"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint CREATE INDEX "agents_role_idx" ON "agents" USING btree ("role");--> statement-breakpoint CREATE INDEX "api_tokens_agent_id_idx" ON "api_tokens" USING btree ("agent_id");--> statement-breakpoint CREATE INDEX "api_tokens_active_prefix_idx" ON "api_tokens" USING btree ("prefix") WHERE "api_tokens"."status" = 'active';--> statement-breakpoint CREATE INDEX "audit_events_ts_idx" ON "audit_events" USING btree ("ts");--> statement-breakpoint CREATE INDEX "audit_events_type_ts_idx" ON "audit_events" USING btree ("type","ts");--> statement-breakpoint CREATE INDEX "audit_events_agent_ts_idx" ON "audit_events" USING btree ("agent_id","ts") WHERE "audit_events"."agent_id" IS NOT NULL;--> statement-breakpoint CREATE INDEX "messages_room_created_at_idx" ON "messages" USING btree ("room_id","created_at" DESC,"id" DESC);--> statement-breakpoint CREATE INDEX "room_members_agent_id_idx" ON "room_members" USING btree ("agent_id");