Issue 067 — fold in the embedded-vs-provisioned axis

Naming which provider is only half of how a module gets a database. The other
half: a module may carry its own version/fork-pinned instance, module-network
only, no published port, not a provision — and the model has no word for it.
Add it as a second axis with its own open questions.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-20 21:24:42 +02:00
parent 1ce9ffe79f
commit c4ff0478b7
@@ -59,6 +59,36 @@ provider, and a consumer has no field in which to choose.
Modelled as mesh-scoped, that topology is expressible only by accident. To carry Modelled as mesh-scoped, that topology is expressible only by accident. To carry
it deliberately the provision must be able to name its provider. it deliberately the provision must be able to name its provider.
## A second axis: provisioned against a provider, or embedded and private
Naming *which* provider is only half of "how a module gets a database". There is a
second, distinct case the model also cannot express: a module that does **not**
consume a shared provider at all, but carries its **own** instance inside its own
composition — on its own module network, publishing no host port, visible to
nothing else in the mesh. This is legitimate and sometimes necessary: some
containers pin a database *server* version or need a fork or extension set (a
customised postgres, a vector extension) that the node's shared provider does not
offer, so they must run their own alongside the main container.
The distinction that matters:
- **Provisioned** — the module requires a provision and is bound to a *named
node-scoped provider* (the first axis above). This should be the default; on the
mesh being migrated onto, most per-module databases are vanilla servers on old
version pins that could simply be consolidated onto the node's shared provider.
- **Embedded and private** — the module ships its own instance because a fork or
version genuinely forces it. Its credential is still a mesh-generated secret, not
a module-authored password; but the *instance* is module-internal — same module
network only, no published port, **not registered as a provision**, so nothing
else can bind to it and it cannot collide on a well-known port.
The model has no word for the second case. A module that carries a private instance
looks, to the mesh, either like nothing (an undeclared container) or like a provider
it must not be treated as. "Embed only when a fork or version forces it; otherwise
provision against the node's provider" is the rule the design should be able to
state and check — and the invisibility of an embedded instance (own network, no host
port, not a provision) should be an enforceable property, not a convention.
## Open questions ## Open questions
- Where does the provider name live — on the provision definition (`scope: "node"` - Where does the provider name live — on the provision definition (`scope: "node"`
@@ -76,3 +106,8 @@ provider, and a consumer has no field in which to choose.
- Does this interact with secret rotation (the issue-scope of `rotate`) — must - Does this interact with secret rotation (the issue-scope of `rotate`) — must
rotation address a specific provider's credential holders rather than "the rotation address a specific provider's credential holders rather than "the
provision's"? provision's"?
- When a module carries an **embedded, private** instance rather than consuming a
provider, how is that declared so the mesh knows it is module-internal — not a
provision, not published, not bindable by anything else — and can enforce it?
- What decides embed-vs-provision — is it the module's declaration alone, or may an
operator override at assignment (consolidate this one onto the node's provider)?