Every field of the two manifest formats, with types and whether it's required. Manifests are TOML. For what the words mean, see Core concepts.


Node manifest

[node] — required

keytyperequiredmeaning
idstringnode identifier
versionstringsemver of this node
kind"source" | "transform" | "router" | "sink"role in the graph
[node]
id      = "enrich"
version = "0.1.0"
kind    = "transform"

[doc] — optional

Human-facing documentation. Both keys optional.

keytypemeaning
intentstringwhat the node is for
ownerstringteam/person responsible

[ports.in] / [ports.out]

A table of named ports. Each value is an inline table:

keytyperequiredmeaning
schemastring "Name@N"the message schema on this port
bufferpositive integerin-ports only: producer mailbox bound (back-pressure). Ignored on out-ports.
[ports.in]
event = { schema = "RawEvent@1", buffer = 1000 }

[ports.out]
enriched = { schema = "EnrichedEvent@1" }
failed   = { schema = "FailedEvent@1" }

The schema strings must be registered via Bloccs.Schema.register/2 at app start. Edges between ports are checked for matching schemas at validation time.

[effects] — optional

Declared capabilities. Omit the block (or an axis) to deny that axis entirely. An undeclared axis is refused at runtime; a declared one is allowlist-enforced.

axistypemeaning
http{ allow = [host, …], methods = [method, …] }outbound HTTP, restricted to those hosts + methods
db{ allow = [scope, …] }DB ops, each scope a "table:action" string
time"wall_clock" | "none"clock access
random"none" | "pseudo" | "crypto"randomness source
[effects]
http = { allow = ["enrichment.local"], methods = ["GET"] }
db   = { allow = ["events:insert"] }
time = "wall_clock"

[contract] — required

keytyperequiredmeaning
pure_corestring "Mod.fun/arity"pure-core function ref; must exist with that arity
effect_shellstring "Mod.fun/arity"effect-shell function ref; must exist with that arity
timeout_mspositive integerbounds each attempt; overrun fails the message with :timeout (retriable)
idempotency{ key = "field" }dedup repeat deliveries by that payload field (atomic in-flight reservation)
retryinline table (below)retry policy

retry table:

keytyperequiredmeaning
strategy"constant" | "linear" | "exponential"back-off curve
maxnon-negative integermax attempts
onlist of stringsfailure reasons that are retriable (e.g. ["timeout"])
base_mspositive integerbase delay for the back-off

Permanent failures (schema mismatch, capability denial) are never retried, regardless of on.

[contract]
pure_core    = "MyApp.Nodes.Enrich.transform/2"
effect_shell = "MyApp.Nodes.Enrich.execute/2"
timeout_ms   = 3000
idempotency  = { key = "id" }
retry        = { strategy = "exponential", max = 2, on = ["timeout"], base_ms = 50 }

[batch] — optional

Present only on aggregate nodes. When set, the node processes messages in batches via Broadway batchers instead of one at a time: its pure_core receives the list of payloads in the batch (not a single payload) and reduces them, and its effect_shell emits the aggregate result (one emit, a split, or a drop — like any node). The batch flushes when it reaches size messages or after timeout_ms of idle, whichever is first, so a partial batch is never stranded.

keytypedefaultmeaning
sizepositive integer100count window — flush after this many messages
timeout_mspositive integer1000time window — flush a partial batch after this idle period

At least one of size / timeout_ms must be set. A [batch] node may not also declare [contract].retry, idempotency, or timeout_ms — the batch path is at-least-once and does not run those per-message policies yet (the validator rejects the combination).

[batch]
size       = 100
timeout_ms = 5000

[join] — optional

Present only on join nodes. A join node has two or more in-ports carrying distinct schemas; arrivals are correlated by the value of the on field (present in every input payload). When all in-ports have produced a payload for the same key, the node's pure_core receives the map %{port => payload} and emits the joined result. (This is the one case where a node may declare more than one in-port — each compiles to its own pipeline.)

