COMMITTED, the remote did it; FAILED, the remote definitely did not;
and AMBIGUOUS, nobody knows. A timeout is not a failure. A lost reply is not a failure. An
exception nobody expected is not a failure. All three are AMBIGUOUS, and an AMBIGUOUS effect
blocks a blind retry.
This is the idea that explains the product. Every framework has two outcomes, success and
error, and retries on error. The real world has a third, and it is where double execution lives.
The asymmetry
NotExecuted is the one exception that makes an effect retryable, which makes it the most
dangerous line in an integration: an executor that raises it after the remote acted has turned
the guarantee into a licence to act twice. ctrlrun verify cannot check that for you. Raise it
only where the remote told you, in band, that it rejected the request before doing anything.
What the lost reply looks like
runnable
Who moves it on
Exactly two things move a record out ofAMBIGUOUS, and both have to say which way:
- A human, with
ctrlrun resolve refund:txn_1 --committedor--failed, after asking the remote. The effect record keepscli:localinresolved_by, and anEFFECT_RESOLVEDevent carries the same string. No receipt changes: a receipt is written when the action ends, a resolution happens afterwards, and editing one would be an alteration the chain reports. - A reconcile hook,
@protect(..., reconcile=...), a function that asks the remote what happened to an effect key and answers committed, not executed, or unknown. It moves the record only in the direction its answer points, and an answer of unknown leaves it where it was.
COMMIT is AMBIGUOUS until the store re-reads the row.
The guarantee it supports
Unknown is not failed: G5 (ambiguous blocks a blind retry) and G10 (unknown exception is ambiguous) inctrlrun verify.
What it does not do
CTRLRun cannot find out what the remote did. It refuses to guess, and it makes the question impossible to skip. It also cannot tell that an executor lied withNotExecuted; that is the
integration bug the threat model names as the most dangerous one available.