bridge.py:
- _send_over_link: splits TCP data into RNS.Packet.ENCRYPTED_MDU-sized chunks
before sending. RNS.Packet.pack() raises IOError on oversized data; a 32 KB
TCP read would silently kill the tunnel on any LoRa or constrained interface.
Order is safe — link is point-to-point, single sender per tunnel.
- Renamed RNS_BUFFER_SIZE → TCP_READ_SIZE (reads stay large for TCP efficiency;
only outbound RNS direction is chunked).
gossip.py:
- _build_ref_payloads: packs refs into JSON payloads that each fit within
ENCRYPTED_MDU. For >5 refs (>383 B), produces multiple packets. The receiver
handles each independently — each triggers a change check and potential sync.
- _broadcast and _send_initial_refs now use _build_ref_payloads instead of
building a single possibly-oversized payload.
tests:
- test_integration: set mock_pkt_cls.ENCRYPTED_MDU=383 so chunk size is correct
under patch; assert single-packet delivery for small payloads
- test_gossip: TestBuildRefPayloads — small fits in 1 packet, 20 refs split
across multiple packets all ≤ MDU, delta flag propagated to all chunks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gossip.py:
- auto_seed param: when refs arrive for an unknown RID, calls 'rad seed <RID>'
then triggers a sync; adds the RID to self.rids so it gets polled going
forward. Combined with auto_discover, the seed becomes fully self-populating.
- Delta broadcasts: _broadcast now accepts old_refs and sends only the changed
subset with "delta": true in the packet. A 50-ref repo push shrinks from
~2.5 KB to ~120 B on LoRa — 95% bandwidth reduction.
- _on_packet: handles "delta": true by merging incoming refs onto local state
instead of replacing; correctly detects changes after merge.
- _auto_seed_and_sync: calls rad seed, adds rid to watchlist, then delegates
to _trigger_sync. No-ops cleanly if rad seed fails.
- _send_initial_refs still sends full refs (new peer has no prior state).
cli.py:
- cmd_seed: passes auto_seed=True to GossipRelay so the seed self-populates
from the mesh as remote seeds announce their repos.
tests/test_gossip.py:
- Delta packet tests: merge, no-sync-on-known, full vs delta flag
- Auto-seed tests: seeds+syncs unknown repo, no-sync on failure, no dup rid
- Broadcast delta tests: full when no old_refs, only changed when delta,
_send_initial_refs always sends full
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gossip.py:
- _poll_loop: run initial baseline poll at startup, then debounce 2s after
watchdog events so a 20-commit push triggers one broadcast not twenty
- _on_announce: move inline _send_packet calls to _send_initial_refs() on a
daemon thread — _send_packet blocks up to 15s waiting for a path, which
was stalling the RNS announce handler when called inline
- _trigger_sync: pass --seed NID@127.0.0.1:PORT to rad sync --fetch when
rad node connect succeeded, targeting the specific peer that sent the
gossip instead of syncing with all known seeds; log clearly when connect
fails and fall back to untagged fetch
tests/test_gossip.py:
- test_seed_flag_added_when_connect_succeeds
- test_no_seed_flag_when_connect_fails
- test_debounce_clears_event_on_early_wakeup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Gossip relay now reacts to rad push instantly when watchdog is installed.
A threading.Event replaces the interruptible sleep loop: watchdog fires
the event on any filesystem change in the seed's storage directory, waking
the poll loop immediately. Without watchdog the relay falls back to the
configured poll interval with a clear log message.
New command 'radicle-rns setup' checks all prerequisites and prints exact
fix instructions for anything missing: rad/radicle-node binaries, seed
identity, watchdog, and whether the seed is registered in the user's
radicle node.
Other changes:
- gossip: _poll_loop_once() extracted so tests can drive one iteration
- gossip: stop() sets poll_event so the thread exits without waiting out
the full poll interval
- gossip: _start_watcher() creates storage dir if absent (watchdog requires
the watched path to exist)
- pyproject.toml: watchdog>=3.0 added as [watch] optional dep and dev dep
- 5 new tests for watchdog/event/auto-discover behaviour
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>