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.
rflow versions— the deployed-config historyrflow plan— the pre-deploy diff + safety gaterflow rollback— reconstruct an old version to disk
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 workflowrflow 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 versionsls 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:
| severity | meaning |
|---|---|
error | cannot apply safely — rflow plan exits non-zero (the CI gate) |
warning | can apply, but review it |
info | a 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_blockchange (backfill vs skip history), lowered confirmations (reorg exposure), arun_onchange - steps — especially money-moving
send_transaction(in the main chain,on_reorgcompensation, orfinally): a send added, removed, or changed (relayer / network / target / function / args / rawdatacalldata / value / multicall / gas policy), plus a weakened guard —simulatedisabled, orassert_sim/recheckremoved - approval policy: an approval gate removed, or
on_timeoutweakened toproceed(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 --yesWriting 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.