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/config/node.py ADDED
@@ -0,0 +1,318 @@
1
+ """NodeConfig: the whole of config.json as one frozen dataclass.
2
+
3
+ `DEFAULTS` in node.app.config is generated from `NodeConfig()` so the file's
4
+ defaults have one source. `check` names every problem; `from_dict` builds
5
+ the typed view. The dict the compartments still read is kept beside it
6
+ (see node.app.config.load_config) until every compartment reads its block
7
+ through `Registry.assemble`.
8
+
9
+ Unknown top-level keys are refused by name: a misspelt block (`capacitty`)
10
+ is exactly the silent failure this exists to catch. Out-of-tree compartments
11
+ put theirs under `extensions`, which is carried as-is and checked by the
12
+ compartment that owns it. Keys beginning with `_` are the node's own
13
+ runtime notes and never saved.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import dataclasses
18
+ from typing import Any, ClassVar, Mapping, MutableMapping, Optional, TypedDict, cast
19
+
20
+ from .base import Block, Problems, all_str, is_hex64, is_int, is_relay_url, positive_int
21
+ from .compartments import (AnnounceConfig, BlobReplicationConfig, BlobWireConfig, BlobsConfig, BridgeConfig,
22
+ CapacityConfig, ComputeConfig, DVMConfig, FetchPricingConfig, NsiteConfig, GossipConfig, LanConfig,
23
+ MediationConfig, P2PConfig, PluginsConfig, SettlementConfig, SidecarsConfig, SIDECAR_KEYS_MOVED, sidecars_of, StorageConfig,
24
+ TransportConfig, UpdatesConfig, HostMultiplexerConfig, NostrProtocolConfig, ProtocolSuitesConfig)
25
+ from .records import (CLASSES, HANDOVER, CoverageClaim, Endpoint, FaceConfig, PeerPull, PolicyConfig,
26
+ TrustDecision)
27
+
28
+ DEFAULT_PROTOCOLS: tuple[str, ...] = ('https://fleetmesh.org/sync/1.0',)
29
+ DEFAULT_CAPABILITIES: tuple[str, ...] = ('relay', 'negentropy')
30
+
31
+
32
+ class NodeConfigDict(TypedDict, total=False):
33
+ cls_: str
34
+ name: str
35
+ relays: list[str]
36
+ face: Mapping[str, Any]
37
+ endpoints: list[Mapping[str, Any]]
38
+ protocols: list[str]
39
+ capabilities: list[str]
40
+ operator: Optional[str]
41
+ pow_bits: Optional[int]
42
+ policy: Mapping[str, Any]
43
+ coverage: list[Mapping[str, Any]]
44
+ handover: str
45
+ retention: dict[int, int]
46
+ trust: Mapping[str, Any]
47
+ peers: list[Mapping[str, Any]]
48
+ tick_seconds: int
49
+ heartbeat: bool
50
+ projection: bool
51
+ telemetry: bool
52
+ fetch_pricing: Mapping[str, Any]
53
+ bridge: Optional[Mapping[str, Any]]
54
+ announce: Mapping[str, Any]
55
+ lan: Mapping[str, Any]
56
+ p2p: Optional[Mapping[str, Any]]
57
+ gossip: Optional[Mapping[str, Any]]
58
+ linked: Mapping[str, Any]
59
+ capacity: Mapping[str, Any]
60
+ compute: Optional[Mapping[str, Any]]
61
+ dvm: Optional[Mapping[str, Any]]
62
+ settlement: Mapping[str, Any]
63
+ mediation: Mapping[str, Any]
64
+ transport: Mapping[str, Any]
65
+ blob_wire: Mapping[str, Any]
66
+ blob_replication: Mapping[str, Any]
67
+ storage: Mapping[str, Any]
68
+ blobs: Mapping[str, Any]
69
+ sidecars: Mapping[str, Any]
70
+ plugins: Mapping[str, Any]
71
+ updates: Optional[Mapping[str, Any]]
72
+ protocol_suites: Optional[Mapping[str, Any]]
73
+ extensions: Mapping[str, Any]
74
+
75
+
76
+ @dataclasses.dataclass(frozen=True)
77
+ class NodeConfig(Block):
78
+ # --- the node -----------------------------------------------------------
79
+ cls_: str = 'edge' # `class` in the file
80
+ name: str = 'meshnode'
81
+ relays: tuple[str, ...] = () # the control plane's relays; empty allowed
82
+ face: FaceConfig = FaceConfig()
83
+ endpoints: tuple[Endpoint, ...] = ()
84
+ protocols: tuple[str, ...] = DEFAULT_PROTOCOLS
85
+ capabilities: tuple[str, ...] = DEFAULT_CAPABILITIES
86
+ operator: Optional[str] = None
87
+ pow_bits: Optional[int] = None # None: the constants' default
88
+ policy: PolicyConfig = PolicyConfig()
89
+ coverage: tuple[CoverageClaim, ...] = ()
90
+ handover: str = 'offer'
91
+ retention: dict[int, int] = dataclasses.field(default_factory=dict) # {kind: seconds}; empty keeps everything
92
+ trust: dict[str, TrustDecision] = dataclasses.field(default_factory=dict)
93
+ peers: tuple[PeerPull, ...] = ()
94
+ tick_seconds: int = 30
95
+ heartbeat: bool = True
96
+ projection: bool = True
97
+ telemetry: bool = False # kind 21802, opt-in: coarse counters about this node, no per-peer attribution
98
+ # --- the compartments ------------------------------------------------------
99
+ fetch_pricing: FetchPricingConfig = FetchPricingConfig()
100
+ bridge: Optional[BridgeConfig] = None # None: the block is absent (bridge on with defaults)
101
+ announce: AnnounceConfig = AnnounceConfig()
102
+ lan: LanConfig = LanConfig()
103
+ p2p: Optional[P2PConfig] = None # None: absent; the compartment is off
104
+ gossip: Optional[GossipConfig] = None # None: absent; the compartment is off
105
+ capacity: CapacityConfig = CapacityConfig()
106
+ compute: Optional[ComputeConfig] = None
107
+ dvm: Optional[DVMConfig] = None
108
+ nsite: Optional[NsiteConfig] = None
109
+ settlement: SettlementConfig = SettlementConfig()
110
+ mediation: MediationConfig = MediationConfig()
111
+ transport: TransportConfig = TransportConfig()
112
+ blob_wire: BlobWireConfig = BlobWireConfig()
113
+ blob_replication: BlobReplicationConfig = BlobReplicationConfig()
114
+ storage: StorageConfig = StorageConfig()
115
+ blobs: BlobsConfig = BlobsConfig()
116
+ sidecars: SidecarsConfig = SidecarsConfig()
117
+ plugins: PluginsConfig = PluginsConfig()
118
+ updates: Optional[UpdatesConfig] = None # optional compartment; presence is the switch
119
+ protocol_suites: ProtocolSuitesConfig = ProtocolSuitesConfig()
120
+ extensions: Mapping[str, Any] = dataclasses.field(default_factory=dict)
121
+
122
+ # Keys the file carries that are written by default. Absent-by-default
123
+ # blocks (bridge, p2p, compute, dvm, and every other compartment's) are
124
+ # not written: their presence has meaning (`enabled_by`), and a default
125
+ # file should ask for nothing. `defaults_dict` is derived from this list
126
+ # and the dataclass defaults, so the two cannot drift.
127
+ WRITTEN_BY_DEFAULT: ClassVar[tuple[str, ...]] = ('class', 'relays', 'face', 'endpoints', 'protocols', 'capabilities', 'operator',
128
+ 'pow_bits', 'policy', 'coverage', 'handover', 'retention', 'trust', 'peers',
129
+ 'tick_seconds', 'heartbeat', 'projection', 'fetch_pricing')
130
+
131
+ # File key -> dataclass field, where they differ.
132
+ FIELD_OF: ClassVar[dict[str, str]] = {'class': 'cls_'}
133
+
134
+ # Every key the file may carry, with the block that checks it (None: checked inline).
135
+ BLOCKS: ClassVar[dict[str, Optional[type[Block]]]] = {
136
+ 'class': None, 'name': None, 'relays': None, 'face': FaceConfig, 'endpoints': None, 'protocols': None,
137
+ 'capabilities': None, 'operator': None, 'pow_bits': None, 'policy': PolicyConfig, 'coverage': None,
138
+ 'handover': None, 'retention': None, 'trust': None, 'peers': None, 'tick_seconds': None,
139
+ 'heartbeat': None, 'projection': None, 'telemetry': None,
140
+ 'fetch_pricing': FetchPricingConfig, 'bridge': BridgeConfig, 'announce': AnnounceConfig, 'lan': LanConfig,
141
+ 'p2p': P2PConfig, 'gossip': GossipConfig, 'capacity': CapacityConfig, 'compute': ComputeConfig, 'dvm': DVMConfig, 'nsite': NsiteConfig,
142
+ # `video` is a known key with its block declared on the compartment
143
+ # (VideoConfig), the way `trust` carries TrustConfig. It was neither:
144
+ # the compartment declared reads=('video',) and config_cls=VideoConfig
145
+ # while NodeConfig had no such key, so the block was never a typed,
146
+ # validated part of the config - which nothing caught, because
147
+ # test_config_contract walks MANIFEST and this compartment was not in it.
148
+ 'video': None,
149
+ 'settlement': SettlementConfig, 'mediation': MediationConfig, 'transport': TransportConfig,
150
+ 'blob_wire': BlobWireConfig, 'blob_replication': BlobReplicationConfig, 'storage': StorageConfig,
151
+ 'blobs': BlobsConfig, 'sidecars': SidecarsConfig, 'plugins': PluginsConfig,
152
+ # The sidecar blocks before they moved under `sidecars`: read for one
153
+ # release, folded in by from_dict, written back in the new shape.
154
+ 'grasp': None, 'ncontainer': None, 'magpie': None,
155
+ 'updates': UpdatesConfig,
156
+ 'protocol_suites': ProtocolSuitesConfig,
157
+ 'extensions': None,
158
+ # Written by the node, not the operator; carried and never checked.
159
+ 'executors': None,
160
+ }
161
+
162
+ @classmethod
163
+ def check(cls, raw: Any, p: Problems) -> None:
164
+ if not isinstance(raw, Mapping):
165
+ p.add('config is not an object'); return
166
+ if raw.get('class') not in CLASSES:
167
+ p.add('class must be leaf, edge or anchor')
168
+ relays = raw.get('relays', [])
169
+ if not isinstance(relays, list) or not all(is_relay_url(r) for r in relays):
170
+ p.add('relays must be a list of ws:// or wss:// URLs')
171
+ FaceConfig.check(raw.get('face', {}), p)
172
+ eps = raw.get('endpoints', [])
173
+ if not isinstance(eps, list) or not all(Endpoint.well_formed(e) for e in eps):
174
+ p.add('endpoints must be a list of {transport, address[, priority]}')
175
+ if not positive_int(raw.get('tick_seconds', 30)):
176
+ p.add('tick_seconds must be a positive integer')
177
+ if not isinstance(raw.get('heartbeat', True), bool):
178
+ p.add('heartbeat must be true or false')
179
+ if not isinstance(raw.get('projection', True), bool):
180
+ p.add('projection must be true or false')
181
+ if not isinstance(raw.get('telemetry', False), bool):
182
+ p.add('telemetry must be true or false')
183
+ if 'name' in raw and not isinstance(raw['name'], str):
184
+ p.add('name must be a string')
185
+ op = raw.get('operator')
186
+ if op is not None and not is_hex64(op):
187
+ p.add('operator must be a 64-hex pubkey')
188
+ bits = raw.get('pow_bits')
189
+ if bits is not None and (not is_int(bits) or not 0 <= bits <= 40):
190
+ p.add('pow_bits must be an integer from 0 to 40')
191
+ raw_protocols = raw.get('protocols')
192
+ if raw_protocols is not None:
193
+ if isinstance(raw_protocols, Mapping):
194
+ ProtocolSuitesConfig.check(raw_protocols, p)
195
+ elif isinstance(raw_protocols, (list, tuple)):
196
+ if not all_str(raw_protocols):
197
+ p.add('protocols must be a list of identifiers')
198
+ else:
199
+ p.add('protocols must be a list of strings or a protocol suite object')
200
+ if 'protocol_suites' in raw and raw['protocol_suites'] is not None:
201
+ ProtocolSuitesConfig.check(raw['protocol_suites'], p)
202
+ if not all_str(raw.get('capabilities', [])):
203
+ p.add('capabilities must be a list of names')
204
+ peers = raw.get('peers', [])
205
+ if not isinstance(peers, list):
206
+ p.add('peers must be a list')
207
+ else:
208
+ for i, e in enumerate(peers):
209
+ PeerPull.check_one(i, e, p)
210
+ trust = raw.get('trust', {})
211
+ if not isinstance(trust, Mapping):
212
+ p.add('trust must be an object of pubkey -> decision')
213
+ else:
214
+ for pk, e in trust.items():
215
+ TrustDecision.check_one(pk, e, p)
216
+ if raw.get('handover', 'offer') not in HANDOVER:
217
+ p.add('handover must be offer or never')
218
+ cov = raw.get('coverage', [])
219
+ if not isinstance(cov, list):
220
+ p.add('coverage must be a list')
221
+ else:
222
+ for i, e in enumerate(cov):
223
+ CoverageClaim.check_one(i, e, p)
224
+ ret = raw.get('retention', {})
225
+ if not isinstance(ret, Mapping):
226
+ p.add('retention must be an object of {kind: seconds}')
227
+ else:
228
+ for k, v in ret.items():
229
+ if not str(k).lstrip('-').isdigit() or not is_int(v) or int(v) < 0:
230
+ p.add('retention[%s] must be a kind number and a positive number of seconds' % str(k)[:12])
231
+ PolicyConfig.check(raw.get('policy', {}), p)
232
+ if 'extensions' in raw and not isinstance(raw['extensions'], Mapping):
233
+ p.add('extensions must be an object of {name: block}')
234
+ for key, blk in cls.BLOCKS.items():
235
+ if blk is not None and key not in ('face', 'policy') and raw.get(key) is not None:
236
+ blk.check(raw[key], p)
237
+ moved = {k: raw[k] for k in SIDECAR_KEYS_MOVED if raw.get(k) is not None}
238
+ if moved:
239
+ SidecarsConfig.check(moved, p)
240
+ unknown = sorted(k for k in raw if k not in cls.BLOCKS and not str(k).startswith('_'))
241
+ if unknown:
242
+ p.add('no compartment reads: %s (an out-of-tree block goes under extensions)' % ', '.join(unknown))
243
+
244
+ @classmethod
245
+ def from_dict(cls, raw: Any) -> 'NodeConfig':
246
+ def block(key: str, blk: Any, absent: Any = 'default') -> Any:
247
+ v = raw.get(key)
248
+ if v is None:
249
+ return blk() if absent == 'default' else None
250
+ return blk.from_dict(v)
251
+ return cls(
252
+ cls_=raw.get('class', 'edge'), name=raw.get('name', 'meshnode'), relays=tuple(raw.get('relays') or ()),
253
+ face=FaceConfig.from_dict(raw.get('face') or {}),
254
+ endpoints=tuple(Endpoint.from_dict(e) for e in raw.get('endpoints') or ()),
255
+ capabilities=tuple(raw.get('capabilities', DEFAULT_CAPABILITIES)),
256
+ operator=raw.get('operator'), pow_bits=raw.get('pow_bits'),
257
+ policy=PolicyConfig.from_dict(raw.get('policy') or {}),
258
+ coverage=tuple(CoverageClaim.from_dict(c) for c in raw.get('coverage') or ()),
259
+ handover=raw.get('handover', 'offer'),
260
+ retention={int(k): int(v) for k, v in (raw.get('retention') or {}).items()},
261
+ trust={pk: TrustDecision.from_dict(e) for pk, e in (raw.get('trust') or {}).items()},
262
+ peers=tuple(PeerPull.from_dict(e) for e in raw.get('peers') or ()),
263
+ tick_seconds=int(raw.get('tick_seconds', 30)), heartbeat=bool(raw.get('heartbeat', True)),
264
+ projection=bool(raw.get('projection', True)), telemetry=bool(raw.get('telemetry', False)),
265
+ fetch_pricing=block('fetch_pricing', FetchPricingConfig),
266
+ bridge=block('bridge', BridgeConfig, absent=None),
267
+ announce=block('announce', AnnounceConfig), lan=block('lan', LanConfig),
268
+ p2p=block('p2p', P2PConfig, absent=None), gossip=block('gossip', GossipConfig, absent=None),
269
+ capacity=block('capacity', CapacityConfig),
270
+ compute=block('compute', ComputeConfig, absent=None), dvm=block('dvm', DVMConfig, absent=None),
271
+ nsite=block('nsite', NsiteConfig, absent=None),
272
+ settlement=block('settlement', SettlementConfig), mediation=block('mediation', MediationConfig),
273
+ transport=block('transport', TransportConfig), blob_wire=block('blob_wire', BlobWireConfig),
274
+ blob_replication=block('blob_replication', BlobReplicationConfig), storage=block('storage', StorageConfig),
275
+ blobs=block('blobs', BlobsConfig), sidecars=SidecarsConfig.from_dict(sidecars_of(raw)),
276
+ plugins=block('plugins', PluginsConfig),
277
+ updates=block('updates', UpdatesConfig, absent=None),
278
+ protocol_suites=(
279
+ ProtocolSuitesConfig.from_dict(raw['protocols']) if isinstance(raw.get('protocols'), Mapping)
280
+ else ProtocolSuitesConfig.from_dict(raw['protocol_suites']) if isinstance(raw.get('protocol_suites'), Mapping)
281
+ else ProtocolSuitesConfig(
282
+ host=HostMultiplexerConfig(
283
+ listen=str((raw.get('face') or {}).get('host', '127.0.0.1')),
284
+ port=int((raw.get('face') or {}).get('port', 0)),
285
+ ),
286
+ nostr=NostrProtocolConfig(
287
+ future_seconds=int((raw.get('face') or {}).get('future_seconds', 900)),
288
+ ),
289
+ )
290
+ ),
291
+ protocols=(
292
+ DEFAULT_PROTOCOLS if isinstance(raw.get('protocols'), Mapping)
293
+ else tuple(raw.get('protocols', DEFAULT_PROTOCOLS))
294
+ ),
295
+ extensions=dict(raw.get('extensions') or {}),
296
+ )
297
+
298
+ @classmethod
299
+ def defaults_dict(cls) -> NodeConfigDict:
300
+ """What a fresh config.json carries: the file shape of NodeConfig(),
301
+ one entry per WRITTEN_BY_DEFAULT key, each the JSON form of that
302
+ field's dataclass default (blocks via to_dict, tuples as lists)."""
303
+ d = cls()
304
+ out: MutableMapping[str, Any] = {}
305
+ for key in cls.WRITTEN_BY_DEFAULT:
306
+ v = getattr(d, cls.FIELD_OF.get(key, key))
307
+ if isinstance(v, Block):
308
+ out[key] = v.to_dict()
309
+ elif isinstance(v, tuple):
310
+ out[key] = list(v)
311
+ elif isinstance(v, dict):
312
+ out[key] = dict(v)
313
+ else:
314
+ out[key] = v
315
+ return cast(NodeConfigDict, out)
316
+
317
+
318
+ __all__ = ['NodeConfig', 'DEFAULT_PROTOCOLS', 'DEFAULT_CAPABILITIES']