Rips out the custom git-bundle/sync/adaptive/qr parallel sync stack and
replaces it with a proper seed-to-seed architecture: two real radicle-node
instances (each with seedingPolicy=allow) bridge over Reticulum/LoRa.
Users connect their own radicle-node to the local seed once; all mesh
sync happens in the background using Radicle's own protocol, so atomicity,
Noise XK encryption, and pack verification are all handled natively.
New modules:
- seed.py: SeedNode — manages a dedicated radicle-node subprocess under its
own RAD_HOME (~/.radicle-rns/seed/), writes a seed config only on first
run, uses DEVNULL I/O to prevent pipe-buffer deadlocks
- gossip.py: GossipRelay — polls ~/.radicle/storage/<rid>/ for ref changes,
broadcasts ~200-500 byte RNS packets to known peers, on receipt calls
`rad sync --fetch --rid X`; thread-safe via separate peers/refs locks
New CLI commands:
- `radicle-rns seed`: starts seed radicle-node + bridge as one command;
auto-discovers remote seeds over the mesh and connects them
- `radicle-rns gossip`: runs ref-watching notification relay
bridge.py: added rad_home parameter so `rad node connect` is called with
the seed's RAD_HOME when auto-connecting remote seeds.
Bug fixes applied during review:
- seed.py: PIPE→DEVNULL (64 KB pipe buffer deadlock)
- seed.py: missing wait() after kill() (zombie process)
- seed.py: write_config() now idempotent (preserves user customisations)
- seed.py: is_initialized() wraps TimeoutExpired in except Exception
- seed.py: removed hardcoded "network":"main" (breaks testnet)
- seed.py: moved `import socket` to top-level
- cli.py: bridge.start() inside try/finally (orphaned seed on start error)
- cli.py: status line gated on known_bridges change (not every 5 s)
- cli.py: removed dead get_remote_bridges() call in bridge status loop
- gossip.py: added _refs_lock protecting _known_refs across threads
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>