From 896b936890d6c56f406eeb9d83ad1f08c4472173 Mon Sep 17 00:00:00 2001 From: "Platform (sharpapi)" Date: Wed, 13 May 2026 08:09:27 -0400 Subject: [PATCH] feat(db): add closed_state_consistency CHECK to execution_workspaces (SHA-2492) execution_workspaces rows must satisfy closed_at IS NULL OR status IN ('archived','cleanup_failed'). The contradictory state (closed_at stamped, status='active'/'idle') fails-closed in isClosedIsolatedExecutionWorkspace and 409s issue comments/PATCHes. Added with NOT VALID so the deploy is fast and does not lock the table while validating every existing row. The constraint takes effect for all INSERTs and UPDATEs immediately. Existing violators (1037 rows on prod paperclip DB at filing time) need to be backfilled to status='archived' before ALTER TABLE execution_workspaces VALIDATE CONSTRAINT execution_workspaces_closed_state_consistency runs as a follow-up. A pre-validate audit confirmed 0 violator rows have a non-terminal source issue, so the bulk archive is safe. Companion: PR sha-2492-patch-normalize-closed-at fixes the PATCH route so reopens clear closed_at (otherwise the new CHECK would reject them). --- .../0077_workspace_closed_state_consistency.sql | 12 ++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 +++++++ packages/db/src/schema/execution_workspaces.ts | 7 +++++++ 3 files changed, 26 insertions(+) create mode 100644 packages/db/src/migrations/0077_workspace_closed_state_consistency.sql diff --git a/packages/db/src/migrations/0077_workspace_closed_state_consistency.sql b/packages/db/src/migrations/0077_workspace_closed_state_consistency.sql new file mode 100644 index 00000000000..6ac4f108767 --- /dev/null +++ b/packages/db/src/migrations/0077_workspace_closed_state_consistency.sql @@ -0,0 +1,12 @@ +-- SHA-2492: closed_at MUST pair with a closed status (archived/cleanup_failed). +-- +-- Added with NOT VALID so the migration is fast and does not lock the table +-- while validating every existing row. The constraint takes effect for all +-- INSERTs and UPDATEs immediately. Existing violator rows (1037 known on +-- prod paperclip DB at filing time) must be backfilled to status='archived' +-- before running `ALTER TABLE ... VALIDATE CONSTRAINT +-- execution_workspaces_closed_state_consistency` as a follow-up. +ALTER TABLE "execution_workspaces" + ADD CONSTRAINT "execution_workspaces_closed_state_consistency" + CHECK ("closed_at" IS NULL OR "status" IN ('archived', 'cleanup_failed')) + NOT VALID; diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 6833d3032d3..ea437a7a16b 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -540,6 +540,13 @@ "when": 1776541198667, "tag": "0076_budget_incidents_dedupe_open_only", "breakpoints": true + }, + { + "idx": 77, + "version": "7", + "when": 1778800000000, + "tag": "0077_workspace_closed_state_consistency", + "breakpoints": true } ] } diff --git a/packages/db/src/schema/execution_workspaces.ts b/packages/db/src/schema/execution_workspaces.ts index 72e63d5b851..97abb9ecbb3 100644 --- a/packages/db/src/schema/execution_workspaces.ts +++ b/packages/db/src/schema/execution_workspaces.ts @@ -1,5 +1,7 @@ +import { sql } from "drizzle-orm"; import { type AnyPgColumn, + check, index, jsonb, pgTable, @@ -64,5 +66,10 @@ export const executionWorkspaces = pgTable( table.companyId, table.branchName, ), + // closed_at MUST pair with a closed status. See SHA-2492. + closedStateConsistency: check( + "execution_workspaces_closed_state_consistency", + sql`${table.closedAt} IS NULL OR ${table.status} IN ('archived', 'cleanup_failed')`, + ), }), );