keytypedefaultmeaning
onstring (field name)— (required)correlation key — a field name present in every input payload (not a predicate)
timeout_mspositive integerhow long to hold a partial match before giving up
deadletterstring (out-port name)a partial match that times out is emitted there as an %{key, present, payloads} envelope; without it, a timeout drops with a [:bloccs, :join, :timeout] event

A [join] node may not also be a [batch] node, nor declare [contract].retry / idempotency / timeout_ms (the join path is at-least-once and does not run those per-message policies yet).

[ports.in]
left  = { schema = "Order@1" }
right = { schema = "Payment@1" }

[ports.out]
joined     = { schema = "Reconciled@1" }
deadletter = { schema = "Unmatched@1" }

[join]
on         = "order_id"
timeout_ms = 30000
deadletter = "deadletter"

[rate] — optional

Throttle: cap how fast the node's producer delivers messages downstream, using Broadway's built-in producer rate limiting.

keytypemeaning
allowedpositive integermessages allowed per interval
interval_mspositive integerthe interval window, in ms
[rate]
allowed     = 100
interval_ms = 1000

[delay] — optional

Delay: hold every message for ms before it enters the node (the producer time-shifts delivery — the push is acked immediately, it does not back-pressure).

[delay]
ms = 5000

[rate] and [delay] are not supported on a [join] node. Debounce (collapse a burst, keep the last) is not a separate block — model it as a time-windowed [batch] whose pure_core keeps the last payload.

[observability] — optional

A free-form table captured onto the node (metrics, traces, …); telemetry is emitted on every span regardless. Stored as-is for downstream tooling.

[observability]
metrics = ["duration_ms"]
traces  = "auto"

Network manifest

A file is parsed as a network (not a node) when it has a top-level [network] table.

[network] — required

keytyperequiredmeaning
idstringnetwork identifier
versionstringsemver
runtimestringtarget runtime; defaults to "beam"

[nodes] — required

A table mapping a local id to an instantiation. Each value is an inline table with a use path pointing at a node manifest — or another network manifest, in which case it is flattened in as a subgraph (namespaced ids like pipe.up).

keytyperequiredmeaning
usestring (path)node or network manifest to instantiate
configinline tableper-instance config overlay for parameterized nodes
[nodes]
ingest = { use = "../nodes/ingest.bloccs" }
enrich = { use = "../nodes/enrich.bloccs" }

[[edges]] — required

An array of tables. Each edge wires one out-port to one or more in-ports. Endpoints are "node.port" strings.

keytyperequiredmeaning
fromstring "node.port"source out-port
tostring | list of stringsone or more destination in-ports (a list fans out)
[[edges]]
from = "ingest.event"
to   = "enrich.event"

[[edges]]
from = "route.known"
to   = ["persist.event", "notify.event"]   # fan-out

[[edges]]
from = "left.out"
to   = "collect.in"
[[edges]]
from = "right.out"
to   = "collect.in"                          # fan-in (merge): both feed collect.in

The validator checks both endpoints exist, schemas match end-to-end, and the graph is acyclic. Several edges may target one in-port (fan-in / merge) — that port's producer receives all of them, interleaved and unordered.

[expose] — optional

Promotes internal ports to the network's public ports (and makes it usable as a subgraph). Two tables, in and out, mapping a public name to an internal "node.port".

[expose]
in  = { webhook = "ingest.received" }
out = { stored  = "persist.stored",
        dead    = "deadletter.recorded" }

mix bloccs.run --port <name> and the default message intake use these names.

[supervision] — optional

Supervisor config for the generated tree. Defaults: one_for_one, 3, 5.

keytypemeaning
strategy"one_for_one" | "one_for_all" | "rest_for_one"restart strategy
max_restartsnon-negative integerrestart intensity
max_secondspositive integerrestart window

[deploy] — optional

keytypemeaning
concurrencytable { node = N, … }per-node Broadway processor concurrency
placementstringplacement hint (captured; "default" today)
[deploy]
concurrency = { enrich = 4, persist = 1 }
placement   = "default"