transforms (UTL-X)
The transform stanza lives inside a connection entry and declares how the MPPM payload is transformed between the upstream component's output schema and the downstream component's input schema. UTL-X transforms are pure, stateless, and execute inside the receiving wrapper — no extra component node, no extra topic hop.
Three mapping modes
inline and ref.Mode 1 — plain connection
No transform stanza in the connection. Schemas match or are compatible. The envelope is published as-is.
connections: - id: conn-inbound-to-enricher from: { component: http-inbound, port: output } to: { component: customer-enricher, port: input } route: topic: logistics.orders.order-fulfillment.http-inbound.out subscription: logistics.orders.order-fulfillment.customer-enricher.sub schema_ref: logistics.schemas.order-received:1.0.0 # no transform stanza — plain, schemas match
Mode 2 — UTL-X inline
The UTL-X mapping body is embedded directly in the YAML. Best for small, pipeline-unique mappings under ~20 field assignments. The mapping accesses one or more steps from this pipe's MPPM envelope, declared in the UTL-X header as named inputs.
transform: type: utlx mode: inline mapping: | %utlx 1.0 input: current json, previous json // two steps from this pipe only output json --- { orderId: $current.id, customerId: $current.customer.id, prevOrderRef: $previous.id } target_schema_ref: logistics.schemas.order-to-validate:1.0.0
An inline mapping can reference multiple steps from the same pipe using a multi-input UTL-X header: input: current json, previous json. It cannot access payloads from other pipes — for that, use Mode 2 ref with N-input or Mode 3.
Mode 2 — UTL-X ref
References a mapping stored in the Mapping Registry by version. Use for complex mappings, mappings reused across pipelines, or mappings that need an independent test suite and version lifecycle.
transform: type: utlx mode: ref ref: logistics.mappings.enrich-to-validate:1.2.0 target_schema_ref: logistics.schemas.order-to-validate:1.0.0
Mode 2 — N-input (same-chain fan-in)
Multiple arrows converge on a single UTL-X transform. One arrow is the trigger (owns the transform stanza, marked trigger: true). Other arrows are context sources referenced by alias. All inputs must share the same correlation_id (same-chain). The wrapper assembles the full context before executing the mapping.
# Trigger arrow — owns the transform definition connections: - id: conn-order-to-dispatcher from: { component: inventory-validator, port: output } to: { component: fulfillment-dispatcher, port: input } route: topic: logistics.orders.order-fulfillment.inventory-validator.out subscription: logistics.orders.order-fulfillment.fulfillment-dispatcher.order-sub source_schema_ref: logistics.schemas.order-validated:1.0.0 transform: type: utlx mode: ref ref: logistics.mappings.compose-dispatch:1.0.0 trigger: true # this arrow fires execution inputs: - alias: order arrow: conn-order-to-dispatcher source_schema_ref: logistics.schemas.order-validated:1.0.0 window_depth: 3 - alias: pricing arrow: conn-pricing-to-dispatcher # context arrow source_schema_ref: logistics.schemas.order-pricing:1.0.0 window_depth: 2 correlation_mode: same_chain # all inputs must share correlation_id target_schema_ref: logistics.schemas.fulfillment-dispatch:2.0.0 # Context arrow — no transform stanza - id: conn-pricing-to-dispatcher from: { component: pricing-calculator, port: output } to: { component: fulfillment-dispatcher, port: input } route: topic: logistics.orders.order-fulfillment.pricing-calculator.out subscription: logistics.orders.order-fulfillment.fulfillment-dispatcher.pricing-sub source_schema_ref: logistics.schemas.order-pricing:1.0.0 # no transform stanza — context arrow, trigger owns the mapping
Inside the UTL-X mapping script, each alias is accessible as a named input with a $ prefix. The wrapper passes the current payload and step window of each arrow under its alias:
%utlx 1.0 input: order json, pricing json output json --- { dispatchId: $order.header.orderId, customerId: $order.customer.id, totalAmount: $pricing.summary.grossTotal, currency: $pricing.summary.currency, lines: $order.lines |> map(line => { sku: line.productCode, qty: line.quantity, price: $pricing.lines[line.lineId].unitPrice }) }
Mode 3 — full mapping component
No transform stanza on the connection. The mapping is an explicit spec.components entry with its own canvas node, log topic, error port, and scaling configuration. Required when:
- The transform makes external calls (lookups, enrichment APIs)
- The transform has side effects (writes, notifications)
- Aggregation or splitting (N→1 or 1→N cardinality change)
- Non-UTL-X engine (XSLT, JSONATA, JQ, custom SDK)
- Fan-in from independent correlation chains (different
correlation_ids) - The transform must appear in the MPPM step window for compliance traceability
transform stanza — field reference
| Field | Type | Req | Description |
|---|---|---|---|
| type | enum | required | Always utlx for Mode 2 transforms. |
| mode | enum | required | inline — mapping body embedded in YAML. ref — mapping referenced from Mapping Registry. |
| mapping | string (|) | inline only | Multi-line UTL-X mapping body. Use YAML block scalar (|). Indented as a child of the connection stanza. |
| ref | string | ref only | Mapping Registry reference in namespace.mappings.name:version format. |
| trigger | boolean | optional | Set to true on the arrow that fires execution in N-input mappings. Exactly one arrow in a fan-in group must be the trigger. Default false. |
| inputs[] | array | optional | Named input list for N-input mappings. Each entry declares an alias, an arrow reference, a source schema, and a window depth. See inputs[] fields below. |
| inputs[].alias | string | required | Name used to access this input in the UTL-X script as $alias. Should be semantic — e.g. order, pricing, customerProfile. |
| inputs[].arrow | string | required | Connection id of the arrow that carries this input. For the trigger arrow this is self-referential. |
| inputs[].source_schema_ref | string | optional | Schema of the current payload arriving on this arrow. Used by the IDE mapping editor to render the input field tree. |
| inputs[].window_depth | integer | optional | How many steps back from the MPPM envelope to make accessible for this alias. Defaults to the pipeline spec.defaults.step_depth. |
| correlation_mode | enum | optional | same_chain — the wrapper asserts all arriving envelopes share the same correlation_id before assembling context. Required for N-input Mode 2. Mismatched envelopes are routed to the error topic. |
| target_schema_ref | string | optional | Schema the UTL-X mapping must produce. The wrapper validates the mapping output against this schema before passing to the downstream component. |
Decision guide
| Situation | Mode |
|---|---|
| Schemas match exactly | Mode 1 — no stanza |
| Simple field rename/coercion, unique to this pipeline, <20 assignments | Mode 2 inline |
| Complex or reused mapping, needs independent versioning | Mode 2 ref |
| Multiple arrows, same correlation_id, pure composition | Mode 2 ref N-input |
| External call, side effect, or cardinality change | Mode 3 component |
| Independent correlation chains (different correlation_ids) | Mode 3 stateful-join |
| Non-UTL-X engine (XSLT, JSONATA, JQ) | Mode 3 component |
| Transform must appear in MPPM step window (compliance) | Mode 3 component |
Mode 2 transforms do not add an entry to the MPPM step window. The topic carries the untransformed payload — the transform is receiver-side and happens after dequeue. If compliance requires full step-level auditability of a specific transform, use Mode 3.