WORKFL: Workflow Engine

Purpose: DAG-based workflow execution for deployments, approvals, and custom automation.

Modules

Module: workflow-designer

AspectSpecification
ResponsibilityTemplate creation; DAG graph editor; validation
Dependenciesstep-registry
Data EntitiesWorkflowTemplate, StepNode, StepEdge

Workflow Template Structure:

interface WorkflowTemplate {
  id: UUID;
  tenantId: UUID;
  name: string;
  displayName: string;
  description: string;
  version: number;

  // DAG structure
  nodes: StepNode[];
  edges: StepEdge[];

  // I/O
  inputs: InputDefinition[];
  outputs: OutputDefinition[];

  // Metadata
  tags: string[];
  isBuiltin: boolean;
  createdAt: DateTime;
  createdBy: UUID;
}

Module: workflow-engine

AspectSpecification
ResponsibilityDAG execution; state machine; pause/resume
Dependenciesstep-executor, step-registry
Data EntitiesWorkflowRun, WorkflowState
Events Producedworkflow.started, workflow.paused, workflow.resumed, workflow.completed, workflow.failed

Workflow Execution Algorithm:

class WorkflowEngine:
    def execute(self, workflow_run: WorkflowRun) -> None:
        """Main workflow execution loop."""

        # Initialize
        workflow_run.status = "running"
        workflow_run.started_at = now()
        self.save(workflow_run)

        try:
            while not self.is_terminal(workflow_run):
                # Handle pause state
                if workflow_run.status == "paused":
                    self.wait_for_resume(workflow_run)
                    continue

                # Get nodes ready for execution
                ready_nodes = self.get_ready_nodes(workflow_run)

                if not ready_nodes:
                    # Check if we're waiting on approvals
                    if self.has_pending_approvals(workflow_run):
                        workflow_run.status = "paused"
                        self.save(workflow_run)
                        continue

                    # Check if all nodes are complete
                    if self.all_nodes_complete(workflow_run):
                        break

                    # Deadlock detection
                    raise WorkflowDeadlockError(workflow_run.id)

                # Execute ready nodes in parallel
                futures = []
                for node in ready_nodes:
                    future = self.executor.submit(
                        self.execute_node,
                        workflow_run,
                        node
                    )
                    futures.append((node, future))

                # Wait for at least one to complete
                completed = self.wait_any(futures)

                for node, result in completed:
                    step_run = self.get_step_run(workflow_run, node.id)

                    if result.success:
                        step_run.status = "succeeded"
                        step_run.outputs = result.outputs
                        self.propagate_outputs(workflow_run, node, result.outputs)
                    else:
                        step_run.status = "failed"
                        step_run.error_message = result.error

                        # Handle failure action
                        if node.on_failure == "fail":
                            workflow_run.status = "failed"
                            workflow_run.error_message = f"Step {node.name} failed: {result.error}"
                            self.cancel_pending_steps(workflow_run)
                            return
                        elif node.on_failure == "rollback":
                            self.trigger_rollback(workflow_run, node)
                        elif node.on_failure.startswith("goto:"):
                            target = node.on_failure.split(":")[1]
                            self.add_ready_node(workflow_run, target)
                        # "continue" just continues to next nodes

                    step_run.completed_at = now()
                    self.save(step_run)

            # Workflow completed successfully
            workflow_run.status = "succeeded"
            workflow_run.completed_at = now()
            self.save(workflow_run)

        except WorkflowCancelledError:
            workflow_run.status = "cancelled"
            workflow_run.completed_at = now()
            self.save(workflow_run)
        except Exception as e:
            workflow_run.status = "failed"
            workflow_run.error_message = str(e)
            workflow_run.completed_at = now()
            self.save(workflow_run)

Module: step-executor

AspectSpecification
ResponsibilityStep dispatch; retry logic; timeout handling
Dependenciesstep-registry, plugin-sandbox
Data EntitiesStepRun, StepResult
Events Producedstep.started, step.progress, step.completed, step.failed, step.retrying

Step Node Structure:

interface StepNode {
  id: string;                    // Unique within template (e.g., "deploy-api")
  type: string;                  // Step type from registry
  name: string;                  // Display name
  config: Record<string, any>;   // Step-specific configuration
  inputs: InputBinding[];        // Input value bindings
  outputs: OutputBinding[];      // Output declarations
  position: { x: number; y: number };  // UI position

  // Execution settings
  timeout: number;               // Seconds (default from step type)
  retryPolicy: RetryPolicy;
  onFailure: FailureAction;
  condition?: string;            // JS expression for conditional execution

