Compare commits

...

2 commits

Author SHA1 Message Date
Alberto Martinez
c5f9ac0563 Fix IAM v2 Liquibase migrate failure on deploy
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Problem

Coolify deploy built all images successfully but failed when the `migrate` job exited with code 1. The IAM changeset `005-iam-v2-roles-crud.sql` had already applied through `005g` on production; it failed at `005h-swap-roles`.

## Root cause

Changeset `005h` used `splitStatements:true` with `endDelimiter:;` while containing a `DO $$ ... END $$` block. Liquibase splits on every semicolon, which breaks PL/pgSQL blocks and causes the migrate step to fail.

Secondary risks on production data:
- Users without `role_id` after backfill (missing per-tenant `operativo` role)
- Multiple `tenant_admin` users in the same tenant (unique partial index would fail)

## Fix

1. **Move the unique-index `DO` block** to a new changeset `005h2-unique-tenant-admin` with `splitStatements:false`.
2. **Add changeset `005g2-ensure-role-backfill`** (new, safe for DBs that already ran `005g`):
   - Create `operativo` roles for every tenant that has users
   - Re-backfill any remaining `role_id`
   - Demote duplicate tenant admins to `operativo` (keep lowest `id` per tenant)
   - Remove orphan users without `tenant_id`

## Deploy

Merge to `main` and redeploy in Coolify. No manual SQL should be required if the DB stopped at `005h` as expected.

If migrate still fails, capture logs with:

```bash
docker logs migrate-pdyrt8ccp804tfmutefau0k6-<latest> 2>&1 | tail -100
```
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a href="https://cursor.com/agents/bc-0da3751a-38ff-40ab-bba2-5df2322c8cda?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a href="https://cursor.com/background-agent?bcId=bc-0da3751a-38ff-40ab-bba2-5df2322c8cda&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>
2026-09-08 18:58:33 +00:00
Cursor Agent
a9fbca232d
Fix IAM v2 migrate: split DO block and harden role backfill
The migrate job failed at changeset 005h during Coolify deploy. Liquibase
was splitting the DO $$ block on inner semicolons (splitStatements:true),
which breaks PL/pgSQL. Move the unique-index DO block to 005h2 with
splitStatements:false.

Add 005g2 to create missing operativo roles per tenant, backfill any
remaining role_id values, demote duplicate tenant_admin users to operativo,
and remove orphan users without tenant_id before SET NOT NULL.

Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
2026-09-08 18:57:19 +00:00

View file

@ -230,6 +230,49 @@ FROM iam.roles_v2 sr
WHERE u.role_id IS NULL
AND sr.code = 'operativo' AND sr.tenant_id = u.tenant_id;
--changeset panel:iam-005g2-ensure-role-backfill endDelimiter:; splitStatements:true
--preconditions onFail:MARK_RAN
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='iam' AND table_name='roles' AND column_name='id'
INSERT INTO iam.roles_v2 (tenant_id, code, label, is_system)
SELECT DISTINCT u.tenant_id, 'operativo', 'Operativo', false
FROM iam.users u
WHERE u.tenant_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM iam.roles_v2 r
WHERE r.tenant_id = u.tenant_id AND r.code = 'operativo'
);
UPDATE iam.users u
SET role_id = sr.id
FROM iam.roles_v2 sr
WHERE u.role_code = 'tenant_admin'
AND sr.code = 'tenant_admin' AND sr.is_system AND sr.tenant_id IS NULL
AND u.role_id IS NULL;
UPDATE iam.users u
SET role_id = sr.id
FROM iam.roles_v2 sr
WHERE u.role_id IS NULL
AND u.tenant_id IS NOT NULL
AND sr.code = 'operativo' AND sr.tenant_id = u.tenant_id;
UPDATE iam.users u
SET role_id = op.id
FROM iam.roles_v2 sr, iam.roles_v2 op
WHERE u.role_id = sr.id
AND sr.code = 'tenant_admin' AND sr.is_system AND sr.tenant_id IS NULL
AND u.tenant_id IS NOT NULL
AND op.code = 'operativo' AND op.tenant_id = u.tenant_id
AND u.id <> (
SELECT MIN(u2.id)
FROM iam.users u2
JOIN iam.roles_v2 sr2 ON sr2.id = u2.role_id
WHERE u2.tenant_id = u.tenant_id
AND sr2.code = 'tenant_admin' AND sr2.is_system AND sr2.tenant_id IS NULL
);
DELETE FROM iam.users WHERE role_id IS NULL AND tenant_id IS NULL;
--changeset panel:iam-005h-swap-roles endDelimiter:; splitStatements:true
--preconditions onFail:MARK_RAN
--precondition-sql-check expectedResult:0 SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='iam' AND table_name='roles' AND column_name='id'
@ -251,6 +294,7 @@ ALTER TABLE iam.users
ALTER TABLE iam.users ALTER COLUMN role_id SET NOT NULL;
--changeset panel:iam-005h2-unique-tenant-admin splitStatements:false
DO $$
DECLARE
v_admin_role_id bigint;