Files
hq/02-DECISIONS/0051-shared-data-is-the-operators.md
jschoubben d5e12c82aa Accept ADR 0051 — shared data is the operator's; a module is granted access
Your decision, ratified: the media library (and shared/pre-existing data) is
operator-owned and external; a module declares access, not ownership; the host
mounts but owns nothing (no create/chown/reconcile/remove); an absent accessed
path is refused clearly; several accessors co-resolve. Status proposed->accepted;
index regenerated (records + index checks pass). Implementation lives on the code
branches (mesh-control/catalog/host), held for merge after the convergence fix.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-05 22:29:24 +02:00

11 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
what runs on it accepted 2026-09-05 jochen false 0030-data-outlives-the-mesh-that-declared-it.md

51. Shared data is the operator's, and a module is granted access to it

Context

Eight modules declared one filesystem as eight private ones. The media stack — a library server, the acquisition managers for films, series, music and books, a subtitle fetcher and two download clients — shares directories on one machine: the download clients write into /services/media/downloads and the managers read it; the managers write into the libraries and the library server reads them. That sharing is the entire point of the stack. Yet each module declared every shared directory it touched as its own directory resource, with an owner and a mode. /services/media/downloads was written seven times, as seven private directories that happen to be the same path.

The resolver refuses exactly that, and is right to. Two modules declaring one path on one node are refused by name, with no exemption for identical content and no merge — because two owners of one path is the class of fault this repository keeps recording (04-ISSUES/036). So the stack as written refuses its own only sensible assignment: all of it on one machine, sharing one filesystem. It passed today only because no test co-resolves any two of the eight. The first machine assigned two of them together is where the refusal would have surfaced.

The vocabulary had one word for two intentions, and this was already seen. 04-ISSUES/026 found the same gap from the other side and named it precisely: two kinds of mount are spelled identically — the directory my data lives in, which the mesh creates and owns, and a facility I was granted, which already exists and the mesh only reaches. That issue deferred inventing a field to tell them apart, because doing so is a design decision and it declined to make one to get a check green. This is that decision.

A directory resource is owned, on every axis. The host creates it, sets its owner and mode, and removes it when it is empty and no longer declared — it removes what it made and leaves what it merely configured (ADR 0005, ADR 0030). A shared media library is none of that. It existed before the mesh, several modules read and write it at once, and losing it is the one failure that does not recover. It is not any module's resource; it is the operator's, and a module only needs to be let at it.

Considered Options

  1. Make the stack one module with several containers, the way the mail system already is. The issue raises it directly: is a set of modules that must share a filesystem really one module? Rejected. The eight are independently assignable and independently useful — a person may run the download client without the library server, or the film manager without the music one — and folding them into a single module to express a shared directory would make what a module is turn on an incidental filesystem contract. It also does not generalise: the next pipeline of modules handing files to each other on one machine (an ingest folder, a spool, a drop directory) would face the same wall and the same wrong remedy.

  2. One module owns the directories and the rest require them. Rejected, and this is the heart of the decision. Nobody owns shared operator data. The library predates the mesh and outlives any one module, so making the library server or a manager its owner means unassigning that module orphans everyone else's access — and the owner would set the owner and mode of a tree it did not create. Ownership is the wrong relationship to model, because the true owner is not a module at all.

  3. A flag on a directory resource — external: true, or an owner of operator. Rejected. It overloads one shape with a boolean that inverts every one of its semantics: created becomes must already exist, owned becomes touch nothing, removed-when-empty becomes never removed. That is the two-kinds-spelled-identically trap of 04-ISSUES/026 reintroduced with a single quiet field — a reviewer reading type: directory would have to check one boolean elsewhere to know whether the mesh owns the thing at all.

  4. A distinct accesses declaration, separate from resources. Adopted.

Decision

Shared, pre-existing data is operator-owned and external. The mesh does not create it, does not set its owner or mode, does not reconcile it and does not remove it. A media library, a download spool, an ingest directory is the operator's, and the mesh is a guest in it.

A module declares that it needs access to such a path, not that it owns a resource there. The manifest field is accesses: a list of {path, mode}, where mode is read or read-write and absent narrows to read — the safe default, because the danger with an access is being given more than was meant, not less. A module's own configuration and state directories stay owned directory resources; only the shared, pre-existing paths become accesses.

