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
88 lines
5.7 KiB
Markdown
88 lines
5.7 KiB
Markdown
---
|
|
topic: building it
|
|
status: accepted
|
|
date: 2026-09-16
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 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](0014-no-npm-workspace.md) decided a module consumes its dependencies — the mesh's own
|
|
shared library included — from the private registry. [ADR 0075](0075-two-stores-and-which-provides-what.md)
|
|
decided the private registry is a `package-registry` provision, and that gitea provides it. What
|
|
neither settled, and what [issue 053](../04-ISSUES/053-the-sdk-is-pinned-twice-and-the-two-disagree/00-report.md)
|
|
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`](../03-DESIGN/01-to-be/12-a-module-repository.md)), 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 ci`s 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](0014-no-npm-workspace.md) 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](0071-where-genesis-gets-its-source.md)). 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. |
|