From 7abc3296da2c9856580a1f3a5c7167c5c779088e Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Thu, 22 Oct 2026 09:43:55 -0700 Subject: [PATCH] Added StartNexusOperation for direct Nexus calls, as api#720. --- openapi/openapiv2.json | 280 +++++++++++++++--- openapi/openapiv3.yaml | 174 +++++++++++ .../workflowservice/v1/request_response.proto | 49 +++ temporal/api/workflowservice/v1/service.proto | 16 + 4 files changed, 483 insertions(+), 36 deletions(-) diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 830cf957d..38c0e8d41 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -2026,6 +2026,67 @@ ] } }, + "/api/v1/namespaces/{namespace}/nexus-requests/endpoints/{endpoint}/services/{service}/operations/{operation}": { + "post": { + "summary": "StartNexusOperation starts a Nexus operation on an endpoint, communicating directly with the\nendpoint using the caller's namespace credentials. No caller-side state is created.\nThe response follows the Nexus RPC spec semantics: synchronous completion, asynchronous start,\nor operation error.\nHandler errors (e.g., NOT_FOUND, BAD_REQUEST) are returned as gRPC errors.", + "operationId": "StartNexusOperation2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1StartNexusOperationResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "The namespace to authenticate to. This may be different from the target namespace of the specified endpoint.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "endpoint", + "description": "Endpoint name, resolved via the Temporal service's endpoint registry.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "service", + "description": "Nexus service name.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "operation", + "description": "Nexus operation name.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceStartNexusOperationBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, "/api/v1/namespaces/{namespace}/schedule-count": { "get": { "summary": "CountSchedules is a visibility API to count schedules in a specific namespace.", @@ -7679,6 +7740,67 @@ ] } }, + "/namespaces/{namespace}/nexus-requests/endpoints/{endpoint}/services/{service}/operations/{operation}": { + "post": { + "summary": "StartNexusOperation starts a Nexus operation on an endpoint, communicating directly with the\nendpoint using the caller's namespace credentials. No caller-side state is created.\nThe response follows the Nexus RPC spec semantics: synchronous completion, asynchronous start,\nor operation error.\nHandler errors (e.g., NOT_FOUND, BAD_REQUEST) are returned as gRPC errors.", + "operationId": "StartNexusOperation", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1StartNexusOperationResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "namespace", + "description": "The namespace to authenticate to. This may be different from the target namespace of the specified endpoint.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "endpoint", + "description": "Endpoint name, resolved via the Temporal service's endpoint registry.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "service", + "description": "Nexus service name.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "operation", + "description": "Nexus operation name.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/WorkflowServiceStartNexusOperationBody" + } + } + ], + "tags": [ + "WorkflowService" + ] + } + }, "/namespaces/{namespace}/schedule-count": { "get": { "summary": "CountSchedules is a visibility API to count schedules in a specific namespace.", @@ -11332,7 +11454,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links to be associated with the WorkflowExecutionSignaled event." } @@ -11373,6 +11495,33 @@ "default": "RUNTIME_TYPE_UNSPECIFIED", "description": " - RUNTIME_TYPE_UNSPECIFIED: Should never actually be set, exists to follow convention of having a default.\nSDKs should just leave `runtimes` empty if none can be determined.\n - RUNTIME_TYPE_GRAAL_AOT: Report when the application is compiled ahead-of-time as a GraalVM native image.\n - RUNTIME_TYPE_KOTLIN: Report when any registered Workflow or Activity implementation is a Kotlin class." }, + "StartNexusOperationResponseAsyncSuccess": { + "type": "object", + "properties": { + "operationToken": { + "type": "string" + } + }, + "description": "An operation was started asynchronously. The token can be used for cancellation." + }, + "StartNexusOperationResponseOperationFailure": { + "type": "object", + "properties": { + "failure": { + "$ref": "#/definitions/v1Failure" + } + }, + "description": "The operation completed unsuccessfully.\nThe operation is considered canceled if the failure has a CanceledFailureInfo object attached." + }, + "StartNexusOperationResponseSyncSuccess": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/v1Payload" + } + }, + "description": "An operation completed successfully and synchronously." + }, "UpdateTaskQueueConfigRequestRateLimitUpdate": { "type": "object", "properties": { @@ -12717,6 +12866,31 @@ } } }, + "WorkflowServiceStartNexusOperationBody": { + "type": "object", + "properties": { + "identity": { + "type": "string", + "description": "The identity of the client who initiated this request." + }, + "requestId": { + "type": "string", + "description": "A unique identifier for this request, used as an idempotency key." + }, + "input": { + "$ref": "#/definitions/v1Payload", + "description": "Serialized input to the operation." + }, + "links": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiCommonV1Link" + }, + "description": "Links for associating the caller with the operation." + } + } + }, "WorkflowServiceStartNexusOperationExecutionBody": { "type": "object", "properties": { @@ -13576,6 +13750,30 @@ }, "description": "Common callback information. Specific CallbackInfo messages should embed this and may include additional fields." }, + "apiCommonV1Link": { + "type": "object", + "properties": { + "workflowEvent": { + "$ref": "#/definitions/LinkWorkflowEvent" + }, + "batchJob": { + "$ref": "#/definitions/LinkBatchJob" + }, + "activity": { + "$ref": "#/definitions/LinkActivity" + }, + "nexusOperation": { + "$ref": "#/definitions/LinkNexusOperation" + }, + "workflow": { + "$ref": "#/definitions/LinkWorkflow" + }, + "callback": { + "$ref": "#/definitions/v1LinkCallback" + } + }, + "description": "Link can be associated with history events. It might contain information about an external entity\nrelated to the history event. For example, workflow A makes a Nexus call that starts workflow B:\nin this case, a history event in workflow A could contain a Link to the workflow started event in\nworkflow B, and vice-versa." + }, "apiCommonV1OnConflictOptions": { "type": "object", "properties": { @@ -13594,6 +13792,18 @@ }, "description": "When starting an execution with a conflict policy that uses an existing execution and there is already an existing\nrunning execution, OnConflictOptions defines actions to be taken on the existing running execution." }, + "apiNexusV1Link": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "See https://github.com/nexus-rpc/api/blob/main/SPEC.md#links." + }, + "type": { + "type": "string" + } + } + }, "apiNexusoperationV1CallbackInfo": { "type": "object", "properties": { @@ -13728,7 +13938,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links associated with the callback. It can be used to link to underlying resources of the\ncallback." } @@ -13905,7 +14115,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links to related entities, such as the entity that started this activity." }, @@ -16650,7 +16860,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links to related entities, such as the entity that started this event's workflow." }, @@ -16920,30 +17130,6 @@ }, "description": "IntervalSpec matches times that can be expressed as:\nepoch + n * interval + phase\nwhere n is an integer.\nphase defaults to zero if missing. interval is required.\nBoth interval and phase must be non-negative and are truncated to the nearest\nsecond before any calculations.\nFor example, an interval of 1 hour with phase of zero would match every hour,\non the hour. The same interval but a phase of 19 minutes would match every\nxx:19:00. An interval of 28 days with phase zero would match\n2022-02-17T00:00:00Z (among other times). The same interval with a phase of 3\ndays, 5 hours, and 23 minutes would match 2022-02-20T05:23:00Z instead." }, - "v1Link": { - "type": "object", - "properties": { - "workflowEvent": { - "$ref": "#/definitions/LinkWorkflowEvent" - }, - "batchJob": { - "$ref": "#/definitions/LinkBatchJob" - }, - "activity": { - "$ref": "#/definitions/LinkActivity" - }, - "nexusOperation": { - "$ref": "#/definitions/LinkNexusOperation" - }, - "workflow": { - "$ref": "#/definitions/LinkWorkflow" - }, - "callback": { - "$ref": "#/definitions/v1LinkCallback" - } - }, - "description": "Link can be associated with history events. It might contain information about an external entity\nrelated to the history event. For example, workflow A makes a Nexus call that starts workflow B:\nin this case, a history event in workflow A could contain a Link to the workflow started event in\nworkflow B, and vice-versa." - }, "v1LinkCallback": { "type": "object", "properties": { @@ -17879,7 +18065,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links attached by the handler of this operation on start or completion." }, @@ -18891,7 +19077,7 @@ "$ref": "#/definitions/v1QueryRejected" }, "link": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Holds the link to the Workflow execution that processed the Query." } } @@ -19103,7 +19289,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Link" + "$ref": "#/definitions/apiCommonV1Link" }, "description": "Links to be associated with this update." }, @@ -20040,7 +20226,7 @@ "description": "If true, a new workflow was started." }, "signalLink": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Link to be associated with the WorkflowExecutionSignaled event.\nAdded on the response to propagate the backlink.\nAvailable from Temporal server 1.31 and up." } } @@ -20049,7 +20235,7 @@ "type": "object", "properties": { "link": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Link to be associated with the WorkflowExecutionSignaled event.\nAdded on the response to propagate the backlink.\nAvailable from Temporal server 1.31 and up." } } @@ -20066,7 +20252,7 @@ "description": "If true, a new activity was started." }, "link": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Link to the started activity." }, "eagerActivityTask": { @@ -20225,6 +20411,28 @@ } } }, + "v1StartNexusOperationResponse": { + "type": "object", + "properties": { + "syncSuccess": { + "$ref": "#/definitions/StartNexusOperationResponseSyncSuccess" + }, + "asyncSuccess": { + "$ref": "#/definitions/StartNexusOperationResponseAsyncSuccess" + }, + "operationFailure": { + "$ref": "#/definitions/StartNexusOperationResponseOperationFailure" + }, + "links": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiNexusV1Link" + }, + "description": "Links associated with the operation, as returned by the handler." + } + } + }, "v1StartWorkflowExecutionResponse": { "type": "object", "properties": { @@ -20249,7 +20457,7 @@ "description": "When `request_eager_execution` is set on the `StartWorkflowExecutionRequest`, the server - if supported - will\nreturn the first workflow task to be eagerly executed.\nThe caller is expected to have a worker available to process the task." }, "link": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Link to the workflow event." } } @@ -21009,7 +21217,7 @@ "description": "The most advanced lifecycle stage that the Update is known to have\nreached, where lifecycle stages are ordered\nUPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_UNSPECIFIED <\nUPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ADMITTED <\nUPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ACCEPTED <\nUPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED.\nUNSPECIFIED will be returned if and only if the server's maximum wait\ntime was reached before the Update reached the stage specified in the\nrequest WaitPolicy, and before the context deadline expired; clients may\nmay then retry the call as needed." }, "link": { - "$ref": "#/definitions/v1Link", + "$ref": "#/definitions/apiCommonV1Link", "description": "Link to the update event. May be null if the update has not yet been accepted." }, "propagatedNexusSerializationContext": { diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index 72df3d718..f66aff1ae 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -1865,6 +1865,61 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /api/v1/namespaces/{namespace}/nexus-requests/endpoints/{endpoint}/services/{service}/operations/{operation}: + post: + tags: + - WorkflowService + description: |- + StartNexusOperation starts a Nexus operation on an endpoint, communicating directly with the + endpoint using the caller's namespace credentials. No caller-side state is created. + The response follows the Nexus RPC spec semantics: synchronous completion, asynchronous start, + or operation error. + Handler errors (e.g., NOT_FOUND, BAD_REQUEST) are returned as gRPC errors. + operationId: StartNexusOperation + parameters: + - name: namespace + in: path + description: The namespace to authenticate to. This may be different from the target namespace of the specified endpoint. + required: true + schema: + type: string + - name: endpoint + in: path + description: Endpoint name, resolved via the Temporal service's endpoint registry. + required: true + schema: + type: string + - name: service + in: path + description: Nexus service name. + required: true + schema: + type: string + - name: operation + in: path + description: Nexus operation name. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StartNexusOperationRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/StartNexusOperationResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /api/v1/namespaces/{namespace}/schedule-count: get: tags: @@ -6946,6 +7001,61 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /namespaces/{namespace}/nexus-requests/endpoints/{endpoint}/services/{service}/operations/{operation}: + post: + tags: + - WorkflowService + description: |- + StartNexusOperation starts a Nexus operation on an endpoint, communicating directly with the + endpoint using the caller's namespace credentials. No caller-side state is created. + The response follows the Nexus RPC spec semantics: synchronous completion, asynchronous start, + or operation error. + Handler errors (e.g., NOT_FOUND, BAD_REQUEST) are returned as gRPC errors. + operationId: StartNexusOperation + parameters: + - name: namespace + in: path + description: The namespace to authenticate to. This may be different from the target namespace of the specified endpoint. + required: true + schema: + type: string + - name: endpoint + in: path + description: Endpoint name, resolved via the Temporal service's endpoint registry. + required: true + schema: + type: string + - name: service + in: path + description: Nexus service name. + required: true + schema: + type: string + - name: operation + in: path + description: Nexus operation name. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StartNexusOperationRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/StartNexusOperationResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /namespaces/{namespace}/schedule-count: get: tags: @@ -17717,6 +17827,70 @@ components: started: type: boolean description: If true, a new operation was started. + StartNexusOperationRequest: + type: object + properties: + namespace: + type: string + description: The namespace to authenticate to. This may be different from the target namespace of the specified endpoint. + identity: + type: string + description: The identity of the client who initiated this request. + requestId: + type: string + description: A unique identifier for this request, used as an idempotency key. + endpoint: + type: string + description: Endpoint name, resolved via the Temporal service's endpoint registry. + service: + type: string + description: Nexus service name. + operation: + type: string + description: Nexus operation name. + input: + allOf: + - $ref: '#/components/schemas/Payload' + description: Serialized input to the operation. + links: + type: array + items: + $ref: '#/components/schemas/Link' + description: Links for associating the caller with the operation. + StartNexusOperationResponse: + type: object + properties: + syncSuccess: + $ref: '#/components/schemas/StartNexusOperationResponse_SyncSuccess' + asyncSuccess: + $ref: '#/components/schemas/StartNexusOperationResponse_AsyncSuccess' + operationFailure: + $ref: '#/components/schemas/StartNexusOperationResponse_OperationFailure' + links: + type: array + items: + $ref: '#/components/schemas/Link' + description: Links associated with the operation, as returned by the handler. + StartNexusOperationResponse_AsyncSuccess: + type: object + properties: + operationToken: + type: string + description: An operation was started asynchronously. The token can be used for cancellation. + StartNexusOperationResponse_OperationFailure: + type: object + properties: + failure: + $ref: '#/components/schemas/Failure' + description: |- + The operation completed unsuccessfully. + The operation is considered canceled if the failure has a CanceledFailureInfo object attached. + StartNexusOperationResponse_SyncSuccess: + type: object + properties: + payload: + $ref: '#/components/schemas/Payload' + description: An operation completed successfully and synchronously. StartOperationRequest: type: object properties: diff --git a/temporal/api/workflowservice/v1/request_response.proto b/temporal/api/workflowservice/v1/request_response.proto index 2ad815a5e..d214c9b0e 100644 --- a/temporal/api/workflowservice/v1/request_response.proto +++ b/temporal/api/workflowservice/v1/request_response.proto @@ -3389,6 +3389,55 @@ message ListActivityExecutionsResponse { bytes next_page_token = 2; } +message StartNexusOperationRequest { + // The namespace to authenticate to. This may be different from the target namespace of the specified endpoint. + string namespace = 1; + // The identity of the client who initiated this request. + string identity = 2; + // A unique identifier for this request, used as an idempotency key. + string request_id = 3; + + // Endpoint name, resolved via the Temporal service's endpoint registry. + string endpoint = 4; + // Nexus service name. + string service = 5; + // Nexus operation name. + string operation = 6; + + // Serialized input to the operation. + temporal.api.common.v1.Payload input = 7; + + // Links for associating the caller with the operation. + repeated temporal.api.common.v1.Link links = 8; +} + +message StartNexusOperationResponse { + // An operation completed successfully and synchronously. + message SyncSuccess { + temporal.api.common.v1.Payload payload = 1; + } + + // An operation was started asynchronously. The token can be used for cancellation. + message AsyncSuccess { + string operation_token = 1; + } + + // The operation completed unsuccessfully. + // The operation is considered canceled if the failure has a CanceledFailureInfo object attached. + message OperationFailure { + temporal.api.failure.v1.Failure failure = 1; + } + + oneof variant { + SyncSuccess sync_success = 1; + AsyncSuccess async_success = 2; + OperationFailure operation_failure = 3; + } + + // Links associated with the operation, as returned by the handler. + repeated temporal.api.nexus.v1.Link links = 4; +} + message StartNexusOperationExecutionRequest { string namespace = 1; // The identity of the client who initiated this request. diff --git a/temporal/api/workflowservice/v1/service.proto b/temporal/api/workflowservice/v1/service.proto index 34f6a73f6..994fd6ac4 100644 --- a/temporal/api/workflowservice/v1/service.proto +++ b/temporal/api/workflowservice/v1/service.proto @@ -1691,6 +1691,22 @@ service WorkflowService { }; } + // StartNexusOperation starts a Nexus operation on an endpoint, communicating directly with the + // endpoint using the caller's namespace credentials. No caller-side state is created. + // The response follows the Nexus RPC spec semantics: synchronous completion, asynchronous start, + // or operation error. + // Handler errors (e.g., NOT_FOUND, BAD_REQUEST) are returned as gRPC errors. + rpc StartNexusOperation (StartNexusOperationRequest) returns (StartNexusOperationResponse) { + option (google.api.http) = { + post: "/namespaces/{namespace}/nexus-requests/endpoints/{endpoint}/services/{service}/operations/{operation}" + body: "*" + additional_bindings { + post: "/api/v1/namespaces/{namespace}/nexus-requests/endpoints/{endpoint}/services/{service}/operations/{operation}" + body: "*" + } + }; + } + // StartNexusOperationExecution starts a new Nexus operation. // // Returns a `NexusOperationExecutionAlreadyStarted` error if an instance already exists with same operation ID in this