Skip to content

Commit c720607

Browse files
docs(interop): update README for the automated suite + lxmd topology
Bring the harness README in line with the working suite: lxmd (not rnsd) owns the shared RNS instance + :4242 TCPServer (rnsd/nomadnet are clients); the suite auto-seeds the sim's TCP interface and pins lxmd's propagation node, so no manual interface setup; diag prefix is [RNS]; `pytest -v` is the run command (14 cases). Add a troubleshooting section incl. the wedged-lxmd symptom + `launchctl kickstart` fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1b24d3f commit c720607

1 file changed

Lines changed: 113 additions & 56 deletions

File tree

Tests/interop/README.md

Lines changed: 113 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,146 @@
11
# Columba-iOS interop test harness
22

3-
Hermetic end-to-end interop suite for Columba-iOS, modelled on Columba-Android's `tests/interop/`. Pins iOS-side wire-format correctness against a reference Sideband peer running headless on the same host.
3+
Hermetic end-to-end interop suite for Columba-iOS, modelled on Columba-Android's
4+
`tests/interop/`. Pins iOS-side wire-format correctness against a reference
5+
Sideband peer running headless on the same host. The pytest suite
6+
(`test_attachments.py`, `test_telemetry.py`) drives the iOS Columba simulator
7+
via Maestro / `lxma://` deep links and asserts delivery on the Sideband side
8+
(and the reverse). 14 cases: text / image (opportunistic, direct, propagated) /
9+
file, both directions, plus location telemetry, custom-meta, periodic-share
10+
toggle, and cease.
411

512
## Topology
613

714
```
8-
┌─────────────────────────────────┐ ┌──────────────────────────────┐
9-
│ iOS Simulator (iPhone 17 …) │ │ Sideband-headless peer │
10-
│ Columba.app │ │ sbapp.sideband.core (in- │
11-
│ identity: 7030e48e… │ │ process via peer_sideband) │
12-
│ TCPClient → 127.0.0.1:4242 │ │ identity: 95617c21… │
13-
│ ↑ │ │ ↑ (RNS shared instance) │
14-
└───┼──────────────────────────────┘ └───┼──────────────────────────┘
15+
┌──────────────────────────────────┐ ┌──────────────────────────────┐
16+
│ iOS Simulator (iPhone 17) │ │ Sideband-headless peer │
17+
│ Columba.app │ │ sbapp.sideband.core (in- │
18+
│ TCPClient → 127.0.0.1:4242 │ │ process via peer_sideband) │
19+
│ (auto-seeded by conftest) │ │ ↑ shared instance :37428 │
20+
└───┼──────────────────────────────┘ └───┼──────────────────────────┘
1521
│ │
16-
└─────────────┐ ┌──────────────────┘
17-
▼ ▼
18-
┌───────────────────┐
19-
│ rnsd (Mac) │
20-
│ TCPServer :4242 │
21-
│ share_instance Y │
22-
└───────────────────┘
22+
│ TCP :4242 LocalServer │ :37428
23+
└──────────────┐ ┌─────────────────┘
24+
▼ ▼
25+
┌─────────────────────────────┐
26+
│ lxmd (network.lxmf.lxmd) │ ← owns the shared RNS instance
27+
│ "Columba Hub" prop node │ enable_transport = True
28+
│ TCPServer :4242 │ share_instance = Yes
29+
│ LocalServer :37428 │
30+
└─────────────────────────────┘
31+
▲ ▲
32+
│ (also clients of the shared instance)
33+
rnsd nomadnet
2334
```
2435

25-
All three RNS endpoints share the host transport (rnsd bridges the simulator's TCPClient to Sideband's shared-instance socket). No physical phone required.
36+
**`lxmd` — not rnsd — owns the shared RNS instance + the `:4242` TCPServer.**
37+
rnsd and nomadnet run alongside it as shared-instance *clients*. lxmd is the
38+
transport node (`enable_transport = True`) that bridges the sim's TCPClient to
39+
Sideband's shared-instance connection, so an announce/message from one side
40+
reaches the other. No physical phone required.
2641

2742
## Components
2843

29-
- **`peer_sideband.py`**`SidebandPeer`, the in-process Sideband daemon wrapper. Copied from Columba-Android's `tests/interop/peer_sideband.py` (commit `cf18d31`). Provides `start()`, `identity_hex`, `send_text()`, `wait_for_message()`, and a `wait_for_tapped_message()` that sees every inbound `LXMessage` *before* Sideband filters it — the diagnostic surface for "iOS shows delivered but message disappears" bugs (the proof fires at the RNS layer before any LXMF processing — see `LXMRouter.delivery_packet:1822`).
30-
- **`run_peer.py`** — Standalone runner: `python run_peer.py` to boot the peer, watch the tap, and print every inbound `LXMessage` (source, content, signature_validated, field-key set). Used interactively while debugging; the pytest harness will drive `SidebandPeer` directly.
31-
- **`flows/`** — Maestro YAML flows for driving the iOS Columba simulator UI.
32-
- **`screenshots/`** — Where Maestro test runs write `takeScreenshot:` outputs. Gitignored.
44+
- **`conftest.py`** — pytest fixtures. Session-scoped, self-contained:
45+
- `sideband` — boots the in-process `SidebandPeer` once per session.
46+
- `_ensure_sim_interface`**auto-seeds** a TCP-client interface
47+
(`127.0.0.1:4242`) into Columba's interface store and relaunches, so a
48+
fresh sim install (which comes up with 0 interfaces) bridges to lxmd with
49+
no manual "Settings → Add Interface" step. Override host/port with
50+
`RNSD_TCP_HOST` / `RNSD_TCP_PORT`.
51+
- `_bootstrap_paths` — cold-starts the bidirectional path table (each side
52+
must have heard the other's announce), then pins lxmd's propagation node
53+
on the sim (discovered via `lxmd --status`) so the propagated test runs
54+
deterministically instead of skipping on lxmd's 5-min announce cadence.
55+
- `Simulator` helper — reads the app's `Documents/diag.log` (the
56+
`[RNS] started/inbound/announce/delivery` lines — note the backend-agnostic
57+
`[RNS]` prefix, renamed from `[PY]` in the dual-backend refactor), fires
58+
`lxma://` deep links via Maestro, and resolves the sim's identity / LXMF
59+
delivery hash.
60+
- **`peer_sideband.py`**`SidebandPeer`, the in-process Sideband daemon
61+
wrapper. Provides `start()`, `identity_hex`, `send_text()`,
62+
`wait_for_message()`, and `wait_for_tapped_message()` that sees every inbound
63+
`LXMessage` *before* Sideband filters it — the diagnostic surface for "iOS
64+
shows delivered but message disappears" bugs (fires at the RNS layer before
65+
any LXMF processing). Teardown uses `SidebandCore.cleanup()`.
66+
- **`run_peer.py`** — Standalone runner (`python run_peer.py`) to boot the peer
67+
interactively and print every inbound `LXMessage`. Used while debugging; the
68+
pytest suite drives `SidebandPeer` directly.
69+
- **`flows/`** — Maestro YAML flows that drive the Columba simulator UI.
70+
- **`screenshots/`** — Maestro `takeScreenshot:` outputs (gitignored).
3371

3472
## Prerequisites
3573

36-
- macOS with Xcode 26+ and an iOS 26 simulator booted
37-
- `rnsd` running with a TCPServer on `127.0.0.1:4242` and `share_instance = Yes`
38-
- Sideband checkout at `~/repos/Sideband`
39-
- Python venv with the RNS / LXMF wheels installed (the same venv `rnsd` uses is fine — `~/.reticulum-host/venv`)
40-
- `maestro` installed (`brew install maestro`)
41-
- Columba.app built for the simulator (see "Build" below)
42-
43-
## Build
74+
- macOS with Xcode 26+ and **one** iOS simulator booted (iPhone 17).
75+
- **`lxmd` running** as the shared instance: `enable_transport = True`,
76+
`share_instance = Yes`, and a `[[TCP Server]]` on `127.0.0.1:4242`
77+
(config in `~/.lxmd/config` + `~/.reticulum/config`). rnsd/nomadnet may run
78+
alongside as clients but lxmd is what the sim and Sideband bridge through.
79+
- Sideband checkout at `~/repos/Sideband` (override with `SIDEBAND_SRC`).
80+
- A Python env with `RNS` / `LXMF` / `pytest` (the host venv
81+
`~/.reticulum-host/venv` works).
82+
- `maestro` installed (`brew install maestro`); JDK 21 on `JAVA_HOME` for it.
83+
- Columba.app **installed** on the booted sim (built `Debug` so the `lxma://`
84+
test deep links — which are `#if DEBUG`-gated in production — are present).
85+
The TCP interface to lxmd is seeded automatically by the suite; you do **not**
86+
need to configure it by hand.
87+
88+
## Build + install the app
4489

