Skip to content

Commit 2ecbfdf

Browse files
committed
fix(projects): backfill only families missing membership
1 parent 78f773d commit 2ecbfdf

2 files changed

Lines changed: 29 additions & 2 deletions

File tree

‎packages/db/migrations/0394_project_membership_enforcement.sql‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@ SET statement_timeout = '15min';
66
--> statement-breakpoint
77
CREATE TEMP TABLE IF NOT EXISTS project_backfill_roots (id text PRIMARY KEY) ON COMMIT PRESERVE ROWS;
88
TRUNCATE project_backfill_roots;
9-
INSERT INTO project_backfill_roots SELECT id FROM workspace WHERE forked_from_workspace_id IS NULL;
9+
-- Only legacy families need assignment locks; existing complete Projects only need validation.
10+
WITH RECURSIVE ancestors(id, parent_id) AS (
11+
SELECT w.id, w.forked_from_workspace_id FROM workspace w
12+
LEFT JOIN project_workspace pw ON pw.workspace_id = w.id WHERE pw.workspace_id IS NULL
13+
UNION
14+
SELECT w.id, w.forked_from_workspace_id FROM workspace w JOIN ancestors a ON a.parent_id = w.id
15+
)
16+
INSERT INTO project_backfill_roots SELECT id FROM ancestors WHERE parent_id IS NULL;
1017
--> statement-breakpoint
1118
CREATE OR REPLACE PROCEDURE pg_temp.backfill_project_families() LANGUAGE plpgsql AS $$
1219
DECLARE
@@ -138,7 +145,7 @@ BEGIN
138145
RAISE EXCEPTION 'Project backfill found a fork cycle or missing parent; reconcile before retrying' USING ERRCODE = '55000';
139146
END IF;
140147
IF EXISTS (SELECT 1 FROM workspace w LEFT JOIN project_workspace pw ON pw.workspace_id = w.id WHERE pw.workspace_id IS NULL) THEN
141-
RAISE EXCEPTION 'Project enforcement found environments unreachable from a valid fork root or concurrently detached; reconcile and retry' USING ERRCODE = '55000';
148+
RAISE EXCEPTION 'Project backfill found newly unassigned or detached environments; retry migration discovery' USING ERRCODE = '55P03';
142149
END IF;
143150
IF EXISTS (
144151
SELECT 1 FROM project p LEFT JOIN project_workspace pw ON pw.project_id = p.id

‎packages/db/scripts/project-contract.integration.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,4 +625,24 @@ describe('Project expand/backfill/contract against PostgreSQL', () => {
625625
expect(await sql`SELECT 1 FROM project_workspace`).toHaveLength(2)
626626
})
627627
}, 15000)
628+
it.each(['separate-roots', 'large-family'] as const)(
629+
'preserves a complete existing Project with %s without putting it through legacy assignment',
630+
async (shape) => {
631+
await database(async (sql) => {
632+
await sql`INSERT INTO project (id, name, owner_id) VALUES ('existing', 'Keep this Project', 'owner')`
633+
await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('root', 'Root', 'owner')`
634+
const count = shape === 'large-family' ? 1001 : 1
635+
await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id)
636+
SELECT 'other-' || n, 'Other', 'owner', ${shape === 'large-family' ? 'root' : null} FROM generate_series(1, ${count}) n`
637+
await sql`INSERT INTO project_workspace (project_id, workspace_id) SELECT 'existing', id FROM workspace`
638+
await enforce(sql)
639+
expect(await sql`SELECT id, name FROM project`).toEqual([
640+
{ id: 'existing', name: 'Keep this Project' },
641+
])
642+
expect((await sql`SELECT count(*)::int AS count FROM project_workspace`)[0].count).toBe(
643+
count + 1
644+
)
645+
})
646+
}
647+
)
628648
})

0 commit comments

Comments
 (0)