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.
This commit is contained in:
@@ -17,6 +17,13 @@ touches:
|
||||
Whether the catalogue's missing structure is a **graph** — modules declaring what they need,
|
||||
what they offer, and what they exclude — and what that replaces.
|
||||
|
||||
**[`worked-postgres.md`](worked-postgres.md) works one module through completely**, and breaks
|
||||
the tidy version. A database is nine things, not one — and a small game asking the mesh for its
|
||||
own database shows there are **two kinds of edge**: *presence*, where the thing must exist, and
|
||||
*instantiation*, where a provider makes something for a consumer and hands back credentials.
|
||||
Instantiation implies presence and not the reverse. The current system already had this split
|
||||
and the design had collapsed it.
|
||||
|
||||
**[`features.md`](features.md) answers what happens to `feature`.** It is one word for four
|
||||
things spanning three tiers — artifacts built once per version, resources applied to a machine,
|
||||
actions run against something that is not this machine, and checks that are requirements in
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# One kind of edge
|
||||
|
||||
> **Superseded in part by [`worked-postgres.md`](worked-postgres.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.
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# Postgres, all the way through
|
||||
|
||||
One module, worked out completely, because it is the case that breaks the tidy version. It
|
||||
looks like *a container that runs a database* and it is at least nine things.
|
||||
|
||||
## What it carries
|
||||
|
||||
**1 — A supervised container.** An image, a version, a data volume, and configuration. Easy,
|
||||
and the only part the phrase "a docker service" describes.
|
||||
|
||||
**2 — Persistent state, and where it lives matters.** The volume is the database. Moving this
|
||||
module between nodes is not rescheduling; it is a migration. Almost nothing else in the
|
||||
catalogue has this property, and nothing in `provides` / `requires` expresses it.
|
||||
|
||||
**3 — Configuration that is partly the machine's.** Tuning follows the hardware — memory,
|
||||
storage. A declaration generated centrally cannot know those, and
|
||||
[research 012](../012-the-minimum-viable-node/00-overview.md) says the machine's own values win
|
||||
on conflict. So some of this module's configuration is *derived from the node it lands on*.
|
||||
|
||||
**4 — A tool surface.** It exposes capabilities agents can call — query, list, provision. That
|
||||
is not a resource on a machine and not an artifact; it is a contract the mesh publishes on the
|
||||
module's behalf.
|
||||
|
||||
**5 — A provisioner.** The part that matters, and the one below.
|
||||
|
||||
**6 — Its own bookkeeping.** The provisioner must remember what it granted to whom, or it
|
||||
cannot revoke, rotate or clean up. So a module that provides state to others *also* holds state
|
||||
about its providing — and that state is not the database's data.
|
||||
|
||||
**7 — An exposure decision, per node it runs on.** Reachable from the machine only, from the
|
||||
local network, or publicly. That is a property of *this assignment*, not of the module: the
|
||||
same module on two nodes may answer differently.
|
||||
|
||||
**8 — Credentials it generates.** Per consumer, and they have to reach the consumer. Which
|
||||
means a provisioning edge carries a payload, and the payload is a secret.
|
||||
|
||||
**9 — Health that is not "the container is up".** A container running and a database accepting
|
||||
connections are different facts, and the second is the one anything cares about. This is the
|
||||
host's read-back rule, at a distance.
|
||||
|
||||
## The provisioner is a second kind of edge
|
||||
|
||||
The tidy version of this effort said: *a module provides names, a module requires names, that is
|
||||
the only edge.* A small game wanting to store data shows it is not.
|
||||
|
||||
```
|
||||
my-cool-game requires postgres # I speak its protocol, it must exist
|
||||
my-cool-game requires a database FROM postgres, called my-cool-game
|
||||
```
|
||||
|
||||
The first is **presence**: the thing exists and is reachable. Nothing is created; nothing flows
|
||||
back. `vscode requires terminal` is this, and so is `requires container-runtime`.
|
||||
|
||||
The second is **instantiation**: the provider is asked to make something *for this consumer*,
|
||||
and hands back what the consumer needs to use it. A database, a user, a password, an address.
|
||||
|
||||
They differ in every way that matters:
|
||||
|
||||
| | presence | instantiation |
|
||||
|---|---|---|
|
||||
| creates something | no | yes, one per consumer |
|
||||
| carries a payload back | no | credentials, an address |
|
||||
| can be revoked | — | yes, and must be when the consumer goes |
|
||||
| provider holds state about it | no | yes — who was granted what |
|
||||
| satisfied by | anything providing the name | that provider, specifically |
|
||||
|
||||
**This is the mesh's actual power**, in the operator's words: a small game declares it wants a
|
||||
database and the mesh makes one. Nobody creates a user by hand, nobody pastes a connection
|
||||
string. That is worth being precise about rather than folding into a single relation because
|
||||
one relation is prettier.
|
||||
|
||||
## What that costs the design
|
||||
|
||||
**The proposal's "one kind of edge" is wrong**, and the current system already knew: it has
|
||||
`dependencies` for presence and `requires: provision:` for instantiation, with the resolver
|
||||
deriving a presence edge from every instantiation edge. [`analysis.md`](analysis.md) recorded
|
||||
that derivation as a convenience. It is not — it is the correct relationship between two
|
||||
genuinely different relations.
|
||||
|
||||
So: **two kinds of edge, one graph.** Instantiation implies presence. Presence does not imply
|
||||
instantiation.
|
||||
|
||||
## What still has no answer
|
||||
|
||||
**Which postgres?** A mesh with two of them, and a game that wants a database. Presence would be
|
||||
satisfied by either. Instantiation cannot be — the data will live in exactly one, and choosing
|
||||
wrongly is not a preference, it is the game's data in the wrong place, discovered later.
|
||||
|
||||
This is the *who chooses between providers* question from [`proposal.md`](proposal.md), and the
|
||||
worked example shows it is far sharper for instantiation than for presence. For `terminal` the
|
||||
consumer genuinely does not care. For a database it cares permanently.
|
||||
|
||||
**How many instances of postgres should exist?** One per mesh is wrong — a node that must work
|
||||
while disconnected cannot depend on a database elsewhere. One per node is wrong — the mesh's own
|
||||
registry is one thing, not one per node. So the answer is per-module, and nothing in the schema
|
||||
says it. This is [`cases.md`](cases.md) axis *how many instances*, and postgres is the case that
|
||||
proves it cannot be a global rule.
|
||||
|
||||
**What happens to a grant when the consumer is removed?** The game is uninstalled. Its database
|
||||
still exists, holding its data. Dropping it silently is data loss; keeping it forever is a leak.
|
||||
[ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md) says
|
||||
the host removes what it applied and no longer declares — but this is not on the host, it is
|
||||
inside another module's state, and the same reasoning does not obviously carry.
|
||||
|
||||
**Where does node-derived configuration come from?** (3) The control plane composes a
|
||||
declaration, and cannot know this machine's memory. Either the host fills in a blank the
|
||||
declaration leaves — which makes the host decide something, against
|
||||
[ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md) — or the control
|
||||
plane reads the node's inventory first and composes with it. The second is consistent and means
|
||||
a declaration is composed *per node from what the node reported*, which is a stronger claim than
|
||||
anything recorded so far.
|
||||
Reference in New Issue
Block a user