One implementation, several surfaces, and what that costs

The mesh is operated from a command line and must be operable from a
browser and from a model's tools, without becoming three systems. The
pattern is already in the code and was unnamed: `board` serves HTTP by
calling the same functions the CLI calls, holding nothing.

Takes the decision 0034 said had to be taken deliberately rather than
arrive with a feature: the HTTP surface is not read-only, so a browser
login now carries authority over the mesh.

Names the dependency by protocol — an OAuth2 identity provider — as the
mesh does for AMQP, S3 and OCI. Keycloak is what fills the role; what
the control plane knows is that it validates a token, and replacing the
provider is a migration rather than a redesign.

Says what this must not become, because it is the failure the project
was started over: a kernel every module imports, 155 files of code from
every context. Shared surfaces are not a shared library. Three adapters
calling the same functions is not the same as logic leaving the context
that owns it.

And records the loop it creates. The networked surfaces depend on a
module the control plane assigns, so when identity is down nobody can
authenticate — including whoever is trying to fix it. The way out is the
command line, which authenticates through nothing and is available to
the account that owns the machine. Hence the rule: no capability exists
only behind an authenticated surface, because that is a capability which
disappears exactly when identity does.
This commit is contained in:
2026-08-31 21:17:17 +02:00
parent fccac61e58
commit 0ef6d3f574
2 changed files with 116 additions and 0 deletions
@@ -0,0 +1,115 @@
---
topic: what runs on it
status: accepted
date: 2026-08-31
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0034-the-local-account-owns-the-mesh.md
---
# 35. One implementation, several surfaces, and what that costs
## Context
The mesh is operated from a command line today. It needs to be operable from a browser and from a
model's tools as well, and the three must not be three different systems.
**The pattern is already in the code and unnamed.** `board` serves HTTP by calling the same
functions the CLI calls; it holds nothing and decides nothing. What follows makes that the rule
rather than a property of one command.
**The board is a presentation layer over the control plane.** Not an application beside it holding
a database credential — the thing that shows what the control plane knows, and asks it to do what
a person asked for.
## Decision
**The logic lives once, in the context that owns it. A surface is an adapter with no decisions in
it.**
| surface | for |
|---|---|
| **command line** | a person on a machine, and the recovery path below |
| **HTTP** | the board, and anything else that speaks to the mesh over a network |
| **model tools** | an agent asking the mesh to do something |
**Every surface refuses identically, because the refusal is not in the surface.** An assignment
that cannot be satisfied is refused by the same resolution whichever way it arrived. The moment a
surface can accept something another would reject, the mesh has two answers to one question and
people learn which to trust.
**Reading and doing are both exposed.** The HTTP surface is not read-only: managing the mesh from
a browser is the point. This takes the decision
[ADR 0034](0034-the-local-account-owns-the-mesh.md) said had to be taken deliberately —
**a browser login now carries authority over the mesh** — and takes it knowingly rather than
letting it arrive with a feature.
**The networked surfaces authenticate through an OAuth2 identity provider.** Named by protocol
rather than by product, like every other dependency the mesh takes — AMQP for the bus, S3 for an
object store, OCI for the registry
([ADR 0006](0006-the-substrate-and-the-control-plane.md)). What fills the role today is a module
running Keycloak; what the control plane knows is that it validates a token against a provider
speaking OAuth2, and replacing that provider is a migration rather than a redesign.
**The command line does not authenticate at all**: it is already behind the machine's own login,
which is what owns the mesh (ADR 0034).
## What this is not: a kernel every module imports
**The shared library is the failure this project was started over**, and the difference has to be
stated or it will be rebuilt. The old one is 155 files and 34,636 lines *containing code from
every context* — work-domain logic sitting in the kernel every module imports, each piece landing
there to avoid a cycle between two modules that both needed it.
**Shared surfaces are not a shared library.** What is shared here is that three adapters call the
same functions. Those functions stay in the context that owns them — provisioning's logic in
provisioning, identity's in identity — and no module imports another's. A surface may call many
contexts; a context still may not reach into another's store
([ADR 0008](0008-a-context-owns-its-store.md)).
The test, when something is about to be put "somewhere shared": *does this belong to a context, or
does it only belong to the surface?* If it belongs to a context it goes there, even if two
surfaces want it.
## The loop this creates, and the way out
**The control plane's networked surfaces will depend on a module the control plane assigns.**
An identity provider is an ordinary module ([ADR 0031](0031-the-control-plane-authenticates-nobody.md)). When
it is down, or being migrated, or misconfigured, the HTTP and tool surfaces cannot authenticate
anybody — including the person trying to fix it.
**The command line is the way out, and it is why local ownership matters more rather than less.**
It authenticates through nothing, needs no network, and is available on the machine to the account
that owns the mesh. **A mesh must always be operable by somebody standing at it.**
So the rule: **no capability exists only behind an authenticated surface.** Anything the board can
do, the command line can do. That is not a courtesy to CLI users; it is the recovery path, and a
capability that exists only over HTTP is one that disappears exactly when identity does.
## Consequences
**Identity is still not substrate**, and the test still answers no: the control plane runs, applies
declarations and reaches nodes with no identity provider in existence
([ADR 0033](0033-the-substrate-is-a-store-and-a-broker.md)). What is unavailable without it is two
surfaces, not the mesh.
**Whoever the identity provider admits has authority over the mesh.** That is now a real perimeter
with real consequences, where before it guarded a page that only read. Who may log in, and to
which realm, becomes a decision about the mesh rather than about an application.
**A surface must not grow an opinion.** The likely erosion is a validation added to the board
because it was quicker there — and then the CLI accepts something the board rejects, or worse the
reverse. Adapters hold no decisions.
**Three surfaces over one implementation is a cost paid three times if it is not one
implementation.** The reason to write this down now is that the second surface is the cheapest
moment to get it right, and the third is where the drift usually starts.
## References
- [ADR 0034](0034-the-local-account-owns-the-mesh.md) — the local account owns the mesh, and the
line this record deliberately crosses
- [ADR 0001](0001-mesh-brokers-nodes-host-agents-think.md) — the shared library this must not
become
- [ADR 0008](0008-a-context-owns-its-store.md) — a context owns its store, which a surface does
not change
+1
View File
@@ -105,6 +105,7 @@ python3 00-META/checks/index.py fail if stale
- **0024** — [Model access is a provision, and a licence is a thing with a name](0024-model-access-is-a-provision.md) - **0024** — [Model access is a provision, and a licence is a thing with a name](0024-model-access-is-a-provision.md)
- **0026** — [The mesh has a session of its own, and it is the node session's mechanism](0026-the-mesh-has-a-session-of-its-own.md) - **0026** — [The mesh has a session of its own, and it is the node session's mechanism](0026-the-mesh-has-a-session-of-its-own.md)
- **0027** — [A provision names what the consumer is coupled to, not the role it plays](0027-a-provision-names-what-the-consumer-is-coupled-to.md) - **0027** — [A provision names what the consumer is coupled to, not the role it plays](0027-a-provision-names-what-the-consumer-is-coupled-to.md)
- **0035** — [One implementation, several surfaces, and what that costs](0035-one-implementation-several-surfaces.md)
### How it is built ### How it is built