Steps
A step is a ## Step: section. Steps run in the order they appear in the file, one at a time,
and each one decides what the next one sees.
## Step: forward-ordereffects: [http_egress]endpoint: https://orders.example.com/ingestmethod: POSTNames and order
A step name follows ## Step: on the same line. Names are for you and for the logs: they appear
in error responses, in log events and in the audit trail, so make them describe what the step
does rather than where it sits.
Execution order is file order, always. There is no dependency graph and no reordering. A
validate step placed after a network call validates after the call has already happened —
put your checks first.
What a step does with the message
One message travels through the flow. Each step reads it as ctx.input and, unless it says
otherwise, replaces it with what it produces.
| The step has | What the next step sees |
|---|---|
A fence (ntd, xslt, …) |
The value the fence produced |
| An effect and no fence | The result of the effect — for a request, the response |
| An effect and a fence | The fence output is sent; the response replaces the message |
A condition fence that is false |
Nothing. Execution stops and the message is dropped |
A validate fence that fails |
Nothing. The request is rejected |
A step with http_egress and no fence sends the current message as-is. That is the common shape
for a forwarding step.
Keeping a copy of the message
A call replaces the message with its response, which loses the request. save_body and
restore_body are how you keep it.
save_body: <variable> snapshot ctx.input into a variable, before anything else runsrestore_body: <variable> overwrite ctx.input from that variable, after the body is evaluatedsave_body happens before effects and before the fence is evaluated, so it captures the
message as the step received it.
---flowmarkdown_version: "0.1"flow: forward-and-acknowledgetenant: acmeeffects: [http_egress]---
## Step: keep-requestsave_body: original```ntd{ "orderId": "{{ $.orderId }}" }```
## Step: sendeffects: [http_egress]endpoint: https://orders.example.com/ingestmethod: POST
## Step: answer-callerrestore_body: original```ntd{ "accepted": true, "orderId": "{{ $.orderId }}", "downstreamStatus": "{{ ctx.original.orderId }}"}```The variable is an ordinary entry in ctx.vars, readable as ctx.<name>. Pick names that will
not collide with anything else the flow writes.
Two names are refused at compile time — so nexus validate and nexus deploy both reject them:
correlated and correlated_late. The platform resolves those from typed context fields before
the variable map, so a save_body: correlated would store a value no read can ever reach, and a
restore_body: correlated would read a slot nothing can fill — silently, with no error at run
time. The same refusal applies to a secret_read step whose key: (or, without key:, whose
sanitised step name) is one of the two. Already-published artifacts are not revisited; the refusal
applies when you publish.
Step key reference
Any step
| Key | Type | Meaning |
|---|---|---|
effects |
list | The effects this step is permitted to perform. Enforced before the step runs |
save_body |
variable name | Snapshot ctx.input into that variable |
restore_body |
variable name | Replace ctx.input from that variable |
effects: on the step is the one that is enforced. The flow-level list documents the flow’s
capabilities; the step-level list is the permission. A step attempting an effect it did not
declare fails before any socket is opened.
HTTP requests
Used with effects: [http_egress]. See HTTP requests.
| Key | Default | Meaning |
|---|---|---|
endpoint |
— | Target URL. Accepts {{ }} interpolation |
method |
POST |
HTTP method |
content_type |
application/json |
Request content type; also selects body encoding |
accept |
— | Accept header |
bearer_token |
— | Literal, ${VAR} from the environment, or a {{ ctx.<var> }} template — the name is checked at publication |
headers |
— | Inline-JSON map of extra request headers; every value is a {{ ... }} template, checked at publication |
connect_timeout_ms |
— | Budget for establishing the connection |
read_timeout_ms |
— | Budget for reading the response |
max_message_bytes |
flow, then platform | Ceiling on the body sent from this step, and on the response read back |
error_body_max_bytes |
4096 |
How much of a non-2xx response body is kept in ctx.HTTP_RESPONSE_BODY |
SOAP
Added to an http_egress step. See SOAP.
| Key | Meaning |
|---|---|
soap_version |
1.1 or 1.2. Presence is what turns the call into a SOAP call |
soap_action |
SOAPAction header value |
soap_operation |
Operation name used when wrapping the body |
gRPC calls
Used with effects: [grpc_egress]. See gRPC calls.
| Key | Default | Meaning |
|---|---|---|
proto |
— | Descriptor name |
proto_version |
latest |
Contract version |
service |
— | Fully-qualified service name |
method |
— | Method name. Not the HTTP method: |
endpoint |
— | Host and port; https:// selects TLS |
deadline_ms |
30000 |
Total budget for the call |
connect_timeout_ms |
10000 |
Handshake budget |
max_message_bytes |
the flow’s ceiling | Message ceiling for this step alone |
max_recv_bytes |
= max_message_bytes |
Largest single response message |
max_send_bytes |
= max_message_bytes |
Largest single request message |
max_stream_messages |
10000 |
Most messages accepted from a stream |
max_stream_bytes |
= max(max_message_bytes, max_recv_bytes) |
Most stream bytes in total |
tls_ca_cert |
— | Path to a PEM CA certificate |
tls_client_cert |
— | Path to a PEM client certificate, for mutual TLS |
tls_client_key |
— | Path to its PEM key. Required together with tls_client_cert |
breaker_failure_threshold |
3 |
Failed handshakes before the endpoint is skipped |
breaker_window_ms |
60000 |
How long a failed handshake stays relevant |
breaker_cooldown_ms |
30000 |
How long a skipped endpoint rests before a probe |
breaker_cooldown_factor |
2.0 |
Multiplier on the cooldown after a failed probe |
breaker_cooldown_max_ms |
300000 |
Ceiling for the escalating cooldown |
method: means two different things depending on the effect. On an HTTP step it is the verb; on
a gRPC step it is the method name from the contract.
Publishing to a queue
Used with effects: [queue_publish]. See
Queues, retries and the DLQ.
| Key | Meaning |
|---|---|
queue |
Required. The flow whose queue receives the message. A step without it is refused at compile time, and so is one whose value breaks the rule for a name that becomes a path |
There is deliberately no tenant key: the message goes to the publishing flow’s own tenant. A key
able to redirect it elsewhere would be a tenant boundary crossing configured in a flow file.
Secrets and tokens
See Secrets and OAuth2.
| Key | Effect | Meaning |
|---|---|---|
key |
secret_read |
Secret name. Defaults to the step name if omitted |
token_url |
oauth2_token |
Token endpoint. Accepts {{ }} interpolation |
client_id |
oauth2_token |
Literal or {{ ctx.<var> }} |
client_secret |
oauth2_token |
Literal or {{ ctx.<var> }} |
scope |
oauth2_token |
Requested scope. Accepts {{ }} interpolation |
grant_type |
oauth2_token |
Defaults to client_credentials. Accepts {{ }} interpolation |
All five, like bearer_token, are templates: a {{ ctx.<var> }} inside them is checked at
publication, and the bare ctx.<var> spelling is refused with the fix named. See
Secrets and OAuth2.
Opening a correlation
Valid on any step. See Correlation.
| Key | Meaning |
|---|---|
correlation_open |
The key this exchange will be answered under. A template, like dedup_key |
correlation_state |
Inline JSON object of name → template: what gets rehydrated when the answer arrives. {} is legal |
correlation_deadline_ms |
How long to wait. Required — a wait with no end is a leak, not a policy |
correlation_timeout_flow |
Optional. The flow to run when the deadline passes |
The first three go together on the same step. Missing any of them is a compile error, and so is
a companion key without correlation_open: — which is what catches the key you misspelled.
The response to the caller
Valid on any step — a flow that succeeds shapes its answer with the same keys a ## Fault:
section uses. Collected from the steps that actually ran, in execution order. See Handling
failures.
| Key | Meaning |
|---|---|
response_status |
HTTP status returned to the caller. Literal, or an NTD template such as "{{ ctx.HTTP_SC }}". A literal outside 100–599 is a compile error |
response_header_<name> |
A response header, literal value. Last writer wins per name |
response_header_remove |
Names to take back out of the accumulated set: [x-internal, x-ephemeral] |
response_header_<name> converts underscores to hyphens and capitalises each segment, so
response_header_retry_after: 60 sends Retry-After: 60 and
response_header_content_type: application/problem+json sends Content-Type.
fault_status: and fault_header_<name>: are the deprecated spellings of the first two. They
still work, but only inside a ## Fault: section — writing one on a regular step is refused at
publication, by name.
An unknown key is an error
A key nothing will read is refused, with the closest match offered:
$ nexus validate search.flow.mderror: step 'search': unknown key `max_stream_messsages` — did you mean `max_stream_messages`?The check is per effect, not against one flat list. A key that is real but belongs to an
effect the step did not declare says so, because the fix is a missing effects: entry rather
than a spelling correction:
error: step 'send': unknown key `key` — `key` is read by `secret_read` — add it to this step's `effects:`And a key belonging to another effect entirely is refused too — tls_ca_cert on an HTTP step is
as dead as an invented one:
error: step 'send': unknown key `tls_ca_cert` — accepted here: accept, bearer_token, connect_timeout_ms, …It runs in the same pass as the other name checks, so nexus validate catches it, not only
nexus compile and nexus deploy.