Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Versioning & config plan — audit every deploy

Every run should be traceable to the exact config that produced it, and every config change should be reviewable before it goes live. rflow builds this in: it fingerprints your rflow.yaml on every boot, stamps that version onto every run, and gives you a semantic plan that flags dangerous changes (and the parked work they collide with) before a restart.

Config versions

On every rflow start, rflow canonicalizes the fully profile-merged config — deterministically, with every declared secret redacted — and sha256s it. If that hash differs from any stored version, it inserts a new row in rflow.config_versions with an id like 20260802143011-b7e2c9 (<UTC timestamp>-<short hash>). An unchanged config re-uses the existing version, so restarts never churn the history.

The version identity is over structure, not secrets: rotating a ${VAR}-provided key, a signer mnemonic (top-level or per-network), a channel token, a block-explorer api_keys entry, an http_call header or the API bearer never mints a new version, but any real change — a step, a trigger, a cap — does. Before hashing, a declared secret field is masked to <redacted> and a value interpolated from ${VAR} (an Infura key inside an RPC url, a Bearer header, a command arg) is restored to that ${VAR} placeholder — so the skeleton (host, path, arg positions) survives while the secret does not. No secret value ever lands in rflow.config_versions or its hash.

Every run created thereafter is stamped with the current config_version alongside its per-workflow config_hash (the resume fence). The two agree: both are copied from the workflow mirror the same boot wrote. You can see the version anywhere runs appear:

rflow runs show <id>     # version   20260802143011-b7e2c9  +  config sha256:…
rflow ls                 # a `version` column per workflow
rflow versions ls                 # id, hash, created, run count
rflow versions show <id>          # summary + per-workflow definitions
rflow versions diff <a> <b>       # the semantic delta between two versions

ls and diff accept --json for automation.

rflow plan — the deploy gate

rflow plan answers "is it safe to restart with this change?". It computes the semantic diff from a baseline (the newest stored version by default, or --from-version <id>) to a target (./rflow.yaml, or --to <path>), then overlays active runtime conflicts from the journal.

rflow plan                                   # newest version -> ./rflow.yaml
rflow plan --from-version <id> --to ./next.yaml --profile prod
rflow plan --json                            # machine-readable (CI)

Every finding carries a severity:

severitymeaning
errorcannot apply safely — rflow plan exits non-zero (the CI gate)
warningcan apply, but review it
infoa normal change

What the diff understands

It reasons over the typed config, not text:

  • workflows added / removed / renamed (a body-identical rename is detected, and flagged because run history stays under the old name)
  • triggers: a changed kind, a start_block change (backfill vs skip history), lowered confirmations (reorg exposure), a run_on change
  • steps — especially money-moving send_transaction (in the main chain, on_reorg compensation, or finally): a send added, removed, or changed (relayer / network / target / function / args / raw data calldata / value / multicall / gas policy), plus a weakened guardsimulate disabled, or assert_sim / recheck removed
  • approval policy: an approval gate removed, or on_timeout weakened to proceed (broadcasts with nobody approving) — both are warnings
  • relayer / signer changes (a secret rotation is not a change)
  • budgets & rate limits: a raised or removed cap is a weakened guard
  • contract address / ABI changes, and profile overlay diffs

Runtime conflicts

A change is not safe in a vacuum. If a workflow being removed or changed has parked or in-flight work — a waiting approval, a parked delay, a wait_for: park, or an in-flight send — plan raises it:

  • removed with parked work → error (that work would be stranded)
  • changed with parked work → warning
error  treasury-sweep  runtime-conflict-removed  workflow 'treasury-sweep' is
       being removed but has 1 parked/in-flight run(s) — 1 waiting approval(s)

Wire rflow plan into CI before a deploy: a non-zero exit blocks the merge until the error-severity items (or the parked work they name) are resolved.

rflow rollback

rflow rollback <version-id> reconstructs a stored version back into an rflow.yaml:

rflow rollback 20260802143011-b7e2c9                 # print to stdout (default)
rflow rollback 20260802143011-b7e2c9 --output ./rflow.yaml --yes

Writing to a file backs up the current one first (rflow.yaml.bak).

Honesty about redaction: the stored snapshot is redacted, so rollback restores the committed structure with secret fields as <redacted> and ${VAR}-interpolated values back as their placeholder. It is not a secrets backup — real values still come from your .env/${VAR} exactly as before. Rollback restores shape; fill the placeholders from your environment.

Non-goals

rflow versioning is not a replacement for Git, and plan does not auto-guess a safe migration for every trigger change — it reports dangerous changes so a human (or CI) decides. Git tracks your text; rflow understands its own runtime semantics.