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,286 @@
1
+ """ActivityStreams 2.0 object and activity models.
2
+
3
+ Standard library only. Defines:
4
+ - Core ActivityStreams 2.0 objects: Note, Article, Image, Document.
5
+ - Core ActivityPub activities: Create, Follow, Accept, Undo, Delete, Announce, Like.
6
+ - Serialization and parsing helpers with schema validation.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from typing import Any, Mapping, Optional, Sequence, Union
12
+
13
+ from fleetmesh_core.conformance.errors import FleetmeshError
14
+
15
+ __all__ = [
16
+ 'ActivityPubError',
17
+ 'AS_CONTEXT',
18
+ 'SECURITY_CONTEXT',
19
+ 'Activity',
20
+ 'Note',
21
+ 'parse_activity',
22
+ 'build_note',
23
+ 'build_create_activity',
24
+ 'build_follow_activity',
25
+ 'build_accept_activity',
26
+ 'build_undo_activity',
27
+ 'build_delete_activity',
28
+ 'build_announce_activity',
29
+ ]
30
+
31
+ AS_CONTEXT = "https://www.w3.org/ns/activitystreams"
32
+ SECURITY_CONTEXT = "https://w3id.org/security/v1"
33
+ PUBLIC_COLLECTION = "https://www.w3.org/ns/activitystreams#Public"
34
+
35
+
36
+ class ActivityPubError(FleetmeshError):
37
+ """Base error for ActivityPub data and parsing."""
38
+
39
+
40
+ from node.protocols.isotime import now_iso as _now_iso # noqa: E402
41
+
42
+
43
+ class Note:
44
+ """An ActivityStreams Note object (microblog post)."""
45
+
46
+ def __init__(
47
+ self,
48
+ id: str,
49
+ attributed_to: str,
50
+ content: str,
51
+ to: Optional[Sequence[str]] = None,
52
+ cc: Optional[Sequence[str]] = None,
53
+ published: Optional[str] = None,
54
+ in_reply_to: Optional[str] = None,
55
+ attachment: Optional[Sequence[Mapping[str, Any]]] = None,
56
+ url: Optional[str] = None,
57
+ ):
58
+ self.id = id
59
+ self.attributed_to = attributed_to
60
+ self.content = content
61
+ self.to = list(to) if to is not None else [PUBLIC_COLLECTION]
62
+ self.cc = list(cc) if cc is not None else []
63
+ self.published = published or _now_iso()
64
+ self.in_reply_to = in_reply_to
65
+ self.attachment = list(attachment) if attachment is not None else []
66
+ self.url = url or id
67
+
68
+ def to_dict(self) -> Mapping[str, Any]:
69
+ doc = {
70
+ '@context': AS_CONTEXT,
71
+ 'id': self.id,
72
+ 'type': 'Note',
73
+ 'attributedTo': self.attributed_to,
74
+ 'content': self.content,
75
+ 'to': self.to,
76
+ 'cc': self.cc,
77
+ 'published': self.published,
78
+ 'url': self.url,
79
+ **({'inReplyTo': self.in_reply_to} if self.in_reply_to else {}),
80
+ **({'attachment': self.attachment} if self.attachment else {}),
81
+ }
82
+ return doc
83
+
84
+
85
+ class Activity:
86
+ """An ActivityStreams Activity (Create, Follow, Accept, Undo, etc.)."""
87
+
88
+ def __init__(
89
+ self,
90
+ id: str,
91
+ type: str,
92
+ actor: str,
93
+ object: Union[str, Mapping[str, Any]],
94
+ to: Optional[Sequence[str]] = None,
95
+ cc: Optional[Sequence[str]] = None,
96
+ published: Optional[str] = None,
97
+ ):
98
+ self.id = id
99
+ self.type = type
100
+ self.actor = actor
101
+ self.object = object
102
+ self.to = list(to) if to is not None else [PUBLIC_COLLECTION]
103
+ self.cc = list(cc) if cc is not None else []
104
+ self.published = published or _now_iso()
105
+
106
+ @property
107
+ def object_id(self) -> str:
108
+ """Extract object ID whether object is a string URI or embedded dict."""
109
+ if isinstance(self.object, dict):
110
+ return self.object.get('id', '')
111
+ return str(self.object)
112
+
113
+ def to_dict(self) -> Mapping[str, Any]:
114
+ return {
115
+ '@context': AS_CONTEXT,
116
+ 'id': self.id,
117
+ 'type': self.type,
118
+ 'actor': self.actor,
119
+ 'object': self.object,
120
+ 'to': self.to,
121
+ 'cc': self.cc,
122
+ 'published': self.published,
123
+ }
124
+
125
+
126
+ def parse_activity(data: Union[str, bytes, Mapping[str, Any]]) -> Activity:
127
+ """Parse JSON or dict into an Activity instance."""
128
+ if isinstance(data, (str, bytes)):
129
+ try:
130
+ doc = json.loads(data)
131
+ except Exception as e:
132
+ raise ActivityPubError(f"invalid JSON: {e}") from e
133
+ else:
134
+ doc = dict(data)
135
+
136
+ if not isinstance(doc, dict):
137
+ raise ActivityPubError("activity must be a JSON object")
138
+
139
+ act_type = doc.get('type')
140
+ if not act_type or not isinstance(act_type, str):
141
+ raise ActivityPubError("activity lacks valid type")
142
+
143
+ actor = doc.get('actor')
144
+ if not actor:
145
+ raise ActivityPubError("activity lacks actor")
146
+
147
+ act_id = doc.get('id', '')
148
+ obj = doc.get('object', '')
149
+ to = doc.get('to')
150
+ cc = doc.get('cc')
151
+ pub = doc.get('published')
152
+
153
+ to_list = [to] if isinstance(to, str) else list(to) if isinstance(to, list) else None
154
+ cc_list = [cc] if isinstance(cc, str) else list(cc) if isinstance(cc, list) else None
155
+
156
+ return Activity(
157
+ id=act_id,
158
+ type=act_type,
159
+ actor=actor,
160
+ object=obj,
161
+ to=to_list,
162
+ cc=cc_list,
163
+ published=pub,
164
+ )
165
+
166
+
167
+ def build_note(
168
+ id: str,
169
+ attributed_to: str,
170
+ content: str,
171
+ to: Optional[Sequence[str]] = None,
172
+ cc: Optional[Sequence[str]] = None,
173
+ in_reply_to: Optional[str] = None,
174
+ attachment: Optional[Sequence[Mapping[str, Any]]] = None,
175
+ ) -> Mapping[str, Any]:
176
+ """Helper to build a standard Note dictionary."""
177
+ return Note(
178
+ id=id,
179
+ attributed_to=attributed_to,
180
+ content=content,
181
+ to=to,
182
+ cc=cc,
183
+ in_reply_to=in_reply_to,
184
+ attachment=attachment,
185
+ ).to_dict()
186
+
187
+
188
+ def build_create_activity(
189
+ activity_id: str,
190
+ actor_uri: str,
191
+ note_or_object: Mapping[str, Any],
192
+ to: Optional[Sequence[str]] = None,
193
+ cc: Optional[Sequence[str]] = None,
194
+ ) -> Mapping[str, Any]:
195
+ """Wrap an object in a Create activity."""
196
+ return Activity(
197
+ id=activity_id,
198
+ type='Create',
199
+ actor=actor_uri,
200
+ object=note_or_object,
201
+ to=to or note_or_object.get('to'),
202
+ cc=cc or note_or_object.get('cc'),
203
+ ).to_dict()
204
+
205
+
206
+ def build_follow_activity(
207
+ activity_id: str,
208
+ actor_uri: str,
209
+ target_actor_uri: str,
210
+ ) -> Mapping[str, Any]:
211
+ """Build a Follow activity targeting another actor."""
212
+ return Activity(
213
+ id=activity_id,
214
+ type='Follow',
215
+ actor=actor_uri,
216
+ object=target_actor_uri,
217
+ to=[target_actor_uri],
218
+ cc=[],
219
+ ).to_dict()
220
+
221
+
222
+ def build_accept_activity(
223
+ activity_id: str,
224
+ actor_uri: str,
225
+ follow_activity: Union[str, Mapping[str, Any]],
226
+ recipient_uri: str,
227
+ ) -> Mapping[str, Any]:
228
+ """Build an Accept activity responding to a Follow."""
229
+ return Activity(
230
+ id=activity_id,
231
+ type='Accept',
232
+ actor=actor_uri,
233
+ object=follow_activity,
234
+ to=[recipient_uri],
235
+ cc=[],
236
+ ).to_dict()
237
+
238
+
239
+ def build_undo_activity(
240
+ activity_id: str,
241
+ actor_uri: str,
242
+ activity_to_undo: Union[str, Mapping[str, Any]],
243
+ recipient_uri: Optional[str] = None,
244
+ ) -> Mapping[str, Any]:
245
+ """Build an Undo activity (e.g. unfollow or unlike)."""
246
+ to_list = [recipient_uri] if recipient_uri else [PUBLIC_COLLECTION]
247
+ return Activity(
248
+ id=activity_id,
249
+ type='Undo',
250
+ actor=actor_uri,
251
+ object=activity_to_undo,
252
+ to=to_list,
253
+ cc=[],
254
+ ).to_dict()
255
+
256
+
257
+ def build_delete_activity(
258
+ activity_id: str,
259
+ actor_uri: str,
260
+ object_uri: str,
261
+ ) -> Mapping[str, Any]:
262
+ """Build a Delete activity removing a previously published object."""
263
+ return Activity(
264
+ id=activity_id,
265
+ type='Delete',
266
+ actor=actor_uri,
267
+ object=object_uri,
268
+ to=[PUBLIC_COLLECTION],
269
+ cc=[],
270
+ ).to_dict()
271
+
272
+
273
+ def build_announce_activity(
274
+ activity_id: str,
275
+ actor_uri: str,
276
+ object_uri: str,
277
+ ) -> Mapping[str, Any]:
278
+ """Build an Announce (boost/repost) activity."""
279
+ return Activity(
280
+ id=activity_id,
281
+ type='Announce',
282
+ actor=actor_uri,
283
+ object=object_uri,
284
+ to=[PUBLIC_COLLECTION],
285
+ cc=[],
286
+ ).to_dict()
@@ -0,0 +1,271 @@
1
+ """fleetmesh-protocol-activitypub: ActivityPub & ActivityStreams 2.0 package.
2
+
3
+ Conforms to ProtocolPackage (fleetmesh_core.wire.interfaces.ProtocolPackage).
4
+ Mounts WebFinger (RFC 7033), Actor profiles, S2S Inbox federation,
5
+ Outbox feeds, and Blossom unified media onto ProtocolHostRouter.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import urllib.parse
11
+ from typing import Any, Mapping, Optional
12
+
13
+ from fleetmesh_core.wire.interfaces import (
14
+ ProtocolHealth,
15
+ ProtocolHostRouter,
16
+ ProtocolIdentityAdapter,
17
+ )
18
+ from node.protocols.activitypub.actor import Actor, WebFingerService, ed25519_pubkey_to_pem
19
+ from node.protocols.activitypub.federation import FederationService, StrainScoreKeeper
20
+ from node.protocols.activitypub.media import ActivityPubMediaManager
21
+ from node.protocols.activitypub.store import ActivityPubStore
22
+ from node.protocols.base import BaseProtocolPackage, Ed25519IdentityAdapter
23
+
24
+ __all__ = [
25
+ 'ActivityPubIdentityAdapter',
26
+ 'ActivityPubProtocolPackage',
27
+ ]
28
+
29
+
30
+ class ActivityPubIdentityAdapter(Ed25519IdentityAdapter):
31
+ """Translates between Fleetmesh Ed25519 identities and ActivityPub Actor URIs."""
32
+
33
+ def __init__(self, actor: Actor, private_key: Any = None):
34
+ super().__init__(private_key=private_key)
35
+ self.actor = actor
36
+
37
+ def resolve_id(self, uri: str) -> Optional[str]:
38
+ if uri in (self.actor.acct_uri, self.actor.actor_url):
39
+ return self.actor.actor_url
40
+ return None
41
+
42
+ def verify_assertion(self, payload: bytes, signature: bytes, pubkey: str) -> bool:
43
+ try:
44
+ raw_pub = bytes.fromhex(pubkey) if len(pubkey) == 64 else pubkey.encode('utf-8')
45
+ except ValueError:
46
+ return False
47
+ return self._verify_ed25519_bytes(payload, signature, raw_pub)
48
+
49
+
50
+ class ActivityPubProtocolPackage(BaseProtocolPackage):
51
+ """The ActivityPub & ActivityStreams 2.0 suite for Fleetmesh."""
52
+
53
+ name: str = 'activitypub'
54
+ version: str = '1.0.0'
55
+ standards: tuple[str, ...] = (
56
+ 'activitypub',
57
+ 'activitystreams-2.0',
58
+ 'RFC-7033',
59
+ 'RFC-9421',
60
+ 'rfc7033-webfinger',
61
+ 'draft-cavage-http-signatures',
62
+ 'rfc9421',
63
+ )
64
+
65
+ def __init__(
66
+ self,
67
+ domain: str = "node.fleetmesh.local",
68
+ username: str = "node",
69
+ actor: Optional[Actor] = None,
70
+ store: Optional[ActivityPubStore] = None,
71
+ private_key: Optional[Any] = None,
72
+ blob_store: Optional[Any] = None,
73
+ strain_ledger: Optional[Any] = None,
74
+ keystore: Optional[Any] = None,
75
+ ):
76
+ self.domain = domain
77
+ self.username = username
78
+ self.keystore = keystore
79
+ self.private_key = private_key
80
+ self.blob_store = blob_store
81
+
82
+ # Storage
83
+ self.store = store or ActivityPubStore(":memory:")
84
+
85
+ # Actor Document
86
+ pub_pem = ""
87
+ if private_key is not None:
88
+ try:
89
+ pub_bytes = private_key.public_key().public_bytes_raw()
90
+ pub_pem = ed25519_pubkey_to_pem(pub_bytes)
91
+ except Exception:
92
+ pass
93
+
94
+ self.actor = actor or Actor(
95
+ domain=domain,
96
+ username=username,
97
+ public_key_pem=pub_pem,
98
+ )
99
+
100
+ # WebFinger Service
101
+ self.webfinger = WebFingerService(self.actor)
102
+
103
+ # Media Manager
104
+ self.media = ActivityPubMediaManager(
105
+ base_url=f"{self.actor.scheme}://{self.actor.domain}",
106
+ blob_store=blob_store,
107
+ )
108
+
109
+ # Federation Service
110
+ self.federation = FederationService(
111
+ store=self.store,
112
+ local_actor=self.actor,
113
+ private_key=private_key,
114
+ strain_score_keeper=StrainScoreKeeper(strain_ledger=strain_ledger),
115
+ )
116
+
117
+ self.config: Mapping[str, Any] = {}
118
+ self._enabled: bool = True
119
+ self._host: Optional[ProtocolHostRouter] = None
120
+
121
+ def configure(self, config: Mapping[str, Any]) -> None:
122
+ self.config = dict(config)
123
+ self._enabled = self.config.get('enabled', True)
124
+ if 'domain' in self.config:
125
+ self.domain = self.config['domain']
126
+ if 'username' in self.config:
127
+ self.username = self.config['username']
128
+
129
+ def identity_adapter(self, keystore: Any) -> Optional[ProtocolIdentityAdapter]:
130
+ return ActivityPubIdentityAdapter(actor=self.actor, private_key=self.private_key)
131
+
132
+ def mount_ingress(self, host: ProtocolHostRouter) -> None:
133
+ if not self._enabled:
134
+ return
135
+ self._host = host
136
+
137
+ from node.transport import ws
138
+
139
+ # 1. WebFinger RFC 7033
140
+ def _handle_webfinger(conn: Any, req: Any) -> bool:
141
+ uri = getattr(req, 'path', '')
142
+ parsed = urllib.parse.urlparse(uri)
143
+ qparams = urllib.parse.parse_qs(parsed.query)
144
+ status, headers, body = self.webfinger.handle_webfinger_request(qparams)
145
+ ws.http_response(
146
+ conn,
147
+ "200 OK" if status == 200 else f"{status} Error",
148
+ body,
149
+ headers.get('Content-Type', 'application/jrd+json'),
150
+ )
151
+ return True
152
+
153
+ host.mount_route('/.well-known/webfinger', _handle_webfinger, methods=('GET',), owner=self.name)
154
+
155
+ # 2. Shared Inbox & Personal Inbox POST
156
+ def _handle_inbox_post(conn: Any, req: Any) -> bool:
157
+ method = getattr(req, 'method', 'POST')
158
+ path = getattr(req, 'path', '')
159
+ raw_headers = getattr(req, 'headers', {}) or {}
160
+ headers = {
161
+ (k.decode('latin-1') if isinstance(k, bytes) else str(k)): (
162
+ v.decode('latin-1') if isinstance(v, bytes) else str(v)
163
+ )
164
+ for k, v in raw_headers.items()
165
+ }
166
+ body = getattr(req, 'body', None)
167
+ if body is None:
168
+ body = ws.read_body(conn, req)
169
+ status, resp_headers, resp_body = self.federation.handle_inbox(method, path, headers, body)
170
+ ws.http_response(
171
+ conn,
172
+ f"{status} OK" if status in (200, 202) else f"{status} Error",
173
+ resp_body,
174
+ resp_headers.get('Content-Type', 'application/json'),
175
+ )
176
+ return True
177
+
178
+ host.mount_route('/inbox', _handle_inbox_post, methods=('POST',), owner=self.name)
179
+ host.mount_route(f"/users/{self.actor.username}/inbox", _handle_inbox_post, methods=('POST',), owner=self.name)
180
+
181
+ # 3. User Actor Profile, Outbox, Followers, Following
182
+ def _handle_user_routes(conn: Any, req: Any) -> bool:
183
+ path = getattr(req, 'path', '').split('?')[0].rstrip('/')
184
+ user_prefix = f"/users/{self.actor.username}"
185
+
186
+ if path == user_prefix:
187
+ # Actor JSON-LD
188
+ body = json.dumps(self.actor.to_jsonld(), indent=2).encode('utf-8')
189
+ ws.http_response(conn, "200 OK", body, "application/activity+json")
190
+ return True
191
+
192
+ if path == f"{user_prefix}/outbox":
193
+ # Outbox OrderedCollection
194
+ acts = self.store.list_outbox(self.actor.actor_url)
195
+ coll = self.store.as_ordered_collection(acts, len(acts), f"{self.actor.actor_url}/outbox")
196
+ body = json.dumps(coll, indent=2).encode('utf-8')
197
+ ws.http_response(conn, "200 OK", body, "application/activity+json")
198
+ return True
199
+
200
+ if path == f"{user_prefix}/followers":
201
+ followers = self.store.get_followers(self.actor.actor_url)
202
+ coll = self.store.as_ordered_collection(followers, len(followers), f"{self.actor.actor_url}/followers")
203
+ body = json.dumps(coll, indent=2).encode('utf-8')
204
+ ws.http_response(conn, "200 OK", body, "application/activity+json")
205
+ return True
206
+
207
+ if path == f"{user_prefix}/following":
208
+ following = self.store.get_following(self.actor.actor_url)
209
+ coll = self.store.as_ordered_collection(following, len(following), f"{self.actor.actor_url}/following")
210
+ body = json.dumps(coll, indent=2).encode('utf-8')
211
+ ws.http_response(conn, "200 OK", body, "application/activity+json")
212
+ return True
213
+
214
+ ws.http_response(conn, "404 Not Found", b'{"error": "not found"}', "application/json")
215
+ return True
216
+
217
+ host.mount_route(f"/users/{self.actor.username}", _handle_user_routes, methods=('GET',), owner=self.name)
218
+
219
+ # 4. Media Attachments
220
+ def _handle_media(conn: Any, req: Any) -> bool:
221
+ path = getattr(req, 'path', '')
222
+ status, headers, body = self.media.handle_media_request(path)
223
+ ws.http_response(
224
+ conn,
225
+ "200 OK" if status == 200 else f"{status} Error",
226
+ body,
227
+ headers.get('Content-Type', 'application/octet-stream'),
228
+ )
229
+ return True
230
+
231
+ host.mount_route('/media', _handle_media, methods=('GET',), owner=self.name)
232
+
233
+ def transport_rung(self, context: Any) -> Optional[Any]:
234
+ return None
235
+
236
+ def compartments(self) -> tuple[type[Any], ...]:
237
+ return ()
238
+
239
+ def start(self) -> None:
240
+ pass
241
+
242
+ def stop(self) -> None:
243
+ self.store.close()
244
+
245
+ def health(self) -> ProtocolHealth:
246
+ if not self._enabled:
247
+ return ProtocolHealth(
248
+ healthy=True,
249
+ status="disabled",
250
+ connections_active=0,
251
+ messages_in=0,
252
+ messages_out=0,
253
+ errors=(),
254
+ )
255
+ return ProtocolHealth(
256
+ healthy=True,
257
+ status="serving",
258
+ connections_active=self.store.count_followers(self.actor.actor_url),
259
+ messages_in=self.store.count_inbox(),
260
+ messages_out=self.store.count_outbox(),
261
+ errors=(),
262
+ )
263
+
264
+ def descriptor_capabilities(self) -> tuple[tuple[str, str], ...]:
265
+ if not self._enabled:
266
+ return ()
267
+ return (
268
+ ('c', 'activitypub'),
269
+ ('c', 'activitystreams-2.0'),
270
+ ('c', 'webfinger'),
271
+ )