Skip to content

Types & Safety Semantics

The isPure, isIdempotent and reversible flags on function blocks are the foundation of the engine’s transaction model. They determine how the engine handles failures, retries, and rollback.

Safety Properties

Property Meaning Example
isPure: true No side effects Collecting show version, reading facts, declaring CMS updates
isIdempotent: true Same input → same result, safe to re-run Writing a complete config (not appending), setting a description
Both false Side effects that may not be safely repeated Incrementing a counter, appending to a log, rebooting a device
reversible: true A rollback() undoes the block’s side effects Restoring a configuration snapshot

Pure

A pure function block has no side effects: it changes nothing on the device or in any external system. If a pure function block fails, nothing has changed – the world is exactly as it was before.

Declared CMS changes do not count as side effects. A block that returns dbUpdates only declares them; the engine, not the block, writes them to the CMS, so a block whose only effect is its dbUpdates is pure.

The engine uses this to classify failures: if a workflow only ran pure steps before failing, the failure is FAILED_SAFE. A pure step is never rolled back.

Idempotent

An idempotent function block can be safely executed multiple times with the same input. The result is the same whether it runs once or ten times. This is a weaker guarantee than pure (the function block does have side effects), but the side effects are predictable and repeatable.

A failed job of a pure or idempotent function block is retried automatically, see Automatic Retry.

Side effects of one attempt

The flags describe the function block. A single attempt can say more: its result may report sideEffects: false, on a success or a failure, when that attempt changed nothing. A step whose every attempt said so is left out of the rollback, and a failure that says so is retried even for a block that is neither pure nor idempotent. A pure block cannot report sideEffects: true: the Worker SDK turns such a result into a failure, and the engine stores it as an invalid_result failure. Details in Steps that changed nothing.

Transaction Classification

The engine classifies a failure from the jobs that actually reached a worker, not from run-level flags:

The failed run executed… On failure →
Only pure steps, or non-pure steps whose every attempt reported sideEffects: false FAILED_SAFE
Other non-pure steps, all reversible ROLLBACK, then FAILED_SAFE or FAILED_UNSAFE
Other non-pure steps, at least one irreversible FAILED_UNSAFE

Automatic retries happen before this: a failed job of a pure or idempotent block, or a failure that reported sideEffects: false, is retried up to three times before its step fails.

isPureExecution and isIdempotentExecution decide nothing

The engine tracks two run-level flags while it executes: isPureExecution (every step was pure) and isIdempotentExecution (every step was pure or idempotent). It logs them, but neither drives the classification or the retries above.

The flags are set by the function block developer

The engine trusts the isPure, isIdempotent and reversible flags declared during registration, and the sideEffects an attempt reports. If a function block claims to be pure, or an attempt claims it changed nothing, but it did modify the device, the engine will incorrectly classify failures as FAILED_SAFE. Accurate declarations are essential for the transaction model to work correctly. isPure and reversible are frozen per exact version, see Registration.

Automatic Retry

A failed job of a function block registered as isPure or isIdempotent, or a failure that reported sideEffects: false, is retried up to three times; a worker can opt a deterministic failure out with retryable: false. Details in Retry & Rollback. The step-level retryConfig is not consumed yet.

Best Practices for Workflow Authors

Know your function blocks: : Check the isPure and isIdempotent flags of the function blocks you use. This tells you what the engine will do if something goes wrong.

Order for safety: : Put pure steps (data collection, validation) before configuration steps. If the workflow fails during the pure phase, it is automatically FAILED_SAFE.

Design idempotent configs: : When writing configuration function blocks, prefer declarative (replace entire config section) over imperative (append line). Declarative configs are naturally idempotent.

Use FAILED_SAFE as a signal: : If your workflows frequently end as FAILED_SAFE, that means your pure steps, or checks that ran before anything was sent, are failing (likely connectivity issues) but nothing harmful happened. Investigate the root cause but don’t panic.


See also: