From 1ee62392b9e620122b932409d6da861253a8acf6 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 1 Sep 2026 01:46:46 +0200 Subject: [PATCH] =?UTF-8?q?Playbook=2006=20=E2=80=94=20writing=20a=20modul?= =?UTF-8?q?e,=20from=20doing=20it=20once?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Written after porting the first real workload end to end. Every step exists because skipping it cost something, and the ratio is recorded because it is the lesson: six attempts, one real bug, and the mesh was right every time. The rule worth carrying out of it: read the host's log before theorising. A declaration that was sent and not applied says so there and nowhere else — it took an hour to look, and the answer was one line. --- 00-META/process/00-overview.md | 1 + 00-META/process/06-writing-a-module.md | 82 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 00-META/process/06-writing-a-module.md diff --git a/00-META/process/00-overview.md b/00-META/process/00-overview.md index c89213d..0926f21 100644 --- a/00-META/process/00-overview.md +++ b/00-META/process/00-overview.md @@ -49,6 +49,7 @@ the expensive half. | [03](03-issues.md) | Issues | Something is wrong — often with the owner unknown | | [04](04-build-handoff.md) | Build handoff | A design is ready to be built | | [05](05-constitution-sync.md) | Constitution sync | `how-we-build.md` changed a rule the mesh enforces | +| [06](06-writing-a-module.md) | Writing a module | Something that runs today must run on the mesh | ## Status lives in frontmatter diff --git a/00-META/process/06-writing-a-module.md b/00-META/process/06-writing-a-module.md new file mode 100644 index 0000000..e0064ee --- /dev/null +++ b/00-META/process/06-writing-a-module.md @@ -0,0 +1,82 @@ +# Playbook 06 — Writing a module + +**Trigger.** Something that runs today must run on the mesh, or a new capability must be +declarable. + +**Who runs it.** Whoever is porting or writing it. + +*Written 2026-09-01 from doing this for the first time end to end. Every step below exists +because skipping it cost something.* + +## Before anything: read what runs + +**A module is written from the thing, not from memory of the thing.** For a port, that means its +current compose file, its environment, and where its data actually sits. Assumptions about any of +the three have been wrong every time they were not checked. + +Three questions, answered from the machine: + +| | why it decides something | +|---|---| +| **what containers, and how do they find each other?** | more than one means a `network`; names between them must match what the software is configured to dial | +| **where is its data?** | a bind mount moves with a path; a named volume does not; an anonymous volume is already losing data on every redeploy | +| **which values are secret, and which are merely settings?** | a secret goes in `own-secrets` or a grant; a setting goes in the manifest and may be overridden per node | + +## The steps + +1. **Name what it provides and requires**, if anything. A name is what a consumer is coupled to, + not the role it plays ([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)): + `postgres-database`, not `database`. Most modules provide nothing and require nothing — an + application is usually a leaf. + +2. **Declare capabilities, not dependencies, for facts about the machine.** `container-runtime`, + `package-manager`, `seat`. A capability is detected and refused against; it is not something a + module can install. + +3. **Write the resources in the order they must happen.** They are applied in the order written + and orphans are removed in reverse, so a `network` is written before the containers that join + it and removed after them. + +4. **Put every secret in a file, never in `env`.** A declaration travels over the broker in plain + text: a password in `env` is a password the broker sees. Use `env-file` for containers, or + `${secret:name}` inside a config file the module supplies. **The mesh delivers parts; a module + that needs them combined combines them.** + +5. **Pin every image by digest.** A tag moves. The manifest in a repository names artifacts; the + manifest the mesh holds names digests, and they are not the same document. + +6. **Decide generate or accept.** A new module's credential is generated. **An adopted one keeps + the credential it already has** — `secret accept` — because minting a new password for a + database that already exists locks the application out of its own data. + +7. **Add a provisioner only if the software cannot read a file.** A proxy that watches a + directory needs nothing. PostgreSQL needs `CREATE ROLE`, an object store needs a bucket and a + policy, an identity provider needs a realm and a client — those need a small program beside + them. It reads what the mesh granted and reconciles; it does not decide anything. + +8. **Prove it in the lab, against the real software.** Not that a container started — that the + thing works: the credential authenticates, a wrong one is refused, the containers reach each + other, the data survives a restart. + +## What the first port actually cost + +Six attempts, one real bug. Recorded because the ratio is the lesson: **the mesh was right every +time and the scaffolding was not.** + +- A shape existed in the language and no host implemented it, so every declaration carrying one + was refused whole — correctly, and the host said exactly that. **Nobody was reading the host's + log.** Read it first; it is the only place that says why a machine did nothing. +- A blind find-and-replace renamed a provision in quotes and missed the same word bare. +- A command was tested only for the invocations that should fail, so it rejected every real one + and the suite stayed green. +- A test asserted on a helper rather than on the code that calls it, three separate times. **A + test that cannot fail when the behaviour is deleted is not defending the behaviour.** + +## Rules + +- **Read the host's log before theorising.** A declaration that was sent and not applied says so + there and nowhere else. +- **A failing test is kept, not skipped.** It is the reproduction. +- **Never rotate during an adoption.** Rotation is a separate act, afterwards, deliberately. +- **A data directory is never removed by the mesh** ([ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md)), + and that protects against the mesh only — not against a disk or a mistaken command.