Skip to content

Read the house before designing one: miakapp discover - #12

Merged
Mathieu2301 merged 1 commit into
mathieu/agent-guidefrom
mathieu/agent-discover
Sep 14, 2026
Merged

Mathieu2301 merged 1 commit into
mathieu/agent-guidefrom
mathieu/agent-discover

Conversation

@Mathieu2301

Copy link
Copy Markdown
Member

Stacked on #11 (mathieu/agent-guide), which is itself stacked on #10. Merge order: #10, then #11, then this. Base is deliberately mathieu/agent-guide, not main: this edits docs/agent-guide.md §3 and packages/cli, neither of which exists on main.

Roadmap item F.3, discovery workflow for existing hosts, brokers, flows, devices, and services.

The gap

docs/agent-guide.md §3 tells an agent to characterize the installation before designing anything — brokers, what each device reports, what is measured versus commanded, what is physically consequential — and then leaves it to do all of that by hand. Most houses arriving at V4 already run Node-RED with the v3 MiakAPI nodes, and their flows export already answers most of those questions.

What this adds

miakapp discover --flows <path> reads a Node-RED flows export offline and reports:

  • the tabs, with how many nodes each holds;
  • the MQTT brokers, with the topics their nodes actually reach, split into subscriptions and publications;
  • the initMiakapi home bindings;
  • every commitVariables path as a V4 state candidate, with where its value came from (jsonata, env, literal);
  • every onUserAction id as a V4 function candidate, with the groups allowed to invoke it;
  • every sendPushNotif node, with the audience it targeted;
  • every node type the inventory does not model, with counts.

It needs no project file and no Home Key: the house it reads has no V4 project yet.

15 nodes, 2 flows, 1 broker, 5 state paths, 2 actions — 2 findings to settle before migrating
  home.maison-colmon: coordinator coord-1
  broker.maison: 192.168.1.10:1883 tls=false in=2 out=1
  flow.Salon: 5 nodes
  state: [chauffage.consigne, salon.*.on, salon.lampe.on, salon.temperature, salon/humidite]
  actions: [chauffage.set, salon.lampe.toggle]
  unmodelled: [function×2, inject×1]
  critical: A coordinator secret is stored in cleartext in this export; treat it as leaked, rotate it, and keep the export out of Git
  critical: Action salon.lampe.toggle lists no group, so every signed-in user may invoke it; V4 needs an explicit rule for it
  attention: Broker 192.168.1.10 is configured without TLS
  attention: Variable path salon.*.on is not a legal V4 state path: it contains *, which V4 reserves for the trailing .* suffix
  note: Subscription maison/salon/# is a pattern, not one device; enumerate what it actually matches
  note: 2 node type(s) are not modelled by this inventory; read them yourself before assuming the house is fully described

The two findings worth reading the diff for

secret_in_export. The v3 initMiakapi node declares coordSecret in its defaults, not in its credentials (miakapi.html). Node-RED encrypts credentials into flows_cred.json; defaults go into flows.json in cleartext. So every v3 house that ever exported its flows has its coordinator secret in that file. The command reports that one is present and never prints its value — there is a test asserting the secret string appears nowhere in the JSON output.

unrestricted_action. The v3 handler sets allowed = true outright when a node lists no group (miakapi.js). An empty allowedGroups is therefore a grant to every signed-in user, not a deny. Porting one of those to V4 without noticing would silently widen an authorization boundary.

Three things it deliberately does not do

  • It opens no socket and writes nothing back into the export. The inventory is a pure function of the bytes, so two runs can be diffed as a migration baseline.
  • It drops no node silently. Unmodelled types are counted and reported rather than ignored, because the value of an inventory is knowing what it missed.
  • It does not guess which actions are physically consequential. It lists every action it found; deciding which of them heats, locks, unlocks, opens or closes is a judgement for the owner, and no keyword list should make it.

Limits

No real V3 export was available in this workspace to test against. Field names come from the two real schemas — Node-RED core mqtt in / mqtt out / mqtt-broker, and the node-red-contrib-MiakAPI v3 node definitions — but the parser is deliberately tolerant: it recognizes nodes by type, reads only fields it knows, and reports everything else. Running it against one real flows.json from the reference installation is the check I could not do.

--flows takes a path rather than stdin, and the byte ceiling is on the file rather than on structure (the strict parser in internal/json.ts is bounded at 2,048 values for untrusted control-plane responses, which a real house exceeds in its first tab).

Verification

  • bun run check — 244 pass, 0 fail, exit 0
  • bun run check:packages — CLI 56 pass, component 38 pass, template 8 pass, exit 0
  • 25 new tests covering the parser's refusals (non-JSON, non-array, oversize, prototype-polluting keys), each inventory section, each finding kind, finding severity ordering, a clean house producing no finding, and the command's exit codes.

The guide tells an agent to characterize an existing installation before
writing anything, and then leaves it to do that by hand. Most houses arriving
at V4 already run Node-RED with the v3 MiakAPI nodes, and their flows export
already answers most of the questions: which brokers exist, which topics the
devices actually use, which variables were committed, which actions users
could invoke and who was allowed to invoke them.

`miakapp discover --flows <path>` reads that export offline and turns it into
an inventory. It needs no project file and no Home Key, because the house it
reads has no V4 project yet.

Three things it will not do. It never opens a socket or writes back into the
export. It never drops a node silently: every type it does not model is
counted and reported, so the reader knows what the inventory missed. And it
never guesses which actions are physically consequential -- it lists them all
and leaves that judgement to the reader.

Four findings decide work that is otherwise discovered late:

- the v3 initMiakapi node declares coordSecret in its defaults rather than its
  credentials, so Node-RED stores that secret in cleartext in flows.json
  instead of the encrypted flows_cred.json. If it is there, it is leaked.
- the v3 handler allows an action outright when its node lists no group, so an
  empty allowedGroups is a grant to every signed-in user, not a deny.
- a v3 name that V4 would reject has to be renamed while nobody depends on it.
- a topic holding # or + is a subscription pattern, not one device.

Field names come from the two real schemas: Node-RED core mqtt in, mqtt out
and mqtt-broker, and the node-red-contrib-MiakAPI v3 node definitions. No real
export was available to test against, so the parser recognizes nodes by type
and reads only fields it knows.
@Mathieu2301
Mathieu2301 merged commit 1ef9c95 into mathieu/agent-guide Sep 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant