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
node/app/commands.py ADDED
@@ -0,0 +1,265 @@
1
+ """NODE_COMMANDS: what the node answers over the control socket, declared where it is.
2
+
3
+ The 26 operator-facing commands answered by NodeApp and the control socket:
4
+ each a Command or an Alias under the node namespace. CONTROL is generated
5
+ from them, and the socket dispatches on them.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import collections
10
+
11
+ from ..compartment import Alias, Command
12
+ from ..config.parameter import Parameter
13
+ from ..interfaces import CommandSpec
14
+
15
+ NODE_COMMANDS: tuple[Command, ...] = (
16
+ Command(
17
+ key='version',
18
+ name='version',
19
+ description='the protocol this socket speaks, the schema digest this node declares, and its identity',
20
+ run='cmd_version',
21
+ reply='{protocol, fleetmesh, digest, did, pubkey, class, commands: [name]}',
22
+ ),
23
+ Command(
24
+ key='commands',
25
+ name='commands',
26
+ description='every command this socket answers, with its arguments and reply',
27
+ run='cmd_commands',
28
+ reply='[{name, args: {name: required|optional}, does, reply}]',
29
+ ),
30
+ Command(
31
+ key='status',
32
+ name='status',
33
+ description='what the node would write to status.json now',
34
+ run='status',
35
+ reply='the status.json object',
36
+ ),
37
+ Command(
38
+ key='health',
39
+ name='health',
40
+ description='every error plane: relays, drops, rejections, failures, throttles',
41
+ run='health',
42
+ reply='the health object status.json carries under health',
43
+ ),
44
+ Command(
45
+ key='peers',
46
+ name='peers',
47
+ description='every peer this node holds a grant with',
48
+ run='peer_view',
49
+ reply='[{peer, issued, received, pending, last_evaluated, standing, zeroed_until, pulls, descriptor}]',
50
+ ),
51
+ Command(
52
+ key='grant',
53
+ name='grant',
54
+ description='both grants with a peer, their history, and what waits for the boundary',
55
+ parameters=(
56
+ Parameter(key='peer', name='peer', description='peer pubkey', type=str, default='', required=True),
57
+ ),
58
+ run='cmd_grant',
59
+ reply='{peer, issued, received, issued_history, received_history, pending, seed_applied}',
60
+ ),
61
+ Command(
62
+ key='windows',
63
+ name='windows',
64
+ description='the windows this node priced for a peer, newest last',
65
+ parameters=(
66
+ Parameter(key='peer', name='peer', description='peer pubkey', type=str, default='', required=True),
67
+ Parameter(key='last', name='last', description='limit count', type=int, default=0, required=False),
68
+ ),
69
+ run='cmd_windows',
70
+ reply='[{epoch, closed, severity, measured, claimed, receipts_in, my_usage, my_receipts_sent}]',
71
+ ),
72
+ Command(
73
+ key='subsystems',
74
+ name='subsystems',
75
+ description='every compartment of this node: what it is, and every refusal it recorded',
76
+ run='cmd_subsystems',
77
+ reply='{name: {..., refusals: {total, by_reason, recent}}}',
78
+ ),
79
+ Command(
80
+ key='refusals',
81
+ name='refusals',
82
+ description='every no this node said, across every compartment, newest last',
83
+ parameters=(
84
+ Parameter(key='last', name='last', description='limit count', type=int, default=5, required=False),
85
+ ),
86
+ run='cmd_refusals',
87
+ reply='[{subsystem, at, kind, subject, why, detail}]',
88
+ ),
89
+ Command(
90
+ key='lifecycle',
91
+ name='lifecycle',
92
+ description='where every compartment is in its lifecycle: running, degraded, '
93
+ 'quarantined (breaker open, and until when), failed, retired',
94
+ run='cmd_lifecycle',
95
+ reply='{name: {state, since, failures, trips, until, last_error, started, restarts, '
96
+ 'transitions, declared}}',
97
+ ),
98
+ Command(
99
+ key='resume',
100
+ name='resume',
101
+ description="end a compartment's quarantine now instead of waiting "
102
+ 'out its backoff; a compartment not quarantined is left as it is',
103
+ parameters=(
104
+ Parameter(key='name', name='name', description='compartment name', type=str, default='', required=True),
105
+ ),
106
+ run='cmd_resume',
107
+ reply='{name, state}',
108
+ ),
109
+ Command(
110
+ key='restart',
111
+ name='restart',
112
+ description='stop and start one compartment in place; only one that '
113
+ 'declares Lifecycle(restart=True) - any other is refused',
114
+ parameters=(
115
+ Parameter(key='name', name='name', description='compartment name', type=str, default='', required=True),
116
+ ),
117
+ run='cmd_restart',
118
+ reply='{name, state}',
119
+ ),
120
+ Command(
121
+ key='events',
122
+ name='events',
123
+ description='the most recent bus events still in memory, oldest first; since is a seq to resume from, topics a list to keep',
124
+ parameters=(
125
+ Parameter(key='since', name='since', description='resume sequence', type=int, default=0, required=False),
126
+ Parameter(key='topics', name='topics', description='topics to filter', type=object, default=None, required=False),
127
+ Parameter(key='last', name='last', description='limit count', type=int, default=0, required=False),
128
+ ),
129
+ run='cmd_events',
130
+ reply='{seq, oldest, topics: [name], events: [{seq, ts, topic, data}]}',
131
+ ),
132
+ Command(
133
+ key='subscribe',
134
+ name='subscribe',
135
+ description='hold the connection open and stream one {seq, ts, topic, data} line per bus event as it happens; '
136
+ 'since replays what the ring still holds first; a slow reader gets meta.dropped with a count',
137
+ parameters=(
138
+ Parameter(key='topics', name='topics', description='topics to filter', type=object, default=None, required=False),
139
+ Parameter(key='since', name='since', description='resume sequence', type=int, default=None, required=False),
140
+ ),
141
+ run='cmd_subscribe',
142
+ reply='ack {seq, topics}; then one envelope per line until the reader closes',
143
+ ),
144
+ Alias(
145
+ key='strain',
146
+ target='strain.show',
147
+ description="what this node has survived, by direction, and the reserves it sets; never a finding and never on the wire",
148
+ reply='{kinds, directions, weighted_directions, reserve_multiple, dominant_share, flag_tree}',
149
+ ),
150
+ Alias(
151
+ key='capacity',
152
+ target='capacity.show',
153
+ description="this node's own ceiling: what it declared, the pressure against it, which shed rungs are engaged and the largest window it has carried",
154
+ reply='{declared, total, pressure, binding, rungs, held, since, measured, peak, suggested, note}',
155
+ ),
156
+ Alias(
157
+ key='scheduled',
158
+ target='scheduler.show',
159
+ description='every scheduled pull, when it last ran, and what is holding it back now',
160
+ reply='{pulls, throttled, held, declined_until}',
161
+ ),
162
+ Alias(
163
+ key='trust',
164
+ target='trust.show',
165
+ description="the operator's decisions about peers, and which are still in force: "
166
+ "a decision the ladder has since overridden is carried out and not true",
167
+ reply='{decided, in_force, pending, overridden, waiting}',
168
+ ),
169
+ Alias(
170
+ key='withdraw',
171
+ target='reporter.withdraw',
172
+ description="withdraw a finding this node made about one window, published or not; the operator's call",
173
+ parameters=(
174
+ Parameter(key='peer', name='peer', description='peer pubkey or did', type=str, default='', required=True),
175
+ Parameter(key='window', name='window', description='window number', type=int, default=0, required=True),
176
+ Parameter(key='why', name='why', description='reason for withdrawal', type=str, default=None, required=False),
177
+ ),
178
+ reply='{peer, window, was_published, report, why}',
179
+ ),
180
+ Command(
181
+ key='tick',
182
+ name='tick',
183
+ description='one tick now',
184
+ run='cmd_tick',
185
+ reply='[[what, ...], ...] the tick did',
186
+ ),
187
+ Command(
188
+ key='pull',
189
+ name='pull',
190
+ description='one sync session with a peer now; refused when the grant does not cover one unless force',
191
+ parameters=(
192
+ Parameter(key='peer', name='peer', description='peer pubkey', type=str, default='', required=True),
193
+ Parameter(key='shard', name='shard', description='shard name', type=str, default='', required=True),
194
+ Parameter(key='filter', name='filter', description='filter object', type=object, default=None, required=True),
195
+ Parameter(key='force', name='force', description='force pull', type=bool, default=False, required=False),
196
+ Parameter(key='timeout', name='timeout', description='timeout in seconds', type=float, default=60.0, required=False),
197
+ ),
198
+ run='cmd_pull',
199
+ reply='{status, counters, findings, ...} the session result',
200
+ ),
201
+ Command(
202
+ key='terminate',
203
+ name='terminate',
204
+ description='end a peering: zero both grants here and tell the peer',
205
+ parameters=(
206
+ Parameter(key='peer', name='peer', description='peer pubkey', type=str, default='', required=True),
207
+ Parameter(key='reason', name='reason', description='reason for termination', type=str, default='operator', required=False),
208
+ ),
209
+ run='cmd_terminate',
210
+ reply='{peer, reason, told: {relay: [accepted, note]}, pulls_removed}',
211
+ ),
212
+ Command(
213
+ key='standing',
214
+ name='standing',
215
+ description='ask a peer what it makes of a third node, over standing/1.0',
216
+ parameters=(
217
+ Parameter(key='peer', name='peer', description='peer pubkey', type=str, default='', required=True),
218
+ Parameter(key='target', name='target', description='target pubkey', type=str, default='', required=True),
219
+ ),
220
+ run='cmd_standing',
221
+ reply='{answer: tell|decline, body}',
222
+ ),
223
+ Command(
224
+ key='reports',
225
+ name='reports',
226
+ description="the kind 30802s about a node on this node's relays, weighed by this node",
227
+ parameters=(
228
+ Parameter(key='target', name='target', description='target pubkey', type=str, default='', required=True),
229
+ ),
230
+ run='cmd_reports',
231
+ reply='{view, reports: [{reporter, severity, admissible, reasons, weight, id, created_at}]}',
232
+ ),
233
+ Command(
234
+ key='republish',
235
+ name='republish',
236
+ description='send the descriptor to the relays again',
237
+ run='cmd_republish',
238
+ reply='{descriptor, relays: {url: [accepted, note]}}',
239
+ ),
240
+ Command(
241
+ key='retire',
242
+ name='retire',
243
+ description='leave the mesh in order: close every asserted range, hand the shards to every '
244
+ 'granted peer, withdraw the claims, and stop. Not a restart',
245
+ parameters=(
246
+ Parameter(key='reason', name='reason', description='reason for retirement', type=str, default='operator', required=False),
247
+ Parameter(key='revoke', name='revoke', description='revoke identity event', type=bool, default=False, required=False),
248
+ Parameter(key='lapses_in', name='lapses_in', description='seconds until claim lapses', type=int, default=86400, required=False),
249
+ ),
250
+ run='cmd_retire',
251
+ reply='{reason, at, did, peers, handed, withdrawn, revoked}',
252
+ ),
253
+ )
254
+
255
+ NODE_COMMANDS_BY_KEY: dict[str, Command] = {c.key: c for c in NODE_COMMANDS}
256
+
257
+ CONTROL: collections.OrderedDict[str, CommandSpec] = collections.OrderedDict([
258
+ (c.key, c.to_spec()) for c in NODE_COMMANDS
259
+ ])
260
+
261
+ __all__ = [
262
+ 'CONTROL',
263
+ 'NODE_COMMANDS',
264
+ 'NODE_COMMANDS_BY_KEY',
265
+ ]
node/app/config.py ADDED
@@ -0,0 +1,131 @@
1
+ """The node's configuration: the control-socket table, the defaults, and the
2
+ file that carries them.
3
+
4
+ Separate from the node so `meshnode init`, `peer` and `trust` can read and
5
+ write config.json without constructing a NodeApp.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ from typing import Any, Mapping, Sequence
12
+
13
+ from fleetmesh_core.identity.did import node_of_did
14
+ from ..config import ConfigError, NodeConfig, Problems
15
+ from .commands import CONTROL, NODE_COMMANDS, NODE_COMMANDS_BY_KEY
16
+
17
+ __all__ = [
18
+ 'CONTROL',
19
+ 'ConfigError',
20
+ 'DEFAULTS',
21
+ 'NODE_COMMANDS',
22
+ 'NODE_COMMANDS_BY_KEY',
23
+ 'NodeConfig',
24
+ 'Problems',
25
+ 'config_from_manifests',
26
+ 'defaults',
27
+ 'load_config',
28
+ 'save_config',
29
+ 'typed_config',
30
+ 'validate_config',
31
+ ]
32
+
33
+ # The file's defaults are the file shape of NodeConfig(): one source. The
34
+ # comments that used to sit beside each key are on the dataclass fields in
35
+ # node/config/. Only the node's own keys are written; a compartment's block
36
+ # is absent until the operator asks for it, because presence has meaning.
37
+ DEFAULTS: Mapping[str, Any] = NodeConfig.defaults_dict()
38
+
39
+
40
+ def defaults() -> Mapping[str, Any]:
41
+ """A fresh copy, nested values included.
42
+
43
+ DEFAULTS holds dicts and lists, and dict(DEFAULTS) copies only the top
44
+ level: two nodes built from it shared one `face` dict, so the first to
45
+ record its port set it for every node after. Anything that hands out
46
+ defaults hands out copies.
47
+ """
48
+ return json.loads(json.dumps(DEFAULTS))
49
+
50
+
51
+ def validate_config(cfg: Any) -> list[str]:
52
+ """Every problem with a config, as a list of sentences; empty if none.
53
+
54
+ The file is the interface, so it is checked the way a message is: at the
55
+ door, with reasons. NodeConfig.check does the checking; this is the list
56
+ the CLI and the tests read.
57
+ """
58
+ p = Problems()
59
+ NodeConfig.check(cfg, p)
60
+ return list(p)
61
+
62
+
63
+ def typed_config(cfg: Mapping[str, Any]) -> NodeConfig:
64
+ """The typed view of a config that validate_config passed."""
65
+ return NodeConfig.from_dict(cfg)
66
+
67
+
68
+ def load_config(directory: str) -> Mapping[str, Any]:
69
+ with open(os.path.join(directory, 'config.json'), encoding='utf-8') as f:
70
+ cfg = json.load(f)
71
+ out = dict(defaults())
72
+ if not isinstance(cfg, dict):
73
+ raise ConfigError('config.json is not an object')
74
+ out.update(cfg)
75
+ problems = validate_config(out)
76
+ if problems:
77
+ raise ConfigError('config.json: ' + '; '.join(problems))
78
+ return out
79
+
80
+
81
+ def _blob_storage_cap(cfg: Mapping[str, Any]) -> int | None:
82
+ """Byte cap for the Blossom blob store, or None for unbounded.
83
+
84
+ FLEETMESH_BLOB_MAX_BYTES wins, then config ``blobs.max_storage_bytes``, then
85
+ the MeshNode manifest's ``storage.maxBytes`` (config_from_manifests copies
86
+ it to ``cfg['storage']``). Before this nothing passed a cap, so the LRU
87
+ eviction in BlobStore.put never ran and a node's disk filled without bound.
88
+ 0 or a negative value means unbounded.
89
+ """
90
+ raw: Any = os.environ.get('FLEETMESH_BLOB_MAX_BYTES')
91
+ if raw in (None, ''):
92
+ raw = (cfg.get('blobs') or {}).get('max_storage_bytes')
93
+ if raw in (None, ''):
94
+ raw = (cfg.get('storage') or {}).get('maxBytes')
95
+ try:
96
+ cap = int(raw) if raw not in (None, '') else 0
97
+ except (TypeError, ValueError):
98
+ raise ConfigError(f'blob storage cap is not an integer: {raw!r}')
99
+ return cap if cap > 0 else None
100
+
101
+
102
+ def save_config(directory: str, cfg: Mapping[str, Any]) -> None:
103
+ os.makedirs(directory, mode=0o700, exist_ok=True)
104
+ path = os.path.join(directory, 'config.json')
105
+ tmp = path + '.tmp'
106
+ with open(tmp, 'w', encoding='utf-8') as f:
107
+ json.dump(cfg, f, indent=2, sort_keys=True)
108
+ f.write('\n')
109
+ os.replace(tmp, path)
110
+
111
+
112
+ def config_from_manifests(meshnode: Mapping[str, Any], peerings: Sequence[Mapping[str, Any]] = ()) -> Mapping[str, Any]:
113
+ """A config from a MeshNode manifest and any Peering manifests (parsed YAML or JSON)."""
114
+ spec = meshnode['spec']
115
+ cfg = dict(defaults())
116
+ cfg['class'] = spec['class']
117
+ cfg['operator'] = spec['operator']['pubkey']
118
+ cfg['endpoints'] = [{'transport': e['transport'], 'address': e.get('address') or e.get('ticket'),
119
+ 'priority': e.get('priority', 50)} for e in spec.get('endpoints', [])]
120
+ caps = spec.get('capabilities', [])
121
+ cfg['capabilities'] = [c for c in caps if not c.startswith('https://')]
122
+ cfg['protocols'] = sorted(set(DEFAULTS['protocols']) | {c for c in caps if c.startswith('https://')})
123
+ cfg['storage'] = spec.get('storage', {})
124
+ cfg['peers'] = []
125
+ for pm in peerings:
126
+ ps = pm['spec']
127
+ cfg['peers'].append({'pubkey': node_of_did(ps['peer']), 'shard': pm['metadata']['name'],
128
+ 'filter': ps['ingress']['filter'], 'pull_every': 600,
129
+ 'window': ps.get('budget', {}).get('window')})
130
+ return cfg
131
+