OpexFlow is built around four primitives. Everything else is plumbing.

| Primitive | Role |
| --- | --- |
| **Source** (adapter) | Ingests events from a protocol: Modbus poll, OPC-UA subscription, HTTP REST poll, MQTT topic, NATS subject. |
| **Transform** (per-source route) | Reshapes each event before fan-out. Keyed by `{source}:{machine.id}`, with a shared default. Either a **DSL** program or a **JavaScript** function. |
| **Sink** (fan-out) | Delivers the result: hub (WebSocket), HMAC-signed HTTP push, NATS, MQTT, SQLite, ClickHouse, Postgres. |
| **Flow** (canvas tab) | A UI grouping of sources, transforms, and sinks on the canvas. Layouts persist to `flows.layout.json`; routing stays global. |

Every event is normalized to `CanonicalMachineData` at the source boundary, so
transforms and sinks always see the same shape regardless of protocol.