Files
fips/testing/chaos/scenarios/mixed-technology.yaml
T
Johnathan Corgan f478f51afe Assert mixed-technology's sound criterion, and correct the one that never was
With the root pinned, n08's test subject is assertable for the first time: its
two candidates sit at equal depth with fiber-grade default netem on one side and
Bluetooth on the other, which is exactly the preference this scenario exists to
test, and it takes the fiber parent in four of four runs. Encoded as written
rather than calibrated from the runs, since "should pick n03" is the spec and
the runs merely confirm it is met.

n06 is deliberately absent, and its documented criterion is corrected rather
than left standing. The header called n03 a fiber parent for n06; the netem
policies in the same file say that link is WiFi and the other candidate is
Bluetooth, so n06 chooses between two impaired links. Its only fiber-grade link
reaches a node at depth 3 that is never a good parent. There was never a reason
to expect it to prefer n03, and the two-of-four split observed is correct
behaviour: in a run where it took the Bluetooth peer, the fiber-labelled
candidate measured a link cost of 3 against the other's 1, so the daemon chose
the cheaper effective depth as it should.

Validated by replaying the assertion against all four archived runs, then
breaking it three ways to confirm it can fail, then one live run for the wiring.
One of the breaks expects n06 to take n03 and reports that it chose n02, which
is the direct demonstration of why n06 is not encoded.

cost-reeval records that its subject now fires in two runs of three, against
zero of five when it was structurally impossible, and why that rate is still not
one an assertion can rest on.
2026-07-23 17:04:23 +00:00

175 lines
6.2 KiB
YAML

