scripts/test-platform.js iterates each submodule, runs npm test, shows
a per-node pass/fail summary, exits non-zero if any node fails.
Wired as `npm run test:platform` in the parent package.json.
Submodule pointer bumps:
dashboardAPI 2874608 → 92d7eba (Mocha → node:test conversion for edge+integration)
diffuser 0ec9dd1 → 15cfb22 (P10.7a test script fix)
generalFunctions 8ebf31d → 95c5e68 (P10.7a test script fix + remove 5 broken Mocha dupes)
pumpingStation 52d3889 → d2384b1 (P10.7a test script fix)
Current platform-wide gate: 729 pass / 5 fail across 12 submodules
(5 failures are all pre-existing AssertionErrors logged in
OPEN_QUESTIONS.md for Phase 10.5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pump-shutdown deadlock fix split across two submodules:
- rotatingMachine@8f9150e: shutdown sequence clears state.delayedMove
so the abort-and-return-to-operational path doesn't auto-pickup the
queued setpoint and re-engage the pump.
- machineGroupControl@ea2857f: turnOffAllMachines clears MGC's
_delayedCall and serializes per-pump shutdown so PS's 2 s tick loop
can't interrupt an in-flight shutdown.
Live verification on pumpingstation-complete-example demo: basin now
shuts pumps off at stopLevel cleanly, reverses to fill, completes the
hysteresis cycle.
Also disable the trends page in the demo flow (build_flow.py + regen
flow.json). FlowFuse ui-chart's per-series server-side history buffer
(7 charts × ~20 series × 3600-point retention) was saturating the
Node-RED event loop at 129% CPU, making the dashboard freeze on every
click. Trends remain available — just disabled by default; flip the
ui_page_trends "d" key to false to re-enable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WARN now fires only when force-aborting an actually in-flight pump
movement (gate-bypass safety net), not on every no-op tick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the _dispatchInFlight gate that mirrors rotatingMachine
state.delayedMove. Before this, PS at 1 Hz overran in-flight pump
ramps via concurrent handleInput entries, producing the live thrash:
120 aborts / 2 min, pump_b clamped at minFlow.
Includes regression test:
test/mgc-overactive-demand-serialization.integration.test.js
covering concurrent-burst serialization (30 calls → ≤ 5 aborts) and
latest-wins semantic.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- nodes/machineGroupControl@69bdf11 makes DOWNSTREAM single-writer
(handlePressureChange = live aggregate; optimizer target moved to
AT_EQUIPMENT). Closes the ps-mgc-flow-contract failure.
- test/inflow-overcapacity-stability now starts the basin at maxLevel
so PS percControl is immediately 100 % (the actual storm condition)
and uses real-time waits between ticks so movementManager intervals
fire — the previous setImmediate yield was too fast for moves to
progress, making pumps look perma-parked even when behaviour was OK.
Park observations dropped from 83 to 3 across the sim window; final
ctrl converges to ~88 % across all 3 pumps.
All 82 cross-node + node integration tests now pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Submodule bumps land the deadlock fix (state.js residue unpark + MGC
optimalControl dispatch reorder) and pumpingStation stopLevel hysteresis.
- Renames examples/pumpingstation-3pumps-dashboard →
pumpingstation-complete-example with regenerated flow.json. New
dashboard groups, demand-broadcast wiring, S88 placement rule
applied, ui-chart trend-split and link-channel naming follow
.claude/rules/node-red-flow-layout.md.
- New cross-node test harness under test/: end-to-end-pumpingstation
drives PS + MGC + 3 pumps + physics simulator end-to-end and
verifies the ~5/15 min cycle.
- Adds Grafana provisioning dashboards (pumping-station.json) and a
helper sync-example.sh script for export/import to live Node-RED.
- Docker entrypoint + settings + compose tweaks for the persistent
user dir layout used by the demo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- package.json: remove @tensorflow/tfjs and @tensorflow/tfjs-node.
Monster's TF code was already stripped; the deps were stale and kept
pulling a heavy native binary back into every install.
- .gitignore: ignore .repo-mem/ regenerable indexes and per-session
.claude/*.lock runtime files.
- CLAUDE.md: prepend READ-FIRST pointer to .claude/rules/repo-mem.md;
collapse the 'three outputs' bullet to a pointer at node-architecture.
- .claude/rules/telemetry.md: drop Port 0/1/2 duplication; reference
node-architecture.md.
- .claude/rules/testing.md: stop requiring a separate test/edge tier and
the basic/integration/edge example flow trio. Reflects what nodes
actually do.
- .claude/rules/repo-mem.md (new): when-to-call-which guide for the
per-repo memory MCP, anti-patterns, refresh model.
- .mcp.json (new): wire repo-mem stdio server.
- docs/DEVELOPER_GUIDE.md (new): step-by-step guide for adding a new
EVOLV node under the three-layer pattern.
- Bump nodes/pumpingStation to 6ab585b (docs + simulations refresh,
spill-flow path renames consistent with d8490aa).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pumpingStation: predicted-volume hard-floor at 0; spill flow refactored
from flow.predicted.out.<child=overflow> to its own position
flow.predicted.overflow. Drops the spillPrev self-subtraction. New
underflowVolume diagnostic for flow-balance errors. 70/70 tests pass.
generalFunctions: MeasurementContainer.get() strict-resolves explicit
.child(name) — missing named child now returns null instead of falling
through to a sibling. Persistent setChildId remains a hint (no
behavioural change for registered children).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Integrator now clamps predicted volume to [dryRunSafetyVol,
maxVolAtOverflow], records cumulative spill as overflowVolume and
exposes a synthetic flow.predicted.out.overflow rate so net flow
balances to ~0 while pinned. _selectBestNetFlow holds the last
level-rate net flow during overflow so dashboards keep a usable
reading. Top-level predictedOverflowVolume / predictedOverflowRate
fields added to getOutput.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- bounds.js sets HTML5 min/max on every level + percent input so the
spinner can't push values past the basin hierarchy.
- Basin-level violations now surface in a visible ribbon above the
basin diagram and block Deploy via oneditsave.
- Layout polish: widened side panel, tightened basin viewBox, dropped
mode-preview axis labels, moved datum below the tank.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- pumpingStation: hold-then-ramp shift hysteresis driven by
shiftArmPercent (% output threshold for arming) instead of by level.
New e2e integration test exercises the full fill→arm→hold→ramp-down
cycle. Editor preview gains the arming-% horizontal line.
- generalFunctions: add shiftArmPercent to the pumpingStation schema;
add prominent doc block on MeasurementContainer documenting the
`${type}.${variant}.${position}.${childId}` flatten format and the
implicit 'default' childId convention so dashboards don't drop it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- rotatingMachine@399e0a8: editor hygiene (name default, status
clear on close), remove redundant idle-position clamp in
flow/power predictions.
- machineGroupControl@9c79dac: bug fix — stale flow/power cache
now cleared on MGC shutdown so parent pumpingStation sees the
drop immediately. Also awaits shutdown promises correctly and
corrects the NCog integration tests to match centrifugal-pump
physics (Q/P monotonic → NCog=0 → fallback to equal distribution).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follows pumpingStation@3e13512 (rename eval/ → simulations/). The
decision log file is renamed to match the new folder name; an
addendum in the body explains that the rename was a naming
clarification, not a rationale change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four decisions recorded under .agents/decisions/ per project convention
(DECISION-YYYYMMDD-slug.md) to close the loop on today's pumpingStation
refactor + eval + docs work:
- wiki-in-code-repo — why docs+diagrams+code now live in one package
- 5-threshold-naming — old/new field mapping + breaking-change rationale
- mode-tier-template — Tier 1/2/3 classification for mode pages
- eval-harness — why eval/ exists alongside test/
Also bumps nodes/pumpingStation to 66fd3fe (eval harness + Tier 2/3
template pages).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follows pumpingStation@a218945 + generalFunctions@4252292 rename:
- Bump pumpingStation and generalFunctions submodule pointers.
- Update examples/pumpingstation-3pumps-dashboard/ (build_flow.py,
flow.json, README.md) to use the new threshold names. Collapsed
minFlowLevel into startLevel; reshuffled order to match the basin
bottom-to-top: minLevel, startLevel, maxLevel.
- wiki/manuals/README.md: drop the stale pumpingStation.md line and
point readers at pumpingStation/wiki instead (docs have moved into
the node's own repo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pumpingStation 5e2ebe4: overfill safety no longer shuts down machine
groups or blocks level control. Pumps keep running during overfill
(sewer can't stop receiving). Only upstream equipment is shut down.
Demo config: minHeightBasedOn=inlet (not outlet). The minimum height
reference for the basin is the inlet pipe elevation — sewage flows
in by gravity and the basin level can't go below the inlet without
the sewer backing up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sinus inflow: 54-270 m³/h (base 0.015 + amplitude 0.06 m³/s), 4 min
period. Peak needs 1-2 pumps, never all 3 = realistic headroom.
PS control: continuous proportional demand when level > stopLevel, not
just when > startLevel && filling. Pumps now ramp down smoothly as
basin drains toward stopLevel instead of staying stuck at last setpoint.
pumpingStation e8dd657: dead zone elimination
build_flow.py: sinus tuned for gradual pump scaling visibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes:
1. PS outflow triple-counted (pumpingStation c62d8bc): MGC registered
twice + individual pumps registered alongside MGC + dual event
subscription per child. Now: one registration per aggregation level,
one event per child. Volume integration tracks correctly.
2. All 3 pumps always on: minFlowLevel was 1.0 m but startLevel was
2.0 m, so at the moment pumps started the percControl was already
40% → MGC mapped to 356 m³/h → all 3 pumps. Fixed: minFlowLevel
= startLevel (2.0 m) so percControl starts at 0% and ramps
linearly. Now pumps graduate: 1-2 pumps at low level, 3 at high.
3. Generalizable registration rule added as code comments: when a group
aggregator exists (MGC), subscribe to it, not its children. Pick
one event name per measurement type per child.
E2E verified: 2/3 pumps active at 56% fill, volume draining correctly,
pump C at 5.2% ctrl delivering 99 m³/h while pump A stays off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
generalFunctions 29b78a3 -> 086e5fe:
Schema default machineCurve.nq had a dummy pressure slice at key "1"
with fake data. Deep merge injected it alongside real curve data,
pulling the pressure-dimension spline negative at low pressures.
Fix: default to empty {nq: {}, np: {}}.
rotatingMachine 26e253d -> 510a423:
Tests updated for corrected fValues.min (70000 vs old 1).
Trace instrumentation removed. 91/91 green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Architecture change: demo is now driven by a sinusoidal inflow into the
pumping station basin, rather than a random demand generator. The basin
fills from the sinus, and PS's levelbased control should start/stop
pumps via MGC when level crosses start/stop thresholds.
Changes:
- Demo Drivers tab: sinus generator (period 120s, base 0.005 + amp 0.03
m³/s) replaces the random demand. Sends q_in to PS via link channel.
- PS config: levelbased mode, 10 m³ basin, startLevel 1.2 m / stopLevel
0.6 m. Volume-based safeties on, time-based off.
- MGC scaling = normalized (was absolute) so PS's percent-based level
control maps correctly.
- Dashboard mode toggle now drives PS mode (levelbased ↔ manual) instead
of per-pump setMode. Slider sends Qd to PS (only effective in manual).
- PS code (committed separately): _controlLevelBased now calls
_applyMachineGroupLevelControl + new Qd topic + forwardDemandToChildren.
KNOWN ISSUE: Basin fills correctly (visible on dashboard), but pumps
don't start when level exceeds startLevel. Likely cause: _pickVariant
for 'level' in _controlLevelBased may not be resolving the predicted
level correctly, or the safetyController is interfering despite
time-threshold being 0. Needs source-level tracing of the PS tick →
_safetyController → _controlLogic → _controlLevelBased path with
logging enabled. To be debugged in the next session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each node repo now has a CLAUDE.md that declares its S88 hierarchy
level (Control Module / Equipment Module / Unit / Process Cell), the
associated S88 colour, and the placement lane per the superproject's
flow-layout rule set (.claude/rules/node-red-flow-layout.md).
The rule set lives in the superproject only (single source of truth).
Per-node repos reference it. When Claude Code opens a node repo, it
reads the local CLAUDE.md and knows which lane / colour / group to
use when building a multi-node demo or production flow.
Submodule pointer bumps for all 11 nodes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New top-level examples/ folder for end-to-end demos that show how multiple
EVOLV nodes work together (complementing the per-node example flows under
nodes/<name>/examples/). Future end-to-end demos will live as siblings.
First demo: pumpingstation-3pumps-dashboard
- 1 pumpingStation (basin model, manual mode for the demo so it observes
rather than auto-shutting pumps; safety guards disabled — see README)
- 1 machineGroupControl (optimalcontrol mode, absolute scaling)
- 3 rotatingMachine pumps (hidrostal-H05K-S03R curve)
- 6 measurement nodes (per pump: upstream + downstream pressure mbar,
simulator mode for continuous activity)
- Process demand input via dashboard slider (0-300 m3/h) AND auto random
generator (3s tick, [40, 240] m3/h) — both feed PS q_in + MGC Qd
- Auto/Manual mode toggle (broadcasts setMode to all 3 pumps)
- Station-wide Start / Stop / Emergency-Stop buttons
- Per-pump setpoint slider, individual buttons, full status text
- Two trend charts (flow per pump, power per pump)
- FlowFuse dashboard at /dashboard/pumping-station-demo
build_flow.py is the source of truth — it generates flow.json
deterministically and is the right place to extend the demo.
Bumps:
nodes/generalFunctions 43f6906 -> 29b78a3
Fix: childRegistrationUtils now aliases the production
softwareType values (rotatingmachine, machinegroupcontrol) to the
dispatch keys parent nodes check for (machine, machinegroup). Without
this, MGC <-> rotatingMachine and pumpingStation <-> MGC wiring
silently never matched in production even though tests passed.
Demo confirms: MGC reports '3 machine(s) connected'.
Verified end-to-end on Dockerized Node-RED 2026-04-13: pumps reach
operational ~5s after deploy, MGC distributes random demand across them,
basin tracks net flow direction, all dashboard widgets update each second.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
generalFunctions: e50be2e -> 43f6906
Fixes the bug where picking a supplier and then a type left the model
dropdown stuck on "Awaiting Type Selection". Affects every node that
uses the shared assetMenu (measurement, rotatingMachine, pumpingStation,
monster, …). The chained dropdowns now use an explicit downward
cascade with no synthetic change-event dispatch, so the parent handler
can no longer wipe a child after the child was populated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
measurement: d6f8af4 -> <new>
Fixes a regression in the previous measurement editor commit where a
const Temporal Dead Zone error in oneditprepare aborted the function
before the asset / logger / position menu init ran. Menus are now
kicked off first, mode logic is guarded with try/catch and null-checks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
measurement: 495b4cf -> d6f8af4
Makes Input Mode the top-level hierarchy in the editor: analog-only and
digital-only field blocks toggle visibility live based on the dropdown,
legacy nodes default to 'analog', channels JSON gets live validation,
and runtime logs an actionable warning when the payload shape doesn't
match the selected mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bumps:
- nodes/generalFunctions 75d16c6 -> e50be2e (permissive unit check + measurement schema additions)
- nodes/measurement f7c3dc2 -> 495b4cf (digital mode + dispatcher fix + 59 new tests + rewritten README + UI)
Wiki:
- wiki/manuals/nodes/measurement.md — new user manual covering analog and
digital modes, topic reference, smoothing/outlier methods, unit policy,
and the pre-fix dispatcher bug advisory.
- wiki/sessions/2026-04-13-measurement-digital-mode.md — session note with
findings, fix scope, test additions, and dual-mode E2E results.
- wiki/index.md — links both pages and adds the missing 2026-04-13
rotatingMachine session entry that was omitted from the earlier commit.
Status: measurement is now trial-ready in both analog and digital modes.
71/71 unit tests green (was 12), dual-mode E2E on live Dockerized
Node-RED verifies analog regression and a three-channel MQTT-style
payload (temperature/humidity/pressure) dispatching independently with
per-channel smoothing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>