Skip to main content

Workflow Configuration Documentation

Module: Workflow Definition & Execution
Version: 1.0
Last Updated: March 12, 2026


📌 Overview

Workflow Configuration manages business process automation in Axiom Genesis. It defines:

  • Workflow logic: nodes (start, action, condition, end), edges (connections), properties
  • Workflow versioning: immutable versions for rollback capability
  • Workflow execution: state tracking, status monitoring, pause/resume/cancel
  • Workflow logs: complete execution audit trail

Core Tables:

  • app_workflow_configs - Workflow definitions
  • wft_instances - Workflow execution instances
  • wft_logs - Execution logs and audit trail
  • wft_node_states - Node-level state tracking
  • wft_nodes - Node configurations
  • wft_steps - Step execution queue

app_workflow_configs - Workflow Definitions

Purpose

Stores complete workflow definitions including nodes, edges, triggers, and execution properties. Supports versioning for workflow updates.

Schema

SELECT
wfc_id (INT), -- Primary Key
wfc_tenant_id (INT), -- Tenant isolation
wfc_code (VARCHAR(256)), -- Workflow identifier (e.g., 'WF_APPROVAL')
wfc_description (VARCHAR), -- Display name
wfc_version_no (INT), -- Version number (immutable)
wfc_config (JSON), -- Complete workflow definition
wfc_is_published (BIT), -- 1=Published, 0=Draft
wfc_status (INT), -- 1=Active, 0=Inactive
wfc_is_deleted (BIT), -- Soft delete flag
wfc_audit_data (JSON), -- Audit trail
wfc_add_dtls (JSON) -- Additional metadata
FROM app_workflow_configs
WHERE wfc_status = 1 AND wfc_is_deleted = 0

wfc_config Structure (JSON) - Complete Workflow Definition

