Files
nr-flow-validator/README.md
T
david f95e8b8ecb Flat-structure generator + Rego guards, rename Laser & Fog to DJ Booth
Closes #1.

Generator (scripts/flow2json.py):
- Emit all nodes at top level with z field (no wrapped tabs)
- Set icon: "" on ui-button to avoid Dashboard 2.0 prependIcon TypeError
- Set z field on ui-* widgets so they render in Dashboard 2.0 SPA
- Use ui-text label from spec, not name

Policy (policy/flow.rego) — 9 new deny rules:
- deny_wrapped_tab: tab with internal nodes[] (nodered 5.0 bug)
- deny_missing_wires: missing or non-array wires (Flow.js rewireNode crash)
- deny_no_autoconnect: mqtt-broker must reconnect
- deny_invalid_protocol: protocolVersion must be 3/4/5
- deny_unused_broker: broker declared but never referenced
- Plus deny_broker_port / deny_group_ref / deny_page_ref / deny_wire_ref refinements

Flow (zigbee-monitor.*):
- 22-node flat flow: button-in → btn-filter → 4 parallel plug-outs
- 4 ui-text widgets + ui-button for permit-join (60s)
- Renamed plug: "Laser & Fog" → "DJ Booth" (matches z2m friendly_name)

Repo hygiene:
- justfile: fix host IP 192.168.9.147 → 192.168.81.147, /ui → /dashboard
- README.md: document 8 generation invariants and rule table

Verified end-to-end: publishing {"action":"single"} on
zigbee2mqtt/Door Button triggers {"state":"TOGGLE"} on all four
.../set topics including the renamed DJ Booth.

462/462 conftest tests pass.
2026-06-30 23:16:17 -07:00

3.2 KiB

nr-flow-validator

Declarative Node-RED flows with conftest/Rego validation. Generates flows.json from a YAML spec, validates against a Rego policy pack, deploys via the Node-RED HTTP API.

Layout

flows/<name>.yaml          # declarative spec
scripts/flow2json.py       # YAML → JSON generator
policy/flow.rego           # Rego policy pack (conftest)
output/<name>.json         # generated flows.json

Quick start

just new my-flow           # copy template
$EDITOR flows/my-flow.yaml
just validate              # generate + validate
just deploy                # push to Node-RED at 192.168.81.147:1880

Generation invariants

These are enforced by policy/flow.rego and burned in from real bugs found in deployment. The generator follows them by construction; the policy is the second line of defense.

  1. Flat structure only. Node-RED 5.0 silently breaks MQTT broker connection when a tab has an internal nodes: [...] array. Always emit all nodes at top level with a z field referencing the tab id. Use policy/flow.rego::deny_wrapped_tab to reject wrapped structure.
  2. Every node needs wires: []. Missing or non-array wires throws TypeError in Flow.js::rewireNode. deny_missing_wires enforces this.
  3. MQTT brokers must have autoConnect: true. Without it the broker never reconnects after the first user disconnects. deny_no_autoconnect.
  4. protocolVersion must be "3", "4", or "5". deny_invalid_protocol.
  5. ui-button must have icon: "". Dashboard 2.0 throws TypeError on prependIcon otherwise. Generator sets this explicitly.
  6. Dashboard widgets need a z field referencing their tab id, otherwise the SPA renders the group but the widgets never appear. Generator sets this.
  7. Wire references must resolve to known node ids. deny_wire_ref.

Validation rules summary

Rule Catches
deny_missing_type node without type
deny_missing_field required field missing for type
deny_broker_ref mqtt in/out references unknown broker
deny_group_ref ui-* references unknown group
deny_page_ref ui-group references unknown page
deny_wire_ref wire targets unknown node
deny_missing_flow wire references a node not in z of a tab
deny_isolated_node node with no wires in/out (config nodes exempted)
deny_broker_port mqtt-broker port outside 1-65535
deny_wrapped_tab tab with internal nodes array (nodered 5.0 bug)
deny_missing_wires node without wires array
deny_no_autoconnect mqtt-broker without autoConnect: true
deny_invalid_protocol mqtt-broker protocolVersion not in 3/4/5
deny_unused_broker broker defined but never referenced

Deployment target

Node-RED on RPi 3B+ DietPi at 192.168.81.147:1880, persisted via Docker bind mount to /root/nodered. The deploy target's flows.json is the source of truth in production; this generator is for declarative editing.

Dashboard 2.0 SPA quirk

Dashboard 2.0's dist/index.html ships with relative asset paths that break at /dashboard/home. /data/start.sh on the host patches in <base href="/dashboard/"> on container start. Without that patch the SPA loads but assets 404 and nothing renders.