4590
```bash
4691
cd ~/repos/Columba-iOS
4792
xcrun simctl boot "iPhone 17"
48-
xcodebuild -project Columba.xcodeproj -scheme Columba -configuration Debug \
93+
xcodebuild -scheme Columba -configuration Debug \
4994
-destination "platform=iOS Simulator,name=iPhone 17" \
5095
-derivedDataPath /tmp/columba-sim-build build
51-
52-
xcrun simctl install booted /tmp/columba-sim-build/Build/Products/Debug-iphonesimulator/ColumbaApp.app
96+
xcrun simctl install booted \
97+
/tmp/columba-sim-build/Build/Products/Debug-iphonesimulator/ColumbaApp.app
5398
```
5499

55-
## Run a manual diagnostic
56-
57-
In one shell, boot the Sideband peer:
100+
## Run the suite
58101

59102
```bash
60-
~/.reticulum-host/venv/bin/python tests/interop/run_peer.py --watch
61-
# prints identity_hex; leave running
103+
cd ~/repos/Columba-iOS/Tests/interop
104+
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
105+
python3 -m pytest -v
62106
```
63107

64-
In another shell, launch Columba on the simulator with the TCP-hub env so it
65-
joins the same rnsd:
108+
The session fixtures handle everything else: seed the sim's lxmd interface,
109+
boot the Sideband peer, bootstrap paths, pin the propagation node. Expect
110+
**14 passed**. (A run takes ~5 min — Sideband boot + a Maestro flow / deep-link
111+
send + delivery wait per case.)
112+
113+
## Manual diagnostic
66114

67115
```bash
68-
SIMCTL_CHILD_COLUMBA_TCP_HUB=127.0.0.1:4242 \
69-
xcrun simctl launch booted network.columba.Columba
116+
SIDEBAND_SRC=~/repos/Sideband python3 run_peer.py # prints identity_hex, watches the tap
70117
```
71-
72-
Drive a send via Maestro:
73-
118+
Then drive a flow against the booted sim:
74119
```bash
75120
maestro --device "$(xcrun simctl list devices booted | awk '/Booted/{print $NF}' | tr -d '()')" \
76-
test tests/interop/flows/send_text_to_sideband.yaml
121+
test flows/send_text_to_peer.yaml
77122
```
78-
79-
Watch the peer log — `tap` prints every inbound `LXMessage` with its `signature_validated` flag and the field-key set. If iOS Columba reports "delivered" but no tap entry appears for the corresponding text, the bug is on the iOS send wire. If the tap entry shows `signature_validated=False`, iOS's announce isn't reaching Sideband. If the tap entry has the right content but Sideband's DB doesn't, the issue is downstream of the upstream LXMF callback.
80-
81-
## Future: pytest-driven runs
82-
83-
Like Columba-Android's suite, individual interop tests will be pytest cases that:
84-
1. Spawn `SidebandPeer`,
85-
2. Drive Maestro to send from iOS,
86-
3. Assert on the tap (and/or the Sideband DB),
87-
4. Reverse direction: `peer.send_text(ios_hash, …)` + Maestro screenshot/assertion that the bubble appeared on iOS.
88-
89-
The `screenshots/` directory captures the per-step UI state for triage when an assertion fails.
123+
The tap prints every inbound `LXMessage` with its `signature_validated` flag and
124+
field-key set. If Columba reports "delivered" but no tap entry appears, the bug
125+
is on the iOS send wire; if the tap shows `signature_validated=False`, iOS's
126+
announce isn't reaching Sideband; if the content is right but Sideband's DB
127+
isn't, the issue is downstream of the upstream LXMF callback.
128+
129+
## Troubleshooting
130+
131+
- **Every case errors at setup with "sim never heard the peer announce"** (and
132+
more broadly, devices connect but receive nothing — `rx=0` on their links):
133+
**lxmd is wedged.** It accepts TCP connections but stops routing packets
134+
(symptom: `~/.reticulum/logfile` stops growing, lxmd at 0% CPU). This also
135+
takes down real phones bridging through it. Fix — bounce it:
136+
```bash
137+
launchctl kickstart -k gui/$(id -u)/network.lxmf.lxmd
138+
```
139+
Confirm it recovered: `lxmd --status` shows peers/traffic and the logfile
140+
resumes `Valid announce for …` lines.
141+
- **`test_image_..._propagated` skips:** the suite pins lxmd's prop node via
142+
`lxmd --status`; a skip means lxmd was unreachable at bootstrap. Check lxmd
143+
is up and `--status` works.
144+
- **Suite can't read the sim identity / "Couldn't find `[RNS] started…`":**
145+
the app must be a `Debug` build (so the deep-link handlers exist) and must
146+
have launched at least once (it clears + writes `diag.log` on launch).

0 commit comments

Comments
 (0)