# Workflows

Workflows are multi-stage processes with team assignment, validation gates,
SLA enforcement, and optional Starlark automation. They are managed in
**Team Admin > Workflows**.

## Core concepts

| Concept | Description |
|---------|-------------|
| **Definition** | A named template: stages, entry mode, staleness timeout. Created per-team or adopted from global definitions. |
| **Stage** | One step in the workflow. Has a mode, audience, optional team assignment, and optional SLA. |
| **Instance** | A running copy of a definition. Pins a published version snapshot and tracks accumulated stage data. |
| **Assignment** | A queue entry linking an instance stage to a team member. Claim → work → complete. |
| **Signoff** | An approval or rejection recorded against an instance stage (multi-party validation). |

## Entry modes

| Mode | Description |
|------|-------------|
| `team_only` | Only authenticated team members can start instances. |
| `public_link` | Anyone with the public URL can start an instance. The first stage must have `audience: public`. An `entry_token` is issued for the anonymous submitter to resume later. |

Public entry URL format:
```
{origin}/api/v1/public/workflows/{workflow_id}/start
```

## Stage modes

Each stage has a **mode** that determines how it progresses:

| Mode | Description |
|------|-------------|
| `form` | User submits structured data. Stage data is accumulated into the instance. |
| `review` | Multi-party sign-off gate. Requires configured approvals before advancing. |
| `delegated` | Assigned to a team member queue. The assignee claims, works, and completes. |
| `automated` | Starlark hook executes without user interaction. Can chain up to 10 consecutive automated stages. |

## Stage types

| Type | Description |
|------|-------------|
| `simple` | Linear — always advances to the next ordinal. |
| `dynamic` | Conditional — evaluates branch rules against stage data to pick the next stage. |
| `automated` | Combined with mode `automated` for fully scripted stages. |

## Audiences

| Audience | Description |
|----------|-------------|
| `team` | Only authenticated team members can interact. |
| `public` | Anonymous users can interact (used with `public_link` entry). |
| `system` | System-generated stages, no direct user interaction. |

## Team assignment

When a stage has `assignment_team_id` set, the engine creates an
**assignment** record:

1. Assignment enters the queue with status `unassigned`.
2. A team member **claims** the assignment (status → `claimed`).
3. The assignee works the stage and **completes** it (status → `completed`).
4. The engine auto-advances to the next stage.

A **required role** can restrict who may claim:
- Set `stage_config.required_role` to a team role name (e.g. `"reviewer"`).
- Only members with that role can claim the assignment.

Team roles are configured in **Team Admin > Settings > Roles**.

## Signoff gates (multi-party validation)

Review-mode stages can require multiple approvals before advancing.
Configure via `stage_config.validation`:

```json
{
  "validation": {
    "required_approvals": 2,
    "required_role": "approver",
    "reject_action": "cancel"
  }
}
```

| Field | Description |
|-------|-------------|
| `required_approvals` | Minimum approve decisions needed to advance. |
| `required_role` | Only members with this team role can sign off. Empty = any member. |
| `reject_action` | What happens on rejection: `"cancel"` (default) cancels the instance, or a stage name to reroute. |

Each signoff records: user, decision (`approve` or `reject`), optional comment, timestamp.

## SLA enforcement

Two timeout mechanisms run in a background scanner (every 5 minutes):

### Per-stage SLA

Set `sla_seconds` on a stage. When an instance has been in that stage
longer than the threshold:

- `sla_breached` flag is set in instance metadata.
- A `workflow.sla_breach` WebSocket event is emitted.
- The instance is **not** auto-cancelled — breaches are informational.

### Per-workflow staleness

Set `staleness_timeout_hours` on the workflow definition. When an instance
has not been updated for longer than the threshold:

- Instance status is set to `stale`.
- All open assignments are cancelled.
- A `workflow.stale` WebSocket event is emitted.

## Branch rules

Dynamic stages evaluate conditions against accumulated `stage_data`
to determine the next stage. Rules are a JSON array on the stage:

```json
[
  { "field": "priority", "op": "eq", "value": "high", "target_stage": "escalation" },
  { "field": "amount",   "op": "gt", "value": 10000,  "target_stage": "manager-review" }
]
```

First matching rule wins. If no rules match, the next ordinal stage is used.

### Operators

| Op | Description |
|----|-------------|
| `eq` | Equal (string-normalized) |
| `neq` | Not equal |
| `gt`, `lt`, `gte`, `lte` | Numeric comparisons |
| `exists` | Field is present in stage data |
| `not_exists` | Field is absent |
| `in` | Value is in a list |
| `contains` | String contains substring |

`target_stage` can be a stage name (case-insensitive) or a numeric ordinal.

## Publishing

Workflows have a draft/publish lifecycle:

1. Edit stages and configuration in the workflow editor (draft state).
2. **Publish** creates a versioned snapshot of all stages.
3. New instances pin the latest published version.
4. Editing stages after publishing does not affect running instances.

Version numbers auto-increment. The snapshot preserves the complete
stage definition array at publish time.

## Starlark hooks

Automated stages execute a Starlark script via the `starlark_hook` field:

```
package_id:entry_point
```

For example: `my-automation:on_review` calls the `on_review` function
in the `my-automation` package. If no entry point is specified,
`on_run` is used.

The hook receives a context dict:

```python
{
  "instance_id": "...",
  "current_stage": "...",
  "workflow_id": "...",
  "started_by": "...",
  "stage_data": { ... }
}
```

The hook returns a dict controlling what happens next:

| Key | Effect |
|-----|--------|
| `advance: True` | Auto-advance to the next stage |
| `data: { ... }` | Merge into stage data for the next stage |
| `error: "msg"` | Set instance status to `error` and halt |

Up to 10 consecutive automated stages can chain before the engine
stops with an error (cycle guard).

See the [Starlark Reference](STARLARK-REFERENCE) for available
sandbox modules.
