MachineSpec v2 Reference

Complete reference for the MachineSpec v2 JSON format used by the Process Editor.

Table of contents

  1. Overview
  2. Schema Structure
    1. Root Object
    2. Metadata Object
    3. State Object
    4. Transition Object
    5. Timer Object
  3. Complete Example
  4. Validation Rules
    1. Schema Validation
    2. Semantic Validation
    3. Timer Validation
    4. Naming Conventions
  5. Import/Export Behavior
    1. BPMN to MachineSpec
    2. MachineSpec to BPMN
    3. Round-Trip Compatibility
  6. Integration with Applications
    1. Loading MachineSpec
    2. Generating Events
    3. Implementing Guards
    4. Implementing Actions
  7. Best Practices
    1. Design Principles
    2. Performance Considerations
    3. Maintenance

Overview

MachineSpec v2 is a JSON format that describes finite state machines for insurance processes. The Process Editor generates this format from BPMN diagrams and can import it back to recreate the visual representation.

Schema Structure

Root Object

1
2
3
4
5
6
7
{
  "id": "string",
  "version": 1,
  "initial": "string",
  "metadata": { ... },
  "states": { ... }
}
Field Type Required Description
id string Yes Unique process identifier
version number Yes Schema version (always 1)
initial string Yes Name of the initial state
metadata object No Process metadata and documentation
states object Yes State definitions

Metadata Object

1
2
3
4
5
6
7
8
{
  "metadata": {
    "documentation": "string",
    "lanes": {
      "LaneName": ["state1", "state2"]
    }
  }
}
Field Type Required Description
documentation string No Process description
lanes object No Lane assignments for states

State Object

1
2
3
4
5
6
7
8
9
10
{
  "states": {
    "state_name": {
      "id": "string",
      "type": "task" | "end",
      "on": { ... },
      "timers": [ ... ]
    }
  }
}
Field Type Required Description
id string Yes Stable element identifier
type string No State type (task or end)
on object No Event transitions
timers array No Timer definitions

Transition Object

1
2
3
4
5
6
7
8
9
10
{
  "on": {
    "EVENT_NAME": {
      "id": "string",
      "target": "string",
      "guard": "string",
      "actions": ["string"]
    }
  }
}
Field Type Required Description
id string Yes Stable flow identifier
target string Yes Target state name
guard string No Guard function name
actions array No Action function names

Timer Object

1
2
3
4
5
6
7
8
9
10
11
{
  "timers": [
    {
      "id": "string",
      "type": "DURATION" | "DATE",
      "iso": "string",
      "at": "string",
      "event": "string"
    }
  ]
}
Field Type Required Description
id string Yes Unique timer identifier
type string Yes Timer type (DURATION or DATE)
iso string Conditional ISO 8601 duration (for DURATION type)
at string Conditional ISO 8601 datetime (for DATE type)
event string Yes Event to trigger when timer expires

Complete Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
{
  "id": "insurance_quote",
  "version": 1,
  "initial": "created",
  "metadata": {
    "documentation": "Insurance quote lifecycle process",
    "lanes": {
      "Customer": ["created", "submitted"],
      "Reviewer": ["under_review", "approved", "rejected"],
      "Finance": ["payment_pending", "paid_full"]
    }
  },
  "states": {
    "created": {
      "id": "task_created",
      "type": "task",
      "on": {
        "SUBMIT": {
          "id": "flow_submit",
          "target": "submitted",
          "actions": ["validateSubmission"]
        }
      }
    },
    "submitted": {
      "id": "task_submitted",
      "type": "task",
      "on": {
        "START_REVIEW": {
          "id": "flow_start_review",
          "target": "under_review",
          "guard": "hasRequiredDocuments"
        }
      }
    },
    "under_review": {
      "id": "task_under_review",
      "type": "task",
      "on": {
        "APPROVE": {
          "id": "flow_approve",
          "target": "approved",
          "guard": "isReviewer",
          "actions": ["recordApproval", "sendNotification"]
        },
        "REJECT": {
          "id": "flow_reject",
          "target": "rejected",
          "guard": "isReviewer",
          "actions": ["recordRejection", "sendRejectionNotice"]
        },
        "REVIEW_TIMEOUT": {
          "id": "flow_timeout",
          "target": "rejected",
          "actions": ["recordTimeout"]
        }
      },
      "timers": [
        {
          "id": "timer_review_deadline",
          "type": "DURATION",
          "iso": "P7D",
          "event": "REVIEW_TIMEOUT"
        }
      ]
    },
    "approved": {
      "id": "task_approved",
      "type": "task",
      "on": {
        "REQUEST_PAYMENT": {
          "id": "flow_request_payment",
          "target": "payment_pending",
          "actions": ["generateInvoice", "sendPaymentRequest"]
        }
      }
    },
    "rejected": {
      "id": "end_rejected",
      "type": "end"
    },
    "payment_pending": {
      "id": "task_payment_pending",
      "type": "task",
      "on": {
        "PAY_FULL": {
          "id": "flow_pay_full",
          "target": "paid_full",
          "actions": ["recordPayment", "activatePolicy"]
        },
        "PAYMENT_EXPIRED": {
          "id": "flow_payment_expired",
          "target": "payment_expired"
        }
      },
      "timers": [
        {
          "id": "timer_payment_deadline",
          "type": "DURATION",
          "iso": "P30D",
          "event": "PAYMENT_EXPIRED"
        }
      ]
    },
    "paid_full": {
      "id": "end_paid_full",
      "type": "end"
    },
    "payment_expired": {
      "id": "end_payment_expired",
      "type": "end"
    }
  }
}

