The consolidation left a sparse sequence -- 1, 4, 6, 7, 9, 10, 12, 15, 16, 18, 19, 25, 34, 35, 36, 37, 40, 42, 44, 45, 48, 49, 58 -- where the gaps were only the archaeology of what used to be there. Renumbered contiguously. Renames run in ascending order, so every target number is already free and no two files ever collide. The reference rewrite is one simultaneous pass rather than a sequence of replacements. Numbers moved into slots other numbers were vacating -- the node host went 37 to 16 while the lab went 16 to 9 -- so replacing one at a time would have cascaded and silently pointed things at the wrong record. Seven plain-text references survived the merges as prose rather than links, naming records that no longer existed: the enrolment token, the link boundary, what a declaration is, reachability, the repository structure. Each mapped to the consolidated record that now holds it. Verified rather than assumed: every [ADR NNNN](path) link now has matching text and target, checked across the whole repository, and the checker passes. Frontmatter `consolidates:` lists dropped -- they named records that are gone, and each consolidated record already says in prose what it absorbed.
168 lines
8.7 KiB
Markdown
168 lines
8.7 KiB
Markdown
# One kind of edge
|
|
|
|
> **Superseded in part by [`worked-provider.md`](worked-provider.md).** Working postgres through
|
|
> completely shows there are **two** kinds of edge, not one: *presence* — the thing exists and is
|
|
> reachable, nothing created — and *instantiation* — the provider is asked to make something for
|
|
> this consumer and hands back credentials. Instantiation implies presence; presence does not
|
|
> imply instantiation. Everything else below stands; the claim in the title does not.
|
|
|
|
A design, not an account of what exists. [`analysis.md`](analysis.md) measured the current
|
|
catalogue and its value here is two lessons rather than its machinery: a field that means
|
|
*depends on* should say so, and placement does not belong in a manifest.
|
|
|
|
## The idea
|
|
|
|
**A module provides names. A module requires names. That is the only edge.**
|
|
|
|
Everything the effort listed as separate declarations turns out to be one relation with
|
|
different kinds of name on either end.
|
|
|
|
```
|
|
postgres provides postgres
|
|
kitty provides kitty, terminal
|
|
xterm provides xterm, terminal
|
|
anthropic provides anthropic, ai-assistant
|
|
|
|
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: 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 | 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 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
|
|
|
|
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 `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
|
|
reason, and through the same code, that it cannot be installed without its libraries.
|
|
|
|
And it means the host's capability detection — which already reports what a machine can be
|
|
asked to do, with a reason for each verdict — **is the node's provides-list**. It was built to
|
|
be read by a person; it turns out to be an input to resolution.
|
|
|
|
## What a module declares
|
|
|
|
```yaml
|
|
name: meshboard
|
|
|
|
provides: [meshboard]
|
|
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
|
|
- container: ...
|
|
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 `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
|
|
deciding it in the manifest — a module pinning its database to a named node, so that a second
|
|
node cannot provide it without editing the module that consumes it. That is the mistake this
|
|
separation exists to avoid.
|
|
|
|
## What it deletes
|
|
|
|
The question the effort opened with, answered for the design rather than for what exists.
|
|
|
|
- **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.
|
|
- **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 0019](../../02-DECISIONS/0019-modules-and-the-graph.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.
|
|
|
|
## What it does not delete, and should not
|
|
|
|
**A resolver still has to exist**, and it will need version constraints, conflict handling and
|
|
a story for when two modules provide one name and nothing says which to use. That is the
|
|
long-solved and easily-botched part, and the design should say what it **delegates** to the
|
|
platform's package manager rather than reimplementing.
|
|
|
|
## Open
|
|
|
|
- **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.
|
|
- **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.
|