The catalogue holds the module graph, and the modules that make it build themselves #21

Merged
jschoubben merged 21 commits from feat/the-catalogue-module into main 2026-09-13 09:17:11 +00:00
21 Commits
Author SHA1 Message Date
jschoubben 729582cc55 Remove what was only there to move a commit
A line in postgres's recipe and a one-line file in amqp-ping, both added to
move a commit and watch the mesh notice. The proofs worked; neither was meant
to stay. MESH_MODULE is set from the sealed credential at run time anyway, so
baking it in was dead weight as well as noise.
2026-09-13 11:15:33 +02:00
jschoubben 21ad008879 Stale means the artifact moved, not the commit
A comment changed in a build recipe is a new commit and a byte-identical image.
Comparing commits called every module standing on it stale, so the mesh would
have rebuilt itself entirely to arrive back exactly where it started — and
listed each dependent once per commit that had produced the same image.
2026-09-13 02:48:21 +02:00
jschoubben 87243bc524 Every module stands on a base the mesh built
The base was a digest typed in by hand, for an image nothing in the mesh could
produce — so the graph held edges pointing at it with no version on the far end,
and the one change that reaches every module at once could never be noticed.
It is a module now, and these edges resolve.
2026-09-13 02:45:37 +02:00
jschoubben 1d0d9a3894 Name the module in its own runtime, and move the artifact with it 2026-09-13 01:58:48 +02:00
jschoubben 358c7d5a2d Move postgres's commit, to watch the mesh roll it out by itself 2026-09-13 01:57:00 +02:00
jschoubben d5300120d6 Move amqp-ping's commit, to see what the catalogue announces 2026-09-13 01:49:48 +02:00
jschoubben c80d9d0f7c The graph holds what a module declares, not only what it was built on
Build edges are discovered by building; requires and provides are stated by the
module about itself. Both belong in the graph and answer different questions —
and "what provides postgres-database" needed a sweep over every manifest, which
only something holding all of them can do.
2026-09-13 01:44:56 +02:00
jschoubben 2d2ca80e3b Index the edge after the column that carries it exists
On a store with the old shape the table is not re-created, so an index declared
beside it is built on a column the migration has not added yet.
2026-09-13 01:41:23 +02:00
jschoubben f15c814145 A build edge names an artifact, because that is what the builder can see
The catalogue expected each edge to name a module and a commit. The builder
sends a pinned image reference — it cannot know which module produced it, that
is a fact about the graph. So every build that had been built on top of anything
was rejected, and only the modules built against nothing ever registered.

Versions now record what they published, and an edge resolves through that. An
edge to an artifact no module here produced is kept: it resolves by itself when
that module is registered, which is the ordinary case while a mesh fills in.
2026-09-13 01:37:51 +02:00
jschoubben 9387f8b040 A module that listens is served, not run
`run` imports an entrypoint without binding a broker — it exists for a step that
works offline and exits. Both the catalogue and amqp-ping subscribe on import,
so both died on the first on() with no broker bound.
2026-09-13 01:30:39 +02:00
jschoubben c774d5dbe0 Install the postgres client the way the runtime base can
The published base is debian; apk is not there and the build said so.
2026-09-13 01:28:15 +02:00
jschoubben 6ebf51d312 postgres's runtime carries the client it provisions through
Its provisioner runs DDL by shelling out to psql, which the runtime base has no
reason to hold. Every create failed with ENOENT and retried for ever.
2026-09-13 01:26:23 +02:00
jschoubben 594295f009 The catalogue restarts when its database credentials change
Without it the container keeps whatever the env file said when it was created.
Nothing reports that: it runs, and it is wrong.
2026-09-13 01:22:38 +02:00
jschoubben f7d57e9556 The catalogue brings its own postgres driver
The runtime base carries what every module needs, and a database driver is not
that. Installed into an empty directory because the module's package.json also
names the sdk, which lives in the base rather than on a registry.
2026-09-13 01:16:23 +02:00
jschoubben e742b6a569 The catalogue builds its own runtime, and postgres serves its tools
Both modules keep their tools in an entrypoint of their own, so an image that
named only the consumer would serve none of them.
2026-09-13 01:13:43 +02:00
jschoubben d6c9c8d666 postgres builds its own runtime, like any other module
Its provisioner container named an image nobody could produce — a zero digest
placeholder. It names an artifact instead, and the module says how to build it,
so the mesh can make the database provider the catalogue needs.
2026-09-13 01:10:45 +02:00
jschoubben 2a6fed6f4f The builder is told the mesh's name for the machine it runs on 2026-09-13 01:07:37 +02:00
jschoubben 81a80c675c The builder declares what it announces
Its account is scoped from what it emits and consumes, and it declared neither —
which is why asking for a generic module account produced one that authenticated
and could do nothing, with the refusal surfacing a layer away as a permissions
error against a queue.

Declaring the announcement is not documentation here. It is what the permission
is derived from.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-13 00:57:41 +02:00
jschoubben 39631d6f87 amqp-ping says what it is made of, and can be built from its own directory
A module's runtime image was assembled by a script copying the sdk and the tool
runtime out of neighbouring checkouts, so it could only be built on a workstation
that had them. That is why no module declared what it was made of and why
forty-seven point at a placeholder.

The tool runtime becomes an image a module's runtime is built FROM, published like
any other artifact. The module then builds from its own directory and that base —
one clone, which is what the builder can actually be asked for (novox/hq ADR 0069).
The dependency stops being a property of somebody's machine and becomes a build
edge, pinned to a digest the mesh's registry assigned.

The compiler is invoked by its real path rather than through node_modules/.bin:
those are symlinks to a launcher that requires its library relatively, and
resolving them while building the base leaves a launcher pointing at nothing.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-13 00:52:57 +02:00
jschoubben a73cb8a2f8 The catalogue, as a module that owns the module graph
It links module-versions to each other and knows nothing about nodes; which
machine runs what stays the control plane's (novox/hq ADR 0070, 0072). Keeping
them apart is what lets the control plane carry on composing declarations while
this is down.

The builder announces what it built, this places it in the graph and announces
what that means, and the control plane hooks the meaning rather than the build
output. A rebuild producing the commit already current is registered and is not
an upgrade — announcing it would ripple outward forever through modules that did
not change.

Ordering is not computed. Modules stale and waiting on nothing that is itself
stale are announced as buildable; the rest stay stale and appear once whatever
they were waiting for is registered, so a chain and a diamond need no special
handling and nothing holds a plan.

Four tools over the graph: what this mesh holds, one module in full, what a
change to a module reaches, and what must be rebuilt and why. The edges are
derived from builds rather than declared, so they cannot drift from what the code
actually uses.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-12 23:16:05 +02:00
jschoubben a4d10341e7 Declare what hello-web is made of, to prove a build from a path
A proof branch, not for main: the route-forwarding bed reads this module's literal
image and would break until the module is built.

The modelling is right regardless — the image is upstream, so the mesh should
mirror it once into its own registry and pin what that registry assigned, rather
than every machine fetching a reference somebody else can move.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-12 16:57:18 +02:00