Validation Rules

Schema Validation

  • Required Fields: All required fields must be present
  • Type Validation: Fields must match expected types
  • Version: Must be exactly 1

Semantic Validation

  • Initial State: Must reference an existing state
  • Target States: All transition targets must exist
  • Unique IDs: All element, flow, and timer IDs must be unique
  • Deterministic: No duplicate events from the same state
  • Reachability: All states must be reachable from initial state

Timer Validation

  • Duration Format: ISO 8601 duration (e.g., P14D, PT2H30M)
  • Date Format: ISO 8601 datetime with timezone (e.g., 2024-12-31T23:59:59Z)
  • Event Names: Must follow UPPER_SNAKE_CASE convention

Naming Conventions

  • State Names: snake_case (e.g., waiting_approval)
  • Event Names: UPPER_SNAKE_CASE (e.g., APPROVE)
  • IDs: Descriptive with prefixes (e.g., task_approval, flow_submit)

Import/Export Behavior

BPMN to MachineSpec

  1. Initial State: Task with no incoming flows becomes initial
  2. States: Tasks and End Events become state objects
  3. Transitions: Sequence Flows become on events
  4. Timers: Boundary Timers become timer objects
  5. Lanes: Lane assignments become metadata.lanes

MachineSpec to BPMN

  1. Start Event: Automatically created, connects to initial state
  2. Tasks: Created for each non-end state
  3. End Events: Created for each end-type state
  4. Flows: Created for each transition
  5. Timers: Attached as boundary events to tasks
  6. Lanes: States grouped by lane assignments

Round-Trip Compatibility

The Process Editor ensures round-trip compatibility:

  • Export BPMN → Generate MachineSpec → Import → Recreate identical BPMN
  • All metadata, IDs, and structure preserved
  • Visual layout may change but semantic meaning remains

Integration with Applications

Loading MachineSpec

1
2
3
4
5
6
7
8
9
10
11
12
import { MachineSpec } from './types/machine-spec';

// Load from JSON file
const spec: MachineSpec = JSON.parse(jsonString);

// Validate schema
if (spec.version !== 1) {
  throw new Error('Unsupported MachineSpec version');
}

// Use in your application
const stateMachine = createMachine(spec);

Generating Events

1
2
3
4
5
// Event names from MachineSpec
const events = Object.keys(spec.states[currentState].on || {});

// Trigger transition
stateMachine.send('APPROVE');

Implementing Guards

1
2
3
4
5
6
7
8
const guards = {
  isReviewer: (context, event) => {
    return context.user.role === 'reviewer';
  },
  hasRequiredDocuments: (context, event) => {
    return context.documents.length > 0;
  }
};

Implementing Actions

1
2
3
4
5
6
7
8
9
10
const actions = {
  recordApproval: (context, event) => {
    console.log('Recording approval:', event);
    // Implementation here
  },
  sendNotification: (context, event) => {
    console.log('Sending notification');
    // Implementation here
  }
};

Best Practices

Design Principles

  • Keep States Focused: Each state should represent a clear business condition
  • Use Meaningful Names: State and event names should be self-documenting
  • Minimize Complexity: Avoid deeply nested or overly complex flows
  • Document Intent: Use descriptions and lane assignments for clarity

Performance Considerations

  • State Count: Keep under 50 states for optimal performance
  • Timer Count: Limit timers per state (recommended: max 3)
  • Action Count: Keep action lists short and focused

Maintenance

  • Version Control: Track changes to MachineSpec files
  • Testing: Validate round-trip compatibility regularly
  • Documentation: Maintain external documentation for complex processes
  • Backup: Keep both BPMN and JSON versions for redundancy

Copyright © 2025 Etherisc. Distributed under the MIT License.