Issue 118 and ADR 0112 (proposed): a module definition names no node, no mesh and no path #112

Closed
jschoubben wants to merge 3 commits from decision/0112-a-module-definition-names-no-path into main
Owner

ADR 0112 is proposed, for your review. Nothing is implemented, and no design document rests on it yet.

Issue 118: the evidence

Every module definition chooses where its files live on the machine: 789 host-path strings in 70 of 71 definitions. Mounts are checked (ADR 0091). The same paths retyped as values are not. The issue records what that has already allowed:

  • A DNS provider that would provision nobody, silently. It reads its contributions file at a host path its container doesn't mount. It's latent only because nothing requires public-dns yet.
  • A contributions file that names each credential by host path. That forces every provider to mount its grants directory at the identical path, a convention nothing states or checks.
  • The SDK's reconcile loop treating an unwritten contributions file as empty, without a word. That defeats the control plane writing it even when empty, which it does precisely so the two cases can be told apart.
  • Defaults in code that disagree with their own manifests.
  • No way to assign one module to one node twice. Every identity is keyed by the module's name.

ADR 0112: the proposed answer

It's the same argument ADR 0038 made for ports. A definition names variables. Installing it resolves every one or refuses, from three sources:

  1. The assignment's own configuration: settings (ADR 0046), such as an endpoint binding a public name to a port.
  2. Provisions resolved against a contract: a database, a bucket, a vhost, a seat's occupant, another assignment, and a secret from the vault (ADR 0085). The assignment chooses which node answers (ADR 0084).
  3. What the mesh generates or knows: the delivery credential it mints for each provision (ADR 0048), assigned ports (ADR 0038), machine facts.

A directory becomes a provision. The module requires one by name, with its owner, mode and persistence. Where it lands on the machine is the assignment's: a node's default layout, or somewhere specific, such as where an adopted machine's data already is. The mesh's own files stop carrying host paths. An assignment gets its own identity, so a module may run twice on one node.

Rejected:

  • checking that the copies agree, which checks something that shouldn't exist;
  • rewriting paths per assignment, which means inferring which strings are paths from their shape.

Left to the design: the variable syntax, a node's default layout, and when a second instance becomes possible.

Review notes

  • The draft first listed "minted secrets" under what the mesh generates. That's corrected in the second commit: a module's own secrets come from the vault (ADR 0085), and the mesh mints only the delivery credential (ADR 0048).
  • cycle.py, records.py and index.py all pass. The leak scan is clean.
**ADR 0112 is `proposed`, for your review.** Nothing is implemented, and no design document rests on it yet. ## Issue 118: the evidence Every module definition chooses where its files live on the machine: **789 host-path strings in 70 of 71 definitions.** Mounts are checked (ADR 0091). The same paths retyped as values are not. The issue records what that has already allowed: - **A DNS provider that would provision nobody, silently.** It reads its contributions file at a host path its container doesn't mount. It's latent only because nothing requires `public-dns` yet. - **A contributions file that names each credential by host path.** That forces every provider to mount its grants directory at the identical path, a convention nothing states or checks. - **The SDK's reconcile loop treating an unwritten contributions file as empty, without a word.** That defeats the control plane writing it even when empty, which it does precisely so the two cases can be told apart. - **Defaults in code that disagree with their own manifests.** - **No way to assign one module to one node twice.** Every identity is keyed by the module's name. ## ADR 0112: the proposed answer It's the same argument ADR 0038 made for ports. A definition names **variables**. Installing it resolves every one or refuses, from three sources: 1. **The assignment's own configuration**: settings (ADR 0046), such as an endpoint binding a public name to a port. 2. **Provisions resolved against a contract**: a database, a bucket, a vhost, a seat's occupant, another assignment, and **a secret from the vault** (ADR 0085). The assignment chooses which node answers (ADR 0084). 3. **What the mesh generates or knows**: the delivery credential it mints for each provision (ADR 0048), assigned ports (ADR 0038), machine facts. **A directory becomes a provision.** The module requires one by name, with its owner, mode and persistence. Where it lands on the machine is the assignment's: a node's default layout, or somewhere specific, such as where an adopted machine's data already is. The mesh's own files stop carrying host paths. An assignment gets its own identity, so **a module may run twice on one node**. **Rejected:** - checking that the copies agree, which checks something that shouldn't exist; - rewriting paths per assignment, which means inferring which strings are paths from their shape. **Left to the design:** the variable syntax, a node's default layout, and when a second instance becomes possible. ## Review notes - The draft first listed "minted secrets" under what the mesh generates. That's corrected in the second commit: a module's own secrets come from the vault (ADR 0085), and the mesh mints only the delivery credential (ADR 0048). - `cycle.py`, `records.py` and `index.py` all pass. The leak scan is clean.
jschoubben added 2 commits 2026-09-25 20:13:51 +00:00
Issue 118 records what a review of where module code reads its files found: 789 host-path strings
in 70 of the catalogue's 71 definitions, every one a decision the definition makes about a machine.
Mounts are checked (ADR 0091); the same paths retyped as values are not. It records what that has
already allowed — a DNS provider that would provision nobody silently, a contributions file that
names credentials by host path and so forces every provider to mount at the identical path, an SDK
loop that treats an unwritten contributions file as empty without a word, defaults in code that
disagree with their own manifests — and that no module can be assigned to one node twice, because
every identity is keyed by the module's name.

