Skip to content

Feature 1: Source Operation Binding

Home | Next: Six Structural Constructs →


Most executable operations in UWS bind to an operation in a source document by reference. UWS 1.6 supports openapi, google-discovery, aws-smithy, asyncapi, graphql, openrpc, grpc-protobuf, odata, browser-profile, and ansible-module source descriptions directly. Extension-owned operations are the explicit non-source escape hatch. UWS never duplicates HTTP methods, paths, AsyncAPI channels, messages, GraphQL schemas, JSON-RPC method metadata, protobuf descriptors, OData metadata, browser profile locator/action/output details, Ansible module argspec details, schemas, servers, security schemes, or protocol metadata; those live in the source document, the separate versions/browser.1.5.{json,md} / versions/ansible.1.0.{json,md} sub-specs, or executor-owned configuration.

Three Mutually Exclusive Shapes

Every valid UWS operation matches exactly one of three shapes:

Shape sourceDescription Generic selector Legacy OpenAPI selector x-uws-operation-profile
Source-bound by sourceOperationId or sourceOperationRef REQUIRED Exactly one REQUIRED MUST NOT be set OPTIONAL
OpenAPI-compatible by openapiOperationId or openapiOperationRef REQUIRED to an openapi source MUST NOT be set Exactly one REQUIRED OPTIONAL
Extension-owned MUST NOT be set MUST NOT be set MUST NOT be set REQUIRED

A document that omits the binding fields of every shape is invalid. Source-bound operations may carry profile metadata, but an extension-owned operation is selected only when all source binding fields are absent.

Source Descriptions

sourceDescriptions[] declares every API or event source document the workflow uses. Every source entry must have a unique name matching ^[A-Za-z0-9_-]+$, a url, and optionally type.

Missing type defaults to openapi. UWS 1.6 added direct declarations for Ansible collection argspecs via type: ansible-module (with the heavier argspec profile schema published separately as versions/ansible.1.0.{json,md}). UWS 1.5 added browser capability profiles via type: browser-profile (with versions/browser.1.5.{json,md}). UWS 1.4 added direct declarations for Google Discovery, AWS Smithy, AsyncAPI, GraphQL, OpenRPC, gRPC/protobuf, and OData via type: google-discovery, type: aws-smithy, type: asyncapi, type: graphql, type: openrpc, type: grpc-protobuf, and type: odata. UWS 1.3 documents remain limited to OpenAPI, Google Discovery, AWS Smithy, and AsyncAPI sources. UWS 1.2 documents remain limited to OpenAPI, Google Discovery, and AWS Smithy sources. UWS 1.1 documents remain OpenAPI-bound: Discovery, Smithy, Stone, Postman Collection, RAML, API Blueprint, and similar non-OpenAPI artifacts can participate only after tooling lowers or converts them into reviewed OpenAPI, or as advisory evidence outside sourceDescriptions.

sourceDescriptions:
  - name: petstore_api
    url: ./petstore.yaml
    type: openapi
  - name: gmail_api
    url: ./google-discovery/gmail.json
    type: google-discovery
  - name: lambda_api
    url: ./aws-smithy/lambda.json
    type: aws-smithy
  - name: billing_events
    url: ./asyncapi/billing-events.yaml
    type: asyncapi
  - name: issue_tracker
    url: ./graphql/linear.graphql
    type: graphql
  - name: pet_rpc
    url: ./openrpc/pets.json
    type: openrpc
  - name: inventory_service
    url: ./protobuf/inventory.proto
    type: grpc-protobuf
  - name: successfactors
    url: ./odata/successfactors.xml
    type: odata
  - name: builtin
    url: ./ansible/ansible-builtin.argspec.json
    type: ansible-module

sourceDescriptions is REQUIRED whenever any operation declares sourceDescription. A document where every operation is extension-owned may omit it.

Binding by operationId

The most common form: name the source description and the source operation identifier. UWS resolves the full operation from the source document.

{
  "operationId": "list_pets",
  "sourceDescription": "petstore_api",
  "sourceOperationId": "listPets",
  "request": {
    "query": { "limit": 10 }
  },
  "outputs": {
    "firstPet": "$response.body#/0"
  }
}

Note what is absent: no HTTP method (GET), no path (/pets), no response schema. Those live in petstore.yaml. UWS only says which operation to call and how to use its result.

Two operations against two different APIs in one document:

sourceDescriptions:
  - name: weather_api
    url: ./weather.openapi.yaml
    type: openapi
  - name: gmail_api
    url: ./gmail.openapi.yaml
    type: openapi

operations:
  - operationId: get_weather
    sourceDescription: weather_api
    sourceOperationId: getCurrentWeather
    request:
      query:
        q: Los Angeles
    outputs:
      summary: $response.body.summary

  - operationId: send_report
    sourceDescription: gmail_api
    sourceOperationId: gmail_users_messages_send
    dependsOn: [get_weather]
    request:
      body:
        userId: me
        text: $steps.get_weather.outputs.summary

workflows:
  - workflowId: main
    type: sequence
    steps:
      - stepId: get_weather
        operationRef: get_weather
      - stepId: send_report
        operationRef: send_report

Each operation points at a different source. UWS orchestrates them; neither OpenAPI document needs to know about the other.

Binding by JSON Pointer

