Order the records the way the system is learned
Jochen asked whether the order made sense. It did not -- it followed when things happened to be decided, which after consolidation is fictional anyway since record 5 alone folds decisions taken across a week. Concretely wrong before: the domain statement sat at 8, after five engineering rules; the constitution was scattered across 5, 12 and 17; the tiers landed at 15, 16, 21 and 22 with process records in between. Now it walks: what the mesh is (1-3), its tiers from the bottom up (4-8), what runs on them and how it gets there (9-10), how it is built (11-16), how it is checked (17-18), how we work (19-23). Two things made this safe rather than free. It is a permutation, not a compaction, so the renames go through temporary names -- otherwise two files want one slot and one is lost. And the reference rewrite is a single simultaneous pass, because almost every number moved into a slot another number was vacating; replacing one at a time would have cascaded and pointed things at the wrong record while still resolving. Verified: 284 [ADR NNNN](path) links across the repository, all with matching text and target. The ordering principle is now stated in 19 rather than left implicit -- the repository already said "the numbering is the flow" about its folders, and there was no reason for the records to be the exception.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
---
|
||||
status: accepted
|
||||
date: 2026-08-28
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
---
|
||||
|
||||
# 4. A node, and how it joins
|
||||
|
||||
*Consolidated 2026-08-28 from four records.*
|
||||
|
||||
## What a node is
|
||||
|
||||
**A managed machine inside the mesh.** Not a device that is merely known about, not an
|
||||
unprivileged something. If the mesh does not manage it, it is not a node — it is a client, a
|
||||
peer, or a thing on the network, and those want their own names rather than a weakened version of
|
||||
this one.
|
||||
|
||||
**A disconnected node is still a node, in a different situation.** Reachability is **state, not
|
||||
class**. A machine switched off, roaming, or behind a connection that has dropped has not become
|
||||
a lesser kind of thing; it has a last-known state and a pending set of declarations.
|
||||
|
||||
The distinction people reach for is real, but it is **capability** — what this machine can be
|
||||
asked to do — and that belongs in the host's profile rather than in the definition of a node.
|
||||
|
||||
**This is the rule that does the most work elsewhere.** A single control plane is tolerable
|
||||
because its absence is every node in the ordinary disconnected situation at once. An episodic
|
||||
host on a phone is that situation more often. Neither needed a new mechanism.
|
||||
|
||||
## How it joins
|
||||
|
||||
**The host has one behaviour and two sources of declaration.** What differs between the first
|
||||
node and the fiftieth is not what the host does but where the declaration comes from — and, as
|
||||
above, that is a situation rather than a class.
|
||||
|
||||
| | declaration comes from |
|
||||
|---|---|
|
||||
| no mesh reachable | the pinned bundle the host carries |
|
||||
| mesh reachable | the control plane, over the link |
|
||||
|
||||
**The first node is not a different kind of node.** It is a node whose mesh is not up yet. It
|
||||
applies the bundle it carries, the control plane comes up on top of it, and from that moment it
|
||||
takes declarations like everything else. **Its specialness is temporary and self-erasing**, which
|
||||
is what the hand-run bootstrap scripts never were.
|
||||
|
||||
**A joining node does the minimum to be reachable and nothing else** — an identity, an address,
|
||||
and one peer to reach. It does **not** compute the overlay: the whole peer set is derived
|
||||
centrally and pushed down.
|
||||
|
||||
That is also why the migration is smaller than it looked. The hard part of the overlay — every
|
||||
node's key, address, site and reachability — is only needed to compute the *whole* mesh, and a
|
||||
joining node needs one peer.
|
||||
|
||||
## The link is the security boundary
|
||||
|
||||
**Everything reaching a node arrives one way**, and four properties make that a boundary rather
|
||||
than a pipe.
|
||||
|
||||
**It is outbound and node-initiated.** The node dials the control plane; nothing dials a node. Not
|
||||
only defensive — most nodes sit behind a household connection with no forwarded port, so an
|
||||
inbound control channel would work for one node and not the rest, and the difference would be
|
||||
invisible until it mattered. **A node has no listening control surface at all.**
|
||||
|
||||
**A node holds its own identity and nothing else.** No shared secret, no credential to anything it
|
||||
does not own. **Compromise of a node is compromise of that node** — which the current arrangement
|
||||
does not have, because every node permanently holds the same database and object-store
|
||||
credentials, and there is no mechanism that rotates one and informs everything holding it.
|
||||
|
||||
**Authority is mutual.** The node proves it may join, and the control plane proves it is the
|
||||
mesh. One-way is not enough: the host applies whatever the link delivers, so a node that cannot
|
||||
tell the mesh from something impersonating it will apply that something's declarations.
|
||||
|
||||
**What may be pushed is bounded by form, not by trust.** Declarations of known shape, never a
|
||||
command to run. Stated honestly, **this bounds form and not impact**: a compromised control plane
|
||||
can declare harmful state and the host will apply it faithfully, because that is what it is for.
|
||||
What the property buys is that the blast radius is *describable* — exactly what the declaration
|
||||
language can express, which can be reviewed. An arbitrary command channel has no such bound.
|
||||
|
||||
## The enrolment token carries the mesh
|
||||
|
||||
Mutual authority needs the node to verify something before it trusts anything, and that is a
|
||||
circle: verifying the mesh needs the mesh's certificate authority, and obtaining one means
|
||||
trusting whoever hands it over. There is a second circle beside it — a node must reach the mesh
|
||||
before the mesh has configured it, so it can resolve no mesh name.
|
||||
|
||||
**Both are the same shape: a node needs a fact about the mesh before it has any trustworthy way
|
||||
to obtain one.** So that fact arrives by a path other than the mesh.
|
||||
|
||||
**The token carries four things**, and it is the only thing a joining node needs:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **where** | the broker's **address**, not a name — there is no resolution yet, and this is why none is needed |
|
||||
| **what it is connecting to** | the fingerprint of the broker's certificate |
|
||||
| **who it will believe** | the control plane's signing identity |
|
||||
| **the right to join** | a one-time secret, useless once used and useless after it expires |
|
||||
|
||||
**Carried out of band**, by the person adopting the machine. That is what breaks both circles:
|
||||
its authenticity comes from the channel it travelled, not from anything the node can check
|
||||
afterwards. **Trust on first use, with the first use moved out of band** — the difference between
|
||||
a pin and a guess.
|
||||
|
||||
**The endpoint and the authority are two identities.** A node connects to the broker and takes
|
||||
instruction from the control plane behind it. Pinning only the broker would make the control
|
||||
plane's authority *transitive*, and a compromised broker could then forge declarations — which,
|
||||
since the host applies whatever the link delivers, is the whole machine. So the transport is
|
||||
verified once at connect, and **each declaration is verified by its signature, every time**.
|
||||
|
||||
**What this settles:** the mesh's certificate authority is not a bootstrap concern — it certifies
|
||||
internal names once a node is a member. Nothing needs name resolution before the link. And
|
||||
nothing is placed on disk beforehand except the token, which is the first moment *a node holds
|
||||
only its own identity* becomes true rather than aspirational.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Declarations must be signed**, and the host must tell *this is not from the mesh I joined*
|
||||
apart from *this is malformed*. Rotating the signing identity is a fleet-wide operation with an
|
||||
overlapping rollover, and that is the cost of not trusting the broker.
|
||||
- **The token becomes security-critical**, because it carries the pin. Tampering substitutes the
|
||||
mesh — which is strictly better than the alternative, where there is nothing to tamper with and
|
||||
the node trusts the first answer unconditionally.
|
||||
- **A rejoining node is ordinary.** There is no long-lived secret to recover, so a node that lost
|
||||
its identity gets a new token.
|
||||
Reference in New Issue
Block a user