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,259 @@
1
+ """Python Pluggable Protocol Transducers & Router (`node.protocols.textile.transducers`).
2
+
3
+ Server-side twin of `@fleetmesh/protocols` (`MeshProtocolRouter` & `ProtocolTransducer`),
4
+ projecting and ingesting FleetMesh Textile primitives (`fiber`, `strand`, `spool`, `fabric`,
5
+ `quilt`, `suit`, `fit`, `tombstone`) across `nostr`, `w3c`, `activitypub`, `atproto`,
6
+ `p2panda`, `ssb`, `didcomm`, and `grasp`.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import time
11
+ from typing import Any, Dict, Iterable, Mapping, MutableMapping, Optional
12
+
13
+ from fleetmesh_core.identity import identity
14
+ from fleetmesh_core.textile.bip340 import schnorr_sign, xonly_pubkey
15
+ from fleetmesh_core.textile.canonical import canonical_json
16
+ from fleetmesh_core.textile.signing import (
17
+ FLEETMESH_STATEMENT_DOMAIN,
18
+ nostr_event_id,
19
+ sign_statement,
20
+ verify_envelope,
21
+ )
22
+
23
+ TEXTILE_CONTEXT = identity.FLEETMESH + 'ns/textile/v1'
24
+ LOCAL_AP_DOMAIN = 'fleetmesh.local'
25
+
26
+ DEFAULT_FALLBACK_SK = '0101010101010101010101010101010101010101010101010101010101010101'
27
+
28
+ PROTOCOL_FAMILIES = ('nostr', 'w3c', 'activitypub', 'atproto', 'p2panda', 'ssb', 'didcomm', 'grasp')
29
+
30
+
31
+ class PythonProtocolRouter:
32
+ """Projects and ingests Textile primitive manifests across all 8 FleetMesh protocol families."""
33
+
34
+ def project(
35
+ self,
36
+ family: str,
37
+ layer: str,
38
+ primitive_id: str,
39
+ bao_root: str,
40
+ manifest: Mapping[str, Any],
41
+ *,
42
+ author: Optional[Mapping[str, Any]] = None,
43
+ visibility: str = 'public',
44
+ sk_hex: str = DEFAULT_FALLBACK_SK,
45
+ timestamp_sec: Optional[int] = None,
46
+ ) -> MutableMapping[str, Any]:
47
+ fam = family.strip().lower()
48
+ if fam not in PROTOCOL_FAMILIES:
49
+ raise ValueError(f"Unsupported protocol family: {family}")
50
+ ts = int(timestamp_sec or time.time())
51
+ pubkey = xonly_pubkey(sk_hex)
52
+ resolved_author = dict(author or {'identity_id': pubkey[:16], 'did': f'did:nostr:{pubkey}'})
53
+ sig_env = sign_statement(
54
+ {
55
+ 'domain': FLEETMESH_STATEMENT_DOMAIN,
56
+ 'kind': f'{layer}_root' if layer != 'tombstone' else 'tombstone',
57
+ 'root': bao_root,
58
+ 'signer_did': f'did:nostr:{pubkey}',
59
+ 'signed_at': ts,
60
+ },
61
+ sk_hex,
62
+ )
63
+
64
+ if fam == 'nostr':
65
+ content = canonical_json({
66
+ 'v': 1,
67
+ 'domain': FLEETMESH_STATEMENT_DOMAIN,
68
+ 'layer': layer,
69
+ 'primitive_id': primitive_id,
70
+ 'bao_root': bao_root,
71
+ 'author': resolved_author,
72
+ 'visibility': visibility,
73
+ 'manifest': dict(manifest),
74
+ })
75
+ unsigned_ev = {
76
+ 'pubkey': pubkey,
77
+ 'created_at': ts,
78
+ 'kind': 30899,
79
+ 'tags': [['d', f'{layer}:{primitive_id}'], ['bao', bao_root], ['layer', layer]],
80
+ 'content': content,
81
+ }
82
+ ev_id = nostr_event_id(unsigned_ev)
83
+ ev_sig = schnorr_sign(bytes.fromhex(ev_id), sk_hex)
84
+ wire: MutableMapping[str, Any] = {
85
+ 'protocol': 'nostr',
86
+ 'event': {**unsigned_ev, 'id': ev_id, 'sig': ev_sig},
87
+ 'layer': layer,
88
+ 'primitive_id': primitive_id,
89
+ 'bao_root': bao_root,
90
+ 'author': resolved_author,
91
+ 'visibility': visibility,
92
+ 'manifest': dict(manifest),
93
+ 'signature': sig_env,
94
+ }
95
+ elif fam == 'w3c':
96
+ wire = {
97
+ '@context': ['https://www.w3.org/ns/credentials/v2', TEXTILE_CONTEXT],
98
+ 'id': f'urn:uuid:vc-{layer}-{bao_root[:32]}',
99
+ 'type': ['VerifiableCredential', f'FleetMeshTextile{layer.capitalize()}Credential'],
100
+ 'issuer': {'id': f'did:nostr:{pubkey}'},
101
+ 'credentialSubject': {
102
+ 'id': f'mesh://{layer}/{bao_root}',
103
+ 'layer': layer,
104
+ 'primitive_id': primitive_id,
105
+ 'bao_root': bao_root,
106
+ 'author': resolved_author,
107
+ 'visibility': visibility,
108
+ 'manifest': dict(manifest),
109
+ },
110
+ 'proof': {'envelope': sig_env},
111
+ }
112
+ elif fam == 'activitypub':
113
+ wire = {
114
+ '@context': ['https://www.w3.org/ns/activitystreams', TEXTILE_CONTEXT],
115
+ 'id': f'https://{LOCAL_AP_DOMAIN}/ap/activities/{layer}/{bao_root}',
116
+ 'type': 'Delete' if layer == 'tombstone' else 'Create',
117
+ 'actor': resolved_author.get('did', f'did:nostr:{pubkey}'),
118
+ 'object': {
119
+ 'id': f'mesh://{layer}/{bao_root}',
120
+ 'fleetmesh:layer': layer,
121
+ 'fleetmesh:primitiveId': primitive_id,
122
+ 'fleetmesh:baoRoot': bao_root,
123
+ 'fleetmesh:author': resolved_author,
124
+ 'fleetmesh:visibility': visibility,
125
+ 'fleetmesh:manifest': dict(manifest),
126
+ 'fleetmesh:signature': sig_env,
127
+ },
128
+ }
129
+ elif fam == 'atproto':
130
+ collection = f'mesh.fleetmesh.textile.{layer}'
131
+ uri = f"at://{resolved_author.get('did', f'did:nostr:{pubkey}')}/{collection}/{bao_root[:13]}"
132
+ wire = {
133
+ 'uri': uri,
134
+ 'collection': collection,
135
+ 'value': {
136
+ '$type': collection,
137
+ 'layer': layer,
138
+ 'primitiveId': primitive_id,
139
+ 'baoRoot': bao_root,
140
+ 'author': resolved_author,
141
+ 'visibility': visibility,
142
+ 'manifest': dict(manifest),
143
+ },
144
+ 'commit': {'signature': sig_env},
145
+ }
146
+ else:
147
+ wire = {
148
+ 'protocol': fam,
149
+ 'layer': layer,
150
+ 'primitive_id': primitive_id,
151
+ 'bao_root': bao_root,
152
+ 'author': resolved_author,
153
+ 'visibility': visibility,
154
+ 'manifest': dict(manifest),
155
+ 'signature': sig_env,
156
+ }
157
+
158
+ return {
159
+ 'protocol': fam,
160
+ 'layer': layer,
161
+ 'primitive_id': primitive_id,
162
+ 'bao_root': bao_root,
163
+ 'wire_envelope': wire,
164
+ 'projected_at': ts,
165
+ }
166
+
167
+ def project_all(
168
+ self,
169
+ layer: str,
170
+ primitive_id: str,
171
+ bao_root: str,
172
+ manifest: Mapping[str, Any],
173
+ *,
174
+ families: Optional[Iterable[str]] = None,
175
+ author: Optional[Mapping[str, Any]] = None,
176
+ visibility: str = 'public',
177
+ sk_hex: str = DEFAULT_FALLBACK_SK,
178
+ ) -> Dict[str, MutableMapping[str, Any]]:
179
+ targets = list(families) if families is not None else list(PROTOCOL_FAMILIES)
180
+ return {
181
+ fam: self.project(
182
+ fam,
183
+ layer,
184
+ primitive_id,
185
+ bao_root,
186
+ manifest,
187
+ author=author,
188
+ visibility=visibility,
189
+ sk_hex=sk_hex,
190
+ )
191
+ for fam in targets
192
+ }
193
+
194
+ def ingest(self, wire: Any) -> Optional[MutableMapping[str, Any]]:
195
+ if not isinstance(wire, Mapping):
196
+ return None
197
+ if 'credentialSubject' in wire and 'proof' in wire:
198
+ subj = wire['credentialSubject']
199
+ sig = wire['proof'].get('envelope')
200
+ root = str(subj.get('bao_root') or '')
201
+ if not verify_envelope(sig, root=root):
202
+ return None
203
+ return {
204
+ 'protocol': 'w3c',
205
+ 'layer': subj.get('layer'),
206
+ 'primitive_id': subj.get('primitive_id'),
207
+ 'bao_root': root,
208
+ 'manifest': subj.get('manifest'),
209
+ 'verified': True,
210
+ }
211
+ if 'object' in wire and isinstance(wire.get('object'), Mapping) and 'fleetmesh:baoRoot' in wire['object']:
212
+ obj = wire['object']
213
+ root = str(obj.get('fleetmesh:baoRoot') or '')
214
+ sig = obj.get('fleetmesh:signature')
215
+ if not verify_envelope(sig, root=root):
216
+ return None
217
+ return {
218
+ 'protocol': 'activitypub',
219
+ 'layer': obj.get('fleetmesh:layer'),
220
+ 'primitive_id': obj.get('fleetmesh:primitiveId'),
221
+ 'bao_root': root,
222
+ 'manifest': obj.get('fleetmesh:manifest'),
223
+ 'verified': True,
224
+ }
225
+ if isinstance(wire.get('uri'), str) and wire['uri'].startswith('at://'):
226
+ val = wire.get('value') or {}
227
+ root = str(val.get('baoRoot') or '')
228
+ sig = (wire.get('commit') or {}).get('signature')
229
+ if not verify_envelope(sig, root=root):
230
+ return None
231
+ return {
232
+ 'protocol': 'atproto',
233
+ 'layer': val.get('layer'),
234
+ 'primitive_id': val.get('primitiveId'),
235
+ 'bao_root': root,
236
+ 'manifest': val.get('manifest'),
237
+ 'verified': True,
238
+ }
239
+ root = str(wire.get('bao_root') or '')
240
+ sig = wire.get('signature')
241
+ if root and verify_envelope(sig, root=root):
242
+ return {
243
+ 'protocol': str(wire.get('protocol') or 'nostr'),
244
+ 'layer': wire.get('layer'),
245
+ 'primitive_id': wire.get('primitive_id'),
246
+ 'bao_root': root,
247
+ 'manifest': wire.get('manifest'),
248
+ 'verified': True,
249
+ }
250
+ return None
251
+
252
+
253
+ default_python_protocol_router = PythonProtocolRouter()
254
+
255
+ __all__ = [
256
+ "PROTOCOL_FAMILIES",
257
+ "PythonProtocolRouter",
258
+ "default_python_protocol_router",
259
+ ]
node/release.py ADDED
@@ -0,0 +1,119 @@
1
+ """What a running node knows about itself: name, semver, commit, protocol.
2
+
3
+ Before this the node had a protocol version (`fleetmesh/0.1-draft@<digest>`) and
4
+ nothing else - "fleetmesh is not installed as a package; the node runs from
5
+ the checkout" (pyproject.toml). An update check needs a left-hand side:
6
+ "0.9.4 is available" means nothing until the node can say "I run 0.9.3".
7
+ This module is that left-hand side, and it imports nothing heavy on purpose:
8
+ `node.config` reads it, and the config layer must stay importable on a bare
9
+ host (see test_config_contract).
10
+
11
+ node/RELEASE one line, the semver. Edited by a reviewed release commit,
12
+ the same way every other pin in this tree moves.
13
+ node/COMMIT written by scripts/release.sh into the packaged tree; a
14
+ checkout has .git instead and asks it.
15
+ node/PROTOCOL the schema digest, stamped likewise; a checkout reads
16
+ schema/DIGEST.
17
+ node/PUBLISHER the pubkey that signed the release this tree came from,
18
+ stamped likewise; a checkout reads it from the package
19
+ manifest. "The key that made me" - the updates compartment
20
+ listens to it unless the operator writes another.
21
+
22
+ `source` says which of those this is. A checkout is a developer's tree, not
23
+ an installed release, and nothing that installs releases should touch it.
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import dataclasses
28
+ import os
29
+ import re
30
+ import subprocess
31
+ from typing import Optional
32
+
33
+ _HERE = os.path.dirname(os.path.abspath(__file__))
34
+ _ROOT = os.path.dirname(_HERE)
35
+ NAME: str = 'fleetmesh-node'
36
+
37
+ # semver grammar and order are a protocol matter (operations plane), so they
38
+ # live in the reference. fleetmesh_core.peering.release is stdlib-only, which keeps this module
39
+ # importable on a bare host (test_config_contract).
40
+ from fleetmesh_core.peering.release import NAME as _REF_NAME, SEMVER, classify, compare, parse_semver # noqa: E402
41
+
42
+ __all__ = ['NAME', 'SEMVER', 'Release', 'classify', 'compare', 'parse_semver', 'running']
43
+
44
+ assert NAME == _REF_NAME, 'node.release.NAME drifted from fleetmesh_core.peering.release.NAME'
45
+
46
+
47
+ @dataclasses.dataclass(frozen=True)
48
+ class Release:
49
+ name: str
50
+ version: str
51
+ protocol: str # the schema digest this tree speaks
52
+ commit: Optional[str] = None
53
+ source: str = 'checkout' # 'release' | 'checkout'
54
+ publisher: Optional[str] = None # who signed the release this tree came from
55
+
56
+ def as_dict(self) -> dict[str, Optional[str]]:
57
+ return {'name': self.name, 'version': self.version, 'protocol': self.protocol,
58
+ 'commit': self.commit, 'source': self.source, 'publisher': self.publisher}
59
+
60
+
61
+ def _read(path: str) -> Optional[str]:
62
+ try:
63
+ with open(path, encoding='utf-8') as f:
64
+ v = f.read().strip()
65
+ return v or None
66
+ except OSError:
67
+ return None
68
+
69
+
70
+ def _git_commit(root: str) -> Optional[str]:
71
+ if not os.path.isdir(os.path.join(root, '.git')):
72
+ return None
73
+ try:
74
+ out = subprocess.run(['git', '-C', root, 'rev-parse', 'HEAD'], capture_output=True, text=True, timeout=5)
75
+ except (OSError, subprocess.SubprocessError):
76
+ return None
77
+ sha = out.stdout.strip()
78
+ return sha if out.returncode == 0 and re.fullmatch(r'[0-9a-f]{40}', sha) else None
79
+
80
+
81
+ def _digest() -> str:
82
+ try:
83
+ from fleetmesh_core.wire import schema_files
84
+ return schema_files.digest()
85
+ except Exception:
86
+ return ''
87
+
88
+
89
+ def running(root: str = _ROOT) -> Release:
90
+ """The release this tree is. Never raises: a tree with no RELEASE file
91
+ is version 0.0.0 from a checkout, which the update check treats as
92
+ "not a release" rather than "behind"."""
93
+ node_dir = os.path.join(root, 'node')
94
+ version = _read(os.path.join(node_dir, 'RELEASE')) or '0.0.0'
95
+ if parse_semver(version) is None:
96
+ version = '0.0.0'
97
+ # a release tree has node/PROTOCOL stamped from the digest it was built
98
+ # with; a checkout speaks whatever fleetmesh_schema it imports. A tree with
99
+ # neither (a bare directory) speaks nothing - that is what '' means.
100
+ protocol = _read(os.path.join(node_dir, 'PROTOCOL')) or (_digest() if os.path.isdir(node_dir) else '')
101
+ stamped = _read(os.path.join(node_dir, 'COMMIT'))
102
+ publisher = _publisher(root, node_dir)
103
+ if stamped and not os.path.isdir(os.path.join(root, '.git')):
104
+ return Release(NAME, version, protocol, commit=stamped, source='release', publisher=publisher)
105
+ return Release(NAME, version, protocol, commit=_git_commit(root) or stamped, source='checkout', publisher=publisher)
106
+
107
+
108
+ def _publisher(root: str, node_dir: str) -> Optional[str]:
109
+ """node/PUBLISHER in a packaged tree; the package manifest in a checkout."""
110
+ v = _read(os.path.join(node_dir, 'PUBLISHER'))
111
+ if v is None:
112
+ try:
113
+ import json
114
+ with open(os.path.join(root, 'packages', NAME, '.npack', 'manifest.json'), encoding='utf-8') as f:
115
+ v = json.load(f).get('publisher')
116
+ except (OSError, ValueError):
117
+ v = None
118
+ v = str(v).strip().lower() if v else None
119
+ return v if v and re.fullmatch(r'[0-9a-f]{64}', v) else None
@@ -0,0 +1,88 @@
1
+ """Node state storage: one base, one dialect per backend.
2
+
3
+ BaseStore the backend-independent half (node/storage/base.py)
4
+ SqliteDialect the default, and the only backend that needs no operator
5
+ PostgresDialect the option, behind psycopg (node/storage/postgres.py)
6
+ Dialect what a second backend has to implement
7
+ Locator which backend a node runs on, decided once from config
8
+
9
+ The design and the census it rests on are docs/storage-backends.md; the order
10
+ of work is docs/storage-plan-2026-10-08.md. All stores build on `SqliteStore`
11
+ or `BaseStore`.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Optional
16
+
17
+ from .adapter import (DatabaseAdapter, PostgresAdapter, PsqlAdapter,
18
+ SqliteAdapter, adapter_from_locator, create_adapter)
19
+ from .base import BaseStore, LEASE_SECONDS, StoreLease, StoreOwnedError
20
+ from .dialect import Dialect, SqliteDialect, ensure_incremental_vacuum
21
+ from .locator import Locator, StorageBackend, redact
22
+ from .postgres import PostgresDialect
23
+
24
+
25
+ class BaseSqliteStore(BaseStore):
26
+ """BaseStore bound to SQLite. The primary store base for SQLite-backed node stores."""
27
+
28
+ def __init__(self, path: str = ':memory:', timeout: float = 5.0,
29
+ dialect: Optional[object] = None, **kw: Any) -> None:
30
+ super().__init__(path, timeout, dialect or SqliteDialect(), **kw) # type: ignore[arg-type]
31
+
32
+
33
+ # Canonical name for SQLite store base
34
+ SqliteStore = BaseSqliteStore
35
+
36
+
37
+
38
+ # Every backend the node knows how to open. PostgresDialect is listed here
39
+ # whether or not its driver is installed: constructing one imports nothing,
40
+ # and its `errors()` answers with an empty tuple when psycopg is missing, so
41
+ # store_errors() below stays correct on a host that only has SQLite.
42
+ DIALECTS: tuple[type, ...] = (SqliteDialect, PostgresDialect)
43
+
44
+
45
+ def store_errors() -> tuple[type[BaseException], ...]:
46
+ """Every error type a configured backend's driver raises.
47
+
48
+ For `except store_errors():` where the old code said `except
49
+ sqlite3.Error:` - a narrow catch that stays narrow, and stays correct when
50
+ a second driver with its own exception hierarchy is added. Catching
51
+ `Exception` instead would be portable and would also swallow bugs.
52
+ """
53
+ out: tuple[type[BaseException], ...] = ()
54
+ for d in DIALECTS:
55
+ out = out + tuple(d().errors())
56
+ return out
57
+
58
+ __all__ = ['BaseSqliteStore', 'SqliteStore', 'BaseStore', 'StoreOwnedError', 'StoreLease', 'LEASE_SECONDS',
59
+ 'Dialect', 'SqliteDialect', 'PostgresDialect', 'Locator', 'StorageBackend', 'redact',
60
+ 'ensure_incremental_vacuum', 'DIALECTS', 'store_errors',
61
+ 'DatabaseAdapter', 'SqliteAdapter', 'PsqlAdapter', 'PostgresAdapter',
62
+ 'create_adapter', 'adapter_from_locator', 'AccountStore', 'GrantStore',
63
+ 'ProjectedNodeStore', 'TableProjector', 'CausalProjector',
64
+ 'ShardCoverageManager', 'PeerStandingStore']
65
+
66
+
67
+ def __getattr__(name: str) -> Any:
68
+ if name == 'AccountStore':
69
+ from .accounts import AccountStore
70
+ return AccountStore
71
+ if name == 'GrantStore':
72
+ from .grants import GrantStore
73
+ return GrantStore
74
+ if name in ('ProjectedNodeStore', 'TableProjector', 'CausalProjector'):
75
+ from . import projected as _proj
76
+ return getattr(_proj, name)
77
+ if name == 'ShardCoverageManager':
78
+ from .sharding import ShardCoverageManager
79
+ return ShardCoverageManager
80
+ if name == 'PeerStandingStore':
81
+ from .standing import PeerStandingStore
82
+ return PeerStandingStore
83
+ if name in ('accounts', 'grants', 'projected', 'sharding', 'standing'):
84
+ import importlib
85
+ return importlib.import_module(f'.{name}', __package__)
86
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
87
+
88
+