From 3f00d3b413e3d56cbca1f0d5df6fc4c32e6ba849 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 1 Sep 2026 16:10:13 +0200 Subject: [PATCH] 026 filed; 025 corrected; the image store is a module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three corrections, two of them to things I wrote today. 026 is the serious one. Four modules mounted fourteen host paths nothing declared — the mail spool, the databases, the object store's data. The runtime creates those as root, so owner and mode go unapplied, and the rule that keeps a directory holding data the mesh did not put there is written in terms of declared directories. It reached the configuration and missed the data. The cause was carrying compose files across: a container shape that can express one gets filled in like one. 025 claimed nothing turns a tag into a digest. That is false, and the answer was designed and built before I wrote it. A module names an artifact, not an image, and `kind: upstream` mirrors somebody else's image into the mesh's own registry, pinned by the digest it lands with. The two-document split the issue described as the shape of a fix is the design. Pinning twelve images by hand was treating the symptom, and left them pointing at a public registry rather than the mesh's. And the image store was written up as something the mesh does. It is an ordinary module — considered for the substrate and removed, because the test is whether the control plane needs it before its first instruction, not whether it can grant itself one. So somebody's own registry is the same module as the mesh's. --- 03-DESIGN/01-to-be/16-module-coverage.md | 7 ++ .../00-report.md | 40 ++++++++--- .../00-report.md | 70 +++++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 04-ISSUES/026-the-data-directories-are-not-declared/00-report.md diff --git a/03-DESIGN/01-to-be/16-module-coverage.md b/03-DESIGN/01-to-be/16-module-coverage.md index cedef65..8b993fa 100644 --- a/03-DESIGN/01-to-be/16-module-coverage.md +++ b/03-DESIGN/01-to-be/16-module-coverage.md @@ -162,6 +162,13 @@ same module. There is one derivation here, and there should stay one. **A live listing returned credentials in plaintext.** Not a coverage question, but the reason sealing is worth its inconvenience. +**An image store is a module, and was written up here as something the mesh does.** It was +considered for the substrate and removed, because the test is not *can it grant itself one* — +nearly anything passes that — but whether the control plane needs it before it can give its first +instruction. It does not. So a registry somebody runs for their own images is the same module as +the one the mesh runs for its own: it offers a place to push, and claims that role once per +machine. + **A rule was enforced only at the far end.** A module may not declare an action, and the host refused one correctly — but the control plane accepted it into the catalogue, resolved it and pushed it, so the refusal arrived on a machine with nothing tying it back to the manifest. The diff --git a/04-ISSUES/025-a-module-must-pin-a-digest-and-nothing-produces-one/00-report.md b/04-ISSUES/025-a-module-must-pin-a-digest-and-nothing-produces-one/00-report.md index ecff3f1..9f20414 100644 --- a/04-ISSUES/025-a-module-must-pin-a-digest-and-nothing-produces-one/00-report.md +++ b/04-ISSUES/025-a-module-must-pin-a-digest-and-nothing-produces-one/00-report.md @@ -84,14 +84,38 @@ system's seven images named repositories that **do not exist** — it publishes registry entirely — and one of the seven had been renamed upstream. Nothing that only checks the shape of a reference could ever have found either. +## The mechanism already existed, and this issue was wrong about that + +**Corrected 2026-09-01, the same day.** This was filed saying nothing turns a tag into a digest. +That is false, and the answer had been designed and built before any of it was written. + +A module does not name an image at all. It names an **artifact**, and declares where that artifact +comes from: + +``` +build.artifacts: [{ name: "gitea", kind: "upstream", from: "gitea/gitea:1.22" }] +resources: [{ id: "server", type: "container", artifact: "gitea", … }] +``` + +`kind: upstream` means *an image somebody else built, mirrored into the mesh's own registry and +pinned by the digest it lands with*. The builder produces it; the manifest the mesh holds is +derived, with `artifact` replaced by the real reference and the key removed, because the host has +never heard of that word. A resource naming an artifact nothing produced is refused. + +So the two-document split this issue described as the shape of a fix **is the design**, and it +covers both cases it said were unsolved: an image the mesh builds, and an image somebody else +built. Mirroring also removes something worse than a stale pin — every machine needing a route to +a public registry, and a tag a stranger can move. + +**What was actually wrong was the examples.** They hard-coded image references instead of naming +artifacts, so they inherited a problem the design does not have. Pinning twelve of them by hand +was treating the symptom, and left the reference pointing at a public registry rather than the +mesh's own. + ## What is still open -**Nothing turns a tag into a digest as part of the mesh's own work.** It was done by hand here, -which is exactly what this issue says a person should not be asked to do. The shape of the answer -is unchanged: a person writes a tag, the mesh resolves it once and records both, and *this pin is -behind its tag* becomes a question the mesh can answer. +**The examples should name upstream artifacts** rather than carry hand-pinned digests. That is the +remaining work, and it is a rewrite of five manifests rather than a mechanism to build. -**The mesh's own provisioner images still cannot be pinned in a repository**, because their digest -does not exist until they are built and pushed. That is the bundle's problem, and the bundle solves -it by writing the digest down after building. A module naming an image the mesh builds needs the -same treatment, and does not have it. +The refusal added here stays: a placeholder must not reach a machine whatever the reason it is +there. diff --git a/04-ISSUES/026-the-data-directories-are-not-declared/00-report.md b/04-ISSUES/026-the-data-directories-are-not-declared/00-report.md new file mode 100644 index 0000000..8663093 --- /dev/null +++ b/04-ISSUES/026-the-data-directories-are-not-declared/00-report.md @@ -0,0 +1,70 @@ +--- +status: located +opened: 2026-09-01 +located-in: [mesh-control] +fixed-by: +amended-design: +--- + +# 026 — The data directories are mounted and never declared + +## Symptom + +Four modules mount **fourteen host paths** that no resource in those modules declares: + +``` +gitea /services/gitea/gitea +postgres /services/postgres/db-data +minio /services/minio/data/data1-1 +mailu eleven more, including the mail spool and the admin database +``` + +Each is a bind mount on a container. None is a `directory` resource. The mesh has never heard of +any of them. + +## What that costs + +**They are created by the container runtime, as root.** A bind mount whose source does not exist +is created for you, owned by root, with whatever mode the runtime picks. So `owner` and `mode` — +which exist precisely so a module can say who its data belongs to — are silently not applied to +the only directories that hold data. + +**The protection that exists for exactly this does not reach them.** A directory the mesh declared +and no longer wants is *kept*, not removed, when it holds anything the mesh did not put there +([ADR 0030](../../02-DECISIONS/0030-data-outlives-the-mesh-that-declared-it.md)). That rule is the +answer to *what happens to my data when a module goes away*, and it is written in terms of +declared directories. **An undeclared one is not protected by it, because the mesh does not know +it is there.** + +So the single rule guarding against data loss covers the configuration directories, which are +cheap to lose, and not the data directories, which are the reason the rule exists. + +## Where it came from + +These manifests were written by reading the arrangement being replaced and carrying its +`docker-compose` files across — service, image, ports, volumes, environment — into the new +manifest's container shape. That shape can express all of it, which is what made the +transliteration feel like progress. + +**A container shape that can express a compose file will be filled in like a compose file.** The +mesh's model is larger than that: a directory is a thing the mesh owns, with an owner and a mode +and a rule about what happens when it is no longer wanted. A volume line borrowed from compose +declares none of it, and nothing complains, because a bind mount source is a string. + +## What a fix has to keep + +- **Every host path a container mounts is declared.** If a module wants a directory on the + machine, it says so, with who owns it and what mode — and gets the removal rule with it. +- **The check is mechanical.** A person comparing volumes against declared directories by hand is + the process that produced this. It is a few lines against the manifest and belongs beside the + other manifest checks. +- **Not by inventing directories at apply time.** The host creating what a mount needs would make + the mesh's ownership of a directory depend on which resource mentioned it first, and would put + the same undeclared path back a layer down. + +## Not yet answered + +**Where a module's data should live at all.** These paths were inherited whole from the +arrangement being replaced, which put everything under one directory per service. Whether that is +right here is a separate question, and a bigger one — it decides what a person backs up, and what +survives a module being removed.