Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Workflow versioning provides complete change tracking, rollback capability, and an immutable audit trail for compliance. Every time you publish a workflow, a permanent snapshot is created that preserves the exact state of nodes, edges, and canvas viewport.

Key Concepts

Versions vs. Drafts

  • Draft Edits: Changes made in the workflow editor are not versioned automatically
  • Publishing: Creates an immutable version snapshot with an incremented version number
  • Version Numbers: Sequential integers starting at 1: v1, v2, v3, etc.
  • Immutability: Once published, versions cannot be edited or deleted

Immutability

Workflow versions are permanently immutable:
  • Cannot be edited after creation
  • Cannot be deleted (preserves complete audit trail)
  • Rollback creates a new version (doesn’t modify the old one)
  • All versions remain accessible forever
This ensures regulatory compliance and provides a complete history of workflow changes.

Use Cases

1. Production Issue Recovery

Quickly rollback when workflow changes break production:
// User publishes v2 with bug
await createVersion({ notes: 'Added new approval step' });

// Production breaks - rollback to v1
await rollback({ 
  target_version: 1, 
  notes: 'Emergency rollback - approval node causing timeout' 
});

// Creates v3 (identical to v1) with rollback tag

2. Compliance Audit

Track who changed what and when for regulatory requirements:
  • Who: published_by user ID links to profile
  • What: Complete snapshot of workflow state
  • When: published_at timestamp
  • Why: Optional notes field for change descriptions
Auditors can see:
  • Version history timeline
  • Changes between versions (visual diff)
  • Rollback events and reasons
  • User attribution for each change

3. Debugging

Compare versions to understand what changed:
const diff = await compareVersions(2, 3);

console.log(diff.nodes.added);     // New nodes in v3
console.log(diff.nodes.removed);   // Deleted from v2
console.log(diff.nodes.modified);  // Changed configuration
console.log(diff.edges.added);     // New connections
console.log(diff.edges.removed);   // Disconnected paths

4. A/B Testing

Run different workflow versions and compare results:
// Deploy v2 to production
await createVersion({ 
  notes: 'Test new notification timing', 
  tags: ['production', 'experiment-a'] 
});

// Later: Deploy v3 with different timing
await createVersion({ 
  notes: 'Alternative notification timing', 
  tags: ['production', 'experiment-b'] 
});

// Compare execution results
// SELECT * FROM fw_workflow_executions WHERE workflow_version IN (2, 3)

5. Staged Rollouts

Use tags to manage deployment stages:
// Development
await createVersion({ tags: ['development', 'feature-xyz'] });

// Staging
await createVersion({ tags: ['staging', 'feature-xyz'] });

// Production
await createVersion({ tags: ['production', 'v2.1.0'] });

API Reference

useWorkflowVersions Hook

Complete workflow version management hook.
import { useWorkflowVersions } from '@/cores/fw/hooks/useWorkflowVersions';

const {
  versions,              // All versions (newest first)
  isLoading,            // Loading state
  error,                // Error state
  getVersion,           // Fetch specific version
  createVersion,        // Create new version
  isCreatingVersion,    // Creating state
  rollback,             // Rollback to version
  isRollingBack,        // Rolling back state
  compareVersions,      // Visual diff
} = useWorkflowVersions(ruleId);

Creating a Version

// Minimal
await createVersion({ notes: 'Bug fix' });

// With tags
await createVersion({ 
  notes: 'Added approval gates', 
  tags: ['production', 'v2.0'] 
});

// Returns: Version number (e.g., 3)

Rolling Back

await rollback({ 
  target_version: 2,
  notes: 'Rollback due to production issue #123'
});

// Creates new version (e.g., v4) identical to v2
// Tags rollback version automatically

Comparing Versions

const diff = await compareVersions(1, 3);

// Returns:
{
  versionA: { /* Version 1 data */ },
  versionB: { /* Version 3 data */ },
  diff: {
    nodes: {
      added: [/* Nodes added in v3 */],
      removed: [/* Nodes removed from v1 */],
      modified: [/* Nodes changed between versions */],
    },
    edges: {
      added: [/* Connections added in v3 */],
      removed: [/* Connections removed from v1 */],
    }
  }
}

Getting Specific Version

const version = await getVersion(2);

console.log(version.version);            // 2
console.log(version.published_by);       // User ID
console.log(version.published_at);       // ISO timestamp
console.log(version.workflow_snapshot);  // { nodes, edges, viewport }
console.log(version.notes);              // Change description
console.log(version.tags);               // ['production', 'v1.5']

Database Schema

fw_workflow_versions Table