When a source document does not assign a stable operation identifier, use a JSON Pointer fragment (sourceOperationRef) resolved against the named source:

{
  "operationId": "get_pet_by_id",
  "sourceDescription": "petstore_api",
  "sourceOperationRef": "#/paths/~1pets~1{petId}/get"
}

The pointer MUST begin with #/. Slashes in path segments are escaped as ~1 per RFC 6901. sourceOperationRef and sourceOperationId MUST NOT be set together on the same operation. For OpenAPI sources only, legacy openapiOperationId and openapiOperationRef remain valid backward-compatible selectors.

For AsyncAPI sources, sourceOperationId names a root AsyncAPI Operation Object. UWS core validates sourceOperationRef as a JSON Pointer fragment; source-aware tooling and runtimes validate whether it resolves to a supported AsyncAPI target. sourceOperationRef should point to #/operations/<name> when possible; #/channels/<name> and #/channels/<name>/messages/<name> are compatibility targets for runtimes that can resolve them unambiguously.

For GraphQL, OpenRPC, gRPC/protobuf, and OData sources, UWS core validates only the source type, selector exclusivity, and JSON Pointer fragment shape. Source-aware tooling and runtimes validate whether a selector resolves to a supported GraphQL operation, JSON-RPC method, protobuf service method, OData entity operation, OData function, or OData action.

For Ansible module sources, sourceOperationId is the module's fully qualified collection name, such as ansible.builtin.apt. sourceOperationRef should point to #/modules/<fqcn> when a pointer form is preferred. Module arguments bind under request.body; source-aware tooling validates those values against the referenced versions/ansible.1.0 argspec document.

Extension-Owned Operations

Operations without a source binding are owned by a named runtime profile. x-uws-operation-profile names the profile; additional x-* fields carry profile-specific configuration.

operationId: build_email
x-uws-operation-profile: uws.runtime.1.0
x-uws-runtime:
  type: fnct
  function: mail_raw
  arguments:
    - from: bot@example.com
      to: user@example.com
      subject: Daily weather report
      body: $steps.get_weather.outputs.summary

The validator accepts this as intentionally runtime-owned. See Extension Profiles for more.

Request Binding

The request object maps values into the referenced source operation's parameters and body. Keys map to common request locations:

request:
  path:
    petId: $steps.list.outputs.firstId
  query:
    includeDetails: true
    format: json
  header:
    X-Request-Id: trace-abc-123
    Accept-Language: en-US
  cookie:
    session: $variables.session_token
  body:
    status: available
    tags:
      - featured

Only path, query, header, cookie, body, and ^x- extension keys are permitted at the top level of request. Any other key is rejected:

For AsyncAPI source operations, use request.body for message payload or operation input values and request.header for AsyncAPI Message Object headers values when the source operation defines them. Channel parameter serialization and protocol binding behavior remain owned by the AsyncAPI document and runtime.

For GraphQL, OpenRPC, gRPC/protobuf, and OData source operations, request binding remains UWS-owned but interpretation of variable names, RPC params, protobuf fields, OData query options, and protocol-specific serialization remains source-aware and runtime-owned.

# This fails validation:
request:
  params:        # ← invalid key — use "query" or "path"
    limit: 10

Outputs

outputs maps friendly names to runtime expressions evaluated after the operation runs:

operationId: search_products
sourceDescription: catalog_api
openapiOperationId: searchProducts
request:
  query:
    q: $variables.search_term
outputs:
  total:    $response.body.total
  firstId:  $response.body#/items/0/id
  status:   $response.statusCode
  location: $response.headers.Location

These outputs are available to downstream steps as $steps.<stepId>.outputs.<name>.

What the Validator Rejects

The following are all invalid — each produces a structured error:

# Shape 1: both binding selectors set at once
- operationId: bad_op
  sourceDescription: api
  openapiOperationId: getOp
  openapiOperationRef: "#/paths/~1foo/get"
  # error: cannot specify both openapiOperationId and openapiOperationRef

# Shape 2: OpenAPI sourceDescription set but no selector
- operationId: bad_op
  sourceDescription: api
  # error: requires exactly one OpenAPI selector or generic source selector

# Shape 3: no binding at all, no profile
- operationId: bad_op
  # error: requires a source binding or x-uws-operation-profile

# Shape 4: sourceDescription set but no operation selector
- operationId: also_bad
  sourceDescription: api
  x-uws-operation-profile: udon   # profile metadata does not replace the missing selector
  # error: requires exactly one source operation selector or OpenAPI selector

From The Big Fixture

The large round-trip fixture includes OpenAPI-bound compatibility operations with request locations, outputs, execution controls, criteria, and actions:

operation "fetch_ticket" {
  sourceDescription  = "incident_api"
  openapiOperationId = "getIncident"
  when               = "$inputs.incidentId != ''"
  forEach            = "$variables.regions"
  parallelGroup      = "api_fetch_group"
  outputs = {
    severity = "$response.body.severity"
    ticket   = "$response.body"
  }
  request {
    path {
      incidentId = "$inputs.incidentId"
      tenantId   = "$inputs.tenantId"
    }
    query {
      depth   = "full"
      include = ["timeline", "assets"]
    }
  }
  timeout = 20
}

Full context: testdata/big/big.hcl.


Home | Next: Six Structural Constructs →