Glossary, the mesh-controller/foundation vocabulary, ADR 0076, Phase 3 closed #43
@@ -0,0 +1,175 @@
|
||||
---
|
||||
layer: to-be
|
||||
status: proposed
|
||||
code:
|
||||
- mesh-catalog modules/showcase
|
||||
- mesh-control internal/builder
|
||||
- mesh-sdk src
|
||||
updated: 2026-09-15
|
||||
decisions:
|
||||
- 02-DECISIONS/0074-the-wire-is-specified-not-the-types.md
|
||||
- 02-DECISIONS/0040-what-a-module-is.md
|
||||
- 02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md
|
||||
---
|
||||
|
||||
# Writing a module
|
||||
|
||||
A worked guide. One module, four capabilities, four languages, and the packages it publishes.
|
||||
|
||||
The reference for *what can be said* is [`18-building-a-module`](18-building-a-module.md); the
|
||||
reference for *what the code and the mesh say to each other* is
|
||||
[`19-the-module-protocol`](19-the-module-protocol.md). This is how you actually write one.
|
||||
|
||||
## First: what is in the SDK, exactly
|
||||
|
||||
**The protocol, and nothing else** ([ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md)).
|
||||
An SDK is an implementation of the module protocol in one language. If something is not in the
|
||||
protocol it does not belong in an SDK, and that rule is what stops it becoming the 34,000-line
|
||||
shared library this design exists to avoid ([ADR 0039](../../02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md)).
|
||||
|
||||
Split the way the protocol is split:
|
||||
|
||||
| the SDK gives you | so that you can |
|
||||
|---|---|
|
||||
| **connection** — reads the sealed credential, pins the certificate, takes your identity from it | reach the broker as *this module on this machine*, and not be able to claim otherwise |
|
||||
| **events** — emit, subscribe, the envelope, dedup | react to what happens in the mesh |
|
||||
| **tools** — register and serve | be asked questions |
|
||||
| **provisioning** — receive a grant, return a credential | give a consumer an instance of what you provide |
|
||||
|
||||
**What it does not give you, deliberately:**
|
||||
|
||||
- **No configuration loader.** Configuration arrives as files the mesh wrote and environment the
|
||||
mesh set. Reading a file is not a thing an SDK needs to teach.
|
||||
- **No API clients.** A Plex client belongs in the Plex module. It changes when Plex changes,
|
||||
which has nothing to do with any other module — *frequent **and** cascading is the disease.*
|
||||
- **No storage, no HTTP framework, no logging library.** Use the language's.
|
||||
|
||||
If you find yourself wanting to add something to the SDK, the test is ADR 0039's: **does editing it
|
||||
recompile unrelated modules, and does it change often?** Both, and it does not belong.
|
||||
|
||||
## A module with four capabilities, in four languages
|
||||
|
||||
A module is **one piece of software** ([ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md)) and
|
||||
may still be written in several languages — each artifact names its own, compiles alone and is
|
||||
packed alone.
|
||||
|
||||
```
|
||||
showcase/
|
||||
module.json
|
||||
events/ ← TypeScript: reacts to what the mesh does
|
||||
tools/ ← Go: answers questions
|
||||
provisioner/ ← Rust: grants instances of what it provides
|
||||
ingest/ ← Python: a scheduled job
|
||||
```
|
||||
|
||||
### The manifest
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "showcase",
|
||||
"build": { "artifacts": [
|
||||
{ "name": "events", "kind": "bundle", "language": "typescript",
|
||||
"entrypoints": ["index.js"] },
|
||||
{ "name": "tools", "kind": "bundle", "language": "go",
|
||||
"entrypoints": ["tools"] },
|
||||
{ "name": "provisioner", "kind": "bundle", "language": "rust",
|
||||
"entrypoints": ["provisioner"] },
|
||||
{ "name": "ingest", "kind": "bundle", "language": "python",
|
||||
"entrypoints": ["ingest.py"] }
|
||||
]},
|
||||
"resources": [
|
||||
{ "id": "events", "type": "process", "name": "showcase-events",
|
||||
"artifact": "events", "run": ["node", "index.js"] },
|
||||
{ "id": "tools", "type": "process", "name": "showcase-tools",
|
||||
"artifact": "tools", "run": ["./tools"] },
|
||||
{ "id": "grants", "type": "process", "name": "showcase-grants",
|
||||
"artifact": "provisioner", "run": ["./provisioner"] },
|
||||
{ "id": "ingest", "type": "process", "name": "showcase-ingest",
|
||||
"artifact": "ingest", "run": ["python", "ingest.py"],
|
||||
"schedule": "0 3 * * *" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Four artifacts, four toolchains, four processes, one module.** Nothing here says how any of them
|
||||
is hosted — that is the mesh's, and it is why these are `process` rather than four containers.
|
||||
|
||||
### Step 1 — say what it is, in each language's own terms
|
||||
|
||||
Each part is an ordinary project in its language, depending on the mesh SDK for that language the
|
||||
way it would depend on anything:
|
||||
|
||||
```
|
||||
events/package.json "@novox/mesh-sdk": "^1.2.0"
|
||||
tools/go.mod require novox.example/mesh-sdk v1.2.0
|
||||
provisioner/Cargo.toml mesh-sdk = "1.2"
|
||||
ingest/pyproject.toml dependencies = ["mesh-sdk~=1.2"]
|
||||
```
|
||||
|
||||
**Resolved from the mesh's own package registries**, which are the ordinary registries for each
|
||||
ecosystem, hosted by the mesh. An author does nothing unusual: `npm install`, `go mod tidy`,
|
||||
`cargo build`, `pip install` all work, and a developer's laptop resolves exactly what a build does.
|
||||
|
||||
### Step 2 — write each part against its capability
|
||||
|
||||
Each uses only the part of the protocol it needs. The event consumer never learns what a grant is.
|
||||
|
||||
```
|
||||
events/index.ts on("module.builder.built", …) → the events capability
|
||||
tools/main.go tool("showcase_state", …) → the tools capability
|
||||
provisioner/main.rs grant → credential → the provisioning capability
|
||||
ingest/ingest.py emit("module.showcase.ingested", …) → events, emitting only
|
||||
```
|
||||
|
||||
### Step 3 — the mesh does the rest
|
||||
|
||||
You do not write a Dockerfile, a unit file, or a port number. The builder picks the toolchain from
|
||||
the language, compiles each artifact alone, and publishes it. The control plane assigns the machine
|
||||
and the ports; the host writes the units.
|
||||
|
||||
### What it costs you to use four languages
|
||||
|
||||
**Honestly: four sets of dependencies to keep current, and four SDKs that must agree.** The mesh
|
||||
makes it possible, not free. A module in one language is simpler, and the reason to use four is
|
||||
that one of them genuinely suits a part better — not that you can.
|
||||
|
||||
## Publishing a package is a capability
|
||||
|
||||
A module may publish libraries as well as run code. **The SDK is not special; it is simply the first
|
||||
module that did this**, and its own consumers are the mesh's modules.
|
||||
|
||||
```json
|
||||
{
|
||||
"module": "plex",
|
||||
"build": { "artifacts": [
|
||||
{ "name": "server", "kind": "upstream", "from": "plexinc/pms-docker@sha256:…" },
|
||||
{ "name": "client-ts", "kind": "package", "language": "typescript", "from": "clients/typescript" },
|
||||
{ "name": "client-rust", "kind": "package", "language": "rust", "from": "clients/rust" },
|
||||
{ "name": "client-py", "kind": "package", "language": "python", "from": "clients/python" }
|
||||
]}
|
||||
}
|
||||
```
|
||||
|
||||
A `package` artifact is built and **published to the mesh's registry for that ecosystem**, under the
|
||||
version its own project file declares. Another module then depends on it the ordinary way:
|
||||
|
||||
```
|
||||
"@novox/plex-client": "^2.0.0"
|
||||
```
|
||||
|
||||
**Why this belongs to modules rather than being a separate thing.** A client for a piece of software
|
||||
changes when that software changes, and the module that owns the software is the only thing that
|
||||
knows. Putting the Plex client anywhere else is the shared-library disease with extra steps.
|
||||
|
||||
### What this leaves open
|
||||
|
||||
- **Which registry.** The catalogue holds a `verdaccio` module, and a forge typically serves package
|
||||
registries too. Two answers exist and nothing says which is the mesh's. That has to be settled
|
||||
before anything publishes.
|
||||
- **Who may publish.** A builder pushing a package needs an account on that registry, which is a
|
||||
credential in the bootstrap path and does not exist yet.
|
||||
- **Versions and ranges.** Everything else the mesh delivers is pinned by digest, and a range is
|
||||
resolved at build time from whatever the registry holds. A lock file records what was chosen, and
|
||||
the builder records that as the build edge — but *a mesh that can rebuild a commit and get a
|
||||
different library* is a real change from how everything else here works, and it should be a
|
||||
decision rather than a consequence.
|
||||
@@ -29,6 +29,7 @@ document is written and this one's status becomes `implemented`.
|
||||
| [`17-raising-a-mesh.md`](17-raising-a-mesh.md) | How a mesh comes into existence, and how a machine joins one that exists | [ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md), [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md), [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) |
|
||||
| [`18-building-a-module.md`](18-building-a-module.md) | How a build is modelled, and why a recipe that is always a Dockerfile does not fit what a module is | [ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md), [ADR 0039](../../02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md), [ADR 0009](../../02-DECISIONS/0009-modules-and-the-graph.md) |
|
||||
| [`19-the-module-protocol.md`](19-the-module-protocol.md) | What a module's code and the mesh say to each other; an SDK is an implementation of it | [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md), [ADR 0042](../../02-DECISIONS/0042-the-shape-of-an-event-on-the-wire.md), [ADR 0043](../../02-DECISIONS/0043-a-module-broker-account-is-scoped-by-emits-and-consumes.md) |
|
||||
| [`20-writing-a-module.md`](20-writing-a-module.md) | A worked guide: one module, four capabilities, four languages, and the packages it publishes | [ADR 0074](../../02-DECISIONS/0074-the-wire-is-specified-not-the-types.md), [ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md), [ADR 0039](../../02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md) |
|
||||
|
||||
## Not yet written
|
||||
|
||||
|
||||
Reference in New Issue
Block a user