Commit Graph
4 Commits
Author SHA1 Message Date
jschoubben 13c6068874 011: the provider shape generalises, and two things differ inside it
The broker has all nine properties the store has. So do the object store and the
image registry. A substrate service is a SERVICE PLUS A FACTORY, there are four
of them, and the pattern generalises past the substrate: anything granting
something per consumer has this shape.

Two differences matter more than the similarity.

The broker cannot be managed over the broker. ADR 0001 makes it the channel
every node takes work from and ADR 0039 makes it the security boundary, so the
module providing it is also the way modules are managed — a declaration cannot
be delivered to it over itself. Nothing else has that property; the store is
consumed by the control plane but is not how the control plane REACHES anything.
This is what the carried bundle exists for: the broker is raised from what the
host carries because there is no other way to raise it. A constraint on one
module, not a general rule, and a schema with no way to say so hides it.

And two modules of identical shape want opposite instance counts. The broker is
one per mesh by decision. The store cannot be, because a node that must keep
working while disconnected cannot depend on a database elsewhere. Which settles
what cases.md left open: how many instances is NOT derivable from what a module
is. It is a per-module decision, it has to be declared, and nothing in provides,
requires or excludes says it.

Revocation differs in consequence too. Dropping a database leaves data until
something removes it — a leak, recoverable. Dropping a virtual host loses
whatever was undelivered — silent, and not. Same relation, different blast
radius, which argues for the provider deciding what revocation means rather than
the mesh applying one rule.

File renamed: it was never really about postgres.
2026-08-26 22:54:49 +02:00
jschoubben f160b28a71 011: postgres worked through, and "one kind of edge" was wrong
The tidy version said a module provides names and requires names and that is the
only edge. Working postgres through completely disproves it.

A small game wanting to store data does not require postgres to EXIST. It
requires postgres to MAKE IT A DATABASE and hand back credentials. Those are
different relations in every way that matters: one creates something per
consumer, carries a payload back, can be revoked, and leaves the provider
holding state about who was granted what. The other creates nothing.

So: two kinds of edge, one graph. Instantiation implies presence; presence does
not imply instantiation. The current system already had exactly this split —
`dependencies` for presence, `requires: provision:` for instantiation, with the
resolver deriving one from the other. analysis.md called that derivation a
convenience. It is not: it is the correct relationship between two genuinely
different relations, and the design had collapsed them.

Postgres also turns out to be nine things, not one. A container. Persistent
state where moving nodes is a migration rather than a reschedule. Configuration
partly derived from the machine's hardware. A tool surface. A provisioner. Its
own bookkeeping about what it granted, which is not the data it stores. An
exposure decision per node it runs on. Credentials it generates, which means a
provisioning edge carries a secret. And health that is not "the container is up".

Four questions the worked example makes concrete rather than abstract. WHICH
postgres, when there are two — a consumer of `terminal` does not care and a
consumer of a database cares permanently. How many instances a module should
have, which cannot be a global rule because one-per-mesh is wrong for a store a
disconnected node needs and one-per-node is wrong for the mesh's own registry.
What happens to a grant when its consumer is removed, where dropping is data
loss and keeping is a leak. And whether a declaration is composed PER NODE from
what that node reported — because tuning follows hardware the control plane
cannot know, and the alternative is the host deciding, which ADR 0037 forbids.
2026-08-26 22:53:59 +02:00
jschoubben f55ecc1a47 011: an abstract name needs providers that are actually substitutable
Two corrections from the operator, and the first improves the design rather than
narrowing it.

`database` is not an edge. The test it fails, and the test the proposal was
missing: can a consumer be switched from one provider to another WITHOUT
CHANGING? A module speaking Postgres does not speak MongoDB or SQL Server —
different wire protocol, dialect, driver — so a consumer declaring `requires:
database` and handed any of them breaks. The name promises what no provider can
deliver, and the resolver would report a requirement satisfied that is not.

`terminal` passes: anything that runs a command in a terminal works and the
consumer never learns which it got.

So the ADAPTER is what creates an interface. `ai-assistant` is legitimate exactly
because adapters normalise what is behind it. Without one there is no interface,
there is a category — and a category is a TAG. Tags describe, edges bind, and
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.

And the domain module goes. A `networking` module gathering a firewall, a
resolver and a proxy under one name came from an older shape and does not fit —
there is no such thing to install. There is core infrastructure: concrete
modules named individually, not flavourable, with no grouping module standing in
front of them.

Fixed three places where the revision left the old rule standing, including an
example manifest still requiring `database` — the kind of contradiction that
would have been read as the design rather than as a leftover.
2026-08-26 22:44:37 +02:00
jschoubben e20a09ae80 011: one kind of edge
The design, rather than an account of what exists. A module provides names and
requires names, and that single relation absorbs three things this effort had
listed separately: requiring another module is requiring a concrete name,
requiring a resource is requiring an abstract one, and an interface is simply a
name with more than one provider. Nothing has to declare that it is an
interface — it either has one provider or several.

The move that does the most work: a NODE provides names too. Its profile is a
set of them — display-server, container-runtime, an architecture — so a module
requiring a display server is satisfied by the node exactly as one requiring a
database is satisfied by another module. One resolution instead of two, and a
graphical application cannot land on a node without a display server for the
same reason, through the same code, that it cannot land without its libraries.

Which makes the host's capability detection an input to resolution rather than
something a person reads. It was built to be read; it turns out to be a
provides-list.

`excludes` is the one genuinely new relation, because it is not derivable: two
modules that both provide message-bus look interchangeable when installing both
would break the machine.

Constraints are not placement. They say what must be true of a node, never which
node — which is the mistake the measurement found in the current catalogue,
where a module pins its database to a named node so a second node cannot provide
it without editing the consumer.

What it deletes, for the design: the module/resource distinction, the interface
as a kind of thing, capability checking as a separate mechanism, domain grouping
— folders assert relationships where edges record them, so a domain becomes a
query over the graph rather than a directory somebody keeps true — and possibly
tiers, if a tier is just a computed level.

What it does not delete, stated so it is not discovered later: a resolver still
has to exist, with version constraints and conflicts, and the design owes an
answer on what it delegates rather than reimplements.
2026-08-26 22:39:17 +02:00