diff --git a/01-RESEARCH/011-the-module-graph/00-overview.md b/01-RESEARCH/011-the-module-graph/00-overview.md index 890af48..d9e12b6 100644 --- a/01-RESEARCH/011-the-module-graph/00-overview.md +++ b/01-RESEARCH/011-the-module-graph/00-overview.md @@ -19,7 +19,10 @@ what they offer, and what they exclude — and what that replaces. **The design is in [`proposal.md`](proposal.md): one kind of edge.** A module provides names and requires names, and that single relation absorbs requiring a module, requiring a resource, -and the interface-and-adapter idea — an interface is simply a name with more than one provider. +and the interface-and-adapter idea. An abstract name is legitimate **only where providers are +genuinely substitutable** — `terminal` passes, `database` does not, because a consumer speaking +Postgres does not speak MongoDB. The adapter is what creates an interface; without one there is +a **tag**, which describes and does not bind. A **node provides names too**, which makes capability checking stop being a separate mechanism and makes the host's own capability report an input to resolution rather than something a person reads. @@ -132,7 +135,8 @@ integration being wrong looks like from the outside. |---|---| | ~~What does the graph **delete**?~~ | **Answered for the design** in [`proposal.md`](proposal.md): the distinction between requiring a module and requiring a resource, the interface as a kind of thing, capability checking as a separate mechanism, domain grouping, and possibly tiers as a separate concept. *(For the existing system the answer was nothing, because it is already there — [`analysis.md`](analysis.md).)* | | When two modules provide one name, who chooses? | The substance of the interface idea, and the proposal does not settle it: a node setting, a mesh setting, or an explicit pin. | -| Does an abstract name need a type? | A consumer of `database` needs credentials; a consumer of `terminal` needs a command to run. Whether that difference lives in the name, beside it, or in what the provider hands back is undecided. | +| What does a provider hand back? | A consumer requiring `postgres` needs credentials and an address; one requiring `terminal` needs a command. Same relation, different shape flowing across it. | +| ~~Where do domain modules fit?~~ | **Answered: they do not.** A `networking` module gathering a firewall, resolver and proxy under one name came from an older shape. There is no such thing to install — there is core infrastructure, a set of concrete modules named individually, not flavourable, with no grouping module in front of them. | | Would the missing declarations be used? | Zero manifests declare exclusions or capabilities, which is equally consistent with *nobody needs them* and *nobody can express them*. Nothing measured separates those. | | Should `provider:` become a real edge, or should the relationship be declared twice? | It names a module and means *depends on*. Reading it as an edge fixes the closure; the alternative is requiring the consumer to also list it under `dependencies:`, which is duplication a resolver already avoids elsewhere. | | Should placement leave the catalogue? | A provision pins itself to a named node, in the manifest. Which node runs what is an inventory decision — tier 2 by the skeleton's own test — and having it in tier 4 means a second node cannot provide the mesh's database without editing the module that consumes it. | diff --git a/01-RESEARCH/011-the-module-graph/proposal.md b/01-RESEARCH/011-the-module-graph/proposal.md index a208b15..c3eebcc 100644 --- a/01-RESEARCH/011-the-module-graph/proposal.md +++ b/01-RESEARCH/011-the-module-graph/proposal.md @@ -12,29 +12,61 @@ Everything the effort listed as separate declarations turns out to be one relati different kinds of name on either end. ``` -postgres provides postgres, database -lavinmq provides lavinmq, message-bus +postgres provides postgres kitty provides kitty, terminal +xterm provides xterm, terminal anthropic provides anthropic, ai-assistant -meshboard requires database, message-bus +meshboard requires postgres, lavinmq vscode requires terminal, display-server ``` A name is either **concrete** — a module's own name, so `requires: postgres` means that module -and no other — or **abstract**, so `requires: database` means whatever provides it. The -difference is not in the schema; it is whether more than one thing offers the name. +and no other — or **abstract**, so `requires: terminal` means whatever provides it. That single relation absorbs three things the effort had listed separately: | Was going to be | Is | |---|---| | requires another module | requires a concrete name | -| requires a resource — a database, a bucket | requires an abstract name | -| an interface, with adapters providing it | an abstract name several modules provide | +| requires a resource | requires the module that provides it, by name | +| an interface, with adapters providing it | an abstract name, legitimate only where an adapter makes providers substitutable | -The interface stops being a kind of module. It is a name with more than one provider, and -nothing has to declare that it is one. +The interface stops being a kind of module. It is a name with more than one provider *and a +contract they all satisfy*, and nothing has to declare that it is one. + +### An abstract name is only legitimate when providers are actually substitutable + +The test, and it is a strict one: **can a consumer be switched from one provider to another +without changing?** + +`terminal` passes. Anything that runs a command in a terminal works, and a consumer never +learns which one it got. + +**`database` fails, and it is the example worth keeping.** A module connecting to Postgres does +not connect to MongoDB, or to SQL Server, or to MariaDB. Different wire protocol, different +dialect, different driver. A consumer that declared `requires: database` and was handed any of +them would break — so the name promises something no provider can deliver, and the resolver +would satisfy a requirement that is not satisfied. + +That failure has a shape this repository already knows: something declared, accepted, and not +true. It is worse here than usual because the graph would report success. + +**The adapter is what creates an interface.** `ai-assistant` is a legitimate abstract name +exactly when adapters exist to normalise the providers behind it. Without an adapter there is +no interface — there is a category, and a category is not an edge. + +So `postgres` is required by name, and a module that could genuinely work with several stores +requires whichever it actually speaks to. + +### Categories are catalogue metadata, not structure + +*Database* is still a useful word — for finding things, for a person browsing what the mesh can +host, for grouping in an interface. It is a **tag**. + +Tags describe. Edges bind. Keeping them apart is what stops the catalogue acquiring a second +kind of relationship that looks like a dependency and is not — which is what a folder named +after a domain already was. ## The node provides names too @@ -42,7 +74,7 @@ The move that makes capabilities stop being a separate system. A node's profile is a set of provided names. `display-server`. `container-runtime`. `amd64`. A module requiring `display-server` is satisfied by **the node**, exactly as a module -requiring `database` is satisfied by another module. +requiring `postgres` is satisfied by another module. So there is one resolution rather than two: *is this name provided by anything available here?* A graphical application cannot be installed on a node with no display server for the same @@ -58,18 +90,23 @@ be read by a person; it turns out to be an input to resolution. name: meshboard provides: [meshboard] -requires: [database, message-bus, container-runtime] +requires: [postgres, lavinmq, container-runtime] # by name: it speaks their protocols excludes: [] +tags: [observability] # for finding it, never for resolving it -runs: # what applying it means +runs: # what applying it means - container: ... -constraints: # what must be true of a node that runs it +constraints: # what must be true of a node, never which node - architecture: amd64 ``` +`postgres` and `lavinmq` are concrete because this module speaks their wire protocols and would +break against anything else. `container-runtime` is abstract and satisfied by the **node**. + **`excludes`** is the one genuinely new relation: naming something that cannot coexist with this. It is not derivable from requires and provides, and without it two modules that both -provide `message-bus` look interchangeable when installing both would break the machine. +provide `terminal` — or two that both want port 443 — look independent right up until +installing the second breaks the first. **`constraints` are not placement.** They say what must be true of a node, never which node. Which node runs what is an inventory decision, and the measurement found the current catalogue @@ -84,9 +121,15 @@ The question the effort opened with, answered for the design rather than for wha - **The distinction between requiring a module and requiring a resource.** One relation. - **The interface as a kind of thing.** A name with several providers. - **Capability checking as a separate mechanism.** The node is a provider. -- **Domain grouping** ([ADR 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md)). - Folders assert relationships; edges record them. A domain is then a *query* over the graph — - what changes together — rather than a directory somebody has to keep true. +- **The domain module** — a `networking` module that exists to gather a firewall, a resolver and + a proxy under one name. It came from an older shape and does not fit: there is no such thing + to install. There is **core infrastructure**, which is a set of concrete modules named + individually — a firewall, a store, a resolver — with no flavour and no grouping module + standing in front of them. +- **Domain grouping as structure** ([ADR 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md)). + Folders assert relationships; edges record them. What grouping was for — finding things, + seeing what belongs together — is a **tag** and a *query* over the graph, neither of which + anybody has to keep true by hand. - **Tiers as a separate concept**, possibly. A tier is a level in the graph, and levels are computed. Whether the coarse boundary is still worth naming is below. @@ -99,17 +142,20 @@ platform's package manager rather than reimplementing. ## Open -- **When two modules provide one name, who chooses?** The consumer names `database` and gets - something. Whether the choice is a node setting, a mesh setting, or an explicit pin is the - substance of the interface idea, and this proposal does not settle it. +- **When two modules provide one abstract name, who chooses?** A node with both `kitty` and + `xterm` satisfies `terminal` twice. Whether the choice is a node setting, a mesh setting, or + an explicit pin is the substance of the interface idea, and this proposal does not settle it. + It is a smaller question than it was: the strict substitutability test means the consumer + genuinely does not care which it gets, so the choice is about preference rather than + correctness. - **Are tiers a view of the graph, or a boundary that survives it?** If tiers are levels, the concept is derived and the word is a convenience. If the tier rule — *a tier may depend only on tiers below it* — is meant to be enforceable, it is a constraint on the graph rather than a description of it, and it has to be stated as one. -- **Does a name need a type?** `database` and `terminal` are both abstract names, and a consumer - of the first needs credentials while a consumer of the second needs a command to run. Whether - that difference lives in the name, in a type beside it, or in what the provider hands back is - undecided. +- **What does a provider hand back?** A consumer requiring `postgres` needs credentials and an + address; one requiring `terminal` needs a command. The requirement is satisfied by the same + relation in both cases, and what flows across it is not the same shape. Whether that lives in + the name, beside it, or in what the provider returns is undecided. - **What does a node provide that is not a capability?** Its architecture is a provided name under this scheme, and so is its operating system. That may be elegant or may be one abstraction too far; nothing here tests it.