1 Commits

Author SHA1 Message Date
znetsixe
f0a7904985 P11.7 wiki: rewrite Home.md to full 14-section visual-first template
Adapts the canonical WIKI_TEMPLATE.md for dashboardAPI as a utility node
(no BaseDomain, no S88 level, no state chart). Key changes vs P9.3 draft:
- Banner hash bumped to 7b3da23
- Section 1: tightened to exactly describe topology→dashboard flow
- Section 2: adds FlowFuse/browser as downstream consumer of Grafana dashboards
- Section 3: expands capabilities (stable UID, bucket-per-position, alias alias)
- Section 4: adds dashboardapi.js entry node + real config/ template list
- Section 5: AUTOGEN markers regenerated via npm run wiki:all
- Section 6: rewrites diagram with resolveChildSource detail
- Section 7: full sequence including stableUid + links[] step
- Section 8: AUTOGEN marker regenerated; adds meta-field table
- Section 9: adds enableLog/logLevel fields; adds bucket-fallback table
- Section 10: explicit SKIPPED marker (stateless node)
- Section 11: adds inline wiring example
- Section 12: expands to 7 recipes (adds UID-change, machineGroupControl alias)
- Section 13: adds "not a BaseDomain node" + OPEN_QUESTIONS reference
- Section 14: adds OPEN_QUESTIONS.md link for BaseDomain decision; keeps 5 issues

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:06:42 +02:00

View File

