Files
buzz/crates/sprout-core
tlongwell-block f711e37a7a mesh-llm: Max review fixups — expires_at, EndpointAddr, same-relay filter
Per Max's pre-PR review of d9a791f:

1. Add MeshLlmOffer.expires_at: u64 (unix seconds). Hard-required by
   serde (no default) since consumers depend on it for correctness:
   crashed publishers cannot send the NIP-33 delete-by-replace
   tombstone, so the TTL is the only thing that reaps stale offers.

   New helpers + tests in sprout-core::mesh_llm:
   - is_expired(now) returns true when expires_at <= now.
   - matches_local_relay(current_relay) compares against the relay's
     NIP-11 iroh_relay_url with lightweight canonicalisation
     (lower-case scheme/host, trailing-slash collapse, query/fragment
     drop). v1 invariant: one relay = one mesh boundary.
   - is_publishable now rejects expires_at == 0.
   - canonical_relay_url() is a small private helper, deliberately
     separate from sprout_auth::nip98_canonical_url (different jobs).

   5 new tests (expires_at_required, is_expired_filter,
   matches_local_relay_canonicalises, is_publishable_rejects_zero_*,
   plus updated JSON fixtures throughout).

2. Fix doc wording: iroh NodeAddr -> EndpointAddr (rc.0 naming).
   Soften 'multiple relays bridging into membership scope' language to
   reflect that v1 is strictly single-relay and the field is reserved
   for future cross-relay only.

3. Desktop publisher (mesh_publish_offer) now computes
   expires_at = now + OFFER_TTL_SECS (15 min) and threads it through
   build_offer(endpoint_id, iroh_relay_url, expires_at). The frontend
   hook is expected to re-invoke publish on heartbeat well before the
   deadline (heartbeat plumbing is in the next commit).

4. Frontend useMeshLlmOffers hook:
   - probes the relay's NIP-11 iroh_relay_url once on mount and
     filters offers whose iroh_relay_url doesn't match (v1 same-relay
     invariant). When the relay doesn't advertise mesh-LLM, the filter
     correctly empties the list.
   - rejects events with schema v != 1 before storing.
   - 30s setInterval ticks 'now' so expired offers drop without waiting
     for a new event. O(1) timer regardless of how many offers are in
     the cache.
   - mirrors the canonicaliser logic from sprout-core in TS so JS-side
     accept/reject decisions match the Rust check.

Tests after change:
- sprout-core --lib: 174 -> 178 (+4 mesh_llm)
- desktop mesh_llm --lib: 11 unchanged (publisher signature change
  required updating build_offer call sites; tests pass exact-same set).
- pnpm typecheck + pnpm check (biome + file-sizes): clean.
- cargo clippy --workspace -- -D warnings: clean.
- cargo fmt --all -- --check: clean.

Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
2026-05-19 17:20:29 -04:00
..