Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
@@ -82,16 +82,48 @@ genuinely different relations.
|
||||
So: **two kinds of edge, one graph.** Instantiation implies presence. Presence does not imply
|
||||
instantiation.
|
||||
|
||||
## Which provider, when two nodes run one
|
||||
|
||||
Asked directly, because two nodes can each run a relational store and a consumer has to be
|
||||
served by one of them. Neither obvious answer is right.
|
||||
|
||||
**Not "the consumer names the node."** That is placement in the consumer's manifest — a small
|
||||
game edited because a database moved, which is the fault
|
||||
[`proposal.md`](proposal.md) separates constraints from placement to avoid.
|
||||
|
||||
**Not "the consumer does not care" either.** For presence it genuinely does not: a terminal is a
|
||||
terminal. For instantiation it cares permanently, because the data lands in exactly one store
|
||||
and the wrong choice is discovered long afterwards.
|
||||
|
||||
**What the consumer does know is the scope of its own need.** Not which node — how many of the
|
||||
thing it wants, relative to itself:
|
||||
|
||||
| Scope | Means | Example |
|
||||
|---|---|---|
|
||||
| **shared** | one instance serves every instance of this consumer | the mesh's own registry: every node reads the same rows |
|
||||
| **per instance** | each instance of this consumer gets its own | a local cache, a per-node queue |
|
||||
|
||||
That is a property of the consumer, expressible without naming anything. And it is the thing
|
||||
that actually decides: a shared need cannot be satisfied by a provider each node runs
|
||||
separately, and a per-instance need should not be satisfied by a shared one.
|
||||
|
||||
**Then the mesh binds, and the binding is recorded on the assignment.** Not recomputed. A
|
||||
resolver that re-derives which store serves a consumer will one day derive a different answer and
|
||||
relocate a database — so the binding is made once, written down, and changed only deliberately.
|
||||
|
||||
The pieces that follow, none of them settled here:
|
||||
|
||||
- **When several providers satisfy the scope**, something chooses — most plausibly locality,
|
||||
preferring a provider on the same node. That is a default, and it must be overridable, because
|
||||
the reason to override it is exactly the reason nobody anticipated it.
|
||||
- **A binding is a thing that can be wrong.** Once recorded it can be inspected, and a consumer
|
||||
bound to a store on a node that no longer exists is a question somebody can be asked rather
|
||||
than a failure at connect time.
|
||||
- **Moving a binding moves data.** Whatever the mechanism, changing it is a migration and not a
|
||||
configuration change, and a design that lets it look like the latter will lose something.
|
||||
|
||||
## 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
|
||||
@@ -113,6 +145,44 @@ a declaration is composed *per node from what the node reported*, which is a str
|
||||
anything recorded so far.
|
||||
|
||||
|
||||
## Providing is not a substrate thing
|
||||
|
||||
The four pinned services are the obvious providers, and they are not a category.
|
||||
|
||||
| Service | What a consumer asks it for |
|
||||
|---|---|
|
||||
| a relational store | a database, a user, credentials |
|
||||
| another relational store, different vendor | a database — **and not the same one** |
|
||||
| a message broker | a virtual host, a user, permissions |
|
||||
| an object store | a bucket and keys |
|
||||
| an image registry | a repository |
|
||||
| an identity provider | a client, a realm, a secret |
|
||||
| an analytics service | a site, and a tracking identity |
|
||||
| a low-code data platform | a base, and a token |
|
||||
| an application platform | a project, which is several of the above at once |
|
||||
| a mail server | a mailbox, an alias, credentials |
|
||||
|
||||
**Any hosted service can be a factory.** Providing is a facet a module may have, not a kind of
|
||||
module it is — which is the same conclusion the effort reached about services and applications,
|
||||
arriving from the other direction.
|
||||
|
||||
That kills the last reason to keep *provider* as a category. A module runs something, or grants
|
||||
something, or both, or neither.
|
||||
|
||||
### Two stores, and why `database` still is not a name
|
||||
|
||||
Two relational stores from different vendors both grant *a database*. They are the sharpest
|
||||
possible test of the substitutability rule from [`proposal.md`](proposal.md), and they fail it
|
||||
completely: different wire protocol, different dialect, different driver, different client
|
||||
library compiled into the consumer.
|
||||
|
||||
A consumer declaring `requires: database` and being handed either would break against one of
|
||||
them. So the name promises what no provider delivers — and now with two real providers in the
|
||||
catalogue rather than a thought experiment.
|
||||
|
||||
*Database* remains a **tag**. It is how a person finds both. It is not how a consumer names what
|
||||
it needs.
|
||||
|
||||
## The same shape, three more times
|
||||
|
||||
The message broker has all nine. So does the object store, and so does the image registry. They
|
||||
@@ -161,3 +231,39 @@ Revoking a virtual host drops whatever had not been delivered — not recoverabl
|
||||
|
||||
The relation is the same and the blast radius is not, which is an argument for the provider
|
||||
deciding what revocation means rather than the mesh applying one rule to all of them.
|
||||
|
||||
|
||||
## The assignment is a third thing
|
||||
|
||||
Recorded because the operator tried the alternative and abandoned it: **modules were once
|
||||
node-agnostic**, and it did not survive contact.
|
||||
|
||||
The worked example says why. Several of a provider's nine properties are not properties of the
|
||||
module at all:
|
||||
|
||||
- **where its state lives** — a volume on a particular machine;
|
||||
- **how it is reached** — the same module on two nodes may answer locally, on the network, or
|
||||
publicly, and that is a per-assignment decision;
|
||||
- **configuration derived from the hardware** — tuning follows the memory and storage of the
|
||||
machine it landed on;
|
||||
- **whether this instance is the one** a given consumer is provisioned from.
|
||||
|
||||
None of those belong in the catalogue, because they differ per node. None belong in the node,
|
||||
because they are about this module. **They belong to the pairing**, and a design with only
|
||||
modules and nodes has nowhere to put them — which is what "node-agnostic" ran out of.
|
||||
|
||||
So there are three entities, not two:
|
||||
|
||||
> **a module** · **a node** · **an assignment**, which is a module on a node and carries its own
|
||||
> configuration
|
||||
|
||||
The current system already has this, arrived at the same way: environment values are stored per
|
||||
module *and per node*, so a module's settings differ between the machines running it.
|
||||
|
||||
**This does not put placement back in the manifest.** A module still says what must be true of a
|
||||
node and never which node ([`proposal.md`](proposal.md)). What changes is that the *result* of
|
||||
placing it is a thing with its own state, rather than a fact recorded on one of the two ends.
|
||||
|
||||
And it makes the composed declaration question from above answerable: a declaration is built
|
||||
from the module, the node's inventory, and the assignment between them. Three inputs, which is
|
||||
why two were never enough.
|
||||
|
||||
Reference in New Issue
Block a user