fleetmesh-node 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
  2. fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
  3. fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
  4. fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
  5. fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
  6. fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
  7. node/RELEASE +1 -0
  8. node/__init__.py +35 -0
  9. node/app/__init__.py +49 -0
  10. node/app/commands.py +265 -0
  11. node/app/config.py +131 -0
  12. node/app/control.py +670 -0
  13. node/app/core.py +867 -0
  14. node/app/plugins.py +76 -0
  15. node/app/router.py +83 -0
  16. node/app/socket.py +431 -0
  17. node/app/systemd.py +36 -0
  18. node/app/views.py +204 -0
  19. node/channels/__init__.py +378 -0
  20. node/channels/bus.py +331 -0
  21. node/channels/journal.py +46 -0
  22. node/commands/__init__.py +140 -0
  23. node/commands/adapter.py +210 -0
  24. node/commands/base.py +278 -0
  25. node/commands/cluster_cmds.py +183 -0
  26. node/commands/compute_cmds.py +401 -0
  27. node/commands/core.py +474 -0
  28. node/commands/gossip_cmds.py +106 -0
  29. node/commands/nsite_cmds.py +141 -0
  30. node/commands/peering.py +402 -0
  31. node/commands/plugin_cmds.py +135 -0
  32. node/commands/sidecar_cmds.py +120 -0
  33. node/commands/sync.py +388 -0
  34. node/commands/transport_cmds.py +451 -0
  35. node/commands/video_cmds.py +99 -0
  36. node/compartment/__init__.py +124 -0
  37. node/compartment/base.py +868 -0
  38. node/compartment/command.py +458 -0
  39. node/compartment/context.py +163 -0
  40. node/compartment/every.py +108 -0
  41. node/compartment/handler.py +131 -0
  42. node/compartment/lifecycle.py +460 -0
  43. node/compartment/lint.py +388 -0
  44. node/compartment/need.py +158 -0
  45. node/compartment/problem.py +521 -0
  46. node/compartment/refusals.py +120 -0
  47. node/compartment/registry.py +790 -0
  48. node/compartment/schema.py +290 -0
  49. node/compartment/sidecar.py +499 -0
  50. node/compartment/stores.py +662 -0
  51. node/compartment/wire.py +225 -0
  52. node/compartment/worker.py +61 -0
  53. node/config/__init__.py +43 -0
  54. node/config/base.py +116 -0
  55. node/config/change.py +441 -0
  56. node/config/changestore.py +201 -0
  57. node/config/compartments.py +1144 -0
  58. node/config/node.py +318 -0
  59. node/config/parameter.py +563 -0
  60. node/config/protocols.py +274 -0
  61. node/config/records.py +171 -0
  62. node/examples/__init__.py +1 -0
  63. node/examples/demo.py +226 -0
  64. node/examples/follower.py +46 -0
  65. node/interfaces.py +235 -0
  66. node/keystore/__init__.py +27 -0
  67. node/keystore/core.py +103 -0
  68. node/keystore/enclave.py +688 -0
  69. node/meshnode.py +111 -0
  70. node/metrics/__init__.py +396 -0
  71. node/metrics/adapters.py +102 -0
  72. node/metrics/face.py +110 -0
  73. node/planes/__init__.py +21 -0
  74. node/planes/identity/__init__.py +20 -0
  75. node/planes/identity/announce/__init__.py +9 -0
  76. node/planes/identity/announce/compartment.py +447 -0
  77. node/planes/identity/announce/types.py +26 -0
  78. node/planes/identity/discovery/__init__.py +9 -0
  79. node/planes/identity/discovery/compartment.py +522 -0
  80. node/planes/identity/discovery/types.py +13 -0
  81. node/planes/identity/linked/__init__.py +1 -0
  82. node/planes/identity/linked/compartment.py +270 -0
  83. node/planes/identity/linked/types.py +25 -0
  84. node/planes/identity/presence/__init__.py +7 -0
  85. node/planes/identity/presence/compartment.py +409 -0
  86. node/planes/identity/presence/types.py +14 -0
  87. node/planes/operations/__init__.py +13 -0
  88. node/planes/operations/scheduler/__init__.py +9 -0
  89. node/planes/operations/scheduler/compartment.py +353 -0
  90. node/planes/operations/scheduler/types.py +21 -0
  91. node/planes/operations/updates/__init__.py +17 -0
  92. node/planes/operations/updates/core.py +310 -0
  93. node/planes/operations/updates/store.py +79 -0
  94. node/planes/operations/updates/types.py +20 -0
  95. node/planes/peering/__init__.py +28 -0
  96. node/planes/peering/budget/__init__.py +43 -0
  97. node/planes/peering/budget/chain.py +43 -0
  98. node/planes/peering/budget/core.py +537 -0
  99. node/planes/peering/budget/evaluation.py +80 -0
  100. node/planes/peering/budget/meter.py +100 -0
  101. node/planes/peering/budget/receipts.py +439 -0
  102. node/planes/peering/budget/shape.py +101 -0
  103. node/planes/peering/budget/tick.py +110 -0
  104. node/planes/peering/budget/types.py +67 -0
  105. node/planes/peering/budget/windows.py +346 -0
  106. node/planes/peering/capacity/__init__.py +7 -0
  107. node/planes/peering/capacity/compartment.py +342 -0
  108. node/planes/peering/capacity/types.py +24 -0
  109. node/planes/peering/peering/__init__.py +25 -0
  110. node/planes/peering/peering/core.py +464 -0
  111. node/planes/peering/peering/envelope.py +142 -0
  112. node/planes/peering/peering/handshake.py +198 -0
  113. node/planes/peering/peering/shape.py +80 -0
  114. node/planes/peering/peering/types.py +39 -0
  115. node/planes/peering/reporter/__init__.py +42 -0
  116. node/planes/peering/reporter/constants.py +29 -0
  117. node/planes/peering/reporter/core.py +299 -0
  118. node/planes/peering/reporter/finding.py +74 -0
  119. node/planes/peering/reporter/findings.py +171 -0
  120. node/planes/peering/reporter/publish.py +285 -0
  121. node/planes/peering/reporter/reading.py +193 -0
  122. node/planes/peering/reporter/shape.py +86 -0
  123. node/planes/peering/reporter/types.py +50 -0
  124. node/planes/peering/strain/__init__.py +9 -0
  125. node/planes/peering/strain/compartment.py +204 -0
  126. node/planes/peering/strain/types.py +21 -0
  127. node/planes/peering/trust/__init__.py +9 -0
  128. node/planes/peering/trust/compartment.py +239 -0
  129. node/planes/peering/trust/types.py +30 -0
  130. node/planes/services/__init__.py +33 -0
  131. node/planes/services/compute/__init__.py +160 -0
  132. node/planes/services/compute/common.py +134 -0
  133. node/planes/services/compute/compartment.py +681 -0
  134. node/planes/services/compute/executors/__init__.py +121 -0
  135. node/planes/services/compute/executors/agent.py +840 -0
  136. node/planes/services/compute/executors/bacalhau.py +605 -0
  137. node/planes/services/compute/executors/base.py +127 -0
  138. node/planes/services/compute/executors/harness.py +439 -0
  139. node/planes/services/compute/executors/mcp.py +454 -0
  140. node/planes/services/compute/executors/media.py +898 -0
  141. node/planes/services/compute/executors/nondet.py +283 -0
  142. node/planes/services/compute/executors/providers.py +499 -0
  143. node/planes/services/compute/executors/registry.py +140 -0
  144. node/planes/services/compute/executors/wasm.py +244 -0
  145. node/planes/services/compute/executors/zk.py +604 -0
  146. node/planes/services/compute/jobs.py +227 -0
  147. node/planes/services/compute/load.py +180 -0
  148. node/planes/services/compute/market.py +433 -0
  149. node/planes/services/compute/sandbox.py +282 -0
  150. node/planes/services/compute/types.py +84 -0
  151. node/planes/services/compute_wire/__init__.py +58 -0
  152. node/planes/services/compute_wire/compartment.py +256 -0
  153. node/planes/services/compute_wire/messages.py +320 -0
  154. node/planes/services/compute_wire/sealing.py +147 -0
  155. node/planes/services/compute_wire/session.py +572 -0
  156. node/planes/services/compute_wire/types.py +49 -0
  157. node/planes/services/compute_wire/verify.py +331 -0
  158. node/planes/services/dvm/__init__.py +24 -0
  159. node/planes/services/dvm/gateway.py +526 -0
  160. node/planes/services/dvm/protocol.py +35 -0
  161. node/planes/services/dvm/translate.py +15 -0
  162. node/planes/services/dvm/types.py +25 -0
  163. node/planes/services/gateway/__init__.py +40 -0
  164. node/planes/services/gateway/settle.py +279 -0
  165. node/planes/services/gateway/translate.py +644 -0
  166. node/planes/services/gossip/__init__.py +22 -0
  167. node/planes/services/gossip/core.py +929 -0
  168. node/planes/services/gossip/envelope.py +154 -0
  169. node/planes/services/gossip/mesh.py +127 -0
  170. node/planes/services/gossip/types.py +50 -0
  171. node/planes/services/nsite/__init__.py +7 -0
  172. node/planes/services/nsite/client.py +62 -0
  173. node/planes/services/nsite/compartment.py +752 -0
  174. node/planes/services/nsite/types.py +44 -0
  175. node/planes/services/paidfetch/__init__.py +7 -0
  176. node/planes/services/paidfetch/compartment.py +862 -0
  177. node/planes/services/paidfetch/types.py +99 -0
  178. node/planes/services/settle/__init__.py +27 -0
  179. node/planes/services/settle/compartment.py +384 -0
  180. node/planes/services/settle/engine.py +456 -0
  181. node/planes/services/settle/gateways.py +541 -0
  182. node/planes/services/settle/payment.py +512 -0
  183. node/planes/services/settle/store.py +74 -0
  184. node/planes/services/settle/types.py +58 -0
  185. node/planes/services/sidecars/__init__.py +8 -0
  186. node/planes/services/sidecars/compartment.py +461 -0
  187. node/planes/services/sidecars/manifest.py +304 -0
  188. node/planes/services/sidecars/manifests/10-grasp.json +37 -0
  189. node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
  190. node/planes/services/sidecars/manifests/30-magpie.json +28 -0
  191. node/planes/services/sidecars/types.py +43 -0
  192. node/planes/services/video/__init__.py +106 -0
  193. node/planes/services/video/barter.py +407 -0
  194. node/planes/services/video/cli.py +158 -0
  195. node/planes/services/video/compartment.py +932 -0
  196. node/planes/services/video/ingest.py +524 -0
  197. node/planes/services/video/ledger.py +705 -0
  198. node/planes/services/video/packager.py +847 -0
  199. node/planes/services/video/player.py +456 -0
  200. node/planes/services/video/timed_segments.py +124 -0
  201. node/planes/services/video/transit.py +478 -0
  202. node/planes/store/__init__.py +24 -0
  203. node/planes/store/blob_replication/__init__.py +7 -0
  204. node/planes/store/blob_replication/compartment.py +496 -0
  205. node/planes/store/blob_replication/types.py +35 -0
  206. node/planes/store/blob_wire/__init__.py +48 -0
  207. node/planes/store/blob_wire/client.py +57 -0
  208. node/planes/store/blob_wire/core.py +359 -0
  209. node/planes/store/blob_wire/intake.py +74 -0
  210. node/planes/store/blob_wire/messages.py +85 -0
  211. node/planes/store/projected/__init__.py +7 -0
  212. node/planes/store/projected/compartment.py +258 -0
  213. node/planes/store/projected/types.py +29 -0
  214. node/planes/store/retention/__init__.py +7 -0
  215. node/planes/store/retention/compartment.py +214 -0
  216. node/planes/store/retention/types.py +12 -0
  217. node/planes/store/sync/__init__.py +35 -0
  218. node/planes/store/sync/core.py +311 -0
  219. node/planes/store/sync/envelope.py +106 -0
  220. node/planes/store/sync/handover.py +206 -0
  221. node/planes/store/sync/initiator.py +128 -0
  222. node/planes/store/sync/responder.py +114 -0
  223. node/planes/store/sync/rules.py +179 -0
  224. node/planes/store/sync/shape.py +80 -0
  225. node/planes/store/sync/types.py +51 -0
  226. node/planes/transport/__init__.py +23 -0
  227. node/planes/transport/bridge/__init__.py +13 -0
  228. node/planes/transport/bridge/compartment.py +785 -0
  229. node/planes/transport/bridge/ratelimit.py +44 -0
  230. node/planes/transport/bridge/types.py +43 -0
  231. node/planes/transport/lan/__init__.py +9 -0
  232. node/planes/transport/lan/compartment.py +446 -0
  233. node/planes/transport/lan/types.py +25 -0
  234. node/planes/transport/link/__init__.py +7 -0
  235. node/planes/transport/link/compartment.py +257 -0
  236. node/planes/transport/link/types.py +27 -0
  237. node/planes/transport/mediation/__init__.py +45 -0
  238. node/planes/transport/mediation/compartment.py +238 -0
  239. node/planes/transport/mediation/leaf.py +107 -0
  240. node/planes/transport/mediation/messages.py +302 -0
  241. node/planes/transport/mediation/service.py +233 -0
  242. node/planes/transport/mediation/store.py +37 -0
  243. node/planes/transport/mediation/types.py +20 -0
  244. node/planes/transport/p2p/__init__.py +7 -0
  245. node/planes/transport/p2p/compartment.py +349 -0
  246. node/planes/transport/p2p/types.py +22 -0
  247. node/protocols/__init__.py +277 -0
  248. node/protocols/activitypub/__init__.py +56 -0
  249. node/protocols/activitypub/actor.py +230 -0
  250. node/protocols/activitypub/federation.py +490 -0
  251. node/protocols/activitypub/media.py +145 -0
  252. node/protocols/activitypub/models.py +286 -0
  253. node/protocols/activitypub/package.py +271 -0
  254. node/protocols/activitypub/store.py +564 -0
  255. node/protocols/arcade/__init__.py +26 -0
  256. node/protocols/atproto/__init__.py +13 -0
  257. node/protocols/atproto/firehose.py +126 -0
  258. node/protocols/atproto/metering.py +165 -0
  259. node/protocols/atproto/package.py +155 -0
  260. node/protocols/atproto/repo.py +250 -0
  261. node/protocols/atproto/xrpc.py +285 -0
  262. node/protocols/base.py +109 -0
  263. node/protocols/circles/__init__.py +29 -0
  264. node/protocols/dm/__init__.py +50 -0
  265. node/protocols/grasp/__init__.py +22 -0
  266. node/protocols/isotime.py +15 -0
  267. node/protocols/nostr/__init__.py +51 -0
  268. node/protocols/nostr/blobstore.py +383 -0
  269. node/protocols/nostr/blossom.py +627 -0
  270. node/protocols/nostr/client.py +377 -0
  271. node/protocols/nostr/descriptor.py +211 -0
  272. node/protocols/nostr/eventstore.py +304 -0
  273. node/protocols/nostr/package.py +182 -0
  274. node/protocols/nostr/peers.py +109 -0
  275. node/protocols/nostr/relayserver.py +656 -0
  276. node/protocols/p2panda/__init__.py +22 -0
  277. node/protocols/p2panda/identity.py +19 -0
  278. node/protocols/p2panda/package.py +236 -0
  279. node/protocols/p2panda/rung.py +152 -0
  280. node/protocols/p2panda/store.py +273 -0
  281. node/protocols/rhio/__init__.py +33 -0
  282. node/protocols/rhio/package.py +275 -0
  283. node/protocols/rhio/rung.py +136 -0
  284. node/protocols/rhio/store.py +1423 -0
  285. node/protocols/ssb/muxrpc.py +256 -0
  286. node/protocols/ssb/package.py +267 -0
  287. node/protocols/textile/__init__.py +111 -0
  288. node/protocols/textile/transducers.py +259 -0
  289. node/release.py +119 -0
  290. node/storage/__init__.py +88 -0
  291. node/storage/accounts.py +305 -0
  292. node/storage/adapter.py +161 -0
  293. node/storage/base.py +385 -0
  294. node/storage/dialect.py +209 -0
  295. node/storage/grants.py +317 -0
  296. node/storage/locator.py +150 -0
  297. node/storage/postgres.py +294 -0
  298. node/storage/projected.py +953 -0
  299. node/storage/sharding.py +448 -0
  300. node/storage/standing.py +568 -0
  301. node/testnet/__init__.py +9 -0
  302. node/testnet/cluster.py +731 -0
  303. node/testnet/fips_daemon.py +230 -0
  304. node/transport/__init__.py +94 -0
  305. node/transport/const.py +110 -0
  306. node/transport/didcomm_inbound.py +251 -0
  307. node/transport/dnstxt.py +169 -0
  308. node/transport/fips_rung/__init__.py +67 -0
  309. node/transport/fips_rung/core.py +666 -0
  310. node/transport/fips_rung/listener.py +96 -0
  311. node/transport/fips_rung/types.py +12 -0
  312. node/transport/host.py +334 -0
  313. node/transport/iroh_rung/__init__.py +29 -0
  314. node/transport/iroh_rung/core.py +477 -0
  315. node/transport/iroh_rung/quic.py +942 -0
  316. node/transport/iroh_rung/types.py +22 -0
  317. node/transport/ladder.py +145 -0
  318. node/transport/lineproto.py +49 -0
  319. node/transport/mdns.py +303 -0
  320. node/transport/netfetch.py +283 -0
  321. node/transport/relay_rung/__init__.py +23 -0
  322. node/transport/relay_rung/cooldown.py +87 -0
  323. node/transport/relay_rung/core.py +138 -0
  324. node/transport/relay_rung/inbound.py +161 -0
  325. node/transport/relay_rung/ledger.py +104 -0
  326. node/transport/relay_rung/listen.py +121 -0
  327. node/transport/relay_rung/outbound.py +181 -0
  328. node/transport/relay_rung/shape.py +71 -0
  329. node/transport/relay_rung/types.py +58 -0
  330. node/transport/relay_rung/wire.py +63 -0
  331. node/transport/router.py +761 -0
  332. node/transport/rung.py +276 -0
  333. node/transport/ssb_rung.py +166 -0
  334. node/transport/ws.py +502 -0
  335. sidecar/fleetmesh_announce.py +338 -0
  336. sidecar/fleetmesh_p2p.py +373 -0
  337. sidecar/fleetmesh_rhio.py +352 -0
  338. sidecar/fleetmesh_ssb.py +603 -0
