1313import io .temporal .api .workflowservice .v1 .SignalWithStartWorkflowExecutionResponse ;
1414import io .temporal .api .workflowservice .v1 .SignalWorkflowExecutionRequest ;
1515import io .temporal .api .workflowservice .v1 .SignalWorkflowExecutionResponse ;
16+ import io .temporal .api .workflowservice .v1 .StartWorkflowExecutionRequest ;
17+ import io .temporal .api .workflowservice .v1 .StartWorkflowExecutionResponse ;
1618import io .temporal .client .WorkflowClient ;
1719import io .temporal .client .WorkflowClientOptions ;
1820import io .temporal .client .WorkflowOptions ;
@@ -232,6 +234,51 @@ public void mixedSignalAndSignalWithStartAccumulateAllBacklinks() {
232234 nexusCtx .getBacklinks ());
233235 }
234236
237+ /**
238+ * Flag-enabled server: the start response carries a backlink; the SDK captures it verbatim onto
239+ * the operation context (used by the WorkflowRunOperation async path).
240+ */
241+ @ Test
242+ public void startUsesServerStartLinkWhenPresent () {
243+ Link serverLink =
244+ workflowEventLink (
245+ WORKFLOW_ID , "target-run" , EventType .EVENT_TYPE_WORKFLOW_EXECUTION_STARTED );
246+ when (genericClient .start (any (StartWorkflowExecutionRequest .class )))
247+ .thenReturn (
248+ StartWorkflowExecutionResponse .newBuilder ()
249+ .setRunId ("target-run" )
250+ .setLink (serverLink )
251+ .build ());
252+
253+ invoker .start (newStartInput ());
254+
255+ List <Link > captured = nexusCtx .getBacklinks ();
256+ Assert .assertEquals ("expected the server-provided start link" , 1 , captured .size ());
257+ Assert .assertEquals (serverLink , captured .get (0 ));
258+ }
259+
260+ /**
261+ * Older server (pre-1.31): the start response has no link. The SDK must fabricate a backlink
262+ * pointing at the started workflow's WorkflowExecutionStarted event so the caller still links to
263+ * the callee.
264+ */
265+ @ Test
266+ public void startFabricatesStartLinkWhenServerOmitsIt () {
267+ when (genericClient .start (any (StartWorkflowExecutionRequest .class )))
268+ .thenReturn (StartWorkflowExecutionResponse .newBuilder ().setRunId ("target-run" ).build ());
269+
270+ invoker .start (newStartInput ());
271+
272+ List <Link > captured = nexusCtx .getBacklinks ();
273+ Assert .assertEquals ("expected one fabricated backlink" , 1 , captured .size ());
274+ Link .WorkflowEvent we = captured .get (0 ).getWorkflowEvent ();
275+ Assert .assertEquals (NAMESPACE , we .getNamespace ());
276+ Assert .assertEquals (WORKFLOW_ID , we .getWorkflowId ());
277+ Assert .assertEquals ("target-run" , we .getRunId ());
278+ Assert .assertEquals (
279+ EventType .EVENT_TYPE_WORKFLOW_EXECUTION_STARTED , we .getEventRef ().getEventType ());
280+ }
281+
235282 // ── helpers ──────────────────────────────────────────────────────────────────────────────
236283
237284 private static WorkflowSignalInput newSignalInput () {
@@ -251,6 +298,14 @@ private static WorkflowSignalWithStartInput newSignalWithStartInput() {
251298 startInput , "test-signal" , new Object [] {"signal-payload" });
252299 }
253300
301+ private static WorkflowStartInput newStartInput () {
302+ // Disable eager execution so start() takes the plain RPC path (no local worker dispatch).
303+ WorkflowOptions options =
304+ WorkflowOptions .newBuilder ().setTaskQueue ("tq" ).setDisableEagerExecution (true ).build ();
305+ return new WorkflowStartInput (
306+ WORKFLOW_ID , "TestWorkflow" , Header .empty (), new Object [] {}, options );
307+ }
308+
254309 private static Link workflowEventLink (String workflowId , String runId , EventType eventType ) {
255310 return Link .newBuilder ()
256311 .setWorkflowEvent (
0 commit comments