diff --git a/03-DESIGN/01-to-be/12-a-module-repository.md b/03-DESIGN/01-to-be/12-a-module-repository.md index b717803..a89956d 100644 --- a/03-DESIGN/01-to-be/12-a-module-repository.md +++ b/03-DESIGN/01-to-be/12-a-module-repository.md @@ -225,9 +225,16 @@ answered to the asker and kept nowhere. | kind | is | |---|---| | **image** | built from a Dockerfile in this repository | +| **bundle** | this module's own code, compiled by the toolchain its language implies, then packed | | **archive** | a directory in this repository, packed | | **upstream** | an image somebody else built, mirrored into the mesh's own registry | +**The second was added later and is why most modules now need no Dockerfile.** An archive packs a +directory as it stands, so shipping compiled output meant compiling somewhere first — and that +meant every module repeating a recipe that is easy to get wrong in ways that fail elsewhere. The +whole surface a module has, and a module that exercises all of it, are in +[`18-building-a-module`](18-building-a-module.md). + **The third exists because a module usually runs software it did not write.** A database module ships configuration and a provisioner and does not build a database. Naming the upstream reference directly would need every machine to reach a public registry, and would pin to a tag its owner can diff --git a/03-DESIGN/01-to-be/16-module-coverage.md b/03-DESIGN/01-to-be/16-module-coverage.md index 8b993fa..8ac9d0a 100644 --- a/03-DESIGN/01-to-be/16-module-coverage.md +++ b/03-DESIGN/01-to-be/16-module-coverage.md @@ -24,7 +24,7 @@ checklist: what is already sayable, what is deliberately not, and what is missin | **depends on another module** | 65 | `requires` naming a module. A requirement naming a module means *that module*, not anything providing the name | | **system packages** | 28 | the `package` shape | | **a container** | 48 | the `container` shape, pinned by digest | -| **systemd units** | 17 | a `file` for the unit, a `service` for the state it should be in | +| **systemd units** | 17 | a `process`, which the mesh writes the unit for. *Was: a `file` for the unit and a `service` for its state — which made every module author write unit syntax, and is why `process` exists ([`18`](18-building-a-module.md))* | | **how to reach it** | 17 | `serves`, with the mesh adding which machine and where | | **a public name** | 11 | requiring `route` and contributing the name | | **ports it opens** | 11 | `listens`, from which filtering is computed | @@ -33,7 +33,7 @@ checklist: what is already sayable, what is deliberately not, and what is missin | **restart when something changes** | 11 | `restart-on` | | **a generated credential** | 20 | `own-secrets`, sealed to the machine | | **a value that differs per node** | 43 | settings, and `computed` for what only the mesh knows | -| **images built from source** | 2 | `build.artifacts` | +| **images built from source** | 2 | `build.artifacts` — an `image` from a Dockerfile, or a `bundle`, which names a language and lets the mesh choose the toolchain ([`18`](18-building-a-module.md)) | ## Deliberately not sayable diff --git a/03-DESIGN/01-to-be/18-building-a-module.md b/03-DESIGN/01-to-be/18-building-a-module.md index 8e8a883..8fe2131 100644 --- a/03-DESIGN/01-to-be/18-building-a-module.md +++ b/03-DESIGN/01-to-be/18-building-a-module.md @@ -148,3 +148,75 @@ makes the drift worse while hiding it. | Nothing is announced that was not made | A build made to fail announces nothing, and the catalogue's graph is unchanged afterwards. | | A build keeps what it was told | A catalogue started after a build asks for what it missed and receives the manifest and the artifacts stood on, not a summary. | | The toolchain is the mesh's, not the author's | Changing the toolchain makes every module built against it stale, and each names what moved. | + +## The whole surface, in one place + +**A reference, and it is kept true by a test rather than by care.** A table like this goes stale the +day somebody adds a field, so `mesh-catalog/modules/showcase` is a module that uses all of it, and +`TestTheShowcaseModuleIsAValidManifest` fails when it stops doing so. Read the module when this +disagrees with it. + +### What a module declares + +| field | is | +|---|---| +| `module`, `version`, `slug` | its identity; the slug is the short name generated names are built from | +| `capabilities` | what a machine must have for this to run there | +| `provides` | **the shared seat** — several modules may fill one capability and coexist | +| `claims` | **the exclusive seat** — two modules claiming one thing in a scope cannot both be assigned there | +| `serves` | what a consumer must know to connect. The mesh fills in the assigned port ([ADR 0038](../../02-DECISIONS/0038-the-mesh-assigns-the-port.md)) | +| `requires` | what must be provided by something on the same node | +| `binds` | where the mesh writes what a requirement resolved to | +| `secrets` | where the mesh seals the credential for a requirement | +| `own-secrets` | secrets that are the module's own — a superuser, a broker account | +| `emits` / `consumes` | the event graph: 1:many, broadcast, no credential | +| `listens` | the port **its own software** uses, and from where. Filtering is computed from these | +| `contributes` / `receives` | values one module adds to another's configuration, and the other half | +| `accesses` | operator-owned paths it may use and must not own ([ADR 0051](../../02-DECISIONS/0051-shared-data-is-the-operators.md)) | +| `certificate` | a certificate for a name it serves | +| `grants` | credentials it must create for its consumers | +| `filtering` | rules beyond its own ports | +| `computed` | marks a module the control plane generates rather than an author writing | +| `build.artifacts` | what it produces | + +### What it builds + +| kind | is | +|---|---| +| `bundle` | its own code, compiled by the toolchain its language implies, then packed | +| `archive` | a directory, packed as it stands | +| `image` | built from a Dockerfile — for software that needs a particular base | +| `upstream` | somebody else's image, mirrored and pinned by a digest this mesh assigned | + +### What it puts on a machine + +| resource | is | a module may | +|---|---|---| +| `directory` | a directory with a mode and an owner | ✅ | +| `file` | literal content, with `${bound:…}` and `${secret:…}` filled in | ✅ | +| `user` | a login | ✅ | +| `access` | a pre-existing path it may use and must not own | ✅ | +| `archive` | files fetched by digest and unpacked | ✅ | +| `package` | a package that must be present | ✅ | +| `network` | a named container network | ✅ | +| `container` | an image, in three modes | ✅ | +| `process` | **its own code**, in three modes | ✅ | +| `service` | an **existing** unit put into a state | for software shipping its own unit | +| `action` | a command to run | ❌ **refused** — [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | + +**The two at the bottom are the interesting rows.** `action` is refused outright: the link may not +carry a command, so a module needing something done ships a program that reconciles — which is what +a run-once `process` is. `service` installs no unit by design, which is right for software that +ships one and wrong for code the mesh built, which has no unit until the mesh writes it. + +### How its code runs, and what that code can be + +| mode | is | | shape | loaded by | +|---|---|---|---|---| +| *(default)* | a unit restarted when it exits | | tools | a tool host, over the broker | +| `run-once` | run to completion; what follows is gated on it | | event consumer | the same host, reacting | +| `schedule` | a timer; a missed fire happens when the machine returns | | provisioner | invoked when a consumer is granted | +| | | | a process | the machine's supervisor | + +**Tools, hooks and consumers are not further modes**, which is the test of whether three is the +right number: they are loaded by a tool host, and a tool host is a process that stays up.