The module stated /var/lib/mosquitto-module and /services/mosquitto/data —
novox's layout, a path no definition may carry (ADR 0112). State, grants and
data are now placed directories (${dir:state}, ${dir:grants}, ${dir:data}),
the admin secret lives beside the broker account under the mesh's own state,
and the receives/grants maps follow the grants directory. Paths inside the
sidecar are its own view and are unchanged.
Image pinned to the 2.1.2 build ace's predecessor runs (2026-09-17); the old
pin was the same version, built in June.
Found preparing ace, whose broker carries a password-file user (an IoT switch
and home-assistant). Carrying it is a data step, not a manifest one: the
migration repo has scripts/mosquitto-pwdfile-to-dynsec.py, which moves 7
PBKDF2 entries into the dynsec store hash-for-hash (tested end to end).
The module stated /var/lib/mosquitto-module and /services/mosquitto/data —
novox's layout, a path no definition may carry (ADR 0112). State, grants and
data are now placed directories (${dir:state}, ${dir:grants}, ${dir:data}),
the admin secret lives beside the broker account under the mesh's own state,
and the receives/grants maps follow the grants directory. Paths inside the
sidecar are its own view and are unchanged.
Image pinned to the 2.1.2 build ace's predecessor runs (2026-09-17); the old
pin was the same version, built in June.
Found preparing ace, whose broker carries a password-file user (an IoT switch
and home-assistant). Carrying it is a data step, not a manifest one: the
migration repo has scripts/mosquitto-pwdfile-to-dynsec.py, which moves $7$
PBKDF2 entries into the dynsec store hash-for-hash (tested end to end).
The module stated /var/lib/mosquitto-module and /services/mosquitto/data —
novox's layout, a path no definition may carry (ADR 0112). State, grants and
data are now placed directories (${dir:state}, ${dir:grants}, ${dir:data}),
the admin secret lives beside the broker account under the mesh's own state,
and the receives/grants maps follow the grants directory. Paths inside the
sidecar are its own view and are unchanged.
Image pinned to the 2.1.2 build ace's predecessor runs (2026-09-17); the old
pin was the same version, built in June.
Found preparing ace, whose broker carries a password-file user (an IoT switch
and home-assistant). Carrying it is a data step, not a manifest one: the
migration repo has scripts/mosquitto-pwdfile-to-dynsec.py, which moves $7$
PBKDF2 entries into the dynsec store hash-for-hash (tested end to end).
mqtt-topic served nothing: with two listens the mesh could not say which port a consumer dials, so a
consumer had to type 1883 into its config. It now serves the MQTT listener's port (the machine's,
once assigned) and the scheme, so `${bound:mqtt-topic:port}` fills.
The provisioner confined every consumer to `<as>/#`, which leaves nothing for the consumers the
broker exists for: Home Assistant discovers under homeassistant/# and tasmota/discovery/#, and
Node-RED's flows follow the devices' own topics. A consumer now contributes `topics` (MQTT topic
filters) to its mqtt-topic requirement and is granted exactly those; with none, its own subtree as
before. Settings merge into contributions, so an operator narrows a grant per assignment. The role
is brought to exactly the wanted ACLs (stale ones removed), `holds` checks the ACLs too, and an
invalid list is refused, never quietly narrowed. Only the role named for the consumer is touched:
a client carried from the predecessor's password file keeps its own.
mqtt-topic is now a provision consumers can use without typing anything (commit 1080f45)
serves{"scheme": "mqtt", "port": 1883}. With two listens the mesh couldn't tell which port a consumer dials, so bindings had no port. Now ${bound:mqtt-topic:port} is the machine port.
Grants are the topics a consumer asks for. Until now the provisioner confined every consumer to <as>/#. That is useless to Home Assistant, which needs homeassistant/#, tasmota/discovery/# and the devices' topics, and to Node-RED, whose flows follow the devices' own topics.
A consumer contributes topics (MQTT topic filters) to mqtt-topic and gets exactly those, for publish, receive and subscribe. With no topics it gets <as>/# as before.
Settings merge into contributions, so an operator can narrow a grant per assignment.
The consumer's role is brought to exactly the wanted ACLs, and stale ones are removed. holds checks the ACLs too, so a hand-edited role is restored within a minute.
An invalid list is refused loudly and never narrowed silently.
Only the role named for the consumer is touched. Carried clients (luffy / legacy-full-access from scripts/mosquitto-pwdfile-to-dynsec.py) are never read or changed.
Tests: test/topics.test.ts (5 passing). tsc typecheck and build are clean in the mesh-tools build image. The controller's catalogue tests pass with #144, #147 (which includes #156) and #149 merged together. A scratch Resolve/Declaration run for ace shows mosquitto's grants/mesh.json carrying mesh_ace_hass and mesh_ace_nodered with topics: ["#"].
E2E (throwaway prov-mosquitto-* on 19381, removed afterwards):
The module's bootstrap seeded the store, a password-file user was carried with the migration script, and the provisioner created both clients with #.
Narrowing to stat/# and cmnd/+/Power removed the # ACLs.
An invalid a/#/b was refused and left the role untouched.
An ACL removed by hand was re-applied by holds within 60 s.
**mqtt-topic is now a provision consumers can use without typing anything** (commit 1080f45)
- **serves** `{"scheme": "mqtt", "port": 1883}`. With two listens the mesh couldn't tell which port a consumer dials, so bindings had no `port`. Now `${bound:mqtt-topic:port}` is the machine port.
- **Grants are the topics a consumer asks for.** Until now the provisioner confined every consumer to `<as>/#`. That is useless to Home Assistant, which needs `homeassistant/#`, `tasmota/discovery/#` and the devices' topics, and to Node-RED, whose flows follow the devices' own topics.
- A consumer contributes `topics` (MQTT topic filters) to `mqtt-topic` and gets exactly those, for publish, receive and subscribe. With no `topics` it gets `<as>/#` as before.
- Settings merge into contributions, so an operator can narrow a grant per assignment.
- The consumer's role is brought to exactly the wanted ACLs, and stale ones are removed. `holds` checks the ACLs too, so a hand-edited role is restored within a minute.
- An invalid list is refused loudly and never narrowed silently.
- Only the role named for the consumer is touched. Carried clients (`luffy` / `legacy-full-access` from `scripts/mosquitto-pwdfile-to-dynsec.py`) are never read or changed.
- Tests: `test/topics.test.ts` (5 passing). tsc typecheck and build are clean in the mesh-tools build image. The controller's catalogue tests pass with #144, #147 (which includes #156) and #149 merged together. A scratch Resolve/Declaration run for ace shows mosquitto's `grants/mesh.json` carrying `mesh_ace_hass` and `mesh_ace_nodered` with `topics: ["#"]`.
- **E2E** (throwaway `prov-mosquitto-*` on 19381, removed afterwards):
- The module's bootstrap seeded the store, a password-file user was carried with the migration script, and the provisioner created both clients with `#`.
- Narrowing to `stat/#` and `cmnd/+/Power` removed the `#` ACLs.
- An invalid `a/#/b` was refused and left the role untouched.
- An ACL removed by hand was re-applied by `holds` within 60 s.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The module stated /var/lib/mosquitto-module and /services/mosquitto/data —
novox's layout, a path no definition may carry (ADR 0112). State, grants and
data are now placed directories (${dir:state}, ${dir:grants}, ${dir:data}),
the admin secret lives beside the broker account under the mesh's own state,
and the receives/grants maps follow the grants directory. Paths inside the
sidecar are its own view and are unchanged.
Image pinned to the 2.1.2 build ace's predecessor runs (2026-09-17); the old
pin was the same version, built in June.
Found preparing ace, whose broker carries a password-file user (an IoT switch
and home-assistant). Carrying it is a data step, not a manifest one: the
migration repo has scripts/mosquitto-pwdfile-to-dynsec.py, which moves
7PBKDF2 entries into the dynsec store hash-for-hash (tested end to end).
The module stated /var/lib/mosquitto-module and /services/mosquitto/data — novox's layout, a path no definition may carry (ADR 0112). State, grants and data are now placed directories (${dir:state}, ${dir:grants}, ${dir:data}), the admin secret lives beside the broker account under the mesh's own state, and the receives/grants maps follow the grants directory. Paths inside the sidecar are its own view and are unchanged. Image pinned to the 2.1.2 build ace's predecessor runs (2026-09-17); the old pin was the same version, built in June. Found preparing ace, whose broker carries a password-file user (an IoT switch and home-assistant). Carrying it is a data step, not a manifest one: the migration repo has scripts/mosquitto-pwdfile-to-dynsec.py, which moves $7$ PBKDF2 entries into the dynsec store hash-for-hash (tested end to end).mqtt-topic served nothing: with two listens the mesh could not say which port a consumer dials, so a consumer had to type 1883 into its config. It now serves the MQTT listener's port (the machine's, once assigned) and the scheme, so `${bound:mqtt-topic:port}` fills. The provisioner confined every consumer to `<as>/#`, which leaves nothing for the consumers the broker exists for: Home Assistant discovers under homeassistant/# and tasmota/discovery/#, and Node-RED's flows follow the devices' own topics. A consumer now contributes `topics` (MQTT topic filters) to its mqtt-topic requirement and is granted exactly those; with none, its own subtree as before. Settings merge into contributions, so an operator narrows a grant per assignment. The role is brought to exactly the wanted ACLs (stale ones removed), `holds` checks the ACLs too, and an invalid list is refused, never quietly narrowed. Only the role named for the consumer is touched: a client carried from the predecessor's password file keeps its own.mqtt-topic is now a provision consumers can use without typing anything (commit
1080f45){"scheme": "mqtt", "port": 1883}. With two listens the mesh couldn't tell which port a consumer dials, so bindings had noport. Now${bound:mqtt-topic:port}is the machine port.<as>/#. That is useless to Home Assistant, which needshomeassistant/#,tasmota/discovery/#and the devices' topics, and to Node-RED, whose flows follow the devices' own topics.topics(MQTT topic filters) tomqtt-topicand gets exactly those, for publish, receive and subscribe. With notopicsit gets<as>/#as before.holdschecks the ACLs too, so a hand-edited role is restored within a minute.luffy/legacy-full-accessfromscripts/mosquitto-pwdfile-to-dynsec.py) are never read or changed.test/topics.test.ts(5 passing). tsc typecheck and build are clean in the mesh-tools build image. The controller's catalogue tests pass with #144, #147 (which includes #156) and #149 merged together. A scratch Resolve/Declaration run for ace shows mosquitto'sgrants/mesh.jsoncarryingmesh_ace_hassandmesh_ace_noderedwithtopics: ["#"].prov-mosquitto-*on 19381, removed afterwards):#.stat/#andcmnd/+/Powerremoved the#ACLs.a/#/bwas refused and left the role untouched.holdswithin 60 s.