fix(roundtrip): pin the swarm step to clearnet when Tor is disabled

The workspace swarm roundtrip for svc-db-postgres failed on every host
with "The lookup plugin 'tls' failed: NETWORK_MODE 'multi' needs
svc-net-tor with a node onion on this node" (CI run 40979252521, job
Workspace debian:13). roundtrip.sh (b2469ff948) pinned the swarm step to
multi whenever the validator accepts it and never looked at disable=,
while 03_roundtrip.sh passes a disable list that removes tor. The
hard-fail is the documented contract (network_modes.md: tor or multi on
a node without the provider fails the deploy); the request was wrong.

roundtrip.sh now pins clearnet when disable= holds the Tor key (read
from utils.github.variant.network.TOR_SERVICE), else multi when the role
takes it, else the rotation. 02_zombie.sh, which also disables tor,
passes network=clearnet instead of relying on the rotation drawing it.
TestRoundtripSwarmNetwork runs the real script against stubs; the old
pin logic fails three of its cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5.5 committed 2026-10-30 08:49:50 +01:00
1 parent b3f875c687
commit 33591ab688
4 files changed
+108 -5

No files matched your search

@@ -1,9 +1,14 @@
"""Contract between the swarm drill's completion marker and the roundtrip gate."""
"""The roundtrip gate: its drill-completion contract and the network its swarm step pins."""
from __future__ import annotations
import os
import re
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from utils.cache.files import PROJECT_ROOT, read_text
@@ -12,6 +17,73 @@ DRILL = (
)
GATE = PROJECT_ROOT / "scripts" / "tests" / "deploy" / "roundtrip.sh"
MARKER = "==> swarm drill complete: app="
APP = "svc-db-postgres"
PYTHON_STUB = (
"#!/usr/bin/env bash\n"
'if [ "$1" = -m ]; then exit "${STUB_MULTI_RC}"; fi\n'
'exec "${REAL_PYTHON}" "$@"\n'
)
MAKE_STUB = (
"#!/usr/bin/env bash\n"
'printf "%s\\n" "$*" >>"${STUB_LOG}"\n'
'echo "==> swarm drill complete: app=${STUB_APP}"\n'
)
def swarm_network(disable: str, *, multi_accepted: bool) -> str:
"""Run the roundtrip's swarm step against stubs and return the network it pins.
Args:
disable: the ``disable`` env the roundtrip runs with.
multi_accepted: whether the stubbed validator accepts the role's
``multi`` row.
Returns:
The ``network=`` value the roundtrip hands to ``swarm-zombie``.
"""
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp) / "repo"
script = root / "scripts" / "tests" / "deploy" / "roundtrip.sh"
script.parent.mkdir(parents=True)
script.symlink_to(GATE)
loader = root / "scripts" / "meta" / "env" / "load.sh"
loader.parent.mkdir(parents=True)
loader.write_text("")
bin_dir = Path(tmp) / "bin"
bin_dir.mkdir()
for name, body in (("python", PYTHON_STUB), ("make", MAKE_STUB)):
stub = bin_dir / name
stub.write_text(body)
stub.chmod(0o755)
log = Path(tmp) / "make.log"
log.touch()
env = {k: v for k, v in os.environ.items() if k != "BASH_ENV"}
env.update(
PATH=f"{bin_dir}{os.pathsep}{os.environ['PATH']}",
PYTHON=str(bin_dir / "python"),
REAL_PYTHON=sys.executable,
PYTHONPATH=str(PROJECT_ROOT),
TMPDIR=tmp,
STUB_LOG=str(log),
STUB_APP=APP,
STUB_MULTI_RC="0" if multi_accepted else "1",
apps=APP,
modes="swarm",
keep="true",
disable=disable,
)
done = subprocess.run(
["bash", str(script)],
env=env,
capture_output=True,
text=True,
check=False,
)
if done.returncode != 0:
raise AssertionError(f"roundtrip failed: {done.stderr}")
calls = log.read_text().splitlines() # nocheck: cache-read stub call log
zombie = next(call for call in calls if " swarm-zombie " in call)
return re.search(r"(?:^| )network=(\S*)", zombie).group(1)
class TestRoundtripMarker(unittest.TestCase):
@@ -44,5 +116,29 @@ class TestRoundtripMarker(unittest.TestCase):
self.assertNotIn("provision/deploy/e2e/verify per round", self.gate)
class TestRoundtripSwarmNetwork(unittest.TestCase):
def test_a_role_that_takes_multi_is_pinned_to_multi(self) -> None:
self.assertEqual(
swarm_network("matomo,dashboard", multi_accepted=True), "multi"
)
def test_a_role_refused_multi_keeps_the_rotation(self) -> None:
self.assertEqual(swarm_network("", multi_accepted=False), "")
def test_a_disabled_tor_provider_pins_clearnet(self) -> None:
self.assertEqual(
swarm_network("matomo,tor,zammad", multi_accepted=True), "clearnet"
)
def test_a_disabled_tor_provider_pins_clearnet_over_the_rotation(self) -> None:
self.assertEqual(swarm_network("tor", multi_accepted=False), "clearnet")
def test_a_space_separated_tor_key_pins_clearnet(self) -> None:
self.assertEqual(swarm_network("matomo tor", multi_accepted=True), "clearnet")
def test_a_key_that_only_starts_with_tor_keeps_multi(self) -> None:
self.assertEqual(swarm_network("tor-smtp", multi_accepted=True), "multi")
if __name__ == "__main__":
unittest.main()