@@ -1060,8 +1060,8 @@ describe('workflowEntrypoint replay guards', () => {
10601060 ) . toEqual ( [ ] ) ;
10611061 } ) ;
10621062
1063- it ( 'replays attribute events before executing a step that loses the same race ' , async ( ) => {
1064- const debug = vi
1063+ it ( 'retains when an attribute write beats a step that signals first ' , async ( ) => {
1064+ using debug = vi
10651065 . spyOn ( runtimeLogger , 'debug' )
10661066 . mockImplementation ( ( ) => undefined ) ;
10671067 const ops : Promise < any > [ ] = [ ] ;
@@ -1080,14 +1080,17 @@ describe('workflowEntrypoint replay guards', () => {
10801080 startedAt : new Date ( '2024-01-01T00:00:00.000Z' ) ,
10811081 deploymentId : 'test-deployment' ,
10821082 } ;
1083+ // Register the step consumer first so it schedules the accepted
1084+ // suspension. The later attribute signal must be dropped by its new
1085+ // generation guard after the retained session resumes.
10831086 const workflowCode = `
10841087 const setAttributes = globalThis[Symbol.for("WORKFLOW_SET_ATTRIBUTES")];
10851088 const useStep = globalThis[Symbol.for("WORKFLOW_USE_STEP")];
10861089 const slowStep = useStep("slowStep");
10871090 async function workflow() {
10881091 await Promise.race([
1089- setAttributes([{ key: "winner", value: "attribute" }]),
10901092 slowStep(),
1093+ setAttributes([{ key: "winner", value: "attribute" }]),
10911094 ]);
10921095 return "attribute won";
10931096 }${ getWorkflowTransformCode ( 'workflow' ) } ` ;
@@ -1103,19 +1106,17 @@ describe('workflowEntrypoint replay guards', () => {
11031106 expect ( createdEvents ) . toContainEqual (
11041107 expect . objectContaining ( { eventType : 'attr_set' } )
11051108 ) ;
1106- // The attr_set suspension skips step processing and resolves through an
1107- // in-process replay, where the durable attribute event wins the race —
1108- // so the run completes within this same delivery, with no queue
1109- // interaction.
1109+ // The durable attribute event wins the retained resume. The later
1110+ // attribute suspension signal must not demote the session.
11101111 expect ( createdEvents ) . toContainEqual (
11111112 expect . objectContaining ( { eventType : 'run_completed' } )
11121113 ) ;
11131114 expect ( queueCalls ) . toEqual ( [ ] ) ;
11141115 // Under lazy inline start the step that loses the attribute race is NOT
11151116 // eagerly created: its step_created is deferred for a lazy step_started
1116- // that never fires, because the attribute-resolving replay decides the
1117- // race before any step executes. So the losing step leaves no events at
1118- // all — strictly less event-log garbage than the eager model.
1117+ // that never fires, because the attribute-resolving retained execution
1118+ // decides the race before any step executes. So the losing step leaves no
1119+ // events at all — strictly less event-log garbage than the eager model.
11191120 expect ( createdEvents ) . not . toContainEqual (
11201121 expect . objectContaining ( { eventType : 'step_created' } )
11211122 ) ;
@@ -1125,8 +1126,7 @@ describe('workflowEntrypoint replay guards', () => {
11251126 const executionModes = debug . mock . calls
11261127 . filter ( ( [ message ] ) => message === 'Starting workflow execution' )
11271128 . map ( ( [ , context ] ) => context ?. executionMode ) ;
1128- expect ( executionModes ) . toEqual ( [ 'replay' , 'replay' ] ) ;
1129- debug . mockRestore ( ) ;
1129+ expect ( executionModes ) . toEqual ( [ 'replay' , 'retained' ] ) ;
11301130 } ) ;
11311131
11321132 it ( 'fails the run when the World rejects an attr_set event as invalid' , async ( ) => {
@@ -3088,11 +3088,11 @@ describe('workflowEntrypoint latency telemetry (ttfs / stso)', () => {
30883088 }${ latXform ( 'workflow' ) } ` ;
30893089
30903090 it ( 'reports ttfs across a pre-step setAttributes, resolved in-process without a re-invoke' , async ( ) => {
3091- // A workflow-body setAttributes before the first step resolves through
3092- // an in-process replay: the same delivery commits attr_set, replays , and
3093- // runs the step — no queue interaction. TTFS ends at the attr write, so
3094- // the setAttributes call's duration is subtracted; here that detour is
3095- // milliseconds, keeping ttfs ≈ the run's ULID backdate.
3091+ // A workflow-body setAttributes before the first step resolves in-process:
3092+ // the same delivery commits attr_set, reloads it , and advances the workflow
3093+ // by resuming or replaying — no queue interaction. TTFS ends at the attr
3094+ // write, so the setAttributes call's duration is subtracted. That detour is
3095+ // only milliseconds here , keeping ttfs ≈ the run's ULID backdate.
30963096 const backdateMs = 10_000 ;
30973097 const before = Date . now ( ) ;
30983098 const first = await driveLatency ( {
@@ -3115,11 +3115,13 @@ describe('workflowEntrypoint latency telemetry (ttfs / stso)', () => {
31153115 expect ( ttfs ) . toBeLessThanOrEqual ( backdateMs + elapsed ) ;
31163116 expect ( stso ) . toBeUndefined ( ) ;
31173117 // The attr detour does not end turbo: no resume invocation source
3118- // exists, so the forced-optimistic fast path stays engaged.
3118+ // exists, so the forced-optimistic fast path stays engaged. The live VM
3119+ // also resumes after the attr write instead of replaying in a fresh VM.
31193120 expect ( optimizations ) . toEqual ( [
31203121 'turbo' ,
31213122 'lazyStepStart' ,
31223123 'optimisticStart' ,
3124+ 'retained' ,
31233125 ] ) ;
31243126 } ) ;
31253127
0 commit comments