Mesh + session transfer: P1 done, P2 core, P4 foundation

Multi-device mesh + session-transfer program (docs/MESH_TRANSFER.md).

P1 (multi-select connection methods, Mac): SyncTransportSet with legacy
migration; CompositeSyncListener partial-failure tolerant + per-method health;
AppStore listens on all enabled methods; HostInfo.hostID = DeviceIdentity.hostID
(key hash, not display name); Settings 3 method toggles + LAN-only recommendation
banner.

P2 core (relay hardening, security-critical, tested): nucleic-edge relayEnroll.ts
X25519 proof-of-possession enroll + server-side roomId derivation; room.ts
role-routed per-peer forwarding with deviceId-tag envelope + frame cap +
one-host-per-room eviction; host-bearer minting bound to the PoP room. Swift
cross-stack contracts pinned to test vectors: RelayEnrollment (PoP proof),
RelayEnvelope (routing tag), PairingPayload relay fields. Socket transport +
iOS un-gating remain deploy-gated.

P4 foundation (peer model + listPeers, tested): PeerTypes (PeerKind/
PeerCapabilities/PeerSummary); Hello.deviceKind+clientCaps;
WireCapabilities.canListPeers; ClientMsg.listPeers -> HostMsg.peerList;
PairedDevice.kind+capabilities (decode-defaulted); ConnectionHandler records
kind at pairing; SyncHost.connectedDeviceIDs(); AppStore.peerSummaries();
SyncClient .peerList event; iOS RemoteStore.meshPeers. PeerClient + Mac<->Mac
pairing UI remain.

All additive + capability-gated; SyncProtocol.version stays 1; pre-mesh stores
and clients unaffected. Swift 724 core + 91 protocol tests green; edge 50 green.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-04 00:39:30 -07:00
co-authored by Claude Fable 5
parent 6a268abbb9
commit 25fb2eb8ad
@@ -49,6 +49,11 @@ final class RemoteStore: ObservableObject {
/// Home / Projects / To-Dos state — the dashboard projection. /// Home / Projects / To-Dos state — the dashboard projection.
@Published private(set) var dashboard = DashboardSnapshot.empty @Published private(set) var dashboard = DashboardSnapshot.empty
/// The host's mesh peers (mesh P4), from `HostMsg.peerList`. Populated only when the host
/// advertises `capabilities.canListPeers` and the client asks; drives the future mesh device
/// list and session-transfer destination picker.
@Published private(set) var meshPeers: [PeerSummary] = []
// Open session projection. // Open session projection.
@Published private(set) var openSessionID: SessionID? @Published private(set) var openSessionID: SessionID?
@Published private(set) var openEvents: [AgentEvent] = [] @Published private(set) var openEvents: [AgentEvent] = []
@@ -1034,6 +1039,10 @@ final class RemoteStore: ObservableObject {
guard diff.sessionID == openSessionID else { break } guard diff.sessionID == openSessionID else { break }
openDiff = diff openDiff = diff
diffLoading = false diffLoading = false
case .peerList(let peers):
// Mesh P4: the host's known peers, for the (future) mesh device list and transfer
// picker. Recorded now so multi-host UI can render it; single-host builds ignore it.
meshPeers = peers
case .wireError(let error): case .wireError(let error):
// A channel mismatch means the host and this remote were built from incompatible // A channel mismatch means the host and this remote were built from incompatible
// release channels. Surface it as a persistent failure with a clear message rather // release channels. Surface it as a persistent failure with a clear message rather