Two decisions from building the lab. ADR 0033 is accepted; ADR 0034 is proposed — §6 requires review by someone who is not the proposer, and that is you.
ADR 0033 — a router is scenery, not a node
ADR 0016 makes a lab node a virtual machine, and its reasoning is fidelity: a node boots a stock image and runs the real install, so it has to be a real machine or the thing under test is not the thing that ships.
That reasoning does not reach a router. Nothing under test runs on one, it holds no identity, the mesh never installs anything on it, and no assertion is ever made about its internals. It exists so packets behave the way they behave in the world — which is the definition of scenery. So it is a container: what it must reproduce is kernel behaviour, and a container has the same kernel.
Verified before deciding. In a plain unprivileged container: ip_forward and IPv6 forwarding settable, nftables masquerade accepted, and the conntrack timeouts mapping_ttl depends on both writable. No privileged mode, no nesting, no capability grants.
Rejected letting the hypervisor provide NAT, on a stronger ground than speed: it makes the lab provide what the declaration is supposed to own, and it cannot express a mapping that expires, a gateway that refuses to forward, or policy between siblings. The model would shrink to fit the tool.
The distinction is load-bearing and has to stay legible: node means something under test, scenery means something that makes the test real. If the mesh ever installs anything on a router, it has become a node and the record no longer covers it.
ADR 0034 (proposed) — a test defends a decision
how-we-build.md §5 already says if a document states a rule about the mesh, it says how the rule is verified. That has never been applied to decisions, and a decision record states the same kind of claim.
The gap was found by review of mesh-lab #1: 1,072 of 2,128 lines untested, all of it the half touching the hypervisor, and no stated rule was broken — because there is no testing posture in how-we-build.md at all. No expectation, no gate, no definition of done.
Everything had been verified by hand — pings across NAT, TTL counts, ruleset comparisons — and none of it survived the terminal it ran in. Which is 04-ISSUES/005 in miniature: coverage assumed rather than checked, for two and a half months.
Every decision record states something that must be true. A test asserts it.
Structure and logic tested first — the behaviour is knowable before the code.
Behaviour against a real system tested alongside — it is discovered, not known.
Mocking the boundary is forbidden.
The gate is blocking, and green is the definition of done.
Rejected a coverage percentage — it measures how much code a test touched, not whether anything important is defended, and would have been satisfied by testing the parser harder while the hypervisor integration stayed unasserted.
Rejected TDD as a hard rule — not because it is wrong in general. Half the implementation was discovery: that the hypervisor CLI reads a definition from stdin and hangs, that it assigns a MAC without recording it, that a stock image's networking flushes a static address. A test written first against undiscovered behaviour asserts a guess.
Records 0001–0033 predate it. Not retroactively invalid, but each should acquire a test or an explicit note that it cannot have one — and until then the rule is aspirational for them, which is the state §5 warns about, recorded rather than hidden.
Already applied, and it earned itself
mesh-lab now has ten integration tests, each named for the decision it defends, against a real hypervisor. On its first run it found that a snapshot could miss a file written seconds earlier — not stale, absent, because the write was still in the guest's page cache.
That was the question the lifecycle design listed as open: does a scenario snapshot need the machines stopped? It does not, but it does need them flushed. This PR records the answer, and states the limit honestly — the fix buys write-durability, not application-consistency.
Two decisions from building the lab. ADR 0033 is **accepted**; ADR 0034 is **proposed** — §6 requires review by someone who is not the proposer, and that is you.
---
## ADR 0033 — a router is scenery, not a node
ADR 0016 makes a lab node a virtual machine, and its reasoning is **fidelity**: a node boots a stock image and runs the real install, so it has to be a real machine or the thing under test is not the thing that ships.
That reasoning does not reach a router. Nothing under test runs on one, it holds no identity, the mesh never installs anything on it, and no assertion is ever made about its internals. It exists so packets behave the way they behave in the world — which is the definition of scenery. So it is a container: what it must reproduce is kernel behaviour, and a container has the same kernel.
**Verified before deciding.** In a plain unprivileged container: `ip_forward` and IPv6 forwarding settable, nftables masquerade accepted, and the conntrack timeouts `mapping_ttl` depends on both writable. No privileged mode, no nesting, no capability grants.
Rejected letting the hypervisor provide NAT, on a stronger ground than speed: it makes the *lab* provide what the declaration is supposed to own, and it cannot express a mapping that expires, a gateway that refuses to forward, or policy between siblings. The model would shrink to fit the tool.
The distinction is load-bearing and has to stay legible: **node** means something under test, **scenery** means something that makes the test real. If the mesh ever installs anything on a router, it has become a node and the record no longer covers it.
---
## ADR 0034 (proposed) — a test defends a decision
`how-we-build.md` §5 already says *if a document states a rule about the mesh, it says how the rule is verified*. That has never been applied to **decisions**, and a decision record states the same kind of claim.
**The gap was found by review of mesh-lab #1:** 1,072 of 2,128 lines untested, all of it the half touching the hypervisor, and **no stated rule was broken** — because there is no testing posture in `how-we-build.md` at all. No expectation, no gate, no definition of done.
Everything had been verified by hand — pings across NAT, TTL counts, ruleset comparisons — and none of it survived the terminal it ran in. Which is `04-ISSUES/005` in miniature: coverage assumed rather than checked, for two and a half months.
> Every decision record states something that must be true. A test asserts it.
- **Structure and logic** tested **first** — the behaviour is knowable before the code.
- **Behaviour against a real system** tested **alongside** — it is discovered, not known.
- **Mocking the boundary is forbidden.**
- **The gate is blocking**, and green is the definition of done.
**Rejected a coverage percentage** — it measures how much code a test touched, not whether anything important is defended, and would have been satisfied by testing the parser harder while the hypervisor integration stayed unasserted.
**Rejected TDD as a hard rule** — not because it is wrong in general. Half the implementation was discovery: that the hypervisor CLI reads a definition from stdin and hangs, that it assigns a MAC without recording it, that a stock image's networking flushes a static address. A test written first against undiscovered behaviour asserts a guess.
Records 0001–0033 predate it. Not retroactively invalid, but each should acquire a test or an explicit note that it cannot have one — and until then the rule is aspirational for them, which is the state §5 warns about, recorded rather than hidden.
---
## Already applied, and it earned itself
mesh-lab now has ten integration tests, each named for the decision it defends, against a real hypervisor. On its **first run** it found that a snapshot could miss a file written seconds earlier — not stale, **absent**, because the write was still in the guest's page cache.
That was the question the lifecycle design listed as open: *does a scenario snapshot need the machines stopped?* It does not, but it does need them flushed. This PR records the answer, and states the limit honestly — the fix buys write-durability, not application-consistency.
Implemented in `novox/mesh-lab` #1.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two decisions from building the lab. ADR 0033 is accepted; ADR 0034 is proposed — §6 requires review by someone who is not the proposer, and that is you.
ADR 0033 — a router is scenery, not a node
ADR 0016 makes a lab node a virtual machine, and its reasoning is fidelity: a node boots a stock image and runs the real install, so it has to be a real machine or the thing under test is not the thing that ships.
That reasoning does not reach a router. Nothing under test runs on one, it holds no identity, the mesh never installs anything on it, and no assertion is ever made about its internals. It exists so packets behave the way they behave in the world — which is the definition of scenery. So it is a container: what it must reproduce is kernel behaviour, and a container has the same kernel.
Verified before deciding. In a plain unprivileged container:
ip_forwardand IPv6 forwarding settable, nftables masquerade accepted, and the conntrack timeoutsmapping_ttldepends on both writable. No privileged mode, no nesting, no capability grants.Rejected letting the hypervisor provide NAT, on a stronger ground than speed: it makes the lab provide what the declaration is supposed to own, and it cannot express a mapping that expires, a gateway that refuses to forward, or policy between siblings. The model would shrink to fit the tool.
The distinction is load-bearing and has to stay legible: node means something under test, scenery means something that makes the test real. If the mesh ever installs anything on a router, it has become a node and the record no longer covers it.
ADR 0034 (proposed) — a test defends a decision
how-we-build.md§5 already says if a document states a rule about the mesh, it says how the rule is verified. That has never been applied to decisions, and a decision record states the same kind of claim.The gap was found by review of mesh-lab #1: 1,072 of 2,128 lines untested, all of it the half touching the hypervisor, and no stated rule was broken — because there is no testing posture in
how-we-build.mdat all. No expectation, no gate, no definition of done.Everything had been verified by hand — pings across NAT, TTL counts, ruleset comparisons — and none of it survived the terminal it ran in. Which is
04-ISSUES/005in miniature: coverage assumed rather than checked, for two and a half months.Rejected a coverage percentage — it measures how much code a test touched, not whether anything important is defended, and would have been satisfied by testing the parser harder while the hypervisor integration stayed unasserted.
Rejected TDD as a hard rule — not because it is wrong in general. Half the implementation was discovery: that the hypervisor CLI reads a definition from stdin and hangs, that it assigns a MAC without recording it, that a stock image's networking flushes a static address. A test written first against undiscovered behaviour asserts a guess.
Records 0001–0033 predate it. Not retroactively invalid, but each should acquire a test or an explicit note that it cannot have one — and until then the rule is aspirational for them, which is the state §5 warns about, recorded rather than hidden.
Already applied, and it earned itself
mesh-lab now has ten integration tests, each named for the decision it defends, against a real hypervisor. On its first run it found that a snapshot could miss a file written seconds earlier — not stale, absent, because the write was still in the guest's page cache.
That was the question the lifecycle design listed as open: does a scenario snapshot need the machines stopped? It does not, but it does need them flushed. This PR records the answer, and states the limit honestly — the fix buys write-durability, not application-consistency.
Implemented in
novox/mesh-lab#1.