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
node/compartment/wire.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""WireProtocol: base class for compartments that speak a wire protocol.
|
|
2
|
+
|
|
3
|
+
A compartment that speaks one or more PIURIs declares what it handles
|
|
4
|
+
(HANDLERS) and what it emits (OUTBOUND).
|
|
5
|
+
The base class verifies the declarations at class definition time and
|
|
6
|
+
binds a WireClient (`self.wire`) to manage outbound sends, requests, replies,
|
|
7
|
+
and correlation.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Callable, Mapping, Optional
|
|
12
|
+
|
|
13
|
+
from fleetmesh_core.wire import schema_files
|
|
14
|
+
|
|
15
|
+
from .base import Compartment
|
|
16
|
+
from .handler import Handler
|
|
17
|
+
from .problem import Problem, Problems, TypeMismatchProblem
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Outbound:
|
|
21
|
+
"""An outbound message kind declared by a WireProtocol.
|
|
22
|
+
|
|
23
|
+
States what message kind this compartment emits over the wire, what
|
|
24
|
+
reply kinds it expects in return (if an RPC request), and what documentation
|
|
25
|
+
explains its role.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
kind: str,
|
|
31
|
+
reply: Optional[str | tuple[str, ...]] = None,
|
|
32
|
+
reads: tuple[str, ...] = (),
|
|
33
|
+
writes: tuple[str, ...] = (),
|
|
34
|
+
doc: str = '',
|
|
35
|
+
) -> None:
|
|
36
|
+
self.kind: str = str(kind)
|
|
37
|
+
if isinstance(reply, str):
|
|
38
|
+
self.reply: tuple[str, ...] = (reply,)
|
|
39
|
+
elif reply is None:
|
|
40
|
+
self.reply = ()
|
|
41
|
+
else:
|
|
42
|
+
self.reply = tuple(reply)
|
|
43
|
+
self.reads: tuple[str, ...] = tuple(reads)
|
|
44
|
+
self.writes: tuple[str, ...] = tuple(writes)
|
|
45
|
+
self.doc: str = str(doc)
|
|
46
|
+
|
|
47
|
+
def __repr__(self) -> str:
|
|
48
|
+
rep = f", reply={self.reply!r}" if self.reply else ""
|
|
49
|
+
return f"Outbound({self.kind!r}{rep})"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class WireClient:
|
|
53
|
+
"""The client adapter bound to self.wire on a WireProtocol compartment."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, protocol: WireProtocol) -> None:
|
|
56
|
+
self._protocol: WireProtocol = protocol
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def transport(self) -> Any:
|
|
60
|
+
return getattr(self._protocol, 'transport', None)
|
|
61
|
+
|
|
62
|
+
def kind_of(self, message: Mapping[str, Any]) -> Optional[str]:
|
|
63
|
+
"""Extract the message kind relative to this protocol's declared PIURIs."""
|
|
64
|
+
t = str(message.get('type', ''))
|
|
65
|
+
for piuri in self._protocol.piuris:
|
|
66
|
+
if t.startswith(piuri + '/'):
|
|
67
|
+
return t[len(piuri) + 1:]
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
def send(
|
|
71
|
+
self,
|
|
72
|
+
to_pubkey: str,
|
|
73
|
+
message: Mapping[str, Any],
|
|
74
|
+
*,
|
|
75
|
+
anonymous: bool = False,
|
|
76
|
+
stored: bool = False,
|
|
77
|
+
) -> Any:
|
|
78
|
+
"""Dispatch one message to to_pubkey over the transport ladder."""
|
|
79
|
+
tr = self.transport
|
|
80
|
+
if tr is None:
|
|
81
|
+
raise RuntimeError(f"{self._protocol.name}: no transport to send {message.get('type')}")
|
|
82
|
+
try:
|
|
83
|
+
return tr.send(message, to_pubkey, anonymous=anonymous, stored=stored)
|
|
84
|
+
except TypeError:
|
|
85
|
+
try:
|
|
86
|
+
return tr.send(message, to_pubkey, stored=stored)
|
|
87
|
+
except TypeError:
|
|
88
|
+
return tr.send(message, to_pubkey)
|
|
89
|
+
|
|
90
|
+
def request(
|
|
91
|
+
self,
|
|
92
|
+
to_pubkey: str,
|
|
93
|
+
message: Mapping[str, Any],
|
|
94
|
+
*,
|
|
95
|
+
timeout: float = 30.0,
|
|
96
|
+
anonymous: bool = False,
|
|
97
|
+
) -> Optional[Any]:
|
|
98
|
+
"""Dispatch a request message and await a correlated reply."""
|
|
99
|
+
tr = self.transport
|
|
100
|
+
if tr is None:
|
|
101
|
+
raise RuntimeError(f"{self._protocol.name}: no transport to request {message.get('type')}")
|
|
102
|
+
try:
|
|
103
|
+
return tr.request(message, to_pubkey, timeout=timeout, anonymous=anonymous)
|
|
104
|
+
except TypeError:
|
|
105
|
+
try:
|
|
106
|
+
return tr.request(message, to_pubkey, timeout=timeout)
|
|
107
|
+
except TypeError:
|
|
108
|
+
return tr.request(message, to_pubkey)
|
|
109
|
+
|
|
110
|
+
def reply(
|
|
111
|
+
self,
|
|
112
|
+
inbound: Any,
|
|
113
|
+
message: Any,
|
|
114
|
+
*,
|
|
115
|
+
sender: Optional[str] = None,
|
|
116
|
+
anonymous: bool = False,
|
|
117
|
+
) -> Any:
|
|
118
|
+
"""Reply in-band to an inbound message, carrying forward its thread id."""
|
|
119
|
+
m = getattr(inbound, 'message', inbound)
|
|
120
|
+
to_peer = sender or getattr(inbound, 'sender', None)
|
|
121
|
+
if to_peer is None and isinstance(m, dict):
|
|
122
|
+
to_peer = m.get('from')
|
|
123
|
+
if to_peer is None:
|
|
124
|
+
raise ValueError(f"{self._protocol.name}: cannot reply to inbound without sender")
|
|
125
|
+
thid = m.get('thid') or m.get('id') if isinstance(m, dict) else None
|
|
126
|
+
if thid:
|
|
127
|
+
message['thid'] = thid
|
|
128
|
+
return self.send(str(to_peer), message, anonymous=anonymous)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class WireProtocol(Compartment, abstract=True):
|
|
132
|
+
"""A compartment that speaks one or more DIDComm wire protocols (PIURIs)."""
|
|
133
|
+
|
|
134
|
+
piuris: tuple[str, ...] = ()
|
|
135
|
+
HANDLERS: tuple[Handler, ...] = ()
|
|
136
|
+
OUTBOUND: tuple[Outbound, ...] = ()
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def declaration_problems(cls) -> list[str]:
|
|
140
|
+
out = Problems(super().declaration_problems())
|
|
141
|
+
if cls.abstract:
|
|
142
|
+
return out
|
|
143
|
+
|
|
144
|
+
# 1. piuris validation
|
|
145
|
+
piuris = getattr(cls, 'piuris', ())
|
|
146
|
+
if not piuris:
|
|
147
|
+
out.append(Problem(
|
|
148
|
+
f"{cls.__name__} must declare non-empty piuris",
|
|
149
|
+
surface='PIURIS',
|
|
150
|
+
code='missing_piuris',
|
|
151
|
+
target='piuris',
|
|
152
|
+
))
|
|
153
|
+
else:
|
|
154
|
+
for p in piuris:
|
|
155
|
+
if not isinstance(p, str) or not p.startswith('https://'):
|
|
156
|
+
out.append(Problem(
|
|
157
|
+
f"piuri {p!r} must be an https:// URI",
|
|
158
|
+
surface='PIURIS',
|
|
159
|
+
code='invalid_piuri',
|
|
160
|
+
target='piuris',
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
# 2. HANDLERS validation
|
|
164
|
+
handlers = getattr(cls, 'HANDLERS', ())
|
|
165
|
+
if not handlers:
|
|
166
|
+
out.append(Problem(
|
|
167
|
+
f"{cls.__name__} must declare HANDLERS",
|
|
168
|
+
surface='HANDLERS',
|
|
169
|
+
code='missing_handlers',
|
|
170
|
+
target='HANDLERS',
|
|
171
|
+
))
|
|
172
|
+
else:
|
|
173
|
+
for h in handlers:
|
|
174
|
+
if not isinstance(h, Handler):
|
|
175
|
+
out.append(TypeMismatchProblem('HANDLERS', 'Handler', type(h).__name__))
|
|
176
|
+
|
|
177
|
+
# 3. OUTBOUND validation
|
|
178
|
+
outbound = getattr(cls, 'OUTBOUND', ())
|
|
179
|
+
for ob in outbound:
|
|
180
|
+
if not isinstance(ob, Outbound):
|
|
181
|
+
out.append(TypeMismatchProblem('OUTBOUND', 'Outbound', type(ob).__name__))
|
|
182
|
+
|
|
183
|
+
# 4. Schema conformance check against schema/messages.json
|
|
184
|
+
try:
|
|
185
|
+
schema_protocols = schema_files.load_messages().get('protocols', {})
|
|
186
|
+
except Exception:
|
|
187
|
+
schema_protocols = {}
|
|
188
|
+
|
|
189
|
+
known_protocols = [p for p in piuris if p in schema_protocols]
|
|
190
|
+
if known_protocols:
|
|
191
|
+
allowed_msgs = set()
|
|
192
|
+
for p in known_protocols:
|
|
193
|
+
allowed_msgs.update(schema_protocols[p].get('messages', []))
|
|
194
|
+
|
|
195
|
+
for h in handlers:
|
|
196
|
+
if isinstance(h, Handler) and not getattr(h, 'exact', False) and h.kind != '*':
|
|
197
|
+
if h.kind not in allowed_msgs:
|
|
198
|
+
out.append(Problem(
|
|
199
|
+
f"handler {h.kind!r} is not declared in schema for {known_protocols}",
|
|
200
|
+
surface='HANDLERS',
|
|
201
|
+
code='schema_mismatch',
|
|
202
|
+
target=h.kind,
|
|
203
|
+
))
|
|
204
|
+
for ob in outbound:
|
|
205
|
+
if isinstance(ob, Outbound) and ob.kind not in allowed_msgs:
|
|
206
|
+
out.append(Problem(
|
|
207
|
+
f"outbound message {ob.kind!r} is not declared in schema for {known_protocols}",
|
|
208
|
+
surface='OUTBOUND',
|
|
209
|
+
code='schema_mismatch',
|
|
210
|
+
target=ob.kind,
|
|
211
|
+
))
|
|
212
|
+
|
|
213
|
+
return out
|
|
214
|
+
|
|
215
|
+
def __init__(
|
|
216
|
+
self,
|
|
217
|
+
node: Any = None,
|
|
218
|
+
clock: Optional[Callable[[], int]] = None,
|
|
219
|
+
cfg: Optional[Any] = None,
|
|
220
|
+
says: Optional[Any] = None,
|
|
221
|
+
db: Optional[Any] = None,
|
|
222
|
+
**kw: Any,
|
|
223
|
+
) -> None:
|
|
224
|
+
super().__init__(node=node, clock=clock, cfg=cfg, says=says, db=db, **kw)
|
|
225
|
+
self.wire_client: WireClient = WireClient(self)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""WORKERS: background worker threads declared on a compartment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Callable, Optional, Union
|
|
6
|
+
|
|
7
|
+
from .problem import Problem, Problems, ReferenceProblem, TypeMismatchProblem
|
|
8
|
+
|
|
9
|
+
WhenType = Optional[Union[str, Callable[[Any], bool]]]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Worker:
|
|
14
|
+
"""A background worker thread declared on a compartment class.
|
|
15
|
+
|
|
16
|
+
The base Compartment starts declared workers during `start()` and
|
|
17
|
+
signals them to stop and joins them during `stop()`.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
run: The method name on the compartment to run in a thread.
|
|
21
|
+
name: A unique name for the worker/thread (defaults to `run`).
|
|
22
|
+
timeout: How long in seconds to wait for the thread to join on stop().
|
|
23
|
+
daemon: Whether the thread is a daemon thread (defaults to True).
|
|
24
|
+
when: Optional condition to run: attribute name (checked for truthiness)
|
|
25
|
+
or callable receiving the compartment instance.
|
|
26
|
+
"""
|
|
27
|
+
run: str
|
|
28
|
+
name: str = ''
|
|
29
|
+
timeout: float = 1.0
|
|
30
|
+
daemon: bool = True
|
|
31
|
+
when: WhenType = None
|
|
32
|
+
|
|
33
|
+
def __post_init__(self) -> None:
|
|
34
|
+
if not self.name:
|
|
35
|
+
object.__setattr__(self, 'name', self.run)
|
|
36
|
+
|
|
37
|
+
def should_run(self, instance: Any) -> bool:
|
|
38
|
+
"""True if any `when` condition holds."""
|
|
39
|
+
if self.when is None:
|
|
40
|
+
return True
|
|
41
|
+
if callable(self.when):
|
|
42
|
+
return bool(self.when(instance))
|
|
43
|
+
val = getattr(instance, self.when, None)
|
|
44
|
+
if callable(val):
|
|
45
|
+
return bool(val())
|
|
46
|
+
return bool(val)
|
|
47
|
+
|
|
48
|
+
def problems(self, cls: type[Any]) -> list[str]:
|
|
49
|
+
"""Lint this declaration against the target class."""
|
|
50
|
+
out = Problems()
|
|
51
|
+
if not isinstance(self.run, str) or not self.run:
|
|
52
|
+
out.append(TypeMismatchProblem('WORKERS', 'non-empty str', type(self.run).__name__,
|
|
53
|
+
message=f"Worker {self.name!r}: run must be a non-empty string method name"))
|
|
54
|
+
elif not getattr(cls, 'abstract', False) and not hasattr(cls, self.run):
|
|
55
|
+
out.append(ReferenceProblem('WORKERS', f"Worker {self.name!r}", self.run,
|
|
56
|
+
message=f"Worker {self.name!r}: method {self.run!r} does not exist on {cls.__name__}"))
|
|
57
|
+
if self.timeout < 0:
|
|
58
|
+
out.append(Problem(f"Worker {self.name!r}: timeout must be non-negative, got {self.timeout!r}",
|
|
59
|
+
surface='WORKERS', code='negative_timeout', target=self.name))
|
|
60
|
+
return out
|
|
61
|
+
|
node/config/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""The node's configuration as typed, frozen dataclasses.
|
|
2
|
+
|
|
3
|
+
base Block, Problems, ConfigError, the predicates
|
|
4
|
+
records Endpoint, PeerPull, CoverageClaim, TrustDecision, FaceConfig, PolicyConfig
|
|
5
|
+
compartments one block per compartment (BridgeConfig, DVMConfig, ...)
|
|
6
|
+
node NodeConfig: the whole file
|
|
7
|
+
|
|
8
|
+
node.app.config keeps the file I/O and the dict the compartments still read;
|
|
9
|
+
its DEFAULTS and validate_config are derived from here.
|
|
10
|
+
"""
|
|
11
|
+
from .base import Block, ConfigError, Problems
|
|
12
|
+
from .compartments import (AnnounceConfig, BlobReplicationConfig, BlobWireConfig, BlobsConfig, BridgeConfig,
|
|
13
|
+
BridgeEgress, BridgeIngress, COMPARTMENT_BLOCKS, CapacityConfig, STRFRY_MOVED, STRFRY_PLUGIN_REF,
|
|
14
|
+
CashuRail, ComputeConfig, DEFAULT_DVM_KINDS, DEFAULT_EGRESS_KINDS,
|
|
15
|
+
DEFAULT_INGRESS_FILTERS, DEFAULT_INGRESS_KINDS, DEFAULT_MAX_EVENT_BYTES,
|
|
16
|
+
DEFAULT_MAX_FUTURE_SECONDS, DEFAULT_RATE_LIMIT_PER_MIN, DVMConfig, NsiteConfig,
|
|
17
|
+
FetchPricingConfig, LanConfig, MediationConfig, PluginSpec, PluginsConfig, SidecarsConfig, SidecarEntry, SIDECAR_KEYS_MOVED, SIDECAR_MOVED, sidecars_of,
|
|
18
|
+
P2PConfig, PRICING_RULE_KEYS, PROJECT_RELEASE_KEY, PricingRule, default_release_key, SettlementConfig,
|
|
19
|
+
StorageConfig, TRACK_NOT_YET, TransportConfig, UPDATE_POLICIES, UpdatesConfig)
|
|
20
|
+
from .node import DEFAULT_CAPABILITIES, DEFAULT_PROTOCOLS, NodeConfig
|
|
21
|
+
from .records import (CLASSES, COMPLETENESS, CoverageClaim, Endpoint, FaceConfig, HANDOVER, PeerPull,
|
|
22
|
+
PolicyConfig, TIERS, TrustDecision)
|
|
23
|
+
|
|
24
|
+
# A literal list, not built from splats: pyflakes reads __all__ statically and
|
|
25
|
+
# only a literal tells it these re-exports are used.
|
|
26
|
+
__all__ = [
|
|
27
|
+
# base
|
|
28
|
+
'Block', 'ConfigError', 'Problems',
|
|
29
|
+
# node
|
|
30
|
+
'NodeConfig', 'DEFAULT_CAPABILITIES', 'DEFAULT_PROTOCOLS',
|
|
31
|
+
# compartments
|
|
32
|
+
'AnnounceConfig', 'BlobReplicationConfig', 'BlobWireConfig', 'BlobsConfig', 'BridgeConfig', 'BridgeEgress',
|
|
33
|
+
'BridgeIngress', 'COMPARTMENT_BLOCKS', 'STRFRY_MOVED', 'STRFRY_PLUGIN_REF', 'CapacityConfig', 'CashuRail', 'ComputeConfig',
|
|
34
|
+
'DEFAULT_DVM_KINDS', 'DEFAULT_EGRESS_KINDS', 'DEFAULT_INGRESS_FILTERS', 'DEFAULT_INGRESS_KINDS',
|
|
35
|
+
'DEFAULT_MAX_EVENT_BYTES', 'DEFAULT_MAX_FUTURE_SECONDS', 'DEFAULT_RATE_LIMIT_PER_MIN', 'DVMConfig', 'NsiteConfig',
|
|
36
|
+
'FetchPricingConfig', 'LanConfig', 'MediationConfig', 'SidecarsConfig', 'SidecarEntry', 'SIDECAR_KEYS_MOVED', 'SIDECAR_MOVED', 'sidecars_of',
|
|
37
|
+
'P2PConfig', 'PluginSpec', 'PluginsConfig', 'PRICING_RULE_KEYS', 'PricingRule',
|
|
38
|
+
'SettlementConfig', 'StorageConfig', 'TransportConfig', 'UpdatesConfig', 'PROJECT_RELEASE_KEY',
|
|
39
|
+
'default_release_key', 'UPDATE_POLICIES', 'TRACK_NOT_YET',
|
|
40
|
+
# records
|
|
41
|
+
'CLASSES', 'COMPLETENESS', 'CoverageClaim', 'Endpoint', 'FaceConfig', 'HANDOVER', 'PeerPull', 'PolicyConfig',
|
|
42
|
+
'TIERS', 'TrustDecision',
|
|
43
|
+
]
|
node/config/base.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""The pieces every config block is built from.
|
|
2
|
+
|
|
3
|
+
A block is a frozen dataclass with a `from_dict` that reads the operator's
|
|
4
|
+
JSON and a `check` that names every problem in it. Problems are collected,
|
|
5
|
+
not raised one at a time: an operator fixes a file once, not one line per
|
|
6
|
+
restart. The messages are the interface the tests and the operator read, so
|
|
7
|
+
they say which field and what it must be.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import dataclasses
|
|
12
|
+
import json
|
|
13
|
+
from typing import Any, Callable, ClassVar, Mapping, Optional, TypeVar
|
|
14
|
+
|
|
15
|
+
from fleetmesh_core.wire import spelling
|
|
16
|
+
|
|
17
|
+
T = TypeVar('T', bound='Block')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConfigError(ValueError):
|
|
21
|
+
"""config.json says something the node cannot run on. The message lists
|
|
22
|
+
every problem, because an operator fixes a file once, not one line per
|
|
23
|
+
restart."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Problems:
|
|
27
|
+
"""Sentences about what is wrong, in file order."""
|
|
28
|
+
|
|
29
|
+
def __init__(self) -> None:
|
|
30
|
+
self.items: list[str] = []
|
|
31
|
+
|
|
32
|
+
def add(self, sentence: str) -> None:
|
|
33
|
+
self.items.append(sentence)
|
|
34
|
+
|
|
35
|
+
def __bool__(self) -> bool:
|
|
36
|
+
return bool(self.items)
|
|
37
|
+
|
|
38
|
+
def __iter__(self):
|
|
39
|
+
return iter(self.items)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_hex64(v: Any) -> bool:
|
|
43
|
+
return spelling.is_hex(v, 64)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_int(v: Any) -> bool:
|
|
47
|
+
return spelling.is_int(v)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_relay_url(v: Any) -> bool:
|
|
51
|
+
return isinstance(v, str) and v.startswith(('ws://', 'wss://'))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def is_http_url(v: Any) -> bool:
|
|
55
|
+
return isinstance(v, str) and v.startswith(('http://', 'https://'))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def all_str(v: Any) -> bool:
|
|
59
|
+
return isinstance(v, list) and all(isinstance(x, str) for x in v)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def all_int(v: Any) -> bool:
|
|
63
|
+
return isinstance(v, list) and all(is_int(x) for x in v)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def opt(raw: Mapping[str, Any], key: str, pred: Callable[[Any], bool], p: Problems, must: str) -> None:
|
|
67
|
+
"""When `key` is present, it has to satisfy `pred`; otherwise say `must`."""
|
|
68
|
+
if key in raw and not pred(raw[key]):
|
|
69
|
+
p.add(must)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def positive_int(v: Any) -> bool:
|
|
73
|
+
return is_int(v) and v > 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def non_negative_int(v: Any) -> bool:
|
|
77
|
+
return is_int(v) and v >= 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclasses.dataclass(frozen=True)
|
|
81
|
+
class Block:
|
|
82
|
+
"""A typed section of config.json.
|
|
83
|
+
|
|
84
|
+
`check(raw, p)` names what is wrong with the raw object; `from_dict(raw)`
|
|
85
|
+
builds the block from a raw object that `check` passed. Subclasses write
|
|
86
|
+
both: the first is the sentence an operator reads, the second the shape
|
|
87
|
+
the compartment reads. `key` is the top-level name the block lives under,
|
|
88
|
+
`label` the prefix its sentences use (defaults to the key).
|
|
89
|
+
"""
|
|
90
|
+
key: ClassVar[str] = ''
|
|
91
|
+
label: ClassVar[str] = ''
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def check(cls, raw: Any, p: Problems) -> None:
|
|
95
|
+
if not isinstance(raw, Mapping):
|
|
96
|
+
p.add('%s must be an object' % (cls.label or cls.key))
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def from_dict(cls: type[T], raw: Any) -> T:
|
|
100
|
+
raise NotImplementedError
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def parse(cls: type[T], raw: Any) -> T:
|
|
104
|
+
"""check then from_dict, raising ConfigError with every sentence."""
|
|
105
|
+
p = Problems()
|
|
106
|
+
cls.check(raw, p)
|
|
107
|
+
if p:
|
|
108
|
+
raise ConfigError('; '.join(p))
|
|
109
|
+
return cls.from_dict(raw)
|
|
110
|
+
|
|
111
|
+
def to_dict(self) -> Mapping[str, Any]:
|
|
112
|
+
"""The JSON shape, tuples made lists, as the file would carry it."""
|
|
113
|
+
return json.loads(json.dumps(dataclasses.asdict(self)))
|
|
114
|
+
|
|
115
|
+
__all__ = ['Block', 'ConfigError', 'Problems', 'is_hex64', 'is_int', 'is_relay_url', 'is_http_url', 'all_str',
|
|
116
|
+
'all_int', 'opt', 'positive_int', 'non_negative_int', 'Optional']
|