Skip to content

Workflow Versioning and Deprecation

Overview

Floh supports safe evolution of published workflows through versioning and deprecation. Instead of editing a live workflow in place (which would break the integrity of existing runs), users create a new draft version that can be modified independently. The previous version can then be deprecated to prevent new runs while allowing in-flight runs to complete.


Workflow Lifecycle

A workflow definition moves through these statuses:

draft → active → deprecated
Status Can edit? Can start runs? Purpose
draft Yes Yes (test runs) Under development
active No (create new version) Yes Production use
deprecated No (create new version) No Superseded; existing runs continue

Workflows can be soft-deleted at any status to remove them from the default list. Soft-deleted workflows can be restored or permanently deleted.


Workspace Writability

Every workflow lives in a workspace (a project row). Which workspaces a caller may write to determines where they can create, version, deprecate, or revert a workflow.

A caller may write to a workspace when any of the following holds:

  • They hold system:administer.
  • The workspace is Default (DEFAULT_PROJECT_ID = 00000000-0000-0000-0000-000000000001).
  • They own the workspace (project.owner_id matches their user id).

There is no workspace membership table — ownership is a single column, so a workspace someone else created is read-only to everyone but its owner and admins. Default exists precisely to give every workflow:create holder somewhere to author.

GET /api/projects returns a canWrite boolean per row that evaluates these same rules for the calling user, so the designer can pre-select a workspace the caller can actually save to and disable the ones they cannot. The server re-checks on every write regardless of what the client sent; canWrite is a UI affordance, not the authorization decision.

The reserved "Default" name

The name Default belongs to the seeded workspace and cannot be taken by another workspace or renamed away. This is enforced in three places:

  • POST /api/projects and PUT /api/projects/:id return 409 when a name normalizes to default on any row other than the seeded one, and when the seeded row is renamed to anything else. Normalization is NFKC + trim + lowercase, so " DEFAULT " and the fullwidth "Default" are both rejected.
  • A partial unique index on lower(btrim(name)) (non-deleted rows only) blocks a duplicate that bypasses the route — soft-deleted rows are exempt so a deleted workspace never blocks the seeded one.
  • A CHECK constraint pins the seeded row's name.

Because both objects cover live rows only, a workspace soft-deleted before the reservation existed can still hold the name. POST /api/projects/:id/restore therefore runs the same guard and returns 409 rather than letting the restore trip the index. Such a workspace currently cannot be restored at all — there is no API path to rename a soft-deleted workspace, tracked in #1227.

Config import binds an incoming workspace named Default to DEFAULT_PROJECT_ID rather than inserting a second row.

Operator note: the migration that installs these objects runs a preflight over every live workspace using the application's normalization — broader than the index expression, so a fullwidth or tab-padded impostor is caught rather than silently left in place. It aborts, writing nothing, and lists the offending ids if any workspace other than the seeded one claims the name. Rename or soft-delete those rows, then re-run.

The same preflight canonicalizes the seeded workspace's name if a prior release left it as a variant spelling (default, " Default "), so editing that workspace does not start returning 409. If the seeded workspace was renamed to something unrelated, the migration aborts and asks you to rename it back rather than overwriting a deliberate choice.


Creating a New Version

When a workflow is active or deprecated, editing it directly is not allowed. Instead, users create a new version:

  1. From the workflow detail page, click New Version.
  2. A confirmation dialog explains that a new draft copy will be created and the original will remain unchanged.
  3. On confirmation, the system clones the workflow as a new draft with a link back to the source.
  4. The user is navigated to the designer to edit the new draft.

What gets copied

The new version is a complete clone of the source workflow, including:

  • Name, description, and error strategy
  • Trigger configuration
  • All context variables
  • All step definitions and transitions
  • Project and workflow set assignment

The new version receives:

  • A new unique ID
  • Status set to draft
  • Version number inherited from the source (incremented upon publish)
  • A sourceWorkflowId reference back to the original

API

POST /api/workflows/:id/create-version

Requirements: workflow:create permission + a write grant on the destination workspace. The seeded Default workspace ships with an all-users write grant, so any workflow:create holder may create there. Other workspaces require ownership, system:administer, or an assigned write grant — see Workspace Access.

Response (201):

{
  "message": "New draft version created",
  "newWorkflow": { ... },
  "sourceWorkflowId": "uuid-of-source"
}

Publishing and Deprecating the Previous Version

When publishing a new version that was created from an existing workflow, the system detects the sourceWorkflowId and prompts the user to deprecate the previous version.

