Documentation / Développement / Référence / Plugin manifest

Plugin manifest

A plugin is a versioned bundle that contributes to a fixed set of extension points: value-sets, dimensions, context kinds, types, conversions, nodes, agents, adapters, event sources, event bindings and port sandboxes. Its plugin.yaml is validated by the zod schema in libs/shared/src/plugin/manifest.ts. This page documents that schema field by field; it is the authoritative source for the manifest grammar. It does not cover authoring workflows — see the how-to guides linked from each section.

The manifest is browser-safe grammar only. The loading logic (filesystem discovery, dynamic import) lives in the API host (PluginHost); the manifest schema defines structure and validation, nothing else.

Top-level fields

Field Type Required Default Notes
apiVersion literal meridian/plugin-v1 yes The only accepted value.
name string yes Unique package-style identifier (e.g. @scope/name).
version string yes Plugin version. Immutable once published to a registry.
description string no Human description.
sdk string no Required range of the SDK (@meridian/shared), e.g. ^0.1.0. Verified at load against PLUGIN_CONTRACT_VERSION using the npm semver package; an incompatible plugin is refused and logged. Absent means no constraint.
locale string no fr Source language of the manifest's labels, descriptions and docs. Other languages are overlay bundles i18n/<locale>.yaml. See Translate a plugin.
dependsOn record { "<plugin>": "<semver range>" } no Other plugins this one builds on; drives load order and cross-plugin type resolution. The range value is not yet enforced.
contributes object no {} The eleven contribution arrays below; each defaults to [].

contributes sections

Key Element schema Section
valueSets value-set contribution contributes.valueSets
dimensions dimension contribution contributes.dimensions
contextKinds context-kind contribution contributes.contextKinds
types type contribution contributes.types
conversions conversion contribution contributes.conversions
nodes node contribution contributes.nodes
agents agent contribution contributes.agents
adapters adapter contribution contributes.adapters
eventSources event-source contribution contributes.eventSources
eventBindings event-binding contribution contributes.eventBindings
portSandboxes port-sandbox contribution contributes.portSandboxes

Load order and resilience

The host applies contributions in a fixed order so that a symbol is always registered before anything that references it:

valueSets → contextKinds → dimensions → types → conversions → nodes → adapters → agents → eventSources → eventBindings → portSandboxes

An invalid manifest, or a node referencing an unknown type/value-set/dimension, is logged and skipped; the other contributions still load and a plugin never blocks startup. Plugins are discovered from a built-in directory plus every path in PLUGINS_PATH, or downloaded from a registry per the instance manifest's plugins: map. See Bundling and distribution and Publish to the registry.

Manifest skeleton

apiVersion: meridian/plugin-v1
name: "@scope/name"
version: 0.1.0
sdk: "^0.1.0"
locale: fr
dependsOn:
  "@posos/clinical": "*"
description: What this plugin does.
contributes:
  valueSets:     [ … ]
  dimensions:    [ … ]
  contextKinds:  [ … ]
  types:         [ … ]
  conversions:   [ … ]
  nodes:         [ … ]
  adapters:      [ … ]
  agents:        [ … ]
  eventSources:  [ … ]
  eventBindings: [ … ]
  portSandboxes: [ … ]

TypeRef

Field and port types are described with a recursive TypeRef. The variants accepted by the manifest schema:

kind Additional keys Meaning
primitive name One of String, Number, Decimal, Integer, Boolean, Date, DateTime, Time, Duration, Code, Uri.
coded valueSet (string) A code bound to a contributed value-set.
quantity dimension (string, optional) A dimension-aware measure.
ref entity (string) A reference to the entity of a contributed context kind (e.g. Patient for the patient kind) — open, validated against the context-kind registry.
object name (string) Another registered type, by name.
list of (TypeRef) A homogeneous list.
any A flow port; the type is inferred from wiring.

For the full semantics and the TypeScript each variant generates, see the TypeRef reference.

contributes.valueSets

Terminologies backing coded types.

Field Type Required Notes
id string yes Value-set identifier referenced by coded types.
system string yes Code-system URI/URN.
doc string yes Description.
concepts array of { code, display, parent? } yes Enumerated concepts; parent (string) is an optional hierarchy link. Empty array when external.
external boolean no When true, the value-set is not enumerated and is resolved by a terminology server.
valueSets:
  - id: renal-stage
    system: urn:acme:renal-stage
    doc: KDIGO chronic-kidney-disease stage.
    concepts:
      - { code: G1, display: "G1 — normal or high (>= 90)" }
      - { code: G3a, display: "G3a", parent: G3 }
  - id: snomed-ct
    system: http://snomed.info/sct
    external: true
    concepts: []

