resume

suspend fun <C, R, S : Any> StatefulSagaExecutor<C, R, S>.resume(runId: RunId, context: C): ResumeOutcome<R, S>

Resumes a stateful saga from its journal using the executor's pre-wired journal configuration.

M9 fix — F003 spec-conformant 2-arg API

The F003 spec declares suspend fun StatefulSagaExecutor<C, R, S>.resume(runId, context). The previous implementation required a 7-arg overload because journal, reducer, terminalDecoder, and terminalEncoder are typed with the payload type P which is not a type parameter of StatefulSagaExecutor.

This implementation delegates to an opaque closure (StatefulSagaExecutorImpl.internalResumeFn) wired by the sagaExecutor(journal, ...) factory. The closure captures P at construction time, so the public API signature remains clean.

For executors built without a journal (no internalResumeFn), this method throws UnsupportedOperationException with a clear message.

Return

ResumeOutcome discriminated by journal state.

Parameters

runId

The run identifier to look up.

context

The saga context (forwarded to any continuation path).


suspend fun <C, R, S : Any, P> StatefulSagaExecutor<C, R, S>.resume(runId: RunId, context: C, journal: SagaJournal<P>): ResumeOutcome<R, S>

Resumes a stateful saga from its journal.

This extension is the entry point for F003 saga resume & replay.

Behaviour (T014 scaffold — foundation only)

Journal stateOutcome
No entries for runIdResumeOutcome.NotFound
Seq gap in entriesResumeOutcome.CorruptJournal
Journal read errorResumeOutcome.CorruptJournal
Entries present (all other cases)throw NotImplementedError until T015–T019

The NotImplementedError paths will be replaced by T015 (Terminal short-circuit), T016 (state replay), T017 (effect-key short-circuit), T018/T019 (compensation resume).

Usage

val journal = InMemorySagaJournal<OrderEvent>()
val executor = sagaExecutor<OrderCtx, OrderResult, OrderState, OrderEvent>(
initialState = OrderState.fresh(),
journal = journal,
) {
step("reserve") { completes with "reserved" }
}

when (val outcome = executor.resume(runId, ctx, journal)) {
is ResumeOutcome.Resumed -> process(outcome.result)
is ResumeOutcome.NotFound -> startFresh()
is ResumeOutcome.CorruptJournal -> quarantine(outcome.reason)
is ResumeOutcome.Indeterminate -> alert(outcome.reason)
}

Return

ResumeOutcome discriminated by journal state.

Parameters

runId

The run identifier to look up.

context

The saga context (used in T015+ to continue execution).

journal

The journal to read from.


suspend fun <C, R, P> SagaExecutor<C, R>.resume(runId: RunId, context: C, journal: SagaJournal<P>): ResumeOutcome<R, Unit>

Resumes a stateless saga from its journal.

State-less variant of StatefulSagaExecutor.resume. Since SagaExecutor carries no saga state (S = Unit), ResumeOutcome.Resumed.state is always Unit and StateReducer is not needed.

Behaviour (T014 scaffold — foundation only)

Journal stateOutcome
No entries for runIdResumeOutcome.NotFound
Seq gap in entriesResumeOutcome.CorruptJournal
Journal read errorResumeOutcome.CorruptJournal
Entries present (all other cases)throw NotImplementedError until T015–T019

Return

ResumeOutcome with S = Unit.

Parameters

runId

The run identifier to look up.

context

The saga context.

journal

The journal to read from.


suspend fun <C, R, S : Any, P> StatefulSagaExecutor<C, R, S>.resume(runId: RunId, context: C, journal: SagaJournal<P>, initialState: S, reducer: StateReducer<S, P>, terminalDecoder: TerminalDecoder<P, R>, terminalEncoder: TerminalEncoder<P>? = null): ResumeOutcome<R, S>

Resumes a stateful saga from its journal when state reconstruction and Terminal decoding are required.

Behaviour (T015)

Journal stateOutcome
No entries for runIdResumeOutcome.NotFound
Seq gap in entriesResumeOutcome.CorruptJournal
Journal read errorResumeOutcome.CorruptJournal
StateReducer throwsResumeOutcome.CorruptJournal with reason = "reducer threw: <msg>"
Last entry is Terminal + decoder succeeds + outcome is Committed/AbortedResumeOutcome.Resumed
Last entry is Terminal + decoded outcome is IndeterminateResumeOutcome.Indeterminate (normalization rule — T017 depends on this)
Terminal payload cannot be decoded (decoder returns null or throws)ResumeOutcome.CorruptJournal at terminal entry seq
Entries present without Terminal (all other cases)throw NotImplementedError until T016–T019

Two-pass algorithm

  1. State pass: fold reducer over ALL entries (including non-Effect phases — the reducer contract requires handling every ca.acendas.kstate.saga.journal.EntryPhase).

  2. Terminal pass: if the last entry is ca.acendas.kstate.saga.journal.EntryPhase.Terminal, decode its payload via terminalDecoder to extract (result: R, TerminalOutcome).

Indeterminate normalization (load-bearing for T017)

When the decoded Terminal payload's outcome is TerminalOutcome.Indeterminate, this function returns ResumeOutcome.Indeterminate — NOT Resumed(terminal = Indeterminate). This ensures that the second resume() call on an Indeterminate run returns the same stable shape as the first resume produced.

No-mutation guarantee

This path never writes to the journal. Idempotency is free: calling resume() twice on a Terminal-present journal produces the same outcome.

Intent-without-effectKey (T017)

When the last entry has phase ca.acendas.kstate.saga.journal.EntryPhase.Intent AND its metadata["effect-key"] is absent, the outcome of the side effect cannot be determined. This function:

  1. Replays state via the reducer fold over all entries.

  2. If terminalEncoder is provided, appends a Terminal(seq=last+1, payload=encoded(Indeterminate)) entry to the journal so that subsequent resumes hit T015's Terminal-short-circuit path and return the same stable ResumeOutcome.Indeterminate shape (idempotent re-resume). If terminalEncoder is null, the journal is left unchanged (pure-read mode).

  3. Returns ResumeOutcome.Indeterminate with reason = "no-effect-key".

Return

ResumeOutcome discriminated by journal state and Terminal payload.

Parameters

runId

The run identifier to look up.

context

The saga context (forwarded to T016+ continuation — unused on this path).

journal

The journal to read from.

initialState

Starting state for the StateReducer fold.

reducer

User-supplied fold function — determines the reconstructed S.

terminalDecoder

Decodes the Terminal entry's payload into (R, TerminalOutcome).

terminalEncoder

Optional encoder for writing a synthetic Terminal entry on Indeterminate detection. When null, the journal is not written (pure-read).