From 8d9c9ab6b5bf0784c99974b78e46adaa203370ea Mon Sep 17 00:00:00 2001 From: jochens Date: Wed, 30 Sep 2026 13:49:25 +0200 Subject: [PATCH 1/6] Issue 169: a machine shares its files, and the mesh does not know ace exports the operator's media library over NFS and Samba with host services no module declares: they close at converge, nothing owns their configuration, and no module elsewhere can require the share. Proposes a file-sharing module that accesses the paths, holds a node-scoped seat it defines, and provides file-share to consumers. --- .../00-report.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md diff --git a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md new file mode 100644 index 0000000..f15141e --- /dev/null +++ b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md @@ -0,0 +1,69 @@ +--- +status: open +opened: 2026-09-30 +located-in: + - mesh-catalog (no module shares a path over the network) + - hq 02-DECISIONS (a file-share seat, per ADR 0126, is a module's own to define) +fixed-by: +amended-design: +--- + +# 169 — A machine shares its files, and the mesh does not know + +## What was observed + +ace serves the operator's media library to the home network with two host services no module +declares and HAL never managed either: + +``` +/etc/exports: /storage/media 192.168.1.0/24(rw,sync,root_squash,…) nfs-server active, :2049 +/etc/samba/smb.conf: [media] path = /storage/media/ valid users = media smb active, :139/:445 +``` + +Two LAN clients were connected at survey (2026-09-30). The library itself is operator data +(ADR 0051: ~40 TB on ZFS, the mesh owns nothing about it — [issue 153](../153-an-adopted-machines-data-cannot-be-placed-where-it-is/00-report.md) +is about modules reaching it in place). + +Under the mesh as it stands, this arrangement has no expression and one failure mode: + +- **Nothing declares the listens.** At `converge ace` the filter is the sum of what modules listen + on (ADR 0045); 2049 and 445 are nobody's, so the shares close — silently, for the two clients + that mount them. +- **Nothing owns the configuration.** `/etc/exports` and `smb.conf` are hand-written files on one + machine; a second machine sharing a directory would be written by hand again. +- **Nothing can consume it.** A module on another node that wanted the library (a player, an + indexer, a backup) has no `requires` to state and no binding to read; it would mount by a + hand-typed host and path. +- The clients are LAN devices, so this also meets [issue 154](../154-a-machines-own-network-is-not-a-reach/00-report.md) + (no reach for the machine's own network). + +## The proposal (the operator's, 2026-09-30) + +A **file-sharing module** — NFS first, Samba the same shape — that: + +- declares the exported paths as `accesses` (ADR 0051: it owns nothing about them, never creates, + chowns or removes), and *which* paths as the assignment's settings (ADR 0046/0112); +- writes the share configuration (`/etc/exports`, `smb.conf`) as mesh-managed files and drives the + units, like `dnsmasq`/`sshd` do for theirs; +- declares its endpoints (`nfs` 2049/tcp, `smb` 445/tcp, …) so the reach — internal, or the LAN + once 154 has an answer — is the assignment's, and converge keeps them open; +- **defines and holds a node-scoped seat** (`file-share`, one holder per machine, as ADR 0126 + lets a module do) — the machine's one answer for "where are the files"; +- **provides** `file-share` at mesh scope, serving the export path(s) and protocol, so a consumer + on another node `requires` it and reads `${bound:file-share:at}` and the path from its binding + instead of a hand-typed mount; a pair credential where the protocol has one (Samba user), none + for `sec=sys` NFS. + +What it would settle: ace's library becomes reachable from the mesh by declaration, the two host +services get an owner, converge stops being a trap for them, and a media module on another machine +(or a backup on novox) can mount the library the way it binds a database today. + +## Open questions for the decision + +- The seat's name and whether Samba and NFS are one seat with two protocols (ADR 0129: a seat + carries the protocol of its role) or two seats. +- Whether an NFS export over the overlay is an `internal` reach of the same endpoint or a second + export line — NFS authorises by client address, so the mesh range and the LAN range are two + entries in one file. +- How a consumer's binding expresses a *path* to mount (today bindings carry `at`, `port`, `as` and + whatever the provider `serves`). From a0a930b1cdafe56b91274314ecb100f6fa720c97 Mon Sep 17 00:00:00 2001 From: jochens Date: Wed, 30 Sep 2026 13:50:31 +0200 Subject: [PATCH 2/6] Issue 169: file sharing is a core seat, one per protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operator's call: a node-scoped system seat family (node-nfs-share, node-smb-share, …) defined by the control plane, one per protocol as package registries are (ADR 0109), so several modules occupy it and a machine may hold both. --- .../00-report.md | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md index f15141e..f51111c 100644 --- a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md +++ b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md @@ -39,20 +39,32 @@ Under the mesh as it stands, this arrangement has no expression and one failure ## The proposal (the operator's, 2026-09-30) -A **file-sharing module** — NFS first, Samba the same shape — that: +**File sharing is a core seat — a role each machine has, defined by the control plane — and, as +with package registries (ADR 0109), one seat per protocol, so several modules occupy the family:** -- declares the exported paths as `accesses` (ADR 0051: it owns nothing about them, never creates, +| seat | scope | delivers | held by | +|---|---|---|---| +| `node-nfs-share` | node | `nfs-share` | an `nfs` module | +| `node-smb-share` | node | `smb-share` | a `samba` module | +| … (`node-webdav-share`) | node | … | whatever comes next | + +Named for their scope (ADR 0121), one holder per node (ADR 0110), each carrying its protocol +(ADR 0129). A machine may hold both — nfs and samba on ace — and one module may hold several +(0109: "gitea may hold several seats at once"). Adding a protocol is adding a seat and a +provision, not widening one. + +The holder module: + +- declares the exported paths as `accesses` (ADR 0051: it owns nothing about them — never creates, chowns or removes), and *which* paths as the assignment's settings (ADR 0046/0112); - writes the share configuration (`/etc/exports`, `smb.conf`) as mesh-managed files and drives the units, like `dnsmasq`/`sshd` do for theirs; -- declares its endpoints (`nfs` 2049/tcp, `smb` 445/tcp, …) so the reach — internal, or the LAN +- declares its endpoints (`nfs` 2049/tcp; `smb` 445/tcp, …) so the reach — internal, or the LAN once 154 has an answer — is the assignment's, and converge keeps them open; -- **defines and holds a node-scoped seat** (`file-share`, one holder per machine, as ADR 0126 - lets a module do) — the machine's one answer for "where are the files"; -- **provides** `file-share` at mesh scope, serving the export path(s) and protocol, so a consumer - on another node `requires` it and reads `${bound:file-share:at}` and the path from its binding - instead of a hand-typed mount; a pair credential where the protocol has one (Samba user), none - for `sec=sys` NFS. +- **provides** the seat's provision, serving the export path(s), so a consumer on another node + `requires nfs-share` (or `smb-share`) and reads `${bound:nfs-share:at}` and the path from its + binding instead of a hand-typed mount — a pair credential where the protocol has one (a Samba + user), none for `sec=sys` NFS. What it would settle: ace's library becomes reachable from the mesh by declaration, the two host services get an owner, converge stops being a trap for them, and a media module on another machine @@ -60,10 +72,10 @@ services get an owner, converge stops being a trap for them, and a media module ## Open questions for the decision -- The seat's name and whether Samba and NFS are one seat with two protocols (ADR 0129: a seat - carries the protocol of its role) or two seats. - Whether an NFS export over the overlay is an `internal` reach of the same endpoint or a second export line — NFS authorises by client address, so the mesh range and the LAN range are two entries in one file. - How a consumer's binding expresses a *path* to mount (today bindings carry `at`, `port`, `as` and - whatever the provider `serves`). + whatever the provider `serves`), and whether one share can serve several paths. +- Whether the seat should exist before its first module (a system seat is a decision, ADR 0110) — + the decision that adds the two seats can be the one that accepts this proposal. From e33191161dc895becfa0137a2feb7feb2102d6ef Mon Sep 17 00:00:00 2001 From: jochens Date: Wed, 30 Sep 2026 13:52:20 +0200 Subject: [PATCH 3/6] =?UTF-8?q?Issue=20169:=20two=20module-defined=20seats?= =?UTF-8?q?,=20and=20the=20gap=20=E2=80=94=20a=20seat=20definition=20has?= =?UTF-8?q?=20no=20neutral=20home?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nfs-share and smb-share share an intent, not a contract; one seat would be a union with every field optional. What the exemplar exposes is that a seat declared inside one module cannot be implemented by another without depending on it. --- .../00-report.md | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md index f51111c..60c28ba 100644 --- a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md +++ b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md @@ -37,21 +37,28 @@ Under the mesh as it stands, this arrangement has no expression and one failure - The clients are LAN devices, so this also meets [issue 154](../154-a-machines-own-network-is-not-a-reach/00-report.md) (no reach for the machine's own network). -## The proposal (the operator's, 2026-09-30) +## The proposal (the operator's, 2026-09-30, settled after two rounds) -**File sharing is a core seat — a role each machine has, defined by the control plane — and, as -with package registries (ADR 0109), one seat per protocol, so several modules occupy the family:** +**Two module-defined seats, one per protocol, because NFS and SMB share an intent and not a +contract.** A seat in the mesh's sense is a contract — what it accepts, emits and serves, and the +tools its holder must answer (ADR 0126, 0132) — and lined up, the two share almost none of it: -| seat | scope | delivers | held by | -|---|---|---|---| -| `node-nfs-share` | node | `nfs-share` | an `nfs` module | -| `node-smb-share` | node | `smb-share` | a `samba` module | -| … (`node-webdav-share`) | node | … | whatever comes next | +| | `nfs-share` | `smb-share` | +|---|---|---| +| serves | export path(s); the client ranges allowed (`sec=sys` authorises by address) | share name(s), path | +| pair credential | none | a user and password per consumer | +| consumer's mount | `at:/path` | `//at/share` with credentials | +| holder's tools | export / unexport a path for a range | add / remove a share, create a user | -Named for their scope (ADR 0121), one holder per node (ADR 0110), each carrying its protocol -(ADR 0129). A machine may hold both — nfs and samba on ace — and one module may hold several -(0109: "gitea may hold several seats at once"). Adding a protocol is adding a seat and a -provision, not widening one. +One `file-share` seat would be the union with every field optional — a consumer could bind it and +still not know how to mount what it got (the emptiness ADR 0129 warns against). "Export a path to +the network" is a category, and the mesh needs no seat category: a consumer requires the one it +can mount. If "give me the library, however" is ever needed, it is a provision an umbrella module +serves, not a seat. + +Both are node-scoped, one holder per node (ADR 0110), so ace holds both. `nfs` and `samba` are the +first implementations; a second (Ganesha for `nfs-share`, ksmbd for `smb-share`) is what proves +0126's promise that "replacing the implementation changes nothing for any caller". The holder module: @@ -61,14 +68,19 @@ The holder module: units, like `dnsmasq`/`sshd` do for theirs; - declares its endpoints (`nfs` 2049/tcp; `smb` 445/tcp, …) so the reach — internal, or the LAN once 154 has an answer — is the assignment's, and converge keeps them open; -- **provides** the seat's provision, serving the export path(s), so a consumer on another node - `requires nfs-share` (or `smb-share`) and reads `${bound:nfs-share:at}` and the path from its - binding instead of a hand-typed mount — a pair credential where the protocol has one (a Samba - user), none for `sec=sys` NFS. +- **provides** the seat's provision, so a consumer on another node `requires nfs-share` (or + `smb-share`) and reads `${bound:nfs-share:at}` and the path from its binding instead of a + hand-typed mount. -What it would settle: ace's library becomes reachable from the mesh by declaration, the two host -services get an owner, converge stops being a trap for them, and a media module on another machine -(or a backup on novox) can mount the library the way it binds a database today. +## The design gap this exposes + +**A seat definition has no home outside the module that first declared it.** Today a seat is +declared inside a manifest (`showcase` declares `the-showcase`, `ca-trust` its own). If `nfs` +declared `nfs-share`, Ganesha could hold it only by depending on nfs's manifest — the coupling +0126 removed for callers, reintroduced for implementations. The protocol needs a neutral place in +the catalogue beside the modules (a seat definition registered like a manifest), with a module +saying which seats it implements. This is the first role with an obvious second implementation, +which is what makes it the exemplar for that mechanism. ## Open questions for the decision @@ -77,5 +89,3 @@ services get an owner, converge stops being a trap for them, and a media module entries in one file. - How a consumer's binding expresses a *path* to mount (today bindings carry `at`, `port`, `as` and whatever the provider `serves`), and whether one share can serve several paths. -- Whether the seat should exist before its first module (a system seat is a decision, ADR 0110) — - the decision that adds the two seats can be the one that accepts this proposal. From 90b89aa1c9633c2f5c8fd66bb471bd28c89227d8 Mon Sep 17 00:00:00 2001 From: jochens Date: Wed, 30 Sep 2026 13:53:32 +0200 Subject: [PATCH 4/6] =?UTF-8?q?Issue=20169:=20the=20consumer's=20half=20?= =?UTF-8?q?=E2=80=94=20the=20host=20mounts=20the=20share,=20a=20mount=20is?= =?UTF-8?q?=20a=20resource=20of=20the=20consuming=20module,=20not=20a=20cl?= =?UTF-8?q?ient=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../00-report.md | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md index 60c28ba..f19d679 100644 --- a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md +++ b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md @@ -82,6 +82,30 @@ the catalogue beside the modules (a seat definition registered like a manifest), saying which seats it implements. This is the first role with an obvious second implementation, which is what makes it the exemplar for that mechanism. +## The consumer's half: the machine mounts it (2026-09-30, third round) + +A binding tells a consumer *where* the share is; it does not put the files on its machine. A +consumer on another node needs the export **mounted by the host, at a directory the consumer +declares, before its container starts**, and unmounted when the requirement goes. That is a host +action, not something a container reads from a file. + +**Not a client module.** A node-wide `nfs-client` with a list of mounts in its settings is fstab +with a manifest around it: consumers would stop requiring `nfs-share` and couple on a path again, +unassigning a consumer would leave its mount behind, and a person is back in the loop deciding +which machine mounts what — the thing the binding removes. + +**A mount is a resource of the consuming module.** The vocabulary (directory, file, package, +container, service, process, network, archive, user, access) has no `mount`. It can be assembled +today — a `package` (nfs-utils), a `file` writing a systemd `.mount` unit with +`What=${bound:nfs-share:at}:${bound:nfs-share:path}` and `Where=${dir:library}`, a `service` +enabling it after the overlay is up — but the honest form is a **`mount` resource kind**: the host +mounts from the binding, refuses a mountpoint the module did not declare (ADR 0091's three ways a +path can be), and unmounts on undeclare. The nfs and samba modules are then the *server* side only. + +**Identity crosses the wire.** `sec=sys` NFS trusts the client's uid, so a consumer must run as the +library's owner on the server (ace: `media`, 1001:2000) — hq 153's `${access::uid}` proposal +extended to a mount, `${mount::uid}`, read from the mounted tree. + ## Open questions for the decision - Whether an NFS export over the overlay is an `internal` reach of the same endpoint or a second From ee801a64412cfe0aa17b9f3b5d2b2d02a28f7ed5 Mon Sep 17 00:00:00 2001 From: jochens Date: Wed, 30 Sep 2026 13:56:07 +0200 Subject: [PATCH 5/6] Issue 169: the consumer half is a module (network-share), not a host resource kind; the access-on-a-mountpoint check is the data-loss case --- .../00-report.md | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md index f19d679..1b44232 100644 --- a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md +++ b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md @@ -82,29 +82,38 @@ the catalogue beside the modules (a seat definition registered like a manifest), saying which seats it implements. This is the first role with an obvious second implementation, which is what makes it the exemplar for that mechanism. -## The consumer's half: the machine mounts it (2026-09-30, third round) +## The consumer's half: a module mounts it (2026-09-30, third and fourth round) -A binding tells a consumer *where* the share is; it does not put the files on its machine. A -consumer on another node needs the export **mounted by the host, at a directory the consumer -declares, before its container starts**, and unmounted when the requirement goes. That is a host -action, not something a container reads from a file. +A binding tells a consumer *where* the share is; it does not put the files on its machine. Mounting +is something done on a machine, and something done on a machine is a module's work — not the host's +(the vocabulary stays closed; no `mount` resource kind). -**Not a client module.** A node-wide `nfs-client` with a list of mounts in its settings is fstab -with a manifest around it: consumers would stop requiring `nfs-share` and couple on a path again, -unassigning a consumer would leave its mount behind, and a person is back in the loop deciding -which machine mounts what — the thing the binding removes. +**A consumer-side module, `network-share`,** assigned on the node that wants the files: -**A mount is a resource of the consuming module.** The vocabulary (directory, file, package, -container, service, process, network, archive, user, access) has no `mount`. It can be assembled -today — a `package` (nfs-utils), a `file` writing a systemd `.mount` unit with -`What=${bound:nfs-share:at}:${bound:nfs-share:path}` and `Where=${dir:library}`, a `service` -enabling it after the overlay is up — but the honest form is a **`mount` resource kind**: the host -mounts from the binding, refuses a mountpoint the module did not declare (ADR 0091's three ways a -path can be), and unmounts on undeclare. The nfs and samba modules are then the *server* side only. +- `requires nfs-share` (or `smb-share`); several shares on one node are several local names of + the requirement (ADR 0094); +- its manifest is a `package` (nfs-utils), a `file` writing a systemd `.mount` unit filled from + the binding — `What=${bound:nfs-share:at}:${bound:nfs-share:path}` — and a `service` enabling it + after the overlay is up: the same shape as `resolv-conf` or `sshd`, files and a unit; +- *where* it mounts is the assignment's setting (`/srv/media` on one machine, elsewhere on + another); which machine mounts what is an operator decision made at assignment, exactly as which + paths a machine shares is. + +**The modules that use the files never learn about NFS.** A player, an indexer, a backup declares +the mounted path as an `access` — an operator-chosen, pre-existing path the mesh never owns +(ADR 0051), exactly as `/storage/media` is on ace. The same app manifest then runs on ace against +the local library and on another node against the mounted one, with only its assignment differing. + +**The one check to add, because it is the data-loss case.** An `access` is confirmed today by the +path being present. For a mountpoint that is not enough: a writer whose container starts before the +mount is up writes into the empty directory underneath it, and the files vanish when the mount +lands. The access check must confirm the path is *a mountpoint* when the module says so (or the +module's unit is ordered before the consumer's container — which crosses modules and is exactly +what the mesh does not order). Which of the two is the decision's. **Identity crosses the wire.** `sec=sys` NFS trusts the client's uid, so a consumer must run as the -library's owner on the server (ace: `media`, 1001:2000) — hq 153's `${access::uid}` proposal -extended to a mount, `${mount::uid}`, read from the mounted tree. +library's owner on the server (ace: `media`, 1001:2000) — hq 153's `${access::uid}`, read from +the mounted tree, answers it on the consumer's side too. ## Open questions for the decision From 105ae9a56add176ad02ff2bca67d77685b6bf8e2 Mon Sep 17 00:00:00 2001 From: jochens Date: Wed, 30 Sep 2026 13:56:24 +0200 Subject: [PATCH 6/6] =?UTF-8?q?Issue=20169:=20network-share=20is=20the=20m?= =?UTF-8?q?odule=20responsible=20for=20a=20node's=20network=20shares=20?= =?UTF-8?q?=E2=80=94=20a=20node=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../00-report.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md index 1b44232..addacf0 100644 --- a/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md +++ b/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md @@ -88,7 +88,11 @@ A binding tells a consumer *where* the share is; it does not put the files on it is something done on a machine, and something done on a machine is a module's work — not the host's (the vocabulary stays closed; no `mount` resource kind). -**A consumer-side module, `network-share`,** assigned on the node that wants the files: +**A consumer-side module, `network-share` — the module responsible for setting up the network +shares a node uses** (the operator's framing). A node role, like `node-uplink` or +`node-dns-resolver`: each machine has it at most once, which is a reason for it to hold a +node-scoped seat, so two modules can never both be writing mount units on one machine. Assigned on +the node that wants the files: - `requires nfs-share` (or `smb-share`); several shares on one node are several local names of the requirement (ADR 0094);