{
"workflowId": "WF_APPROVAL_PROCESS",
"workflowName": "Customer Approval Workflow",
"workflowDescription": "Approve new customer registrations",

"triggers": [
{
"triggerId": "trg_customer_created",
"triggerName": "Customer Created",
"triggerType": "event", // 'event', 'schedule', 'manual', 'webhook'
"config": {
"entityCode": "CUSTOMERS",
"eventType": "CREATE", // CREATE, UPDATE, DELETE
"condition": "status = 'PENDING'"
}
},
{
"triggerId": "trg_schedule",
"triggerName": "Daily Batch",
"triggerType": "schedule",
"config": {
"cronExpression": "0 9 * * *", // 9 AM daily
"timezone": "UTC"
}
}
],

"nodes": [
{
"nodeId": "node_start",
"nodeName": "Start",
"nodeType": "startEvent",
"nodeOrder": 1,
"position": { "x": 100, "y": 100 },
"config": {
"description": "Process begins"
}
},
{
"nodeId": "node_fetch_customer",
"nodeName": "Fetch Customer Data",
"nodeType": "action",
"nodeOrder": 2,
"position": { "x": 100, "y": 250 },
"config": {
"actionType": "database", // 'database', 'api', 'script'
"entityCode": "CUSTOMERS",
"operation": "read",
"queryFilter": "status = 'PENDING'",
"inputMapping": {
"customerId": "@trigger.recordId" // Map from trigger data
},
"outputMapping": {
"customerData": "@result" // Store result for next node
}
}
},
{
"nodeId": "node_assign_manager",
"nodeName": "Assign to Manager",
"nodeType": "action",
"nodeOrder": 3,
"position": { "x": 100, "y": 400 },
"config": {
"actionType": "assignment",
"assignRole": "MANAGER",
"assignField": "assigned_to",
"notifyAssignee": true,
"taskDescription": "Please review and approve customer: ${customerData.name}"
}
},
{
"nodeId": "node_approval_check",
"nodeName": "Approval Decision",
"nodeType": "condition",
"nodeOrder": 4,
"position": { "x": 350, "y": 400 },
"config": {
"conditions": [
{
"conditionId": "cond_approved",
"conditionName": "Approved",
"expression": "approval_status == 'APPROVED'",
"nextNode": "node_update_active"
},
{
"conditionId": "cond_rejected",
"conditionName": "Rejected",
"expression": "approval_status == 'REJECTED'",
"nextNode": "node_notify_rejection"
},
{
"conditionId": "cond_default",
"conditionName": "Default",
"expression": "true",
"nextNode": "node_notify_pending"
}
]
}
},
{
"nodeId": "node_update_active",
"nodeName": "Mark as Active",
"nodeType": "action",
"nodeOrder": 5,
"position": { "x": 600, "y": 250 },
"config": {
"actionType": "database",
"entityCode": "CUSTOMERS",
"operation": "update",
"updateData": {
"status": "ACTIVE",
"approvedAt": "@now",
"approvedBy": "@user.id"
}
}
},
{
"nodeId": "node_notify_approval",
"nodeName": "Send Approval Email",
"nodeType": "action",
"nodeOrder": 6,
"position": { "x": 600, "y": 400 },
"config": {
"actionType": "notification",
"notificationType": "email",
"recipient": "${customerData.email}",
"subject": "Customer Approval Confirmation",
"template": "customer_approved",
"templateData": {
"customerName": "${customerData.name}",
"approvalDate": "@now"
}
}
},
{
"nodeId": "node_notify_rejection",
"nodeName": "Send Rejection Email",
"nodeType": "action",
"nodeOrder": 7,
"position": { "x": 600, "y": 550 },
"config": {
"actionType": "notification",
"notificationType": "email",
"recipient": "${customerData.email}",
"subject": "Customer Registration - Additional Information Needed",
"template": "customer_rejected"
}
},
{
"nodeId": "node_end",
"nodeName": "End",
"nodeType": "endEvent",
"nodeOrder": 8,
"position": { "x": 900, "y": 400 },
"config": {
"description": "Workflow completed"
}
}
],

"edges": [
{
"edgeId": "edge_1",
"sourceNodeId": "node_start",
"targetNodeId": "node_fetch_customer",
"label": "Start"
},
{
"edgeId": "edge_2",
"sourceNodeId": "node_fetch_customer",
"targetNodeId": "node_assign_manager",
"label": "On Success"
},
{
"edgeId": "edge_3",
"sourceNodeId": "node_assign_manager",
"targetNodeId": "node_approval_check",
"label": "Workflow"
},
{
"edgeId": "edge_4",
"sourceNodeId": "node_approval_check",
"targetNodeId": "node_update_active",
"label": "Approved",
"condition": "approval_status == 'APPROVED'"
},
{
"edgeId": "edge_5",
"sourceNodeId": "node_update_active",
"targetNodeId": "node_notify_approval",
"label": "On Success"
},
{
"edgeId": "edge_6",
"sourceNodeId": "node_notify_approval",
"targetNodeId": "node_end",
"label": "Complete"
},
{
"edgeId": "edge_7",
"sourceNodeId": "node_approval_check",
"targetNodeId": "node_notify_rejection",
"label": "Rejected"
},
{
"edgeId": "edge_8",
"sourceNodeId": "node_notify_rejection",
"targetNodeId": "node_end",
"label": "Complete"
}
]
}

Node Type Reference

Node TypePurposeConfig
startEventWorkflow entry pointTrigger configuration
endEventWorkflow completion pointOutput mapping
actionPerform operationactionType (database, api, script)
conditionDecision pointconditions[], nextNode routing
loopIterate over collectioncollection, elementVariable
waitPause executionwaitTime, waitCondition
parallelFork into multiple pathsbranches[]
mergeMerge parallel pathsmergeType (and, or, first)

Action Type Reference

Actiondescription
databaseCRUD operations on entities
apiCall external API endpoints
scriptExecute custom JavaScript/Node code
notificationSend email, SMS, teams messages
assignmentAssign task to user/role
webhookCall webhooks
transformationTransform data

Workflow Versioning

All workflow versions are immutable; creates allow rollback:

-- Version 1 (original)
SELECT * FROM app_workflow_configs
WHERE wfc_code = 'WF_APPROVAL' AND wfc_version_no = 1;

-- Version 2 (updated)
SELECT * FROM app_workflow_configs
WHERE wfc_code = 'WF_APPROVAL' AND wfc_version_no = 2;

