Documentation / Conception / Référence / Workflow DSL (meridian/v3)

Workflow DSL (meridian/v3)

This is the complete grammar of a Meridian workflow document. A workflow is a single declarative YAML file — the sole source of truth for a pipeline, versioned in git and executed by the generic interpreter. The grammar is defined by the Zod schema WorkflowSpec in libs/shared/src/domain/workflow-spec.ts; this page mirrors that schema field by field.

Documents live in the definition store, seeded from *.workflow.yaml files in git (content/clinical-demo/workflows/ for the shipped demos, apps/api/workflows/ for the host's system definitions).

For the why behind the dataflow model, see The dataflow model. For task-oriented authoring, see the how-to guides.

Top-level document

The document root is an object with exactly the following fields. All six are required; there are no other top-level keys.

Field Type Required Description
apiVersion string literal "meridian/v3" yes Grammar version discriminator.
name string yes Human-readable workflow name.
context array of string yes Ambient context kinds established by the trigger (open, plugin-contributed).
trigger object yes The entry node (reserved id trigger).
nodes map of node instances yes Node instances keyed by id.
connections array of connections yes Port-to-port dataflow edges.
apiVersion: meridian/v3
name: Réception résultat biologie
context: [patient, encounter]
trigger:
  type: trigger.bio-result-received
  description: Un résultat de biologie est reçu.
nodes:
  load_demographics: { type: patient.load-demographics }
  guard_creatinine:
    type: flow.guard
    config: { condition: 'criteria.code.code == "creatinine"' }
  compute_dfg: { type: clinical.egfr.ckd-epi-2021 }
connections:
  - { from: trigger.result,                 to: guard_creatinine.criteria }
  - { from: guard_creatinine.pass,          to: compute_dfg.result }
  - { from: load_demographics.demographics, to: compute_dfg.demographics }

apiVersion

Zod: z.literal("meridian/v3").

Must be the exact string meridian/v3. Any other value fails schema validation. This field discriminates the grammar version.

name

Zod: z.string().

Free-form human-readable name. No format constraint beyond being a string.

context

Zod: z.array(z.string().min(1)).

The ambient variables the trigger establishes and that nodes may read. The vocabulary is open: each element must be a context kind contributed by a loaded plugin (contributes.contextKinds@posos/common contributes patient, encounter, order, document); the array may contain any subset (including empty) and any order.

Static validation (validateSpec) checks that every element is a registered kind (an unknown kind is an error naming the known ones), that the trigger establishes each one (a trigger declaring establishes: "*" — webhook, workflow-called — establishes any registered kind opportunistically), and that context covers what the workflow's nodes require.

trigger

Zod: z.object({ type: z.string(), description: z.string().optional() }).

The entry node of the workflow. It establishes the ambient context and emits the initial outputs consumed by downstream connections.

Field Type Required Description
type string yes Id of a node type of kind trigger in the catalogue.
description string no Optional human-readable description.

The trigger instance is addressed in connections by the reserved node id trigger (see Reserved identifiers) — e.g. trigger.result. The trigger's type maps to a registered event type; if no trigger with a matching event type is registered (core or plugin), inbound events are rejected. The event model is open — see The open event model.

nodes

Zod: z.record(WorkflowNodeInstance).

A map from node id to a node instance. Keys are the node ids used on both sides of connection port references. Each value conforms to the node-instance schema below.

Node instance

Zod: WorkflowNodeInstance.

Field Type Required Description
type string yes Id of a node type from the catalogue.
config object (z.record(z.unknown())) no Static configuration for the node type.
inputs object (z.record(z.unknown())) no Inline constants for input ports (port → parameter).
expose array of string no Config field names shown as input ports in the editor (parameter → port).
nodes:
  load_demographics: { type: patient.load-demographics }
  guard_creatinine:
    type: flow.guard
    config: { condition: 'criteria.code.code == "creatinine"' }

type

Zod: z.string().

The id of a node type in the catalogue (core or a loaded plugin). Static validation checks the type exists. Node types are documented per plugin; see the behavior contract and core primitives.

config

Zod: z.record(z.unknown()).optional().

Static configuration consumed by the node type. Its shape is defined by the node type, not by this schema. Config fields may be overridden at runtime by a wired connection targeting that field — see expose below and Connections.

inputs

Zod: z.record(z.unknown()).optional().

Inline constants for input ports, in the "port → parameter" direction. A key is an input port name; the value is a constant that the port takes when it is not wired. This removes the need for a dedicated value.* node to supply a constant.

nodes:
  dbl: { type: clinical.double, inputs: { value: 21 } }   # required input satisfied inline

Rules (enforced by static validation):

  • The inline value is validated statically against the port's type.
  • A port that is both wired and given an inline value is a validation error.
  • For a wired port whose upstream branch is absent, absence propagation governs — there is no fallback to the inline constant (the wire is authoritative).

expose

Zod: z.array(z.string()).optional().

A list of config field names to display as input ports in the visual editor, in the "parameter → port" direction. This is a UX hint only: whether a field is listed here or not, any connection … → node.<configField> is valid and overrides the config at runtime (falling back to the static config value if the upstream branch is absent).

nodes:
  build_cond: { type: transform.format, config: { template: "criteria > {seuil}" }, inputs: { seuil: 90 } }
  garde:      { type: flow.guard, config: { condition: "criteria > 1000" }, expose: [condition] }
connections:
  - { from: trigger.value,    to: garde.criteria }
  - { from: build_cond.value, to: garde.condition }   # wired config field

connections

Zod: z.array(Connection), where Connection is z.object({ from: PortRef, to: PortRef }).

An array of dataflow edges. Each edge carries the typed output of one node's port to the typed input of another's. Order within the array is not significant.

Field Type Required Description
from port reference yes Source: an output port.
to port reference yes Target: an input port (or an exposed config field).
connections:
  - { from: compute_dfg.egfr, to: store_dfg.egfr }
  - { from: compute_dfg.egfr, to: detect_condition.egfr }

Static validation checks both endpoints exist and that their types are compatible (structural subtyping, value-set, dimension). See Two-level validation and the type system.

Port references

Zod: PortRef = z.string().regex(/^[\w-]+\.[\w-]+(\.[\w-]+)?$/).

A port reference is a dot-separated string. \w matches [A-Za-z0-9_]; a hyphen is also allowed. There are two forms:

Form Grammar Meaning
nodeId.portName two segments A whole port.
nodeId.portName.fieldName three segments A split-pin field of an object port.

Each segment matches [A-Za-z0-9_-]+. A reference with fewer than two segments, or more than three, fails the regex.

The helper parsePortRef(ref) (same file) parses a reference into { node, port, field? }: it splits on the first dot for node, then on the next dot for port and the optional field.

Input node port field
trigger.result trigger result
trigger.result.value trigger result value

Split-pin field references

The three-segment form nodeId.portName.fieldName addresses a single field of an object-typed port without an intermediate node. Field-level types are validated; the interpreter extracts (on a source) or assembles (on a target) at runtime.

connections:
  - { from: trigger.result.value, to: log_value.input }   # field `value` of the Observation

Reserved identifiers

Identifier Kind Meaning
trigger node id The trigger instance. Addressed as trigger.<port> in connections.
done implicit output port Present on every node. Emitted when the node has produced (at least one output, or no declared outputs).
after implicit input port Present on every node. Multi-wire join (AND): waits for all wired branches; skipped if any is absent. Received data is ignored.

done and after are flow pins managed by the engine; node behaviors never see them. They are used to sequence and join without transporting data — see Sequence with after and done.

Exported types and helpers

From workflow-spec.ts:

Export Kind Description
WorkflowSpec Zod schema + inferred type The document schema.
WorkflowNodeInstance Zod schema + inferred type A single node instance.
Connection Zod schema + inferred type A single edge.
parsePortRef(ref) function Parses a port reference into { node, port, field? }.
75 documents17 sectionssource : /docs · généré au build