diff --git a/wiki/Home.md b/wiki/Home.md
index b6647f7..f7ec281 100644
--- a/wiki/Home.md
+++ b/wiki/Home.md
@@ -1,313 +1,134 @@
# valve
-> **Reflects code as of `e27135b` · regenerated `2026-05-11` via `npm run wiki:all`**
-> If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
+  
-## 1. What this node is
+A `valve` models a single actuated throttling valve. It loads a supplier Kv-vs-position characteristic curve, drives a position FSM (`accelerating` / `decelerating` through `operational`), and recomputes pressure drop from flow + Kv via a hydraulic model that picks a liquid or gas formula by `serviceType`. Used standalone, or as a child of `valveGroupControl`, downstream of a `rotatingMachine` / `machineGroupControl` / `pumpingStation`.
-**valve** models a single actuated throttling valve at the S88 Equipment Module level. It loads a supplier Kv-vs-position characteristic curve, drives an FSM for open/close move sequences (using `accelerating`/`decelerating` states shared with `rotatingMachine`), and recomputes pressure drop from flow + Kv via a hydraulic model. Used standalone or as a child of `valveGroupControl`.
+> [!NOTE]
+> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
-## 2. Position in the platform
+---
+
+## At a glance
+
+| Thing | Value |
+|:---|:---|
+| What it represents | One actuated throttling valve — supplier Kv curve, position FSM, deltaP estimate |
+| S88 level | Equipment Module |
+| Use it when | You need a position-controlled valve whose deltaP depends on flow, Kv(position), and service-type (gas / liquid) |
+| Don't use it for | Fixed-restriction orifices, non-return / check valves, or curveless throttling devices (no fallback model) |
+| Children it accepts | Upstream sources (`rotatingmachine`, `machinegroup` / `machinegroupcontrol`, `pumpingstation`, `valvegroupcontrol`) for fluid-contract tracking; `measurement` for pressure / flow |
+| Parents it talks to | `valveGroupControl` (typical) or any node that issues `set.position` / `cmd.startup` / `cmd.shutdown` |
+
+---
+
+## How it fits
```mermaid
flowchart LR
- vgc[valveGroupControl
Unit]:::unit -->|set.position| this[valve
Equipment]:::equip
- src["machine / MGC / PS
(upstream source)"]:::unit -->|child.register| this
- meas[measurement
type=pressure / flow]:::ctrl -.->|data| this
- this -->|child.register Port 2| vgc
- this -->|evt.deltaPChange| vgc
- this -->|evt.fluidCompatibilityChange| vgc
+ parent[valveGroupControl]:::unit -->|set.position
cmd.startup / shutdown| v[valve
Equipment]:::equip
+ src["rotatingMachine /
MGC / pumpingStation"]:::unit -->|child.register
(fluid contract)| v
+ m_p[measurement
pressure]:::ctrl -.measured.-> v
+ m_f[measurement
flow]:::ctrl -.measured.-> v
+ v -->|child.register| parent
+ v -.->|evt.deltaPChange
evt.fluidCompatibilityChange
evt.fluidContractChange| parent
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
-S88 colours: Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source: `.claude/rules/node-red-flow-layout.md`.
+S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`.
-## 3. Capability matrix
+---
-| Capability | Status | Notes |
-|---|---|---|
-| Predicts deltaP from flow + Kv | ✅ | Hydraulic model picks liquid vs gas formula per `serviceType`. |
-| Loads supplier curve by model name | ✅ | `asset.model` resolved through `loadModel`; inline `valveCurve` override supported. |
-| Position move FSM | ✅ | `accelerating` / `decelerating` states with interruptible setpoints; `moveTo` uses shared state machine. |
-| Startup / shutdown sequences | ✅ | Pre-shutdown ramps valve to position 0 before executing stop sequence. |
-| Emergency-stop sequence | ✅ | `cmd.estop` → `emergencystop → off` sequence. |
-| Fluid-contract aggregation | ✅ | Tracks upstream service type via registered sources through `FluidCompatibility`. |
-| Gas-choke detection | ⚠️ | Hard cap at `gasChokedRatioLimit`; surfaced in `hydraulicDiagnostics`. |
-| Multi-parent registration | ⚠️ | Allowed but not exercised in production tests. |
+## Try it — 3-minute demo
-## 4. Code map
+Import the basic example flow, deploy, and drive a single valve through a position move.
-```mermaid
-flowchart TB
- subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
- nc["buildDomainConfig()
static DomainClass=Valve, commands
tickInterval=null (event-driven)"]
- end
- subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
- sc["Valve.configure()
wires concern modules
overrides registerChild → FluidCompatibility"]
- end
- subgraph concerns["src/ concern modules"]
- state["state/
stateBindings → positionChange"]
- fluid["fluid/
FluidCompatibility"]
- curve["curve/
SupplierCurvePredictor"]
- meas["measurement/
MeasurementRouter + FORMULA_UNITS"]
- flow["flow/
FlowController (setpoint, sequences)"]
- io["io/
buildOutput + buildStatusBadge"]
- hyd["hydraulicModel.js
ValveHydraulicModel"]
- end
- nc --> sc
- sc --> state
- sc --> fluid
- sc --> curve
- sc --> meas
- sc --> flow
- sc --> io
- sc --> hyd
+```bash
+curl -X POST -H 'Content-Type: application/json' \
+ --data @nodes/valve/examples/basic.flow.json \
+ http://localhost:1880/flow
```
-| Module | Owns | Read first if you're changing… |
-|---|---|---|
-| `state/` | Bindings from state-machine `positionChange` → `updatePosition()` | Move-finished triggers, position callbacks. |
-| `fluid/` | Service-type compatibility, contract aggregation | Gas-vs-liquid mismatch warnings, upstream fluid tracking. |
-| `curve/` | Supplier Kv curve load + interpolation | Curve fitting, model selection, density keys. |
-| `measurement/` | Pressure/flow routing + deltaP recompute | What triggers a recalc, measurement container writes. |
-| `flow/` | Sequence + setpoint execution, mode validation | Startup / shutdown / move semantics, allowed-source checks. |
-| `io/` | Port-0 output shape + status badge | What lands on the wire each tick. |
-| `hydraulicModel.js` | Liquid + gas deltaP formulas, choke detection | Hydraulic calculation errors, choke ratio behaviour. |
+> [!NOTE]
+> The shipped `examples/{basic,integration,edge}.flow.json` files are minimal stubs (one inject → valve → debug). A tiered `01 - Basic Manual Control.json` / `02 - Integration with Valve Group.json` / `03 - Dashboard Visualization.json` set, matching the `rotatingMachine` template, is on the backlog. Until then, drive the node directly with injects.
-## 5. Topic contract
+What to send after deploy (the topics map one-to-one to entries in [Reference — Contracts](Reference-Contracts#topic-contract)):
-> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`.
+1. `set.mode = virtualControl` — lets the GUI source drive the valve (parent path is for grouped use).
+2. `cmd.startup` — FSM runs `idle → starting → warmingup → operational`.
+3. `set.position = {setpoint: 60}` (position %) — valve ramps from 0 to 60; state goes `operational → accelerating → operational`. Each position tick fires a Kv lookup + deltaP recompute.
+4. `data.flow = {variant: 'measured', value: 25, position: 'downstream', unit: 'm3/h'}` — push flow so the hydraulic model has something to chew on. `delta_predicted_pressure` updates and `evt.deltaPChange` fires upward.
+5. `cmd.shutdown` — if currently `operational`, the controller first ramps to position 0, then transitions `stopping → coolingdown → idle`.
-
+> [!IMPORTANT]
+> **GIF needed.** Demo recording of steps 1–5 with the live status badge. Save as `wiki/_partial-gifs/valve/01-basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
-| Canonical topic | Aliases | Payload | Unit | Effect |
-|---|---|---|---|---|
-| `set.mode` | `setMode` | `string` | — | Switch the valve between auto / manual control modes. |
-| `cmd.startup` | _(none)_ | `any` | — | Initiate the valve startup sequence. |
-| `cmd.shutdown` | _(none)_ | `any` | — | Initiate the valve shutdown sequence. |
-| `cmd.estop` | `emergencystop`, `emergencyStop` | `any` | — | Trigger an emergency stop on the valve. |
-| `execSequence` | _(none)_ | `object` | — | Legacy umbrella that demuxes payload.action to startup / shutdown / estop. |
-| `set.position` | `execMovement` | `object` | — | Move the valve to a control-% position via execMovement. |
-| `data.flow` | `updateFlow` | `object` | — | Push a measured flow into the valve (variant + position + unit). |
-| `query.curve` | `showcurve` | `any` | — | Return the valve characteristic curve on the reply port. |
-| `child.register` | `registerChild` | `string` | — | Register a child measurement with this valve. |
+---
-
+## The seven things you'll send
-## 6. Child registration
+| Topic | Aliases | Payload | What it does |
+|:---|:---|:---|:---|
+| `set.mode` | `setMode` | `"auto"` \| `"virtualControl"` \| `"fysicalControl"` \| `"maintenance"` | Switch operational mode. Source allow-list per mode (defaults from `valve.json`). |
+| `cmd.startup` | — | `{ source?: string }` | Run the configured `startup` sequence (default `[starting, warmingup, operational]`). |
+| `cmd.shutdown` | — | `{ source?: string }` | Run `shutdown`. If currently `operational`, first ramps the valve to position 0, then transitions `stopping → coolingdown → idle`. |
+| `cmd.estop` | `emergencystop`, `emergencyStop` | `{ source?: string, action?: string }` | Trigger an emergency stop — runs the `emergencystop` sequence (default `[emergencystop, off]`). |
+| `set.position` | `execMovement` | `{ source?: string, action?: string, setpoint: number }` | Move the valve to a position (control-%, `0..100`). Setpoint is coerced to `Number`. |
+| `data.flow` | `updateFlow` | `{ variant, value, position, unit? }` — `variant ∈ {'measured','predicted'}` | Push a flow measurement; triggers a Kv lookup + deltaP recompute via the hydraulic model. |
+| `query.curve` | `showcurve` | any | Reply on Port 0 with `{ topic: 'Showing curve', payload: }`. |
-valve overrides `BaseDomain.registerChild` with `FluidCompatibility.registerChild`. Upstream sources feed the fluid-contract aggregator; measurement children attach through the standard measurement handshake and land in `MeasurementRouter`.
+Plus the registration topic emitted upward at startup and accepted from real `measurement` children:
-```mermaid
-flowchart LR
- subgraph kids["accepted children (softwareType)"]
- src["machine / rotatingmachine
machinegroup / pumpingstation
valvegroupcontrol"]:::unit
- m["measurement
type=pressure or flow"]:::ctrl
- end
- src -->|getFluidContract| fluid[FluidCompatibility
aggregates serviceType]
- m -->|"<type>.measured.<position>"| router[MeasurementRouter
updatePressure / updateFlow]
- router --> deltaP[updateDeltaP
writes pressure.predicted.delta]
- fluid --> evt1["evt.fluidCompatibilityChange
evt.fluidContractChange"]
- deltaP --> evt2[evt.deltaPChange]
- classDef unit fill:#50a8d9,color:#000
- classDef ctrl fill:#a9daee,color:#000
+| Topic | Aliases | Payload |
+|:---|:---|:---|
+| `child.register` | `registerChild` | child Node-RED id (string); `msg.positionVsParent` carries the position label |
+
+The legacy umbrella `execSequence` (`{action: 'startup' \| 'shutdown' \| 'emergencystop'}`) is still accepted — it forwards to the canonical `cmd.*` handler and logs a one-time deprecation warning. Scheduled for removal in Phase 7.
+
+---
+
+## What you'll see come out
+
+Sample Port 0 message (delta-compressed, while operational at ~60 % open with a 25 m³/h flow):
+
+```json
+{
+ "topic": "valve#valve_a",
+ "payload": {
+ "state": "operational",
+ "percentageOpen": 60,
+ "moveTimeleft": 0,
+ "mode": "auto",
+ "downstream_measured_flow": 25,
+ "downstream_predicted_flow": 0,
+ "delta_predicted_pressure": 84
+ }
+}
```
-| softwareType | onRegister side-effect | Subscribed events |
-|---|---|---|
-| `machine` / `rotatingmachine` | Stored as upstream source; reads `getFluidContract()` or defaults to `liquid`. | `fluidContractChange` |
-| `machinegroup` / `machinegroupcontrol` | Same; recomputes aggregate service type. | `fluidContractChange` |
-| `pumpingstation` | Same. | `fluidContractChange` |
-| `valvegroupcontrol` | Same. | `fluidContractChange` |
-| `measurement` | Routed via measurement handshake; values land in `MeasurementContainer`. | `.measured.` |
+Key shape: **`__`** — the legacy three-segment shape. Position labels are lowercase (`downstream`, `delta`, `upstream`). `valve` does **not** use the four-segment `...` shape that `rotatingMachine` emits.
-## 7. Lifecycle — what one event does
+| Field | Meaning |
+|:---|:---|
+| `state` | Current FSM state. See [Architecture — FSM](Reference-Architecture#fsm). |
+| `percentageOpen` | Current position (`0..100`). 0 = closed, 100 = fully open. |
+| `moveTimeleft` | Seconds remaining on the current position move (0 when stationary). |
+| `mode` | One of `auto` / `virtualControl` / `fysicalControl` / `maintenance`. |
+| `delta_predicted_pressure` | Predicted deltaP across the valve (output unit `mbar`). |
+| `downstream_predicted_flow` / `_measured_flow` | Last flow pushed via `data.flow` (output unit `m3/h`). |
+| `downstream_measured_pressure` / `_predicted_pressure` | Pressure measurements pushed via the `MeasurementRouter`. |
-```mermaid
-sequenceDiagram
- participant parent as valveGroupControl
- participant valve as valve
- participant fsm as state FSM
- participant hyd as hydraulicModel
- participant out as Port-0
+---
- parent->>valve: set.position { setpoint: 60 }
- valve->>fsm: moveTo(60)
- fsm-->>fsm: operational → accelerating
- fsm-->>valve: positionChange ticks (stateBindings)
- valve->>valve: predictKv(position)
- valve->>hyd: calculateDeltaPMbar(q, kv, downP, rho, T)
- hyd-->>valve: { deltaPMbar, diagnostics }
- valve->>valve: write pressure.predicted.delta
- valve->>parent: emitter.emit('deltaPChange', deltaP)
- valve->>out: msg { topic, payload (delta-compressed) }
- fsm-->>fsm: accelerating → operational (setpoint reached)
-```
+## Need more?
-## 8. Data model — `getOutput()`
+| Page | What you'll find |
+|:---|:---|
+| [Reference — Contracts](Reference-Contracts) | Full topic contract, config schema, child registration filters |
+| [Reference — Architecture](Reference-Architecture) | Code map, FSM, hydraulic model, lifecycle |
+| [Reference — Examples](Reference-Examples) | Shipped example flows + debug recipes |
+| [Reference — Limitations](Reference-Limitations) | When not to use, known limitations, open questions |
-What lands on Port 0. Composed in `io/output.buildOutput`, then delta-compressed by `outputUtils.formatMsg`.
-
-
-
-| Key | Type | Unit | Sample |
-|---|---|---|---|
-| `state` | string | — | `"operational"` |
-| `percentageOpen` | number | % | `0` |
-| `moveTimeleft` | number | s | `0` |
-| `mode` | string | — | `"auto"` |
-| `downstream_predicted_flow` | number | m3/h | `0` |
-| `downstream_measured_flow` | number | m3/h | _(emitted when measurement child present)_ |
-| `downstream_predicted_pressure` | number | mbar | _(emitted when upstream pressure present)_ |
-| `downstream_measured_pressure` | number | mbar | _(emitted when measurement child present)_ |
-| `delta_predicted_pressure` | number | mbar | `0` |
-
-
-
-Measurement keys follow the legacy `__` shape (e.g. `downstream_predicted_flow`, `delta_predicted_pressure`). Only keys with finite values are emitted — consumers must cache and merge (delta-compression is active).
-
-## 9. Configuration — editor form ↔ config keys
-
-```mermaid
-flowchart TB
- subgraph editor["Node-RED editor form"]
- f1[Reaction Speed]
- f2[Asset model / supplier / category]
- f3[Service type]
- f4[Fluid density / temperature K]
- f5[Gas choke ratio limit]
- f6[Startup / warmup / shutdown / cooldown times]
- f7[Log level / enableLog]
- f8[positionVsParent]
- end
- subgraph config["Domain config slice"]
- c1["movement.speed (stateConfig)"]
- c2[asset.model]
- c3["runtimeOptions.serviceType → hydraulicModel"]
- c4["runtimeOptions.fluidDensity / fluidTemperatureK"]
- c5["runtimeOptions.gasChokedRatioLimit"]
- c6["stateConfig.time.starting / warmingup / stopping / coolingdown"]
- c7["general.logging.enabled / logLevel"]
- c8["functionality.positionVsParent → Port-2 registration"]
- end
- f1 --> c1
- f2 --> c2
- f3 --> c3
- f4 --> c4
- f5 --> c5
- f6 --> c6
- f7 --> c7
- f8 --> c8
-```
-
-| Form field | Config path | Default | Range / type | Where used |
-|---|---|---|---|---|
-| Reaction Speed | `movement.speed` (stateConfig) | `1` | > 0 (%/s) | `MovementManager` — sets rate of position change |
-| Asset model | `asset.model` | `'Unknown'` | string | `SupplierCurvePredictor` — selects Kv curve dataset |
-| Service type | `runtimeOptions.serviceType` | `null` (from asset) | `'gas'` / `'liquid'` | `ValveHydraulicModel` formula selection |
-| Fluid density | `runtimeOptions.fluidDensity` | model default | > 0 (kg/m³) | liquid hydraulic formula |
-| Fluid temperature | `runtimeOptions.fluidTemperatureK` | model default | > 0 (K) | gas hydraulic formula |
-| Gas choke limit | `runtimeOptions.gasChokedRatioLimit` | per asset | 0–1 | gas choke cap in `ValveHydraulicModel` |
-| Startup time | `stateConfig.time.starting` | `10` s | > 0 (s) | `StateManager` transition timer |
-| Warmup time | `stateConfig.time.warmingup` | `5` s | > 0 (s) | `StateManager` protected transition |
-| Shutdown time | `stateConfig.time.stopping` | `5` s | > 0 (s) | `StateManager` transition timer |
-| Cooldown time | `stateConfig.time.coolingdown` | `10` s | > 0 (s) | `StateManager` transition timer |
-| Mode | `mode.current` | `'auto'` | `auto` / `virtualControl` / `fysicalControl` / `maintenance` | `FlowController.isValidSourceForMode` |
-| Log level | `general.logging.logLevel` | `'info'` | enum | structured logger |
-| positionVsParent | `functionality.positionVsParent` | `'atEquipment'` | enum | Port-2 registration message to parent |
-
-## 10. State chart
-
-```mermaid
-stateDiagram-v2
- [*] --> off
- off --> idle : cmd.startup (boot sequence)
- off --> emergencystop : cmd.estop
- off --> maintenance : set.mode=maintenance
-
- idle --> starting : cmd.startup
- idle --> off : (direct transition)
- idle --> emergencystop : cmd.estop
- idle --> maintenance : set.mode=maintenance
-
- starting --> warmingup : timed (starting duration)
- starting --> emergencystop : cmd.estop
-
- warmingup --> operational : timed (warmup duration) [protected — cannot abort]
- warmingup --> emergencystop : cmd.estop
-
- operational --> accelerating : set.position > current
- operational --> decelerating : set.position < current
- operational --> stopping : cmd.shutdown
- operational --> emergencystop : cmd.estop
-
- accelerating --> operational : setpoint reached
- accelerating --> emergencystop : cmd.estop
-
- decelerating --> operational : setpoint reached
- decelerating --> emergencystop : cmd.estop
-
- stopping --> coolingdown : timed (stopping duration)
- stopping --> idle : (direct)
- stopping --> emergencystop : cmd.estop
-
- coolingdown --> idle : timed (cooldown duration) [protected — cannot abort]
- coolingdown --> off : (direct)
- coolingdown --> emergencystop : cmd.estop
-
- emergencystop --> idle : cmd.reset / sequence
- emergencystop --> off : cmd.reset / sequence
- emergencystop --> maintenance : (allowed)
-
- maintenance --> idle : manual reset
- maintenance --> off : manual reset
-```
-
-**Key valve-specific behaviours:**
-
-- `accelerating` = position moving up; `decelerating` = position moving down. Both fire `positionChange` ticks. The valve's `stateBindings` hooks these to `updatePosition()` → Kv lookup → deltaP recompute.
-- `warmingup` and `coolingdown` are **protected** — the abort signal is disabled; these phases cannot be interrupted.
-- `cmd.shutdown` from `operational` first ramps the valve to position 0 (via `FlowController.executeSequence('shutdown')`), then transitions `stopping → coolingdown → idle`.
-- `cmd.estop` triggers `emergencystop → off` regardless of current state (except from within protected transitions).
-- Default sequences: `startup` = `[starting, warmingup, operational]`; `boot` = `[idle, starting, warmingup, operational]`; `emergencystop` = `[emergencystop, off]`.
-
-## 11. Examples
-
-| Tier | File | What it shows | Mandatory? |
-|---|---|---|---|
-| Basic | `examples/basic.flow.json` | Inject `set.position` + minimal wiring, no parent | ✅ |
-| Integration | `examples/integration.flow.json` | valve + VGC + upstream measurement source | ✅ |
-| Edge | `examples/edge.flow.json` | Edge-case inputs (gas, choke, estop, bad setpoints) | ⭕ optional |
-
-Renamed example files (`01-Basic.flow.json`, `02-Integration.flow.json`, `03-Dashboard.flow.json`) will replace the above when produced. Screenshots under `wiki/_partial-screenshots/valve/`. Docker compose snippet under `examples/README.md`.
-
-## 12. Debug recipes
-
-| Symptom | First thing to check | Where to look |
-|---|---|---|
-| Status badge shows `⚠ no input` | Did any pressure / flow measurement register? Watch Port 2. | Debug tap on Port 2 |
-| `delta_predicted_pressure` stuck at `0` | Is `kv > 0`? FSM may be in `off` / `idle` — valve is closed. | `state.getCurrentState()`, `percentageOpen` |
-| Gas mismatch warning on status badge | `fluidCompatibility.status` is `mismatch` or `conflict`. | `getFluidCompatibility()` |
-| `query.curve` returns empty curve | `asset.model` not found by `loadModel`; check `SupplierCurvePredictor.snapshot()`. | `SupplierCurvePredictor.snapshot()` |
-| deltaP non-finite | Downstream gauge pressure absolute term ≤ 0, or choked ratio reached. | `hydraulicDiagnostics` in output |
-| `set.position` has no effect | Check `currentMode` — source may not be in `mode.allowedSources[mode]`. | `FlowController.isValidSourceForMode` |
-| FSM stuck in `accelerating` / `decelerating` | Movement was aborted but `_returnToOperationalOnAbort` was false. Send a new `set.position`. | `state.js` abort logic |
-
-> Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging.
-
-## 13. When you would NOT use this node
-
-- Use valve for a **throttling actuator** with a known Kv curve. For a fixed-restriction orifice (no actuator, no curve), model the deltaP externally.
-- Don't use valve to model a **non-return / check valve** — no position control or FSM-driven actuation is exposed.
-- Skip valve when an upstream source already provides flow directly and **no pressure-drop estimate is needed** — wire the source straight to the parent without inserting a valve.
-
-## 14. Known limitations / current issues
-
-| # | Issue | Tracked in |
-|---|---|---|
-| 1 | Gas-choke detection is a hard cap, not a smooth transition — chart traces show a step at the choked-ratio limit. | `hydraulicModel.js` |
-| 2 | Multi-parent registration is allowed but not exercised in production tests. | `CONTRACT.md` — Children registered by this node |
-| 3 | `set.position` move sequences are interruptible but tests cover happy-path only; abort-deadlock edge case documented separately. | `state.js` abort logic + `test/integration/` |
-| 4 | `execSequence` (legacy umbrella topic) will be removed in Phase 7 — callers must migrate to `cmd.startup` / `cmd.shutdown` / `cmd.estop`. | `CONTRACT.md` — execSequence demux |
+[EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)
diff --git a/wiki/Reference-Architecture.md b/wiki/Reference-Architecture.md
new file mode 100644
index 0000000..7c93cf2
--- /dev/null
+++ b/wiki/Reference-Architecture.md
@@ -0,0 +1,300 @@
+# Reference — Architecture
+
+
+
+> [!NOTE]
+> Code structure for `valve`: the three-tier sandwich, the `src/` layout, the position FSM, the hydraulic-model pipeline, the lifecycle, and the output ports. For an intuitive overview, return to [Home](Home).
+>
+> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
+
+---
+
+## Three-tier code layout
+
+```
+nodes/valve/
+|
++-- valve.js entry: RED.nodes.registerType('valve', NodeClass)
+|
++-- src/
+| nodeClass.js extends BaseNodeAdapter (Node-RED bridge)
+| specificClass.js extends BaseDomain (orchestration only)
+| hydraulicModel.js ValveHydraulicModel + normalizeServiceType
+| |
+| +-- commands/
+| | index.js topic descriptors
+| | handlers.js pure handler functions
+| |
+| +-- curve/
+| | supplierCurve.js SupplierCurvePredictor (Kv-vs-position load + interp)
+| |
+| +-- fluid/
+| | fluidCompatibility.js FluidCompatibility — upstream service-type aggregation
+| |
+| +-- measurement/
+| | measurementRouter.js MeasurementRouter + FORMULA_UNITS
+| |
+| +-- flow/
+| | flowController.js handleInput, executeSequence, setpoint (pre-shutdown ramp)
+| |
+| +-- state/
+| | stateBindings.js wires state.emitter('positionChange') → updatePosition()
+| |
+| +-- io/
+| output.js buildOutput + buildStatusBadge
+```
+
+### Tier responsibilities
+
+| Tier | File | What it owns | Touches `RED.*` |
+|:---|:---|:---|:---:|
+| entry | `valve.js` | Type registration | Yes |
+| nodeClass | `src/nodeClass.js` | UI-config → domain config, legacy-asset-field reject, status-badge polling (`statusInterval=1000`). No tick loop (`tickInterval=null`) — event-driven. | Yes |
+| specificClass | `src/specificClass.js` | Wire concern modules in `configure()`; expose the public surface tests + parents (VGC) depend on (`handleInput`, `setMode`, `updatePosition`, `updateFlow`, `updatePressure`, `registerChild`, `showCurve`, `getOutput`, …). Overrides `BaseDomain.registerChild` so upstream-source registration falls into `FluidCompatibility` instead of the generic ChildRouter. | No |
+
+`specificClass` is stitching. All real work lives in the concern modules: position bindings in `state/`, deltaP math in `hydraulicModel.js`, Kv interpolation in `curve/`, measurement → deltaP plumbing in `measurement/`, mode + sequences in `flow/`, fluid contract aggregation in `fluid/`, and Port-0 shaping in `io/`.
+
+---
+
+## FSM
+
+> [!NOTE]
+> The state machine is declared in `generalFunctions/src/state/stateConfig.json`. Same allowed-transition graph as `rotatingMachine`, with `accelerating` / `decelerating` reused for position moves up / down.
+
+```mermaid
+stateDiagram-v2
+ [*] --> idle
+ idle --> starting: cmd.startup
+ idle --> off
+ idle --> maintenance
+ starting --> warmingup: timer (time.starting)
+ warmingup --> operational: timer (time.warmingup) [protected]
+ operational --> accelerating: set.position up
+ operational --> decelerating: set.position down
+ operational --> stopping: cmd.shutdown
+ accelerating --> operational: target reached
+ decelerating --> operational: target reached
+ stopping --> coolingdown: timer (time.stopping)
+ coolingdown --> idle: timer (time.coolingdown) [protected]
+ coolingdown --> off
+ off --> idle: boot
+ off --> maintenance
+ maintenance --> off
+ maintenance --> idle
+
+ note right of operational
+ any state -> emergencystop via cmd.estop
+ from emergencystop: idle / off / maintenance
+ end note
+```
+
+Default sequences (from `valve.json`):
+
+| Sequence | States |
+|:---|:---|
+| `startup` | `[starting, warmingup, operational]` |
+| `shutdown` | `[stopping, coolingdown, idle]` |
+| `emergencystop` | `[emergencystop, off]` |
+| `boot` | `[idle, starting, warmingup, operational]` |
+
+### Pre-shutdown ramp to zero
+
+`FlowController.executeSequence('shutdown')` checks the FSM. When the valve is `operational` it first calls `setpoint(0)` — the position-ramp to fully closed is interruptible — then iterates the sequence states.
+
+### Protected states
+
+`warmingup` and `coolingdown` are **protected** at the state-machine layer (same mechanism as `rotatingMachine`). Aborts during these phases are ignored to preserve safety guarantees.
+
+> [!NOTE]
+> Whether `valve` adopts the `sequenceAbortToken` mechanism from `rotatingMachine` (2026-05-15) for mid-shutdown re-engage races is an open question. TODO: confirm from `generalFunctions/src/state/state.js` whether valve inherits the token automatically. Source: `nodes/valve/src/flow/flowController.js`.
+
+### Position-move bindings
+
+`src/state/stateBindings.js` wires the underlying state machine's `positionChange` event to `host.updatePosition()`. Every position tick triggers:
+
+1. `host.kv = host.curvePredictor.predictKvForPosition(x)` — Kv lookup against the supplier curve.
+2. `MeasurementRouter.updateDeltaP(currentFlow, kv, downstreamP)` — recompute the hydraulic deltaP and write `pressure.predicted.delta`.
+3. `host.emitter.emit('deltaPChange', deltaP)` — upward to the parent VGC.
+
+`updatePosition()` is a no-op outside of `operational` / `accelerating` / `decelerating` (see `MeasurementRouter.updatePositionDependent`).
+
+---
+
+## Hydraulic + measurement pipeline
+
+```mermaid
+flowchart TB
+ set[set.position]:::input --> fc[FlowController.setpoint]
+ fc --> moveTo[state.moveTo]
+ moveTo --> tick[state.emitter 'positionChange']
+ tick --> upd[updatePosition]
+ upd --> kv[curvePredictor.predictKvForPosition]
+ fdat[data.flow]:::input --> mr[MeasurementRouter.updateFlow]
+ fpres[measurement child
pressure.measured.*]:::input --> mp[MeasurementRouter.updatePressure]
+ mr --> dp[updateDeltaP]
+ mp --> dp
+ kv --> dp
+ dp --> hyd[ValveHydraulicModel
calculateDeltaPMbar]
+ hyd --> write[write pressure.predicted.delta]
+ write --> emit[emitter 'deltaPChange']
+ write --> out[Port 0]
+ classDef input fill:#a9daee,color:#000
+```
+
+### Curve loading
+
+At `configure()` startup:
+
+1. `assetResolver.resolveAssetMetadata('valve', model)` resolves supplier / type / allowed units from `generalFunctions/datasets/assetData/` — **may return null** for valve; the predictor tolerates an inline `asset.valveCurve` fallback.
+2. `SupplierCurvePredictor` is constructed with the model, the inline curve override, density, temperature, and valve diameter.
+3. `predictKv` (the curve-evaluation function) is exposed on the host; `host.curveSelection` records which `(densityKey, diameterKey)` lane of the dataset is in use.
+
+The `asset.valveCurve` schema is a nested map keyed by gas-density (kg per nm³) and valve diameter (mm); the leaf carries `{x: [position%], y: [Kv (m³/h)]}` lookup tables.
+
+### Hydraulic formula selection
+
+`ValveHydraulicModel.calculateDeltaPMbar` picks one of two formulas by `serviceType`:
+
+| serviceType | Formula | Notes |
+|:---|:---|:---|
+| `liquid` | `deltaP_bar = (Q / Kv)^2 * (rho / 1000)` | Density override via `runtimeOptions.fluidDensity` (default 997 kg/m³). |
+| `gas` | `deltaP_bar = (Q^2 * rho * T) / (514^2 * Kv^2 * P2_abs)` | Density (default 1.204), absolute downstream pressure, temperature K. Capped at `gasChokedRatioLimit * P2_abs` when choked. |
+
+Inputs are validated: `kv > 0`, `flow !== 0`, and (for gas) a finite downstream gauge pressure are required — otherwise the function returns `null` and the router skips the write.
+
+### Formula units are pinned
+
+`measurement/measurementRouter.js` declares:
+
+```js
+const FORMULA_UNITS = Object.freeze({ pressure: 'mbar', flow: 'm3/h', temperature: 'K' });
+```
+
+The hydraulic model expects q in m³/h, downstream gauge in mbar, and T in K. The router reads MeasurementContainer values back in these units before calling `calculateDeltaPMbar` regardless of the per-node `unitPolicy.output.*` rendering choices.
+
+### Unit policy
+
+Source: `src/specificClass.js` lines 20–24.
+
+| Quantity | Canonical (internal) | Output (rendered) | Required-unit |
+|:---|:---|:---|:---:|
+| Pressure | `Pa` | `mbar` | ✓ |
+| Flow | `m3/s` | `m3/h` | ✓ |
+| Temperature | `K` | `C` | ✓ |
+
+`requireUnitForTypes` means MeasurementContainer rejects writes that omit `unit` for these types.
+
+---
+
+## Lifecycle — what one event does
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant parent as VGC / GUI
+ participant v as valve
+ participant fc as flowController
+ participant fsm as state (FSM)
+ participant hyd as hydraulicModel
+ participant out as Port 0 / parent
+
+ parent->>v: set.position {setpoint: 60}
+ v->>fc: flowController.handleInput('parent','execMovement', 60)
+ fc->>fc: isValidSourceForMode check
+ fc->>fsm: setpoint(60) → state.moveTo(60)
+ fsm-->>v: positionChange events per move tick
+ v->>v: kv = curvePredictor.predictKvForPosition(pos)
+ v->>hyd: calculateDeltaPMbar(q, kv, downP, rho, T)
+ hyd-->>v: { deltaPMbar, details }
+ v->>v: write pressure.predicted.delta
+ v->>parent: emitter.emit('deltaPChange', deltaP)
+ v->>out: notifyOutputChanged (Port 0 delta)
+ parent->>v: data.flow {variant, value, position, unit}
+ v->>v: MeasurementRouter.updateFlow → updateDeltaP
+```
+
+### Mode + source allow-lists
+
+Each input is gated in `flowController.handleInput`:
+
+```js
+if (!this.isValidSourceForMode(source, this.host.currentMode)) {
+ this.logger.warn(`Source '${source}' is not valid for mode '${currentMode}'.`);
+ return { status: false, feedback: msg };
+}
+```
+
+Defaults (per `valve.json` `mode.allowedSources`):
+
+| Mode | Allowed sources |
+|:---|:---|
+| `auto` | `parent, GUI, fysical` |
+| `virtualControl` | `GUI, fysical` |
+| `fysicalControl` | `fysical` |
+| `maintenance` | _(no entry — no source accepted; only `statusCheck` action allowed)_ |
+
+A rejected request logs at warn and short-circuits.
+
+> [!NOTE]
+> Unlike `rotatingMachine`, `valve`'s `flowController` does not currently gate by `allowedActions` — only by source. The schema defines `mode.allowedActions` but it isn't enforced in `flowController.handleInput`. TODO: confirm intentional or backlog. Source: `nodes/valve/src/flow/flowController.js` lines 18–24.
+
+---
+
+## Output ports
+
+| Port | Carries | Sample shape |
+|:---|:---|:---|
+| 0 (process) | Delta-compressed state snapshot — FSM state, position %, mode, every populated MeasurementContainer slot | `{topic, payload: {state, percentageOpen, moveTimeleft, mode, delta_predicted_pressure, downstream_measured_flow, ...}}` |
+| 1 (telemetry) | InfluxDB line-protocol payload (same fields as Port 0) | `valve,id=valve_a state="operational",percentageOpen=60,delta_predicted_pressure=84,...` |
+| 2 (register / control) | `child.register` upward at startup; `positionVsParent` and optional `distance` carried on the msg | `{topic: 'child.register', payload: , positionVsParent, distance}` |
+
+Port-0 key shape is **`__`** (legacy three-segment). Examples: `delta_predicted_pressure`, `downstream_measured_flow`, `downstream_predicted_pressure`. Only keys with finite values are emitted — consumers must cache and merge.
+
+On `query.curve` the node additionally emits `{topic: 'Showing curve', payload: }` synchronously on Port 0.
+
+See [EVOLV — Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) for the full InfluxDB layout.
+
+---
+
+## Event sources
+
+| Source | Where it fires | What it triggers |
+|:---|:---|:---|
+| `state.emitter` `'positionChange'` | `movementManager` during a position move | `updatePosition()` — Kv lookup, deltaP recompute, Port 0 |
+| `state.emitter` `'stateChange'` | `stateManager.transitionTo` resolve | Logged; `getOutput()` picks up the new `state` value on the next tick |
+| `source.emitter` `'deltaPChange'` | `MeasurementRouter.updateDeltaP` after a finite deltaP | Consumed by `valveGroupControl` to update group totals |
+| `source.emitter` `'fluidCompatibilityChange'` | `FluidCompatibility` on upstream-source contract change | Consumed by parent for service-type aggregation |
+| `source.emitter` `'fluidContractChange'` | `FluidCompatibility` when the contract this valve advertises downstream changes | Consumed by downstream consumers |
+| `source.measurements.emitter` `'..'` | MeasurementContainer write | Generic handshake; parents subscribe via `child.measurements.emitter.on` |
+| Inbound `msg.topic` | Node-RED input wire | `commandRegistry` dispatch |
+| `setInterval(statusInterval = 1000)` | `BaseNodeAdapter` | Status badge re-render |
+
+No per-second tick on the domain itself. Position moves drive their own animation interval inside `movementManager`.
+
+---
+
+## Where to start reading
+
+| If you're changing... | Read first |
+|:---|:---|
+| Kv curve load / inline-curve fallback | `src/curve/supplierCurve.js` |
+| Liquid / gas deltaP math, choke cap | `src/hydraulicModel.js` |
+| Measurement → deltaP plumbing (when a recompute fires) | `src/measurement/measurementRouter.js` |
+| Position-tick → updatePosition wiring | `src/state/stateBindings.js` |
+| Mode allow-list, setpoint, executeSequence, pre-shutdown ramp | `src/flow/flowController.js` |
+| Upstream-source fluid tracking, contract aggregation | `src/fluid/fluidCompatibility.js` |
+| `query.curve` reply / status badge / Port 0 shape | `src/io/output.js` |
+| Topic registration, payload validation, aliases | `src/commands/{index, handlers}.js` |
+
+---
+
+## Related pages
+
+| Page | Why |
+|:---|:---|
+| [Home](Home) | Intuitive overview |
+| [Reference — Contracts](Reference-Contracts) | Topic + config + child filters |
+| [Reference — Examples](Reference-Examples) | Shipped flows + debug recipes |
+| [Reference — Limitations](Reference-Limitations) | Known issues and open questions |
+| [valveGroupControl wiki](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Home) | The grouped-control parent |
+| [EVOLV — Architecture](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Architecture) | Platform-wide three-tier pattern |
diff --git a/wiki/Reference-Contracts.md b/wiki/Reference-Contracts.md
new file mode 100644
index 0000000..6cb99f4
--- /dev/null
+++ b/wiki/Reference-Contracts.md
@@ -0,0 +1,254 @@
+# Reference — Contracts
+
+
+
+> [!NOTE]
+> Full topic contract, configuration schema, and child-registration filters for `valve`. Source of truth: `src/commands/index.js`, `src/specificClass.js` `configure()`, and the schema at `generalFunctions/src/configs/valve.json`.
+>
+> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only. For an intuitive overview, return to the [Home](Home).
+
+---
+
+## Topic contract
+
+The registry lives in `src/commands/index.js`. Each descriptor maps a canonical `msg.topic` to its handler; aliases emit a one-time deprecation warning the first time they fire.
+
+
+
+| Canonical topic | Aliases | Payload | Unit | Effect |
+|:---|:---|:---|:---|:---|
+| `set.mode` | `setMode` | `string` (`auto` / `virtualControl` / `fysicalControl` / `maintenance`) | — | Calls `source.setMode(payload)`. Invalid mode logs `warn` and is dropped. |
+| `cmd.startup` | — | `{ source?: string }` | — | `source.handleInput(payload.source ?? 'parent', 'execSequence', 'startup')` — runs the configured `startup` sequence (default `[starting, warmingup, operational]`). |
+| `cmd.shutdown` | — | `{ source?: string }` | — | `source.handleInput(..., 'execSequence', 'shutdown')`. Pre-shutdown the valve ramps to position 0 if currently `operational`. |
+| `cmd.estop` | `emergencystop`, `emergencyStop` | `{ source?: string, action?: string }` | — | `source.handleInput(payload.source ?? 'parent', payload.action ?? 'emergencystop')` — runs the `emergencystop` sequence (default `[emergencystop, off]`). |
+| `set.position` | `execMovement` | `{ source?: string, action?: string, setpoint: number }` | control % (no `units`; no `percent` measure in convert) | `source.handleInput(..., 'execMovement', Number(payload.setpoint))` — moves the valve to a position via `state.moveTo`. |
+| `data.flow` | `updateFlow` | `{ variant, value, position, unit? }` — `variant ∈ {'measured','predicted'}` | `volumeFlowRate` (default `m3/h`) | `source.updateFlow(...)` — pushes a flow value into MeasurementContainer at `` and triggers a deltaP recompute. |
+| `query.curve` | `showcurve` | any | — | `source.showCurve()` — replies on **Port 0** with `{ topic: 'Showing curve', payload: }` via `ctx.send`. |
+| `child.register` | `registerChild` | `string` (child node id); `msg.positionVsParent` carries the position label | — | Resolves the child via `RED.nodes.getNode(payload)` and registers it through `childRegistrationUtils.registerChild(child.source, msg.positionVsParent)`. The valve's `registerChild` records the child for fluid-contract tracking. |
+| `execSequence` | — (legacy umbrella, `_legacy: true`) | `{ source, action, parameter }` with `action ∈ {'startup','shutdown','emergencyStop','emergencystop'}` | — | Content-based router: forwards to canonical `cmd.startup` / `cmd.shutdown` / `cmd.estop` based on `payload.action`. Unknown action logs `warn`. Prefer the canonical `cmd.*` topics. |
+
+
+
+### `execSequence` demux
+
+The pre-refactor topic `execSequence` carried `{source, action, parameter}` where `action` selected the verb. The command registry does not natively dispatch by payload content, so `execSequence` keeps its own descriptor whose handler forwards directly to the canonical `cmd.startup` / `cmd.shutdown` / `cmd.estop` handler based on `payload.action`. A deprecation warning fires once. Future-Phase-7 removal of `execSequence` is a behavioural change — callers must migrate to the canonical topics.
+
+### Mode / source allow-lists
+
+A topic that survives the registry still passes through `flowController.handleInput`:
+
+```js
+if (!this.isValidSourceForMode(source, this.host.currentMode)) {
+ this.logger.warn(`Source '${source}' is not valid for mode '${currentMode}'.`);
+ return { status: false, feedback: msg };
+}
+```
+
+Defaults from `valve.json`:
+
+| Mode | `allowedSources` | `allowedActions` (schema) |
+|:---|:---|:---|
+| `auto` | `parent, GUI, fysical` | `statusCheck, execMovement, execSequence, emergencyStop` |
+| `virtualControl` | `GUI, fysical` | `statusCheck, execMovement, execSequence, emergencyStop` |
+| `fysicalControl` | `fysical` | `statusCheck, emergencyStop` |
+| `maintenance` | _(none)_ | `statusCheck` |
+
+> [!NOTE]
+> `flowController.handleInput` currently enforces only the source side. The schema's `allowedActions` is defined but not gated in code — flagged as a TODO in [Architecture](Reference-Architecture#mode--source-allow-lists). Source: `nodes/valve/src/flow/flowController.js` line 13.
+
+A rejected request logs at warn and short-circuits.
+
+---
+
+## Data model — `getOutput()` shape
+
+Composed each tick by `src/io/output.js` `buildOutput()`. Delta-compressed: consumers see only the keys that changed.
+
+### Per-measurement keys
+
+For every `(type, variant, position)` stored in MeasurementContainer with a finite value, the flattened output emits:
+
+```
+__
+```
+
+This is the **legacy three-segment** key shape (no trailing ``). Position labels are normalised to lowercase. valve does **not** use the four-segment `...` shape that `rotatingMachine` emits.
+
+
+
+| Key | Type | Unit | Sample / notes |
+|:---|:---|:---|:---|
+| `state` | string | — | `"operational"` — one of the FSM states. |
+| `percentageOpen` | number | % | `60` — current position `0..100`. |
+| `moveTimeleft` | number | s | `0` — seconds remaining on the current move. |
+| `mode` | string | — | `"auto"` / `"virtualControl"` / `"fysicalControl"` / `"maintenance"`. |
+| `delta_predicted_pressure` | number | mbar | Predicted deltaP across the valve. Emitted on the next position tick once kv > 0 and a finite flow is known. |
+| `downstream_predicted_flow` | number | m3/h | Last flow pushed via `data.flow` with `variant=predicted`. |
+| `downstream_measured_flow` | number | m3/h | Last flow pushed via `data.flow` with `variant=measured`, or written by a registered flow measurement child. |
+| `downstream_predicted_pressure` | number | mbar | Last predicted pressure written upstream. |
+| `downstream_measured_pressure` | number | mbar | Last measured pressure from a registered pressure measurement child. |
+
+
+
+### Status badge
+
+`buildStatusBadge` in `io/output.js`:
+
+```
+: % 💨 ΔP
+```
+
+(The `position` / `flow` / `deltaP` line only appears in `operational` / `warmingup` / `accelerating` / `decelerating`; other states show just `: `.)
+
+State symbols (per `STATE_SYMBOLS` map in `io/output.js`):
+
+| State | Symbol | Fill |
+|:---|:---:|:---|
+| `off` | ⬛ | red |
+| `idle` | ⏸️ | blue |
+| `operational` | ⏵️ | green |
+| `starting` | ⏯️ | yellow |
+| `warmingup` | 🔄 | green |
+| `accelerating` | ⏩ | yellow |
+| `decelerating` | ⏪ | yellow |
+| `stopping` | ⏹️ | yellow |
+| `coolingdown` | ❄️ | yellow |
+
+When `getFluidCompatibility().status` is `mismatch` or `conflict`, the badge is overridden to a yellow ring with `⚠ ` appended.
+
+---
+
+## Configuration schema — editor form to config keys
+
+Source of truth: `generalFunctions/src/configs/valve.json` plus `nodeClass.buildDomainConfig`.
+
+### General (`config.general`)
+
+| Form field | Config key | Default | Notes |
+|:---|:---|:---|:---|
+| Name | `general.name` | `valve` | Re-derived in `configure()`. |
+| (auto-assigned) | `general.id` | `null` | Node-RED node id. |
+| Default unit | `general.unit` | `m3/h` (schema) | Re-resolved to the unitPolicy `output.flow` in `configure()`. |
+| Enable logging | `general.logging.enabled` | `true` | Master switch. |
+| Log level | `general.logging.logLevel` | `info` | `debug` / `info` / `warn` / `error`. |
+
+### Functionality (`config.functionality`)
+
+| Form field | Config key | Default | Notes |
+|:---|:---|:---|:---|
+| Position vs parent | `functionality.positionVsParent` | `atEquipment` | One of `atEquipment` / `upstream` / `downstream`. Used in the child-register payload that goes UP to VGC. |
+| (hidden) | `functionality.softwareType` | `valve` | Constant. |
+| (hidden) | `functionality.role` | `controller` | Constant. |
+
+### Asset (`config.asset`)
+
+| Form field | Config key | Default | Notes |
+|:---|:---|:---|:---|
+| Asset UUID | `asset.uuid` | `null` | Globally-unique identifier. |
+| Tag code | `asset.tagCode` | `null` | |
+| Geolocation | `asset.geoLocation` | `{x:0, y:0, z:0}` | |
+| Model | `asset.model` | `null` | Optional. If set, resolves curve + supplier / type / allowed units via `assetResolver.resolveAssetMetadata('valve', model)`. If null, the predictor uses the inline `valveCurve`. |
+| Deployment unit | `asset.unit` | `null` | Must appear in the registry's allowed list for the model when set. |
+| Accuracy | `asset.accuracy` | `null` | Optional. |
+| Valve curve | `asset.valveCurve` | `{ '1.204': { '1': { x: [0..100 by 10], y: [0,18,50,95,150,216,337,564,882,1398,1870] } } }` | Nested map: outer key = density (kg per nm³), middle = diameter (mm), leaf = `{x: position%, y: Kv (m³/h)}`. |
+
+Runtime options that bypass `config` and reach `configure()` via `Valve._pendingExtras.runtimeOptions`:
+
+| UI field | runtimeOptions key | Default | Effect |
+|:---|:---|:---|:---|
+| Service type | `serviceType` | derived (`gas` if not `liquid`) | Picks the hydraulic formula in `ValveHydraulicModel`. |
+| Fluid density | `fluidDensity` | model default (997 / 1.204) | Sets `host.rho`. |
+| Fluid temperature K | `fluidTemperatureK` | 293.15 | Sets `host.T`. |
+| Gas choke ratio limit | `gasChokedRatioLimit` | 0.7 | Cap for the gas hydraulic formula. |
+
+> [!WARNING]
+> **Legacy asset fields rejected.** `supplier`, `category`, and `assetType` are no longer node config — the registry derives them from the model. Flows saved before the AssetResolver refactor will throw a startup error from `_rejectLegacyAssetFields` with a clear migration message. Re-open the node, re-select the model from the asset menu, and save.
+
+### State times (`stateConfig.time`)
+
+Set on the state machine via `nodeClass.buildDomainConfig` from editor fields:
+
+| Form field | Config key | Notes |
+|:---|:---|:---|
+| Startup Time | `time.starting` | Time spent in `starting` before transitioning to `warmingup`. |
+| Warmup Time | `time.warmingup` | Time in `warmingup` — **non-interruptible** safety. |
+| Shutdown Time | `time.stopping` | Time in `stopping`. |
+| Cooldown Time | `time.coolingdown` | Time in `coolingdown` — **non-interruptible** safety. |
+
+> [!NOTE]
+> TODO: confirm canonical defaults. `valve.json` does not declare them inline; they come from `generalFunctions/src/configs/state.json` or the parent state-machine schema. Source: `nodeClass.buildDomainConfig` lines 19–26.
+
+### Movement (`stateConfig.movement`)
+
+| Form field | Config key | Notes |
+|:---|:---|:---|
+| Reaction Speed | `movement.speed` | Position ramp rate (%/s). E.g. `1` means setpoint 60 from 0 takes ~60 s. |
+
+### Sequences (`config.sequences`)
+
+State-transition lists per sequence name. Defaults:
+
+| Sequence | States |
+|:---|:---|
+| `startup` | `[starting, warmingup, operational]` |
+| `shutdown` | `[stopping, coolingdown, idle]` |
+| `emergencystop` | `[emergencystop, off]` |
+| `boot` | `[idle, starting, warmingup, operational]` |
+
+Note: unlike `rotatingMachine`, `valve.json` does not ship `entermaintenance` / `exitmaintenance` sequences. TODO: confirm whether maintenance transitions are intentionally manual.
+
+### Mode (`config.mode`)
+
+| Form field | Config key | Default | Range |
+|:---|:---|:---|:---|
+| Mode | `mode.current` | `auto` | `auto` / `virtualControl` / `fysicalControl` / `maintenance` |
+| (defaults) | `mode.allowedActions.` | see [Topic contract](#mode--source-allow-lists) | schema only — not currently gated in code |
+| (defaults) | `mode.allowedSources.` | see above | enforced by `flowController.isValidSourceForMode` |
+
+### Unit policy
+
+Source: `src/specificClass.js` lines 20–24.
+
+| Quantity | Canonical (internal) | Output (rendered) | Required-unit |
+|:---|:---|:---|:---:|
+| Pressure | `Pa` | `mbar` | ✓ |
+| Flow | `m3/s` | `m3/h` | ✓ |
+| Temperature | `K` | `C` | ✓ |
+
+`requireUnitForTypes` means MeasurementContainer rejects writes that omit `unit` for these types. The hydraulic model itself reads back via fixed `FORMULA_UNITS = {pressure: 'mbar', flow: 'm3/h', temperature: 'K'}`.
+
+### Calculation mode (`config.calculationMode`)
+
+`low` / `medium` (default) / `high` — declared in the schema. TODO: confirm whether the dispatch path consults `calculationMode` for trigger frequency. Source: `valve.json` lines 346–366.
+
+---
+
+## Child registration
+
+Source: `src/specificClass.js` lines 100–101 and `src/fluid/fluidCompatibility.js`.
+
+valve **overrides** `BaseDomain.registerChild` so registrations fall into `FluidCompatibility.registerChild` rather than the generic ChildRouter. Upstream sources feed the fluid-contract aggregator; measurement children attach via the standard measurement handshake and land in `MeasurementRouter`.
+
+| Software type | Side-effect | Subscribed events |
+|:---|:---|:---|
+| `machine` / `rotatingmachine` | Stored as upstream source; reads `getFluidContract()` or `asset.serviceType`, defaulting to `liquid` for the rotating-equipment family. Recomputes aggregate service type. | `fluidContractChange` |
+| `machinegroup` / `machinegroupcontrol` | Same; recomputes aggregate service type. | `fluidContractChange` |
+| `pumpingstation` | Same. | `fluidContractChange` |
+| `valvegroupcontrol` | Same. | `fluidContractChange` |
+| `measurement` (asset.type=`pressure`, position=`*`) | Routed through `MeasurementRouter.updatePressure(variant, value, position, unit)`; triggers a deltaP recompute. | `.measured.` |
+| `measurement` (asset.type=`flow`, position=`*`) | Routed through `MeasurementRouter.updateFlow(variant, value, position, unit)`; triggers a deltaP recompute. | `.measured.` |
+
+The valve's `_updateMeasurement` path (via `updateMeasurement(variant, subType, value, position, unit)`) currently handles `pressure` and `flow`. `power` is recognised but ignored.
+
+---
+
+## Related pages
+
+| Page | Why |
+|:---|:---|
+| [Home](Home) | Intuitive overview |
+| [Reference — Architecture](Reference-Architecture) | Code map, FSM, hydraulic-model pipeline |
+| [Reference — Examples](Reference-Examples) | Shipped flows + debug recipes |
+| [Reference — Limitations](Reference-Limitations) | Known issues and open questions |
+| [EVOLV — Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions) | Platform-wide topic rules |
+| [EVOLV — Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry) | Port 0 / 1 / 2 InfluxDB layout |
diff --git a/wiki/Reference-Examples.md b/wiki/Reference-Examples.md
new file mode 100644
index 0000000..e233348
--- /dev/null
+++ b/wiki/Reference-Examples.md
@@ -0,0 +1,143 @@
+# Reference — Examples
+
+
+
+> [!NOTE]
+> Every example flow shipped under `nodes/valve/examples/`, plus how to load them, what they show, and the debug recipes that go with them. Live source: `nodes/valve/examples/`.
+>
+> Pending full node review (2026-05). The shipped flows are currently minimal stubs; tiered demos (matching the `rotatingMachine` template) are on the backlog.
+
+---
+
+## Shipped examples
+
+| File | Tier | Dependencies | What it shows |
+|:---|:---:|:---|:---|
+| `basic.flow.json` | 1 (stub) | EVOLV only | Minimal: one `inject` → one `valve` → one `debug`. Sanity check that the node loads. |
+| `integration.flow.json` | 2 (stub) | EVOLV only | Same shape as basic; placeholder for VGC + measurement integration. |
+| `edge.flow.json` | 3 (stub) | EVOLV only | Placeholder for edge cases (gas-choke, e-stop, invalid setpoints). |
+
+> [!IMPORTANT]
+> **Tiered example flows TODO.** Replace the three stubs with `01 - Basic Manual Control.json` / `02 - Integration with Valve Group.json` / `03 - Dashboard Visualization.json` following the `rotatingMachine` template. Track in `.agents/improvements/IMPROVEMENTS_BACKLOG.md` and validate live against Docker-stack Node-RED. Screenshots / GIFs land under `wiki/_partial-screenshots/valve/` and `wiki/_partial-gifs/valve/`.
+
+---
+
+## Loading a flow
+
+### Via the editor
+
+1. Open the Node-RED editor at `http://localhost:1880`.
+2. Menu → Import → drag the JSON file.
+3. Click Deploy.
+
+### Via the Admin API
+
+```bash
+curl -X POST -H 'Content-Type: application/json' \
+ --data @"nodes/valve/examples/basic.flow.json" \
+ http://localhost:1880/flow
+```
+
+Use `POST /flow` (single tab, full replace) or `POST /flows` (full deploy) depending on whether other tabs are already loaded.
+
+---
+
+## Driving the basic flow manually
+
+The shipped `basic.flow.json` has a single `inject` wired to the valve. To exercise the FSM + hydraulic model, send the following sequence by hand (e.g. via additional inject nodes you wire in, or the Admin API):
+
+1. `set.mode` — payload `"virtualControl"` — lets the GUI source drive the valve.
+2. `cmd.startup` — payload `{}`. FSM walks `idle → starting → warmingup → operational`. Watch `state` on Port 0.
+3. `set.position` — payload `{"setpoint": 60}`. FSM goes `operational → accelerating → operational`; `percentageOpen` ramps 0 → 60 at `movement.speed` %/s.
+4. `data.flow` — payload `{"variant": "measured", "value": 25, "position": "downstream", "unit": "m3/h"}`. Flow lands in MeasurementContainer; `MeasurementRouter.updateFlow` recomputes deltaP. `delta_predicted_pressure` appears on Port 0; `evt.deltaPChange` fires upward.
+5. `data.flow` — payload `{"variant": "measured", "value": 0, "position": "downstream", "unit": "mbar"}` to push downstream pressure as well (needed for the gas-flow path).
+6. `cmd.shutdown` — payload `{}`. Because the valve is `operational`, the controller first ramps `percentageOpen` to 0, then `state` transitions `stopping → coolingdown → idle`.
+
+> [!IMPORTANT]
+> **GIF needed.** Demo recording of steps 1–6 + status badge progression. Save as `wiki/_partial-gifs/valve/01-basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
+
+### Try the position-residue handler
+
+After the valve reaches `operational` at 60 %:
+
+1. Send `set.position = {setpoint: 20}`. State goes `operational → decelerating → ...`.
+2. While `decelerating`, send `set.position = {setpoint: 80}`.
+3. `state.moveTo` recognises the residue state, transitions back to `operational` synchronously, then ramps up to 80. No setpoint is lost.
+
+This is the same residue mechanism `rotatingMachine` uses for fast retargets.
+
+### Try the e-stop sequence
+
+From `operational`, send `cmd.estop`. The valve runs the `emergencystop` sequence (`[emergencystop, off]`). Allowed transitions out of `emergencystop` are `idle` / `off` / `maintenance`. To restart, drop to `idle` first (`cmd.shutdown` from `off` may not work depending on the state graph — TODO: confirm).
+
+---
+
+## Integration with `valveGroupControl`
+
+> [!IMPORTANT]
+> **TODO: Tier-2 example.** A proper integration flow with `valveGroupControl` + 2×valve children + an upstream `rotatingMachine` / `pumpingStation` for fluid-contract tracking is on the backlog. Screenshot under `wiki/_partial-screenshots/valve/02-integration.png`.
+
+When built, the integration flow will demonstrate:
+
+- Auto-registration via Port 2 at deploy — each valve's `child.register` reaches the VGC; no manual wiring needed.
+- Upstream-source registration — a `rotatingMachine` registered as a child of the valve feeds `getFluidContract()` into `FluidCompatibility`. Status flips from `pending` / `compatible` / `mismatch` based on `serviceType` agreement.
+- `evt.deltaPChange` propagation from each valve to the VGC for group-level deltaP aggregation.
+
+---
+
+## Dashboard visualization
+
+> [!IMPORTANT]
+> **TODO: Tier-3 example.** A FlowFuse Dashboard 2.0 page (`@flowfuse/node-red-dashboard`) with control buttons (mode, startup, shutdown, e-stop, position slider), live status (state badge, position %, deltaP, flow), and trend charts (deltaP, position) is on the backlog. Save as `03 - Dashboard Visualization.json`.
+
+---
+
+## Docker compose snippet
+
+To bring up Node-RED + InfluxDB with EVOLV nodes pre-loaded:
+
+```yaml
+# docker-compose.yml (extract)
+services:
+ nodered:
+ build: ./docker/nodered
+ ports: ['1880:1880']
+ volumes:
+ - ./docker/nodered/data:/data/evolv
+ influxdb:
+ image: influxdb:2.7
+ ports: ['8086:8086']
+```
+
+Full file: [EVOLV/docker-compose.yml](https://gitea.wbd-rd.nl/RnD/EVOLV/src/branch/development/docker-compose.yml).
+
+---
+
+## Debug recipes
+
+| Symptom | First thing to check | Where to look |
+|:---|:---|:---|
+| Editor throws `legacy asset field(s) [supplier, ...]` on deploy | Flow predates the AssetResolver refactor. Re-open the node, pick the model from the asset menu, save. The registry derives supplier / category / type. | `src/nodeClass.js` `_rejectLegacyAssetFields`. |
+| Status badge shows `⚠ ` (yellow ring) | `getFluidCompatibility().status` is `mismatch` or `conflict`. An upstream source advertised a service type that doesn't match this valve's expected type. | `src/fluid/fluidCompatibility.js`, `getFluidCompatibility()`. |
+| `delta_predicted_pressure` stuck at `0` or missing | `kv` is 0 (valve closed), the FSM isn't in `operational` / `accelerating` / `decelerating`, or no flow has landed. For gas flow, also needs a finite `downstream_measured_pressure`. | `state.getCurrentState()`, `percentageOpen`, `MeasurementRouter.updateDeltaP`. |
+| `set.position` has no effect | Source not in `mode.allowedSources[currentMode]`. Watch for `Source '...' is not valid for mode '...'` in the warn log. | `src/flow/flowController.js` `isValidSourceForMode`. |
+| `data.flow` payloads aren't reflected on Port 0 | Payload shape: `{variant: 'measured'\|'predicted', value: , position: , unit?: 'm3/h'}`. Missing `variant` warns `Unrecognized variant '...' for flow update`. Missing `value` warns `Received null or undefined value for flow update`. | `src/measurement/measurementRouter.js` `updateFlow`. |
+| Gas-flow deltaP saturates at a ceiling | The choked-flow cap fired (`isChoked: true` in `hydraulicDiagnostics`). Increase `gasChokedRatioLimit` or revise downstream pressure. | `src/hydraulicModel.js` `_calculateGasDeltaP`. |
+| `query.curve` returns empty `valveCurve` | `asset.model` not found by `assetResolver`; the predictor falls back to inline `asset.valveCurve` — check that exists. | `src/curve/supplierCurve.js` `SupplierCurvePredictor.snapshot()`. |
+| FSM stuck in `accelerating` / `decelerating` | A move was aborted with `returnToOperationalOnAbort = false`. Send a new `set.position` — the residue handler in `state.moveTo` transitions back to `operational` first. | `generalFunctions/src/state/state.js` `moveTo` residue branch. |
+| Per-valve Port 0 key names differ from what your dashboard expects | valve uses `__` (e.g. `delta_predicted_pressure`, `downstream_measured_flow`). `rotatingMachine` uses `...`. Don't mix them. | `src/io/output.js` `buildOutput`. |
+
+> Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors.
+
+---
+
+## Related pages
+
+| Page | Why |
+|:---|:---|
+| [Home](Home) | Intuitive overview |
+| [Reference — Contracts](Reference-Contracts) | Topic + config + child filters |
+| [Reference — Architecture](Reference-Architecture) | Code map, FSM, hydraulic-model pipeline |
+| [Reference — Limitations](Reference-Limitations) | Known issues and open questions |
+| [valveGroupControl — Examples](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Reference-Examples) | Group-control demo flows |
+| [EVOLV — Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) | Where valve fits in a larger plant |
diff --git a/wiki/Reference-Limitations.md b/wiki/Reference-Limitations.md
new file mode 100644
index 0000000..c0042a2
--- /dev/null
+++ b/wiki/Reference-Limitations.md
@@ -0,0 +1,111 @@
+# Reference — Limitations
+
+
+
+> [!NOTE]
+> What `valve` does not do, current rough edges, and open questions. Open items live in `.agents/improvements/IMPROVEMENTS_BACKLOG.md` in the superproject.
+>
+> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
+
+---
+
+## When you would not use this node
+
+| Scenario | Use instead |
+|:---|:---|
+| A fixed-restriction orifice (no actuator, no curve) | Model the deltaP externally; valve assumes a position-controlled Kv lookup. |
+| A non-return / check valve (no motorised actuation) | Don't use valve — no FSM-driven position control is exposed for this case. |
+| A pump or compressor (rotating equipment on a Q–H curve) | `rotatingMachine` — it loads a flow / power curve and predicts the operating point. |
+| A throttling device with no known Kv curve | Without `asset.valveCurve` or a registry-resolved model, the predictor stays empty and deltaP recomputes return null. There is no fallback hydraulic model. |
+| A grouped valve manifold | `valveGroupControl` — instantiate this as a child. |
+
+---
+
+## Known limitations
+
+### Gas-choke detection is a hard cap
+
+`hydraulicModel.js` `_calculateGasDeltaP` caps the effective deltaP at `gasChokedRatioLimit * P2_abs` once the raw deltaP exceeds that threshold. The result is a discontinuous step in deltaP — chart traces show a sharp ceiling rather than a smooth choked-flow transition. The `isChoked` flag in `hydraulicDiagnostics` lets consumers detect the regime. Tracked.
+
+### Single-source pressure for the deltaP recompute
+
+`MeasurementRouter` looks for `pressure.measured.downstream` (preferred) or `pressure.predicted.downstream` to feed the gas-flow formula. There is no fallback if both are missing — the hydraulic model returns `null` and `delta_predicted_pressure` simply doesn't get written. The liquid-flow path doesn't need downstream pressure. Tracked.
+
+### Multi-parent registration
+
+Allowed but not exercised in production tests. valve overrides `BaseDomain.registerChild` with `FluidCompatibility.registerChild`, which records upstream sources and aggregates `serviceType`. Teardown ordering (parent gone first vs valve gone first) is not test-covered. Open question.
+
+### `flowController.handleInput` only gates by source, not action
+
+The schema's `mode.allowedActions` is defined but `flowController.handleInput` only enforces `isValidSourceForMode`. A `cmd.shutdown` from a source allowed for the current mode will fire regardless of whether the schema lists `execSequence` in `allowedActions[mode]`. This differs from `rotatingMachine`, which gates both. TODO: confirm whether this is intentional (valve's reduced operational set) or a backlog item.
+
+### `execSequence` legacy umbrella
+
+The `execSequence` topic (with `payload.action = 'startup' | 'shutdown' | 'emergencystop'`) is kept alive for legacy flows. The handler demuxes to the canonical topic; the deprecation warning fires once per session. Scheduled for removal in Phase 7. Use `cmd.startup` / `cmd.shutdown` / `cmd.estop` instead.
+
+### `data.flow` payloads don't clear stale values
+
+If a flow source stops emitting, the last-known value persists in MeasurementContainer. There is no TTL and no explicit clear topic. Workaround: send `value: 0` explicitly. The deltaP recompute then writes 0 (or `null` if it short-circuits on `flow === 0`). Tracked.
+
+### Editor cosmetics don't reflect `asset` derivation
+
+The editor form may still expose supplier / category / type fields even though `_rejectLegacyAssetFields` rejects them on save. Re-saving an old flow surfaces the legacy-fields error until each valve is re-opened and the model re-picked. Cosmetic; the registry is the source of truth.
+
+### No `entermaintenance` / `exitmaintenance` sequences out of the box
+
+`valve.json` ships `startup` / `shutdown` / `emergencystop` / `boot` sequences only. `maintenance` is reachable via `set.mode = maintenance`, but there is no canned state-transition sequence for entering / leaving. `rotatingMachine` defines these in its own config — valve does not. TODO: confirm intentional.
+
+### Position residue handling depends on shared state machine
+
+The residue handler that lets a mid-decel `set.position` re-engage cleanly lives in `generalFunctions/src/state/state.js` `moveTo`. valve inherits the behaviour but the integration test coverage is thin. The sequence-abort token mechanism (`rotatingMachine`, 2026-05-15) may or may not apply to valve's shutdown ramp; TODO: confirm from `FlowController.executeSequence` behaviour when a new `set.position` arrives during the pre-shutdown ramp-to-zero.
+
+---
+
+## Open questions (tracked)
+
+| Question | Where it lives |
+|:---|:---|
+| Gate `mode.allowedActions` in `flowController.handleInput` for parity with `rotatingMachine`? | Internal |
+| Add `entermaintenance` / `exitmaintenance` sequences to `valve.json`? | Internal |
+| Multi-parent teardown ordering | Internal |
+| Add an explicit `data.clear-flow` topic for stale flow cleanup | Internal |
+| Smooth choked-flow transition instead of a hard cap | `hydraulicModel.js` |
+| Does the sequence-abort token mechanism (rotatingMachine, 2026-05-15) apply to valve's pre-shutdown ramp? | Internal — flagged in [Architecture](Reference-Architecture#fsm) |
+| Phase 7 removal of `execSequence` umbrella + legacy aliases | Internal |
+| `calculationMode` (`low` / `medium` / `high`) — does the dispatch path consult it? | `valve.json` schema vs source |
+
+---
+
+## Migration notes
+
+### From pre-AssetResolver
+
+Old flows saved with `supplier`, `category`, or `assetType` fields will throw on deploy:
+
+```
+valve: legacy asset field(s) [supplier, category] are saved on this node.
+After the AssetResolver refactor these are derived from the model id.
+Open the node in the editor, re-select the model, and save to migrate.
+```
+
+The fix is mechanical: open each valve node, re-pick the model from the asset menu, save. No data is lost — the registry has the same supplier / category / type the old flow carried.
+
+### From inline `valveCurve` only
+
+valve still supports `asset.valveCurve` as a fully inline curve fallback when no `asset.model` is set. The predictor logs which `(densityKey, diameterKey)` lane of the dataset it selected. If you migrate from inline-only to a registry-resolved model, the curve may pick a slightly different lane and your operating-point predictions will shift — review on the dashboard before declaring done.
+
+### From `setpoint` topic name (pre-canonical)
+
+The old topic without a `set.` prefix has been retired. Use `set.position` (alias `execMovement`) for position setpoints.
+
+---
+
+## Related pages
+
+| Page | Why |
+|:---|:---|
+| [Home](Home) | Intuitive overview |
+| [Reference — Contracts](Reference-Contracts) | Topic + config + child filters |
+| [Reference — Architecture](Reference-Architecture) | Code map, FSM, hydraulic-model pipeline |
+| [Reference — Examples](Reference-Examples) | Shipped flows + debug recipes |
+| [valveGroupControl — Limitations](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Reference-Limitations) | Where the parent's aggregator sits |
diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md
new file mode 100644
index 0000000..295307d
--- /dev/null
+++ b/wiki/_Sidebar.md
@@ -0,0 +1,19 @@
+### valve
+
+- [Home](Home)
+
+**Reference**
+
+- [Contracts](Reference-Contracts)
+- [Architecture](Reference-Architecture)
+- [Examples](Reference-Examples)
+- [Limitations](Reference-Limitations)
+
+**Related**
+
+- [EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home)
+- [valveGroupControl wiki](https://gitea.wbd-rd.nl/RnD/valveGroupControl/wiki/Home)
+- [rotatingMachine wiki](https://gitea.wbd-rd.nl/RnD/rotatingMachine/wiki/Home)
+- [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns)
+- [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)
+- [Telemetry](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Telemetry)