# Workflow Engine contract v1

## Graph JSON

```json
{
  "nodes": [
    {"id":"start","type":"start","x":100,"y":200,"config":{}},
    {"id":"hello","type":"message","x":350,"y":200,"config":{"text":"سلام {{user.first_name}}"}},
    {"id":"end","type":"end","x":600,"y":200,"config":{}}
  ],
  "edges": [
    {"from":"start","to":"hello","label":"next"},
    {"from":"hello","to":"end","label":"next"}
  ]
}
```

## Nodes and semantics

| Node | Configuration | Behavior |
|---|---|---|
| start | none | one per graph; launches on matching command |
| message | text | queues a Bale text message |
| menu | text, buttons: [label,target] | inline button; waits for selected action |
| question | text, key, input, options [{label,score}] | waits for text/number/phone/choice; persists response and score |
| condition | field, operator, value | routes to edges `yes` / `no` |
| set | key,value | stores a value in user fields |
| finish | none | records answers+score into form_submissions |
| end | none | closes workflow session |

- Node `question` with `input=choice` supports option scores; all other inputs default score 0.
- Answer validation rejects invalid number/mobile and asks user to retry.
- Callback routing only honors menu targets belonging to the active menu of the user's current session.
- Every execution uses `workflow_version_id` stored in `workflow_sessions`; an update to draft cannot mutate running sessions.
- Guard at most 100 immediate nodes per event; validation rejects immediate loops not crossing a waiting node.
- The only workflow activation mechanism in this version is a slash command (`/start`, `/help`, etc). Triggers by image/event/schedule are not yet present.

## Source of truth

`workflows.draft_json` and `draft_command` only affect the editor. The published graph lives in immutable `workflow_versions.graph_json`. `command` is only replaced on publish. Old sessions may continue with their original graph until completion; `/start` restarts at currently published version.