The host mounts an accessed path and owns nothing about it. It reaches the machine as a new declaration shape, access, distinct from directory. The host confirms the path is present and does nothing else — no create, no chown, no mode, no removal.

An accessed path absent at apply time is refused, clearly, not created. The mesh does not own it, so conjuring it would be a lie the host then acts on — and specifically the lie 04-ISSUES/026 records, where a bind mount whose source does not exist is made by the container runtime as root with the wrong ownership. The host says the operator must provide the path instead.

Several modules accessing one path is normal, and never refused. The duplicate-path refusal is about ownership, not use: it applies to resources a module owns and to those alone. An access is not a resource and never enters the check, so the eight-module stack co-resolves. What stays refused is genuine rivalry — two modules owning one path — and the new contradiction it exposes: a path one module owns while another merely accesses it, because that asserts both that the mesh owns the directory and that the operator does.

This is a decision and not a patch because it settles what a module may say about a path it did not make, which every co-located file-handoff in the catalogue now and later depends on — and because it draws the ownership line ADR 0030 started: the host owns what it made and keeps what it merely configured, and this adds the third case it did not have a word for — what it neither made nor configured, and must not touch.

How each claim is checked

  • The stack co-resolves. A control-plane unit test assigns two modules that declare access to one path on one node and asserts no refusal — the exact case the resolver refuses when the same path is owned. The mirror test, two modules owning one path, still refuses, so the sharing vocabulary does not weaken the rule it sits beside.
  • Ownership and access cannot both be claimed of one path. A unit test asserts the resolver refuses a path one module owns and another accesses, naming both.
  • Absent is refused, not created. A host unit test applies an access to a path that does not exist and asserts a clear refusal that names the operator, and that nothing was created.
  • Present is confirmed and nothing moves. A host unit test applies an access to an existing directory and asserts the apply reports no change and disturbs nothing.
  • Undeclaring never removes. A host unit test drops a previously declared access and asserts the operator's directory and its contents are left exactly as they were — the data-loss failure ADR 0030 exists to prevent, on a directory the mesh never made.
  • Every media manifest is corrected. No /services/media/* path is an owned directory resource in any of the eight; each is an accesses entry, and each module's own config and state directories remain owned. Checked by the control-plane manifest parser, which now understands accesses and refuses a malformed one.

Consequences

A shared filesystem between co-located modules now has a vocabulary, and it is not the media stack's alone: any pipeline handing files to a neighbour on one machine — an ingest directory, a spool, a drop folder — says I access this operator path rather than I own this directory, and several of them may say it of one path.

Unassigning a module that reached shared data leaves the data. Correct, and the same trade ADR 0030 made for owned directories: removing data is a person's act, done knowingly, not a side effect of unassignment.

The operator must provision the shared paths before the stack is applied, and a machine that lacks one is told plainly which. That is a real new obligation, and it is the right one: the mesh cannot own what predates it, so it cannot create it either, and saying so at apply time beats a directory conjured as root and a service that half-works.

The host vocabulary grew by one shape, which is a cost — every added shape widens what a compromised control plane can express (ADR 0005). It is a narrow one: an access is confirmed by a stat and grants the host no new action. It earns its place by letting the host refuse to create what it must not own, which no existing shape could say.

A path can be both owned and accessed only by refusal. If a future manifest declares one path as an owned directory in one module and an access in another, the resolver refuses it rather than guessing which is meant — the two assertions about who owns the data cannot both hold.

References

  • 04-ISSUES/036 — six (in fact eight) modules own what they must share; the problem this resolves
  • 04-ISSUES/026 — the two kinds of mount spelled identically, which deferred this field to a decision
  • ADR 0030 — data outlives the mesh; the host keeps what it did not make. This record adds the case it had no word for
  • ADR 0005 — the host removes what it made and leaves what it merely configured; the vocabulary is finite and every shape is a security decision
  • ADR 0040 — what a module is; an access is a new thing a module may say about the machine it lands on
  • mesh-control feat/shared-data-access, mesh-catalog feat/media-access-not-ownership, mesh-host feat/mount-operator-owned — the mechanism, the corrected manifests, and the host shape