@@ -1,11 +1,11 @@
# dashboardAPI # dashboardAPI
> **Reflects code as of `92d7eba` · regenerated `2026-05-11` via `npm run wiki:all`** > **Reflects code as of `7b3da23` · regenerated `2026-05-11` via `npm run wiki:all`**
> If this banner is stale, the page may be out of date. Treat as informative, not authoritative. > If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
## 1. What this node is ## 1. What this node is
**dashboardAPI** is an HTTP-emitter utility node — it listens for `child.register` events from other EVOLV nodes and emits one Grafana dashboard upsert HTTP request per node on Port 0. It is not a domain in the `BaseDomain` sense: no measurements, no tick loop, no children of its own, no parent registration. **dashboardAPI** is a utility node that converts EVOLV node topology into Grafana dashboards. On each `child.register` event it resolves the child's source, walks its direct children, loads per-`softwareType` Grafana JSON templates from `config/`, and emits one HTTP upsert request per dashboard on Port 0 to a downstream `http request` node. It has no measurements, no tick loop, no parent registration, and no BaseDomain/BaseNodeAdapter.
## 2. Position in the platform ## 2. Position in the platform
@@ -15,7 +15,8 @@ flowchart LR
mgc[machineGroupControl<br/>Unit]:::unit -.child.register.-> dash mgc[machineGroupControl<br/>Unit]:::unit -.child.register.-> dash
rm[rotatingMachine<br/>Equipment]:::equip -.child.register.-> dash rm[rotatingMachine<br/>Equipment]:::equip -.child.register.-> dash
meas[measurement<br/>Control Module]:::ctrl -.child.register.-> dash meas[measurement<br/>Control Module]:::ctrl -.child.register.-> dash
dash[dashboardAPI<br/>Utility]:::neutral -->|POST /api/dashboards/db| grafana[(Grafana<br/>HTTP API)] dash[dashboardAPI<br/>Utility]:::neutral -->|"POST /api/dashboards/db"| grafana[(Grafana<br/>HTTP API)]
grafana -.renders dashboards for.-> ff[FlowFuse / Browser]
classDef pc fill:#0c99d9,color:#fff classDef pc fill:#0c99d9,color:#fff
classDef unit fill:#50a8d9,color:#000 classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000 classDef equip fill:#86bbdd,color:#000
@@ -23,19 +24,21 @@ flowchart LR
classDef neutral fill:#dddddd,color:#000 classDef neutral fill:#dddddd,color:#000
``` ```
dashboardAPI has **no S88 level** — it's a utility node. Dashed arrows = inbound `child.register` events; the solid arrow is the outbound HTTP upsert on Port 0 to a downstream `http request` node. dashboardAPI has **no S88 level** — it is a utility node (`#dddddd` neutral). Dashed arrows = inbound `child.register` events (fired at deploy time). The solid arrow is the outbound HTTP upsert on Port 0. The Grafana dashboards that result are what FlowFuse / browser clients view.
## 3. Capability matrix ## 3. Capability matrix
| Capability | Status | Notes | | Capability | Status | Notes |
|---|---|---| |---|---|---|
| Accept `child.register` from any EVOLV node | ✅ | Resolves via `RED.nodes.getNode``node._flow.getNode` → inline payload. | | Accept `child.register` from any EVOLV node | ✅ | Resolves via `RED.nodes.getNode``node._flow.getNode` → inline payload. |
| Emit Grafana dashboard upsert (Port 0) | ✅ | One msg per generated dashboard, shaped for `http request`. | | Emit Grafana dashboard upsert (Port 0) | ✅ | One msg per generated dashboard, shaped for `http request` node. |
| Walk child graph + emit per-child dashboards | ✅ | `includeChildren: true` by default; opt-out per call. | | Walk child graph + emit per-child dashboards | ✅ | `msg.includeChildren: true` by default; opt-out per call. |
| Add root → child dashboard `links[]` | ✅ | Each direct child appears as a link on the root dashboard. | | Add root → child dashboard `links[]` | ✅ | Each direct child appears as a navigation link on the root dashboard. |
| Template selection by `softwareType` | ✅ | Reads from `config/<softwareType>.json`; case-insensitive fallback. | | Template selection by `softwareType` | ✅ | Reads from `config/<softwareType>.json`; case-insensitive fallback. `machineGroupControl``machineGroup.json` alias. |
| Bearer-token auth | ✅ | Set via editor or `INFLUXDB_BUCKET` env (bucket only). | | Stable dashboard UID across re-deploys | ✅ | SHA-1(`softwareType:nodeId`) first 12 chars — deterministic, idempotent upsert. |
| Domain output on Port 0 | | Never. Port 0 carries HTTP request envelopes, not measurements. | | Bearer-token auth header | | Set via editor `bearerToken` field; omitted if empty. |
| InfluxDB bucket injection per position | ✅ | `upstream → lvl1`, `downstream → lvl3`, else `lvl2`; overridden by `defaultBucket` or `INFLUXDB_BUCKET` env. |
| Domain output on Port 0 | ❌ | Port 0 carries HTTP request envelopes only, not measurement data. |
| Port 1 telemetry / Port 2 registration | ❌ | Both unused — see Section 8. | | Port 1 telemetry / Port 2 registration | ❌ | Both unused — see Section 8. |
| Status badge / tick loop / FSM | ❌ | Stateless; no periodic emission. | | Status badge / tick loop / FSM | ❌ | Stateless; no periodic emission. |
@@ -43,31 +46,37 @@ dashboardAPI has **no S88 level** — it's a utility node. Dashed arrows = inbou
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph nodeRED["nodeClass.js — passive adapter"] subgraph entry["dashboardapi.js — entry (Node-RED registration)"]
nc["createRegistry(commands)<br/>dispatch on msg input<br/>NO BaseNodeAdapter"] e["RED.nodes.registerType('dashboardapi')<br/>admin: GET /dashboardapi/menu.js<br/>admin: GET /dashboardapi/configData.js"]
end end
subgraph domain["specificClass.js — DashboardApi service"] subgraph nodeRED["src/nodeClass.js — passive adapter"]
sc["buildDashboard()<br/>generateDashboardsForGraph()<br/>extractChildren()"] nc["_buildConfig(uiConfig)<br/>createRegistry(commands)<br/>_attachInputHandler() → dispatch<br/>NO BaseNodeAdapter"]
end
subgraph domain["src/specificClass.js — DashboardApi service"]
sc["buildDashboard({ nodeConfig, positionVsParent })<br/>generateDashboardsForGraph(rootSource)<br/>extractChildren(nodeSource)<br/>grafanaUpsertUrl()<br/>loadTemplate(softwareType)"]
end end
subgraph cmd["src/commands/"] subgraph cmd["src/commands/"]
h["child.register handler<br/>+ registerChild alias"] h["index.js — child.register + registerChild alias<br/>handlers.js — resolveChildSource + registerChild"]
end end
subgraph tpl["config/ (templates)"] subgraph tpl["config/ — Grafana JSON templates"]
t["<softwareType>.json<br/>Grafana JSON skeletons"] t["aeration | dashboardapi | machine<br/>machineGroup | measurement | monster<br/>pumpingStation | reactor | settler<br/>valve | valveGroupControl"]
end end
nc --> sc e --> nodeRED
nc --> cmd nodeRED --> domain
sc --> tpl nodeRED --> cmd
domain --> tpl
``` ```
| Module | Owns | Read first if you're changing… | | Module | Owns | Read first if you're changing… |
|---|---|---| |---|---|---|
| `src/nodeClass.js` | Input wiring, command dispatch, config build | Topic dispatching, configuration mapping. | | `dashboardapi.js` | Node-RED registration, admin HTTP endpoints | Adding editor endpoints, node category. |
| `src/specificClass.js` | Template loading, dashboard composition, child graph walk | UID stability, links, templating-var substitution. | | `src/nodeClass.js` | Input wiring, command dispatch, config build | Topic dispatching, config key mapping. |
| `src/commands/` | `child.register` handler + legacy alias | Adding / renaming inbound topics. | | `src/specificClass.js` | Template loading, dashboard composition, graph walk | UID stability, links, bucket injection. |
| `config/` | Per-softwareType Grafana templates | Adding support for new node types. | | `src/commands/index.js` | Command registry definition | Adding or renaming inbound topics. |
| `src/commands/handlers.js` | `child.register` handler logic | Payload resolution, emit loop. |
| `config/` | Per-softwareType Grafana JSON templates | Adding support for new EVOLV node types. |
dashboardAPI deliberately does NOT split into `concerns/` — its surface is too narrow. See CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain" for the rationale. dashboardAPI deliberately does NOT use the `concerns/` module pattern — its logic surface is too narrow. See `CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain"` for the full rationale.
## 5. Topic contract ## 5. Topic contract
@@ -81,18 +90,20 @@ dashboardAPI deliberately does NOT split into `concerns/` — its surface is too
<!-- END AUTOGEN: topic-contract --> <!-- END AUTOGEN: topic-contract -->
The legacy `registerChild` alias logs a one-time deprecation warning on first use. The payload can be a string (child id), `{ source: {...} }`, or `{ config: {...} }`; `msg.includeChildren` (default `true`) controls graph-walk depth. The legacy `registerChild` alias logs a one-time deprecation warning on first use. The payload can be a string (child node id), `{ source: {...} }`, or `{ config: {...} }`; `msg.includeChildren` (default `true`) controls graph-walk depth.
There is **no HTTP endpoint contract** for dashboardAPI — it is a Node-RED input node only. The outbound HTTP call shape is documented in Section 8.
## 6. Child registration ## 6. Child registration
dashboardAPI does **not** maintain a child registry of its own. Instead, every inbound `child.register` triggers a one-shot resolution + dashboard emission. No state is held between calls. dashboardAPI does **not** maintain a child registry of its own. Every inbound `child.register` triggers a one-shot resolution + dashboard emission. No state is held between calls.
```mermaid ```mermaid
flowchart LR flowchart LR
src["any EVOLV node<br/>(has functionality.softwareType)"]:::other -->|child.register| dash[dashboardAPI<br/>Utility]:::neutral src["any EVOLV node<br/>(has functionality.softwareType)"]:::other -->|child.register| dash[dashboardAPI<br/>Utility]:::neutral
dash --> resolve[resolve child source<br/>RED.nodes.getNode → _flow → inline] dash --> resolve["resolveChildSource(payload, ctx)<br/>RED.nodes.getNode → _flow → inline"]
resolve --> walk["source.generateDashboardsForGraph(child)<br/>(includes children if flag set)"] resolve --> walk["generateDashboardsForGraph(childSource)<br/>(walks direct children if includeChildren=true)"]
walk --> emit[emit one msg per dashboard<br/>topic='create'] walk --> emit["emit one msg per dashboard<br/>topic='create'"]
emit --> http[(downstream<br/>http request node)] emit --> http[(downstream<br/>http request node)]
classDef neutral fill:#dddddd,color:#000 classDef neutral fill:#dddddd,color:#000
classDef other fill:#ffffff,stroke:#666 classDef other fill:#ffffff,stroke:#666
@@ -101,36 +112,40 @@ flowchart LR
| Inbound softwareType | Filter | Side effect | | Inbound softwareType | Filter | Side effect |
|---|---|---| |---|---|---|
| any | child has `functionality.softwareType` | Loads `config/<softwareType>.json`; emits one upsert msg per dashboard in the graph walk. | | any | child has `functionality.softwareType` | Loads `config/<softwareType>.json`; emits one upsert msg per dashboard in the graph walk. |
| (template missing) | no matching `config/*.json` | Warns and skips that dashboard. No error. | | (template missing) | no matching `config/*.json` | Warns at `warn` level and skips that dashboard. No error thrown. |
## 7. Lifecycle — what one event does ## 7. Lifecycle — what one event does
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant emitter as any EVOLV node participant emitter as any EVOLV node
participant dash as dashboardAPI participant dash as dashboardAPI (nodeClass)
participant api as DashboardApi participant api as DashboardApi (specificClass)
participant out as Port-0 output participant out as Port-0 output
participant grafana as Grafana HTTP API participant grafana as Grafana HTTP API
emitter->>dash: child.register {source / config / id} emitter->>dash: child.register {source / config / id}
dash->>dash: commandRegistry dispatch dash->>dash: commandRegistry.dispatch → handlers.registerChild
dash->>api: generateDashboardsForGraph(child, {includeChildren}) dash->>dash: resolveChildSource(payload, ctx)
api->>api: loadTemplate(softwareType) dash->>api: generateDashboardsForGraph(childSource, {includeChildren})
api->>api: stableUid + updateTemplatingVar api->>api: buildDashboard({ nodeConfig, positionVsParent })
api->>api: walk children via childRegistrationUtils api->>api: loadTemplate(softwareType) from config/
api-->>dash: [{dashboard, uid, title}, ...] api->>api: stableUid = sha1(softwareType:nodeId).slice(0,12)
loop per dashboard api->>api: updateTemplatingVar(measurement, bucket)
dash->>out: msg{topic:'create', url, method, headers, payload} api->>api: extractChildren → build child dashboards
api->>api: add links[] on root dashboard
api-->>dash: [{dashboard, uid, title, softwareType, nodeId}, ...]
loop per dashboard in results
dash->>out: msg{topic:'create', url, method, headers, payload, meta}
out->>grafana: POST /api/dashboards/db out->>grafana: POST /api/dashboards/db
end end
``` ```
One inbound event yields N outbound HTTP messages (N = 1 + direct child count when `includeChildren=true`). One inbound event yields N outbound HTTP messages (N = 1 + direct child count when `includeChildren=true`).
## 8. Data model — `getOutput()` ## 8. Data model — output shape
> **dashboardAPI has no domain output.** It does not extend `BaseDomain` and does not implement `getOutput()`. The `wiki:datamodel` script falls back to the placeholder below. > **dashboardAPI has no domain output.** It does not extend `BaseDomain` and does not implement `getOutput()`. The `wiki:datamodel` script falls back to the hand-curated template below.
<!-- BEGIN AUTOGEN: data-model --> <!-- BEGIN AUTOGEN: data-model -->
@@ -155,72 +170,118 @@ Port 1 (InfluxDB telemetry) and Port 2 (registration / control plumbing) are unu
<!-- END AUTOGEN: data-model --> <!-- END AUTOGEN: data-model -->
See CONTRACT.md for the full envelope spec. **`meta` fields:**
| Field | Type | Value |
|---|---|---|
| `nodeId` | string | `config.general.id` or `config.general.name` |
| `softwareType` | string | `config.functionality.softwareType` |
| `uid` | string | SHA-1(`softwareType:nodeId`) first 12 chars — stable across re-deploys |
| `title` | string | `config.general.name` |
Inbound `msg` fields propagate via spread (`{...msg, ...envelope}`) — caller-supplied correlation/trace fields survive.
See `CONTRACT.md` for the full envelope spec and port definitions.
## 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 (dashboardapi.html)"]
f1[Protocol] f1[Protocol — select http/https]
f2[Host] f2[Grafana Host — text]
f3[Port] f3[Grafana Port — number]
f4[Bearer token] f4[Bearer Token — password]
f5[Default bucket] f5[InfluxDB Bucket — text]
f6[Enable Log — checkbox]
f7[Log Level — select]
end end
subgraph config["Domain config slice"] subgraph config["Runtime config slice (_buildConfig)"]
c1[grafanaConnector.protocol] c1[grafanaConnector.protocol]
c2[grafanaConnector.host] c2[grafanaConnector.host]
c3[grafanaConnector.port] c3[grafanaConnector.port]
c4[grafanaConnector.bearerToken] c4[grafanaConnector.bearerToken]
c5[defaultBucket] c5[defaultBucket]
c6[general.logging.enabled]
c7[general.logging.logLevel]
end end
f1 --> c1 f1 --> c1
f2 --> c2 f2 --> c2
f3 --> c3 f3 --> c3
f4 --> c4 f4 --> c4
f5 --> c5 f5 --> c5
f6 --> c6
f7 --> c7
``` ```
| Form field | Config key | Default | Range | Where used | | Form field | Config key | Default | Range / values | Where used |
|---|---|---|---|---| |---|---|---|---|---|
| Protocol | `grafanaConnector.protocol` | `http` | `http`\|`https` | `grafanaUpsertUrl()` | | Protocol | `grafanaConnector.protocol` | `http` | `http` \| `https` | `grafanaUpsertUrl()` |
| Host | `grafanaConnector.host` | `localhost` | hostname | `grafanaUpsertUrl()` | | Grafana Host | `grafanaConnector.host` | `localhost` | hostname / IP | `grafanaUpsertUrl()` |
| Port | `grafanaConnector.port` | `3000` | 165535 | `grafanaUpsertUrl()` | | Grafana Port | `grafanaConnector.port` | `3000` | 165535 | `grafanaUpsertUrl()` |
| Bearer token | `grafanaConnector.bearerToken` | `''` | string | `Authorization` header | | Bearer Token | `grafanaConnector.bearerToken` | `''` | string (Grafana service account token) | `Authorization: Bearer …` header |
| Default bucket | `defaultBucket` | `''` / `INFLUXDB_BUCKET` env | string | `updateTemplatingVar(bucket)` | | InfluxDB Bucket | `defaultBucket` | `''` → falls back to `INFLUXDB_BUCKET` env → position default | string | `updateTemplatingVar('bucket', …)` |
| Enable Log | `general.logging.enabled` | `false` | boolean | Logger constructor |
| Log Level | `general.logging.logLevel` | `'info'` | `info` \| `debug` \| `warn` \| `error` | Logger constructor |
**Position-based bucket fallback** (when `defaultBucket` is empty):
| `positionVsParent` | Bucket used |
|---|---|
| `upstream` | `lvl1` |
| `downstream` | `lvl3` |
| any other / absent | `lvl2` |
## 10. State chart
> **Skipped.** dashboardAPI is stateless — no FSM, no tick loop, no operating states. See template rule: "Skip this section for stateless nodes (`measurement`, `dashboardAPI`)."
## 11. Examples ## 11. Examples
| Tier | File | What it shows | Status | | Tier | File | What it shows | Status |
|---|---|---|---| |---|---|---|---|
| Basic | `examples/01-Basic.flow.json` | Inject `child.register` payload + a downstream `http request` mock Grafana | ⏳ TBD | | Basic | `examples/basic.flow.json` | Inject `child.register` payload (inline config) + downstream `http request` → Grafana | ⏳ TBD — stub exists |
| Integration | `examples/02-Integration.flow.json` | Real EVOLV node (e.g. pumpingStation) wired into dashboardAPI | ⏳ TBD | | Integration | `examples/integration.flow.json` | Real EVOLV node (e.g. pumpingStation) → child.register → dashboardAPI → Grafana | ⏳ TBD — stub exists |
| Dashboard | _n/a_ | dashboardAPI **is** the dashboard plumbing — no FlowFuse tier | — | | Dashboard | _n/a_ | dashboardAPI **generates** Grafana dashboards — no FlowFuse chart tier for this node | — |
**Wiring pattern** (inline-payload basic test):
```json
[
{ "type": "inject", "payload": { "source": { "config": { "functionality": { "softwareType": "measurement" }, "general": { "id": "pump-a-flow", "name": "Pump A flow" } } } }, "topic": "child.register" },
{ "type": "dashboardapi" },
{ "type": "http request", "method": "POST" }
]
```
## 12. Debug recipes ## 12. Debug recipes
| Symptom | First thing to check | Where to look | | Symptom | First thing to check | Where to look |
|---|---|---| |---|---|---|
| No HTTP request emitted | Did the `child.register` resolve a source? `source.generateDashboardsForGraph` returns `[]` when child has no `config`. | `node_redlog` for "generateDashboardsForGraph skipped" warning. | | No HTTP message emitted on Port 0 | Did `resolveChildSource` return a non-null source? Check that payload has `.source.config` or `.config` or a valid node id. | Container log for "generateDashboardsForGraph skipped" warning. |
| `Skipping dashboard generation: no template` | `config/<softwareType>.json` missing for this node type. | `config/` directory; add a template. | | `Skipping dashboard generation: no template` | `config/<softwareType>.json` missing. | `config/` directory add a template JSON file for the new node type. |
| Empty `Authorization` header | `bearerToken` not set in editor. | Editor form → Bearer token field. | | `machineGroupControl` produces no dashboard | The alias maps to `machineGroup.json` — verify that file exists in `config/`. | `_templateFileForSoftwareType` in `specificClass.js`. |
| Wrong bucket in Grafana | `defaultBucket` overrides position-based default. Check `INFLUXDB_BUCKET` env. | `_buildConfig` in nodeClass.js. | | Empty `Authorization` header | `bearerToken` not set in editor form. | Editor → Bearer Token field. |
| `registerChild` alias warns once | Expected — migrate callers to `child.register`. | Caller's `msg.topic`. | | Wrong InfluxDB bucket in Grafana template variables | `defaultBucket` config or `INFLUXDB_BUCKET` env overrides the position-based default. | `_buildConfig` in `nodeClass.js` + `defaultBucketForPosition` in `specificClass.js`. |
| Dashboard UID changes between deploys | Node id or `softwareType` changed — UID is `sha1(softwareType:nodeId)`. | `stableUid` in `specificClass.js`. |
| `registerChild` alias warns once | Expected — deprecation warning on first use. Migrate caller to topic `child.register`. | Caller `msg.topic`. |
> Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. > Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging sessions.
## 13. When you would NOT use this node ## 13. When you would NOT use this node
- Use dashboardAPI when you want **auto-generated Grafana dashboards** from EVOLV node topology. If you maintain dashboards by hand in Grafana, skip it. - **Use dashboardAPI only for auto-generating Grafana dashboards from EVOLV topology.** If you maintain dashboards manually in Grafana, skip it — it will overwrite your customisations on every registration event.
- Don't use dashboardAPI as a generic HTTP client — it only emits Grafana dashboard upserts. For arbitrary HTTP, use a plain `http request` node. - **Don't use dashboardAPI as a generic Grafana HTTP client.** It only emits dashboard upserts (`POST /api/dashboards/db`). For arbitrary Grafana API calls (annotations, alerts, data sources) use a plain `http request` node.
- Don't put dashboardAPI in a hot data path. It fires on registration events, not on each tick — wiring tick data to it is a misuse. - **Don't wire tick/measurement data into dashboardAPI.** It fires on `child.register` events (deploy time), not on the measurement tick. Wiring Port-0 data from a rotatingMachine or pumpingStation here is a misuse.
- **Don't expect EVOLV child registration to happen automatically.** dashboardAPI passively receives `child.register`; the emitting node (e.g. pumpingStation) must have its Port 2 wired to dashboardAPI's input. See Section 7.
- **Not a BaseDomain node.** dashboardAPI cannot be used wherever a BaseDomain-capable node is required (e.g. as a registered child of machineGroupControl). See OPEN_QUESTIONS.md (2026-05-10) and Section 14.
## 14. Known limitations / current issues ## 14. Known limitations / current issues
| # | Issue | Tracked in | | # | Issue | Tracked in |
|---|---|---| |---|---|---|
| 1 | No domain output — cannot be introspected through the standard `getOutput()` channel; debugging relies on Port 0 HTTP envelopes. | CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain" | | 1 | No domain output — cannot be introspected via the standard `getOutput()` channel. Debugging relies on watching Port 0 HTTP envelopes in a debug node. | `CONTRACT.md → "Why no BaseNodeAdapter / BaseDomain"` |
| 2 | Template discovery is filename-based; renaming a node's `softwareType` requires renaming the template file (with the `machineGroupControl → machineGroup.json` alias being a one-off). | `_templateFileForSoftwareType` in specificClass.js | | 2 | Does not extend `BaseNodeAdapter` / `BaseDomain` — decision deferred pending a passive/HTTP-only mode on `BaseNodeAdapter` (skip-registration + skip-output-stream flags). Until that ships the bespoke adapter shape is correct. | `OPEN_QUESTIONS.md` (2026-05-10) — "dashboardAPI skipped BaseNodeAdapter + BaseDomain". Confirm with team whether to revisit when `BaseNodeAdapter` grows a passive mode. |
| 3 | No retry / circuit-breaker on the downstream `http request` — Grafana outages drop dashboards silently. | TBD | | 3 | Template discovery is filename-based. Renaming a node's `softwareType` requires renaming (or aliasing) the template file. The `machineGroupControl → machineGroup.json` mapping is a one-off alias in `_templateFileForSoftwareType`. | `src/specificClass.js → _templateFileForSoftwareType` |
| 4 | Tier 1/2 example flows not yet written. | P9 wiki cleanup follow-up | | 4 | No retry / circuit-breaker on the downstream `http request` node — Grafana outages silently drop dashboard upserts. | TBD — no issue filed yet |
| 5 | Tier 1/2 example flows exist as stubs only (`basic.flow.json`, `integration.flow.json`) — not yet validated on a live Node-RED instance. | P9 wiki cleanup follow-up |