Interoperability
An integrator needs three things before the first flow: the shape of the file, which adapters exist, and what happens when something falls over. This page answers in that order, and the limit sits next to the capability rather than in a footnote.
The contract is measured at the far end
What we claim about an integration is measured at the far end, on a real client’s shapes, from outside the process. Not what the platform answered at its own door: the exact body the receiver saw, with the assertion made on that.
It matters because a flow can answer 200 with the wrong value and a suite that only measures the status stays green. That is why the proof of an integration is the body the receiver saw — and why each adapter’s limit is written beside it below, rather than in a footnote.
What fits inside a flow file
A flow is one text file: a frontmatter, then step sections, then optionally a fault section. The language has one parser, and the parser does not panic on any input — a broken file produces an error that names the position.
flow: sync-meters
tenant: acme
effects: [http_egress]
config: [ANRE_URL]
dedup_key: "{{ $.messageId }}"
queue_max_attempts: 3
## Step: check
```validate
$.id != null | "id is required" | syntactic
```
## Step: forward
effects: [http_egress]
endpoint: "{{ ctx.ANRE_URL }}/ingest"
## Fault: answer-with-an-error
response_status: 502The same file is written in two ways, depending on who opens it. In VS Code it opens in the visual editor — a node per step, routes labelled on the edges, drag to reorder, jump into a subflow — and a language-server extension checks it as it is written, with the diagnostics on the position in the file. On disk it stays text: it reads in a git diff, it is reviewed like any other change, and it is published from the command line.
- The frontmatter declares the flow’s identity and its tenant, the effects it needs, the environment configuration it requires from the installation, the schedule, the size ceiling, the queue policy, the logging level and the extra fields to mask, the deduplication key and the correlation key, plus the four keys through which a flow exposes a gRPC service — all four or none.
- A step’s body is a fenced block tagged with its language:
ntdfor text and XML templates,xsltfor transforms,conditionandvalidatefor the two guards,routefor branching,includeandcall_flowfor libraries,foreach,splitandforkfor composition. - Step keys cover the rest: an outbound call’s address and method, its headers, the response’s status and headers, snapshots of the message, and the opening of a correlation with its state and its deadline.
Vocabulary with no executor, and what is used instead
- No custom code runs inside a flow. A step carrying JavaScript is refused at publication, and that is a decision, not a delay.
- Four effects exist in the vocabulary with no executor: clock, random, and the two of a key-value store. Declared on a step, the first message stops with an error naming the unregistered effect. Declared on the frontmatter, they do nothing at all — no refusal, no warning. What works instead are the library functions,
now()anduuid(), which need no declaration. - There is no schema validation and no type coercion at transform. What exists is the
validateguard — rules on the message that arrived, with no schema — and a transform that would coerce types against a declared schema is not built. - There is no temporal type. That is why the format is mandatory at the call site —
date_format(now())— and why a timestamp rendered straight into text is refused at publication, instead of writing a number under a field name that promises a date.
The adapters and each one’s limit
| Adapter | What travels through it | The named limit |
|---|---|---|
| SOAP | A SOAP request goes through **the same** gate unchanged, and a refusal is plain HTTP 401/403, never a SOAP Fault. | No foreign WSDL is consumed, and there is no typed contract for an exposed service: the generated WSDL is xsd:anyType. There is no WS-Security — a credential inside the message body authenticates nothing. |
| gRPC | A separate door, through the same gate and the same delivery bracket as the HTTP routes. | No inbound streaming: a streaming method is explicitly refused. The routing table is built at start-up, so a flow published afterwards needs a restart. |
| Files | The file connector reads a directory and posts what it finds. | A flow does not write files. Writing into a directory is not a platform capability. |
| Composition | A message can be split across branches, iterated, or detached into a branch that does not hold the response. | There is no aggregation: the branches are not gathered back into one message at the end. Rejoining stays something the flow says explicitly. |
The route that publishes an exposed service’s WSDL exists and is public — a client can read it with no credentials — but only for a visible tenant: a tenant that does not exist and a disabled one answer identically. What you read there is the loosely typed contract from the table, not a schema.
The detail of the two doors and of outbound calls is on the protocols page; what connectors do with a refusal is on the connectors page. What lives here are the limits, because those are what you need before, not after.
When a delivery fails
- A flow may carry a fault sequence. It runs instead of the error propagating, and a handler that succeeds does **not** answer 200: the status stays the natural one of the error that triggered it.
- Deduplication is declared, not programmed: a second delivery of the same key does not run the flow again, within the declared window.
- A request→late-response correlation has a mandatory deadline — an open-ended wait is a leak, not a policy — and an overdue one is reconciled on its own.
- A delivery that exits through the fault sequence is **not** committed as a success.
Four behaviours that surprise an integrator
- On a queue, a fault sequence that succeeds **acks** the message: no retry, no dead letter. A handler added for the synchronous door and forgotten there silently disarms that flow’s dead-letter queue. If both are wanted, the handler has to end in a step that fails.
- Because a handled delivery is not committed as a success, a duplicate of the same key runs the **whole** flow again — including the fault sequence, and including the effects the failed delivery already consumed. Effect accounting is not built.
- On a flow that also closes a correlation, the second delivery of the same key starts and dies on “the correlation is already settled”, before the first step — so the fault sequence never sees the refusal, and a corrected body does not help.
- The correlation header on a replayed response names the delivery that **opened** the row, not the one whose body is being replayed. Anyone following that header to find the original in the journal lands on a delivery that ended in an error.
A message’s ceiling
There is no single installation-wide ceiling. It is resolved per access point, in three layers, general to specific: the installation’s value, the flow’s value, an egress step’s value. A body announcing a size above the ceiling is refused without a byte of it being read.
What is not built, said because it otherwise reads as a promise: below the ceiling a body is still materialised **and parsed** in memory, so a raised ceiling buys transport at a memory cost of the same order. There is no accounting of materialised memory with a temporary refusal and a retry hint, and there are no per-access-point refusal counters in the UI.
Publication, versions, and what the validator does not do
Publication is a gate, not a file copy. It compiles the flow, requires a registered tenant, checks the declared ceilings and the shape of the routing blocks, and refuses a transform outside the supported subset — naming the construct. The refusals arrive at publication, not at the first message in production.
What the pre-publication validator does not do: there is no dry run, there is no publication onto a slice of traffic with a later promotion, and the reference publication moves is moved even when the artifact holds semantic errors that only appear at runtime. And two lists are photographed at start-up and do not refresh themselves — the scheduled flows and the gRPC routing table — so a new flow on either needs a restart.
The site describes version 1.2.0. Distribution is commercial, under a proprietary licence; the source code is not public.