Cloned alone, built by the mesh, every module moved onto it, and the base then changed for real: all three went stale naming what moved. A comment-only change correctly makes nothing stale, which found a second bug — staleness compared commits where it should compare artifacts.
113 lines
6.4 KiB
Markdown
113 lines
6.4 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-09-13
|
|
located-in: [mesh-tools, mesh-sdk]
|
|
fixed-by: mesh-tools feat/run-once-entry, mesh-sdk feat/adr-ref-resync
|
|
amended-design:
|
|
---
|
|
|
|
# 044 — The runtime every module builds on cannot be built by the mesh
|
|
|
|
## Symptom
|
|
|
|
Every module written in the mesh's own toolchain is compiled on top of one shared base image — the
|
|
tool runtime. The mesh can build the modules. It cannot build the base.
|
|
|
|
Two things stop it, and each is enough on its own:
|
|
|
|
- The base's container definition copies a compiled output directory that is not in the repository.
|
|
A clean clone has the sources and no compiled output, so the definition refers to something that
|
|
is not there.
|
|
- The base's dependency on the mesh's own software development kit resolves to a **sibling checkout
|
|
on disk**, not to anything a clone can fetch:
|
|
|
|
```
|
|
"node_modules/@novox/mesh-sdk" -> { "resolved": "../mesh-sdk", "link": true }
|
|
```
|
|
|
|
Both mean the same thing: the base can only be produced on a workstation that happens to have the
|
|
neighbouring repositories laid out beside it, and has run a compile step by hand first.
|
|
|
|
Observed while building four modules through the mesh from a repository and a path. All four
|
|
succeeded, and all four recorded that they were built on top of the same base — an image reference
|
|
that no module in the mesh produced, because no module could have.
|
|
|
|
## Why this matters
|
|
|
|
**This is the one artifact the whole build chain rests on, and it is the one artifact outside the
|
|
chain.** Three separate consequences, all visible today:
|
|
|
|
- *The base is pinned by hand.* Every module's container definition names it as a literal digest
|
|
written by a person. Nothing checks that digest against anything.
|
|
- *Nothing can tell when it moves.* A build records what it was built on top of, so an artifact is
|
|
stale when anything beneath it moved. That rule cannot fire for the base: the graph has edges
|
|
pointing at it, and no version on the other end, because no build ever registered one. The mesh
|
|
is therefore unable to answer "what must be rebuilt" for the change that would reach *every*
|
|
module at once.
|
|
- *Nobody can check what is in it.* The published base and the definition in the repository have
|
|
already drifted: the definition names one operating system base, and the image actually serving
|
|
every module is built on a different one. This was found by running the image, not by reading
|
|
anything, and it went unnoticed for exactly as long as nobody could rebuild it.
|
|
|
|
A rule the design states — an artifact is stale when anything it was built against moved — is
|
|
unenforceable for the artifact it matters most for. That is the shape of a wrong rule, not a
|
|
missing feature: everything downstream reports "nothing to rebuild" and is believed.
|
|
|
|
## How it would be checked
|
|
|
|
The check is the fix's own test: clone the repository alone, into an empty directory, and build it.
|
|
Nothing else present, no neighbouring checkouts, no compile run first. If that produces the base,
|
|
the mesh can produce it too, because that is all the mesh does.
|
|
|
|
## Open questions
|
|
|
|
- Where should the software development kit come from, for a build that has only one repository?
|
|
The mesh already knows how to run a package registry as a module, and it already publishes
|
|
container images to one of its own — so this may be the same answer twice rather than a new one.
|
|
- Is the base a module like any other, or is it one of the few things a new mesh must arrive
|
|
carrying? It behaves like both: everything is built on top of it, and it is built out of the same
|
|
sources as everything else.
|
|
- If it becomes an ordinary module, is there a circularity? **No** — checked rather than assumed:
|
|
the builder is a compiled program built from its own sources and a language toolchain, and does
|
|
not stand on this base at all. Only modules written in the mesh's scripted toolchain do. So the
|
|
thing that would build the base is not one of the things built on it, and the chain has an end.
|
|
|
|
## Resolved, 2026-09-13
|
|
|
|
The base is a module now, built from its own repository and nothing else.
|
|
|
|
Three things were in the way, and the third was not visible until the first two were gone:
|
|
|
|
- **The toolkit could not be fetched.** It is named by a pinned commit of its own repository rather
|
|
than by a folder on somebody's disk. Both repositories turned out to be readable without a
|
|
credential, so no package registry was needed after all — which is why this was smaller than it
|
|
looked.
|
|
- **The toolkit shipped no compiled output.** Every one of its entry points pointed into a directory
|
|
that is not in source control. It declares the hook that is meant to compile it on install, and
|
|
the package manager in use does not run that hook — so the build compiles it explicitly instead.
|
|
Relying on a hook firing would have failed the same invisible way this issue is about.
|
|
- **The base is also the compile environment.** Every module's recipe starts from this image and
|
|
invokes the compiler out of it. A first attempt dropped the build tools to make a smaller image,
|
|
which broke every module built on it. They stay, and the recipe now says why.
|
|
|
|
The drift this issue reported is also closed: the recipe claimed one operating system while serving
|
|
another, and now states what is actually in service. Changing that was deliberately *not* folded in
|
|
— a module already builds against it with that system's package manager, and moving the ground
|
|
under every module at the same moment as making it buildable would be two changes wearing one
|
|
commit.
|
|
|
|
**Checked the way the report said it should be.** The repository was cloned alone, with nothing
|
|
beside it, and built by the mesh. Then every module was moved onto the result, and the base changed
|
|
for real: all three went stale, each naming the base and the commits it moved between, each listed
|
|
once. Changing only a comment in the base — a new commit, a byte-identical image — correctly makes
|
|
nothing stale, which is a second bug this exercise found and fixed: staleness was comparing commits
|
|
when it should compare artifacts, so the mesh would have rebuilt itself entirely to arrive back
|
|
exactly where it started.
|
|
|
|
## What is still true
|
|
|
|
A module names its base as a literal fingerprint in its own recipe. So the mesh can now *say* that
|
|
three modules must be rebuilt, and rebuilding them still produces the old base until somebody edits
|
|
that fingerprint. Noticing is solved; acting on it is the build chain driving itself, which is
|
|
separate work.
|