diff --git a/scripts/supabase-target/invariants.ts b/scripts/supabase-target/invariants.ts index 1342edb1..837dba7f 100644 --- a/scripts/supabase-target/invariants.ts +++ b/scripts/supabase-target/invariants.ts @@ -181,7 +181,15 @@ export async function validateIntegrityConstraints(client: PgClient): Promise candidate["conname"] === contract.name); - if (row && normalizedSqlDefinition(row["definition"]) !== contract.definition) { + // pg_get_constraintdef's AND-chain grouping changed between Postgres + // minors (17.4 prints left-associated parentheses, 17.6 flattens), so the + // comparison ignores parentheses. Operator, literal, and regex changes + // still surface, which is what this drift check exists to catch. + if ( + row && + parenInsensitiveSqlDefinition(row["definition"]) !== + parenInsensitiveSqlDefinition(contract.definition) + ) { issues.push(`Security check public.${contract.tableName}.${contract.name} has drifted.`); } } @@ -261,3 +269,7 @@ function stringField(row: Record, key: string): string | undefi function normalizedSqlDefinition(value: unknown): string { return typeof value === "string" ? value.replaceAll(/\s+/g, " ").trim() : ""; } + +function parenInsensitiveSqlDefinition(value: unknown): string { + return normalizedSqlDefinition(value).replaceAll(/[()]/g, "").replaceAll(/\s+/g, " ").trim(); +}