"""Mesh user-story proof — runs the all-to-all relay mesh for real and screenshots it. Each "user story" is executed end-to-end against a genuine, locally-hosted relay hub (uvicorn) with real HearthNet nodes — no mocks, no fake answers: US-M1 Bob (no LLM locally) asks a question -> routed over the relay to Alice. US-M2 Bob queries RAG -> routed over the relay to Alice's corpus. US-M3 Carol joins late -> roster gossip makes A/B/C mutually aware; Carol then routes an LLM call to Alice. US-M4 Local-first guard -> a node that never joined the relay cannot reach mesh peers (proves relay is opt-in). The real results are rendered into an annotated HTML report and captured as screenshots in docs/screenshots/stories/ via Playwright. Usage: python scripts/mesh_story_proof.py """ from __future__ import annotations import asyncio import contextlib import datetime import html import socket import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) OUT = Path("docs/screenshots/stories") OUT.mkdir(parents=True, exist_ok=True) REPORT = Path("docs/screenshots/_mesh_story.html") def _free_port() -> int: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(("127.0.0.1", 0)) return s.getsockname()[1] async def _serve_relay(port: int): import httpx import uvicorn from fastapi import FastAPI from hearthnet.transport.relay_hub import RelayHub, mount_relay_endpoints app = FastAPI() hub = RelayHub(member_ttl_seconds=120) mount_relay_endpoints(app, hub) config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="warning", lifespan="off") server = uvicorn.Server(config) task = asyncio.create_task(server.serve()) deadline = asyncio.get_event_loop().time() + 5.0 async with httpx.AsyncClient(timeout=1.0) as client: while asyncio.get_event_loop().time() < deadline: with contextlib.suppress(Exception): if (await client.get(f"http://127.0.0.1:{port}/relay/v1/roster")).status_code == 200: break await asyncio.sleep(0.1) else: raise TimeoutError("relay hub never became ready") async def _shutdown() -> None: server.should_exit = True with contextlib.suppress(Exception): await task return _shutdown async def _wait_until(predicate, timeout: float = 6.0) -> bool: deadline = asyncio.get_event_loop().time() + timeout while asyncio.get_event_loop().time() < deadline: if predicate(): return True await asyncio.sleep(0.05) return False async def run_stories() -> dict: from hearthnet.node import HearthNode port = _free_port() relay_url = f"http://127.0.0.1:{port}" shutdown = await _serve_relay(port) alice = HearthNode("ed25519:alice-mesh", "Alice", "ed25519:community") alice.install_demo_services(corpus="alpha") bob = HearthNode("ed25519:bob-mesh", "Bob", "ed25519:community") carol = HearthNode("ed25519:carol-mesh", "Carol", "ed25519:community") stories: list[dict] = [] try: bob_local = sorted({e.descriptor.name for e in bob.bus.registry.all_local()}) await alice.join_relay(relay_url) await bob.join_relay(relay_url) await _wait_until( lambda: any(e.node_id == "ed25519:alice-mesh" for e in bob.bus.registry.all_remote()) ) # US-M1 — Bob's LLM call routes across the relay to Alice. q1 = "In one sentence, how do I store water safely?" c1 = await bob.bus.call( "llm.chat", (1, 0), {"input": {"messages": [{"role": "user", "content": q1}]}} ) stories.append( { "id": "USM-01-bob-llm-over-relay", "title": "US-M1 · Bob asks the mesh — answered by Alice over the relay", "facts": [ ("Bob's local capabilities", ", ".join(bob_local) or "(none)"), ("llm.chat available locally on Bob?", "no — must route over the mesh"), ("Question", q1), ("Answer", c1["output"]["message"]["content"]), ("Served by model", c1["meta"]["model"] + " (Alice)"), ], } ) # US-M2 — Bob's RAG query routes across the relay to Alice's corpus. c2 = await bob.bus.call("rag.query", (1, 0), {"input": {"query": "water"}}) chunk = c2["output"]["chunks"][0] stories.append( { "id": "USM-02-bob-rag-over-relay", "title": "US-M2 · Bob queries RAG — Alice's corpus answers over the relay", "facts": [ ("Query", "water"), ("Top chunk", chunk["text"]), ("Source doc", chunk["metadata"]["doc_title"]), ("Corpus", c2["meta"]["corpus"] + " (Alice)"), ], } ) # US-M3 — Carol joins late; roster gossip; Carol routes to Alice. await carol.join_relay(relay_url) a_sees_c = await _wait_until(lambda: alice.peers.get("ed25519:carol-mesh") is not None) b_sees_c = await _wait_until(lambda: bob.peers.get("ed25519:carol-mesh") is not None) c3 = await carol.bus.call( "llm.chat", (1, 0), {"input": {"messages": [{"role": "user", "content": "Hello mesh, this is Carol"}]}}, ) stories.append( { "id": "USM-03-roster-gossip-all-to-all", "title": "US-M3 · Carol joins late — all-to-all roster gossip", "facts": [ ("Alice now sees Carol", "yes" if a_sees_c else "no"), ("Bob now sees Carol", "yes" if b_sees_c else "no"), ("Carol sees Alice", "yes" if carol.peers.get("ed25519:alice-mesh") else "no"), ("Carol's LLM call answered by", c3["output"]["message"]["content"]), ], } ) # US-M4 — Local-first guard: a node that never joined the relay is isolated. loner = HearthNode("ed25519:loner", "Loner", "ed25519:community") from hearthnet.bus import BusError try: await loner.bus.call( "llm.chat", (1, 0), {"input": {"messages": [{"role": "user", "content": "anyone there?"}]}}, ) guard_result = "unexpected success" except BusError as exc: guard_result = f"{exc.code} — no mesh provider reachable" stories.append( { "id": "USM-04-local-first-guard", "title": "US-M4 · Local-first guard — relay is opt-in", "facts": [ ("Loner joined the relay?", "no"), ("llm.chat result", guard_result), ("Meaning", "without an explicit join, a node makes NO mesh calls"), ], } ) finally: await alice.leave_relay() await bob.leave_relay() await carol.leave_relay() await shutdown() return {"stories": stories, "relay_url": relay_url} def _render_html(result: dict) -> str: ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") cards = [] for s in result["stories"]: rows = "".join( f'