Publish flow

  1. User clicks Publish on the draft workflow.
  2. The server validates the workflow graph (strict mode) and sets status to active.
  3. If the workflow has a sourceWorkflowId and the source workflow is still active, the frontend shows a prompt: "Would you like to deprecate the previous version?"
  4. If the user confirms, a follow-up API call deprecates the source.

The publish endpoint also supports atomic deprecation via the request body:

POST /api/workflows/:id/publish

Body (optional):

{
  "deprecatePreviousVersionId": "uuid-of-previous-version"
}

When deprecatePreviousVersionId is provided and the referenced workflow is active, it is automatically set to deprecated as part of the publish operation.


Deprecating a Workflow

Deprecation marks a workflow as superseded. Deprecated workflows:

  • Cannot start new runs.
  • Continue to serve existing in-flight runs normally.
  • Can have new versions created from them.
  • Can be deleted or (with confirmation) reverted to draft.

API

POST /api/workflows/:id/deprecate

Requirements: workflow:update permission + a write grant on the workflow's workspace. Only active workflows can be deprecated. Default-hosted workflows are writable by any workflow:update holder via Default's all-users grant; other workspaces require ownership, system:administer, or an assigned write grant.

Response (200):

{
  "message": "Workflow deprecated",
  "id": "uuid"
}

UI

The Deprecate button appears on active workflows in both the workflow detail page and the workflow list. A confirmation dialog explains the impact before proceeding.


Reverting to Draft

Reverting a published workflow to draft is a destructive action that should be avoided for production workflows. It is provided as a last resort for development and testing scenarios.

Safeguards

When reverting an active or deprecated workflow to draft:

  1. A warning dialog explains that existing workflow runs would lose integrity.
  2. The dialog advises that creating a new version is the preferred practice.
  3. The user must type the exact workflow name to confirm the action.
  4. The typed name is validated server-side before the revert proceeds.

API

POST /api/workflows/:id/revert-to-draft

Body (required):

{
  "confirmationName": "Exact Workflow Name"
}

If confirmationName does not match the workflow's name, the server returns 400:

{
  "message": "Confirmation name does not match the workflow name"
}

Version Tracking

Each workflow definition has two fields for version tracking:

Field Type Description
version integer Incremented each time a workflow is published
sourceWorkflowId uuid (nullable) References the workflow this version was cloned from

The sourceWorkflowId creates a chain of versions that can be followed to trace the history of a workflow. The workflow detail page displays a "Previous Version" link when a source exists.

Database

The source_workflow_id column was added in migration 012_workflow_versioning:

ALTER TABLE workflow_definition
  ADD COLUMN source_workflow_id VARCHAR(255) DEFAULT NULL;

ALTER TABLE workflow_definition
  ADD CONSTRAINT fk_wf_source
  FOREIGN KEY (source_workflow_id)
  REFERENCES workflow_definition(id)
  ON DELETE SET NULL;

The v_workflow_definition view includes this column.


Run Behavior by Status

Workflow status New runs Existing runs
draft Allowed (test runs) Continue normally
active Allowed Continue normally
deprecated Blocked (400 error) Continue normally

When a deprecated workflow is started, the API returns:

{
  "statusCode": 400,
  "message": "Deprecated workflows cannot start new runs. Create a new version instead."
}

Audit Logging

All versioning and deprecation operations are recorded in the audit log:

Action Description
workflow.version_created New draft version cloned from a published workflow
workflow.published Workflow published (draft → active)
workflow.deprecated Workflow deprecated (active → deprecated)
workflow.reverted_to_draft Workflow reverted to draft

The workflow.version_created and workflow.deprecated entries include metadata linking the source and target versions.


UI Summary

Workflow Detail Page

Status Available Actions
draft Test Run, Edit, Publish, Delete
active Start Run, View Runs, New Version, Deprecate, Delete
deprecated View Runs, New Version, Revert to Draft, Delete

Workflow List Page

Status Row Actions
draft Test Run, Edit, Publish, Delete
active Start Run, New Version, Deprecate, Delete
deprecated New Version, Delete

The status filter dropdown includes Deprecated as an option. Deprecated workflows display with a red/danger status badge.


  1. Never revert a production workflow to draft. Always create a new version instead. Reverting breaks the integrity of existing runs that reference the workflow's published state.

  2. Deprecate the previous version when publishing a new one. This ensures users always start runs on the latest version.

  3. Delete deprecated workflows once all their runs have completed, to keep the workflow list clean. Deleted workflows can be restored if needed.

  4. Use version tracking to maintain an audit trail of how workflows have evolved over time.