Inverts the dependency that made the Mesh non-swappable. Previously the RTC/LAN
transports were handed the engine's state maps (direct/present/discovered/
transfers) as Arc<Mutex> and mutated them directly — Mesh reaching up into Sync.
Now transports hold only an EventTx and emit MeshEvents (Message/Status/File/
Present/DirectUp/DirectDown/Discovered/Transfer). A single engine event loop is
the sole owner/writer of all derived state and reacts to events: presence,
reachability, discovery, transfer progress, plus the existing decrypt/dedup/
ack/deliver pipeline. Transports no longer touch engine state.
Also replaces the stringly-typed capability checks (t.name() == "rtc"||"lan",
== "sse") with typed Transport::direct() / is_relay().
This is the real Mesh/Sync boundary the architecture doc described — and the
prerequisite for swapping in an alternative Mesh (Iroh), which now slots in as
just another MeshEvent source behind the same Transport contract.
Public API (Engine::new/start/send/send_file/nearby/present/receipts/transfers,
MessageHandler) is unchanged — examples, agent, iOS and macOS need no changes.
Verified: clean build, no new clippy lints; serverless LAN delivery byte-
identical to prior behavior (git-stash diff); crypto-enabled round-trip through
the new loop decrypts + delivers (e2e_demo). Doc §5a/§1/roadmap updated to
reflect the seam now existing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>