Skip to content

Recipes

Short, task-oriented answers to “how do I …” questions. Each recipe shows only the distinctive steps — assume nodes, an installed application, and a context already exist. Every step, field, and flag here is grounded in the workflow YAML reference (which also covers the setup steps) and a real workflow-examples/*.yml scenario. Validate any file first:

Terminal window
merobox bootstrap run workflow.yml --dry-run

How do I test sync recovery after a network partition?

Section titled “How do I test sync recovery after a network partition?”

Cut libp2p traffic between a node and its peers with partition_peers, write to the isolated node (its RPC stays reachable), then restore delivery with heal_peers and confirm state reconverges with wait_for_sync. This is the surgical alternative to disconnect_node, which also severs RPC. It is Docker-only and needs Linux with iptables and passwordless sudo.

# after a baseline key has replicated to node-1, node-2 and node-3…
- type: partition_peers
node: calimero-node-1
peers: [calimero-node-2, calimero-node-3]
- type: call # RPC still works on the isolated node
node: calimero-node-1
context_id: '{{context_id}}'
method: set
args: { key: from_n1, value: while_partitioned }
- type: heal_peers
node: calimero-node-1
peers: [calimero-node-2, calimero-node-3]
- type: wait_for_sync # the write converges everywhere after the heal
context_id: '{{context_id}}'
nodes: [calimero-node-1, calimero-node-2, calimero-node-3]
timeout: 90
trigger_sync: true

How do I write a negative / auth-failure test?

Section titled “How do I write a negative / auth-failure test?”

Set expected_failure: true on a call so the step passes only if the operation is rejected — the workflow fails if it unexpectedly succeeds. For an authorization test, add unauthenticated: true to force a no-token request and assert the 401. The same flags work on ws_connect, login, and refresh.

# a bad call is expected to be rejected
- type: call
node: calimero-node-1
context_id: '{{context_id}}'
method: invalid_method_that_does_not_exist
args: {}
expected_failure: true
# a no-token call against an auth-protected node must 401
- type: call
node: calimero-node-1
context_id: '{{context_id}}'
method: get
args: { key: hello }
unauthenticated: true
expected_failure: true

How do I upgrade an app across a namespace?

Section titled “How do I upgrade an app across a namespace?”

Emit one signed cascade with cascade_namespace_application from an admin node; it fans the target-application change out to every matching descendant subgroup in one sync round. Then block until it lands with assert_cascade_complete, which polls get_cascade_status under the hood until the subtree finishes or the timeout elapses.

- type: cascade_namespace_application
node: calimero-node-1
namespace_id: '{{namespace_id}}'
target_application_id: '{{app_v2}}'
- type: assert_cascade_complete
node: calimero-node-1
namespace_id: '{{namespace_id}}'
timeout_seconds: 60

Use fuzzy_test to hammer a context with weighted, randomized operation patterns for a set duration. Each operation is a mini-sequence of steps; random generators ({{random_int(min, max)}}, {{uuid}}, {{random_node}}) and the auto-captured {{fuzzy_key}} / {{fuzzy_value}} from a prior call keep the load varied. Mark per-iteration checks non_blocking: true so a single miss is tracked, not fatal.

- type: fuzzy_test
duration_minutes: 5 # 30–60 for a real run
context_id: '{{context_id}}'
nodes:
- name: calimero-node-1
- name: calimero-node-2
operations:
- name: set_and_verify
weight: 70
steps:
- type: call
node: '{{random_node}}'
context_id: '{{context_id}}'
method: set
args:
key: 'fuzzy_key_{{random_int(1, 10000)}}'
value: 'value_{{uuid}}'
- type: call
node: '{{random_node}}'
context_id: '{{context_id}}'
method: get
args: { key: '{{fuzzy_key}}' }
outputs:
fuzzy_get_result: result
- type: assert
non_blocking: true
statements:
- 'contains({{fuzzy_get_result}}, {{fuzzy_value}})'

How do I run against a locally-built merod?

Section titled “How do I run against a locally-built merod?”

Pass --no-docker to run merod as a native process instead of a container, and --binary-path to point at your build. Extra merod run arguments go through --merod-args (binary mode only — it is ignored under Docker). See bootstrap run for the full flag list.

Terminal window
merobox bootstrap run workflow.yml \
--no-docker \
--binary-path ./target/release/merod \
--merod-args="--sync-strategy delta"

Push a file into a node’s blob store with upload_blob and capture the returned blob_id (and size) for later steps — for example, to register the blob inside your application. Remove it later through the admin API with delete_blob, which cascades chunked blobs.

- type: upload_blob
node: calimero-node-1
file_path: workflow-examples/blob-test-files/sample.pdf
context_id: '{{context_id}}'
outputs:
pdf_blob_id: blob_id
pdf_blob_size: size
# …use {{pdf_blob_id}} in a call, then clean up:
- type: delete_blob
node: calimero-node-1
blob_id: '{{pdf_blob_id}}'

How do I capture a value from one step and reuse it?

Section titled “How do I capture a value from one step and reuse it?”

Add an outputs: block that maps a new variable name to a field in the step’s response, then reference it anywhere later as {{name}} (see variable substitution). This is how ids thread through a workflow — an applicationId becomes a namespace’s application_id, whose contextId becomes a call’s context_id.

- type: install_application
node: calimero-node-1
path: ./workflow-examples/res/kv_store.wasm
dev: true
outputs:
app_id: applicationId # store response.applicationId as {{app_id}}
- type: create_context
node: calimero-node-1
application_id: '{{app_id}}' # …and read it back here
group_id: '{{namespace_id}}'
outputs:
context_id: contextId
member_key: memberPublicKey

How do I keep nodes running after a workflow?

Section titled “How do I keep nodes running after a workflow?”

Leave stop_all_nodes at its default of false (or set it explicitly) and the executor calls keep_resources_on_exit() during teardown, so the containers stay up after bootstrap run returns — ready for inspection or a follow-up workflow. Set it to true to shut everything down at the end instead.

name: Leave nodes running
nodes:
count: 2
image: ghcr.io/calimero-network/merod:edge
steps:
- type: wait
seconds: 1
stop_all_nodes: false # default — nodes keep running after the run