CREATE TABLE fw_workflow_versions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  organization_id UUID NOT NULL REFERENCES pf_organizations(id),
  rule_id UUID NOT NULL REFERENCES fw_automation_rules(id),
  version INTEGER NOT NULL,
  published_by UUID NOT NULL REFERENCES auth.users(id),
  published_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  workflow_snapshot JSONB NOT NULL,  -- { nodes, edges, viewport }
  notes TEXT,
  tags TEXT[],
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (rule_id, version)
);
Snapshot Structure:
interface WorkflowSnapshot {
  nodes: Array<{
    id: string;
    type: string;
    position: { x: number; y: number };
    data: Record<string, any>;
  }>;
  edges: Array<{
    id: string;
    source: string;
    target: string;
    sourceHandle?: string;
    targetHandle?: string;
  }>;
  viewport?: {
    x: number;
    y: number;
    zoom: number;
  };
}

Execution Version Linking

The fw_workflow_executions table links each execution to the version used:
CREATE TABLE fw_workflow_executions (
  id UUID PRIMARY KEY,
  rule_id UUID NOT NULL REFERENCES fw_automation_rules(id),
  workflow_version INTEGER,  -- Links to version used
  status TEXT NOT NULL,
  -- ... other fields
);
Query executions by version:
SELECT * FROM fw_workflow_executions 
WHERE rule_id = 'rule-uuid' 
  AND workflow_version = 2;

Best Practices

1. Publish Before Major Changes

Create a version before making risky changes:
// Before refactoring
await createVersion({ 
  notes: 'Pre-refactor checkpoint', 
  tags: ['checkpoint'] 
});

// Make changes...

// After testing
await createVersion({ 
  notes: 'Refactored error handling', 
  tags: ['refactor-complete'] 
});

2. Add Descriptive Notes

Help future maintainers understand changes:
// ❌ Bad
await createVersion({ notes: 'Changes' });

// ✅ Good
await createVersion({ 
  notes: 'Fixed timeout in approval node (15min → 30min) per ticket #456' 
});

3. Use Meaningful Tags

Organize versions with consistent tagging:
// Environment tags
tags: ['development']
tags: ['staging']
tags: ['production']

// Release tags
tags: ['v1.0.0', 'production']
tags: ['v1.0.1', 'hotfix']

// Feature tags
tags: ['feature-notifications', 'staging']

// Special markers
tags: ['rollback', 'emergency']
tags: ['checkpoint', 'pre-refactor']

4. Review Diff Before Rollback

Always compare versions before rolling back:
// 1. Compare versions
const diff = await compareVersions(currentVersion, targetVersion);

// 2. Review changes
console.log('Will remove:', diff.nodes.removed.length, 'nodes');
console.log('Will add:', diff.nodes.added.length, 'nodes');

// 3. Confirm and rollback
if (userConfirmed) {
  await rollback({ target_version: targetVersion });
}
Reference issue tracking systems in notes:
await createVersion({ 
  notes: 'Implemented multi-level approval (JIRA-1234)',
  tags: ['feature-approvals', 'sprint-23']
});

Security & Compliance

Immutability

Versions cannot be modified or deleted:
  • No UPDATE policy on fw_workflow_versions
  • No DELETE policy on fw_workflow_versions
  • Even platform admins cannot alter versions
This ensures audit trail integrity for compliance.

Multi-Tenant Isolation

Row-Level Security (RLS) ensures:
  • Users can only see versions in their organization
  • Cross-tenant version access is blocked
  • Execution version links are organization-scoped

Audit Trail

Every version captures:
  • Who: User ID of publisher
  • When: Timestamp of publication
  • What: Complete workflow state
  • Why: Optional notes explaining changes

Troubleshooting

Version Creation Fails

Symptom: create_workflow_version() returns error Causes:
  1. No workflow definition exists for the rule
  2. User lacks organization access
  3. Invalid rule_id
Solution:
// Verify definition exists
const { data } = await supabase
  .from('fw_workflow_definitions')
  .select()
  .eq('rule_id', ruleId)
  .single();

if (!data) {
  console.error('No workflow definition found');
}

Rollback Creates Wrong Version

Symptom: Rolled back version doesn’t match target Cause: Snapshot stored incorrectly Solution:
// Verify target version snapshot
const { data: targetVersion } = await supabase
  .from('fw_workflow_versions')
  .select('workflow_snapshot')
  .eq('version', targetVersionNum)
  .single();

console.log('Target nodes:', targetVersion.workflow_snapshot.nodes);

Version Comparison Incorrect

Symptom: Diff shows wrong changes Cause: Node IDs changed between versions Solution: Version comparison uses node IDs as identity. If you manually change node IDs, comparison will treat them as different nodes.

Migration Guide

Adding Versioning to Existing Workflows

If you have workflows created before versioning was implemented:
// For each existing workflow:
const { data: definition } = await supabase
  .from('fw_workflow_definitions')
  .select()
  .eq('rule_id', ruleId)
  .single();

if (definition) {
  // Create initial version (v1)
  await supabase.rpc('create_workflow_version', {
    p_rule_id: ruleId,
    p_published_by: currentUserId,
    p_notes: 'Initial version (migrated)',
    p_tags: ['migration', 'v1.0'],
  });
}