Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 61 additions & 82 deletions agents/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,115 +1,94 @@
# Durable — Roadmap

**Last Updated:** 2026-03-09
**Last Updated:** 2026-10-23
**Current release:** `durable` 1.0.0 and `durable_dashboard` 1.0.0 on Hex (2026-10-21)
**Reference:** See `arch.md` for technical architecture, `WORKPLAN_ARCHIVED.md` for historical detail

---

## Quick Stats

| Metric | Value |
|--------|-------|
| Source Modules | 42 |
| Passing Tests | ~291 |
| Documentation Guides | 6 |
| Lines of Code | ~11,000 |
| Metric | Core (`durable/`) | Dashboard (`durable_dashboard/`) |
|--------|-------------------|----------------------------------|
| Source modules | 77 | 37 |
| Tests | ~483 | ~235 |
| Lines of code | ~18,000 | — |
| Guides | 6 | — |

---

## Completed Work

| Area | Key Features | Tests |
|------|-------------|-------|
| **Foundation** | Mix project, Ecto schemas (6), programmatic migrations, NimbleOptions config, CI (`mix precommit`), Credo strict | — |
| **Core MVP** | `workflow`/`step`/`decision` macros, context management, executor, retry/backoff, PostgreSQL queue (FOR UPDATE SKIP LOCKED), query API, time helpers | ~65 |
| **Wait Primitives** | `sleep`, `schedule_at`, `wait_for_event`, `wait_for_any/all`, `wait_for_input`, `wait_for_approval`, `wait_for_choice`, `wait_for_text`, `wait_for_form`, timeouts | 52 |
| **Branching** | `branch on:` macro with pattern matching, default clause, multi-step branches | 19 |
| **Parallel Execution** | `parallel do` macro, `__results__` model, `into:` merge, `returns:` option, error strategies, resume durability | 20 |
| **Compensation/Saga** | `compensate` macro, `step :name, compensate:`, reverse-order execution | 10 |
| **Cron Scheduling** | `@schedule` decorator, scheduler GenServer, multi-node safety, timezone support, CRUD API, enable/disable, manual trigger | 49 |
| **Workflow Orchestration** | `call_workflow/3`, `start_workflow/3`, parallel call_workflow, cascade cancellation, parent notifications, `list_children/2` | 12 |
| **Observability** | Logger handler, IO capture, log buffer | 13 |
| **Developer Experience** | `mix durable.gen.migration`, `mix durable.install`, DataCase, @moduledoc/@doc/@spec, 6 guides | — |