  // Documentation
  description?: string;
  documentation?: string;
}

type FailureAction = "fail" | "continue" | "rollback" | "goto:{nodeId}";

interface InputBinding {
  name: string;                  // Input parameter name
  source: InputSource;
}

type InputSource =
  | { type: "literal"; value: any }
  | { type: "context"; path: string }        // e.g., "release.name"
  | { type: "output"; nodeId: string; outputName: string }
  | { type: "secret"; secretName: string }
  | { type: "expression"; expression: string };  // JS expression

interface StepEdge {
  id: string;
  from: string;           // Source node ID
  to: string;             // Target node ID
  condition?: string;     // Optional condition expression
  label?: string;         // Display label for conditional edges
}

interface RetryPolicy {
  maxRetries: number;
  backoffType: "fixed" | "exponential";
  backoffSeconds: number;
  retryableErrors: string[];
}

Module: step-registry

AspectSpecification
ResponsibilityBuilt-in + plugin-provided step types
Dependenciesplugin-registry
Data EntitiesStepType, StepSchema

Built-in Step Types:

Step TypeCategoryDescription
approvalControlWait for human approval
security-gateGateEvaluate security policy
custom-gateGateCustom OPA policy evaluation
deploy-dockerDeployDeploy single container
deploy-composeDeployDeploy Docker Compose stack
deploy-ecsDeployDeploy to AWS ECS
deploy-nomadDeployDeploy to HashiCorp Nomad
health-checkVerifyHTTP/TCP health check
smoke-testVerifyRun smoke test suite
execute-scriptCustomRun C#/Bash script
webhookIntegrationCall external webhook
trigger-ciIntegrationTrigger CI pipeline
wait-ciIntegrationWait for CI pipeline
notifyNotificationSend notification
rollbackRecoveryRollback deployment
traffic-shiftProgressiveShift traffic percentage

Step Type Definition:

interface StepType {
  type: string;                  // "deploy-compose"
  displayName: string;           // "Deploy Compose Stack"
  description: string;
  category: StepCategory;
  icon: string;

  // Schema
  configSchema: JSONSchema;      // Step configuration schema
  inputSchema: JSONSchema;       // Required inputs schema
  outputSchema: JSONSchema;      // Produced outputs schema

  // Execution
  executor: "builtin" | UUID;    // builtin or plugin ID
  defaultTimeout: number;
  safeToRetry: boolean;
  retryableErrors: string[];

  // Documentation
  documentation: string;
  examples: StepExample[];
}

Workflow Run State Machine

┌─────────────────────────────────────────────────────────────────────────────┐
│                    WORKFLOW RUN STATE MACHINE                               │
│                                                                             │
│                              ┌──────────┐                                   │
│                              │ CREATED  │                                   │
│                              └────┬─────┘                                   │
│                                   │ start()                                 │
│                                   ▼                                         │
│                    ┌─────────────────────────────┐                          │
│                    │                             │                          │
│         pause() ┌──┴──────────┐                  │                          │
│       ┌────────►│   PAUSED    │◄─────────┐      │                          │
│       │         └──────┬──────┘          │      │                          │
│       │                │ resume()        │      │                          │
│       │                ▼                 │      │                          │
│       │         ┌─────────────┐          │      │                          │
│       └─────────│   RUNNING   │──────────┘      │                          │
│                 └──────┬──────┘ (waiting for    │                          │
│                        │         approval)       │                          │
│           ┌────────────┼────────────┐           │                          │
│           │            │            │           │                          │
│           ▼            ▼            ▼           │                          │
│    ┌───────────┐ ┌───────────┐ ┌───────────┐   │                          │
│    │ SUCCEEDED │ │  FAILED   │ │ CANCELLED │   │                          │
│    └───────────┘ └───────────┘ └───────────┘   │                          │
│                                                                             │
│    Transitions:                                                             │
│    - CREATED → RUNNING: start()                                            │
│    - RUNNING → PAUSED: pause(), waiting approval                           │
│    - PAUSED → RUNNING: resume(), approval granted                          │
│    - RUNNING → SUCCEEDED: all nodes complete                               │
│    - RUNNING → FAILED: node fails with fail action                         │
│    - RUNNING → CANCELLED: cancel()                                         │
│    - PAUSED → CANCELLED: cancel()                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Step Run State Machine

