A flow is a canvas tab: a saved layout of source, transform, and sink nodes. The engine
runs each source as an async polling task; each record is routed through its transform,
then fans out to the configured sink set. Events are passed as native
`CanonicalMachineData` structs, never re-serialized at the DSL boundary, which is why
the DSL is so fast.

## Per-source routing

Transforms are addressed by `{source}:{machine.id}`. A record looks up its own route
first, then falls back to `default`. Each route carries its own script _and_ its own
sink set, so one source can drop noisy machines, enrich others, and push to different
backends:

```yaml
transform:
  enabled: true
  default:
backend: "dsl"
script_inline: "set metadata.processedAt = timestamp"
sinks: ["storage", "push"]
  sources:
- key: "http:ERP-01"
  backend: "js"
  script: "./transforms/erp.js"
  sinks: ["nats", "clickhouse"]
```