ErrorsBackground work · rule
step.late_failure
An action can fail for good after a call that changes something outside the app took effect, leaving that change behind.
Area 12, Background work; declinable; the next step is usually the agent's.
Why Jadpo refuses it
A changing call, such as a store's put or a provider operation with
changes: true, takes effect as soon as it is made. When the action
then fails for good, Jadpo rolls back the action's own writes, but it
can't take back what the call did. A retry fails the same way, so the
effect stays with nothing pointing at it.
A failure that ends an action for good is any point after the first
changing call that passes on a failure whose kind isn't unavailable or
rate_limited: an attempt whose call or write can fail that way, a
reject, or a propagate. Those two kinds are retried instead, and a
failure handled with match doesn't end the action.
What it looks like
domains/bakery/actions/keep_price_sheet.jadpo, in a bakery app that keeps
price sheets in sheets, a folder of files:
use app { sheets }use bakery/types { PriceList, ShopState }use bakery/failures { SheetNotKept, ShopClosed }/// Keeps the week's price sheet for a shop that is trading.action keep_price_sheet( price_list: PriceList, state: ShopState) fails SheetNotKept, ShopClosed -> File { var sheet: File = attempt sheets.put(encoding.to_utf8(price_list), FileType.csv) { storage_full: SheetNotKept } if (state == ShopState.closed) { reject ShopClosed } return sheet}
error keep_price_sheet can fail for good at reject ShopClosed, after sheets.put took effect
--> domains/bakery/actions/keep_price_sheet.jadpo:14:9
|10 | var sheet: File = attempt sheets.put(encoding.to_utf8(price_list), FileType.csv) { | ---------- takes effect first14 | reject ShopClosed | ^^^^^^^^^^^^^^^^^ can fail with `ShopClosed`
- reason
- Once a changing call has taken effect, a failure that ends
keep_price_sheetfor good rolls back its writes but not whatsheets.putdid, and a retry fails the same way; a failure of kindunavailableorrate_limitedis retried instead, and one handled withmatchdoesn't end it. - next
- Check the condition before
sheets.put, record what happened and finish in a subscriber of an eventkeep_price_sheetemits, or undosheets.putas another step in amatcharm, or writedecline step.late_failure: "<why>"above the action if this is deliberate (agent) - rule
- step.
late_ failure - help
- https://jadpo.
dev/docs/errors/step. late_ failure
(agent) at the end of the next: line means your coding agent can make
the fix itself.
How to fix it
If the condition can be checked before the call, move the check
first. In domains/bakery/actions/keep_price_sheet.jadpo:
if (state == ShopState.closed) { reject ShopClosed } var sheet: File = attempt sheets.put(encoding.to_utf8(price_list), FileType.csv) { ... }
A closed shop is now refused before anything is kept, so a refusal
leaves no sheet behind. A failure of sheets.put itself is the last
point, and nothing has taken effect before it.
If what is left behind does no harm, decline the finding above the action, with the reason:
/// Keeps the week's price sheet for a shop that is trading.decline step.late_failure: "A sheet kept for a closed shop does no harm, and nothing reads it"action keep_price_sheet( price_list: PriceList, state: ShopState) fails SheetNotKept, ShopClosed -> File { ...}
The action is unchanged, and the reason stays on it for whoever reads it next. One decline answers every such point in the action.
When the effect must not be left behind and the condition can only be
known afterwards, the next step names two more fixes: record what
happened and finish in a subscriber of an event the action emits, or
undo the call as another changing call in a match arm.
Related
Markdown for agents: /docs/errors/step.late_failure.md.Built from Jadpo main at aee2e8f.