Convert lavinmq: it is not only a broker, so it has to be built

The mesh refused to place it — two of its three containers named
mesh-runtime-lavinmq@sha256:000…0, "a placeholder digest, which is never a real
image". That refusal was right, and the belief behind the placeholder was that
lavinmq needs no building because its broker is an upstream image.

The broker is upstream. The module is not the broker. It carries a run-once
bootstrap that writes the broker's configuration before it first starts, a
provisioner that grants each consumer its own vhost and user, a set of tools and
an event consumer — all of it this module's own TypeScript, and none of it
producible by naming somebody else's image.

So it gets what every module with code of its own gets: a Dockerfile standing on
the shared toolchain and runtime bases, a build block naming them, and containers
that name the artifact rather than a digest nothing can produce. Same recipe as
postgres, which is the converted module closest in shape — it has a provisioner
too.

Noted and deliberately not changed: postgres runs its provisioner from its
container's args, and lavinmq's equivalent container names none, so on this
manifest the provisioner is never started. That may be why, or may be a second
fault; it is left alone so the next run says which.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-14 21:05:10 +02:00
parent 030509558c
commit c61c7f74f9
2 changed files with 65 additions and 3 deletions
+41
View File
@@ -0,0 +1,41 @@
# lavinmq's runtime: the tool runtime, carrying this module's compiled bootstrap, provisioner,
# tools and event consumer.
#
# **Built from this module's own directory and nothing else.** The sdk is in the base image, so
# nothing is copied out of a neighbouring checkout — which is what lets the mesh build this from a
# repository and a path (novox/hq ADR 0069) rather than only on a workstation that happens to have
# the siblings.
#
# Two bases, named rather than pinned: the image this is COMPILED in, and the image it RUNS in.
# They are different images on purpose — the first carries a compiler and the second must not, or
# every running container would carry one it never invokes. The mesh answers both with the copies it
# holds, because a fingerprint written here would name one particular copy and no other mesh has it
# (novox/hq issue 044). Declared in module.json's `build.on`; deliberately no defaults, so a build
# nobody told stops here and says which module to build first.
ARG BUILD_BASE
ARG RUNTIME_BASE
FROM ${BUILD_BASE} AS build
# Compiled under /app/modules so `@novox/mesh-sdk` resolves upward into the base's own
# node_modules — the module is compiled against exactly the sdk it will run against.
WORKDIR /app/modules/lavinmq
COPY . .
# The compiler is invoked by its real path rather than through node_modules/.bin, whose entries are
# symlinks to a launcher that requires its library relatively — resolved away when the base image
# was assembled.
#
# Four entrypoints and a client, because this module is four things: a run-once bootstrap that
# writes the broker's configuration before it first starts, a provisioner that grants consumers
# their own vhost and user, a set of tools, and an event consumer.
RUN node /app/node_modules/typescript/bin/tsc \
client.ts index.ts bootstrap/index.ts provisioner/index.ts tools/index.ts \
--module NodeNext --moduleResolution NodeNext --target ES2022 --outDir dist
FROM ${RUNTIME_BASE}
COPY --from=build /app/modules/lavinmq/dist /app/modules/lavinmq/dist
# What a tool host should load from this module: its event consumer and its tools, which are
# separate entrypoints because they are loaded by different things. The bootstrap and the
# provisioner are not listed here — the declaration names each in its container's `args`, because
# they are what this module's own containers run. One image, because they are one module and share
# a client.
ENV MESH_TOOL_MODULES=/app/modules/lavinmq/dist/index.js,/app/modules/lavinmq/dist/tools/index.js
+23 -2
View File
@@ -75,7 +75,7 @@
"id": "bootstrap",
"type": "container",
"name": "lavinmq-bootstrap",
"image": "mesh-runtime-lavinmq@sha256:0000000000000000000000000000000000000000000000000000000000000000",
"artifact": "runtime",
"run-once": true,
"volumes": [
"/var/lib/lavinmq-module:/var/lib/lavinmq-module",
@@ -114,7 +114,7 @@
"id": "runtime",
"type": "container",
"name": "mesh-lavinmq",
"image": "mesh-runtime-lavinmq@sha256:0000000000000000000000000000000000000000000000000000000000000000",
"artifact": "runtime",
"network": "lavinmq",
"volumes": [
"/var/lib/mesh/lavinmq/broker:/run/secrets/broker:ro",
@@ -129,5 +129,26 @@
"MESH_PROVISION_PASSWORD_FILE": "/run/secrets/default"
}
}
],
"build": {
"on": [
{
"arg": "BUILD_BASE",
"module": "mesh-tools",
"artifact": "build"
},
{
"arg": "RUNTIME_BASE",
"module": "mesh-tools",
"artifact": "runtime"
}
],
"artifacts": [
{
"name": "runtime",
"kind": "image",
"from": "Dockerfile"
}
]
}
}