f95e8b8ecb
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.
61 lines
3.2 KiB
Markdown
61 lines
3.2 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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.
|