Files
hq/04-ISSUES/169-a-machine-shares-its-files-and-the-mesh-does-not-know/00-report.md
T
jschoubben e33191161d Issue 169: two module-defined seats, and the gap — a seat definition has no neutral home
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.
2026-09-30 13:52:20 +02:00

4.9 KiB

status, opened, located-in, fixed-by, amended-design
status opened located-in fixed-by amended-design
open 2026-09-30
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)

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 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 (no reach for the machine's own network).

The proposal (the operator's, 2026-09-30, settled after two rounds)

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:

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

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:

  • 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;
  • 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.

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

  • 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), and whether one share can serve several paths.