┌─────────────────────────────────────────────────────────────────────────────┐
│                      STEP RUN STATE MACHINE                                 │
│                                                                             │
│    ┌──────────┐                                                             │
│    │ PENDING  │ ◄──── Initial state; dependencies not met                  │
│    └────┬─────┘                                                             │
│         │ dependencies met + condition true                                 │
│         ▼                                                                   │
│    ┌──────────┐                                                             │
│    │ RUNNING  │ ◄──── Step is executing                                    │
│    └────┬─────┘                                                             │
│         │                                                                   │
│    ┌────┴────────────────┬─────────────────┐                               │
│    │                     │                 │                               │
│    ▼                     ▼                 ▼                               │
│ ┌───────────┐      ┌───────────┐     ┌───────────┐                        │
│ │ SUCCEEDED │      │  FAILED   │     │  SKIPPED  │                        │
│ └───────────┘      └─────┬─────┘     └───────────┘                        │
│                          │            ▲                                    │
│                          │            │ condition false                    │
│                          ▼            │                                    │
│                    ┌───────────┐      │                                    │
│                    │ RETRYING  │──────┘ (max retries exceeded)             │
│                    └─────┬─────┘                                           │
│                          │                                                  │
│                          │ retry attempt                                    │
│                          └──────────────────┐                              │
│                                             │                              │
│                                             ▼                              │
│                                        ┌──────────┐                        │
│                                        │ RUNNING  │ (retry)                │
│                                        └──────────┘                        │
│                                                                             │
│    Additional transitions:                                                  │
│    - Any state → CANCELLED: workflow cancelled                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Database Schema

-- Workflow Templates
CREATE TABLE release.workflow_templates (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    display_name VARCHAR(255) NOT NULL,
    description TEXT,
    version INTEGER NOT NULL DEFAULT 1,
    nodes JSONB NOT NULL,
    edges JSONB NOT NULL,
    inputs JSONB NOT NULL DEFAULT '[]',
    outputs JSONB NOT NULL DEFAULT '[]',
    tags JSONB NOT NULL DEFAULT '[]',
    is_builtin BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by UUID REFERENCES users(id)
);

CREATE INDEX idx_workflow_templates_tenant ON release.workflow_templates(tenant_id);
CREATE INDEX idx_workflow_templates_name ON release.workflow_templates(name);

-- Workflow Runs
CREATE TABLE release.workflow_runs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
    template_id UUID NOT NULL REFERENCES release.workflow_templates(id),
    template_version INTEGER NOT NULL,
    status VARCHAR(50) NOT NULL DEFAULT 'created',
    context JSONB NOT NULL,
    inputs JSONB NOT NULL DEFAULT '{}',
    outputs JSONB NOT NULL DEFAULT '{}',
    error_message TEXT,
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by UUID REFERENCES users(id)
);

CREATE INDEX idx_workflow_runs_tenant ON release.workflow_runs(tenant_id);
CREATE INDEX idx_workflow_runs_template ON release.workflow_runs(template_id);
CREATE INDEX idx_workflow_runs_status ON release.workflow_runs(status);

-- Step Runs
CREATE TABLE release.step_runs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    workflow_run_id UUID NOT NULL REFERENCES release.workflow_runs(id) ON DELETE CASCADE,
    node_id VARCHAR(255) NOT NULL,
    status VARCHAR(50) NOT NULL DEFAULT 'pending',
    inputs JSONB NOT NULL DEFAULT '{}',
    outputs JSONB NOT NULL DEFAULT '{}',
    error_message TEXT,
    logs TEXT,
    attempt_number INTEGER NOT NULL DEFAULT 1,
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    UNIQUE (workflow_run_id, node_id)
);

CREATE INDEX idx_step_runs_workflow ON release.step_runs(workflow_run_id);
CREATE INDEX idx_step_runs_status ON release.step_runs(status);

