Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.1 KiB
Reference — Limitations
Note
Pending full node review (2026-05). What
diffuserdoes not do, current rough edges, and open questions. Some items below are auditor inferences from CONTRACT.md and the source — flagged where uncertain. Open items live in.agents/improvements/IMPROVEMENTS_BACKLOG.mdin the superproject.
When you would not use this node
| Scenario | Use instead |
|---|---|
| Coarse-bubble / jet / surface aeration without a fine-bubble OTR curve | Model OTR externally and feed the reactor's α-OTR endpoint directly. |
| The blower already publishes oxygen-transfer telemetry | Don't duplicate — pipe the blower's oKgo2H straight into the reactor. |
| You only need flow-per-element warning bands without OTR | A measurement node with bands is lighter and has typed series. |
| A diffuser zone with multiple distinct membrane types | Instantiate one diffuser node per type and sum the oKgo2H upstream. |
| A non-fine-bubble curve at non-standard membrane area | Configure diffuser.membraneAreaPerElement to override the curve metadata. |
Known limitations
Stateless by design — no FSM
The diffuser has no state machine. The idle flag is a derived predicate (i_flow ≤ 0), not a state. There is no startup / warmup / cooldown / e-stop concept — the model is purely algebraic. The blower upstream owns those concerns.
If you need a "diffuser is offline / under maintenance" indicator, today that has to be carried by the parent or by a separate metadata path. Open question whether a future revision should introduce a soft mode field for dashboards.
No typed MeasurementContainer emission
All output flows via getOutput() and the delta-compressed Port 0 message. The diffuser does not publish typed measurement series (otr.measured.atequipment.<id>, etc.) through MeasurementContainer.emitter. Parents that want OTR via the standard ChildRouter handshake must wait for the future phase that promotes oOtr / oZoneOtr to typed series. Tracked in CONTRACT.md ## Events emitted by source.measurements.emitter.
No mode / source allow-lists
Unlike rotatingMachine and pumpingStation, the diffuser accepts every input topic from every source. There is no auto / virtualControl / fysicalControl distinction. Open question whether this matters in practice — the diffuser has no "physical" path today (no real OTR sensor wired in).
Alarm hysteresis is hardcoded
warning.flow.min.hyst = 2 and alarm.flow.min.hyst = 10 are literals in configure() (specificClass.js). They are not config-driven. If a particular installation has wider acceptable bands, you currently have to edit the source. Tracked.
Asset model fallback can mask configuration drift
_loadSpecs() falls back to DEFAULT_DIFFUSER_MODEL (gva-elastox-r) when the configured model is missing from the registry. This is a deliberate availability-first choice (don't crash the constructor in production) — but it means a typo or stale asset id silently runs against the GVA curve instead of erroring. The constructor logs the fallback; consumers should monitor logs at deploy.
data.flow clamps silently
setFlow(v) clamps to Math.max(0, ...). Negative payloads land as zero with no warn. This mostly does the right thing (negative airflow is nonsensical) but a payload bug upstream is invisible. Open question whether to log at warn on clamp.
No typed MeasurementContainer consumption
The diffuser is a leaf node and accepts no children. There is no path for a real OTR sensor (a hypothetical dissolved-oxygen probe) to feed into this node and refine the curve-based prediction. The whole pipeline is open-loop. Tracked as a future scope item.
Hidden config defaults
diffuser.localAtmPressure (1013.25 mbar) and diffuser.waterDensity (997 kg/m³) are config keys but typically not exposed in the editor form. If your plant runs at significant altitude or the process water has unusual density, you have to override them via the raw config or a Setup-tab inject. Cosmetic; tracked.
Pre-Phase-6 port-count migration
Pre-refactor the diffuser exposed four outputs: process, dbase, a dedicated reactor-control message with topic: 'OTR', and parent registration. The reactor-control message merged into Port 0 as oZoneOtr; consumers reading the dedicated control port must migrate to payload.oZoneOtr. No alias is provided — the shape differs (single value vs full process payload). See Migration notes.
Test scaffolding is placeholder
test/basic/, test/integration/, test/edge/ each contain a single structure-…test.js placeholder file. test/README.md still says "Placeholder structure (diffuser currently has no runtime module files)" — the README is stale; the runtime modules exist (src/specificClass.js, src/nodeClass.js, src/commands/*.js). A real domain-test pass against the supplier curves is TODO.
Open questions (tracked)
| Question | Where it lives |
|---|---|
Should oOtr / oZoneOtr be promoted to typed MeasurementContainer series so parents can subscribe via ChildRouter? |
Internal — future phase |
| Should alarm hysteresis (2 % / 10 %) be config-driven? | Internal |
Should setFlow(v) log a warn on negative clamp? |
Internal |
Should the editor expose localAtmPressure / waterDensity for altitude / non-water cases? |
Internal |
Should a soft mode field exist for "offline / maintenance" dashboards even without an FSM? |
Internal |
| Should the curve-fallback path warn at deploy AND every N ticks for a configurable window? | Internal |
Refresh test/README.md — it incorrectly claims the node has no runtime modules |
Internal — stale doc |
Refresh examples/README.md — it is a one-line "Placeholder structure" |
Internal — stale doc |
Migration notes
From pre-Phase-6 four-port output
Pre-Phase-6 the diffuser emitted on four ports:
| Port (old) | Topic | Replacement |
|---|---|---|
| 0 | process | unchanged |
| 1 | dbase | unchanged |
| 2 | OTR (single value, reactor control) |
merged into Port 0 as payload.oZoneOtr. No alias provided. |
| 3 | parent registration | now Port 2 |
If you have a downstream function or link out that listens for msg.topic === 'OTR' on the legacy port-2, switch it to reading payload.oZoneOtr from the Port-0 process message. The shape differs (single number vs full process payload) so the migration is not mechanical — you must update the downstream node's payload-extraction logic too.
From legacy alias topics
Aliases are accepted but log a one-time deprecation warning the first time they fire:
| Alias (deprecated) | Canonical |
|---|---|
air_flow |
data.flow |
density |
set.density |
height_water |
set.water-height |
header_pressure |
set.header-pressure |
elements |
set.elements |
alfaFactor |
set.alfa-factor |
There is no deprecation-removal date set. Migrate flows opportunistically.
From diffuser.density semantics drift
An earlier refactor mis-tagged diffuser.density as "elements per m²". The current semantics — reflected in the schema description and the curve files — is bottom-coverage percentage (the fraction of tank floor occupied by membrane). Typical fine-bubble installs run 10–25 %. If a saved flow still carries the legacy elements-per-m² value (typically 2.4), update it to the bottom-coverage % the asset is actually installed at. The default has been updated to 15.
Related pages
| Page | Why |
|---|---|
| Home | Intuitive overview |
| Reference — Contracts | Topic + config + child registration |
| Reference — Architecture | Code map, OTR / ΔP pipeline, output ports |
| Reference — Examples | Shipped flows + debug recipes |
| reactor — Limitations | Where the typical parent currently consumes diffuser output |