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,331 @@
1
+ """Dispute automation: trusted-vk checks, job-evidence evaluation and dispute reports.
2
+
3
+ Separate because verification re-runs jobs through compute.executors/jobs and must not depend on the session or the Compute subsystem.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import re
9
+ import time
10
+ from typing import Any, Callable, Collection, Mapping, Optional, Sequence
11
+
12
+ from fleetmesh_core.accounting import market
13
+ from fleetmesh_core.crypto import crypto, zk
14
+ from fleetmesh_core.peering import reports
15
+ from fleetmesh_core.wire.interfaces import NostrEvent
16
+ from node.planes.services.compute import executors as compute_executors, jobs as compute_jobs
17
+ from .messages import ComputeWireError
18
+
19
+
20
+ from .types import EvidenceDetail
21
+
22
+ # -------------------------------------------------------------------------
23
+ # Independent Verification & Finding Evaluation
24
+ # -------------------------------------------------------------------------
25
+ def vk_is_trusted(vk_sha256: str, pinned: Optional[Collection[str]] = None) -> bool:
26
+ """Whether a verification-key digest may be used to judge a proof.
27
+
28
+ Trust comes from this node, never from the job: the digest must be in
29
+ zk.VK_REGISTRY (keys registered in-process) or in the operator's pinned
30
+ set. Matching an inline vk to the digest the requester wrote in the same
31
+ payload proves only self-consistency, so that alone is not enough.
32
+ """
33
+ h = str(vk_sha256 or '').lower()
34
+ if not h:
35
+ return False
36
+ if h in zk.VK_REGISTRY:
37
+ return True
38
+ return any(str(p).lower() == h for p in (pinned or ()))
39
+
40
+
41
+ def evaluate_job_evidence(
42
+ declared_mode: str,
43
+ sealed_body: Mapping[str, Any],
44
+ decrypted_output: bytes,
45
+ job_payload: Mapping[str, Any],
46
+ registry: Optional[compute_executors.ExecutorRegistry] = None,
47
+ pinned_vk_digests: Optional[Collection[str]] = None,
48
+ resolve_blob: Optional[Callable[..., Optional[bytes]]] = None,
49
+ ) -> tuple[bool, Optional[str], Optional[EvidenceDetail]]:
50
+ """Evaluate whether job output satisfies the verify mode declared in the ask.
51
+
52
+ ``pinned_vk_digests`` is the operator's configured set of trusted
53
+ verification-key digests (compute.zk_pinned_vks in config); it extends
54
+ zk.VK_REGISTRY when deciding whether an inline vk may be used.
55
+
56
+ Returns (valid, failure_kind, failure_detail).
57
+ Failure kinds match fleetmesh_core/market.py admissible findings:
58
+ - 'rerun-divergence' under sample / dual
59
+ - 'proof-invalid' or 'commitment-mismatch' under proof
60
+ """
61
+ if declared_mode not in market.MODES:
62
+ raise ComputeWireError('unknown verify mode: %r' % declared_mode)
63
+
64
+ claimed_result = sealed_body.get('result', '')
65
+
66
+ if declared_mode in ('sample', 'dual'):
67
+ reg = registry or compute_executors.DEFAULT_REGISTRY
68
+ rerun_res = compute_jobs.run_job(job_payload, registry=reg)
69
+ actual_result = rerun_res['result_hash']
70
+ if actual_result != claimed_result:
71
+ return False, 'rerun-divergence', {
72
+ 'claimed_result': claimed_result,
73
+ 'actual_result': actual_result,
74
+ 'wasi_profile_digest': rerun_res.get('wasi_profile_digest', ''),
75
+ 'engine': rerun_res.get('engine', ''),
76
+ 'engine_version': '1.0',
77
+ }
78
+ return True, None, None
79
+
80
+ if declared_mode == 'proof':
81
+ verify_spec = sealed_body.get('verify', {})
82
+ input_spec = job_payload.get('input', {})
83
+ proof_system = str(verify_spec.get('proof_system') or (input_spec.get('proof_system') if isinstance(input_spec, dict) else '') or 'zk-verify-groth16').lower()
84
+
85
+ # zkVM STARK / Groth16 receipt verification
86
+ if proof_system in ('zkvm', 'zkvm-risc0', 'zkvm-sp1', 'risc0-groth16', 'sp1-groth16', 'risc0-stark', 'sp1-stark'):
87
+ from node.planes.services.compute.executors.zk import ZkVmReceiptVerifier
88
+ pinned_list = list(pinned_vk_digests) if pinned_vk_digests is not None else None
89
+ verifier = ZkVmReceiptVerifier(pinned_control_ids=pinned_list)
90
+ image_id = str(input_spec.get('image_id') if isinstance(input_spec, dict) else '') or str(verify_spec.get('image_id') or '')
91
+ raw_seal = verify_spec.get('proof') or verify_spec.get('seal') or (input_spec.get('proof') if isinstance(input_spec, dict) else '') or b''
92
+ seal: bytes | Mapping[str, Any] = raw_seal if isinstance(raw_seal, (bytes, Mapping)) else str(raw_seal).encode('utf-8')
93
+ res = verifier.verify_receipt(
94
+ image_id=image_id,
95
+ journal=decrypted_output,
96
+ seal=seal,
97
+ proof_system=proof_system,
98
+ )
99
+ if not res.valid:
100
+ return False, 'proof-invalid', {
101
+ 'proof_system': proof_system,
102
+ 'reason': res.error or 'invalid-zkvm-receipt',
103
+ }
104
+ return True, None, None
105
+
106
+ # A verification key travelling with the job is only trusted when the
107
+ # ask committed to it: its canonical digest must equal
108
+ # input_spec['vk_sha256'] AND that digest must be one this node
109
+ # already trusts (zk.VK_REGISTRY, or the operator's pinned set).
110
+ # Otherwise a prover can ship a vk of their own making and any proof
111
+ # under it verifies -- the pairing check would then prove nothing
112
+ # about the statement the requester paid for.
113
+ custom_vk = None
114
+ vk_sha = str(input_spec.get('vk_sha256') or '').lower() if isinstance(input_spec, dict) else ''
115
+ if isinstance(input_spec, dict) and 'vk' in input_spec:
116
+ mismatch: EvidenceDetail = {
117
+ 'proof_system': verify_spec.get('proof_system', 'zk-verify-groth16'),
118
+ 'reason': 'commitment-mismatch',
119
+ }
120
+ refusal: tuple[bool, Optional[str], Optional[EvidenceDetail]] = (False, 'commitment-mismatch', mismatch)
121
+ if not isinstance(input_spec['vk'], dict) or not vk_sha:
122
+ return refusal
123
+ try:
124
+ candidate = zk.VerificationKey.from_dict(input_spec['vk'])
125
+ except Exception:
126
+ return refusal
127
+ if candidate.digest() != vk_sha or not vk_is_trusted(vk_sha, pinned_vk_digests):
128
+ return refusal
129
+ custom_vk = candidate
130
+
131
+ # Check proof verification
132
+ proof_payload = dict(input_spec)
133
+ if 'proof' in verify_spec:
134
+ proof_payload['proof'] = verify_spec['proof']
135
+
136
+ try:
137
+ verif_res = zk.verify_groth16_job(proof_payload, custom_vk=custom_vk)
138
+ if not verif_res.get('valid', False):
139
+ return False, 'proof-invalid', {
140
+ 'proof_system': verify_spec.get('proof_system', 'zk-verify-groth16'),
141
+ 'reason': 'invalid-proof',
142
+ }
143
+ except Exception:
144
+ return False, 'proof-invalid', {
145
+ 'proof_system': verify_spec.get('proof_system', 'zk-verify-groth16'),
146
+ 'reason': 'invalid-proof',
147
+ }
148
+
149
+ return True, None, None
150
+
151
+ if declared_mode == 'schema-bounds':
152
+ try:
153
+ output_text = decrypted_output.decode('utf-8')
154
+ except Exception as e:
155
+ return False, 'schema-validation-failure', {
156
+ 'reason': 'output-failed-json-schema-bounds',
157
+ 'detail': f'output is not valid utf-8: {e}',
158
+ 'error': f'output is not valid utf-8: {e}',
159
+ }
160
+
161
+ raw_grammar = job_payload.get('grammar')
162
+ grammar: Mapping[str, Any] = raw_grammar if isinstance(raw_grammar, Mapping) else {}
163
+ grammar_type = str(grammar.get('type') or ('regex' if grammar.get('guided_regex') else 'json_schema')).lower()
164
+ regex_pattern = grammar.get('guided_regex') or (grammar.get('definition') if grammar_type == 'regex' else None)
165
+
166
+ if regex_pattern:
167
+ try:
168
+ if not re.search(regex_pattern, output_text):
169
+ return False, 'grammar-constraint-violation', {
170
+ 'reason': 'regex-match-failure',
171
+ 'pattern': str(regex_pattern),
172
+ 'detail': f'output does not match regex pattern {regex_pattern}',
173
+ 'error': f'output does not match regex pattern {regex_pattern}',
174
+ }
175
+ except re.error as e:
176
+ return False, 'grammar-constraint-violation', {
177
+ 'reason': 'regex-syntax-error',
178
+ 'pattern': str(regex_pattern),
179
+ 'error': str(e),
180
+ 'detail': str(e),
181
+ }
182
+
183
+ # Resolve schema specification if present (inline or via Blossom CAS CID)
184
+ schema_spec = (
185
+ job_payload.get('schema')
186
+ or grammar.get('json_schema')
187
+ or (grammar.get('definition') if grammar_type == 'json_schema' else None)
188
+ or (job_payload.get('verification') or {}).get('target_schema')
189
+ )
190
+ schema_cid = (
191
+ grammar.get('schema_cid')
192
+ or job_payload.get('schema_cid')
193
+ or (job_payload.get('verification') or {}).get('target_schema_cid')
194
+ )
195
+
196
+ has_schema = bool(schema_spec or schema_cid or grammar_type == 'json_schema')
197
+
198
+ # If a schema is required, validate JSON syntax and schema bounds
199
+ if has_schema:
200
+ try:
201
+ output_json = json.loads(output_text)
202
+ except Exception as e:
203
+ return False, 'schema-validation-failure', {
204
+ 'reason': 'output-failed-json-schema-bounds',
205
+ 'detail': f'invalid json: {e}',
206
+ 'error': f'invalid json: {e}',
207
+ }
208
+
209
+ if not schema_spec and schema_cid:
210
+ blob_store = job_payload.get('blob_store') or (registry.blob_store if registry else None)
211
+ blossom_servers = job_payload.get('blossom_servers')
212
+ resolve_fn = resolve_blob
213
+ if resolve_fn is None:
214
+ try:
215
+ from fleetmesh_core.storage.cas import resolve_blob as _default_res
216
+ resolve_fn = _default_res
217
+ except ImportError:
218
+ resolve_fn = None
219
+ if resolve_fn is not None:
220
+ try:
221
+ schema_bytes = resolve_fn({'sha256': schema_cid}, blob_store=blob_store, blossom_servers=blossom_servers)
222
+ except TypeError:
223
+ schema_bytes = resolve_fn(schema_cid)
224
+ if schema_bytes:
225
+ try:
226
+ schema_spec = json.loads(schema_bytes.decode('utf-8'))
227
+ except Exception:
228
+ pass
229
+
230
+ if isinstance(schema_spec, str) and schema_spec.strip().startswith('{'):
231
+ try:
232
+ schema_spec = json.loads(schema_spec)
233
+ except Exception:
234
+ pass
235
+
236
+ if isinstance(schema_spec, Mapping):
237
+ try:
238
+ import jsonschema
239
+ validator_cls = getattr(jsonschema, 'Draft202012Validator', jsonschema.Draft7Validator)
240
+ validator = validator_cls(schema_spec)
241
+ errors = list(validator.iter_errors(output_json))
242
+ if errors:
243
+ first_err = errors[0]
244
+ return False, 'schema-validation-failure', {
245
+ 'reason': 'schema-validation-failure',
246
+ 'detail': first_err.message,
247
+ 'error': first_err.message,
248
+ 'path': [str(p) for p in first_err.path],
249
+ 'schema_path': [str(p) for p in first_err.schema_path],
250
+ }
251
+ except Exception as e:
252
+ return False, 'schema-validation-failure', {
253
+ 'reason': 'schema-validation-error',
254
+ 'detail': str(e),
255
+ 'error': str(e),
256
+ }
257
+
258
+ return True, None, None
259
+
260
+ return True, None, None
261
+
262
+
263
+ # -------------------------------------------------------------------------
264
+ # Automated Dispute & Report Generation (Kind 30802)
265
+ # -------------------------------------------------------------------------
266
+ def build_compute_dispute_report(
267
+ reporter_sk: str,
268
+ seller_pk: str,
269
+ grant_id: str,
270
+ windows: tuple[int, int] | Sequence[int],
271
+ evidence_kind: str,
272
+ receipt_event: NostrEvent,
273
+ sealed_message: Optional[Mapping[str, Any]] = None,
274
+ evidence_detail: Optional[EvidenceDetail] = None,
275
+ grant_envelope: Optional[NostrEvent] = None,
276
+ now: Optional[int] = None,
277
+ ttl_seconds: int = 86400 * 7,
278
+ ) -> NostrEvent:
279
+ """Build a signed Kind 30802 conformance report citing compute violation evidence.
280
+
281
+ Assigns severity:
282
+ - S3: for delivery fraud ('sealed-key-does-not-open', 'result-hash-mismatch-after-decrypt')
283
+ - S2: for computational divergence ('rerun-divergence', 'proof-invalid', 'commitment-mismatch')
284
+ Carries signed inline evidence per fleetmesh_core/reports.py.
285
+ """
286
+ now_val = int(now if now is not None else time.time())
287
+ reporter_pk = crypto.pubkey(reporter_sk)
288
+
289
+ delivery_frauds = {'sealed-key-does-not-open', 'result-hash-mismatch-after-decrypt'}
290
+ if evidence_kind in delivery_frauds:
291
+ severity = 'S3'
292
+ else:
293
+ severity = 'S2'
294
+
295
+ # Evidence list: Kind 30811 receipt signed by seller
296
+ ev_list: list[tuple[str, str, Any] | tuple[str, str]] = []
297
+ receipt_id = receipt_event.get('id', '')
298
+ if receipt_event:
299
+ # Cited as public event reference
300
+ ev_list.append(('event', receipt_id))
301
+
302
+ claimed = None
303
+ observed = None
304
+ if evidence_detail:
305
+ if 'claimed_result' in evidence_detail and 'actual_result' in evidence_detail:
306
+ claimed = ('result', evidence_detail['claimed_result'])
307
+ observed = ('result', evidence_detail['actual_result'], evidence_detail.get('limit', ''))
308
+
309
+ note_text = f"compute/1.0 dispute: {evidence_kind} against receipt {receipt_id[:16]}"
310
+ if sealed_message:
311
+ note_text += f" (sealed payment_hash {sealed_message.get('body', {}).get('payment_hash', '')[:16]})"
312
+
313
+ unsigned = reports.build(
314
+ reporter_pubkey=reporter_pk,
315
+ subject_pubkey=seller_pk,
316
+ severity=severity,
317
+ grant_id=grant_id,
318
+ windows=windows,
319
+ evidence=ev_list,
320
+ now=now_val,
321
+ ttl_seconds=ttl_seconds,
322
+ observed=observed,
323
+ claimed=claimed,
324
+ content=note_text,
325
+ grant_envelope=grant_envelope,
326
+ )
327
+ # Sign report with reporter's secret key. reports.build returns the
328
+ # unsigned shape; adding 'sig' is what turns it into a NostrEvent.
329
+ report_event: NostrEvent = {**unsigned,
330
+ 'sig': crypto.schnorr_sign(bytes.fromhex(unsigned['id']), reporter_sk)}
331
+ return report_event
@@ -0,0 +1,24 @@
1
+ """NIP-90 Data Vending Machine (DVM) compute gateway for fleetmesh.
2
+
3
+ Translates client-facing NIP-90 compute job requests (kinds 5000-5999) into
4
+ deterministic fleetmesh compute jobs, runs them, and emits kind 6000-6999
5
+ results with kind 7000 feedback. Three seams:
6
+
7
+ protocol.py the kinds, parsing a request, building feedback/result events
8
+ translate.py one function per executor turning a request into a job
9
+ gateway.py the compartment: process, delegate, fan out, commands
10
+ """
11
+ from .protocol import (DEFAULT_SUPPORTED_KINDS, FEEDBACK_KIND, DVMError, build_dvm_feedback, build_dvm_response,
12
+ dvm_response_kind, is_dvm_request, is_dvm_response, parse_dvm_request)
13
+ from .translate import (agent_job, bacalhau_job, dvm_request_to_mesh_job, is_agent, is_bacalhau, is_llm, is_zk,
14
+ llm_job, wasm_job, zk_job)
15
+ from .gateway import DVMGateway
16
+ from .types import DVMDelegationResult, DVMStateReport
17
+
18
+ __all__ = [
19
+ 'DEFAULT_SUPPORTED_KINDS', 'FEEDBACK_KIND', 'DVMError', 'build_dvm_feedback', 'build_dvm_response',
20
+ 'dvm_response_kind', 'is_dvm_request', 'is_dvm_response', 'parse_dvm_request',
21
+ 'agent_job', 'bacalhau_job', 'dvm_request_to_mesh_job', 'is_agent', 'is_bacalhau', 'is_llm', 'is_zk',
22
+ 'llm_job', 'wasm_job', 'zk_job',
23
+ 'DVMGateway', 'DVMDelegationResult', 'DVMStateReport',
24
+ ]