-- Both versions exist; can execute either
-- New instances = latest version
-- Existing instances = remain on their version

Workflow Execution Tables

wft_instances - Workflow Execution Instances

Tracks each workflow execution:

SELECT
wfi_id (INT), -- Primary Key
wfi_tenant_id (INT), -- Tenant isolation
wfi_instance_code (VARCHAR), -- Unique instance ID
wfi_workflow_code (VARCHAR), -- Links to app_workflow_configs
wfi_status (VARCHAR), -- pending, executing, completed, failed, paused
wfi_progress_pct (DECIMAL), -- 0-100 completion percentage
wfi_current_node_id (VARCHAR), -- Currently executing node
wfi_input_data (JSON), -- Input parameters
wfi_output_data (JSON), -- Final output
wfi_triggered_at (DATETIME), -- When triggered
wfi_started_at (DATETIME), -- When execution started
wfi_completed_at (DATETIME), -- When completed
wfi_duration_ms (INT), -- Total execution time
wfi_parent_instance_id (INT), -- Parent workflow (sub-workflows)
wfi_root_instance_id (INT), -- Root workflow
wfi_audit_data (JSON) -- Audit trail
FROM wft_instances
WHERE wfi_is_deleted = 0
ORDER BY wfi_started_at DESC

wft_logs - Execution Logs

Complete audit trail of All workflow execution events:

SELECT
log_id (INT), -- Primary Key
log_tenant_id (INT), -- Tenant isolation
log_instance_id (INT), -- Links to wft_instances
log_node_id (VARCHAR), -- Node executed
log_timestamp (DATETIME), -- When logged
log_level (VARCHAR), -- INFO, WARN, ERROR
log_category (VARCHAR), -- START, NODE_EXEC, CONDITION, ERROR
log_message (VARCHAR), -- Log message
log_details (JSON), -- Additional JSON data
log_audit_data (JSON) -- Audit trail
FROM wft_logs
WHERE log_is_deleted = 0
ORDER BY log_timestamp DESC

wft_node_states - Node State Tracking

Tracks execution state of each node:

SELECT
wfns_id (INT), -- Primary Key
wfns_instance_id (INT), -- Links to wft_instances
wfns_node_id (NVARCHAR), -- Node ID
wfns_status (VARCHAR), -- pending, executing, completed, failed, skipped
wfns_enqueued_count (INT), -- Times queued
wfns_execution_count (INT), -- Times executed
wfns_created_at (DATETIME2), -- Created timestamp
wfns_updated_at (DATETIME2) -- Updated timestamp
FROM wft_node_states
WHERE wfns_updated_at > DATEADD(DAY, -30, GETDATE())

Workflow Execution Flow (With Code)

Start Workflow

// From: services/workflow.service.js
WorkflowService.startWorkflow = async (workflowId, inputData, user) => {
const db = getConnection();

// 1. Fetch workflow config
const configRes = await db
.request()
.input("wfc_code", workflowId)
.input("wfc_tenant_id", user.tenant_id).query(`
SELECT wfc_config, wfc_version_no
FROM app_workflow_configs
WHERE wfc_code = @wfc_code AND wfc_tenant_id = @wfc_tenant_id
`);

if (!configRes.recordset[0]) {
throw new NotFoundError("Workflow not found");
}

const config = JSON.parse(configRes.recordset[0].wfc_config);

// 2. Create workflow instance
const instanceRes = await db
.request()
.input("wfi_workflow_code", workflowId)
.input("wfi_status", "pending")
.input("wfi_input_data", JSON.stringify(inputData || {}))
.input("wfi_tenant_id", user.tenant_id)
.input("wfi_instance_code", generateInstanceCode())
.input("wfi_triggered_at", new Date())
.input("wfi_trigger_type", "manual")
.input("wfi_trigger_source", "api").query(`
INSERT INTO wft_instances (
wfi_workflow_code, wfi_status, wfi_input_data, wfi_tenant_id,
wfi_instance_code, wfi_triggered_at, wfi_trigger_type, wfi_trigger_source,
wfi_audit_data, wfi_is_deleted
)
OUTPUT INSERTED.*
VALUES (
@wfi_workflow_code, @wfi_status, @wfi_input_data, @wfi_tenant_id,
@wfi_instance_code, @wfi_triggered_at, @wfi_trigger_type, @wfi_trigger_source,
JSON_OBJECT('created_by', @user_id, 'created_at', GETUTCDATE()), 0
)
`);

const instance = instanceRes.recordset[0];

// 3. Queue for background execution
const startNode = config.nodes.find((n) => n.nodeType === "startEvent");
await workflowQueue.add("execute-node", {
instanceId: instance.wfi_id,
nodeId: startNode.nodeId,
inputData,
});

return instance;
};

