An audit of the six code repositories found eleven open issues fixed on main with commits and beds to show (025, 027, 033, 036, 037, 040, 045, 047, 050, 052, 053), three partly (007, 026, 035), nine not (020, 031, 041, 046, 049, 054, 064, 065, 066) and one whose fix would live outside those repos (006). Resolved ones name their evidence; partly ones say what remains; 041 records that the exposure has widened since it was reported.
3.5 KiB
status: resolved opened: 2026-09-05 located-in: [mesh-control, mesh-host, mesh-catalog] fixed-by: ADR 0052 run-once container: mesh-host 19e5dd8, PR #5 24e9ae4; ADR 0053 scheduled twin 9d1f001; proven by mesh-lab assigned-catalogue-mqtt.test.ts amended-design: 02-DECISIONS/0052-a-step-that-runs-once-before-a-container.md
037 — A module cannot run its own code at a lifecycle phase
The symptom, as observed
Found while converting the catalogue (2026-09-05), across several modules at once. A module can declare things that exist — a directory, a file with fixed content, a network, a container — but it cannot declare a step that runs at a defined point in its own lifecycle. Three converted modules need exactly that and have nowhere to put it:
- mosquitto. Its Dynamic Security plugin will not start unless
dynamic-security.jsonalready contains an admin client before the broker's first start — the broker loads the plugin at boot. Seeding it is a run-once step that must happen after the file resource exists and before the container starts. The vocabulary has no "before first start." - The database providers (postgres/mongodb/mssql). First-boot seeding works today only because the image happens to do it from an env var. Anything the mesh itself must run once against the server — a schema migration, an extension enable, a health gate before the module is announced ready — has no home.
- The seed-then-mutate family already recorded in 035 is the same shape seen from the content side; this is it seen from the timing side.
Why it matters beyond the instance
This is not a defect in a module — it is a capability the module system does not yet offer. A real class of modules needs to run their own code at points in the build/install/run lifecycle: seed-before-start, migrate, post-start health-gate, pre-remove drain. The declarative resource model deliberately describes state, not steps, and that is right for what it covers; the gap is that some modules genuinely have a step.
Prior art, and its warning. An earlier mesh had exactly this as a feature: event-driven hooks that ran custom code at phases of the build/publish/deploy pipeline. It was powerful and it was complex to set up and flaky — which is the real content of this record. The need is not in question; the cost of the obvious answer is. Whatever shape this takes must not reproduce that fragility, or it will be worse than the gap.
Open questions
- Is the right unit narrow — a run-once / init resource ("run this once, here, in the lifecycle") — or general — a per-phase lifecycle hook on a module, and if so which phases (build / publish / install / pre-start / post-start / pre-remove)?
- Where does a hook's code run — in the module's own runtime container under its scoped account (ADR 0043/0047), so it inherits the same isolation as its tools and events? Or is some of it the host's, before a container exists?
- How is a step made idempotent and reconcilable so a re-apply does not re-run it destructively — the same discipline the resource model gets for free and a step does not?
- What is the smallest version that unblocks the three modules above without rebuilding the old flaky hook engine? Is "seed-before-first-start" alone enough for now, with the general case deferred?
- A rule states how it is checked: whatever shape is chosen, what lab scenario proves a hook runs exactly once, at the right phase, and converges on re-apply?