v0.9.0-rc.30
Airgap-safe peer dial + accept-side initial-sync push — unblocks ephemeral consumers (peat-cli) joining established sidecar meshes. Single landed PR (peat-mesh#206) closes peat-mesh#205. Two fixes ship together because they share the same surface (the MeshSyncTransport connect path + SyncProtocolHandler::accept) and were uncovered as adjacent bugs while debugging peat-node PR #114's QUICKSTART CI: a fresh-empty-store CLI dialing an established sidecar would (a) consult iroh's address_lookup chain even with the address in hand, then (b) authenticate cleanly but never observe the sidecar's existing docs. End-to-end validated against peat-node kp/quickstart-docs quickstart-compose + cross-cluster Tests 1–6.
Added
MeshSyncTransport::connect_and_authenticate_with_addr(EndpointAddr)(peat-mesh#206, closes peat-mesh#205). Surgical-caller variant alongside the existingEndpointId-onlyconnect_and_authenticate. When the caller already has a populatedEndpointAddr(peat-cli'sjoin::connect_peer, peat-node sidecars'dial_and_attach, any out-of-band address-exchange path), this passes the address straight toendpoint.connect(addr, ALPN)so iroh seesip_addresses=[Ip(…)]instead of[]— noaddress_lookupchain consultation, no DNS attempt, no chain-dispatch race. Guards against the obvious misuse (emptyaddrsset) with a loudanyhow::bail!at the call site rather than silently re-introducing the bug. Substantial doc-comments on both methods spell out when to prefer which variant.
Fixed
-
SyncProtocolHandler::acceptnow spawns initial-sync push on every inbound connection. Pre-fix, only the dial side calledsync_all_documents_with_peer(viapeat-node::start_syncandpeat-cli::join::connect_peer). The accept side registered the connection and spawned the sync-receiver loop but never proactively pushed its own docs. That worked fine for sidecar-pair bootstrap because the dialer typically had docs too; it broke the moment an empty-store ephemeral consumer (peat-cli) dialed an established sidecar — the dialer's push iterated zero local docs, the acceptor never reciprocated, and the dialer's store stayed empty even though the connection was authenticated and idle. Accept side now symmetric with dial: spawns a one-shotsync_all_documents_with_peer(peer)afterstart_sync_connection, on a tokio task so iroh'sProtocolHandler::acceptcontract is preserved. Latent pre-existing bug surfaced by peat-node PR #114; not specific to the airgapped dial fix above. -
DataSyncBackend::connect_to_peerhonors theaddressesit already accepts. Pre-fix the implementation hadlet _ = addresses;and silently dropped the address hints consumers passed through this trait method — exactly the information that makes the airgap-safe path possible. Non-emptyaddressesnow routes throughconnect_and_authenticate_with_addr(the new method above), preserving the legacy bare-idconnect_and_authenticatepath for the "caller only has the id, rely on registered lookup source" case. Consumer-surface half of the peat-mesh#205 fix.
Added — regression tests
tests/connect_with_addr_airgapped.rs(5 in-tree pins, all run on every CI build):old_method_requires_chain_new_method_bypasses_it— side-by-side proof on a single dialer endpoint with an emptyaddress_lookupchain: the oldEndpointId-only method fails (No addressing information available); the newEndpointAddrmethod succeeds. The chain-dependency is isolated as the only variable.connect_with_addr_rejects_empty_addrs— pins the misuse-rejection guard:EndpointAddr::new(id)(sentinel-empty addrs) hits a loudanyhow::bail!instead of silently falling back to the chain.connect_with_addr_empty_guard_fails_before_any_io— timing pin (elapsed < 50 ms) that catches a future refactor that moves the guard afterendpoint.connect(...).await.sync_round_trip_via_with_addr_adapter_path— full Automerge sync round-trip throughDataSyncBackend::connect_to_peer's new with-addr branch with noMemoryLookuppopulation. Proves the adapter actually establishes a sync-capable connection, not just a connect+auth handshake.accept_side_pushes_existing_docs_to_fresh_dialer— pins the accept-side spawn introduced above: backend B upserts a doc before A connects, A connects fresh-empty viaconnect_and_authenticate_with_addr+start_sync_connection, A'sdocument_storeobserves B's doc within 10 s. Pre-fix (verified by reverting the spawn while keeping the test) the test times out at the 10 s deadline. If the accept-side spawn ever regresses, this test catches it deterministically — A has zero local docs to push, so the accept-side path is the only way the doc can reach A.
Refs
- peat-node PR #114 QUICKSTART CI on
kp/quickstart-docs: all four gates (CI, Quickstart compose, cross-cluster sync, QA Review) confirmed green against revcfa9023and forward. Receipt thread on peat-mesh#205.