# Mixed Technology: 10-node heterogeneous network
#
# Explicit topology with Bluetooth (L2CAP), WiFi, and fiber links.
# Tests that cost-based parent selection produces a tree favoring
# low-cost paths when multiple link technologies coexist.
#
# Topology:
#
# n01 (root)
# / | \
# f f f
# / | \
# n02 n03 n04
# | \ | \ | \
# f BT f f BT f
# | \ | | \ |
# n05 n06 n07 n08 n09
# \ /
# wifi---wifi
# n10
#
# Edges and link types:
# Fiber: n01-n02, n01-n03, n01-n04, n02-n05, n03-n07, n04-n09
# Bluetooth: n02-n06, n04-n08
# WiFi: n03-n06, n08-n10
# Fiber: n03-n08, n06-n10
#
# Test subjects:
# - n08 has fiber (n03) and Bluetooth (n04) parents — should pick n03.
# ASSERTED below. Holds in 4 of 4 runs under the pinned root.
# - n06 was documented as having "fiber (n03) and Bluetooth (n02) parents".
# CORRECTED 2026-07-23: that is wrong, and the netem block below is the
# evidence rather than the ASCII art above. `n03-n06` is a **WiFi** link
# (5-20ms, 1-3% loss) and `n02-n06` is Bluetooth (15-40ms, 2-8% loss), so
# n06 chooses between two impaired links, not between fiber and Bluetooth.
# n06's only fiber-grade link is `n06-n10`, and n10 sits at depth 3 so it
# is never a good parent. There is therefore no reason to expect n06 to
# prefer n03, and the mutation block (fraction 0.2, degraded 50-100ms)
# moves the two costs run to run: across four runs n06 took n03 twice and
# n02 twice. NOT asserted, and the observed split is the correct outcome
# rather than a defect — in the run checked, n03 measured a link cost of 3
# against n02's 1, so effective depth 1+3 beat by 1+1 and the daemon chose
# the cheaper path exactly as it should.
#
# Netem mutation shifts fiber-only links between normal and degraded.
#
# NEITHER TEST SUBJECT IS ASSERTED YET, and the reason has been found and
# fixed at the root rather than papered over here.
#
# The diagram above puts n01 at the top, and both test subjects only make
# sense in a tree rooted there. The mesh does not root by topology: it roots
# at the numerically smallest NodeAddr (src/tree/state.rs:363-390), which is a
# hash of the node's key and bears no relation to the numbering. Until
# 2026-07-23 this scenario rooted at **n09** in every run, which put both
# criteria out of reach. Under that root n08's two candidates were not at
# equal depth, so its choice was settled by depth long before link technology
# could matter, and n08 taking n04 was CORRECT behaviour that these comments
# made look like a defect.
#
# What the archive shows, for anyone re-deriving this later: across the five
# runs under the old root, n06's parent is n10 in three and n03 in two, and
# n08's is n04 in four and n03 in one. Those numbers describe a tree rooted at
# n09 and must not be used to calibrate anything now.
#
# `topology.pin_root` (default true) now assigns identities in NodeAddr order,
# so n01 holds the smallest and the diagram describes the tree that forms.
# Both criteria should therefore become reachable and assertable.
#
# STILL TO DO: run this under the pinned root, confirm n06 and n08 both take
# n03, and encode `tree_parents` for them. Until then the baseline block below
# is a floor and not a substitute — it holds the mesh to forming a tree and
# carrying traffic, and nothing here yet checks that the tree is the one these
# comments describe. Tracked in ISSUE-2026-0085.
scenario:
name: "mixed-technology"
seed: 42
duration_secs: 90
topology:
algorithm: explicit
num_nodes: 10
params:
adjacency:
- [n01, n02]
- [n01, n03]
- [n01, n04]
- [n02, n05]
- [n02, n06]
- [n03, n06]
- [n03, n07]
- [n03, n08]
- [n04, n08]
- [n04, n09]
- [n06, n10]
- [n08, n10]
subnet: "172.20.0.0/24"
ip_start: 10
netem:
enabled: true
default_policy:
# Fiber-like defaults
delay_ms: [1, 5]
jitter_ms: [0, 1]
loss_pct: [0, 0.5]
link_policies:
# Bluetooth (L2CAP) links
- edges: ["n02-n06", "n04-n08"]
policy:
delay_ms: [15, 40]
jitter_ms: [5, 15]
loss_pct: [2, 8]
# WiFi links (moderate latency, low loss)
- edges: ["n03-n06", "n08-n10"]
policy:
delay_ms: [5, 20]
jitter_ms: [2, 5]
loss_pct: [1, 3]
mutation:
interval_secs: {min: 30, max: 60}
fraction: 0.2
policies:
normal:
delay_ms: [1, 10]
loss_pct: [0, 1]
degraded:
delay_ms: [50, 100]
jitter_ms: [10, 30]
loss_pct: [3, 8]
link_flaps:
enabled: false
traffic:
enabled: true
max_concurrent: 3
interval_secs: {min: 10, max: 30}
duration_secs: {min: 5, max: 15}
parallel_streams: 4
# Baseline: the mesh came up, agreed on a root, and took parents. This
# asserts nothing about parent choice, which this scenario cannot
# currently assert; it exists so that a run in which the mesh never formed
# cannot report success, which until now it could,
# because this scenario carried no assertions at all.
#
# Ten nodes, one root, nine parented and six to eight sessions across
# the five provably-completed archived runs. The session floor is set
# below the observed minimum deliberately: it is here to catch a mesh
# that carries no traffic at all, not to pin a throughput.
assertions:
baseline:
min_nodes_reporting: 10
max_roots: 1
min_nodes_parented: 9
min_sessions: 3
# The scenario's own subject, assertable for the first time now that the
# root is pinned to n01 and the topology above describes the tree that
# forms. n08's two candidates sit at equal depth, so the choice is decided
# by link cost alone: n03 over fiber-grade default netem against n04 over
# Bluetooth. That is the fiber-versus-Bluetooth preference this scenario
# exists to test.
#
# The criterion is encoded as written rather than calibrated from runs:
# "should pick n03" is the spec, and four of four runs agree. n06 is
# deliberately absent — see the corrected note in the header for why its
# documented criterion was never founded on this topology.
tree_parents:
n08: n03
logging:
rust_log: "info"
output_dir: "./sim-results"