From f7d474965f4ce446137e5ab6af2e770d94d5111b Mon Sep 17 00:00:00 2001 From: mfsiega <93014743+mfsiega@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:42:22 +0200 Subject: [PATCH] feat(core): Enforce one pending outbox row per workflow (no-changelog) (#31497) --- .../db/src/entities/workflow-publication-outbox.ts | 6 +++++- ...000000020-CreateWorkflowPublicationOutboxTable.ts | 12 +++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/@n8n/db/src/entities/workflow-publication-outbox.ts b/packages/@n8n/db/src/entities/workflow-publication-outbox.ts index 2c20a6cfa51..a0641c37aaf 100644 --- a/packages/@n8n/db/src/entities/workflow-publication-outbox.ts +++ b/packages/@n8n/db/src/entities/workflow-publication-outbox.ts @@ -1,4 +1,4 @@ -import { Column, Entity, PrimaryGeneratedColumn } from '@n8n/typeorm'; +import { Column, Entity, Index, PrimaryGeneratedColumn } from '@n8n/typeorm'; import { WithTimestamps } from './abstract-entity'; @@ -10,6 +10,10 @@ export type WorkflowPublicationOutboxStatus = | 'failed'; @Entity({ name: 'workflow_publication_outbox' }) +@Index('IDX_workflow_publication_outbox_pending_workflow', ['workflowId'], { + unique: true, + where: "status = 'pending'", +}) export class WorkflowPublicationOutbox extends WithTimestamps { @PrimaryGeneratedColumn() id: number; diff --git a/packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts b/packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts index 5df4a11ab25..e9b1a15e470 100644 --- a/packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts +++ b/packages/@n8n/db/src/migrations/common/1784000000020-CreateWorkflowPublicationOutboxTable.ts @@ -6,7 +6,7 @@ import type { MigrationContext, ReversibleMigration } from '../migration-types'; * publication request that the outbox consumer will process asynchronously. */ export class CreateWorkflowPublicationOutboxTable1784000000020 implements ReversibleMigration { - async up({ schemaBuilder: { createTable, column } }: MigrationContext) { + async up({ schemaBuilder: { createTable, createIndex, column } }: MigrationContext) { await createTable('workflow_publication_outbox').withColumns( column('id').int.primary.autoGenerate2, // No foreign keys on workflowId or publishedVersionId: this is a @@ -34,6 +34,16 @@ export class CreateWorkflowPublicationOutboxTable1784000000020 implements Revers 'Error details for surfacing failed publications to the user.', ), ).withTimestamps; + + // At most one pending record per workflow: enqueueing a newer version + // while one is still pending supersedes the older publishedVersionId. + await createIndex( + 'workflow_publication_outbox', + ['workflowId'], + true, + 'IDX_workflow_publication_outbox_pending_workflow', + "status = 'pending'", + ); } async down({ schemaBuilder: { dropTable } }: MigrationContext) {