> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
>
> Full topic contract, configuration schema, and child-registration filters for `valveGroupControl`. Source of truth: `src/commands/index.js`, `src/specificClass.js` `configure()`, and the schema at `generalFunctions/src/configs/valveGroupControl.json`.
>
> 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.
| `maintenance` | `statusCheck` | (schema does NOT define `allowedSources.maintenance`; `isValidSourceForMode` returns `false` for every source — effectively monitoring-only) |
> [!WARNING]
> **Source contradiction:** `CONTRACT.md` describes `set.mode` as switching between "auto / manual control modes", but the schema defines four modes (`auto` / `virtualControl` / `fysicalControl` / `maintenance`) and `specificClass.setMode` validates against the schema's enum. The wider four-mode set is the implementation. TODO: tighten the prose in `CONTRACT.md` to enumerate the schema modes.
> [!WARNING]
> **Source contradiction:** the schema declares an `mode.allowedActions` table, but the running implementation only consults `isValidSourceForMode` — **`isValidActionForMode` is not implemented on VGC**. Action allow-lists are effectively dead config. TODO: either implement the action check (mirroring `rotatingMachine`'s pattern) or remove `allowedActions` from the schema.
---
## Data model — `getOutput()` shape
Composed each tick by `src/io/output.getOutput()`. Delta-compressed: consumers see only keys whose `getCurrentValue()` is non-null.
<!-- BEGIN AUTOGEN: data-model — populate via wiki-gen tool (TODO) -->
| `maxDeltaP` | number | `vgc.maxDeltaP` | Cached max delta-P over registered valves (in output pressure unit, default `mbar`). Same data is also surfaced via the measurement-derived key `deltaMax_predicted_pressure`. |
### Measurement-derived keys
For every `(type, variant, position)` in MeasurementContainer with a finite value, the flattened output emits:
```
<position>_<variant>_<type>
```
| Example key | Unit | Source | Notes |
|:---|:---|:---|:---|
| `atEquipment_measured_flow` | m³/h | upstream source `flow.measured.*` events; `data.totalFlow` with `variant=measured` | Total measured flow at the group inlet. |
| `atEquipment_predicted_flow` | m³/h | written by `distributeFlow` as `sum(accepted)` | Sum of per-valve accepted flows after Kv-share + residual. |
| `deltaMax_predicted_pressure` | mbar | written by `calcMaxDeltaP` | Max `pressure.predicted.delta` across registered valves. |
> Delta compression: only changed fields are sent per tick. Consumers must cache and merge. See `outputUtils.formatMsg`.
`flow` is the rounded `flow.measured.atEquipment`, or `flow.predicted.atEquipment` if no measured value is available.
---
## Configuration schema — editor form to config keys
Source of truth: `generalFunctions/src/configs/valveGroupControl.json` plus `nodeClass.buildDomainConfig` (which returns `{}` — no domain overrides).
### General (`config.general`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Name | `general.name` | `"ValveGroupControl"` | Node label, status badge prefix (via `topic`). |
| Position vs parent | `functionality.positionVsParent` | `""` (per `CONTRACT.md`) | Used in the Port-2 register payload sent to the upstream parent. (Not in the JSON schema; supplied at runtime from the editor.) |
`executeSequence(name)` iterates the list and awaits `state.transitionToState(stateName)` per step. The default state object is created at boot with `currentState = 'operational'` so `executeSequence` works without a pre-warmup phase. (See [Architecture — What VGC does NOT have](Reference-Architecture#what-vgc-does-not-have).)
### Calculation mode (`config.calculationMode`)
| Value | Description |
|:---|:---|
| `low` | Calculations run at fixed intervals (time-based). |
| `medium` (default) | Calculations run when new setpoints arrive or measured changes occur (event-driven). |
| `high` | Calculations run on all event-driven info, including every movement. |
> [!WARNING]
> `calculationMode` is in the schema but is not currently consulted by `specificClass` or `nodeClass`. The tick interval is fixed at `tickInterval = 1000 ms` and only retunable through `set.reconcileInterval`. TODO: wire `calculationMode` through or remove it.
### Flow reconciliation (runtime only)
`flowReconciliation` lives on the domain (not in the schema):
| Field | Default | Notes |
|:---|:---:|:---|
| `maxPasses` | `2` | Max iterations of the Kv-share residual loop. |
`requireUnitForTypes: ['pressure', 'flow']` — MeasurementContainer rejects writes that omit `unit` for these types.
---
## Child registration
Source: `src/specificClass.js``_registerValve` / `_registerSource` and `src/sources/fluidContract.js`.
| Software type | Filter | Wired to | Side-effect |
|:---|:---|:---|:---|
| `valve` | `child` exposes `updateFlow`, `state.getCurrentState`, `measurements` (`_isValveLike`) | Stored in `vgc.valves[id]`; events bound. | Subscribes to `state.emitter.positionChange` (→ `calcValveFlows`) and `emitter.deltaPChange` (→ `calcMaxDeltaP`). Triggers an initial `calcValveFlows` + `calcMaxDeltaP` + `refreshFluidContract`. |
| `machine` (incl. canonicalised `rotatingmachine`) | router callback | `registerSource` (`sources/fluidContract`) | Subscribes to 6 flow event names on `child.measurements.emitter`; subscribes to `child.emitter.fluidContractChange`. |
| `machinegroup` (incl. canonicalised `machinegroupcontrol`) | router callback | `registerSource` | Same as `machine`. |
| `pumpingstation` | router callback | `registerSource` | Same as `machine`. |
| `valvegroupcontrol` | router callback | `registerSource` | Cascaded VGC; accepted by router. Not exercised in production — see [Limitations](Reference-Limitations#cascaded-vgc-not-test-covered). |
Position labels accepted from children are `upstream`, `downstream`, `atEquipment` (and case variants — normalised internally).
### Source flow events
`bindSource` attaches a listener for every event name in `SOURCE_FLOW_EVENTS`:
```
flow.predicted.downstream
flow.predicted.atEquipment
flow.predicted.atequipment
flow.measured.downstream
flow.measured.atEquipment
flow.measured.atequipment
```
The handler reads `eventData.value` (number) and `eventData.unit` and writes `vgc.updateFlow(variant, value, 'atEquipment', unit)`. `variant` is derived from the event-name middle segment (`measured` vs `predicted`).
### Fluid contract reconciliation
See [Architecture — Source aggregation](Reference-Architecture#fluid-contract-aggregation) for the full reconciliation logic. The aggregated `fluidContract` is exposed via `vgc.getFluidContract()`: