From 6197361b22e883553a391a4e8752c923369eb337 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:56:43 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20cycle=206=20=E2=80=94=20asymmetric=20bl?= =?UTF-8?q?ock,=20partition-register,=20bidir=20suspicion,=203-way=20parti?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4 new distribution sim tests: - asymmetric_one_way_block_does_not_kill_node: gossip recovery through intermediates - names_registered_during_partition_propagate_after_heal: re_disseminate_all on Alive - bidirectional_suspicion_both_nodes_recover: mutual suspicion + refutation cycle - three_way_partition_heals_and_converges: 9-node 3-group split recovers 35 total distribution sim tests (15 registry + 10 lifecycle + 10 property), all green. Authored by Claude, lovingly guided by Zachery Aaron Shores-Chmielewski --- .../tests/distribution_properties.rs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/crates/simulation/tests/distribution_properties.rs b/crates/simulation/tests/distribution_properties.rs index 760018a..dfaba63 100644 --- a/crates/simulation/tests/distribution_properties.rs +++ b/crates/simulation/tests/distribution_properties.rs @@ -484,3 +484,73 @@ fn large_cluster_registry_converges() { result.actual ); } + +// ──────────────────────────────────────────────────────────────────────────── +// 10. Three-way partition: cluster splits into 3 groups, heals, converges +// ──────────────────────────────────────────────────────────────────────────── + +#[test] +fn three_way_partition_heals_and_converges() { + // Given: 9 nodes split into 3 groups {0,1,2}, {3,4,5}, {6,7,8}. + // Each group registers a name during the partition. After healing, + // all 9 nodes should converge on all 3 names. + let config = DistributionSimConfig { + name: "three-way-partition".into(), + num_nodes: 9, + num_rounds: 140, + ticks_per_round: 3, + actors_per_node: 0, + network_faults: vec![ + // Partition A vs B + NetworkFault::Partition { + round: 10, + partition: Partition { + side_a: vec![0, 1, 2], + side_b: vec![3, 4, 5, 6, 7, 8], + asymmetric: false, + }, + }, + // Partition B vs C (stacks with the above: now 3 groups isolated) + NetworkFault::Partition { + round: 10, + partition: Partition { + side_a: vec![3, 4, 5], + side_b: vec![6, 7, 8], + asymmetric: false, + }, + }, + NetworkFault::Heal { round: 60 }, + ], + action_schedule: vec![ + (20, SimAction::RegisterName { node_idx: 0, name: "group-a-svc".into() }), + (20, SimAction::RegisterName { node_idx: 3, name: "group-b-svc".into() }), + (20, SimAction::RegisterName { node_idx: 6, name: "group-c-svc".into() }), + ], + swim: distribution::swim::probe::SwimConfig { + probe_interval: 1, + probe_timeout: 3, + indirect_probes: 1, + // Must exceed partition duration (50 rounds × 3 ticks = 150 ticks) + suspicion_timeout: 200, + dead_reprobe_interval: 10, + }, + ..DistributionSimConfig::default() + }; + + let (_trace, nodes) = run_simulation_with_nodes(config); + + let alive_count = nodes.iter().filter(|n| n.is_some()).count(); + assert_eq!(alive_count, 9, "all 9 nodes should survive the three-way partition"); + + // After healing + gossip, all 3 names should be resolvable from every node + for (i, node) in nodes.iter().enumerate() { + if let Some(node) = node { + for name in &["group-a-svc", "group-b-svc", "group-c-svc"] { + assert!( + node.resolve_name(name).is_some(), + "node {i} should resolve '{name}' after three-way partition heals" + ); + } + } + } +}