See Add vocabulary and types.

contributes.dimensions

Backing for quantity types (UCUM-style unit checking).

Field Type Required Notes
name string yes Dimension name referenced by quantity types.
label string yes Display label.
canonical string yes Canonical unit symbol.
units array of { symbol, toCanonical } yes symbol (string) and toCanonical (number) — the multiplicative factor to the canonical unit.
dimensions:
  - name: filtration-rate
    label: Glomerular filtration rate
    canonical: "mL/min/{1.73_m2}"
    units:
      - { symbol: "mL/min/{1.73_m2}", toCanonical: 1 }

contributes.contextKinds

The kinds of ambient context a workflow can carry (context: in a workflow spec, context:/establishes: on nodes, keys of the ContextEnvelope). The registry (libs/shared/src/domain/context-kinds.ts) is open and starts empty: the core ships none; @posos/common contributes patient, encounter, order and document. A kind ties three axes together: the context key, the id field an external caller knows how to supply (what makes the kind composable by an incoming webhook or an out-of-workflow agent invocation), and the entity name of the associated ref TypeRef.

Field Type Required Notes
name string yes Context key (context:/establishes:/ContextEnvelope) — e.g. patient.
idField string yes HTTP/event field carrying the entity id — e.g. patientId.
entity string no Entity name of the associated ref TypeRef — e.g. Patient.
description string no Description.
contextKinds:
  - { name: patient, idField: patientId, entity: Patient, description: The patient whose record is in context. }

Registering an already-registered kind is a collision and fails the contribution. Kinds contributed by the dependsOn closure are referenced by name from context:/establishes:. GET /api/catalog serves the registered kinds (contextKinds), and validateSpec checks a workflow's context: against the registry. The core trigger.webhook and trigger.workflow-called triggers declare establishes: "*" — opportunistic: every registered kind whose idField is present in the call becomes ambient context.

contributes.types

Datatypes and resources. Each field carries its own TypeRef.

Field Type Required Notes
name string yes Type name; referenced by object type-refs.
doc string yes Description.
layer enum datatype | resource yes datatype = reusable building block; resource = domain resource.
fields record { <field>: FieldDescriptor } yes Field descriptors keyed by field name.

FieldDescriptor:

Field Type Required Notes
type TypeRef yes The field's type.
required boolean yes Whether the field is mandatory.
doc string no Field description.
types:
  - name: EgfrResult
    doc: Estimated GFR (value + equation + stage).
    layer: resource
    fields:
      value:    { type: { kind: quantity, dimension: filtration-rate }, required: true }
      equation: { type: { kind: primitive, name: String }, required: true }
      stage:    { type: { kind: coded, valueSet: renal-stage }, required: true }

Types owned by a dependsOn plugin are referenced by name and resolved across the dependency closure. See Generate types.

contributes.conversions

A declarative conversion powering the transform.convert node. expr is a JEXL expression evaluated over value (the input).

Field Type Required Notes
from string yes Source type name.
to string yes Target type name.
label string yes Display label.
expr string yes JEXL expression evaluated over value; result is the converted output.
conversions:
  - from: CreatinineClearance
    to: Decimal
    label: Clearance -> number
    expr: value.value.value

contributes.nodes

A node declares typed ports and (optionally) points at a behavior factory module.

Field Type Required Default Notes
id string yes Unique node id.
kind enum yes One of trigger, source, sink, compute, gateway, human-task, agent, transform.
category string yes Palette grouping in the editor.
label string yes Display label.
description string yes Description.
context array of string no [] Ambient context kinds the node requires. OPEN: each value must resolve in the dependsOn closure's contextKinds — checked at load, and an unknown kind skips the node with a warning (same rule as an unknown object type).
establishes array of string no Context kinds the node establishes (typically a trigger). Same open resolution rule as context.
eventType string no For a trigger: the domain event type it accepts (must be a registered event; the API accepts it on POST /api/events).
example record { … } no For a trigger: an example payload pre-filled in the console.
inputs array of PortDef no [] Input ports.
outputs array of PortDef no [] Output ports.
config array of ConfigFieldDef no Configuration fields.
agentTool boolean no Opt-in: let an exposed agent (api.enabled) list this node as a tool. Only meaningful for a sink, which is otherwise excluded because it writes to an external system with no workflow author arbitrating the call. Eligibility for every other kind is derived from the meta itself (kind, dynamic, context) and checked when the agent is registered.
dynamic enum no One of break, make, convert, switch, map, guard. Marks an engine primitive whose ports derive from config; plugins do not define these.
behavior { module, export } no The behavior factory module (see below). Optional for dynamic or purely structural nodes.

