Settles the design repository now that the self-upgrade build is on main: - Records the two decisions that shipped without a record — ADR 0077 (the controller/foundation/node vocabulary) and ADR 0078 (the store and broker are ordinary modules); accepts ADR 0075 and 0076, which shipped work rests on. - Fills issue 051's amended-design and wires ADR 0078 into 07-the-foundation. - Sweeps the repo rename (mesh-control -> mesh-controller) into the mutable docs now that the forge repo is renamed; updates the glossary note and repos.md. - Fixes the six broken links from the design-doc renames, indexes the glossary, regenerates the decisions reading order. Both checks (records.py, index.py) are green. Statuses stay honest: the build is on main and lab-proven but not deployed as the production mesh, so the to-be docs remain in-progress and the as-is layer (the hal mesh) is unchanged — graduation to implemented + as-is belongs to deployment, not merge. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
5.7 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| building it | accepted | 2026-09-16 | jochen | false | 0075-two-stores-and-which-provides-what.md |
76. The SDK is a published package, and the toolchain resolves it by version
Context
ADR 0014 decided a module consumes its dependencies — the mesh's own
shared library included — from the private registry. ADR 0075
decided the private registry is a package-registry provision, and that gitea provides it. What
neither settled, and what issue 053
left open, is the one build where the rule cannot simply be obeyed: the first one.
The TypeScript toolchain image is built from the SDK — it carries the SDK so that every module compiled inside it resolves the shared library without each build fetching it. So the thing that compiles TypeScript and the thing that contains the SDK were the same object, and that object cannot be what builds the SDK. Stated as a question — "how does the SDK reach the registry before the toolchain exists, when the toolchain is what builds it?" — it reads as a paradox.
It is not one. The paradox exists only because the toolchain bakes a git-cloned copy of the SDK.
The SDK itself is plain TypeScript: it needs node and tsc and nothing the mesh makes. A public
base image can build it. The circularity is a property of the workaround, not of the SDK.
Decision
The SDK is an ordinary published package in the mesh's package-registry, consumed by version.
The git URL in the toolchain's manifest and the sibling-path lock beside it — the two halves of
issue 053 — are both removed. A build resolves the SDK the way it resolves any dependency, with a
lock that agrees with its manifest, so npm ci is the command and reproducibility is by
construction rather than by the machine the build ran on.
The SDK is built with a public base image, not with the mesh's toolchain. It is not one of
the components the loop cannot build — the control plane, the registry, the builder, the catalogue
(12-a-module-repository), which arrive by
carrying an init builder because they are the loop's own machinery. The SDK is machinery for
nothing; it is an ordinary dependency the loop builds and publishes like any other. The only
constraint is narrow: it cannot be compiled in the mesh toolchain, because that toolchain is built
from it. So it is compiled on a public base image instead — which needs nothing the mesh makes — and
published before the toolchain that consumes it. It is not carried, because building it does not
wait on a mesh existing first.
The toolchain base stays, thinned. mesh-tools remains the image bundles are compiled in and the
one place the SDK is resolved — but it npm cis the SDK by version from the registry instead of
baking a copy cloned from a git URL. Bundles keep borrowing its resolved dependencies; what changes
is that the version they borrow is named and honest. This was the shape chosen over dropping the
shared base entirely and having every bundle resolve the SDK itself: one resolution point, one
place to be right about the version.
Genesis orders the publish before the first compile. The package-registry provider is a public
image (gitea), so it comes up needing no toolchain; the SDK is published into it; only then is the
toolchain built, so the first npm ci has a registry to read from. Nothing in that chain is
circular, because the only thing that needed the toolchain — baking the SDK — is gone.
Consequences
Each language's toolchain repeats the shape: its own SDK, built from that language's public base
image, published to the same registry, resolved by version with that ecosystem's lockfile-honest
install (npm ci, cargo against a vendored or registry source, pip against a pinned set). The
warning in issue 053 — that whatever the TypeScript repository does the others will copy — is
answered by making the copied thing the correct one.
A change to the SDK is publish-then-consume, exactly as ADR 0014 already priced it: publish the new SDK version, then bump the toolchain (and any module pinning it directly) to consume it. There is no shortcut that resolves an unpublished SDK, which is the property that was missing.
mesh-tools is no longer an SDK carrier in the sense that mattered — it does not contain a copy whose provenance is a branch head somebody force-pushes. It contains a version.
A mesh with no package-registry cannot build TypeScript. This is accepted and is not new: it is the same shape as a mesh that cannot reach a forge being unable to be raised (ADR 0071). Installing brings the registry up first.
How this is checked
| Rule | Checked by |
|---|---|
| The SDK a build compiles against is named, not cloned from a branch | The toolchain manifest pins @novox/mesh-sdk to a version, and the build runs npm ci, which refuses a lock that disagrees with the manifest. Issue 053's two checks become this one. |
| The SDK builds without the mesh's own toolchain | The SDK's build recipe names a public base image. A recipe that named the mesh toolchain would reintroduce the cycle and is refused in review. |
| The registry is up before the first compile | The genesis bed asserts the package-registry answers, and the SDK is published, before the base build runs. A base build that ran first would fail its npm ci with no registry, which is the positive control. |
| A second language repeats the shape, not a new one | When a second SDK is added, its recipe is compared to this one: public base, publish by version, lockfile-honest install. |