Execute Node (Background Worker)

// From: queue/workflow.queue.js
workflowQueue.process("execute-node", async (job) => {
const { instanceId, nodeId, inputData } = job.data;
const db = getConnection();

// 1. Load instance & config
const instanceRes = await db
.request()
.input("wfi_id", instanceId)
.query(`SELECT * FROM wft_instances WHERE wfi_id = @wfi_id`);

const configRes = await db
.request()
.input("wfc_code", instanceRes.recordset[0].wfi_workflow_code)
.query(`SELECT wfc_config FROM app_workflow_configs...`);

const config = JSON.parse(configRes.recordset[0].wfc_config);

// 2. Find node definition
const nodeConfig = config.nodes.find((n) => n.nodeId === nodeId);

// 3. Update instance status
await db
.request()
.input("wfi_id", instanceId)
.input("wfi_status", "executing")
.input("wfi_current_node_id", nodeId).query(`
UPDATE wft_instances
SET wfi_status = 'executing', wfi_current_node_id = @wfi_current_node_id
WHERE wfi_id = @wfi_id
`);

// 4. Execute node based on type
let nodeOutput;
try {
switch (nodeConfig.nodeType) {
case "action":
nodeOutput = await executeActionNode(nodeConfig, inputData, instanceId);
break;
case "condition":
nodeOutput = await executeConditionNode(
nodeConfig,
inputData,
instanceId,
);
break;
// Handle other node types
}
} catch (error) {
// Log error
await logWorkflowError(instanceId, nodeId, error);
throw error;
}

// 5. Log node execution
await db
.request()
.input("log_instance_id", instanceId)
.input("log_node_id", nodeId)
.input("log_message", `Node ${nodeConfig.nodeName} completed`)
.input("log_level", "INFO").query(`
INSERT INTO wft_logs (log_instance_id, log_node_id, log_message, log_level)
VALUES (@log_instance_id, @log_node_id, @log_message, @log_level)
`);

// 6. Find next node(s)
const nextNodeIds = findNextNodes(nodeId, config.edges);

// 7. Queue next nodes or complete workflow
if (nextNodeIds.length === 0) {
// Workflow complete
await db
.request()
.input("wfi_id", instanceId)
.input("wfi_status", "completed")
.input("wfi_output_data", JSON.stringify(nodeOutput)).query(`
UPDATE wft_instances
SET wfi_status = 'completed', wfi_output_data = @wfi_output_data
WHERE wfi_id = @wfi_id
`);
} else {
// Queue next nodes
for (const nextNodeId of nextNodeIds) {
await workflowQueue.add("execute-node", {
instanceId,
nodeId: nextNodeId,
inputData: { ...inputData, ...nodeOutput },
});
}
}
});

Workflow Control Operations

// Pause workflow
WorkflowService.pauseWorkflow = async (instanceId) => {
return await db
.request()
.input("wfi_id", instanceId)
.input("wfi_status", "paused")
.input("wfi_paused_at", new Date()).query(`
UPDATE wft_instances
SET wfi_status = 'paused', wfi_paused_at = @wfi_paused_at
WHERE wfi_id = @wfi_id
`);
};

// Resume workflow
WorkflowService.resumeWorkflow = async (instanceId) => {
const instance = await getWorkflowInstance(instanceId);

// Queue current node for execution
await workflowQueue.add("execute-node", {
instanceId,
nodeId: instance.wfi_current_node_id,
inputData: instance.wfi_context_data,
});

return await db
.request()
.input("wfi_id", instanceId)
.input("wfi_status", "executing").query(`
UPDATE wft_instances
SET wfi_status = 'executing'
WHERE wfi_id = @wfi_id
`);
};

// Cancel workflow
WorkflowService.cancelWorkflow = async (instanceId) => {
return await db
.request()
.input("wfi_id", instanceId)
.input("wfi_status", "cancelled").query(`
UPDATE wft_instances
SET wfi_status = 'cancelled'
WHERE wfi_id = @wfi_id
`);
};

Querying Workflows

Get Active Workflows

SELECT
wfc_code,
wfc_description,
wfc_version_no,
wfc_is_published
FROM app_workflow_configs
WHERE wfc_status = 1 AND wfc_is_deleted = 0 AND wfc_is_published = 1
ORDER BY wfc_code;

Get Workflow Execution History

SELECT
wfi_instance_code,
wfi_status,
wfi_progress_pct,
wfi_triggered_at,
wfi_completed_at,
DATEDIFF(MILLISECOND, wfi_triggered_at, wfi_completed_at) as duration_ms
FROM wft_instances
WHERE wfi_workflow_code = 'WF_APPROVAL'
AND wfi_tenant_id = 1
ORDER BY wfi_triggered_at DESC;

Get Workflow Execution Logs

SELECT
log_timestamp,
log_level,
log_node_id,
log_message,
log_details
FROM wft_logs
WHERE log_instance_id = @instance_id
ORDER BY log_timestamp ASC;

API Examples

Start Workflow

POST /api/v1/workflows/WF_APPROVAL/start
Headers: {
'x-tenant-id': 'encrypted_value',
'Authorization': 'Bearer token'
}
Body: {
"customerId": 123,
"orderAmount": 50000,
"reason": "Wholesale bulk order"
}

Response:
{
"success": true,
"data": {
"wfi_id": 42,
"wfi_instance_code": "instance_1710326400000_xyz",
"wfi_status": "pending",
"wfi_workflow_code": "WF_APPROVAL"
}
}

Get Workflow Status

GET /api/v1/workflows/WF_APPROVAL/instances/42
Headers: {
'x-tenant-id': 'encrypted_value',
'Authorization': 'Bearer token'
}

Response:
{
"success": true,
"data": {
"wfi_id": 42,
"wfi_instance_code": "instance_...",
"wfi_status": "executing",
"wfi_progress_pct": 50,
"wfi_current_node_id": "node_approval_check",
"wfi_started_at": "2026-03-12T10:30:00Z"
}
}

Get Workflow Logs

GET /api/v1/workflows/WF_APPROVAL/instances/42/logs
Headers: {
'x-tenant-id': 'encrypted_value',
'Authorization': 'Bearer token'
}

Response:
{
"success": true,
"data": [
{
"log_timestamp": "2026-03-12T10:30:00Z",
"log_level": "INFO",
"log_node_id": "node_start",
"log_message": "Workflow started"
},
{
"log_timestamp": "2026-03-12T10:30:05Z",
"log_level": "INFO",
"log_node_id": "node_fetch_customer",
"log_message": "Fetched customer data"
}
]
}

Do's and Don'ts

✅ DO

  • Version workflows carefully - New versions immutable, existing executions unaffected
  • Log every node execution - Essential for debugging and auditing
  • Use conditions for routing - Conditional nodes determine workflow paths
  • Handle errors gracefully - Implement error handling in action nodes
  • Test workflows before publishing - Use draft mode for testing
  • Monitor execution progress - Use wfi_progress_pct to track completion
  • Chain data through output mappings - Pass data between nodes via mappings
  • Set meaningful node names - Help users understand workflow flow

❌ DON'T

  • Execute long operations synchronously - Use background queue for scalability
  • Skip error handling - Catch exceptions and log appropriately
  • Create infinite loops - Ensure loop termination condition
  • Assume input data exists - Always validate input before use
  • Modify wfc_config directly - Use workflow editing APIs
  • Execute same workflow concurrently without isolation - Instance isolation critical
  • Forget to update wfi_progress_pct - Users need visibility into execution
  • Leave nodes without logging - Every step should be logged