Run-once bootstrap (ADR 0052): an init container seeds mosquitto's dynsec admin client into dynamic-security.json (offline, via mesh-tools run + mosquitto_ctrl) and chowns it to the broker's uid 1883, placed before the broker server container. The broker now comes up seeded instead of crash-looping.
Provisioner fix:mosquitto_ctrl dynsecexits 0 even on failure (verified live — "Client not found"/"Not authorized"/"Unable to connect" all exit 0). The client trusted the exit code, so the existence-probe always returned true, createClient never ran, and the provisioner logged success for a consumer client that never landed. Now ctl() scans stderr and throws on the real error shapes; idempotent re-provision and rotation still pass; a real auth/connection failure now errors instead of silently "succeeding."
Proven by assigned-catalogue-mqtt (green): the broker comes up seeded and a consumer's scoped MQTT client actually lands in the store.
Two things, both lab-verified green end-to-end:
1. **Run-once bootstrap (ADR 0052):** an init container seeds mosquitto's dynsec admin client into `dynamic-security.json` (offline, via `mesh-tools run` + `mosquitto_ctrl`) and chowns it to the broker's uid 1883, placed before the broker `server` container. The broker now comes up seeded instead of crash-looping.
2. **Provisioner fix:** `mosquitto_ctrl dynsec` **exits 0 even on failure** (verified live — "Client not found"/"Not authorized"/"Unable to connect" all exit 0). The client trusted the exit code, so the existence-probe always returned true, `createClient` never ran, and the provisioner logged success for a consumer client that never landed. Now `ctl()` scans stderr and throws on the real error shapes; idempotent re-provision and rotation still pass; a real auth/connection failure now errors instead of silently "succeeding."
Proven by `assigned-catalogue-mqtt` (green): the broker comes up seeded and a consumer's scoped MQTT client actually lands in the store.
https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
The Dynamic Security plugin refuses to start the broker unless
dynamic-security.json already holds an admin client, and no reconcile loop
seeds it (novox/hq ADR 0052). Add a run-once init container, declared before
the server container, that runs mosquitto's own bootstrap entrypoint in the
runtime image: it seeds the store offline via mosquitto_ctrl and exits, and
the host gates the broker on its completion.
The bootstrap hands the seeded file to the broker's user (uid 1883, chown +
0600): the broker must read the seed at startup AND persist to it as clients
come and go, but the init container runs as root and would otherwise leave a
file the broker can neither read nor rewrite. This is the ownership question
ADR 0052 left for the lab to settle. It seeds only when the file is absent, so
what the running plugin grows is never clobbered (issue 035).
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
mosquitto_ctrl's dynsec subcommands exit 0 even when they fail — a
"Client not found", an "already exists", a "Connection error: Not
authorized", an "Unable to connect" all return status 0 and report the
failure only as a line of text (verified live against 2.0.11). ctl()
trusted the exit code, so clientExists()'s getClient probe never threw
and always returned true; createScopedClient therefore took the
setClientPassword branch, never ran createClient, and the consumer's
client never landed in the dynsec store — while the provisioner logged
it as provisioned. That is the assigned-catalogue-mqtt failure.
ctl() now scans the combined stdout/stderr for the tool's error markers
and raises a match as the failure it is. Surfacing those errors exposed
two calls that only "worked" by being swallowed: addRoleACL re-run
reports "already exists" (now ignored like createRole), and addClientRole
re-run reports a bare "Internal error" that cannot be told from a real
fault — so the binding is checked with clientHasRole and only added when
absent. Fresh provision, idempotent re-provision, password rotation, bad
admin auth and unreachable broker all verified against a live broker.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
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.
Two things, both lab-verified green end-to-end:
dynamic-security.json(offline, viamesh-tools run+mosquitto_ctrl) and chowns it to the broker's uid 1883, placed before the brokerservercontainer. The broker now comes up seeded instead of crash-looping.mosquitto_ctrl dynsecexits 0 even on failure (verified live — "Client not found"/"Not authorized"/"Unable to connect" all exit 0). The client trusted the exit code, so the existence-probe always returned true,createClientnever ran, and the provisioner logged success for a consumer client that never landed. Nowctl()scans stderr and throws on the real error shapes; idempotent re-provision and rotation still pass; a real auth/connection failure now errors instead of silently "succeeding."Proven by
assigned-catalogue-mqtt(green): the broker comes up seeded and a consumer's scoped MQTT client actually lands in the store.https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF