A worked example: the whole model applied to an ordinary mesh

The shape research 004 identified — one machine with a routable address,
one publicly named but behind a household connection, one stationary on
that network, one that roams — written out with every field the model has,
in role names and documentation addresses.

It shows the ISP modem doing nothing, because in bridge mode it is a media
converter: it changes the physical medium and leaves the packets alone, so
it creates no IP-level fact and appears nowhere. In router mode it would be
a second gateway and publishing would need a rule on both, which is the one
case the model still cannot express.

It shows two segments sharing one gateway declaration, which means one
gateway machine, and a policy rule between them that is asymmetric because
useful ones almost always are.

And it shows what is deliberately absent. Research 004 recorded overlay
addresses, hub election and names for exactly this topology, and none of
them appear: a scenario must not state what the mesh is responsible for.
Given the declaration, whether a hub is elected, whether the NATed
machine's endpoint is learned, and whether the roaming machine re-forms
after moving are all observed rather than arranged. The absence is the
point.

The run at the end moves one identity through four positions — home,
foreign network, asleep, home again — against a foreign gateway whose
mapping expires in 30 seconds, which is why a number is there rather than
a boolean.
This commit is contained in:
2026-08-23 23:43:09 +02:00
parent b1874f1d0f
commit a873088140
@@ -388,6 +388,115 @@ This is **not** the same as `machines[].inbound`, and conflating them loses a re
A mesh node behind a `policy` deny cannot be reached even by a peer that knows exactly where it
is — and that is a real topology, not a contrived one.
## Worked example — a whole mesh of the ordinary kind
The shape research 004 identified: one machine with a routable address, one publicly named but
behind a household connection, one stationary machine on that network, one that roams. Written
out completely, with every field the model has.
```yaml
scenario: the-ordinary-shape
segments:
internet:
kind: public
cidr: [203.0.113.0/24, 2001:db8::/32]
mtu: 1500
home: # the household network
kind: private
cidr: [192.168.1.0/24, 2001:db8:1::/64]
mtu: 1492 # PPPoE on the uplink; 1500 if the line is not PPPoE
gateway:
to: internet
address: 203.0.113.50 # the address the ISP hands the household
nat: [v4] # v4 translated, v6 routed — the modern default
forwardable: true # the household router is ours to configure
mapping_ttl: 120s
devices: # optional: a segmented network on the same router
kind: private
cidr: [192.168.30.0/24]
gateway:
to: internet
address: 203.0.113.50 # identical → the SAME gateway machine
nat: [v4]
forwardable: true
mapping_ttl: 120s
elsewhere: # wherever the roaming machine happens to be
kind: private
cidr: [198.51.100.0/24]
mtu: 1400
gateway:
to: internet
address: 203.0.113.80
nat: [v4]
forwardable: false # someone else's network, or carrier-grade NAT
mapping_ttl: 30s
policy:
- { from: devices, to: home, allow: false }
- { from: home, to: devices, allow: true }
machines:
anchor: # routable, nothing in front of it
at: { segment: internet, address: [203.0.113.10, 2001:db8::10] }
inbound: allow
home-server: # publicly named, behind the household connection
at: { segment: home, address: [192.168.1.135, 2001:db8:1::135] }
published:
- { port: 443, on: home } # v4 reaches it only through the forward
inbound: allow # and v6 reaches it directly, so this matters
workstation: # on the household network, not published
at: { segment: home, address: [192.168.1.250, 2001:db8:1::250] }
inbound: deny
laptop: # starts at home; moves during the run
at: { segment: home, address: [192.168.1.98, 2001:db8:1::98] }
inbound: deny
place:
all: [host]
anchor: [substrate]
snapshot: raised
```
### What the ISP modem is doing here
**Nothing, if it is in bridge mode** — which is the ordinary arrangement when the household has
its own router. A bridged modem is a media converter: it changes the physical medium and leaves
the packets alone, so it creates no IP-level fact and appears nowhere above.
Were it in router mode it would be a second gateway, `home` would sit behind it rather than
behind `internet` directly, and publishing would need a rule on both — the case the model
cannot yet express.
### What is deliberately absent
Nothing here mentions overlay addresses, which node is the hub, who peers with whom, any name,
or any certificate. Research 004 recorded all of those for this topology, and **a scenario must
not state them** ([ADR 0031](../../02-DECISIONS/0031-the-lab-provides-the-underlay.md)): they
are what the mesh does, and a scenario that supplied them would be certifying its own work.
The absence is the point. Given the declaration above, whether a hub is elected, whether the
NATed machine's endpoint is learned, whether the roaming machine re-forms after moving — all of
it is observed.
### Running it
```
move laptop → { segment: elsewhere, address: [198.51.100.23] } # it leaves the house
move laptop → detached # it sleeps
move laptop → { segment: home, address: [192.168.1.98, 2001:db8:1::98] }
```
One identity, four positions, one run. The `mapping_ttl: 30s` on `elsewhere` means a connection
held without refreshing dies while it is out — which is the point of putting a number there.
## Is this general? — the axes a setup can vary along
The question that matters is not *does this cover our mesh*, but **can it express any mesh**.