@@ -0,0 +1,33 @@
1
+ """Rhio cluster sync & P2P JetStream protocol suite for Fleetmesh.
2
+
3
+ Native and sidecar integration for HIRO Rhio:
4
+ - NATS JetStream messaging and stream replication
5
+ - Bao-compatible chunked tree hashing for blob integrity
6
+ - S3 / MinIO cluster storage synchronization
7
+ - Outbound RhioRung for TransportRouter ladder
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from .package import RhioProtocolPackage
12
+ from .rung import RhioRung
13
+ from .store import (
14
+ FiberAccessError,
15
+ RhioBaoTree,
16
+ RhioBlobSync,
17
+ RhioStoreError,
18
+ binary_http_response,
19
+ handle_fiber_http,
20
+ is_fiber_route,
21
+ )
22
+
23
+ __all__ = [
24
+ 'FiberAccessError',
25
+ 'RhioBaoTree',
26
+ 'RhioBlobSync',
27
+ 'RhioProtocolPackage',
28
+ 'RhioRung',
29
+ 'RhioStoreError',
30
+ 'binary_http_response',
31
+ 'handle_fiber_http',
32
+ 'is_fiber_route',
33
+ ]
@@ -0,0 +1,275 @@
1
+ """fleetmesh-protocol-rhio: Rhio cluster sync protocol package for Fleetmesh.
2
+
3
+ Conforms to ProtocolPackage (fleetmesh_core.wire.interfaces.ProtocolPackage).
4
+ Mounts Rhio cluster blob sync, NATS JetStream bridge, and Bao tree verification
5
+ onto ProtocolHostRouter and provides RhioRung for the TransportRouter ladder.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import json
11
+ import logging
12
+ import os
13
+ import urllib.parse
14
+ from typing import Any, Mapping, Optional
15
+
16
+ from fleetmesh_core.wire.interfaces import (
17
+ ProtocolHealth,
18
+ ProtocolHostRouter,
19
+ ProtocolIdentityAdapter,
20
+ )
21
+ from node.protocols.base import BaseProtocolPackage
22
+ from node.protocols.p2panda.identity import P2PandaIdentityAdapter
23
+ from .rung import RhioRung
24
+ from .store import RhioBlobSync
25
+
26
+ __all__ = [
27
+ 'RhioProtocolPackage',
28
+ ]
29
+
30
+ log = logging.getLogger(__name__)
31
+
32
+
33
+ class RhioProtocolPackage(BaseProtocolPackage):
34
+ """The Rhio cluster synchronization suite for Fleetmesh."""
35
+
36
+ name: str = 'rhio'
37
+ version: str = '0.1.0'
38
+ standards: tuple[str, ...] = (
39
+ 'rhio-nats-v1',
40
+ 'p2panda-v0.4.0',
41
+ 'bao-tree-hash',
42
+ )
43
+
44
+ def __init__(
45
+ self,
46
+ node_dir: Optional[str] = None,
47
+ keystore: Optional[Any] = None,
48
+ blob_store: Optional[Any] = None,
49
+ store: Optional[RhioBlobSync] = None,
50
+ sidecar_client: Optional[Any] = None,
51
+ auto_start_sidecar: bool = False,
52
+ **kwargs: Any,
53
+ ) -> None:
54
+ self.node_dir = node_dir
55
+ self.keystore = keystore
56
+ self.blob_store = blob_store
57
+ self.auto_start_sidecar = auto_start_sidecar
58
+ self.config: Mapping[str, Any] = {}
59
+ self._enabled: bool = False
60
+ self._running: bool = False
61
+ self._messages_in: int = 0
62
+ self._messages_out: int = 0
63
+ self._daemon: Optional[Any] = None
64
+
65
+ # Identity adapter (p2panda author identity used by Rhio)
66
+ if keystore is not None:
67
+ self.identity: Optional[P2PandaIdentityAdapter] = P2PandaIdentityAdapter.from_keystore(keystore)
68
+ else:
69
+ self.identity = None
70
+
71
+ # Blob & Tree Hash Store
72
+ if store is not None:
73
+ self.store: RhioBlobSync = store
74
+ elif node_dir:
75
+ db_path = os.path.join(node_dir, "rhio_blobs.db")
76
+ cas_dir = os.path.join(node_dir, "rhio_blobs")
77
+ self.store = RhioBlobSync(db_path=db_path, cas_dir=cas_dir)
78
+ else:
79
+ self.store = RhioBlobSync(":memory:")
80
+
81
+ # Outbound Transport Rung
82
+ self.socket_path: Optional[str] = os.path.join(node_dir, "rhio.sock") if node_dir else None
83
+ self.rung: RhioRung = RhioRung(
84
+ sidecar_client=sidecar_client,
85
+ socket_path=self.socket_path,
86
+ enabled=self._enabled,
87
+ )
88
+
89
+ self._host: Optional[ProtocolHostRouter] = None
90
+
91
+ def configure(self, config: Mapping[str, Any]) -> None:
92
+ self.config = dict(config)
93
+ self._enabled = bool(self.config.get('enabled', False))
94
+ if 'auto_start_sidecar' in self.config:
95
+ self.auto_start_sidecar = bool(self.config['auto_start_sidecar'])
96
+ if 'socket_path' in self.config and self.config['socket_path']:
97
+ self.socket_path = str(self.config['socket_path'])
98
+ self.rung._socket_path = self.socket_path
99
+ self.rung.enabled = self._enabled
100
+
101
+ def identity_adapter(self, keystore: Any) -> Optional[ProtocolIdentityAdapter]:
102
+ if keystore is not None:
103
+ return P2PandaIdentityAdapter.from_keystore(keystore)
104
+ return self.identity
105
+
106
+ def mount_ingress(self, host: ProtocolHostRouter) -> None:
107
+ if not self._enabled:
108
+ return
109
+ self._host = host
110
+
111
+ from node.transport import ws
112
+
113
+ # 1. GET /rhio/status
114
+ def _handle_status(conn: Any, req: Any) -> bool:
115
+ body_dict = {
116
+ 'status': 'online' if self._running else 'configured',
117
+ 'name': self.name,
118
+ 'version': self.version,
119
+ 'network_id': self.config.get('network_id', 'rhio-default-network-1'),
120
+ 'nats_endpoint': self.config.get('nats_endpoint', 'localhost:4222'),
121
+ 's3_endpoint': self.config.get('s3_endpoint', 'http://localhost:9000'),
122
+ 'blobs_count': self.store.count_blobs(),
123
+ }
124
+ body = json.dumps(body_dict, indent=2).encode('utf-8')
125
+ ws.http_response(conn, "200 OK", body, "application/json")
126
+ return True
127
+
128
+ # 2. GET & POST /rhio/blobs
129
+ def _handle_blobs(conn: Any, req: Any) -> bool:
130
+ method = getattr(req, 'method', 'GET').upper()
131
+ uri = getattr(req, 'path', '')
132
+ parsed = urllib.parse.urlparse(uri)
133
+
134
+ if method == 'GET':
135
+ qparams = urllib.parse.parse_qs(parsed.query)
136
+ blob_hash = qparams.get('hash', [None])[0]
137
+ target_route = parsed.path
138
+ if not blob_hash and target_route.startswith('/rhio/blobs/'):
139
+ tail = target_route[len('/rhio/blobs/'):].strip('/')
140
+ if tail:
141
+ blob_hash = tail
142
+
143
+ if blob_hash:
144
+ blob = self.store.get_blob(blob_hash)
145
+ if blob is None and self.blob_store is not None and hasattr(self.blob_store, 'get'):
146
+ try:
147
+ blob = self.blob_store.get(blob_hash)
148
+ except Exception:
149
+ blob = None
150
+ if blob is None:
151
+ ws.http_response(conn, "404 Not Found", b'{"error":"not_found"}', "application/json")
152
+ return True
153
+ ws.http_response(conn, "200 OK", blob, "application/octet-stream")
154
+ return True
155
+ else:
156
+ blobs = self.store.list_blobs(limit=50)
157
+ body = json.dumps({'blobs': blobs, 'total': self.store.count_blobs()}).encode('utf-8')
158
+ ws.http_response(conn, "200 OK", body, "application/json")
159
+ return True
160
+
161
+ elif method == 'POST':
162
+ try:
163
+ payload = ws.read_body(conn, req)
164
+ except Exception as e:
165
+ ws.http_response(conn, "400 Bad Request", json.dumps({'error': str(e)}).encode('utf-8'))
166
+ return True
167
+
168
+ headers = getattr(req, 'headers', {})
169
+ content_type = headers.get(b'content-type', b'').decode('utf-8', errors='ignore').lower()
170
+
171
+ # If JSON payload with data_b64
172
+ if 'json' in content_type:
173
+ try:
174
+ obj = json.loads(payload.decode('utf-8'))
175
+ blob_bytes = base64.b64decode(obj['data_b64'])
176
+ except Exception as e:
177
+ ws.http_response(conn, "400 Bad Request", json.dumps({'error': str(e)}).encode('utf-8'))
178
+ return True
179
+ else:
180
+ blob_bytes = payload
181
+
182
+ blob_hash, tree_root, size = self.store.store_blob(blob_bytes)
183
+ if self.blob_store is not None and hasattr(self.blob_store, 'put'):
184
+ try:
185
+ self.blob_store.put(blob_bytes)
186
+ except Exception:
187
+ pass
188
+ self._messages_in += 1
189
+ resp_data = {
190
+ 'hash': blob_hash,
191
+ 'tree_root': tree_root,
192
+ 'size': size,
193
+ }
194
+ ws.http_response(conn, "201 Created", json.dumps(resp_data).encode('utf-8'), "application/json")
195
+ return True
196
+
197
+ return False
198
+
199
+ # 3. GET /rhio/sync
200
+ def _handle_sync(conn: Any, req: Any) -> bool:
201
+ body = json.dumps({
202
+ 'network_id': self.config.get('network_id', 'rhio-default-network-1'),
203
+ 'synced': True,
204
+ 'blobs_stored': self.store.count_blobs(),
205
+ }).encode('utf-8')
206
+ ws.http_response(conn, "200 OK", body, "application/json")
207
+ return True
208
+
209
+ host.mount_route('/rhio/status', _handle_status, methods=('GET',), owner=self.name)
210
+ host.mount_route('/rhio/blobs', _handle_blobs, methods=('GET', 'POST'), owner=self.name)
211
+ host.mount_route('/rhio/sync', _handle_sync, methods=('GET',), owner=self.name)
212
+
213
+ def transport_rung(self, context: Any) -> Optional[Any]:
214
+ if not self._enabled:
215
+ return None
216
+ return self.rung
217
+
218
+ def compartments(self) -> tuple[type[Any], ...]:
219
+ return ()
220
+
221
+ def start(self) -> None:
222
+ if not self._enabled:
223
+ return
224
+ if self.auto_start_sidecar and self._daemon is None and self.node_dir:
225
+ from sidecar.fleetmesh_rhio import RhioSidecarClient, RhioSidecarDaemon
226
+ self._daemon = RhioSidecarDaemon(
227
+ node_dir=self.node_dir,
228
+ uds_path=self.socket_path,
229
+ network_id=str(self.config.get('network_id', 'rhio-default-network-1')),
230
+ nats_endpoint=str(self.config.get('nats_endpoint', 'localhost:4222')),
231
+ s3_endpoint=str(self.config.get('s3_endpoint', 'http://localhost:9000')),
232
+ )
233
+ self._daemon.start()
234
+ client = RhioSidecarClient(self._daemon.uds_path)
235
+ client.connect()
236
+ self.rung._client = client
237
+ self._running = True
238
+
239
+ def stop(self) -> None:
240
+ self._running = False
241
+ if self._daemon is not None:
242
+ if self.rung._client is not None:
243
+ try:
244
+ self.rung._client.close()
245
+ except Exception:
246
+ pass
247
+ self.rung._client = None
248
+ self._daemon.stop()
249
+ self._daemon = None
250
+ if self.store is not None:
251
+ self.store.close()
252
+
253
+ def health(self) -> ProtocolHealth:
254
+ if not self._enabled:
255
+ return ProtocolHealth(
256
+ healthy=True,
257
+ status="disabled",
258
+ connections_active=0,
259
+ messages_in=0,
260
+ messages_out=0,
261
+ errors=(),
262
+ )
263
+ return ProtocolHealth(
264
+ healthy=True,
265
+ status="serving" if self._running else "configured",
266
+ connections_active=0,
267
+ messages_in=self._messages_in,
268
+ messages_out=self._messages_out,
269
+ errors=(),
270
+ )
271
+
272
+ def descriptor_capabilities(self) -> tuple[tuple[str, str], ...]:
273
+ if not self._enabled:
274
+ return ()
275
+ return (('c', 'rhio'), ('c', 'nats-jetstream'), ('c', 'bao'))
@@ -0,0 +1,136 @@
1
+ """Rhio cluster transport ladder rung for TransportRouter.
2
+
3
+ Integrates Rhio NATS JetStream and cluster blob replication into the transport ladder:
4
+ - Checks sidecar availability over local Unix domain socket (rhio.sock).
5
+ - Resolves peer Rhio endpoints from node descriptors (scheme 'rhio').
6
+ - Delivers messages to remote cluster peers via NATS JetStream pub/sub.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import logging
11
+ import os
12
+ from typing import Any, Callable, Mapping, Optional
13
+
14
+ from node.transport.const import TransportError
15
+ from node.transport.ladder import FaultLog
16
+ from node.transport.rung import Rung, Verdict, endpoints_of
17
+ from sidecar.fleetmesh_rhio import RhioSidecarClient
18
+
19
+ log = logging.getLogger('rhio_rung')
20
+
21
+ __all__ = [
22
+ 'RhioRung',
23
+ ]
24
+
25
+
26
+ class RhioRung(Rung):
27
+ """Transport rung delegating outbound deliveries to the Rhio cluster sidecar daemon."""
28
+
29
+ name: str = 'rhio'
30
+ rank: int = 12 # Cluster sync tier (below FIPS 5, above P2Panda 15)
31
+
32
+ def __init__(
33
+ self,
34
+ router: Optional[Any] = None,
35
+ sidecar_client: Optional[RhioSidecarClient] = None,
36
+ socket_path: Optional[str] = None,
37
+ enabled: bool = True,
38
+ clock: Optional[Callable[[], int]] = None,
39
+ ):
40
+ self.router = router
41
+ self._client = sidecar_client
42
+ self._socket_path = socket_path
43
+ self.enabled = enabled
44
+ clk = clock or (getattr(router, 'clock', None) if router else None)
45
+ self._faults = FaultLog(self.name, clock=clk)
46
+
47
+ @property
48
+ def faults(self) -> FaultLog:
49
+ return self._faults
50
+
51
+ def fault_log(self) -> Optional[FaultLog]:
52
+ return self._faults
53
+
54
+ def _get_client(self) -> Optional[RhioSidecarClient]:
55
+ if self._client is not None:
56
+ return self._client
57
+ sock_path = self._socket_path
58
+ if sock_path is None and self.router is not None:
59
+ node_dir = getattr(self.router, 'dir', None) or getattr(self.router, 'node_dir', None)
60
+ if node_dir:
61
+ sock_path = os.path.join(node_dir, 'rhio.sock')
62
+ if sock_path and os.path.exists(sock_path):
63
+ self._client = RhioSidecarClient(sock_path)
64
+ return self._client
65
+
66
+ def available(self) -> bool:
67
+ """Returns True if the Rhio sidecar socket is reachable and healthy."""
68
+ if not self.enabled:
69
+ return False
70
+ client = self._get_client()
71
+ if client is None:
72
+ return False
73
+ try:
74
+ st = client.status()
75
+ return bool(st.get('ok'))
76
+ except Exception:
77
+ return False
78
+
79
+ def target_of(self, desc: Optional[Mapping[str, Any]], theirs: Mapping[str, int]) -> Optional[str]:
80
+ """Extract Rhio endpoint target from descriptor."""
81
+ if desc is None:
82
+ return None
83
+ _, where = endpoints_of(desc)
84
+ if 'rhio' in where:
85
+ return where['rhio']
86
+
87
+ # Check raw tags
88
+ for t in desc.get('tags', []):
89
+ if len(t) > 2 and t[0] in ('endpoint', 'address') and t[1] == 'rhio':
90
+ return str(t[2])
91
+ if len(t) > 1 and t[0] == 'c' and t[1] == 'rhio':
92
+ return ''
93
+ if 'rhio' in theirs:
94
+ return ''
95
+ return None
96
+
97
+ def deliver(
98
+ self,
99
+ message: Mapping[str, Any],
100
+ to_pubkey: str,
101
+ target: str,
102
+ anonymous: bool = False,
103
+ ) -> Verdict:
104
+ """Attempt delivery of message to target Rhio endpoint via JetStream."""
105
+ if not self.enabled:
106
+ raise TransportError("rhio rung is disabled")
107
+
108
+ client = self._get_client()
109
+ if client is None:
110
+ raise TransportError("rhio sidecar client unavailable")
111
+
112
+ subject = f"fleetmesh.{to_pubkey[:16]}" if to_pubkey else "fleetmesh.broadcast"
113
+ try:
114
+ res = client.publish(subject, dict(message))
115
+ if not res.get("ok"):
116
+ raise TransportError(f"rhio publish failed: {res.get('error', 'unknown error')}")
117
+ msg_id = res.get("id", "")
118
+ return {self.name: (True, f"published to {subject} id={msg_id[:16]} via JetStream")}
119
+ except Exception as e:
120
+ self._faults.record(f"deliver failed: {e}", ident=to_pubkey[:16] if to_pubkey else None)
121
+ raise TransportError(f"rhio delivery failed: {e}") from e
122
+
123
+ def health(self) -> Optional[Mapping[str, Any]]:
124
+ client = self._get_client()
125
+ st = {}
126
+ if client:
127
+ try:
128
+ st = dict(client.status())
129
+ except Exception:
130
+ pass
131
+ return {
132
+ 'available': self.available(),
133
+ 'sidecar_connected': bool(st.get('connected', False)),
134
+ 'messages_count': st.get('messages_count', 0),
135
+ 'blobs_count': st.get('blobs_count', 0),
136
+ }