**Total Tests:** ~291
| Area | Key Features |
|------|-------------|
| **Foundation** | Mix project, Ecto schemas, programmatic migrations, NimbleOptions config, CI (`mix precommit`), Credo strict |
| **Core MVP** | `workflow`/`step`/`decision` macros, context management, executor, retry/backoff, PostgreSQL queue (FOR UPDATE SKIP LOCKED), query API, time helpers |
| **Wait Primitives** | `sleep`, `schedule_at`, `wait_for_event`, `wait_for_any/all`, `wait_for_input`, `wait_for_approval`, `wait_for_choice`, `wait_for_text`, `wait_for_form`, timeouts |
| **Branching** | `branch on:` macro with pattern matching, default clause, multi-step branches |
| **Parallel Execution** | `parallel do` macro, `__results__` model, `into:` merge, `returns:` option, error strategies, resume durability, deterministic block ids |
| **Compensation/Saga** | `compensate` macro, `step :name, compensate:`, reverse-order execution |
| **Cron Scheduling** | `@schedule` decorator, scheduler GenServer, multi-node safety, timezone support, CRUD API, enable/disable, manual trigger, failure tracking + auto-disable, `upcoming_runs/2` |
| **Workflow Orchestration** | `call_workflow/3`, `start_workflow/3`, parallel call_workflow, cascade cancellation, parent notifications, `list_children/2` |
| **Hardening (Apr 2026 audit)** | JSONB sanitization at every write boundary, context survives retries/gotos, `Ecto.Multi` for timeout/child/wait-group transitions, ack retries, `:compensating` zombie recovery — see `docs/bug-reports/2026-04-13-follow-up-audit.md` |
| **Operator Controls** | Retry/rerun, cancel (CAS race fixed), queue pause, workflow `timeout:` (deadline enforced between steps), workflow `queue:`, `trigger` tracking, pruner, node registry |
| **Observability** | Logger handler, IO capture, log buffer, `Durable.Telemetry` events + metrics, `Durable.Stats`, `Durable.Activity` log, queue sampler, `Durable.PubSub` lifecycle broadcasts |
| **Testing ([M03](milestones/M03-testing-framework.md))** | `Durable.Testing`: `run_workflow`, `drain` (runs parallel branches + child workflows, skips sleeps and retry backoff), `assert_status`/`assert_step`/`assert_waiting_for_input`/`assert_waiting_for_event`, `guides/testing.md` |
| **CLI ([M02](milestones/M02-developer-tooling.md))** | `mix durable.*`: status, list, inspect, run, cancel, retry, cleanup, pending, provide_input, send_event, doctor, install, migrations, gen.migration, gen.upgrade |
| **Dashboard ([M06](milestones/M06-phoenix-dashboard.md))** | Separate `durable_dashboard` package: LiveView pages (Overview, Runs, Run, Workflows, Inbox, Queues, Schedules, Telemetry, Settings), ReactFlow graph island, ⌘K palette |
| **Docs & Release ([M04](milestones/M04-documentation-release.md))** | HexDocs, 6 guides, git-cliff changelogs, GitHub Actions release + Hex publish, durable.wavezync.com, exported `locals_without_parens` for the DSL (#10) |

---

## Active Milestones

| ID | Title | Priority | Effort | Status | Dependencies |
|----|-------|----------|--------|--------|--------------|
| [M01](milestones/M01-graph-visualization.md) | Graph Visualization | Medium | 1 week | Not Started | None |
| [M02](milestones/M02-developer-tooling.md) | Developer Tooling | High | 1 week | Not Started | None |
| [M03](milestones/M03-testing-framework.md) | Testing Framework | Medium | 1 week | Not Started | None |
| [M04](milestones/M04-documentation-release.md) | Documentation & Release | High | 1 week | Not Started | M02, M03 (soft) |
| [M05](milestones/M05-message-bus.md) | Message Bus | Medium | 1.5 weeks | Not Started | None |
| [M06](milestones/M06-phoenix-dashboard.md) | Phoenix Dashboard | Low | 2 weeks | Not Started | M01, M05 |
| [M07](milestones/M07-scalability-adapters.md) | Scalability Adapters | Low | 2 weeks | Not Started | M05 (soft) |

### Recommended Execution Order

```
M02 (Developer Tooling) ─┐
M03 (Testing Framework) ─┤──→ M04 (Documentation & Release)
M01 (Graph Visualization) ┘
──→ M05 (Message Bus)
├──→ M06 (Phoenix Dashboard)
└──→ M07 (Scalability Adapters)
```

**Phase A** (parallel, no dependencies): M01, M02, M03
**Phase B** (needs Phase A): M04
**Phase C** (independent): M05
**Phase D** (needs M01 + M05): M06, M07
## Milestones

| ID | Title | Status | Notes |
|----|-------|--------|-------|
| [M01](milestones/M01-graph-visualization.md) | Graph Visualization | Superseded | Dashboard `GraphBuilder` + ReactFlow covers it. Core DOT/Mermaid export not built → backlog |
| [M02](milestones/M02-developer-tooling.md) | Developer Tooling | Done | Shipped more tasks than planned |
| [M03](milestones/M03-testing-framework.md) | Testing Framework | Done | Shipped as `Durable.Testing` helpers (imported into the user's own case) rather than a `Durable.TestCase` template |
| [M04](milestones/M04-documentation-release.md) | Documentation & Release | Done | 1.0.0 published |
| [M05](milestones/M05-message-bus.md) | Message Bus | Partial | `Durable.PubSub` lifecycle events shipped. Adapter behaviour / pg_notify not built → backlog |
| [M06](milestones/M06-phoenix-dashboard.md) | Phoenix Dashboard | Done | Shipped as its own package |
| [M07](milestones/M07-scalability-adapters.md) | Scalability Adapters | Backlog | No demand for Redis yet |

---

## Backlog
## Next Up

In priority order:

1. **Correctness follow-ups from v1:**
- The workflow deadline can't interrupt a step that's still running; it's only checked between steps.
- `wait_for_input` with `on_timeout: :fail` ends the run as `cancelled`, not `failed`. Fixing it changes behaviour, so ship it in a minor release with a changelog note.
2. **Workflow versioning.** Needs a milestone doc. Changing a deployed workflow while runs are still in flight is the main open problem for production users.
3. **DSL gaps:** waits inside `parallel` blocks, an `each`/foreach primitive (each iteration needs its own context, see `Durable.DSL.Step` docs), decisions inside `parallel`.
4. **Testing follow-up:** wait timeouts can't be exercised by `Durable.Testing.drain/2` (the timeout worker only runs with the queue on). A helper that fires a run's pending timeouts would close it.
5. **Formatter follow-up to #10:** the repo's own `durable/.formatter.exs` only *exports* `locals_without_parens`, so internal code and tests still get parens. Also add `:durable` to `import_deps` in `durable_dashboard` and `examples/phoenix_demo`.

---

Lower-priority items not warranting standalone milestone documents:
## Backlog

| Item | Notes |
|------|-------|
| DOT/Mermaid graph export | Leftover from M01. For docs/READMEs, if requested |
| Message bus adapter behaviour / pg_notify | Leftover from M05. `Phoenix.PubSub` already spans nodes |
| Redis queue adapter | M07. Add when someone needs it |
| RabbitMQ queue adapter | Niche — add if requested |
| Switch/Case macro | Low priority — `branch` covers most use cases |
| Pipe-based API | Functional workflow composition — nice-to-have |
| Example Project | Standalone demo app with common patterns |
| Redis message bus | Quick follow-up after M05 + M07 |
| RabbitMQ queue adapter | Niche — add if requested |

---

## Test Coverage

| Test File | Tests | Area |
|-----------|-------|------|
| wait_test.exs | 52 | Wait primitives |
| scheduler_test.exs | 49 | Cron scheduling |
| parallel_test.exs | 20 | Parallel execution |
| branch_test.exs | 19 | Branch macro |
| postgres_test.exs | 16 | Queue adapter |
| decision_test.exs | 14 | Decision steps |
| log_capture_test.exs | 13 | Log/IO capture |
| orchestration_test.exs | 12 | Workflow orchestration |
| integration_test.exs | 11 | End-to-end flows |
| validation_test.exs | 10 | Input validation |
| context_test.exs | 10 | Context management |
| compensation_test.exs | 10 | Saga pattern |
| durable_test.exs | 10 | Core API |
| handler_test.exs | 8 | Log handler |
| io_server_test.exs | 7 | IO capture |
| resume_edge_cases_test.exs | 5 | Resume edge cases |
| log_capture/integration_test.exs | 5 | Log capture integration |
| Other | ~20 | Misc |
| **Total** | **~291** | |

---

## Known Limitations

1. Wait primitives not supported in parallel blocks
2. Child workflows with waits (`sleep`, `wait_for_event`) not supported in parallel blocks
1. Wait primitives (and child workflows that wait) are not supported inside parallel blocks
2. Decisions are not supported inside parallel blocks
3. No backward jumps in decision steps (forward-only by design)
4. Context is single-level atomized (top-level keys only)
5. No workflow versioning
6. No foreach/loop DSL primitives (use Elixir's `Enum` functions)
6. No foreach/loop DSL primitive (use Elixir's `Enum` functions)
7. A resumed wait re-runs the step body from the top; side effects before the wait must be idempotent
8. Only one `sleep/1` per step body
9. The workflow deadline is checked between steps and can't interrupt a running step
10. `Durable.Testing.drain/2` doesn't fire wait timeouts
2 changes: 1 addition & 1 deletion agents/milestones/M01-graph-visualization.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# M01: Graph Visualization

**Status:** Not Started
**Status:** Superseded (dashboard GraphBuilder; DOT/Mermaid export moved to backlog)
**Priority:** Medium
**Effort:** 1 week
**Dependencies:** None
Expand Down
2 changes: 1 addition & 1 deletion agents/milestones/M02-developer-tooling.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# M02: Developer Tooling

**Status:** Not Started
**Status:** Done
**Priority:** High
**Effort:** 1 week
**Dependencies:** None
Expand Down
22 changes: 21 additions & 1 deletion agents/milestones/M03-testing-framework.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# M03: Testing Framework

**Status:** Not Started
**Status:** Done (shipped as `Durable.Testing` + `guides/testing.md`; see note below)
**Priority:** Medium
**Effort:** 1 week
**Dependencies:** None
Expand Down Expand Up @@ -175,6 +175,26 @@ Sections:
- [ ] Existing tests in `test/` still pass (no regressions)
- [ ] `mix credo --strict` passes

## Outcome (2026-10-23)

Shipped as `Durable.Testing`, a helper module users `import` into their own
`DataCase`, instead of a `Durable.TestCase` case template: users already have a
case template that owns the sandbox, and ExUnit allows only one per module.

- `run_workflow/3`, `drain/2`: inline execution of the whole run tree (parallel
branches, child workflows); sleeps end immediately, retry backoff is skipped
(`Backoff.pause/1` checks `:durable_skip_backoff`).
- `assert_status/3`, `assert_step/4` (matches parallel/branch steps by their own
name, returns the `%StepExecution{}`), `assert_waiting_for_input/3`,
`assert_waiting_for_event/3`, `steps/2`.
- Dropped as unneeded: `assert_context` (pattern-match `run.context`),
`assert_completed_within`, `provide_test_input`/`send_test_event` (use
`Durable.provide_input/3` / `send_event/3` + `drain/2`).
- Not covered: wait timeouts (TimeoutWorker needs the queue on).
- Found and fixed while testing it: a step returning `{:error, "string"}` left
the run stuck `:running` (or crashed compensation) because the error wasn't
normalized to a map before the write.

## Open Questions

- Should `run_workflow_sync` return a rich struct or the raw `%WorkflowExecution{}` schema?
Expand Down
2 changes: 1 addition & 1 deletion agents/milestones/M04-documentation-release.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# M04: Documentation & Release

**Status:** Not Started
**Status:** Done
**Priority:** High
**Effort:** 1 week
**Dependencies:** M02, M03 (soft — can start without, but references their output)
Expand Down
2 changes: 1 addition & 1 deletion agents/milestones/M05-message-bus.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# M05: Message Bus

**Status:** Not Started
**Status:** Partial (Durable.PubSub lifecycle events; adapter behaviour moved to backlog)
**Priority:** Medium
**Effort:** 1.5 weeks
**Dependencies:** None
Expand Down
2 changes: 1 addition & 1 deletion agents/milestones/M06-phoenix-dashboard.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# M06: Phoenix Dashboard

**Status:** Not Started
**Status:** Done
**Priority:** Low
**Effort:** 2 weeks
**Dependencies:** M01 (graph visualization), M05 (message bus for real-time updates)
Expand Down
2 changes: 1 addition & 1 deletion agents/milestones/M07-scalability-adapters.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# M07: Scalability Adapters

**Status:** Not Started
**Status:** Backlog
**Priority:** Low
**Effort:** 2 weeks
**Dependencies:** M05 (soft — message bus pattern informs adapter design)
Expand Down
1 change: 1 addition & 0 deletions durable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ option list and design notes.
- [Compensations](guides/compensations.md) - Saga pattern
- [Waiting](guides/waiting.md) - Sleep, events, human input
- [Orchestration](guides/orchestration.md) - Parent/child workflow composition
- [Testing](guides/testing.md) - Run and assert on workflows in ExUnit

## License

Expand Down
Loading
Loading