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,388 @@
1
+ """Static declaration linter for Compartment subclasses."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Mapping
5
+
6
+ from . import lifecycle as lifecycle_mod
7
+ from . import stores as stores_mod
8
+ from .command import Command
9
+ from .every import Every
10
+ from .handler import Handler
11
+ from .need import Need
12
+ from .problem import (
13
+ DuplicateProblem,
14
+ IncompatibleProblem,
15
+ InvalidIdentifierProblem,
16
+ Problem,
17
+ Problems,
18
+ ReferenceProblem,
19
+ ShadowingProblem,
20
+ TypeMismatchProblem,
21
+ )
22
+ from .worker import Worker
23
+
24
+ __all__ = [
25
+ 'declaration_problems',
26
+ ]
27
+
28
+
29
+ def declaration_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
30
+ """Everything wrong with this class's declarations, as sentences.
31
+
32
+ Collected rather than raised one at a time, like `Metric.problems`
33
+ and `config.Problems`: whoever fixes one wants to see them all.
34
+ Returns Problems (a list subclass) holding typed Problem instances.
35
+ """
36
+ out = Problems()
37
+ if not cls.abstract and not (isinstance(cls.name, str) and cls.name):
38
+ out.append(Problem(
39
+ 'a compartment has a name (or is declared abstract=True, a kind to extend)',
40
+ surface='NAME',
41
+ code='missing_name',
42
+ target='name',
43
+ ))
44
+ for surface, attr in (
45
+ ('CHANNELS', 'name'),
46
+ ('METRICS', 'name'),
47
+ ('COMMANDS', 'key'),
48
+ ('STORES', 'key'),
49
+ ('NEEDS', 'key'),
50
+ ('EVERY', 'name'),
51
+ ('WORKERS', 'name'),
52
+ ('HANDLERS', 'kind'),
53
+ ):
54
+ seen: set[str] = set()
55
+ for d in getattr(cls, surface, ()) or ():
56
+ k = getattr(d, attr, None)
57
+ if k in seen:
58
+ out.append(DuplicateProblem(surface, str(k)))
59
+ seen.add(str(k))
60
+ # The annotation promises a Lifecycle; this is for the subclass that
61
+ # broke the promise, so look at it as whatever it actually is.
62
+ lc: object = cls.LIFECYCLE
63
+ if not isinstance(lc, lifecycle_mod.Lifecycle):
64
+ out.append(TypeMismatchProblem('LIFECYCLE', 'node.lifecycle.Lifecycle', type(lc).__name__,
65
+ message='LIFECYCLE is a node.lifecycle.Lifecycle'))
66
+ return out
67
+ for p in lc.problems():
68
+ out.add(p, surface='LIFECYCLE')
69
+ out.extend(store_problems(cls, base_cls))
70
+ out.extend(command_problems(cls, base_cls))
71
+ out.extend(command_data_problems(cls))
72
+ out.extend(need_problems(cls, base_cls))
73
+ out.extend(every_problems(cls))
74
+ out.extend(worker_problems(cls))
75
+ out.extend(schema_problems(cls))
76
+ out.extend(migration_problems(cls))
77
+ out.extend(handler_problems(cls, base_cls))
78
+ out.extend(dependency_problems(cls))
79
+ out.extend(channel_problems(cls))
80
+ out.extend(metric_problems(cls))
81
+ out.extend(cli_problems(cls))
82
+ out.extend(sidecar_problems(cls))
83
+ return out
84
+
85
+
86
+ def schema_problems(cls: type[Any]) -> list[str]:
87
+ out = Problems()
88
+ declared: tuple[object, ...] = tuple(getattr(cls, 'SCHEMA', ()) or ())
89
+ from .schema import Index, Table
90
+ seen_tables: set[str] = set()
91
+ seen_indices: set[str] = set()
92
+ for s in declared:
93
+ if not isinstance(s, (Table, Index)):
94
+ out.append(TypeMismatchProblem('SCHEMA', 'Table or Index', type(s).__name__,
95
+ message='SCHEMA holds Table or Index, not %s' % type(s).__name__))
96
+ continue
97
+ if isinstance(s, Table):
98
+ if s.name in seen_tables:
99
+ out.append(DuplicateProblem('SCHEMA', s.name, message='SCHEMA declares table %r twice' % s.name))
100
+ seen_tables.add(s.name)
101
+ out.extend(s.problems())
102
+ for idx in s.indices:
103
+ if idx.name in seen_indices:
104
+ out.append(DuplicateProblem('SCHEMA', idx.name, message='SCHEMA declares index %r twice' % idx.name))
105
+ seen_indices.add(idx.name)
106
+ elif isinstance(s, Index):
107
+ if s.name in seen_indices:
108
+ out.append(DuplicateProblem('SCHEMA', s.name, message='SCHEMA declares index %r twice' % s.name))
109
+ seen_indices.add(s.name)
110
+ out.extend(s.problems())
111
+ return out
112
+
113
+
114
+ def migration_problems(cls: type[Any]) -> list[str]:
115
+ out = Problems()
116
+ declared: tuple[object, ...] = tuple(getattr(cls, 'MIGRATIONS', ()) or ())
117
+ from .schema import Migration
118
+ last_version = 0
119
+ seen_versions: set[int] = set()
120
+ for m in declared:
121
+ if not isinstance(m, Migration):
122
+ out.append(TypeMismatchProblem('MIGRATIONS', 'node.compartment.schema.Migration', type(m).__name__,
123
+ message='MIGRATIONS holds node.compartment.schema.Migration, not %s' % type(m).__name__))
124
+ continue
125
+ if m.version in seen_versions:
126
+ out.append(DuplicateProblem('MIGRATIONS', str(m.version), message='MIGRATIONS declares version %d twice' % m.version))
127
+ seen_versions.add(m.version)
128
+ if m.version <= last_version:
129
+ out.append(IncompatibleProblem(
130
+ 'MIGRATIONS', str(m.version),
131
+ f'MIGRATIONS versions must be strictly ascending (got {m.version} after {last_version})'
132
+ ))
133
+ last_version = m.version
134
+ out.extend(m.problems())
135
+ if declared and not getattr(cls, 'SCHEMA', ()):
136
+ out.append(IncompatibleProblem(
137
+ 'MIGRATIONS', 'SCHEMA',
138
+ 'a compartment that declares MIGRATIONS must also declare SCHEMA'
139
+ ))
140
+ return out
141
+
142
+
143
+ def handler_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
144
+ out = Problems()
145
+ declared: tuple[object, ...] = tuple(getattr(cls, 'HANDLERS', ()) or ())
146
+ for h in declared:
147
+ if not isinstance(h, Handler):
148
+ out.append(TypeMismatchProblem('HANDLERS', 'node.compartment.Handler', type(h).__name__,
149
+ message='HANDLERS holds node.compartment.Handler, not %s' % type(h).__name__))
150
+ continue
151
+ out.extend(h.problems(cls))
152
+ if declared and cls.handle is not base_cls.handle:
153
+ out.append(IncompatibleProblem(
154
+ 'HANDLERS', 'handle',
155
+ 'a compartment that declares HANDLERS does not write handle(): '
156
+ 'the base dispatches them, from the declarations'
157
+ ))
158
+ return out
159
+
160
+
161
+ def dependency_problems(cls: type[Any]) -> list[str]:
162
+ out = Problems()
163
+ req = getattr(cls, 'requires', ()) or ()
164
+ use = getattr(cls, 'uses', ()) or ()
165
+ if not isinstance(req, (tuple, list)):
166
+ out.append(TypeMismatchProblem('requires', 'tuple[str, ...]', type(req).__name__,
167
+ message='requires must be a tuple of strings, not %s' % type(req).__name__))
168
+ req = ()
169
+ if not isinstance(use, (tuple, list)):
170
+ out.append(TypeMismatchProblem('uses', 'tuple[str, ...]', type(use).__name__,
171
+ message='uses must be a tuple of strings, not %s' % type(use).__name__))
172
+ use = ()
173
+
174
+ seen_req: set[str] = set()
175
+ for r in req:
176
+ if not isinstance(r, str):
177
+ out.append(TypeMismatchProblem('requires', 'str', type(r).__name__,
178
+ message='requires entries must be strings, not %s' % type(r).__name__))
179
+ continue
180
+ if not r.isidentifier():
181
+ out.append(InvalidIdentifierProblem('requires', r, message='requires %r is not a valid identifier' % r))
182
+ if r in seen_req:
183
+ out.append(DuplicateProblem('requires', r, message='requires declares %r twice' % r))
184
+ seen_req.add(r)
185
+ if cls.name and r == cls.name:
186
+ out.append(IncompatibleProblem('requires', r, '%s cannot require itself' % cls.name))
187
+
188
+ seen_use: set[str] = set()
189
+ for u in use:
190
+ if not isinstance(u, str):
191
+ out.append(TypeMismatchProblem('uses', 'str', type(u).__name__,
192
+ message='uses entries must be strings, not %s' % type(u).__name__))
193
+ continue
194
+ if not u.isidentifier():
195
+ out.append(InvalidIdentifierProblem('uses', u, message='uses %r is not a valid identifier' % u))
196
+ if u in seen_use:
197
+ out.append(DuplicateProblem('uses', u, message='uses declares %r twice' % u))
198
+ seen_use.add(u)
199
+ if cls.name and u == cls.name:
200
+ out.append(IncompatibleProblem('uses', u, '%s cannot use itself' % cls.name))
201
+
202
+ overlap = seen_req & seen_use
203
+ for o in sorted(overlap):
204
+ out.append(IncompatibleProblem(
205
+ 'requires/uses', o,
206
+ '%s cannot be both required and optional (in both requires and uses)' % o
207
+ ))
208
+ return out
209
+
210
+
211
+ def channel_problems(cls: type[Any]) -> list[str]:
212
+ out = Problems()
213
+ from ..channels import Channel
214
+ declared: tuple[object, ...] = tuple(getattr(cls, 'CHANNELS', ()) or ())
215
+ for ch in declared:
216
+ if not isinstance(ch, Channel):
217
+ out.append(TypeMismatchProblem('CHANNELS', 'node.channels.Channel', type(ch).__name__,
218
+ message='CHANNELS holds node.channels.Channel, not %s' % type(ch).__name__))
219
+ return out
220
+
221
+
222
+ def metric_problems(cls: type[Any]) -> list[str]:
223
+ out = Problems()
224
+ from ..metrics import Metric
225
+ declared: tuple[object, ...] = tuple(getattr(cls, 'METRICS', ()) or ())
226
+ for m in declared:
227
+ if not isinstance(m, Metric):
228
+ out.append(TypeMismatchProblem('METRICS', 'node.metrics.Metric', type(m).__name__,
229
+ message='METRICS holds node.metrics.Metric, not %s' % type(m).__name__))
230
+ continue
231
+ for p in m.problems():
232
+ out.add(p, surface='METRICS')
233
+ return out
234
+
235
+
236
+ def cli_problems(cls: type[Any]) -> list[str]:
237
+ out = Problems()
238
+ declared: tuple[object, ...] = tuple(getattr(cls, 'cli', ()) or ())
239
+ for item in declared:
240
+ if not (isinstance(item, (tuple, list)) and len(item) == 2):
241
+ out.append(TypeMismatchProblem('cli', 'tuple[str, Callable]', type(item).__name__,
242
+ message='cli entries must be (verb, register_fn) pairs, not %s' % type(item).__name__))
243
+ continue
244
+ verb, fn = item
245
+ if not (isinstance(verb, str) and verb.isidentifier()):
246
+ out.append(InvalidIdentifierProblem('cli', str(verb), message='cli verb %r is not a valid identifier' % verb))
247
+ if not callable(fn):
248
+ out.append(TypeMismatchProblem('cli', 'callable register_fn', type(fn).__name__,
249
+ message='cli register function for %r must be callable, not %s' % (verb, type(fn).__name__)))
250
+ return out
251
+
252
+
253
+ def sidecar_problems(cls: type[Any]) -> list[str]:
254
+ out = Problems()
255
+ declared: tuple[object, ...] = tuple(getattr(cls, 'sidecars', ()) or ())
256
+ for item in declared:
257
+ if not (isinstance(item, Mapping) or (isinstance(item, str) and item.startswith('@'))):
258
+ out.append(TypeMismatchProblem(
259
+ 'sidecars', 'manifest mapping or "@/path" string', type(item).__name__,
260
+ message='sidecars holds manifest dict or "@/path", not %s' % type(item).__name__
261
+ ))
262
+ return out
263
+
264
+
265
+ def worker_problems(cls: type[Any]) -> list[str]:
266
+ out = Problems()
267
+ declared: tuple[object, ...] = tuple(getattr(cls, 'WORKERS', ()) or ())
268
+ for w in declared:
269
+ if not isinstance(w, Worker):
270
+ out.append(TypeMismatchProblem('WORKERS', 'node.compartment.Worker', type(w).__name__,
271
+ message='WORKERS holds node.compartment.Worker, not %s' % type(w).__name__))
272
+ continue
273
+ out.extend(w.problems(cls))
274
+ return out
275
+
276
+
277
+ def every_problems(cls: type[Any]) -> list[str]:
278
+ out = Problems()
279
+ declared: tuple[object, ...] = tuple(getattr(cls, 'EVERY', ()) or ())
280
+ for e in declared:
281
+ if not isinstance(e, Every):
282
+ out.append(TypeMismatchProblem('EVERY', 'node.compartment.Every', type(e).__name__,
283
+ message='EVERY holds node.compartment.Every, not %s' % type(e).__name__))
284
+ continue
285
+ out.extend(e.problems(cls))
286
+ return out
287
+
288
+
289
+ def need_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
290
+ out = Problems()
291
+ declared: tuple[object, ...] = tuple(getattr(cls, 'NEEDS', ()) or ())
292
+ for n in declared:
293
+ if not isinstance(n, Need):
294
+ out.append(TypeMismatchProblem('NEEDS', 'node.compartment.Need', type(n).__name__,
295
+ message='NEEDS holds node.compartment.Need, not %s' % type(n).__name__))
296
+ continue
297
+ out.extend(n.problems(cls))
298
+ fc = getattr(cls.from_context, '__func__', None)
299
+ base_fc = getattr(base_cls.from_context, '__func__', None)
300
+ if declared and fc is not base_fc:
301
+ out.append(IncompatibleProblem(
302
+ 'NEEDS', 'from_context',
303
+ 'a compartment that declares NEEDS does not override from_context: '
304
+ 'the base constructs it from the declarations'
305
+ ))
306
+ return out
307
+
308
+
309
+ def command_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
310
+ out = Problems()
311
+ declared: tuple[object, ...] = tuple(cls.COMMANDS)
312
+ for c in declared:
313
+ if not isinstance(c, Command):
314
+ out.append(TypeMismatchProblem('COMMANDS', 'node.compartment.Command', type(c).__name__,
315
+ message='COMMANDS holds node.compartment.Command, not %s' % type(c).__name__))
316
+ continue
317
+ out.extend(c.problems(cls))
318
+ if declared and cls.command is not base_cls.command:
319
+ out.append(IncompatibleProblem(
320
+ 'COMMANDS', 'command',
321
+ 'a compartment that declares COMMANDS does not write command(): '
322
+ 'the base answers them, from the declarations'
323
+ ))
324
+ return out
325
+
326
+
327
+ def store_problems(cls: type[Any], base_cls: type[Any]) -> list[str]:
328
+ out = Problems()
329
+ persisted = []
330
+ declared: tuple[object, ...] = tuple(cls.STORES)
331
+ for s in declared:
332
+ if not isinstance(s, stores_mod.Store):
333
+ out.append(TypeMismatchProblem('STORES', 'node.stores.Store', type(s).__name__,
334
+ message='STORES holds node.stores.Store, not %s' % type(s).__name__))
335
+ continue
336
+ out.extend(s.problems())
337
+ if any(s.key in vars(k) for k in cls.__mro__ if k is not object):
338
+ out.append(ShadowingProblem('STORES', s.key, cls.__name__))
339
+ if s.persist == stores_mod.SNAPSHOT:
340
+ persisted.append(s.key)
341
+ snap = cls.LIFECYCLE.snapshot
342
+ if persisted and not snap:
343
+ out.append(IncompatibleProblem(
344
+ 'STORES', ', '.join(persisted),
345
+ 'stores %s persist across a restart, so LIFECYCLE must declare snapshot=N '
346
+ '(the version of their shape)' % ', '.join(persisted)
347
+ ))
348
+ if snap and not persisted and cls.snapshot is base_cls.snapshot:
349
+ out.append(IncompatibleProblem(
350
+ 'STORES', 'snapshot',
351
+ 'Lifecycle(snapshot=%d) carries nothing: declare a SNAPSHOT store, or snapshot() '
352
+ 'and restore(state)' % snap
353
+ ))
354
+ if snap and (cls.snapshot is base_cls.snapshot) != (cls.restore is base_cls.restore):
355
+ out.append(IncompatibleProblem(
356
+ 'STORES', 'snapshot/restore',
357
+ 'snapshot() and restore(state) come as a pair'
358
+ ))
359
+ return out
360
+
361
+
362
+ def command_data_problems(cls: type[Any]) -> list[str]:
363
+ out = Problems()
364
+ own = {s.key for s in cls.STORES if isinstance(s, stores_mod.Store)}
365
+ reach = set(cls.requires) | set(cls.uses)
366
+ for c in cls.COMMANDS:
367
+ if not isinstance(c, Command):
368
+ continue
369
+ for verb, refs in (('reads', c.reads), ('writes', c.writes)):
370
+ for ref in refs:
371
+ other, dot, key = ref.partition('.')
372
+ if not dot and ref not in own:
373
+ out.append(ReferenceProblem(
374
+ 'COMMANDS', f'{c.key}.{verb}', ref,
375
+ 'command %s %s store %r, which this compartment does not declare' % (c.key, verb, ref)
376
+ ))
377
+ elif dot and verb == 'writes':
378
+ out.append(IncompatibleProblem(
379
+ 'COMMANDS', f'{c.key}.writes',
380
+ 'command %s writes %s, a store of %s; a command writes only its own '
381
+ "compartment's stores - ask %s instead" % (c.key, ref, other, other)
382
+ ))
383
+ elif dot and other not in reach:
384
+ out.append(ReferenceProblem(
385
+ 'COMMANDS', f'{c.key}.reads', other,
386
+ 'command %s reads %s from %s, which this compartment neither requires nor uses' % (c.key, key, other)
387
+ ))
388
+ return out
@@ -0,0 +1,158 @@
1
+ """Declared dependencies a compartment takes from the node."""
2
+ from __future__ import annotations
3
+
4
+ import dataclasses
5
+ import inspect
6
+ from typing import Any, Mapping, Optional
7
+
8
+ from .problem import (IncompatibleProblem, Problem, Problems,
9
+ ReferenceProblem, TypeMismatchProblem)
10
+
11
+
12
+ @dataclasses.dataclass(frozen=True)
13
+ class Need:
14
+ """One dependency a compartment takes from the node, declared where the
15
+ compartment is.
16
+
17
+ Resolved against NodeContext at assembly by the base `from_context`:
18
+ - a field (`ks`, `transport`, `stores.grants`, `clock`, `propose`, `pull`, `regrant`, etc.)
19
+ - a typed config block (`Need('settings', block=SettlementConfig)`)
20
+ - a compartment, which must appear in `requires` or `uses`, resolved from registry / context
21
+ - a live config getter (`Need('cfg', 'cfg', getter=True)` or `Need('cfg', block=UpdatesConfig, getter=True)`)
22
+ - a node-relative path (`Need('db_path', path='DB')` or `Need('db_path', path='updates.sqlite')`)
23
+ - or `late=True`: resolved and bound after all compartments are assembled, replacing wire().
24
+ """
25
+ key: str
26
+ source: Optional[str] = None
27
+ block: Optional[type] = None
28
+ late: bool = False
29
+ getter: bool = False
30
+ path: Optional[str] = None
31
+ field: Optional[str] = None
32
+
33
+ def __init__(
34
+ self,
35
+ key: str,
36
+ source: Optional[str] = None,
37
+ *,
38
+ block: Optional[type] = None,
39
+ late: bool = False,
40
+ getter: bool = False,
41
+ path: Optional[str] = None,
42
+ field: Optional[str] = None,
43
+ ) -> None:
44
+ object.__setattr__(self, 'key', key)
45
+ object.__setattr__(self, 'source', source if source is not None else (None if (block is not None or path is not None) else key))
46
+ object.__setattr__(self, 'block', block)
47
+ object.__setattr__(self, 'late', late)
48
+ object.__setattr__(self, 'getter', getter)
49
+ object.__setattr__(self, 'path', path)
50
+ object.__setattr__(self, 'field', field)
51
+
52
+ def resolve(self, ctx: Any, cls: type) -> Any:
53
+ if self.late:
54
+ return None
55
+ if self.path is not None:
56
+ rel = getattr(cls, self.path, self.path)
57
+ return ctx.path(rel)
58
+ if self.block is not None:
59
+ if self.getter:
60
+ return ctx.block_getter(self.block)
61
+ b = ctx.block(self.block)
62
+ if self.field is not None:
63
+ return getattr(b, self.field)
64
+ return b
65
+ src = self.source or self.key
66
+ if self.getter:
67
+ if src == 'cfg':
68
+ return ctx.cfg_getter()
69
+ return lambda: self._resolve_src(ctx, cls, src)
70
+ res = self._resolve_src(ctx, cls, src)
71
+ if self.field is not None:
72
+ if isinstance(res, Mapping):
73
+ return res.get(self.field)
74
+ return getattr(res, self.field)
75
+ return res
76
+
77
+ def _resolve_src(self, ctx: Any, cls: type, src: str) -> Any:
78
+ reqs = getattr(cls, 'requires', ()) or ()
79
+ uses = getattr(cls, 'uses', ()) or ()
80
+ if src in reqs or src in uses:
81
+ return ctx.optional(src)
82
+ val = ctx
83
+ for part in src.split('.'):
84
+ if hasattr(val, part):
85
+ val = getattr(val, part)
86
+ elif isinstance(val, Mapping) and part in val:
87
+ val = val[part]
88
+ else:
89
+ raise AttributeError(f"NodeContext has no field {part!r} (resolving {src!r})")
90
+ return val
91
+
92
+ def resolve_late(self, ctx: Any, instance: Any, registry: Any) -> Any:
93
+ comp_name = self.source or self.key
94
+ val = registry.get(comp_name)
95
+ if val is None:
96
+ val = getattr(ctx, comp_name, None)
97
+ return val
98
+
99
+ def problems(self, cls: type) -> list[str]:
100
+ out = Problems()
101
+ if not self.key or not isinstance(self.key, str):
102
+ out.append(Problem('Need key must be a non-empty string', surface='NEEDS', code='empty_key', target=''))
103
+ return out
104
+
105
+ # Check __init__ parameter (unless late=True)
106
+ if not self.late:
107
+ try:
108
+ init_fn = getattr(cls, '__init__', None)
109
+ if init_fn is not None:
110
+ sig = inspect.signature(init_fn)
111
+ params = sig.parameters
112
+ has_varkw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values())
113
+ if not has_varkw and self.key not in params:
114
+ out.append(IncompatibleProblem(
115
+ 'NEEDS', self.key,
116
+ f"need {self.key!r} is not an __init__ parameter of {cls.__name__}"
117
+ ))
118
+ except (ValueError, TypeError):
119
+ pass
120
+
121
+ if self.path is not None:
122
+ if not isinstance(self.path, str) or not self.path:
123
+ out.append(TypeMismatchProblem('NEEDS', 'non-empty str path', type(self.path).__name__,
124
+ message=f"need {self.key!r}: path must be a non-empty string"))
125
+ elif self.block is not None:
126
+ blk: object = self.block
127
+ if not isinstance(blk, type):
128
+ out.append(TypeMismatchProblem('NEEDS', 'type', type(blk).__name__,
129
+ message=f"need {self.key!r}: block must be a type, got {type(blk).__name__}"))
130
+ else:
131
+ src = self.source or self.key
132
+ reqs = getattr(cls, 'requires', ()) or ()
133
+ uses = getattr(cls, 'uses', ()) or ()
134
+ top = src.split('.')[0]
135
+ from .context import NodeContext
136
+ known_context_fields = set(NodeContext.__dataclass_fields__.keys()) | {
137
+ 'pubkey', 'did', 'node_class',
138
+ }
139
+ if top in known_context_fields:
140
+ if top == 'stores' and '.' in src:
141
+ store_field = src.split('.')[1]
142
+ from .context import NodeStores
143
+ if store_field not in NodeStores._fields:
144
+ out.append(ReferenceProblem(
145
+ 'NEEDS', self.key, store_field,
146
+ message=f"need {self.key!r}: stores has no field {store_field!r} (declared: {', '.join(NodeStores._fields)})"
147
+ ))
148
+ elif self.late:
149
+ pass # late needs can name any compartment built after this one
150
+ elif src in reqs or src in uses:
151
+ pass # valid declared dependency
152
+ else:
153
+ out.append(ReferenceProblem(
154
+ 'NEEDS', self.key, src,
155
+ message=f"need {self.key!r}: source {src!r} is neither a NodeContext field nor in requires/uses"
156
+ ))
157
+ return out
158
+