fix: preserve command effect certainty across retries

Separate per-attempt physical state from the logical execution history. The
executor now keeps one monotonic certainty accumulator joined through
joinMutationEffectCertainty, records MAYBE_APPLIED at dispatch, and reads the
accumulator from every retry-loop fence, final-invariant, cancellation and
timeout return.

A retry-time scope fence landing between the loop-entry check and the
pre-dispatch invariant can no longer downgrade an already dispatched command to
NOT_STARTED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-13 23:01:48 +09:00
co-authored by Claude Opus 5
parent 4e87bacdf3
commit e06e4377ca
6 changed files with 158 additions and 62 deletions
@@ -48,6 +48,30 @@ export function certaintyForAbandonedAttempt(
}
}
/**
* §8.7. The conservative certainty lattice for one logical execution.
*
* `PhysicalAttemptState` describes only the attempt in flight. A new retry that
* has not been sent yet must never lower what an earlier attempt already
* established, so the executor joins observations into a monotonic accumulator.
*/
const CERTAINTY_RANK: Readonly<Record<MutationEffectCertainty, number>> =
Object.freeze({
NOT_STARTED: 0,
NOT_APPLIED: 1,
MAYBE_APPLIED: 2,
APPLIED_CONFIRMED: 3,
});
export function joinMutationEffectCertainty(
current: MutationEffectCertainty,
observed: MutationEffectCertainty,
): MutationEffectCertainty {
return CERTAINTY_RANK[observed] > CERTAINTY_RANK[current]
? observed
: current;
}
export type ProblemEffectInput<Problem> = Readonly<{
status: number;
problem: Problem;