PortDef (inputs / outputs):

Field Type Required Notes
name string yes Port name.
type TypeRef yes Port type.
required boolean no Whether the input must be connected.
description string no Description.

ConfigFieldDef (config):

Field Type Required Notes
name string yes Config key.
type TypeRef yes Config value type.
description string no Description.
options array of string no Closed vocabulary of a non-coded field: the console renders a picker instead of a free-text box. Purely a UX affordance — enforcement still belongs to your behavior. Do not use it on a coded field: its codes are already enumerated by its value set, which the console expands on its own (with each concept's display as the label).

A field whose values are known in advance should never be free text. Prefer a coded type bound to a value set you contribute — you get validation, display labels, i18n and the picker in one move — and fall back to options only for technical vocabularies that are not clinical terminology (a mode, a unit, an auth scheme). An external value set (SNOMED, via a terminology server) is not enumerable, so such a field stays free text.

behavior:

Field Type Required Default Notes
module string yes Path to the JS module exporting the behavior factory (resolved as .ts in dev or .js when compiled).
export string no default Named export to use.

The behavior module exports a factory (ctx) => NodeBehavior. The method the interpreter calls depends on kind. For the full contract see the behavior contract reference and Implement a node behavior.

nodes:
  - id: acme.egfr.ckd-epi-2021
    kind: compute
    category: Labs & calculations
    label: eGFR (CKD-EPI 2021)
    description: Estimates glomerular filtration rate.
    context: []
    inputs:
      - { name: result, type: { kind: object, name: Observation }, required: true }
      - { name: demographics, type: { kind: object, name: Demographics }, required: true }
    outputs:
      - { name: egfr, type: { kind: object, name: EgfrResult } }
    behavior: { module: ./behaviors/egfr.ts, export: egfrCkdEpi2021 }

contributes.adapters

Binds an implementation to a hexagonal port. A factory (config) => PortImpl. The port bundle is fully open: any port name is addressable, and the port's contract lives with the plugin that owns it (the SDK types only agent and terminology — see Port contracts).

Field Type Required Default Notes
port string yes The port implemented.
name string yes Selection identifier used by the instance manifest.
module string yes JS module exporting the factory.
export string no default Named export to use.
config record { … } no Default/static config.
systems array of string no For a terminology adapter: code systems it can handle (informative).
record { contextKind } no Declares the adapter the record port of a context kind: its implementation satisfies SubjectRecordPort (SDK) for that kind's entities. This is what wires the host integrations — GET /api/records/:kind/:id and validation publication on suspension — without the host knowing the domain.

Adapters are selected by the deployment's instance manifest (e.g. ports.patient = { adapter: fhir, config }); a port that nothing selects does not exist. See Port contracts, Implement a port adapter and the instance manifest reference.

adapters:
  - { port: patient, name: fhir, module: ./adapters/patient.ts, record: { contextKind: patient } }
  - { port: terminology, name: hermes, module: ./adapters/hermes.ts, systems: [snomed-ct] }
  - { port: patient, name: in-memory, module: ./adapters/in-memory.ts, export: patient, record: { contextKind: patient } }

contributes.agents

An agent is projected into a node agent.<slug> whose ports are typed from its inputs and outputs.

Field Type Required Default Notes
name string yes Agent name.
description string no Description.
provider string yes Model provider. Conventionally one of anthropic, openai, gemini, mistral, azure-openai, local; the schema accepts any string.
model string yes Model identifier.
prompt string yes System prompt.
inputs array of AgentPort yes (min 1) Named input ports.
outputs array of AgentPort yes (min 1) Named output ports.
tools array of string no [] Node ids the model may call; a terminal submit_result tool validates the final answer against the outputs.
maxIterations integer no Between 1 and 50.

AgentPort (inputs / outputs):

Field Type Required Notes
name string yes Port name.
type string yes A type name (not a TypeRef).
list boolean no Whether the port is a list.
required boolean no Whether the port is required.
description string no Description.

Backward compatibility: a scalar input: <TypeName> / output: <TypeName> is accepted and rewritten to a single named port (inputs: [{ name: "input", type: … }], outputs: [{ name: "output", type: … }]).

agents:
  - name: Renal triage
    provider: anthropic
    model: claude-sonnet-4-5
    inputs:  [ { name: observation, type: Observation } ]
    outputs: [ { name: condition,   type: Condition } ]
    maxIterations: 6
    tools: [patient.load-demographics, acme.egfr.ckd-epi-2021]
    prompt: |
      You are a nephrologist. From the creatinine result, estimate renal function
      (use the tools) and propose a Condition if warranted.

The agent runner is itself a port adapter (provided by a plugin such as @posos/llm). See Author an agent.

contributes.eventSources

A generic ingestion mechanism (polling, an HL7 socket…). It receives a connection config and a list of feeds (opaque queries) and emits raw records; it has no knowledge of domain events.

Field Type Required Default Notes
name string yes Source mechanism name (referenced by bindings).
module string yes JS module exporting the SourceFactory.
export string no default Named export to use.
description string no Description.
eventSources:
  - { name: fhir-poll, module: ./sources/poll.ts, description: Polls a FHIR server and emits raw resources. }

See Contribute an event source and binding and The open event model.

contributes.eventBindings

The declarative, reusable link between a source feed and a domain action. Any plugin may contribute bindings; the instance activates them and supplies connection config. A binding declares either event (start a run) or resolve (resolve a pending human validation) — exactly one of the two.

Field Type Required Default Notes
name string yes Unique binding id; equals the feed name on the mechanism.
source string yes Target source mechanism (an eventSources name).
query record { … } no {} Source-specific query descriptor (e.g. { resource, params } for FHIR).
event string no Issue A: the domain event type emitted (must match a registered trigger).
map record { <field>: MapExpr } no {} Issue A: projection of a raw record onto the event's fields.
resolve { run, decision, by? } no Issue B: resolve a suspended run's human validation instead of starting a new run.
description string no Description.

resolve:

Field Type Required Notes
run MapExpr yes Projects the run key.
decision MapExpr yes Projects the decision (accepted | rejected).
by MapExpr no Projects the author.

Validation rule (zod refine): a binding must declare event xor resolve. Declaring both, or neither, is rejected.

eventBindings:
  - name: fhir-creatinine
    source: fhir-poll
    event: BioResultReceived
    query: { resource: Observation, params: { code: "http://loinc.org|2160-0" } }
    map:
      patientId:  { path: subject.reference, ref: true }
      analyte:    { const: creatinine }
      value:      valueQuantity.value
      loincCode:  { jsonpath: "$.code.coding[?(@.system=='http://loinc.org')].code" }

The deployment supplies only the connection config (and an optional binding allow-list) in its instance manifest. See Connect an event source.

MapExpr

The projection grammar used by map and by resolve. Its evaluation lives in the API host (JSONPath-based). Accepted forms:

Form Meaning
"a.b.0.c" Dotted path (shorthand).
{ path, ref?: true } Dotted path; ref: true strips a FHIR reference ("Patient/x""x").
{ jsonpath, all?: true, ref?: true } JSONPath (filters, wildcards); first match by default, all: true returns every match.
{ const } Literal value.

Defined in libs/shared/src/plugin/mapping.ts. See the MapExpr reference for full semantics.

contributes.portSandboxes

A simulation sandbox for a port the plugin owns — the engine ships no business sandbox of its own. The module exports a factory (seed, record) => portImpl: reads are served from the seed (shape defined by the owning plugin, opaque to the engine), writes are captured through record(op, targetId, payload) (never applied to a real system) while staying re-readable (copy-on-write). A port with no contributed sandbox is simply absent in simulation — a behavior that needs it fails with the clear "Port « X » non branché" error, visible in the report.

Field Type Required Default Notes
port string yes The port simulated.
module string yes JS module exporting the sandbox factory.
export string no default Named export to use.
portSandboxes:
  - { port: patient, module: ./simulation/sandbox.ts, export: patientSandbox }

@posos/clinical contributes the patient and prescription sandboxes, @posos/notify the notification one. Seeds are supplied per port name in POST /api/simulate (options.seeds), or read from a real record with options.subject: { kind, id } (the record port of that context kind seeds the corresponding port). See the simulation engine.

  • TypeRef — the type grammar used across fields and ports.
  • MapExpr — the binding projection grammar.
  • Behavior contract — what a node behavior module exports.
  • Port contracts — the interfaces adapters implement.
  • SDK surface — the @meridian/shared sub-paths a plugin imports.
  • CLImeridian-plugin commands (gen-types, build, validate, publish, i18n).
  • Instance manifest — how a deployment selects adapters, sources and bindings.
  • Bundled plugins — real manifests to read.
75 documents18 sectionssource : /docs · généré au build