> Full topic contract, configuration schema, and child-registration filters for `reactor`. Source of truth: `src/commands/index.js`, `src/specificClass.js` `configure()`, and the schema at `generalFunctions/src/configs/reactor.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.
reactor has **no mode, no action allow-lists, no source gating**. All topics are accepted as long as the payload shape is valid. (Contrast with `rotatingMachine`, which gates every input through a mode × source matrix.)
---
## Data model — `getOutput()` shape
Composed each tick by `src/specificClass.js``getOutput()`. Used to build the Port-1 InfluxDB payload; Port 0 carries the engine's `getEffluent` envelope directly.
### Port-0 process payload
The engine's effluent envelope, emitted on every successful `updateState` advance:
Non-finite species values are **omitted** from the output (the `Number.isFinite` guard in `getOutput`); they are not emitted as `null`. Pick one convention per consumer (absent vs null) and document it — see `.claude/rules/output-coverage.md`.
### Species ordering
The 13-species vector is **fixed**:
| Index | Key | Group |
|:---:|:---|:---|
| 0 | `S_O` | soluble |
| 1 | `S_I` | soluble |
| 2 | `S_S` | soluble |
| 3 | `S_NH` | soluble |
| 4 | `S_N2` | soluble |
| 5 | `S_NO` | soluble |
| 6 | `S_HCO` | soluble |
| 7 | `X_I` | particulate |
| 8 | `X_S` | particulate |
| 9 | `X_H` | particulate |
| 10 | `X_STO` | particulate |
| 11 | `X_A` | particulate |
| 12 | `X_TS` | particulate |
Don't reshuffle — `getOutput()` and `_flattenEngineConfig()` both depend on this exact order, as does `additional_nodes/settling-basin` and the downstream `settler` node.
### Status badge
`getStatusBadge()` in `src/specificClass.js`:
```
<EngineType> T=<°C>.X C F=<m³/d>.XX m³/d S_O=<mg/L>.XX mg/L
```
Engine type is the constructor name with `Reactor_` stripped (so `CSTR` or `PFR`). Badge is always green-dot (no FSM-driven state).
---
## Configuration schema — editor form to config keys
Source of truth: `generalFunctions/src/configs/reactor.json` plus `nodeClass.buildDomainConfig` (`src/nodeClass.js`).
### General (`config.general`)
| Form field | Config key | Default | Notes |
|:---|:---|:---|:---|
| Name | `general.name` | `Reactor` | Human-readable. |
| Default unit | `general.unit` | `null` | Unused by the reactor's own logic (the engines pick up units from the schema's `rules.unit` strings); kept for parent compatibility. |
| Position vs parent | `functionality.positionVsParent` | `atEquipment` | Used in the child-register payload that goes UP to whatever parent registers this reactor. Enum: `upstream` / `atEquipment` / `downstream`. |
| Form field | Config key | Schema default | Range / unit | Notes |
|:---|:---|:---|:---|:---|
| Reactor type | `reactor.reactor_type` | `CSTR` | enum: `CSTR` / `PFR` | Selected once at `configure()`. `_buildEngine` calls `.toUpperCase()` so `pfr` and `PFR` both resolve. |
| Volume | `reactor.volume` | `1000` | m³, `> 0` | Used by mass balance and (PFR) surface-area derivation. |
| kLa | `reactor.kla` | `0` | 1/h, `≥ 0`; set `NaN` to disable | Enables internal aeration `OTR = kla · (sat(T) − S_O)`. When `NaN`, `data.otr` is honoured instead. |
| Time step | `reactor.timeStep` | `0.001` | `≥ 0.0001` | Schema declares unit `h`; `baseEngine.js` converts by `÷ 86400` (treating it as seconds). See [Limitations — timeStep unit mismatch](Reference-Limitations#timestep-unit-mismatch). |
| Speed-up factor | `reactor.speedUpFactor` | `1` | `≥ 1` | Multiplies wall-clock Δt when computing `n_iter`. `2` means twice as many internal steps per second. |
### Initial state (`config.initialState`)
13 starting concentrations, all written into the engine's `state` (CSTR: single row; PFR: replicated across all `n_x` grid cells at construction).
| Form field | Config key | Schema default | HTML default | Unit | Notes |
|:---|:---|:---|:---|:---|:---|
| Initial S_O | `initialState.S_O` | `0` | check editor | mg/L | Capped to saturation on the first tick. |
> The HTML form supplies its own defaults; for fields where they differ from the schema (notably `X_A`), the HTML wins at deploy time. Either match the schema in the HTML or audit every deployed flow.
### Unit policy
reactor does **not** declare a UnitPolicy in `specificClass`. Units are carried in the schema's `rules.unit` strings (m³, m, 1/h, mg/L, mmol/L) and consumed by the engines without normalisation through MeasurementContainer's canonical-unit rule. Notable internal conversions:
| Quantity | What the engine uses internally | Where converted |
|:---|:---|:---|
| `timeStep` | days | `baseEngine.js` line ~40: `timeStep = config.timeStep / 86400` |
| `Fs` | m³/d (assumed by mass-balance formulas) | not converted — the caller is expected to push m³/d on `data.fluent` |
| `reactor` | `positionVsParent = 'upstream'` | `engine._connectReactor` | Subscribes to upstream reactor's `stateChange`. Each event triggers downstream `updateState`, which pulls upstream `getEffluent` into `Fs[0]` / `Cs_in[0]` before integrating. |
### Not a child: `diffuser`
`diffuser` (Equipment Module) is **not** registered as a reactor child. It feeds aeration via the `data.otr` topic on Port 0. No child-registration handshake is involved. If you want the diffuser's OTR to drive the reactor, wire the diffuser's process output to the reactor's input directly.
### Unrecognised softwareType
`BaseReactorEngine.registerChild` logs `Unrecognized softwareType: <x>` and drops the registration. There is no `valve`, `rotatingMachine`, etc. acceptance path.