feat(mesh): NATS JetStream transport replacing MQTT

Replace the Mosquitto/paho mesh transport with NATS JetStream KV:
- mesh_wire.py: transport-agnostic registry (de)serialization (secret-stripped)
- nats_client.py: async CastleNATSClient — registry in the castle-registry KV
  bucket, heartbeat liveness, delete-on-stop offline, watch-driven mesh state
- rewire main/config/models/nodes; MeshStatus -> connected/nats_url
- frontend MeshStatus + MeshPanel to the new fields
- drop paho-mqtt/mqtt_client + _mqtt._tcp mDNS browse; add nats-py
- keep the secret-stripping invariant; mDNS peer discovery retained

Single-node verified; two-node parity pending the second node.
This commit is contained in:
2026-07-07 04:55:02 -07:00
parent f826d972b8
commit 6c00a9d33c
12 changed files with 436 additions and 76 deletions

View File

@@ -66,15 +66,14 @@ def _deployed_to_summaries(registry: object, hostname: str) -> list[DeploymentSu
@router.get("/mesh/status", response_model=MeshStatus)
def get_mesh_status(request: Request) -> MeshStatus:
"""Get the current state of the mesh coordination layer."""
mqtt_client = getattr(request.app.state, "mqtt_client", None)
nats_client = getattr(request.app.state, "nats_client", None)
peers = list(mesh_state.all_nodes(include_stale=True).keys())
return MeshStatus(
enabled=settings.mqtt_enabled,
mqtt_connected=mqtt_client.connected if mqtt_client else False,
mqtt_broker_host=mqtt_client.broker_host if mqtt_client else None,
mqtt_broker_port=mqtt_client.broker_port if mqtt_client else None,
enabled=settings.nats_enabled,
connected=nats_client.connected if nats_client else False,
nats_url=str(nats_client.servers) if nats_client else None,
mdns_enabled=settings.mdns_enabled,
peer_count=len(peers),
peers=peers,