Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
@@ -1,6 +1,11 @@
|
||||
---
|
||||
status: active
|
||||
status: graduated
|
||||
initiated: 2026-08-25
|
||||
became:
|
||||
- 02-DECISIONS/0044-a-module-declares-presence-instantiation-and-exclusion.md
|
||||
- 02-DECISIONS/0045-a-context-owns-its-store.md
|
||||
- 03-DESIGN/01-to-be/06-the-control-plane.md
|
||||
- 03-DESIGN/01-to-be/07-the-substrate.md
|
||||
touches:
|
||||
- 02-DECISIONS/0002-everything-is-a-module.md
|
||||
- 02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
status: proposed
|
||||
status: superseded
|
||||
superseded-by: 02-DECISIONS/0044-a-module-declares-presence-instantiation-and-exclusion.md
|
||||
date: 2026-08-23
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-08-26
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
supersedes: 0017-modules-outside-the-core-are-grouped-by-domain.md
|
||||
---
|
||||
|
||||
# 44. A module declares presence, instantiation and exclusion
|
||||
|
||||
## Context
|
||||
|
||||
[Research 011](../01-RESEARCH/011-the-module-graph/00-overview.md) set out to find the
|
||||
catalogue's missing structure. It opened with *what does a graph delete?* and the answer for the
|
||||
existing system was **nothing — it is already there**: 126 manifests, 103 edges, no cycles,
|
||||
nothing dangling, and a resolver already used for build, load and install order.
|
||||
|
||||
So the work became a design question rather than a discovery one, worked through twenty cases
|
||||
and one provider in full.
|
||||
|
||||
## Decision
|
||||
|
||||
### Two kinds of edge, one graph
|
||||
|
||||
**Presence** — the thing must exist and be reachable. Nothing is created, nothing flows back.
|
||||
*An editor requires a terminal. A dashboard requires a container runtime.*
|
||||
|
||||
**Instantiation** — a provider is asked to make something *for this consumer*, and hands back
|
||||
what the consumer needs to use it. *A game requires a database from the store, and receives one,
|
||||
with credentials.*
|
||||
|
||||
Instantiation implies presence; presence does not imply instantiation. They differ in whether
|
||||
something is created, whether a payload returns, whether it can be revoked, and whether the
|
||||
provider holds state about it — which is too much to collapse into one relation for tidiness.
|
||||
|
||||
### Names are concrete unless providers are genuinely substitutable
|
||||
|
||||
A requirement names either a **concrete** thing — that module and no other — or an **abstract**
|
||||
name satisfied by whatever provides it.
|
||||
|
||||
**An abstract name is legitimate only where a consumer can be switched between providers without
|
||||
changing.** `terminal` passes. `database` fails: a consumer speaking one store's protocol does
|
||||
not speak another's, so the name would promise what no provider delivers and the resolver would
|
||||
report a requirement satisfied that is not.
|
||||
|
||||
**The adapter is what creates an interface.** A name has several providers *and a contract they
|
||||
all satisfy*, or it is not an interface. Nothing declares itself to be one.
|
||||
|
||||
**Where there is no contract there is a tag.** *Database* remains a useful word for finding
|
||||
things and grouping them in a catalogue. Tags describe; edges bind; keeping them apart is what
|
||||
stops a second relationship appearing that looks like a dependency and is not.
|
||||
|
||||
### Exclusion is the third relation, and it is not derivable
|
||||
|
||||
`excludes` names what cannot coexist with this. Two modules providing one name look
|
||||
interchangeable, and two things wanting one port look independent, right up until installing the
|
||||
second breaks the first.
|
||||
|
||||
### A node provides names too
|
||||
|
||||
A node's profile is a set of provided names — a display server, a container runtime, an
|
||||
architecture. A module requiring one is satisfied by **the node**, exactly as one requiring a
|
||||
store is satisfied by another module.
|
||||
|
||||
**So capability checking is not a separate mechanism.** There is one question — *is this name
|
||||
provided by anything available here?* — 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 dependencies.
|
||||
|
||||
This makes the host's capability detection an **input to resolution** rather than a report for a
|
||||
person. And what a node provides is partly *derived*: installing a container runtime makes the
|
||||
node provide `container-runtime` thereafter.
|
||||
|
||||
### Constraints, never placement
|
||||
|
||||
A module says what must be true of a node and never which node. Which node runs what is an
|
||||
inventory decision, and today's catalogue decides it in manifests — a module pinning its store
|
||||
to a named node, so a second node cannot provide it without editing the consumer.
|
||||
|
||||
### Scope decides which provider, and the binding is written down
|
||||
|
||||
A consumer of an instantiation edge declares the **scope of its own need**: one instance shared
|
||||
across every instance of itself, or one each. That decides without naming a node — a shared need
|
||||
cannot be met by something each node runs separately.
|
||||
|
||||
The mesh then binds, and **the binding is recorded and sticky**. Not recomputed: a resolver that
|
||||
re-derives which store serves a consumer will one day derive a different answer and relocate a
|
||||
database. **Where it is recorded follows the scope** — a shared grant belongs to the module, a
|
||||
per-instance grant to the assignment.
|
||||
|
||||
### A module, a node, and an assignment
|
||||
|
||||
Three entities. The assignment carries what belongs to neither end: where state lives, how it is
|
||||
reached, configuration derived from the hardware, and which provider instance serves this
|
||||
consumer.
|
||||
|
||||
Recorded because the alternative was tried: modules were once node-agnostic, and it did not
|
||||
survive — there was nowhere for these to live.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **[ADR 0017](0017-modules-outside-the-core-are-grouped-by-domain.md) is superseded.** Folders
|
||||
assert relationships; edges record them. What grouping was for — finding things, seeing what
|
||||
belongs together — is a tag and a query over the graph, neither of which anybody keeps true by
|
||||
hand. The domain module goes with it: there is no `networking` thing to install, there are
|
||||
concrete modules named individually.
|
||||
- **Provider stops being a category**, as service and application already had. Any hosted thing
|
||||
can be a factory — an identity provider grants clients, a mail server grants mailboxes. It is
|
||||
a facet, not a kind.
|
||||
- **`excludes` and node capabilities do not exist in any manifest today**, so this adds
|
||||
declarations rather than removing them. What it removes is listed in
|
||||
[ADR 0045](0045-a-context-owns-its-store.md), which is the other half of this design.
|
||||
- **A resolver is still needed**, with version constraints and conflicts. What it delegates to
|
||||
the platform's package manager rather than reimplementing is **not decided here**.
|
||||
- **Two axes remain undeclarable**: how many instances a module should have — not derivable, and
|
||||
opposite for a broker and a store — and what a provider hands back, which differs between
|
||||
credentials and a command.
|
||||
|
||||
## References
|
||||
|
||||
- [Research 011](../01-RESEARCH/011-the-module-graph/00-overview.md) — the measurement, the
|
||||
cases, the worked provider, and what happens to `feature`.
|
||||
- [ADR 0045](0045-a-context-owns-its-store.md) — the ownership half.
|
||||
- [ADR 0043](0043-a-declaration-is-an-ordered-list-of-owned-resources.md) — what the graph
|
||||
produces for a node.
|
||||
- [ADR 0002](0002-everything-is-a-module.md) — survives; this says what a module declares.
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-08-26
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0044-a-module-declares-presence-instantiation-and-exclusion.md
|
||||
---
|
||||
|
||||
# 45. A context owns its store, exclusively
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0044](0044-a-module-declares-presence-instantiation-and-exclusion.md) settles what a module
|
||||
declares. This settles what a grant may be, and it is the half that **removes** things.
|
||||
|
||||
`how-we-build` §4 already says *contexts integrate through the record, never through a shared
|
||||
schema*, and states the cost: several domains share one forty-five-table schema, which is why
|
||||
work belonging to one context keeps having to be implemented in another.
|
||||
|
||||
That was written as a principle. Counted, it is thirteen foreign tables belonging to three
|
||||
separate contexts, living in the mesh's own registry database.
|
||||
|
||||
## Considered options
|
||||
|
||||
1. **A schema per consumer inside a shared database.** Namespaced, revocable by dropping the
|
||||
schema, with a cross-context join possible but deliberate. Rejected: it keeps the letter of
|
||||
§4 and leaves the temptation in place, and a boundary that is merely inconvenient to cross
|
||||
gets crossed.
|
||||
2. **Read-only roles on another context's store.** Rejected for the same reason and one worse:
|
||||
reading another context's tables couples you to its layout exactly as firmly as writing them,
|
||||
and the coupling is invisible until the owner changes a column.
|
||||
3. **Exclusive ownership.** Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
> **A context is granted only what it exclusively owns.**
|
||||
|
||||
No shared writes. No read-only role on another context's store. If you need what another context
|
||||
holds, you ask it or you subscribe to it.
|
||||
|
||||
**The unit is the context, not the process.** Everything inside a context — its service, its
|
||||
surface, its tools — reads its own store freely. A board showing the mesh's own nodes and
|
||||
modules is the mesh showing its own data, not a boundary crossing. What is forbidden is a
|
||||
*different* context reading it.
|
||||
|
||||
### Asking or subscribing is derived, not chosen
|
||||
|
||||
[ADR 0036](0036-a-node-is-a-managed-machine.md) makes disconnection an ordinary situation. So:
|
||||
|
||||
- **Anything that must keep working while disconnected cannot ask** — there is nobody to ask. It
|
||||
keeps a local copy, which means subscribing.
|
||||
- **Anything where a stale answer is worse than none cannot subscribe.** A display may lag; a
|
||||
decision about whether a grant is still valid may not.
|
||||
|
||||
Neither is a query against another store, whatever transport it travels over.
|
||||
|
||||
## What this removes
|
||||
|
||||
The first clear list of what the design deletes rather than adds:
|
||||
|
||||
- **Grant kinds.** There is one: an exclusive resource. No schema grants, no read roles, no
|
||||
rules about who may see what inside a shared thing.
|
||||
- **The question of who owns which table**, and the guessing at revocation time. Removing a
|
||||
consumer drops what it was granted, whole.
|
||||
- **Cross-context migration ordering.** Two contexts migrating one database must be ordered
|
||||
against each other. Exclusive ownership means a context's migrations are ordered only against
|
||||
itself.
|
||||
- **A class of permission modelling** a shared store would otherwise need.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Cross-context reporting is harder, and that is the point.** Anything wanting to see across
|
||||
contexts consumes their events or calls their interfaces. That is §4's argument, and the cost
|
||||
it names is the one already paid.
|
||||
- **A single surface over several contexts still works** — that is what a surface is. It reads
|
||||
interfaces, not stores. This holds while the contexts sit behind **one** interface; splitting
|
||||
a context into its own deployable costs that, and the composition would have nowhere to live
|
||||
that tier 3 permits. **A real constraint on how far the control plane may be split.**
|
||||
- **Three contexts must move out of the registry database**, taking thirteen tables with them.
|
||||
Their dependency on the registry then shrinks to almost nothing — one of them needs a single
|
||||
table.
|
||||
- **The node appliers were already handled.** [ADR 0037](0037-the-host-applies-it-does-not-decide.md)
|
||||
stopped the host querying the mesh database for tier reasons unrelated to this, and it removes
|
||||
most of the remaining direct readers as a side effect.
|
||||
- **What a consumer does about events missed while disconnected is not decided** — replay from a
|
||||
point, ask once and resume, or rebuild. The question every projection has.
|
||||
|
||||
## References
|
||||
|
||||
- [`how-we-build.md`](../00-META/how-we-build.md) §4 — the rule this makes enforceable.
|
||||
- [Research 011](../01-RESEARCH/011-the-module-graph/worked-provider.md) — the count, the worked
|
||||
provider, and the dashboard case.
|
||||
- [ADR 0036](0036-a-node-is-a-managed-machine.md) — why asking or subscribing is derived.
|
||||
Reference in New Issue
Block a user