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,225 @@
1
+ """WireProtocol: base class for compartments that speak a wire protocol.
2
+
3
+ A compartment that speaks one or more PIURIs declares what it handles
4
+ (HANDLERS) and what it emits (OUTBOUND).
5
+ The base class verifies the declarations at class definition time and
6
+ binds a WireClient (`self.wire`) to manage outbound sends, requests, replies,
7
+ and correlation.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Callable, Mapping, Optional
12
+
13
+ from fleetmesh_core.wire import schema_files
14
+
15
+ from .base import Compartment
16
+ from .handler import Handler
17
+ from .problem import Problem, Problems, TypeMismatchProblem
18
+
19
+
20
+ class Outbound:
21
+ """An outbound message kind declared by a WireProtocol.
22
+
23
+ States what message kind this compartment emits over the wire, what
24
+ reply kinds it expects in return (if an RPC request), and what documentation
25
+ explains its role.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ kind: str,
31
+ reply: Optional[str | tuple[str, ...]] = None,
32
+ reads: tuple[str, ...] = (),
33
+ writes: tuple[str, ...] = (),
34
+ doc: str = '',
35
+ ) -> None:
36
+ self.kind: str = str(kind)
37
+ if isinstance(reply, str):
38
+ self.reply: tuple[str, ...] = (reply,)
39
+ elif reply is None:
40
+ self.reply = ()
41
+ else:
42
+ self.reply = tuple(reply)
43
+ self.reads: tuple[str, ...] = tuple(reads)
44
+ self.writes: tuple[str, ...] = tuple(writes)
45
+ self.doc: str = str(doc)
46
+
47
+ def __repr__(self) -> str:
48
+ rep = f", reply={self.reply!r}" if self.reply else ""
49
+ return f"Outbound({self.kind!r}{rep})"
50
+
51
+
52
+ class WireClient:
53
+ """The client adapter bound to self.wire on a WireProtocol compartment."""
54
+
55
+ def __init__(self, protocol: WireProtocol) -> None:
56
+ self._protocol: WireProtocol = protocol
57
+
58
+ @property
59
+ def transport(self) -> Any:
60
+ return getattr(self._protocol, 'transport', None)
61
+
62
+ def kind_of(self, message: Mapping[str, Any]) -> Optional[str]:
63
+ """Extract the message kind relative to this protocol's declared PIURIs."""
64
+ t = str(message.get('type', ''))
65
+ for piuri in self._protocol.piuris:
66
+ if t.startswith(piuri + '/'):
67
+ return t[len(piuri) + 1:]
68
+ return None
69
+
70
+ def send(
71
+ self,
72
+ to_pubkey: str,
73
+ message: Mapping[str, Any],
74
+ *,
75
+ anonymous: bool = False,
76
+ stored: bool = False,
77
+ ) -> Any:
78
+ """Dispatch one message to to_pubkey over the transport ladder."""
79
+ tr = self.transport
80
+ if tr is None:
81
+ raise RuntimeError(f"{self._protocol.name}: no transport to send {message.get('type')}")
82
+ try:
83
+ return tr.send(message, to_pubkey, anonymous=anonymous, stored=stored)
84
+ except TypeError:
85
+ try:
86
+ return tr.send(message, to_pubkey, stored=stored)
87
+ except TypeError:
88
+ return tr.send(message, to_pubkey)
89
+
90
+ def request(
91
+ self,
92
+ to_pubkey: str,
93
+ message: Mapping[str, Any],
94
+ *,
95
+ timeout: float = 30.0,
96
+ anonymous: bool = False,
97
+ ) -> Optional[Any]:
98
+ """Dispatch a request message and await a correlated reply."""
99
+ tr = self.transport
100
+ if tr is None:
101
+ raise RuntimeError(f"{self._protocol.name}: no transport to request {message.get('type')}")
102
+ try:
103
+ return tr.request(message, to_pubkey, timeout=timeout, anonymous=anonymous)
104
+ except TypeError:
105
+ try:
106
+ return tr.request(message, to_pubkey, timeout=timeout)
107
+ except TypeError:
108
+ return tr.request(message, to_pubkey)
109
+
110
+ def reply(
111
+ self,
112
+ inbound: Any,
113
+ message: Any,
114
+ *,
115
+ sender: Optional[str] = None,
116
+ anonymous: bool = False,
117
+ ) -> Any:
118
+ """Reply in-band to an inbound message, carrying forward its thread id."""
119
+ m = getattr(inbound, 'message', inbound)
120
+ to_peer = sender or getattr(inbound, 'sender', None)
121
+ if to_peer is None and isinstance(m, dict):
122
+ to_peer = m.get('from')
123
+ if to_peer is None:
124
+ raise ValueError(f"{self._protocol.name}: cannot reply to inbound without sender")
125
+ thid = m.get('thid') or m.get('id') if isinstance(m, dict) else None
126
+ if thid:
127
+ message['thid'] = thid
128
+ return self.send(str(to_peer), message, anonymous=anonymous)
129
+
130
+
131
+ class WireProtocol(Compartment, abstract=True):
132
+ """A compartment that speaks one or more DIDComm wire protocols (PIURIs)."""
133
+
134
+ piuris: tuple[str, ...] = ()
135
+ HANDLERS: tuple[Handler, ...] = ()
136
+ OUTBOUND: tuple[Outbound, ...] = ()
137
+
138
+ @classmethod
139
+ def declaration_problems(cls) -> list[str]:
140
+ out = Problems(super().declaration_problems())
141
+ if cls.abstract:
142
+ return out
143
+
144
+ # 1. piuris validation
145
+ piuris = getattr(cls, 'piuris', ())
146
+ if not piuris:
147
+ out.append(Problem(
148
+ f"{cls.__name__} must declare non-empty piuris",
149
+ surface='PIURIS',
150
+ code='missing_piuris',
151
+ target='piuris',
152
+ ))
153
+ else:
154
+ for p in piuris:
155
+ if not isinstance(p, str) or not p.startswith('https://'):
156
+ out.append(Problem(
157
+ f"piuri {p!r} must be an https:// URI",
158
+ surface='PIURIS',
159
+ code='invalid_piuri',
160
+ target='piuris',
161
+ ))
162
+
163
+ # 2. HANDLERS validation
164
+ handlers = getattr(cls, 'HANDLERS', ())
165
+ if not handlers:
166
+ out.append(Problem(
167
+ f"{cls.__name__} must declare HANDLERS",
168
+ surface='HANDLERS',
169
+ code='missing_handlers',
170
+ target='HANDLERS',
171
+ ))
172
+ else:
173
+ for h in handlers:
174
+ if not isinstance(h, Handler):
175
+ out.append(TypeMismatchProblem('HANDLERS', 'Handler', type(h).__name__))
176
+
177
+ # 3. OUTBOUND validation
178
+ outbound = getattr(cls, 'OUTBOUND', ())
179
+ for ob in outbound:
180
+ if not isinstance(ob, Outbound):
181
+ out.append(TypeMismatchProblem('OUTBOUND', 'Outbound', type(ob).__name__))
182
+
183
+ # 4. Schema conformance check against schema/messages.json
184
+ try:
185
+ schema_protocols = schema_files.load_messages().get('protocols', {})
186
+ except Exception:
187
+ schema_protocols = {}
188
+
189
+ known_protocols = [p for p in piuris if p in schema_protocols]
190
+ if known_protocols:
191
+ allowed_msgs = set()
192
+ for p in known_protocols:
193
+ allowed_msgs.update(schema_protocols[p].get('messages', []))
194
+
195
+ for h in handlers:
196
+ if isinstance(h, Handler) and not getattr(h, 'exact', False) and h.kind != '*':
197
+ if h.kind not in allowed_msgs:
198
+ out.append(Problem(
199
+ f"handler {h.kind!r} is not declared in schema for {known_protocols}",
200
+ surface='HANDLERS',
201
+ code='schema_mismatch',
202
+ target=h.kind,
203
+ ))
204
+ for ob in outbound:
205
+ if isinstance(ob, Outbound) and ob.kind not in allowed_msgs:
206
+ out.append(Problem(
207
+ f"outbound message {ob.kind!r} is not declared in schema for {known_protocols}",
208
+ surface='OUTBOUND',
209
+ code='schema_mismatch',
210
+ target=ob.kind,
211
+ ))
212
+
213
+ return out
214
+
215
+ def __init__(
216
+ self,
217
+ node: Any = None,
218
+ clock: Optional[Callable[[], int]] = None,
219
+ cfg: Optional[Any] = None,
220
+ says: Optional[Any] = None,
221
+ db: Optional[Any] = None,
222
+ **kw: Any,
223
+ ) -> None:
224
+ super().__init__(node=node, clock=clock, cfg=cfg, says=says, db=db, **kw)
225
+ self.wire_client: WireClient = WireClient(self)
@@ -0,0 +1,61 @@
1
+ """WORKERS: background worker threads declared on a compartment."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+ from typing import Any, Callable, Optional, Union
6
+
7
+ from .problem import Problem, Problems, ReferenceProblem, TypeMismatchProblem
8
+
9
+ WhenType = Optional[Union[str, Callable[[Any], bool]]]
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class Worker:
14
+ """A background worker thread declared on a compartment class.
15
+
16
+ The base Compartment starts declared workers during `start()` and
17
+ signals them to stop and joins them during `stop()`.
18
+
19
+ Attributes:
20
+ run: The method name on the compartment to run in a thread.
21
+ name: A unique name for the worker/thread (defaults to `run`).
22
+ timeout: How long in seconds to wait for the thread to join on stop().
23
+ daemon: Whether the thread is a daemon thread (defaults to True).
24
+ when: Optional condition to run: attribute name (checked for truthiness)
25
+ or callable receiving the compartment instance.
26
+ """
27
+ run: str
28
+ name: str = ''
29
+ timeout: float = 1.0
30
+ daemon: bool = True
31
+ when: WhenType = None
32
+
33
+ def __post_init__(self) -> None:
34
+ if not self.name:
35
+ object.__setattr__(self, 'name', self.run)
36
+
37
+ def should_run(self, instance: Any) -> bool:
38
+ """True if any `when` condition holds."""
39
+ if self.when is None:
40
+ return True
41
+ if callable(self.when):
42
+ return bool(self.when(instance))
43
+ val = getattr(instance, self.when, None)
44
+ if callable(val):
45
+ return bool(val())
46
+ return bool(val)
47
+
48
+ def problems(self, cls: type[Any]) -> list[str]:
49
+ """Lint this declaration against the target class."""
50
+ out = Problems()
51
+ if not isinstance(self.run, str) or not self.run:
52
+ out.append(TypeMismatchProblem('WORKERS', 'non-empty str', type(self.run).__name__,
53
+ message=f"Worker {self.name!r}: run must be a non-empty string method name"))
54
+ elif not getattr(cls, 'abstract', False) and not hasattr(cls, self.run):
55
+ out.append(ReferenceProblem('WORKERS', f"Worker {self.name!r}", self.run,
56
+ message=f"Worker {self.name!r}: method {self.run!r} does not exist on {cls.__name__}"))
57
+ if self.timeout < 0:
58
+ out.append(Problem(f"Worker {self.name!r}: timeout must be non-negative, got {self.timeout!r}",
59
+ surface='WORKERS', code='negative_timeout', target=self.name))
60
+ return out
61
+
@@ -0,0 +1,43 @@
1
+ """The node's configuration as typed, frozen dataclasses.
2
+
3
+ base Block, Problems, ConfigError, the predicates
4
+ records Endpoint, PeerPull, CoverageClaim, TrustDecision, FaceConfig, PolicyConfig
5
+ compartments one block per compartment (BridgeConfig, DVMConfig, ...)
6
+ node NodeConfig: the whole file
7
+
8
+ node.app.config keeps the file I/O and the dict the compartments still read;
9
+ its DEFAULTS and validate_config are derived from here.
10
+ """
11
+ from .base import Block, ConfigError, Problems
12
+ from .compartments import (AnnounceConfig, BlobReplicationConfig, BlobWireConfig, BlobsConfig, BridgeConfig,
13
+ BridgeEgress, BridgeIngress, COMPARTMENT_BLOCKS, CapacityConfig, STRFRY_MOVED, STRFRY_PLUGIN_REF,
14
+ CashuRail, ComputeConfig, DEFAULT_DVM_KINDS, DEFAULT_EGRESS_KINDS,
15
+ DEFAULT_INGRESS_FILTERS, DEFAULT_INGRESS_KINDS, DEFAULT_MAX_EVENT_BYTES,
16
+ DEFAULT_MAX_FUTURE_SECONDS, DEFAULT_RATE_LIMIT_PER_MIN, DVMConfig, NsiteConfig,
17
+ FetchPricingConfig, LanConfig, MediationConfig, PluginSpec, PluginsConfig, SidecarsConfig, SidecarEntry, SIDECAR_KEYS_MOVED, SIDECAR_MOVED, sidecars_of,
18
+ P2PConfig, PRICING_RULE_KEYS, PROJECT_RELEASE_KEY, PricingRule, default_release_key, SettlementConfig,
19
+ StorageConfig, TRACK_NOT_YET, TransportConfig, UPDATE_POLICIES, UpdatesConfig)
20
+ from .node import DEFAULT_CAPABILITIES, DEFAULT_PROTOCOLS, NodeConfig
21
+ from .records import (CLASSES, COMPLETENESS, CoverageClaim, Endpoint, FaceConfig, HANDOVER, PeerPull,
22
+ PolicyConfig, TIERS, TrustDecision)
23
+
24
+ # A literal list, not built from splats: pyflakes reads __all__ statically and
25
+ # only a literal tells it these re-exports are used.
26
+ __all__ = [
27
+ # base
28
+ 'Block', 'ConfigError', 'Problems',
29
+ # node
30
+ 'NodeConfig', 'DEFAULT_CAPABILITIES', 'DEFAULT_PROTOCOLS',
31
+ # compartments
32
+ 'AnnounceConfig', 'BlobReplicationConfig', 'BlobWireConfig', 'BlobsConfig', 'BridgeConfig', 'BridgeEgress',
33
+ 'BridgeIngress', 'COMPARTMENT_BLOCKS', 'STRFRY_MOVED', 'STRFRY_PLUGIN_REF', 'CapacityConfig', 'CashuRail', 'ComputeConfig',
34
+ 'DEFAULT_DVM_KINDS', 'DEFAULT_EGRESS_KINDS', 'DEFAULT_INGRESS_FILTERS', 'DEFAULT_INGRESS_KINDS',
35
+ 'DEFAULT_MAX_EVENT_BYTES', 'DEFAULT_MAX_FUTURE_SECONDS', 'DEFAULT_RATE_LIMIT_PER_MIN', 'DVMConfig', 'NsiteConfig',
36
+ 'FetchPricingConfig', 'LanConfig', 'MediationConfig', 'SidecarsConfig', 'SidecarEntry', 'SIDECAR_KEYS_MOVED', 'SIDECAR_MOVED', 'sidecars_of',
37
+ 'P2PConfig', 'PluginSpec', 'PluginsConfig', 'PRICING_RULE_KEYS', 'PricingRule',
38
+ 'SettlementConfig', 'StorageConfig', 'TransportConfig', 'UpdatesConfig', 'PROJECT_RELEASE_KEY',
39
+ 'default_release_key', 'UPDATE_POLICIES', 'TRACK_NOT_YET',
40
+ # records
41
+ 'CLASSES', 'COMPLETENESS', 'CoverageClaim', 'Endpoint', 'FaceConfig', 'HANDOVER', 'PeerPull', 'PolicyConfig',
42
+ 'TIERS', 'TrustDecision',
43
+ ]
node/config/base.py ADDED
@@ -0,0 +1,116 @@
1
+ """The pieces every config block is built from.
2
+
3
+ A block is a frozen dataclass with a `from_dict` that reads the operator's
4
+ JSON and a `check` that names every problem in it. Problems are collected,
5
+ not raised one at a time: an operator fixes a file once, not one line per
6
+ restart. The messages are the interface the tests and the operator read, so
7
+ they say which field and what it must be.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import dataclasses
12
+ import json
13
+ from typing import Any, Callable, ClassVar, Mapping, Optional, TypeVar
14
+
15
+ from fleetmesh_core.wire import spelling
16
+
17
+ T = TypeVar('T', bound='Block')
18
+
19
+
20
+ class ConfigError(ValueError):
21
+ """config.json says something the node cannot run on. The message lists
22
+ every problem, because an operator fixes a file once, not one line per
23
+ restart."""
24
+
25
+
26
+ class Problems:
27
+ """Sentences about what is wrong, in file order."""
28
+
29
+ def __init__(self) -> None:
30
+ self.items: list[str] = []
31
+
32
+ def add(self, sentence: str) -> None:
33
+ self.items.append(sentence)
34
+
35
+ def __bool__(self) -> bool:
36
+ return bool(self.items)
37
+
38
+ def __iter__(self):
39
+ return iter(self.items)
40
+
41
+
42
+ def is_hex64(v: Any) -> bool:
43
+ return spelling.is_hex(v, 64)
44
+
45
+
46
+ def is_int(v: Any) -> bool:
47
+ return spelling.is_int(v)
48
+
49
+
50
+ def is_relay_url(v: Any) -> bool:
51
+ return isinstance(v, str) and v.startswith(('ws://', 'wss://'))
52
+
53
+
54
+ def is_http_url(v: Any) -> bool:
55
+ return isinstance(v, str) and v.startswith(('http://', 'https://'))
56
+
57
+
58
+ def all_str(v: Any) -> bool:
59
+ return isinstance(v, list) and all(isinstance(x, str) for x in v)
60
+
61
+
62
+ def all_int(v: Any) -> bool:
63
+ return isinstance(v, list) and all(is_int(x) for x in v)
64
+
65
+
66
+ def opt(raw: Mapping[str, Any], key: str, pred: Callable[[Any], bool], p: Problems, must: str) -> None:
67
+ """When `key` is present, it has to satisfy `pred`; otherwise say `must`."""
68
+ if key in raw and not pred(raw[key]):
69
+ p.add(must)
70
+
71
+
72
+ def positive_int(v: Any) -> bool:
73
+ return is_int(v) and v > 0
74
+
75
+
76
+ def non_negative_int(v: Any) -> bool:
77
+ return is_int(v) and v >= 0
78
+
79
+
80
+ @dataclasses.dataclass(frozen=True)
81
+ class Block:
82
+ """A typed section of config.json.
83
+
84
+ `check(raw, p)` names what is wrong with the raw object; `from_dict(raw)`
85
+ builds the block from a raw object that `check` passed. Subclasses write
86
+ both: the first is the sentence an operator reads, the second the shape
87
+ the compartment reads. `key` is the top-level name the block lives under,
88
+ `label` the prefix its sentences use (defaults to the key).
89
+ """
90
+ key: ClassVar[str] = ''
91
+ label: ClassVar[str] = ''
92
+
93
+ @classmethod
94
+ def check(cls, raw: Any, p: Problems) -> None:
95
+ if not isinstance(raw, Mapping):
96
+ p.add('%s must be an object' % (cls.label or cls.key))
97
+
98
+ @classmethod
99
+ def from_dict(cls: type[T], raw: Any) -> T:
100
+ raise NotImplementedError
101
+
102
+ @classmethod
103
+ def parse(cls: type[T], raw: Any) -> T:
104
+ """check then from_dict, raising ConfigError with every sentence."""
105
+ p = Problems()
106
+ cls.check(raw, p)
107
+ if p:
108
+ raise ConfigError('; '.join(p))
109
+ return cls.from_dict(raw)
110
+
111
+ def to_dict(self) -> Mapping[str, Any]:
112
+ """The JSON shape, tuples made lists, as the file would carry it."""
113
+ return json.loads(json.dumps(dataclasses.asdict(self)))
114
+
115
+ __all__ = ['Block', 'ConfigError', 'Problems', 'is_hex64', 'is_int', 'is_relay_url', 'is_http_url', 'all_str',
116
+ 'all_int', 'opt', 'positive_int', 'non_negative_int', 'Optional']