# Scheduling

Workflows can run on a cron-like schedule via the background executor. Schedules are durable (stored in SQLite) and survive restarts.

## Prerequisites

Scheduling requires the background executor to be enabled:

```yaml
workflow:
  executor_enabled: true
  scheduler_enabled: true        # default: true
  min_schedule_interval: 60      # minimum seconds between runs (denial-of-wallet guard)
```

Without `executor_enabled: true`, schedules can be registered and inspected but no runs will fire.

## Defining Triggers in YAML

Add an optional `triggers:` section to your workflow definition:

```yaml
kind: workflow
id: daily-report
version: 1.0
triggers:
  - type: schedule
    cron: "0 8 * * *"           # Every day at 08:00 UTC
    target_ref: daily-check
    missed_policy: skip          # or "queue"
steps:
  - id: generate
    type: runner
    runner: shell
    command: echo "Generating report..."
```

### Trigger Fields

| Field | Required | Default | Description |
|---|---|---|---|
| `type` | Yes | — | Must be `schedule` |
| `cron` | Yes | — | 5-field cron expression |
| `target_ref` | No | `default` | Target reference for the enqueued run |
| `target_kind` | No | `generic` | Target kind |
| `inputs` | No | — | Input values passed to the enqueued run |
| `missed_policy` | No | `skip` | What happens when the schedule fires: `skip` advances immediately, `queue` waits for the prior run to finish |

### Cron Syntax

Standard 5-field format: `minute hour day month dow`

| Token | Example | Meaning |
|---|---|---|
| `*` | `* * * * *` | Every minute |
| Value | `30 * * * *` | At minute 30 |
| Range | `1-5 * * * *` | Minutes 1 through 5 |
| Step | `*/15 * * * *` | Every 15 minutes |
| List | `0,30 * * * *` | At minutes 0 and 30 |
| Alias | `@hourly` | `0 * * * *` |

Supported aliases: `@hourly`, `@daily`, `@weekly`, `@monthly`, `@yearly`.

### Missed Policy

- **`skip`** (default): After enqueuing a run, the schedule immediately advances to the next cron occurrence. If the run is still executing when the next occurrence arrives, a new run is enqueued.
- **`queue`**: After enqueuing a run, the schedule waits until that run reaches a terminal status (completed, failed, cancelled, compensated, compensation_failed) before advancing. Blocked runs are resumable, not terminal, so they do not trigger schedule advancement. Prevents run pile-up for long-running workflows.

## Registering Schedules

```bash
# Register triggers from a workflow definition
aroom workflow schedule path/to/workflow.yaml
```

This parses the `triggers:` section, validates the cron expression and path confinement, and creates schedule records in the database.

## Managing Schedules

```bash
# List all schedules
aroom workflow triggers list

# Fire a schedule manually (enqueues a run immediately)
aroom workflow triggers fire <schedule_id>

# Enable/disable a schedule
aroom workflow triggers enable <schedule_id>
aroom workflow triggers disable <schedule_id>
```

## Monitoring

Schedules are visible via the REST API:

```bash
# List all schedules
curl http://localhost:8080/api/workflow-schedules

# Get a specific schedule
curl http://localhost:8080/api/workflow-schedules/<id>
```

Enqueued runs appear in `GET /api/workflow-runs` with `trigger_source: "schedule"`.

## Security

- **Path confinement**: Schedules with `ref_type=path` are confined to `~/.anteroom`, the current directory, and built-in workflow directories. Path traversal is rejected at registration time.
- **Denial-of-wallet**: `min_schedule_interval` (default 60s) prevents sub-minute cron expressions.
- **Atomic claiming**: Due schedules are claimed via atomic CAS to prevent duplicate enqueue across processes.
