From 7cbe1d3d4e741a3fc55b63324b6372d59fb79f67 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Fri, 24 Jul 2026 01:43:20 +0000 Subject: [PATCH 1/3] Retire the smoke-10 chaos scenario; convergence is covered in-process smoke-10 was a no-stressor 10-node tree-convergence sanity check (netem off, no ping). Its subject, spanning-tree convergence and root election, is now covered in-process, faster and deterministically, by the loopback spanning-tree harness (src/node/tests/spanning_tree.rs: ring, star, chain, 100-node and disconnected-component convergence) plus end-to-end datagram delivery (src/node/tests/forwarding.rs). Real-UDP convergence smoke still runs via static-mesh and the other scenarios' baseline assertions, so no Docker coverage is lost. Drop it from both runners in lockstep (the CHAOS_SUITES list and the GitHub chaos matrix) so the parity guard stays green, delete the scenario YAML, and update the chaos README. --- .github/workflows/ci.yml | 3 -- testing/chaos/README.md | 4 +-- testing/chaos/scenarios/smoke-10.yaml | 51 --------------------------- testing/ci-local.sh | 12 +++++-- 4 files changed, 11 insertions(+), 59 deletions(-) delete mode 100644 testing/chaos/scenarios/smoke-10.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a78cd65..2010736 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -396,9 +396,6 @@ jobs: type: gateway topology: gateway # ── Chaos / stochastic scenarios ─────────────────────────────────── - - suite: chaos-smoke-10 - type: chaos - scenario: smoke-10 - suite: churn-mixed-10 type: chaos scenario: churn-mixed diff --git a/testing/chaos/README.md b/testing/chaos/README.md index d280972..1733a9b 100644 --- a/testing/chaos/README.md +++ b/testing/chaos/README.md @@ -18,7 +18,7 @@ Ethernet). Logs are collected and analyzed automatically. ```bash ./testing/chaos/scripts/build.sh -./testing/chaos/scripts/chaos.sh smoke-10 +./testing/chaos/scripts/chaos.sh churn-mixed ``` ## Available Scenarios @@ -29,12 +29,10 @@ Random topologies with increasing stressor intensity. | Scenario | Nodes | Topology | Duration | Netem | Link Flaps | Traffic | Node Churn | Bandwidth | | -------- | ----- | ---------------- | -------- | ----- | ---------- | ------- | ---------- | --------- | -| smoke-10 | 10 | random_geometric | 60s | -- | -- | -- | -- | -- | | chaos-10 | 10 | random_geometric | 120s | yes | yes | yes | -- | -- | | churn-10 | 10 | random_geometric | 600s | yes | yes | yes | yes | -- | | churn-20 | 20 | erdos_renyi | 600s | yes | yes | yes | yes | yes | -- **smoke-10**: Baseline sanity check. No stressors, just verify tree convergence. - **chaos-10**: Network degradation (5-50ms delay, 0-2% loss), link flaps (max 2 down, 10-30s), and iperf traffic (max 3 concurrent). Netem mutates 30% of links every 15-30s between normal and degraded policies. diff --git a/testing/chaos/scenarios/smoke-10.yaml b/testing/chaos/scenarios/smoke-10.yaml deleted file mode 100644 index 452e9b5..0000000 --- a/testing/chaos/scenarios/smoke-10.yaml +++ /dev/null @@ -1,51 +0,0 @@ -scenario: - name: "smoke-10" - seed: 42 - duration_secs: 30 - -topology: - num_nodes: 10 - algorithm: random_geometric - params: - radius: 0.5 - ensure_connected: true - subnet: "172.20.0.0/24" - ip_start: 10 - # The one scenario that deliberately does NOT pin the root. Every other - # scenario assigns identities in NodeAddr order so that n01 is root and its - # diagram describes the tree that actually forms. That is right for them, - # because they test parent selection and were silently testing it under a - # root they did not intend, but applying it everywhere would leave root - # election itself exercised nowhere. This is the generic 10-node convergence - # baseline and its `baseline` assertion is root-agnostic, so it is the - # cheapest place to keep election under test. - # Do not "fix" this to true for consistency with the others. - pin_root: false - -# Phase 2+ features (disabled for MVP) -netem: - enabled: false - -link_flaps: - enabled: false - -traffic: - enabled: false - -# Baseline: the mesh came up, agreed on a root, and took parents. This -# asserts nothing about any particular subsystem; 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 in all six provably-completed -# archived runs. As the smoke test this is the scenario whose silent -# success was least defensible. -assertions: - baseline: - min_nodes_reporting: 10 - max_roots: 1 - min_nodes_parented: 9 - -logging: - rust_log: "info" - output_dir: "./sim-results" diff --git a/testing/ci-local.sh b/testing/ci-local.sh index 962e16c..36d571a 100755 --- a/testing/ci-local.sh +++ b/testing/ci-local.sh @@ -30,7 +30,7 @@ # rekey-outbound-only, gateway, # acl-allowlist, admission-cap, firewall, nat-cone, nat-symmetric, # nat-lan, nostr-publish-consume, stun-faults, -# chaos-smoke-10, chaos-churn-mixed-10, chaos-ethernet-mesh, +# chaos-churn-mixed-10, chaos-ethernet-mesh, # chaos-ethernet-only, chaos-tcp-mesh, chaos-congestion-stress, # chaos-bloom-storm, # sidecar, dns-resolver, deb-install @@ -117,7 +117,6 @@ REKEY_SUITES=(rekey rekey-accept-off rekey-outbound-only) ADMISSION_SUITES=(admission-cap) # Each entry: "display-name scenario [--flag value ...]" CHAOS_SUITES=( - "smoke-10 smoke-10" "churn-mixed-10 churn-mixed --nodes 10 --duration 120" "ethernet-mesh ethernet-mesh" "ethernet-only ethernet-only" @@ -137,6 +136,15 @@ CHAOS_SUITES=( # detection logic is now unit-tested in src/node/tests/unit.rs. # congestion-stress stays: it exercises the ECN/MMP congestion signals, # which do need the real shaped bottleneck queue. +# +# Retired 2026-07-24 for a different reason — redundant, not unreliable: +# - smoke-10: a no-stressor 10-node tree-convergence sanity check (netem +# off, no ping). The convergence logic it exercised is covered in-process, +# faster and deterministically, by the loopback spanning-tree harness +# (src/node/tests/spanning_tree.rs: ring/star/chain/100-node/disconnected) +# plus end-to-end datagram delivery (src/node/tests/forwarding.rs). Real- +# UDP convergence smoke still runs via static-mesh and the other chaos +# scenarios' baseline assertions, so no Docker coverage is lost. GATEWAY_SUITES=(gateway) SIDECAR_SUITES=(sidecar) ACL_SUITES=(acl-allowlist) From 41ce64ba825d255dc6b66d186a56aa3849bcc624 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Fri, 24 Jul 2026 02:29:38 +0000 Subject: [PATCH 2/3] Retire the acl-allowlist Docker suite; the decision is proven in-process The ACL admission decision is exhaustively unit-tested per npub over real loaded allow/deny files (src/node/acl.rs test module: allow-match-wins, allowlist-miss falls through, deny-only, deny-all, allow_all override, deny-after-allowlist-miss), and the inbound and outbound handshake- admission paths are covered in-process over the loopback transport (src/node/tests/acl.rs: inbound msg1 denial, outbound denial, reload). The Docker suite's only unique coverage was admission over a real UDP transport, which every other real-transport suite already exercises, and its operator-facing log assertion. Drop it from both runners in lockstep (its *_SUITES array, run function, default-flow call, --only dispatch arm, and the GitHub matrix leg with its steps) so the parity guard stays green, and record the retirement in the deliberately-not-run block with a pointer to the standalone runner. The testing/acl-allowlist/ suite stays on disk and runs by hand. --- .github/workflows/ci.yml | 17 ----------------- testing/ci-local.sh | 32 +++++++++++--------------------- 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2010736..7420709 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -386,8 +386,6 @@ jobs: - suite: admission-cap type: admission-cap topology: mesh - - suite: acl-allowlist - type: acl-allowlist # ── Firewall baseline (fips0 nftables default-deny) ──────────── - suite: firewall type: firewall @@ -626,21 +624,6 @@ jobs: docker compose -f testing/static/docker-compose.yml \ --profile rekey-outbound-only down --volumes --remove-orphans - # ── ACL allowlist integration test ───────────────────────────────────── - - name: Run ACL allowlist integration test - if: matrix.type == 'acl-allowlist' - run: bash testing/acl-allowlist/test.sh --skip-build --keep-up - - - name: Collect logs on failure (acl-allowlist) - if: matrix.type == 'acl-allowlist' && failure() - run: | - docker compose -f testing/acl-allowlist/docker-compose.yml logs --no-color - - - name: Stop containers (acl-allowlist) - if: matrix.type == 'acl-allowlist' && always() - run: | - docker compose -f testing/acl-allowlist/docker-compose.yml down --volumes --remove-orphans - # ── Firewall baseline integration test ───────────────────────────────── - name: Run firewall baseline integration test if: matrix.type == 'firewall' diff --git a/testing/ci-local.sh b/testing/ci-local.sh index 36d571a..fa2ba0a 100755 --- a/testing/ci-local.sh +++ b/testing/ci-local.sh @@ -28,7 +28,7 @@ # Integration suites (default coverage): # static-mesh, static-chain, rekey, rekey-accept-off, # rekey-outbound-only, gateway, -# acl-allowlist, admission-cap, firewall, nat-cone, nat-symmetric, +# admission-cap, firewall, nat-cone, nat-symmetric, # nat-lan, nostr-publish-consume, stun-faults, # chaos-churn-mixed-10, chaos-ethernet-mesh, # chaos-ethernet-only, chaos-tcp-mesh, chaos-congestion-stress, @@ -54,6 +54,16 @@ # because it asserts nothing. Making it gateable needs a # calibration corpus that does not exist; see its header. # mesh-lab/ Long-running multi-host lab, not a suite. +# acl-allowlist/ Retired from CI 2026-07-24 as redundant, not unrunnable. +# The ACL decision is exhaustively unit-tested per npub over +# real loaded allow/deny files (src/node/acl.rs mod tests: +# allow-wins, allowlist-miss, deny-only, deny-all, +# allow_all-override), and the inbound/outbound handshake- +# admission path is covered in-process over loopback +# (src/node/tests/acl.rs). The Docker suite's only unique +# coverage was real-UDP admission, exercised by every other +# real-transport suite. Still runnable by hand: +# bash testing/acl-allowlist/test.sh # ecn-ab-on, ecn-ab-off, maelstrom, maelstrom-sparse # Chaos scenarios excluded from CHAOS_SUITES. The two # ecn-ab ones are halves of the manual comparison above. @@ -147,7 +157,6 @@ CHAOS_SUITES=( # scenarios' baseline assertions, so no Docker coverage is lost. GATEWAY_SUITES=(gateway) SIDECAR_SUITES=(sidecar) -ACL_SUITES=(acl-allowlist) FIREWALL_SUITES=(firewall) NAT_SUITES=(cone symmetric lan) NOSTR_RELAY_SUITES=(nostr-publish-consume) @@ -189,9 +198,6 @@ list_suites() { echo " Gateway:" for s in "${GATEWAY_SUITES[@]}"; do echo " $s"; done echo "" - echo " ACL allowlist:" - for s in "${ACL_SUITES[@]}"; do echo " $s"; done - echo "" echo " Firewall baseline:" for s in "${FIREWALL_SUITES[@]}"; do echo " $s"; done echo "" @@ -785,17 +791,6 @@ run_rekey_outbound_only() { record "rekey-outbound-only" $rc } -# Run ACL allowlist integration test -run_acl_allowlist() { - export COMPOSE_PROJECT_NAME="$(ci_project acl)" - info "[acl-allowlist] Running integration test" - if bash testing/acl-allowlist/test.sh --skip-build 2>&1; then - record "acl-allowlist" 0 - else - record "acl-allowlist" 1 - fi -} - # Run firewall baseline integration test run_firewall() { export COMPOSE_PROJECT_NAME="$(ci_project firewall)" @@ -938,9 +933,6 @@ run_integration() { # Gateway run_gateway - # ACL allowlist - run_acl_allowlist - # Firewall baseline run_firewall @@ -1055,8 +1047,6 @@ run_suite() { run_admission_cap ;; gateway) run_gateway ;; - acl-allowlist) - run_acl_allowlist ;; firewall) run_firewall ;; nat-cone|nat-symmetric|nat-lan) From 9846e85705cb07b8c33720c00bf1d6b9bf341602 Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Fri, 24 Jul 2026 02:31:43 +0000 Subject: [PATCH 3/3] Retire the admission-cap Docker suite; the inbound gate is proven in-process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inbound max_peers early-gate in handle_msg1 (silent-drop a Msg1 from a net-new identity at saturation, send no Msg2, admit no peer) is unit-tested over a real UDP socket by handle_msg1_silent_drops_at_cap_for_new_peer in src/node/tests/unit.rs: it saturates a node, sends a Msg1 from a fresh identity, and polls the sender socket to assert no Msg2 comes back — the same wire-observable discriminator the Docker suite's tcpdump used — with a sibling test for the existing-peer bypass. That is a deterministic superset of the Docker packet-capture assertion. Drop it from both runners in lockstep (its *_SUITES array, run function, default-flow loop, --only dispatch arm, and the GitHub matrix leg with its steps) so the parity guard stays green, and record the retirement in the deliberately-not-run block with a pointer to the standalone runner. The admission-cap-test.sh script stays on disk and runs by hand. --- .github/workflows/ci.yml | 40 ------------------------------- testing/ci-local.sh | 52 +++++++++------------------------------- 2 files changed, 11 insertions(+), 81 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7420709..b74943b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -382,10 +382,6 @@ jobs: - suite: rekey-outbound-only type: rekey-outbound-only topology: rekey-outbound-only - # ── Inbound max_peers admission-cap test ─────────────────────── - - suite: admission-cap - type: admission-cap - topology: mesh # ── Firewall baseline (fips0 nftables default-deny) ──────────── - suite: firewall type: firewall @@ -754,42 +750,6 @@ jobs: docker compose -f testing/static/docker-compose.yml \ --profile gateway down --volumes --remove-orphans - # ── Inbound max_peers admission-cap integration test ──────────────── - # Lowers node.max_peers on one mesh node and asserts the inbound cap - # holds under sustained retry pressure: denied peers keep retrying but - # are never promoted to an active session. The admission-cap-test.sh - # assertions are tailored per link-layer handshake variant; the leg - # itself is uniform. Static-style harness on the shared mesh profile. - - name: Generate configs (admission-cap) - if: matrix.type == 'admission-cap' - run: bash testing/static/scripts/generate-configs.sh mesh - - - name: Inject admission-cap config (admission-cap) - if: matrix.type == 'admission-cap' - run: bash testing/static/scripts/admission-cap-test.sh inject-config - - - name: Start containers (admission-cap) - if: matrix.type == 'admission-cap' - run: | - docker compose -f testing/static/docker-compose.yml \ - --profile mesh up -d - - - name: Run admission-cap test - if: matrix.type == 'admission-cap' - run: bash testing/static/scripts/admission-cap-test.sh - - - name: Collect logs on failure (admission-cap) - if: matrix.type == 'admission-cap' && failure() - run: | - docker compose -f testing/static/docker-compose.yml \ - --profile mesh logs --no-color | tail -300 - - - name: Stop containers (admission-cap) - if: matrix.type == 'admission-cap' && always() - run: | - docker compose -f testing/static/docker-compose.yml \ - --profile mesh down --volumes --remove-orphans - # ── Real-deb install integration ──────────────────────────────────── # The deb-install harness builds its own .deb from source in a # cargo-deb builder image; the pre-built Linux binary from the diff --git a/testing/ci-local.sh b/testing/ci-local.sh index fa2ba0a..f879ff4 100755 --- a/testing/ci-local.sh +++ b/testing/ci-local.sh @@ -28,7 +28,7 @@ # Integration suites (default coverage): # static-mesh, static-chain, rekey, rekey-accept-off, # rekey-outbound-only, gateway, -# admission-cap, firewall, nat-cone, nat-symmetric, +# firewall, nat-cone, nat-symmetric, # nat-lan, nostr-publish-consume, stun-faults, # chaos-churn-mixed-10, chaos-ethernet-mesh, # chaos-ethernet-only, chaos-tcp-mesh, chaos-congestion-stress, @@ -64,6 +64,16 @@ # coverage was real-UDP admission, exercised by every other # real-transport suite. Still runnable by hand: # bash testing/acl-allowlist/test.sh +# admission-cap Retired from CI 2026-07-24 as redundant, not unrunnable. +# The inbound max_peers early-gate in handle_msg1 is unit- +# tested over a real UDP socket by +# handle_msg1_silent_drops_at_cap_for_new_peer in +# src/node/tests/unit.rs: it sends a Msg1 from a fresh +# identity at saturation and polls the sender socket to +# assert no Msg2 returns — the same wire-observable +# discriminator the Docker tcpdump used — plus a sibling +# test for the existing-peer bypass. Still runnable by hand: +# bash testing/static/scripts/admission-cap-test.sh # ecn-ab-on, ecn-ab-off, maelstrom, maelstrom-sparse # Chaos scenarios excluded from CHAOS_SUITES. The two # ecn-ab ones are halves of the manual comparison above. @@ -124,7 +134,6 @@ ONLY_SUITE="" # All integration suites matching ci.yml STATIC_SUITES=(static-mesh static-chain) REKEY_SUITES=(rekey rekey-accept-off rekey-outbound-only) -ADMISSION_SUITES=(admission-cap) # Each entry: "display-name scenario [--flag value ...]" CHAOS_SUITES=( "churn-mixed-10 churn-mixed --nodes 10 --duration 120" @@ -192,9 +201,6 @@ list_suites() { echo " Rekey:" for s in "${REKEY_SUITES[@]}"; do echo " $s"; done echo "" - echo " Admission cap:" - for s in "${ADMISSION_SUITES[@]}"; do echo " $s"; done - echo "" echo " Gateway:" for s in "${GATEWAY_SUITES[@]}"; do echo " $s"; done echo "" @@ -568,35 +574,6 @@ run_rekey() { record "rekey" $rc } -# Run the admission-cap integration test -# Verifies the inbound max_peers early-gate silent-drops at scale by -# lowering node.max_peers on one mesh node and asserting via tcpdump -# that no Msg2 responses go to the sustained-retrying denied peers. -run_admission_cap() { - local compose="testing/static/docker-compose.yml" - local rc=0 - export COMPOSE_PROJECT_NAME="$(ci_project static)" - - info "[admission-cap] Generating configs" - bash testing/static/scripts/generate-configs.sh mesh || { record "admission-cap" 1; return; } - bash testing/static/scripts/admission-cap-test.sh inject-config || { record "admission-cap" 1; return; } - - info "[admission-cap] Starting containers (mesh profile)" - docker compose -f "$compose" --profile mesh up -d || { record "admission-cap" 1; return; } - - info "[admission-cap] Running admission-cap test" - if bash testing/static/scripts/admission-cap-test.sh; then - rc=0 - else - rc=1 - info "[admission-cap] Collecting failure logs" - docker compose -f "$compose" --profile mesh logs --no-color 2>&1 | tail -100 - fi - - docker compose -f "$compose" --profile mesh down --volumes --remove-orphans 2>/dev/null - record "admission-cap" $rc -} - # Run a chaos scenario run_chaos() { local name="$1" @@ -925,11 +902,6 @@ run_integration() { run_rekey_accept_off run_rekey_outbound_only - # Admission cap (mesh profile, max_peers=1 on one node) - for _suite in "${ADMISSION_SUITES[@]}"; do - run_admission_cap - done - # Gateway run_gateway @@ -1043,8 +1015,6 @@ run_suite() { run_rekey_accept_off ;; rekey-outbound-only) run_rekey_outbound_only ;; - admission-cap) - run_admission_cap ;; gateway) run_gateway ;; firewall)