-- Step Registry
CREATE TABLE release.step_types (
    type VARCHAR(255) PRIMARY KEY,
    display_name VARCHAR(255) NOT NULL,
    description TEXT,
    category VARCHAR(100) NOT NULL,
    icon VARCHAR(255),
    config_schema JSONB NOT NULL,
    input_schema JSONB NOT NULL,
    output_schema JSONB NOT NULL,
    executor VARCHAR(255) NOT NULL DEFAULT 'builtin',
    default_timeout INTEGER NOT NULL DEFAULT 300,
    safe_to_retry BOOLEAN NOT NULL DEFAULT FALSE,
    retryable_errors JSONB NOT NULL DEFAULT '[]',
    documentation TEXT,
    examples JSONB NOT NULL DEFAULT '[]',
    plugin_id UUID REFERENCES release.plugins(id),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_step_types_category ON release.step_types(category);
CREATE INDEX idx_step_types_plugin ON release.step_types(plugin_id);

Workflow Template Example: Standard Deployment

{
  "id": "template-standard-deploy",
  "name": "standard-deploy",
  "displayName": "Standard Deployment",
  "version": 1,
  "inputs": [
    { "name": "releaseId", "type": "uuid", "required": true },
    { "name": "environmentId", "type": "uuid", "required": true },
    { "name": "promotionId", "type": "uuid", "required": true }
  ],
  "nodes": [
    {
      "id": "approval",
      "type": "approval",
      "name": "Approval Gate",
      "config": {},
      "inputs": [
        { "name": "promotionId", "source": { "type": "context", "path": "promotionId" } }
      ],
      "position": { "x": 100, "y": 100 }
    },
    {
      "id": "security-gate",
      "type": "security-gate",
      "name": "Security Verification",
      "config": {
        "blockOnCritical": true,
        "blockOnHigh": true
      },
      "inputs": [
        { "name": "releaseId", "source": { "type": "context", "path": "releaseId" } }
      ],
      "position": { "x": 100, "y": 200 }
    },
    {
      "id": "deploy-targets",
      "type": "deploy-compose",
      "name": "Deploy to Targets",
      "config": {
        "strategy": "rolling",
        "parallelism": 2
      },
      "inputs": [
        { "name": "releaseId", "source": { "type": "context", "path": "releaseId" } },
        { "name": "environmentId", "source": { "type": "context", "path": "environmentId" } }
      ],
      "timeout": 600,
      "retryPolicy": {
        "maxRetries": 2,
        "backoffType": "exponential",
        "backoffSeconds": 30
      },
      "onFailure": "rollback",
      "position": { "x": 100, "y": 400 }
    },
    {
      "id": "health-check",
      "type": "health-check",
      "name": "Health Verification",
      "config": {
        "type": "http",
        "path": "/health",
        "expectedStatus": 200,
        "timeout": 30,
        "retries": 5
      },
      "inputs": [
        { "name": "targets", "source": { "type": "output", "nodeId": "deploy-targets", "outputName": "deployedTargets" } }
      ],
      "onFailure": "rollback",
      "position": { "x": 100, "y": 500 }
    },
    {
      "id": "notify-success",
      "type": "notify",
      "name": "Success Notification",
      "config": {
        "channel": "slack",
        "template": "deployment-success"
      },
      "onFailure": "continue",
      "position": { "x": 100, "y": 700 }
    },
    {
      "id": "rollback-handler",
      "type": "rollback",
      "name": "Rollback Handler",
      "config": {
        "strategy": "to-previous"
      },
      "inputs": [
        { "name": "deploymentJobId", "source": { "type": "output", "nodeId": "deploy-targets", "outputName": "jobId" } }
      ],
      "position": { "x": 300, "y": 450 }
    }
  ],
  "edges": [
    { "id": "e1", "from": "approval", "to": "security-gate" },
    { "id": "e2", "from": "security-gate", "to": "deploy-targets" },
    { "id": "e3", "from": "deploy-targets", "to": "health-check" },
    { "id": "e4", "from": "health-check", "to": "notify-success" },
    { "id": "e5", "from": "deploy-targets", "to": "rollback-handler", "condition": "status === 'failed'" },
    { "id": "e6", "from": "health-check", "to": "rollback-handler", "condition": "status === 'failed'" }
  ]
}

API Endpoints

See API Documentation for full specification.

# Workflow Templates
POST   /api/v1/workflow-templates
GET    /api/v1/workflow-templates
GET    /api/v1/workflow-templates/{id}
PUT    /api/v1/workflow-templates/{id}
DELETE /api/v1/workflow-templates/{id}
POST   /api/v1/workflow-templates/{id}/validate

# Step Registry
GET    /api/v1/step-types
GET    /api/v1/step-types/{type}

# Workflow Runs
POST   /api/v1/workflow-runs
GET    /api/v1/workflow-runs
GET    /api/v1/workflow-runs/{id}
POST   /api/v1/workflow-runs/{id}/pause
POST   /api/v1/workflow-runs/{id}/resume
POST   /api/v1/workflow-runs/{id}/cancel
GET    /api/v1/workflow-runs/{id}/steps
GET    /api/v1/workflow-runs/{id}/steps/{nodeId}
GET    /api/v1/workflow-runs/{id}/steps/{nodeId}/logs
GET    /api/v1/workflow-runs/{id}/steps/{nodeId}/artifacts

References