Skip to main content

CHANGELOG.md

# Changelog

## 0.2.0

First public release on Hex.

`0.2.0` is a clean rewrite. The pre-release `0.1.0` SDK was never published;
the API is intentionally not backwards-compatible with that version.

### Programming model

Workflows read top-to-bottom as sequential code. Concurrency is explicit and
structured — the only ways to introduce it are `API.receive/2` (a message
loop with signal/update handlers) and `API.parallel/1` (concurrent fan-out).
Every spawned handler must complete before the receive returns.

### Features

- `Temporalex.Workflow` — workflow modules with `run/1` and `handle_query/3`.
- `Temporalex.Activity``defactivity` macro for both regular and
  `local: true` activities. Local activities are durable across worker
  crashes (recorded in workflow history).
- `Temporalex.Workflow.API`:
  - Sequential primitives: `execute_activity/3`, `execute_local_activity/3`,
    `sleep/1`, `wait_for_signal/1`, `publish_state/1`, `patched?/1`,
    `cancelled?/0`.
  - Structured concurrency: `receive/2` with signal/update handlers and an
    optional `:timeout`; `parallel/1` for fan-out.
  - Async-only: `update_state/1` for atomic mutations from inside an
    `{:async, fn, _}` handler.
- `Temporalex.Worker` — supervisor for one task queue. Drop into an OTP
  supervision tree.
- `Temporalex.Client` — start, signal, query, cancel workflows from
  outside workflow code.
- `Temporalex.Testing` — step-by-step test driver. The same call protocol
  as the production executor, so workflow code is unchanged between
  tests and real runs.
- ETF as the default payload encoding (preserves full Elixir type fidelity).

### Robustness

This release shipped after several rounds of bug-hunting. Notable fixes
worth knowing about:

- **Updates respond end-to-end.** Update handler return values are now
  emitted as `UpdateResponse` commands (Accepted → Completed / Rejected).
  Without this, the Temporal Update API caller would hang until update
  timeout.
- **Multi-signal-in-one-activation.** When Temporal delivers several
  signals in a single activation, all handlers run in dispatch order and
  every state mutation lands.
- **Cancelled activities and child workflows replay correctly.** Replay
  log builds entries for `:cancelled` resolutions; runtime apply path
  handles them. Local-activity backoffs are filtered from the replay log.
- **Receive timer / handler stop race.** Timer fires while a sync handler
  is in flight; first stop wins, executor doesn't crash on a cleared
  `receive_from`.
- **Heartbeat details encode as a Payload.** Details now round-trip
  through Temporal history with metadata intact, instead of being sent
  as raw ETF bytes.
- **Bounded buffers.** `signal_buffer` and `pending_handler_queue` are
  capped (defaults: 10_000 each, configurable per executor). Floods drop
  oldest with a warning; updates beyond the queue cap surface a
  `:rejected` response instead of hanging.
- **Worker shutdown awaits drain.** `terminate/2` calls the async
  `shutdown_worker` NIF and waits up to 30s (configurable) for Core to
  finish in-flight activations before returning.
- **Server crash takes the worker tree with it.** Worker supervisor uses
  `:one_for_all` so a Server crash doesn't leave executors orphaned with
  stale `WorkerResource` references.
- **Query and validator handlers are crash-protected.** A throwing or
  exiting query/validator no longer kills the executor — surfaces as
  `{:error, _}` to the caller.
- **Native command encoding propagates errors.** Malformed commands and
  malformed payloads inside command lists now surface as `{:error, _}`
  from `encode_workflow_completion` instead of silently disappearing.

### Status

Suitable for evaluation and small-scale production. 303 unit tests pass.
The API surface in `Temporalex.Workflow.API` is stable for `0.2.x`;
lower-level modules (`Worker.Server`, `Worker.Executor`, `Native`) are
public for testing hooks but not part of the API contract — those will
likely change.

### Known limitations

- **Update results aren't readable via the `temporal` CLI.** Workflow
  payloads default to `binary/etf` encoding (full Elixir term fidelity).
  The CLI's update-execute can't render binary/etf responses — it
  errors with "payload encoding is not supported" when displaying. The
  update itself executes correctly; only the CLI display is affected.
  Two ways to drive updates without the CLI today: from another worker,
  or from external code that reads ETF directly. A
  `Temporalex.Client.update_workflow` (matching `start_workflow`,
  `signal_workflow`) is the planned 0.2.1 fix.

### Wire-protocol invariant

Each workflow activation must produce exactly one
`complete_workflow_activation` call. The unit test harness
`Temporalex.Test.ExecutorHelpers.assert_one_flush_per_activation`
validates this without needing a Temporal server. Tests under
`test/temporalex/worker_executor_test.exs` exercise the harness for
sync update handlers and update handlers that park on activities.