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.
- fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
- fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
- fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
- fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
- fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
- fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
- node/RELEASE +1 -0
- node/__init__.py +35 -0
- node/app/__init__.py +49 -0
- node/app/commands.py +265 -0
- node/app/config.py +131 -0
- node/app/control.py +670 -0
- node/app/core.py +867 -0
- node/app/plugins.py +76 -0
- node/app/router.py +83 -0
- node/app/socket.py +431 -0
- node/app/systemd.py +36 -0
- node/app/views.py +204 -0
- node/channels/__init__.py +378 -0
- node/channels/bus.py +331 -0
- node/channels/journal.py +46 -0
- node/commands/__init__.py +140 -0
- node/commands/adapter.py +210 -0
- node/commands/base.py +278 -0
- node/commands/cluster_cmds.py +183 -0
- node/commands/compute_cmds.py +401 -0
- node/commands/core.py +474 -0
- node/commands/gossip_cmds.py +106 -0
- node/commands/nsite_cmds.py +141 -0
- node/commands/peering.py +402 -0
- node/commands/plugin_cmds.py +135 -0
- node/commands/sidecar_cmds.py +120 -0
- node/commands/sync.py +388 -0
- node/commands/transport_cmds.py +451 -0
- node/commands/video_cmds.py +99 -0
- node/compartment/__init__.py +124 -0
- node/compartment/base.py +868 -0
- node/compartment/command.py +458 -0
- node/compartment/context.py +163 -0
- node/compartment/every.py +108 -0
- node/compartment/handler.py +131 -0
- node/compartment/lifecycle.py +460 -0
- node/compartment/lint.py +388 -0
- node/compartment/need.py +158 -0
- node/compartment/problem.py +521 -0
- node/compartment/refusals.py +120 -0
- node/compartment/registry.py +790 -0
- node/compartment/schema.py +290 -0
- node/compartment/sidecar.py +499 -0
- node/compartment/stores.py +662 -0
- node/compartment/wire.py +225 -0
- node/compartment/worker.py +61 -0
- node/config/__init__.py +43 -0
- node/config/base.py +116 -0
- node/config/change.py +441 -0
- node/config/changestore.py +201 -0
- node/config/compartments.py +1144 -0
- node/config/node.py +318 -0
- node/config/parameter.py +563 -0
- node/config/protocols.py +274 -0
- node/config/records.py +171 -0
- node/examples/__init__.py +1 -0
- node/examples/demo.py +226 -0
- node/examples/follower.py +46 -0
- node/interfaces.py +235 -0
- node/keystore/__init__.py +27 -0
- node/keystore/core.py +103 -0
- node/keystore/enclave.py +688 -0
- node/meshnode.py +111 -0
- node/metrics/__init__.py +396 -0
- node/metrics/adapters.py +102 -0
- node/metrics/face.py +110 -0
- node/planes/__init__.py +21 -0
- node/planes/identity/__init__.py +20 -0
- node/planes/identity/announce/__init__.py +9 -0
- node/planes/identity/announce/compartment.py +447 -0
- node/planes/identity/announce/types.py +26 -0
- node/planes/identity/discovery/__init__.py +9 -0
- node/planes/identity/discovery/compartment.py +522 -0
- node/planes/identity/discovery/types.py +13 -0
- node/planes/identity/linked/__init__.py +1 -0
- node/planes/identity/linked/compartment.py +270 -0
- node/planes/identity/linked/types.py +25 -0
- node/planes/identity/presence/__init__.py +7 -0
- node/planes/identity/presence/compartment.py +409 -0
- node/planes/identity/presence/types.py +14 -0
- node/planes/operations/__init__.py +13 -0
- node/planes/operations/scheduler/__init__.py +9 -0
- node/planes/operations/scheduler/compartment.py +353 -0
- node/planes/operations/scheduler/types.py +21 -0
- node/planes/operations/updates/__init__.py +17 -0
- node/planes/operations/updates/core.py +310 -0
- node/planes/operations/updates/store.py +79 -0
- node/planes/operations/updates/types.py +20 -0
- node/planes/peering/__init__.py +28 -0
- node/planes/peering/budget/__init__.py +43 -0
- node/planes/peering/budget/chain.py +43 -0
- node/planes/peering/budget/core.py +537 -0
- node/planes/peering/budget/evaluation.py +80 -0
- node/planes/peering/budget/meter.py +100 -0
- node/planes/peering/budget/receipts.py +439 -0
- node/planes/peering/budget/shape.py +101 -0
- node/planes/peering/budget/tick.py +110 -0
- node/planes/peering/budget/types.py +67 -0
- node/planes/peering/budget/windows.py +346 -0
- node/planes/peering/capacity/__init__.py +7 -0
- node/planes/peering/capacity/compartment.py +342 -0
- node/planes/peering/capacity/types.py +24 -0
- node/planes/peering/peering/__init__.py +25 -0
- node/planes/peering/peering/core.py +464 -0
- node/planes/peering/peering/envelope.py +142 -0
- node/planes/peering/peering/handshake.py +198 -0
- node/planes/peering/peering/shape.py +80 -0
- node/planes/peering/peering/types.py +39 -0
- node/planes/peering/reporter/__init__.py +42 -0
- node/planes/peering/reporter/constants.py +29 -0
- node/planes/peering/reporter/core.py +299 -0
- node/planes/peering/reporter/finding.py +74 -0
- node/planes/peering/reporter/findings.py +171 -0
- node/planes/peering/reporter/publish.py +285 -0
- node/planes/peering/reporter/reading.py +193 -0
- node/planes/peering/reporter/shape.py +86 -0
- node/planes/peering/reporter/types.py +50 -0
- node/planes/peering/strain/__init__.py +9 -0
- node/planes/peering/strain/compartment.py +204 -0
- node/planes/peering/strain/types.py +21 -0
- node/planes/peering/trust/__init__.py +9 -0
- node/planes/peering/trust/compartment.py +239 -0
- node/planes/peering/trust/types.py +30 -0
- node/planes/services/__init__.py +33 -0
- node/planes/services/compute/__init__.py +160 -0
- node/planes/services/compute/common.py +134 -0
- node/planes/services/compute/compartment.py +681 -0
- node/planes/services/compute/executors/__init__.py +121 -0
- node/planes/services/compute/executors/agent.py +840 -0
- node/planes/services/compute/executors/bacalhau.py +605 -0
- node/planes/services/compute/executors/base.py +127 -0
- node/planes/services/compute/executors/harness.py +439 -0
- node/planes/services/compute/executors/mcp.py +454 -0
- node/planes/services/compute/executors/media.py +898 -0
- node/planes/services/compute/executors/nondet.py +283 -0
- node/planes/services/compute/executors/providers.py +499 -0
- node/planes/services/compute/executors/registry.py +140 -0
- node/planes/services/compute/executors/wasm.py +244 -0
- node/planes/services/compute/executors/zk.py +604 -0
- node/planes/services/compute/jobs.py +227 -0
- node/planes/services/compute/load.py +180 -0
- node/planes/services/compute/market.py +433 -0
- node/planes/services/compute/sandbox.py +282 -0
- node/planes/services/compute/types.py +84 -0
- node/planes/services/compute_wire/__init__.py +58 -0
- node/planes/services/compute_wire/compartment.py +256 -0
- node/planes/services/compute_wire/messages.py +320 -0
- node/planes/services/compute_wire/sealing.py +147 -0
- node/planes/services/compute_wire/session.py +572 -0
- node/planes/services/compute_wire/types.py +49 -0
- node/planes/services/compute_wire/verify.py +331 -0
- node/planes/services/dvm/__init__.py +24 -0
- node/planes/services/dvm/gateway.py +526 -0
- node/planes/services/dvm/protocol.py +35 -0
- node/planes/services/dvm/translate.py +15 -0
- node/planes/services/dvm/types.py +25 -0
- node/planes/services/gateway/__init__.py +40 -0
- node/planes/services/gateway/settle.py +279 -0
- node/planes/services/gateway/translate.py +644 -0
- node/planes/services/gossip/__init__.py +22 -0
- node/planes/services/gossip/core.py +929 -0
- node/planes/services/gossip/envelope.py +154 -0
- node/planes/services/gossip/mesh.py +127 -0
- node/planes/services/gossip/types.py +50 -0
- node/planes/services/nsite/__init__.py +7 -0
- node/planes/services/nsite/client.py +62 -0
- node/planes/services/nsite/compartment.py +752 -0
- node/planes/services/nsite/types.py +44 -0
- node/planes/services/paidfetch/__init__.py +7 -0
- node/planes/services/paidfetch/compartment.py +862 -0
- node/planes/services/paidfetch/types.py +99 -0
- node/planes/services/settle/__init__.py +27 -0
- node/planes/services/settle/compartment.py +384 -0
- node/planes/services/settle/engine.py +456 -0
- node/planes/services/settle/gateways.py +541 -0
- node/planes/services/settle/payment.py +512 -0
- node/planes/services/settle/store.py +74 -0
- node/planes/services/settle/types.py +58 -0
- node/planes/services/sidecars/__init__.py +8 -0
- node/planes/services/sidecars/compartment.py +461 -0
- node/planes/services/sidecars/manifest.py +304 -0
- node/planes/services/sidecars/manifests/10-grasp.json +37 -0
- node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
- node/planes/services/sidecars/manifests/30-magpie.json +28 -0
- node/planes/services/sidecars/types.py +43 -0
- node/planes/services/video/__init__.py +106 -0
- node/planes/services/video/barter.py +407 -0
- node/planes/services/video/cli.py +158 -0
- node/planes/services/video/compartment.py +932 -0
- node/planes/services/video/ingest.py +524 -0
- node/planes/services/video/ledger.py +705 -0
- node/planes/services/video/packager.py +847 -0
- node/planes/services/video/player.py +456 -0
- node/planes/services/video/timed_segments.py +124 -0
- node/planes/services/video/transit.py +478 -0
- node/planes/store/__init__.py +24 -0
- node/planes/store/blob_replication/__init__.py +7 -0
- node/planes/store/blob_replication/compartment.py +496 -0
- node/planes/store/blob_replication/types.py +35 -0
- node/planes/store/blob_wire/__init__.py +48 -0
- node/planes/store/blob_wire/client.py +57 -0
- node/planes/store/blob_wire/core.py +359 -0
- node/planes/store/blob_wire/intake.py +74 -0
- node/planes/store/blob_wire/messages.py +85 -0
- node/planes/store/projected/__init__.py +7 -0
- node/planes/store/projected/compartment.py +258 -0
- node/planes/store/projected/types.py +29 -0
- node/planes/store/retention/__init__.py +7 -0
- node/planes/store/retention/compartment.py +214 -0
- node/planes/store/retention/types.py +12 -0
- node/planes/store/sync/__init__.py +35 -0
- node/planes/store/sync/core.py +311 -0
- node/planes/store/sync/envelope.py +106 -0
- node/planes/store/sync/handover.py +206 -0
- node/planes/store/sync/initiator.py +128 -0
- node/planes/store/sync/responder.py +114 -0
- node/planes/store/sync/rules.py +179 -0
- node/planes/store/sync/shape.py +80 -0
- node/planes/store/sync/types.py +51 -0
- node/planes/transport/__init__.py +23 -0
- node/planes/transport/bridge/__init__.py +13 -0
- node/planes/transport/bridge/compartment.py +785 -0
- node/planes/transport/bridge/ratelimit.py +44 -0
- node/planes/transport/bridge/types.py +43 -0
- node/planes/transport/lan/__init__.py +9 -0
- node/planes/transport/lan/compartment.py +446 -0
- node/planes/transport/lan/types.py +25 -0
- node/planes/transport/link/__init__.py +7 -0
- node/planes/transport/link/compartment.py +257 -0
- node/planes/transport/link/types.py +27 -0
- node/planes/transport/mediation/__init__.py +45 -0
- node/planes/transport/mediation/compartment.py +238 -0
- node/planes/transport/mediation/leaf.py +107 -0
- node/planes/transport/mediation/messages.py +302 -0
- node/planes/transport/mediation/service.py +233 -0
- node/planes/transport/mediation/store.py +37 -0
- node/planes/transport/mediation/types.py +20 -0
- node/planes/transport/p2p/__init__.py +7 -0
- node/planes/transport/p2p/compartment.py +349 -0
- node/planes/transport/p2p/types.py +22 -0
- node/protocols/__init__.py +277 -0
- node/protocols/activitypub/__init__.py +56 -0
- node/protocols/activitypub/actor.py +230 -0
- node/protocols/activitypub/federation.py +490 -0
- node/protocols/activitypub/media.py +145 -0
- node/protocols/activitypub/models.py +286 -0
- node/protocols/activitypub/package.py +271 -0
- node/protocols/activitypub/store.py +564 -0
- node/protocols/arcade/__init__.py +26 -0
- node/protocols/atproto/__init__.py +13 -0
- node/protocols/atproto/firehose.py +126 -0
- node/protocols/atproto/metering.py +165 -0
- node/protocols/atproto/package.py +155 -0
- node/protocols/atproto/repo.py +250 -0
- node/protocols/atproto/xrpc.py +285 -0
- node/protocols/base.py +109 -0
- node/protocols/circles/__init__.py +29 -0
- node/protocols/dm/__init__.py +50 -0
- node/protocols/grasp/__init__.py +22 -0
- node/protocols/isotime.py +15 -0
- node/protocols/nostr/__init__.py +51 -0
- node/protocols/nostr/blobstore.py +383 -0
- node/protocols/nostr/blossom.py +627 -0
- node/protocols/nostr/client.py +377 -0
- node/protocols/nostr/descriptor.py +211 -0
- node/protocols/nostr/eventstore.py +304 -0
- node/protocols/nostr/package.py +182 -0
- node/protocols/nostr/peers.py +109 -0
- node/protocols/nostr/relayserver.py +656 -0
- node/protocols/p2panda/__init__.py +22 -0
- node/protocols/p2panda/identity.py +19 -0
- node/protocols/p2panda/package.py +236 -0
- node/protocols/p2panda/rung.py +152 -0
- node/protocols/p2panda/store.py +273 -0
- node/protocols/rhio/__init__.py +33 -0
- node/protocols/rhio/package.py +275 -0
- node/protocols/rhio/rung.py +136 -0
- node/protocols/rhio/store.py +1423 -0
- node/protocols/ssb/muxrpc.py +256 -0
- node/protocols/ssb/package.py +267 -0
- node/protocols/textile/__init__.py +111 -0
- node/protocols/textile/transducers.py +259 -0
- node/release.py +119 -0
- node/storage/__init__.py +88 -0
- node/storage/accounts.py +305 -0
- node/storage/adapter.py +161 -0
- node/storage/base.py +385 -0
- node/storage/dialect.py +209 -0
- node/storage/grants.py +317 -0
- node/storage/locator.py +150 -0
- node/storage/postgres.py +294 -0
- node/storage/projected.py +953 -0
- node/storage/sharding.py +448 -0
- node/storage/standing.py +568 -0
- node/testnet/__init__.py +9 -0
- node/testnet/cluster.py +731 -0
- node/testnet/fips_daemon.py +230 -0
- node/transport/__init__.py +94 -0
- node/transport/const.py +110 -0
- node/transport/didcomm_inbound.py +251 -0
- node/transport/dnstxt.py +169 -0
- node/transport/fips_rung/__init__.py +67 -0
- node/transport/fips_rung/core.py +666 -0
- node/transport/fips_rung/listener.py +96 -0
- node/transport/fips_rung/types.py +12 -0
- node/transport/host.py +334 -0
- node/transport/iroh_rung/__init__.py +29 -0
- node/transport/iroh_rung/core.py +477 -0
- node/transport/iroh_rung/quic.py +942 -0
- node/transport/iroh_rung/types.py +22 -0
- node/transport/ladder.py +145 -0
- node/transport/lineproto.py +49 -0
- node/transport/mdns.py +303 -0
- node/transport/netfetch.py +283 -0
- node/transport/relay_rung/__init__.py +23 -0
- node/transport/relay_rung/cooldown.py +87 -0
- node/transport/relay_rung/core.py +138 -0
- node/transport/relay_rung/inbound.py +161 -0
- node/transport/relay_rung/ledger.py +104 -0
- node/transport/relay_rung/listen.py +121 -0
- node/transport/relay_rung/outbound.py +181 -0
- node/transport/relay_rung/shape.py +71 -0
- node/transport/relay_rung/types.py +58 -0
- node/transport/relay_rung/wire.py +63 -0
- node/transport/router.py +761 -0
- node/transport/rung.py +276 -0
- node/transport/ssb_rung.py +166 -0
- node/transport/ws.py +502 -0
- sidecar/fleetmesh_announce.py +338 -0
- sidecar/fleetmesh_p2p.py +373 -0
- sidecar/fleetmesh_rhio.py +352 -0
- 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
|
+
]
|