docs(wiki): rewrite Home.md — correct FSM states + config keys for Section 9/10

Section 10 (State chart): replace invented opening/closing/closed states with
the real shared FSM states (accelerating/decelerating for moves; idle/starting/
warmingup/operational/stopping/coolingdown/emergencystop/off/maintenance for
lifecycle). Show all valid transitions from stateConfig.json allowedTransitions.
Document protected transitions (warmingup, coolingdown) and valve-specific
pre-shutdown ramp-to-zero behaviour.

Section 9 (Config): add missing editor fields from nodeClass.buildDomainConfig
(startup/warmup/shutdown/cooldown times, speed, serviceType, fluidDensity,
fluidTemperatureK, gasChokedRatioLimit). Correct config paths to match actual
stateConfig / runtimeOptions split.

Section 7 (Lifecycle): add FSM state labels to sequence diagram; show
accelerating → operational final step.

Sections 2/6/12/14: minor precision improvements (Port-2 note, abort-deadlock
recipe, execSequence Phase-7 removal warning).

Re-ran npm run wiki:all; AUTOGEN blocks intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-05-11 21:06:26 +02:00
parent 43a17ad83f
commit 95ccc77b25

View File

@@ -5,35 +5,36 @@
## 1. What this node is ## 1. What this node is
**valve** models a single throttling valve. It loads a supplier characteristic curve (Kv-vs-position), drives an FSM-style move sequence for opening/closing, and recomputes pressure drop across the valve from current flow + Kv. Used standalone or as a child of `valveGroupControl` when grouped. **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`.
## 2. Position in the platform ## 2. Position in the platform
```mermaid ```mermaid
flowchart LR flowchart LR
vgc[valveGroupControl<br/>Unit]:::unit -->|set.position| this[valve<br/>Equipment]:::equip vgc[valveGroupControl<br/>Unit]:::unit -->|set.position| this[valve<br/>Equipment]:::equip
src[machine / MGC / PS<br/>upstream source]:::unit -->|child.register| this src["machine / MGC / PS<br/>(upstream source)"]:::unit -->|child.register| this
meas[measurement<br/>type=pressure / flow]:::ctrl -.data.-> this meas[measurement<br/>type=pressure / flow]:::ctrl -.->|data| this
this -->|child.register| vgc this -->|child.register Port 2| vgc
this -->|evt.deltaPChange| vgc this -->|evt.deltaPChange| vgc
this -->|evt.fluidCompatibilityChange| vgc
classDef unit fill:#50a8d9,color:#000 classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000 classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000 classDef ctrl fill:#a9daee,color:#000
``` ```
S88 colours: Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`. S88 colours: Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source: `.claude/rules/node-red-flow-layout.md`.
## 3. Capability matrix ## 3. Capability matrix
| Capability | Status | Notes | | Capability | Status | Notes |
|---|---|---| |---|---|---|
| Predicts deltaP from flow + Kv | ✅ | Hydraulic model picks liquid vs gas formula per `serviceType`. | | 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 curve override supported. | | Loads supplier curve by model name | ✅ | `asset.model` resolved through `loadModel`; inline `valveCurve` override supported. |
| Position move FSM | ✅ | `opening` / `closing` states with interruptible setpoints. | | Position move FSM | ✅ | `accelerating` / `decelerating` states with interruptible setpoints; `moveTo` uses shared state machine. |
| Startup / shutdown sequences | ✅ | Pre-shutdown ramps to position 0 when operational. | | Startup / shutdown sequences | ✅ | Pre-shutdown ramps valve to position 0 before executing stop sequence. |
| Emergency-stop sequence | ✅ | Aliased `cmd.estop` state-machine `emergencystop`. | | Emergency-stop sequence | ✅ | `cmd.estop``emergencystop → off` sequence. |
| Fluid-contract aggregation | ✅ | Tracks upstream service type via registered sources. | | Fluid-contract aggregation | ✅ | Tracks upstream service type via registered sources through `FluidCompatibility`. |
| Gas-choke detection | ⚠️ | Capped at `gasChokedRatioLimit`; surfaced in `hydraulicDiagnostics`. | | Gas-choke detection | ⚠️ | Hard cap at `gasChokedRatioLimit`; surfaced in `hydraulicDiagnostics`. |
| Multi-parent registration | ⚠️ | Allowed but not exercised in production tests. | | Multi-parent registration | ⚠️ | Allowed but not exercised in production tests. |
## 4. Code map ## 4. Code map
@@ -41,10 +42,10 @@ S88 colours: Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Sour
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"] subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
nc["buildDomainConfig()<br/>static DomainClass, commands"] nc["buildDomainConfig()<br/>static DomainClass=Valve, commands<br/>tickInterval=null (event-driven)"]
end end
subgraph domain["specificClass.js — orchestrator (BaseDomain)"] subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
sc["Valve.configure()<br/>wires concern modules<br/>installs FluidCompatibility registerChild"] sc["Valve.configure()<br/>wires concern modules<br/>overrides registerChild → FluidCompatibility"]
end end
subgraph concerns["src/ concern modules"] subgraph concerns["src/ concern modules"]
state["state/<br/>stateBindings → positionChange"] state["state/<br/>stateBindings → positionChange"]
@@ -67,12 +68,13 @@ flowchart TB
| Module | Owns | Read first if you're changing… | | Module | Owns | Read first if you're changing… |
|---|---|---| |---|---|---|
| `state/` | Bindings from state-machine `positionChange``updatePosition()` | Move-finished triggers. | | `state/` | Bindings from state-machine `positionChange``updatePosition()` | Move-finished triggers, position callbacks. |
| `fluid/` | Service-type compatibility, contract aggregation | Gas-vs-liquid mismatch warnings. | | `fluid/` | Service-type compatibility, contract aggregation | Gas-vs-liquid mismatch warnings, upstream fluid tracking. |
| `curve/` | Supplier Kv curve load + interpolation | Curve fitting, model selection. | | `curve/` | Supplier Kv curve load + interpolation | Curve fitting, model selection, density keys. |
| `measurement/` | Pressure/flow routing + deltaP recompute | What triggers a recalc. | | `measurement/` | Pressure/flow routing + deltaP recompute | What triggers a recalc, measurement container writes. |
| `flow/` | Sequence + setpoint execution | Startup / shutdown / move semantics. | | `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. | | `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. |
## 5. Topic contract ## 5. Topic contract
@@ -96,18 +98,18 @@ flowchart TB
## 6. Child registration ## 6. Child registration
valve overrides BaseDomain's default `registerChild` with `FluidCompatibility.registerChild` so upstream-source contracts feed the fluid aggregator. Measurement children attach through the generic measurement handshake. 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`.
```mermaid ```mermaid
flowchart LR flowchart LR
subgraph kids["accepted children (softwareType)"] subgraph kids["accepted children (softwareType)"]
src["machine / rotatingmachine /<br/>machinegroup / pumpingstation /<br/>valvegroupcontrol"]:::unit src["machine / rotatingmachine<br/>machinegroup / pumpingstation<br/>valvegroupcontrol"]:::unit
m["measurement"]:::ctrl m["measurement<br/>type=pressure or flow"]:::ctrl
end end
src -->|getFluidContract| fluid[FluidCompatibility<br/>aggregates serviceType] src -->|getFluidContract| fluid[FluidCompatibility<br/>aggregates serviceType]
m -->|"&lt;type&gt;.measured.&lt;position&gt;"| router[MeasurementRouter<br/>updatePressure / updateFlow] m -->|"&lt;type&gt;.measured.&lt;position&gt;"| router[MeasurementRouter<br/>updatePressure / updateFlow]
router --> deltaP[updateDeltaP<br/>writes pressure.predicted.delta] router --> deltaP[updateDeltaP<br/>writes pressure.predicted.delta]
fluid --> evt1[evt.fluidCompatibilityChange] fluid --> evt1["evt.fluidCompatibilityChange<br/>evt.fluidContractChange"]
deltaP --> evt2[evt.deltaPChange] deltaP --> evt2[evt.deltaPChange]
classDef unit fill:#50a8d9,color:#000 classDef unit fill:#50a8d9,color:#000
classDef ctrl fill:#a9daee,color:#000 classDef ctrl fill:#a9daee,color:#000
@@ -115,11 +117,11 @@ flowchart LR
| softwareType | onRegister side-effect | Subscribed events | | softwareType | onRegister side-effect | Subscribed events |
|---|---|---| |---|---|---|
| `machine` / `rotatingmachine` | Stored as upstream source; reads `getFluidContract()` or default `liquid`. | `fluidContractChange`. | | `machine` / `rotatingmachine` | Stored as upstream source; reads `getFluidContract()` or defaults to `liquid`. | `fluidContractChange` |
| `machinegroup` / `machinegroupcontrol` | Same; recomputes aggregate service type. | `fluidContractChange`. | | `machinegroup` / `machinegroupcontrol` | Same; recomputes aggregate service type. | `fluidContractChange` |
| `pumpingstation` | Same. | `fluidContractChange`. | | `pumpingstation` | Same. | `fluidContractChange` |
| `valvegroupcontrol` | Same. | `fluidContractChange`. | | `valvegroupcontrol` | Same. | `fluidContractChange` |
| `measurement` | Routed via measurement handshake; values land in MeasurementContainer. | `<type>.measured.<position>`. | | `measurement` | Routed via measurement handshake; values land in `MeasurementContainer`. | `<type>.measured.<position>` |
## 7. Lifecycle — what one event does ## 7. Lifecycle — what one event does
@@ -127,19 +129,21 @@ flowchart LR
sequenceDiagram sequenceDiagram
participant parent as valveGroupControl participant parent as valveGroupControl
participant valve as valve participant valve as valve
participant state as state FSM participant fsm as state FSM
participant hyd as hydraulicModel participant hyd as hydraulicModel
participant out as Port-0 participant out as Port-0
parent->>valve: set.position { setpoint: 60 } parent->>valve: set.position { setpoint: 60 }
valve->>state: moveTo(60) valve->>fsm: moveTo(60)
state-->>valve: positionChange ticks fsm-->>fsm: operational → accelerating
fsm-->>valve: positionChange ticks (stateBindings)
valve->>valve: predictKv(position) valve->>valve: predictKv(position)
valve->>hyd: calculateDeltaPMbar(q, kv, downP, rho, T) valve->>hyd: calculateDeltaPMbar(q, kv, downP, rho, T)
hyd-->>valve: { deltaPMbar, details } hyd-->>valve: { deltaPMbar, diagnostics }
valve->>valve: write pressure.predicted.delta valve->>valve: write pressure.predicted.delta
valve->>parent: emitter.emit('deltaPChange', deltaP) valve->>parent: emitter.emit('deltaPChange', deltaP)
valve->>out: msg { topic, payload (delta-compressed) } valve->>out: msg { topic, payload (delta-compressed) }
fsm-->>fsm: accelerating → operational (setpoint reached)
``` ```
## 8. Data model — `getOutput()` ## 8. Data model — `getOutput()`
@@ -162,27 +166,31 @@ What lands on Port 0. Composed in `io/output.buildOutput`, then delta-compressed
<!-- END AUTOGEN: data-model --> <!-- END AUTOGEN: data-model -->
Measurement-derived keys follow the legacy `<position>_<variant>_<type>` shape (e.g. `downstream_predicted_flow`, `delta_predicted_pressure`) and are emitted only when the container holds a finite value. Measurement keys follow the legacy `<position>_<variant>_<type>` 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 ## 9. Configuration — editor form ↔ config keys
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph editor["Node-RED editor form"] subgraph editor["Node-RED editor form"]
f1[Mode] f1[Reaction Speed]
f2[Asset model] f2[Asset model / supplier / category]
f3[Service type] f3[Service type]
f4[Diameter] f4[Fluid density / temperature K]
f5[Fluid density / temperature] f5[Gas choke ratio limit]
f6[Inline valveCurve override] f6[Startup / warmup / shutdown / cooldown times]
f7[Log level / enableLog]
f8[positionVsParent]
end end
subgraph config["Domain config slice"] subgraph config["Domain config slice"]
c1[mode.current] c1["movement.speed (stateConfig)"]
c2[asset.model] c2[asset.model]
c3[asset.serviceType] c3["runtimeOptions.serviceType → hydraulicModel"]
c4[asset.valveDiameter] c4["runtimeOptions.fluidDensity / fluidTemperatureK"]
c5[asset.fluidDensity / fluidTemperatureK] c5["runtimeOptions.gasChokedRatioLimit"]
c6[asset.valveCurve] c6["stateConfig.time.starting / warmingup / stopping / coolingdown"]
c7["general.logging.enabled / logLevel"]
c8["functionality.positionVsParent → Port-2 registration"]
end end
f1 --> c1 f1 --> c1
f2 --> c2 f2 --> c2
@@ -190,70 +198,116 @@ flowchart TB
f4 --> c4 f4 --> c4
f5 --> c5 f5 --> c5
f6 --> c6 f6 --> c6
f7 --> c7
f8 --> c8
``` ```
| Form field | Config key | Default | Range | Where used | | Form field | Config path | Default | Range / type | Where used |
|---|---|---|---|---| |---|---|---|---|---|
| Mode | `mode.current` | per schema | enum | `setMode`, `flowController` | | Reaction Speed | `movement.speed` (stateConfig) | `1` | > 0 (%/s) | `MovementManager` — sets rate of position change |
| Asset model | `asset.model` | `null` | string | `SupplierCurvePredictor` | | Asset model | `asset.model` | `'Unknown'` | string | `SupplierCurvePredictor` — selects Kv curve dataset |
| Service type | `asset.serviceType` | per asset | `gas` / `liquid` | `ValveHydraulicModel` | | Service type | `runtimeOptions.serviceType` | `null` (from asset) | `'gas'` / `'liquid'` | `ValveHydraulicModel` formula selection |
| Diameter | `asset.valveDiameter` | per asset | > 0 (m) | curve key selection | | Fluid density | `runtimeOptions.fluidDensity` | model default | > 0 (kg/m³) | liquid hydraulic formula |
| Fluid density | `asset.fluidDensity` | model default | > 0 (kg/m³) | hydraulic formula | | Fluid temperature | `runtimeOptions.fluidTemperatureK` | model default | > 0 (K) | gas hydraulic formula |
| Fluid temperature | `asset.fluidTemperatureK` | model default | > 0 (K) | hydraulic formula | | Gas choke limit | `runtimeOptions.gasChokedRatioLimit` | per asset | 01 | gas choke cap in `ValveHydraulicModel` |
| Choked-flow cap | `asset.gasChokedRatioLimit` | per asset | 01 | gas formula clamp | | 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 ## 10. State chart
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
[*] --> off [*] --> off
off --> idle: cmd.startup off --> idle : cmd.startup (boot sequence)
idle --> opening: set.position > 0 off --> emergencystop : cmd.estop
opening --> operational: position reached off --> maintenance : set.mode=maintenance
operational --> opening: set.position changed
operational --> closing: set.position < current idle --> starting : cmd.startup
closing --> closed: position == 0 idle --> off : (direct transition)
closed --> opening: set.position > 0 idle --> emergencystop : cmd.estop
operational --> stopping: cmd.shutdown (ramps to 0) idle --> maintenance : set.mode=maintenance
stopping --> idle: cooldown elapsed
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 operational --> emergencystop : cmd.estop
emergencystop --> off: cmd.reset
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
``` ```
The `opening` / `closing` states cover the move-in-progress window; `positionChange` ticks fire until the setpoint is reached, then the FSM lands on `operational`. Pre-shutdown ramp to 0 is enforced by `FlowController.executeSequence('shutdown')`. **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 ## 11. Examples
| Tier | File | What it shows | Mandatory? | | Tier | File | What it shows | Mandatory? |
|---|---|---|---| |---|---|---|---|
| Basic | `examples/01-Basic.flow.json` | Inject `set.position` + dashboard, no parent | ✅ | | Basic | `examples/basic.flow.json` | Inject `set.position` + minimal wiring, no parent | ✅ |
| Integration | `examples/02-Integration.flow.json` | valve + VGC + upstream source | ✅ | | Integration | `examples/integration.flow.json` | valve + VGC + upstream measurement source | ✅ |
| Dashboard | `examples/03-Dashboard.flow.json` | Live FlowFuse charts (position, ΔP, flow) | ⭕ | | Edge | `examples/edge.flow.json` | Edge-case inputs (gas, choke, estop, bad setpoints) | ⭕ optional |
Screenshots under `wiki/_partial-screenshots/valve/` when produced. Docker compose snippet under `examples/README.md`. 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 ## 12. Debug recipes
| Symptom | First thing to check | Where to look | | Symptom | First thing to check | Where to look |
|---|---|---| |---|---|---|
| Status badge shows `⚠ no input` | Did any pressure / flow measurement register? Watch Port 2. | Editor debug tap on Port 2 | | Status badge shows `⚠ no input` | Did any pressure / flow measurement register? Watch Port 2. | Debug tap on Port 2 |
| `delta_predicted_pressure` stuck at zero | Is `kv > 0`? FSM may be in `off` / `closed`. | `state.getCurrentState()` | | `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` / `conflict`. | `getFluidCompatibility()` | | Gas mismatch warning on status badge | `fluidCompatibility.status` is `mismatch` or `conflict`. | `getFluidCompatibility()` |
| `query.curve` returns empty curve | Asset model not found by `loadModel`; fallback to `config.asset.valveCurve`. | `SupplierCurvePredictor.snapshot()` | | `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` | | 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. > 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 ## 13. When you would NOT use this node
- Use valve for a **throttling element** with a known Kv curve. For a fixed-restriction orifice with no actuator, model the deltaP externally. - 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 curve fitting is exposed. - 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 provides flow directly and no pressure-drop estimate is needed; just wire the source straight to the parent. - 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 ## 14. Known limitations / current issues
| # | Issue | Tracked in | | # | 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` | | 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` | | 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. | P10 test-suite refactor | | 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 |