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 definitionswft_instances- Workflow execution instanceswft_logs- Execution logs and audit trailwft_node_states- Node-level state trackingwft_nodes- Node configurationswft_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 Type | Purpose | Config |
|---|---|---|
startEvent | Workflow entry point | Trigger configuration |
endEvent | Workflow completion point | Output mapping |
action | Perform operation | actionType (database, api, script) |
condition | Decision point | conditions[], nextNode routing |
loop | Iterate over collection | collection, elementVariable |
wait | Pause execution | waitTime, waitCondition |
parallel | Fork into multiple paths | branches[] |
merge | Merge parallel paths | mergeType (and, or, first) |
Action Type Reference
| Action | description |
|---|---|
database | CRUD operations on entities |
api | Call external API endpoints |
script | Execute custom JavaScript/Node code |
notification | Send email, SMS, teams messages |
assignment | Assign task to user/role |
webhook | Call webhooks |
transformation | Transform 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
Related Documentation
- WORKFLOW.md - Workflow designer guide
- DATABASE.md - Core schema reference
- OBJECT_MANAGEMENT.md - Entity configuration