Skip to content

mesh: upgrade to mesh-llm v0.72.2, relay tunneling on, membership-derived admission (WIP)#1656

Draft
michaelneale wants to merge 7 commits into
mainfrom
micn/mesh-upgrade
Draft

mesh: upgrade to mesh-llm v0.72.2, relay tunneling on, membership-derived admission (WIP)#1656
michaelneale wants to merge 7 commits into
mainfrom
micn/mesh-upgrade

Conversation

@michaelneale

Copy link
Copy Markdown

WIP — draft.

Upgrades the embedded mesh-llm and hardens compute sharing to the Buzz-server membership circle:

  1. mesh-llm 0.68.0 → v0.72.2 (latest release) + 180s startup timeout + smoke-test exit fix
  2. iroh relay tunneling on by default (BUZZ_MESH_IROH_RELAYS=0 opts out) — transport-only encrypted fallback so member pairs behind symmetric NATs can connect; mesh presence is still never published publicly
  3. Membership-derived admission: each machine gets a mesh owner keypair; the relay republishes member owner ids in its signed status notes; nodes enforce TrustPolicy::Allowlist over that roster. Leaving the server revokes mesh access (roster watcher restarts the node on membership change, ~60s)

Verified: mesh unit tests (30 desktop + 24 relay), clippy clean over baseline, hardware smoke (real serve→client→inference) PASS after each commit.

Still to do:

  • two-machine live test of the membership→admission loop (join, share, consume, remove member, verify rejection)
  • full just ci on a machine with VPN + mobile toolchain (local hooks were env-blocked; Rust gates all green)

… iroh relay tunneling

Bump the mesh-llm pin from ebc3ab4c (0.68.0) to c209d177 (v0.72.2, latest
release) in both the desktop crate and buzz-relay dev-deps. One API break:
initialize_host_runtime() is now async — awaited in the desktop runtime
gate and the hardware smoke example.

Ergonomics adopted from the sibling mesh-console integration:

- startup_timeout(180s) on both serve and client builders. The SDK default
  (30s) times out during first model load (multi-GB download + Metal warmup).

- BUZZ_MESH_IROH_RELAYS env (default: disabled — unchanged behavior).
  "1"/"default" enables iroh's default relay servers as a transport
  fallback for symmetric-NAT peers; any other value is a comma-separated
  list of custom iroh relay URLs. Iroh relay traffic is end-to-end
  encrypted QUIC — relays forward ciphertext only. This is transport-only:
  publish(false) stays hardcoded, the Nostr relay list stays empty, and
  mesh presence is never announced publicly regardless of this setting.