ADR 0112, proposed for review, answers it the way ADR 0038 answered ports: a definition names
variables, and installing it resolves every one or refuses, from three sources — the assignment's
own configuration, provisions the mesh resolves against a contract, and what the mesh generates or
knows. A directory becomes a provision: the module requires one by name with its owner, mode and
persistence, and where it lands is the assignment's. The mesh's own files stop carrying host paths.
An assignment gets an identity of its own, so a module may run twice on one node.

Checking copies for agreement was rejected as checking something that should not exist; rewriting
paths per assignment was rejected as inferring which strings are paths by their shape. Syntax, a
node's default layout, and when a second instance becomes possible are left to the design.
The first draft listed minted secrets under what the mesh generates. ADR 0085 made a module's own
secret — a password, an internal token, an external key it was handed — a secret provision answered
by the vault, like a database by the store. What the mesh still mints is the delivery credential for
each provision a module takes (ADR 0048), the vault's own included.
jschoubben added 1 commit 2026-09-25 20:21:43 +00:00
- Secrets follow ADR 0085 as amended: a module's own secret is a provision the controller mints and
  the vault records. The previous commit had that backwards. Whether the vault should generate
  instead is recorded as an open question, not decided.
- A directory's contract is owner and mode only. The persistence flag was the keep flag ADR 0030
  refused; a directory is kept while it holds anything, and disposable data is a named volume (0107).
- An operator's shared data stays an access (ADR 0051), which rejected an operator-owned directory.
  Only where its path is written moves to the assignment.
- The records it changes on acceptance are named: 0051, 0091, 0046 (settings keyed by instance),
  0084 (a provider is a node and an instance), and the glossary, which gains its new words only
  when the record is accepted.
- How it is checked covers every stated rule. Container-side paths are no longer flagged by the
  host-path rule, and code fallbacks are covered.
- Provisions are what other modules provide. A seat's occupant is not listed as one, and the vault
  is not described as selectable per assignment.
- 'Control plane' becomes 'controller'. The provider count is ten of eleven, not eleven of twelve.
Author
Owner

Closing: superseded by #113 (now merged), which carries a later ADR 0112 — retitled "everything it needs is a requirement the mesh resolves", with the six mechanisms a module gets what it needs listed, and the one-assignment-per-node consequence stated rather than asserted in passing.

The report this branch filed as issue 118 is on main as issue 119, which is where #113's ADR 0112 points. That renumber is what resolved the collision: 118 was also claimed by issue/118-umami-store-query-timeout, and with this report at 119 the umami report keeps 118.

Nothing here is lost — every paragraph of the report and the record is in #113's versions. Branch tip was d169f9d8cd3cf5bd1bfc8d8d8d9dc77acb07f7f3; branch deleted.

Closing: superseded by #113 (now merged), which carries a later ADR 0112 — retitled *"everything it needs is a requirement the mesh resolves"*, with the six mechanisms a module gets what it needs listed, and the one-assignment-per-node consequence stated rather than asserted in passing. The report this branch filed as issue **118** is on `main` as issue **119**, which is where #113's ADR 0112 points. That renumber is what resolved the collision: `118` was also claimed by `issue/118-umami-store-query-timeout`, and with this report at 119 the umami report keeps 118. Nothing here is lost — every paragraph of the report and the record is in #113's versions. Branch tip was `d169f9d8cd3cf5bd1bfc8d8d8d9dc77acb07f7f3`; branch deleted.
jschoubben closed this pull request 2026-09-26 12:18:57 +00:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/hq#112