← Community
bugfixed

Workflow receipt seals compensated:True on any rollback, even when a committed external_send effect was never reversed (v0.99)

ShwetaShweta#112d ago · 37 views
affected: station-v0.99fixed in: station-v1.4.0

Reproduction steps:

  1. Build a workflow that fires an irreversible effect and then fails on a later

node:
node 1: an effect with action_class "external_send" (an SMS/message — by the
registry's own definition, "irreversible: no compensator"), which
commits;
node 2: any node that fails (e.g. an http node with no allow_domains policy
raises HttpPolicyError), triggering the saga rollback.

  1. Inspect the signed workflow receipt.
  2. Run repro_receipt_compensated_true_while_effect_stands_v099.py against a clean

v0.99 extraction. Output:
trace: [('effect','sms'), ('http','boom'), ('retry','boom'), ('rollback',None)]
outcome=ROLLED_BACK ; receipt.compensated=True ; SMS committed=True
SMS node action_class=external_send

Expected:
compensated: true on a receipt should mean the committed effects were actually
reversed. An external_send effect has no compensator (it cannot be reversed), and
a compensator can fail its retries (the saga records it as an "orphan fault"), so
those effects STAND after the rollback. The receipt must not claim they were
compensated.

Actual:
run_workflow derives the flag purely from the outcome (workflow_engine.py ~1091):

"compensated": (outcome == "ROLLED_BACK")

and outcome is set to "ROLLED_BACK" on ANY node exception (~1078), regardless of
whether compensation reversed anything. The saga's compensation loop correctly
SKIPS a no-compensator step (if comp is None: continue) and records a FAILED
compensator as an orphan — but neither feeds back into compensated. So a run
that sent an SMS (external_send, un-reversible) and then failed seals
compensated: True; an auditor reading the receipt sees a fully compensated run
while a real irreversible outbound action was delivered and never reversed. The
same overstatement occurs whenever a compensator fails after its retries (the
effect stands as an orphan) — compensated still reads True.

Distinctness: this is broader than, and different from, the persist-failure saga
finding (which needs a DB persist failure to SKIP compensation). Here the saga
works perfectly and correctly cannot reverse an external_send — no failure of any
kind is required; every workflow that mixes an external_send (or reversible+later
failure with a flaky compensator) with a subsequent error mislabels its receipt.

Suggested fix:
Derive compensated from the saga's ACTUAL result, not the outcome. The saga
already knows: it skipped no-compensator steps and recorded orphans for failed
ones. Report compensated: true ONLY when every committed step has
compensation_status == "ok" and there were no orphans AND no committed
external_send (no-compensator) step; otherwise report compensated: "partial" (or
false) and list the effects that still stand (labels from the saga's orphan /
skipped set). A signed receipt must not assert a clean rollback over an
un-reversed irreversible effect.

3 pts

1 reply

Fixed in station-v1.4.0. compensated is now derived from the saga's ACTUAL per-step result, not the outcome: True only when every committed step was reversed; "partial" (with a new uncompensated_effects list) when a no-compensator external_send or a failed/orphaned compensator stands. An SMS that was delivered and couldn't be reversed no longer reads compensated:true.

Thanks for the report — credited.

Sign in to reply.