@ -407,6 +407,7 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
const runId = generateRunId ( ) ;
const startedAt = timers . clock . now ( ) ;
const tokens = new Set < OrcaToken > ( ) ;
const tokenPayloads = new Map < OrcaToken , unknown > ( ) ;
const actionRuns : OrcaActionRun [ ] = [ ] ;
// Stack of actions that completed success AND declared a compensate.
// Pushed in completion order so reverse iteration is LIFO. The
@ -457,7 +458,8 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
entry . payload ,
envelope ,
runId ,
tokens
tokens ,
tokenPayloads
) ;
compensations . push ( compResult ) ;
entry . compensated = true ;
@ -484,9 +486,10 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
// wave, every action sees the same view; their emissions
// are merged into the run-level set after the wave settles.
const waveTokens : ReadonlySet < OrcaToken > = new Set ( tokens ) ;
const waveTokenPayloads : ReadonlyMap < OrcaToken , unknown > = new Map ( tokenPayloads ) ;
const waveItems = await Promise . all (
wave . map ( ( action ) = > runActionInWave ( action , stage , waveTokens , runId, envelope , controller ) )
wave . map ( ( action ) = > runActionInWave ( action , stage , waveTokens , waveTokenPayloads, runId, envelope , controller ) )
) ;
for ( const item of waveItems ) {
@ -504,6 +507,9 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
} ) ;
}
for ( const token of item . actionRun . emitted ) tokens . add ( token ) ;
if ( item . actionRun . emittedPayloads ) {
for ( const [ k , v ] of item . actionRun . emittedPayloads ) tokenPayloads . set ( k , v ) ;
}
}
// Detect transaction failure first: any tx member that
@ -535,7 +541,8 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
entry . payload ,
envelope ,
runId ,
tokens
tokens ,
tokenPayloads
) ;
compensations . push ( compResult ) ;
entry . compensated = true ;
@ -607,6 +614,7 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
endedAt ,
durationMs : endedAt - startedAt ,
tokens : Array.from ( tokens ) ,
tokenPayloads ,
actions : actionRuns ,
compensations
} ;
@ -636,6 +644,7 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
action : OrcaAction ,
stage : OrcaStage ,
waveTokens : ReadonlySet < OrcaToken > ,
waveTokenPayloads : ReadonlyMap < OrcaToken , unknown > ,
runId : OrcaRunId ,
envelope : OrcaEnvelope ,
runController : AbortController
@ -689,6 +698,7 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
envelope ,
action . stage ,
waveTokens ,
waveTokenPayloads ,
actionController . signal ,
action . id
) ;
@ -703,6 +713,7 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
envelope : OrcaEnvelope ,
stage : OrcaStage ,
tokens : ReadonlySet < OrcaToken > ,
tokenPayloads : ReadonlyMap < OrcaToken , unknown > ,
signal : AbortSignal ,
actionId : OrcaActionId
) : OrcaActionContext {
@ -715,6 +726,7 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
parentEventId : envelope.meta.parentEventId ,
depth : envelope.meta.depth ,
tokens ,
tokenPayloads ,
signal ,
logger : actionLogger ,
emit < TPayload > (
@ -772,7 +784,8 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
payload : unknown ,
envelope : OrcaEnvelope ,
runId : OrcaRunId ,
tokens : ReadonlySet < OrcaToken >
tokens : ReadonlySet < OrcaToken > ,
tokenPayloads : ReadonlyMap < OrcaToken , unknown >
) : Promise < OrcaActionRun > {
const startedAt = timers . clock . now ( ) ;
emitOrcaDiagnostic ( diagnostics , ORCA_DIAGNOSTIC_EVENTS . COMPENSATION_STARTED , {
@ -793,6 +806,7 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
parentEventId : envelope.meta.parentEventId ,
depth : envelope.meta.depth ,
tokens ,
tokenPayloads ,
signal : compController.signal ,
logger : actionLogger ,
emit() {
@ -932,11 +946,21 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
try {
const result = await runActionWithTimeout ( action , payload , context , actionController ) ;
const endedAt = timers . clock . now ( ) ;
const emitted = result . emits ? ? [ ] ;
// Tokens are NOT merged into the run-level set here. The wave
// coordinator merges `actionRun.emitted` after the surrounding
// wave settles, so parallel siblings never see each other's
// tokens mid-flight.
// Normalise the heterogeneous `emits` array into name-only and
// payload-keyed views. The wave coordinator merges these into
// the run-level state once the surrounding wave settles, so
// parallel siblings never see each other's emissions mid-flight.
const rawEmits = result . emits ? ? [ ] ;
const emittedNames : OrcaToken [ ] = [ ] ;
const emittedPayloads = new Map < OrcaToken , unknown > ( ) ;
for ( const e of rawEmits ) {
if ( typeof e === 'string' ) {
emittedNames . push ( e ) ;
} else {
emittedNames . push ( e . token ) ;
emittedPayloads . set ( e . token , e . payload ) ;
}
}
const status = mapResultToActionStatus ( result ) ;
@ -1004,7 +1028,8 @@ export function createEngineOrca(options: EngineOrcaOptions): EngineOrca {
startedAt ,
endedAt ,
durationMs : endedAt - startedAt ,
emitted : Array.from ( emitted ) ,
emitted : emittedNames ,
emittedPayloads : emittedPayloads.size > 0 ? emittedPayloads : undefined ,
error :
status === ORCA_ACTION_STATUS_ERROR ||
status === ORCA_ACTION_STATUS_FATAL