# Changelog
All notable changes to `bloccs` are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.6.0] — 2026-06-08
### Added
- **Request/response — `Bloccs.call/4` and `Bloccs.cast/4`.** A network was until
now fire-and-forget: `Bloccs.Producer.push/3` returns once a message is
*admitted*, never once it is *processed*, so a caller got nothing back. The new
functions add request/response on top **without making the pipeline
synchronous** — only the calling process waits.
- A terminal node opts in with `reply = true` in its `[node]` block and emits
the response on an out-port.
- `Bloccs.call(network_id, in_port, payload, opts)` pushes to an exposed input
port and blocks until that node replies, returning `{:ok, reply}` or
`{:error, reason}`. `:timeout` defaults to `5_000` ms.
- `Bloccs.cast(network_id, in_port, payload, opts)` returns `{:ok, trace_id}`
immediately; with `send_result: true` the caller is later sent
`{:bloccs_reply, trace_id, result}`.
- **Errors come back as data — `Bloccs.EffectError`.** When a node on a request's
trace fails terminally — a bad inbound schema (`:validate`), the node raising /
returning `{:error, _}` / a `timeout_ms` overrun (`:execute`), or a downstream
delivery failure (`:dispatch`) — the caller receives `{:error,
%Bloccs.EffectError{node, phase, attempt, reason}}` instead of waiting out the
timeout. So `call/4` can tell "failed" from "slow"; `{:error, :timeout}` is
reserved for a request that is legitimately filtered/dropped (no reply *and* no
error). `cast/4` with `send_result: true` delivers the typed error the same way.
A *retried* failure is not reported (a later attempt may still reply). The other
`{:error, _}` reasons are `:no_producer | :unknown_network | {:unknown_port, p}`
(the request could not be admitted).
Correlation reuses the per-message `trace_id` (`Bloccs.Lineage`): the new
`Bloccs.Collector` process keys an in-flight request on `{network_id, trace_id}`,
registered synchronously *before* the push (so a reply/error for an unregistered
trace is dropped, not buffered — plain `push/3` and fire-and-forget `reply =
true` nodes cost the collector nothing) and enforces the timeout collector-side
(a late reply can never crash a timed-out caller).
Limitations: first-wins aggregation (one result per request); correlation
(reply *and* error) does not survive a `[batch]`/`[join]` (which mint a fresh
`trace_id`). See `Bloccs.Collector`, the
[request/response guide](guides/request-response.md), and
`research/12-request-response-primitive.md`.
## [0.5.0] — 2026-06-07
### Added
- **Per-message lineage tracking.** Every message now carries a small lineage in
its `Broadway.Message` metadata (never the payload) so a single logical message
can be tracked across the hops it takes through a network:
%{msg_id: id, parents: [id], trace_id: id}
- `msg_id` — a unique id minted at each emit.
- `parents` — the input message id(s) that caused this emit: one for a
transform/split, **many** for a batch/join fan-in (a causal DAG).
- `trace_id` — root correlation; propagated unchanged on 1:1 and fan-out, and
minted fresh on fan-in (a merge is a new logical message; `parents` preserves
the cross-trace links).
A root lineage is minted at external ingress; `Bloccs.Router.dispatch/5`
threads it to each downstream producer and the runtime stamps it at every emit
site (transform, split, filter, batch, join, join deadletter, retry). Subgraph
composition needs no special handling — `use` is flattened into one namespaced
network at parse time, so lineage threads across the seam for free.
The `[:bloccs, :emit]` telemetry metadata gains `:msg_id`, `:parents`,
`:trace_id` (additive — existing consumers are unaffected). See the new
`Bloccs.Lineage` module.
## [0.4.0] — 2026-06-07
### Added
- **`Bloccs.Introspect` now exposes each node's contract and primitive config.**
`node_view/1` gained two fields so observability tools can show *what code runs
at a node* and *how a primitive is tuned* without re-parsing manifests:
- `:contract` — the author's `pure_core` / `effect_shell` refs (as
`"Mod.fun/arity"`), plus `timeout_ms`, `retry`, and `idempotency` policy.
- `:config` — the primitive block a node declares, as the relevant
`Bloccs.Manifest.{Batch,Join,Rate}` struct (or `delay_ms`), `nil` when absent.
Both are additive; existing `node_view` consumers are unaffected.
## [0.3.0] — 2026-06-05
### Added
- **`Bloccs.Inspect` — opt-in payload capture.** Off by default. When enabled,
the runtime attaches a rendered, **bounded**, **redacted** snapshot of each
emitted payload to the `[:bloccs, :emit]` telemetry event under the new
`:payload` metadata key (a string; `nil` when disabled), so observability tools
(e.g. the `bloccs_web` Messages feed) can show message contents without holding
the live term.
```elixir
config :bloccs, :inspect,
enabled: true,
max_bytes: 512,
redact: [:password, :token, :secret, :authorization]
```
Redaction matches map keys by name (atom or string) at any depth; structs keep
their type.
### Changed
- **Relicensed from MIT to Apache License 2.0** (adds an explicit patent grant).
Applies to this and future releases; previously published versions (≤ 0.2.0)
remain under their original MIT terms.
## [0.2.0] — 2026-06-05
### Added
- **Introspection API** — a read-only window into running networks, the
foundation an observability dashboard (`bloccs_web`) reads. All additive.
- `Bloccs.Introspect` — `list_networks/0` (summaries), `network/1` (a
normalized `Bloccs.Introspect.Network` with nodes, ports, effects, edges,
per-node concurrency, supervision strategy, and exposed ports), `producers/1`,
and `producer_state/1`.
- `Bloccs.Introspect.glyph/1` — the canonical notation glyph for a node
manifest (`:source`, `:sink`, `:split`, `:batch`, `:join`, `:throttle`,
`:delay`, `:node_effect`, `:node`), so the notation stays a property of the
library rather than being reinvented by each viewer.
- `Bloccs.Discovery` — boot-time registration of running networks in a new
`Bloccs.NetworkRegistry`, so tools enumerate live networks in O(1) without
scanning the supervision tree. Entries are owned by each network's supervisor
and clean up automatically when it stops or crashes. The generated supervisor
now calls `Bloccs.Discovery.register/2` from `init/1` — **recompile networks
built with an older bloccs** (`mix bloccs.compile`) to make them discoverable.
- `Bloccs.Producer.stats/1` — a safe, typed snapshot of a producer's queue
(`size`, `buffer`, `blocked`, `pending_demand`, `utilization`) via a bounded
`GenStage` call, so observers never reach in with `:sys.get_state`.
- The generated supervisor gained `__bloccs_introspect__/0` (topology + node→impl
+ supervision + exposed ports) feeding the API above.
## [0.1.1] — 2026-06-05
### Changed
- Docs: dropped the README Status and "Out of scope" sections (the roadmap now
lives in GitHub Projects), corrected the License note to plain MIT (no Pro
call-out), and refreshed the post-publish wording now that bloccs is on Hex.
## [0.1.0] — 2026-06-05
First public release.
### Added
- **Manifest formats** — TOML node manifests (`.bloccs`, one node per file) and
network manifests, parsed into typed structs (`Bloccs.Manifest.*`) by
`Bloccs.Parser`.
- **Validator** — `Bloccs.Validator` enforces declared ports, end-to-end edge
schema matching, DAG-only topology (cycle detection), effect declaration, and
well-formed `pure_core`/`effect_shell` function refs.
- **`use Bloccs.Node`** — compile-time parse + validate of a node's manifest,
`@after_compile` arity check of the contract functions, and an AST-walk warning
on use of undeclared effects.
- **Code generator** — `Bloccs.Compiler` emits a Broadway supervision tree as
real `.ex` source under `_build/<env>/bloccs_generated/<network>/` (debuggable,
PR-reviewable), one pipeline module per node plus a supervisor.
- **Runtime contracts**, wired into the generated `handle_message`: retry
(constant/linear/exponential back-off, matched on failure reason), `timeout_ms`
(bounded task per attempt), idempotency (atomic in-flight reservation by key),
bounded-buffer back-pressure (producer parks the caller, never drops), and
`:telemetry` spans.
- **Effect capability model** — pure core + effect shell split; four effect axes
(`http`, `db`, `time`, `random`); declared axes bind to real adapters,
undeclared axes bind to a denied-capability stub that raises. Mock adapters by
default; real `Bloccs.Effects.HTTP.Req` and `Bloccs.Effects.DB.Ecto` behind a
config switch, with `:req`/Ecto as optional deps.
- **Flow primitives** — `:drop` (filter) and `{:emit, [{port, payload}, …]}`
(split / multi-emit) effect-shell return shapes alongside the single
`{:emit, port, payload}`; **merge** (several edges into one in-port);
**`[batch]`** windows (`size` / `timeout_ms`) reduced by `pure_core`;
**`[join]`** correlating two or more distinct typed in-ports by a key (`on`) —
each in-port compiles to its own pipeline; partials past `timeout_ms`
dead-letter; **`[rate]`** throttle and **`[delay]`** time-shift via the Broadway
producer. A filtered message emits `[:bloccs, :node, :dropped]`.
- **Subgraph composition** — a `[nodes]` entry may `use` a network manifest; the
parser flattens it into namespaced leaf nodes at parse time.
- **Trace + coverage** — `Bloccs.Trace` records a run from telemetry to a
`.bloccs-trace`; `mix bloccs.coverage` reports real structural coverage
(in-ports, out-ports, edges reached) live (`--message`) or from a loaded trace
(`--trace`).
- **CLI** — `mix bloccs.new`, `mix bloccs.validate`, `mix bloccs.compile`,
`mix bloccs.run` (with `--message`, `--port`, `--trace`), `mix bloccs.coverage`.
- **Examples** — a graded ladder: `examples/tour` (core concepts, mock effects),
`examples/events` (the flagship webhook processor — branching, fan-out, retry,
timeout, idempotency, coverage), and `examples/real_backend` (real HTTP to a
local stub + real SQLite).
- **Guides** — core concepts, getting started, manifest reference, architecture,
and effect adapters.
### Known limitations
- **Cyclic networks** are out of scope (DAG-only); feedback loops need a
deadlock-safe edge mode still on the roadmap.
[Unreleased]: https://github.com/Bloccs/bloccs/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/Bloccs/bloccs/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/Bloccs/bloccs/compare/v0.3.0...v0.4.0
[0.1.1]: https://github.com/Bloccs/bloccs/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/Bloccs/bloccs/releases/tag/v0.1.0