- mesh_serve_client_smoke exits via std::process::exit(0) after PASS to
  skip C++ static destructors — the embedded ggml Metal runtime can
  GGML_ASSERT in its global teardown (same crash as mesh-console issue #8).

Verified: cargo check (desktop --features mesh-llm, relay --all-targets),
fmt, clippy (no new lints over baseline), 27 mesh unit tests pass, and the
hardware smoke proves real serve→client→inference over the mesh on 0.72.2.

Native runtime and CI need no changes: ensure-mesh-native-runtime.sh and
the release/ci llama cache both derive the version from the Cargo metadata.
Flip BUZZ_MESH_IROH_RELAYS default from disabled to enabled: unset/empty
now means iroh's default relay servers are available as transport fallback,
so member pairs behind symmetric NATs can connect. "0" opts out (direct
QUIC only); a comma-separated URL list selects custom relays.

Iroh relays forward end-to-end encrypted QUIC only — no admission role.
publish(false) and the empty Nostr relay list remain hardcoded; mesh
presence is never announced publicly.

Reachability is no longer a de-facto membership gate, so explicit mesh
admission (owner allowlist from relay membership) follows in the next
commits. Docs connectivity section updated to match.
…t the mesh layer

With iroh relay tunneling on by default, reachability no longer acts as an
accidental membership gate: anyone holding a dial pointer could reach a
node. This makes admission explicit using mesh-llm's native trust layer,
with Buzz relay membership as the single source of truth.

Desktop:
- mesh_llm/identity.rs: per-machine mesh owner keypair (ed25519, distinct
  from the Nostr identity) at mesh-llm's default keystore path
  (~/.mesh-llm/owner-keystore.json); load-or-generate, process-cached.
- Both node modes present a signed ownership attestation (.owner_key).
- Status reports now carry the reporter's ownerId; the relay republishes
  it in the sanitized kind:30621 note (relay-signed, member-gated), which
  makes the set of owner ids in those notes the admission roster.
- StartMeshNodeRequest.trusted_owner_ids: when a roster is resolved, nodes
  enforce TrustPolicy::Allowlist + owner_required over exactly those
  owners (self always included so a solo sharer and a fresh relay's first
  member are never locked out). None = no roster resolved (open relay /
  tests / relay outage) -> no enforcement, matching relay-side gating.
- mesh_start_node and both client start paths resolve the roster from the
  relay before starting.
- Coordinator roster watcher (60s poll): membership changed -> restart the
  node with the fresh roster. The SDK trust store is fixed at node start,
  so restart is how join/leave takes effect; an ex-member's access ends
  within one poll interval + restart. Relay outages leave the node as-is.

Relay:
- BuzzMeshStatus.owner_id (camelCase + snake_case accepted, omitted when
  absent) carried through sanitize_mesh_status.

Security outcome: a leaked EndpointAddr or relay reachability admits
nobody; leaving the Buzz server revokes mesh access. Consumption-side
trust (dialing a member's real machine) is unchanged and remains anchored
in relay-signed discovery notes.

Verified: mesh unit tests 30 passed (desktop) + 24 passed (relay), clippy
clean over baseline, non-mesh build unaffected, hardware smoke PASS.
Replaces guess-into-a-text-field with mesh-console's diagnose pattern:
survey the machine (GPU, AI memory), rank mesh-llm's curated MODEL_CATALOG
by fit (comfortable/tight/tradeoff/too_large, same thresholds as
mesh-llm's fit_code_for_size_label), mark installed models from the HF
cache, and recommend the best fit via auto_model_pack.

- New optional deps (mesh-llm feature only): mesh-llm-client (catalog +
  auto_model_pack), mesh-llm-node (HF cache scan), mesh-llm-system
  (hardware survey), same pinned rev.
- mesh_llm/catalog.rs: model_catalog() -> MeshModelCatalog (recommended
  first, then by fit, larger first within a fit; draft-only speculative
  models excluded).
- New Tauri command mesh_model_catalog (+ stub for non-mesh builds);
  survey + cache scan run on spawn_blocking.
- MeshComputeSettingsCard: "Suggested for this machine" list above the
  model field — click to fill, fit labels, Recommended/Installed badges,
  too-large entries visible but disabled, show-all toggle. Free-text field
  unchanged; catalog failure degrades to the old UI.
- e2e bridge mocks mesh_model_catalog.

Verified: 33 mesh unit tests (3 new: fit thresholds, ranking order,
installed matching), tsc + biome clean, non-mesh build clean, all 7
mesh-compute Playwright specs pass.
Fixes the two worst Share-compute UX failures:

1. Starting a serve node with an uncached model froze the card greyed-out
   for the whole multi-GB download. Now the model is pre-downloaded
   *before* serve::start() (ensure_model_downloaded, mesh-console's
   ensure_model pattern), and byte progress flows out of mesh-llm's
   process-global OutputSink (mesh_llm/progress.rs, installed at setup)
   as `mesh-download-progress` Tauri events. The card renders a live
   progress bar: model name, N.N GB of N.N GB, percent. Already-cached
   models skip straight to node start.

2. The recommendation was ghost placeholder text in an empty field. Now
   the catalog fetch pre-fills the recommended model as a real value
   (draft persisted); placeholder only shows when there is genuinely no
   recommendation.

- mesh-llm-events added as an optional dep (same pinned rev);
  ConsoleSessionMode::InteractiveDashboard is what makes byte-level
  progress flow through the sink instead of stderr ANSI bars.
- recursion_limit=256 on buzz_lib: the ensure→download→start async chain
  under Tauri command futures exceeded the default query depth.
- useMeshDownloadProgress hook + DownloadProgressBar; progress resets
  when the start action settles or errors.

Verified: 33 mesh unit tests, tsc + biome clean, non-mesh build clean,
all 7 mesh-compute Playwright specs pass.
…poses upstream token-holder inference hole

Three-process harness (mesh_admission_smoke): allowlist serve node, a
trusted client (allowlisted owner key), and a stranger (different owner
key, SAME leaked invite token). One process per node because mesh-llm
keeps process-global state (~/.mesh-llm node identity + attestation);
each role gets an isolated HOME.

Results on v0.72.2 (run on hardware, real inference):
- PASS: trusted client admitted, model visible, inference routes ("PONG!")
- PASS: serve rejects the stranger at gossip — "Rejecting peer due to
  owner policy: UntrustedOwner" — it is never promoted to the peer set
- FAIL (upstream, deliberate): the stranger can STILL route an inference
  with the leaked token. mesh-llm's quarantine gate exempts
  STREAM_TUNNEL_HTTP (0x04) and STREAM_ROUTE_REQUEST (0x05) from
  admission ("passive SDK inference path for callers that have an invite
  token" — stream_allowed_before_admission, mesh/mod.rs:2550).
  signed_join_tokens(true) does not help: the leak IS the signed token.

So on current upstream, the owner allowlist gates mesh *membership*
(peer promotion, gossip, raw tunnel) but not *inference serving* to any
holder of a valid invite token. Buzz's membership-derived admission
therefore hardens the pool, and the leaked-dial-pointer inference hole
needs an upstream change (gate 0x04/0x05 behind admission whenever
trust_policy != Off). The harness asserts today's reachable guarantee
(gossip rejection) and documents the routing gap; flip the inference
assertion to load-bearing once upstream closes it.
… upstream inference-path gap

Verdict handling now matches what v0.72.2 actually enforces:
- hard PASS/FAIL on membership gating (trusted admitted + routes,
  stranger rejected at gossip as UntrustedOwner)
- the stranger's token-holder inference via the admission-exempt
  STREAM_TUNNEL_HTTP path is reported as a KNOWN UPSTREAM GAP, not a
  harness failure; flip to hard-fail once upstream gates 0x04/0x05
  behind admission when trust_policy != Off.

Full run: exit 0, PASS 1/3 + 2/3 + 3/3(with gap noted).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant