Validate a workflow
meridian-plugin validate statically checks a workflow YAML against your plugin's
declared nodes (plus its dependsOn closure) and the engine's core primitives
(flow.*, transform.*) — no engine, no infrastructure, nothing is executed
(libs/plugin-cli/src/commands/validate.ts). It reports unknown node types, unknown or
mistyped ports, missing context, and required inputs left unconnected, and exits
non-zero on error so it drops straight into CI.
This is the static half of validation only — it never loads a behavior module and never checks that a port's runtime value actually conforms. For that split, see Two-level validation; for what actually runs your behaviors against sample data, see Simulate a workflow.
It assumes a plugin directory with a plugin.yaml (see
Scaffold a plugin) and a workflow YAML wired against its nodes
(see Your first workflow and the
workflow DSL for the grammar itself). You do
not need gen-types or build first: the validator reads contributes straight out
of plugin.yaml — types, value-sets, dimensions and node metadata — never the
generated/ output or a compiled dist/.
Run the validator
From the plugin's directory, run the CLI directly or the
validatenpm script the scaffold wires up (libs/plugin-cli/src/commands/scaffold.ts):npm run validate workflows/example.workflow.yaml # → meridian-plugin validate …With no
--pluginflag, the plugin directory defaults to.— the current directory (libs/plugin-cli/src/cli.ts).Pass
--plugin <dir>once per additional plugin directory the workflow's nodes come from. You only need this for plugins that aren't reachable through adependsOnclosure already:dependsOnentries are resolved automatically throughnode_modules, the same waygen-typesresolves them (libs/plugin-cli/src/discover.ts,loadPluginClosure). Repeat the flag for each extra root; results are merged and de-duplicated by plugin name.Inside the platform repository, use the root
pnpm pluginscript (there is no globally installedmeridian-pluginbinary there) instead of a package-localnpm run validate. For example,content/clinical-demo/workflows/bio-result.workflow.yamlwires a trigger and a fewpatient.*/human.*nodes from the coreclinicalplugin to compute/sink nodes contributed by the external@posos/demoplugin — neither depends on the other, so both roots must be listed:pnpm plugin validate content/clinical-demo/workflows/bio-result.workflow.yaml \ --plugin external-plugins/clinical \ --plugin external-plugins/demoflow.guard, also used in that workflow, needs no--pluginat all — core primitives are baked into the validator's catalog before any plugin is registered (libs/shared/src/engine/catalog-metadata.ts).
Read the result
A valid workflow prints its name and exits 0:
✓ Réception résultat biologie: valid (ports exist, connection types compatible, context, required inputs).
An invalid one lists every error, one per line, and exits 1:
✗ Réception résultat biologie: 2 error(s):
- Connexion trigger.result → ghost.criteria : nœud cible « ghost » inconnu.
- Nœud « guard_creatinine » : entrée requise « criteria » non connectée.
The checker's own diagnostic strings are emitted in French today, regardless of your
plugin's locale: or an instance's locale — this is internal tooling output from
libs/engine-core/src/engine/validate.ts and graph.ts, not the runtime ctx.t
translation layer described in the manifest reference's i18n section; don't confuse the
two.
Fix common failures
- Unknown node or trigger type (
type inconnu) — a typo in a node'stype, or the owning plugin's directory is missing from--pluginand isn't reachable throughdependsOneither. Add the directory or fix the id. - Unknown source/target node on a connection (
nœud … inconnu) — thefrom/toside of a connection names a node id that isn't declared undernodes:(or isn'ttrigger). Check for a rename or a typo. - Unknown port, or unknown split-pin field (
sortie/entrée … inexistante,champ … inconnu) — the port name, or the field addressed after the dot on a split port (node.port.field), doesn't exist on that node or type. Cross-check the node's ports in Plugin manifest or the type's fields in TypeRef. - Incompatible connection type (
type incompatible) — the output type isn't assignable to the input type under structural subtyping. See TypeRef and The dataflow model. - Missing or unestablished context — a node's
contextentry isn't in the workflow'scontext:list, or the workflow lists a context key the trigger doesn'testablish. Add it tocontext:or pick a trigger that establishes it. - Required input not connected — wire it, or set a fixed value under
nodes.<id>.inputs.<port>instead (never both on the same port). - Graph structural errors — a cycle outside a loop's own return edge, a nested
map, a node claimed by two loop bodies, ormap.itemfeeding a node outside its body. These come from the same loop-body checks the interpreter enforces at runtime; see Loop with map for the model.
For the full mechanics behind every check (context propagation, split-pin resolution, loop-body inference), see Static validation engine.
Limitations
- Agent nodes aren't projected. The CLI doesn't turn
agents:manifest entries into node metadata, so a workflow using anagent.<slug>node reports it as an unknown node type (libs/plugin-cli/src/commands/validate.ts). See Author an agent and Use agents in a workflow. - It's static only. A green run confirms nothing about your behaviors, adapters, or live ports at runtime — only that the graph is well-typed and complete. Follow up with Simulate a workflow for a run against sample data, or Run and resolve for the real thing.
Add it to CI
Because the command exits non-zero on any error (libs/plugin-cli/src/cli.ts, main),
add it as a plain step in your pipeline — no special integration required:
- run: npm run validate workflows/example.workflow.yaml
Run it once per workflow file you ship, alongside meridian-plugin i18n check (see
Translate a plugin) and before
building and publishing — a workflow that fails to validate has
no reason to reach the registry.
Related
- Plugin manifest — the
plugin.yamlschemavalidatereads its node/type/value-set metadata from. - TypeRef — the type grammar behind connection compatibility.
- CLI reference — every
meridian-plugincommand and flag. - Workflow DSL — the YAML grammar being
checked (
trigger,nodes,connections,context). - Two-level validation — how this static pass relates to runtime port validation.
- Static validation engine
— internals of
validateSpecand the graph/loop checks. - Simulate a workflow — the next check, actually running your behaviors.
- Scaffold a plugin and Generate types — the steps before this one.
- Build and bundle — the step after a green validation.
- Build a plugin, end to end — this step in context.