The scenario model, the lifecycle, and what the lab actually costs #6

Merged
jschoubben merged 9 commits from design/scenario-underlay-detail into main 2026-08-23 22:40:55 +00:00
Showing only changes of commit a873088140 - Show all commits
@@ -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**.