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,320 @@
1
+ """compute/1.0 and /1.1 wire message types, ComputeWireError and the make_* builders.
2
+
3
+ Separate because the message vocabulary is needed by sealing, verification and the session alike and has no dependencies of its own beyond fleetmesh_core.crypto.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import uuid
8
+ from typing import Any, Mapping, Optional, Sequence
9
+
10
+ from fleetmesh_core.wire.wire_types import (
11
+ ComputeAcceptBody, ComputeCancelBody, ComputeProgressBody, ComputeQuoteBody, ComputeRefuteBody,
12
+ ComputeMode, ComputeSealedBody, ComputeSettledBody, ComputeStreamBody, Message, ProgressState,
13
+ ProofFailure, SealedVerify,
14
+ )
15
+
16
+ PIURI: str = 'https://fleetmesh.org/compute/1.0'
17
+ PIURI_V11: str = 'https://fleetmesh.org/compute/1.1'
18
+ QUOTE_TYPE: str = 'https://fleetmesh.org/compute/1.0/quote'
19
+ ACCEPT_TYPE: str = 'https://fleetmesh.org/compute/1.0/accept'
20
+ SEALED_TYPE: str = 'https://fleetmesh.org/compute/1.0/sealed'
21
+ SETTLED_TYPE: str = 'https://fleetmesh.org/compute/1.0/settled'
22
+ REFUTE_TYPE: str = 'https://fleetmesh.org/compute/1.0/refute'
23
+ STREAM_TYPE: str = 'https://fleetmesh.org/compute/1.1/stream'
24
+ PROGRESS_TYPE: str = 'https://fleetmesh.org/compute/1.1/progress'
25
+ CANCEL_TYPE: str = 'https://fleetmesh.org/compute/1.1/cancel'
26
+
27
+ MESSAGES: tuple[str, ...] = (
28
+ 'quote', 'accept', 'sealed', 'settled', 'refute', 'stream', 'progress', 'cancel'
29
+ )
30
+
31
+
32
+ class ComputeWireError(Exception):
33
+ """Raised when a wire message or lifecycle transition is invalid."""
34
+
35
+
36
+ # -------------------------------------------------------------------------
37
+ # DIDComm compute/1.0 Message Builders
38
+ # -------------------------------------------------------------------------
39
+ def make_quote(
40
+ from_did: str,
41
+ to_did: str | Sequence[str],
42
+ ask_id: str,
43
+ price_msat: int,
44
+ invoice: str,
45
+ payment_hash: str,
46
+ thid: Optional[str] = None,
47
+ estimated_duration_s: Optional[int] = None,
48
+ created_time: Optional[int] = None,
49
+ msg_id: Optional[str] = None,
50
+ ) -> Message[ComputeQuoteBody]:
51
+ """Construct a DIDComm compute/1.0/quote message."""
52
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
53
+ body: ComputeQuoteBody = {
54
+ 'ask': ask_id,
55
+ 'price_msat': int(price_msat),
56
+ 'invoice': invoice,
57
+ 'payment_hash': payment_hash,
58
+ }
59
+ if estimated_duration_s is not None:
60
+ body['estimated_duration_s'] = int(estimated_duration_s)
61
+
62
+ msg: Message[ComputeQuoteBody] = {
63
+ 'id': msg_id or str(uuid.uuid4()),
64
+ 'type': QUOTE_TYPE,
65
+ 'from': from_did,
66
+ 'to': to_list,
67
+ 'body': body,
68
+ }
69
+ if thid:
70
+ msg['thid'] = thid
71
+ if created_time is not None:
72
+ msg['created_time'] = int(created_time)
73
+ return msg
74
+
75
+
76
+ def make_accept(
77
+ from_did: str,
78
+ to_did: str | Sequence[str],
79
+ thid: str,
80
+ payment_hash: str,
81
+ job_payload: Mapping[str, Any],
82
+ input_key: Optional[str] = None,
83
+ created_time: Optional[int] = None,
84
+ msg_id: Optional[str] = None,
85
+ ) -> Message[ComputeAcceptBody]:
86
+ """Construct a DIDComm compute/1.0/accept message."""
87
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
88
+ body: ComputeAcceptBody = {
89
+ 'payment_hash': payment_hash,
90
+ 'held': True,
91
+ 'job': dict(job_payload),
92
+ }
93
+ if input_key is not None:
94
+ body['input_key'] = input_key
95
+
96
+ msg: Message[ComputeAcceptBody] = {
97
+ 'id': msg_id or str(uuid.uuid4()),
98
+ 'type': ACCEPT_TYPE,
99
+ 'from': from_did,
100
+ 'to': to_list,
101
+ 'thid': thid,
102
+ 'body': body,
103
+ }
104
+ if created_time is not None:
105
+ msg['created_time'] = int(created_time)
106
+ return msg
107
+
108
+
109
+ def make_sealed(
110
+ from_did: str,
111
+ to_did: str | Sequence[str],
112
+ thid: str,
113
+ ciphertext: str,
114
+ payment_hash: str,
115
+ result: str,
116
+ fuel_used: int,
117
+ verify: Optional[SealedVerify] = None,
118
+ created_time: Optional[int] = None,
119
+ msg_id: Optional[str] = None,
120
+ ) -> Message[ComputeSealedBody]:
121
+ """Construct a DIDComm compute/1.0/sealed message."""
122
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
123
+ body: ComputeSealedBody = {
124
+ 'ciphertext': ciphertext,
125
+ 'payment_hash': payment_hash,
126
+ 'result': result,
127
+ 'fuel_used': int(fuel_used),
128
+ }
129
+ if verify is not None:
130
+ body['verify'] = verify
131
+
132
+ msg: Message[ComputeSealedBody] = {
133
+ 'id': msg_id or str(uuid.uuid4()),
134
+ 'type': SEALED_TYPE,
135
+ 'from': from_did,
136
+ 'to': to_list,
137
+ 'thid': thid,
138
+ 'body': body,
139
+ }
140
+ if created_time is not None:
141
+ msg['created_time'] = int(created_time)
142
+ return msg
143
+
144
+
145
+ def make_settled(
146
+ from_did: str,
147
+ to_did: str | Sequence[str],
148
+ thid: str,
149
+ payment_preimage: str,
150
+ receipt_event: Optional[str] = None,
151
+ created_time: Optional[int] = None,
152
+ msg_id: Optional[str] = None,
153
+ ) -> Message[ComputeSettledBody]:
154
+ """Construct a DIDComm compute/1.0/settled message."""
155
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
156
+ body: ComputeSettledBody = {
157
+ 'payment_preimage': payment_preimage,
158
+ }
159
+ if receipt_event is not None:
160
+ body['receipt_event'] = receipt_event
161
+
162
+ msg: Message[ComputeSettledBody] = {
163
+ 'id': msg_id or str(uuid.uuid4()),
164
+ 'type': SETTLED_TYPE,
165
+ 'from': from_did,
166
+ 'to': to_list,
167
+ 'thid': thid,
168
+ 'body': body,
169
+ }
170
+ if created_time is not None:
171
+ msg['created_time'] = int(created_time)
172
+ return msg
173
+
174
+
175
+ def make_refute(
176
+ from_did: str,
177
+ to_did: str | Sequence[str],
178
+ thid: str,
179
+ job_receipt: str,
180
+ mode: ComputeMode,
181
+ claimed_result: Optional[str] = None,
182
+ actual_result: Optional[str] = None,
183
+ wasi_profile_digest: Optional[str] = None,
184
+ engine: Optional[str] = None,
185
+ engine_version: Optional[str] = None,
186
+ proof_failure: Optional[ProofFailure] = None,
187
+ created_time: Optional[int] = None,
188
+ msg_id: Optional[str] = None,
189
+ ) -> Message[ComputeRefuteBody]:
190
+ """Construct a DIDComm compute/1.0/refute message."""
191
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
192
+ body: ComputeRefuteBody = {
193
+ 'job_receipt': job_receipt,
194
+ 'mode': mode,
195
+ }
196
+ if claimed_result is not None:
197
+ body['claimed_result'] = claimed_result
198
+ if actual_result is not None:
199
+ body['actual_result'] = actual_result
200
+ if wasi_profile_digest is not None:
201
+ body['wasi_profile_digest'] = wasi_profile_digest
202
+ if engine is not None:
203
+ body['engine'] = engine
204
+ if engine_version is not None:
205
+ body['engine_version'] = engine_version
206
+ if proof_failure is not None:
207
+ body['proof_failure'] = proof_failure
208
+
209
+ msg: Message[ComputeRefuteBody] = {
210
+ 'id': msg_id or str(uuid.uuid4()),
211
+ 'type': REFUTE_TYPE,
212
+ 'from': from_did,
213
+ 'to': to_list,
214
+ 'thid': thid,
215
+ 'body': body,
216
+ }
217
+ if created_time is not None:
218
+ msg['created_time'] = int(created_time)
219
+ return msg
220
+
221
+
222
+ def make_stream_chunk(
223
+ from_did: str,
224
+ to_did: str | Sequence[str],
225
+ thid: str,
226
+ seq: int,
227
+ delta: str,
228
+ is_final: bool = False,
229
+ counters: Optional[Mapping[str, Any]] = None,
230
+ created_time: Optional[int] = None,
231
+ msg_id: Optional[str] = None,
232
+ ) -> Message[ComputeStreamBody]:
233
+ """Construct a DIDComm compute/1.1/stream chunk message."""
234
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
235
+ body: ComputeStreamBody = {
236
+ 'seq': int(seq),
237
+ 'delta': str(delta),
238
+ }
239
+ if is_final:
240
+ body['is_final'] = True
241
+ if counters is not None:
242
+ body['counters'] = dict(counters)
243
+
244
+ msg: Message[ComputeStreamBody] = {
245
+ 'id': msg_id or str(uuid.uuid4()),
246
+ 'type': STREAM_TYPE,
247
+ 'from': from_did,
248
+ 'to': to_list,
249
+ 'thid': thid,
250
+ 'body': body,
251
+ }
252
+ if created_time is not None:
253
+ msg['created_time'] = int(created_time)
254
+ return msg
255
+
256
+
257
+ def make_progress(
258
+ from_did: str,
259
+ to_did: str | Sequence[str],
260
+ thid: str,
261
+ state: ProgressState,
262
+ progress: Optional[float] = None,
263
+ elapsed_seconds: Optional[int] = None,
264
+ logs_tail: Optional[str] = None,
265
+ created_time: Optional[int] = None,
266
+ msg_id: Optional[str] = None,
267
+ ) -> Message[ComputeProgressBody]:
268
+ """Construct a DIDComm compute/1.1/progress message."""
269
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
270
+ body: ComputeProgressBody = {
271
+ 'state': state,
272
+ }
273
+ if progress is not None:
274
+ body['progress'] = float(progress)
275
+ if elapsed_seconds is not None:
276
+ body['elapsed_seconds'] = int(elapsed_seconds)
277
+ if logs_tail is not None:
278
+ body['logs_tail'] = str(logs_tail)
279
+
280
+ msg: Message[ComputeProgressBody] = {
281
+ 'id': msg_id or str(uuid.uuid4()),
282
+ 'type': PROGRESS_TYPE,
283
+ 'from': from_did,
284
+ 'to': to_list,
285
+ 'thid': thid,
286
+ 'body': body,
287
+ }
288
+ if created_time is not None:
289
+ msg['created_time'] = int(created_time)
290
+ return msg
291
+
292
+
293
+ def make_cancel(
294
+ from_did: str,
295
+ to_did: str | Sequence[str],
296
+ thid: str,
297
+ reason: str,
298
+ penalty_msat: int = 0,
299
+ created_time: Optional[int] = None,
300
+ msg_id: Optional[str] = None,
301
+ ) -> Message[ComputeCancelBody]:
302
+ """Construct a DIDComm compute/1.1/cancel message."""
303
+ to_list = [to_did] if isinstance(to_did, str) else list(to_did)
304
+ body: ComputeCancelBody = {
305
+ 'reason': str(reason),
306
+ }
307
+ if penalty_msat > 0:
308
+ body['penalty_msat'] = int(penalty_msat)
309
+
310
+ msg: Message[ComputeCancelBody] = {
311
+ 'id': msg_id or str(uuid.uuid4()),
312
+ 'type': CANCEL_TYPE,
313
+ 'from': from_did,
314
+ 'to': to_list,
315
+ 'thid': thid,
316
+ 'body': body,
317
+ }
318
+ if created_time is not None:
319
+ msg['created_time'] = int(created_time)
320
+ return msg
@@ -0,0 +1,147 @@
1
+ """seal_result / open_sealed: symmetric sealing of a job result under a preimage.
2
+
3
+ Separate because sealing is a self-contained primitive used by both the session and the dispute evaluator.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import hashlib
8
+ import os
9
+ from typing import Optional
10
+
11
+ from fleetmesh_core.crypto import nip44
12
+ from .messages import ComputeWireError
13
+
14
+
15
+ from .types import SealedResult
16
+
17
+
18
+ # -------------------------------------------------------------------------
19
+ # Cryptographic Sealing & Accountable Settlement (Phase 4)
20
+ # -------------------------------------------------------------------------
21
+ def seal_result(raw_output: bytes | str, preimage_hex: Optional[str] = None) -> SealedResult:
22
+ """Encrypt raw output under 32-byte preimage k using NIP-44 v2 ChaCha20/HMAC.
23
+
24
+ Returns dict containing:
25
+ ciphertext: base64-encoded encrypted payload
26
+ payment_hash: SHA-256(k) hex
27
+ payment_preimage: k as 64 hex characters
28
+ result: SHA-256 hash of raw_output bytes
29
+ """
30
+ if preimage_hex is None:
31
+ preimage_hex = os.urandom(32).hex()
32
+ k_bytes = bytes.fromhex(preimage_hex)
33
+ if len(k_bytes) != 32:
34
+ raise ComputeWireError('preimage must be 32 bytes')
35
+
36
+ payment_hash = hashlib.sha256(k_bytes).hexdigest()
37
+ raw_bytes = raw_output.encode('utf-8') if isinstance(raw_output, str) else raw_output
38
+ result_hash = hashlib.sha256(raw_bytes).hexdigest()
39
+
40
+ ciphertext = nip44.encrypt(raw_bytes, k_bytes)
41
+ return {
42
+ 'ciphertext': ciphertext,
43
+ 'payment_hash': payment_hash,
44
+ 'payment_preimage': preimage_hex,
45
+ 'result': result_hash,
46
+ 'result_hash': result_hash,
47
+ }
48
+
49
+
50
+ def open_sealed(
51
+ ciphertext: str,
52
+ preimage_hex: str,
53
+ expected_payment_hash: Optional[str] = None,
54
+ expected_result: Optional[str] = None,
55
+ ) -> tuple[bool, Optional[bytes], Optional[str]]:
56
+ """Decrypt sealed output with preimage k and verify SHA-256 hashes.
57
+
58
+ Returns (success, decrypted_bytes, failure_code).
59
+ Failure codes match fleetmesh_core/market.py admissible evidence:
60
+ - 'sealed-key-does-not-open'
61
+ - 'result-hash-mismatch-after-decrypt'
62
+ - 'payment-hash-mismatch'
63
+ """
64
+ try:
65
+ k_bytes = bytes.fromhex(preimage_hex)
66
+ except Exception:
67
+ return False, None, 'sealed-key-does-not-open'
68
+
69
+ if len(k_bytes) != 32:
70
+ return False, None, 'sealed-key-does-not-open'
71
+
72
+ if expected_payment_hash:
73
+ actual_payment_hash = hashlib.sha256(k_bytes).hexdigest()
74
+ if actual_payment_hash != expected_payment_hash:
75
+ return False, None, 'payment-hash-mismatch'
76
+
77
+ try:
78
+ decrypted = nip44.decrypt_bytes(ciphertext, k_bytes)
79
+ except Exception:
80
+ return False, None, 'sealed-key-does-not-open'
81
+
82
+ if expected_result:
83
+ actual_result_hash = hashlib.sha256(decrypted).hexdigest()
84
+ if actual_result_hash != expected_result:
85
+ return False, decrypted, 'result-hash-mismatch-after-decrypt'
86
+
87
+ return True, decrypted, None
88
+
89
+
90
+ def encrypt_stream_frame(
91
+ session_key: bytes,
92
+ seq: int,
93
+ delta: str = '',
94
+ plaintext: Optional[str] = None,
95
+ ) -> str:
96
+ """Encrypt a streaming token delta with ChaCha20Poly1305.
97
+
98
+ Framing per non-deterministic-compute.md § 4.1:
99
+ Frame_i = ChaCha20Poly1305(k_session, seq_i || delta_text)
100
+ Nonce (12 bytes): 4 zero bytes + 8 bytes big-endian seq.
101
+ AAD: 8 bytes big-endian seq.
102
+ Returns: base64-encoded ciphertext (including 16-byte Poly1305 tag).
103
+ """
104
+ import base64
105
+ from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
106
+
107
+ text = delta if plaintext is None else plaintext
108
+ if len(session_key) != 32:
109
+ raise ComputeWireError('session key must be 32 bytes')
110
+ if seq < 0:
111
+ raise ComputeWireError('stream seq cannot be negative')
112
+
113
+ nonce = b'\x00' * 4 + int(seq).to_bytes(8, 'big')
114
+ aad = int(seq).to_bytes(8, 'big')
115
+ cipher = ChaCha20Poly1305(session_key)
116
+ ct = cipher.encrypt(nonce, text.encode('utf-8'), aad)
117
+ return base64.b64encode(ct).decode('ascii')
118
+
119
+
120
+ def decrypt_stream_frame(
121
+ session_key: bytes,
122
+ seq: int,
123
+ frame_b64: str = '',
124
+ ciphertext_b64: Optional[str] = None,
125
+ ) -> str:
126
+ """Decrypt a ChaCha20Poly1305 streaming token frame.
127
+
128
+ Returns decrypted delta string, or raises ComputeWireError if authentication fails.
129
+ """
130
+ import base64
131
+ from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
132
+
133
+ b64_in = frame_b64 if ciphertext_b64 is None else ciphertext_b64
134
+ if len(session_key) != 32:
135
+ raise ComputeWireError('session key must be 32 bytes')
136
+ if seq < 0:
137
+ raise ComputeWireError('stream seq cannot be negative')
138
+
139
+ nonce = b'\x00' * 4 + int(seq).to_bytes(8, 'big')
140
+ aad = int(seq).to_bytes(8, 'big')
141
+ cipher = ChaCha20Poly1305(session_key)
142
+ try:
143
+ raw_ct = base64.b64decode(b64_in.encode('ascii'))
144
+ pt = cipher.decrypt(nonce, raw_ct, aad)
145
+ return pt.decode('utf-8')
146
+ except Exception as e:
147
+ raise ComputeWireError(f'stream frame decryption failed at seq {seq}: {e}')