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,259 @@
|
|
|
1
|
+
"""Python Pluggable Protocol Transducers & Router (`node.protocols.textile.transducers`).
|
|
2
|
+
|
|
3
|
+
Server-side twin of `@fleetmesh/protocols` (`MeshProtocolRouter` & `ProtocolTransducer`),
|
|
4
|
+
projecting and ingesting FleetMesh Textile primitives (`fiber`, `strand`, `spool`, `fabric`,
|
|
5
|
+
`quilt`, `suit`, `fit`, `tombstone`) across `nostr`, `w3c`, `activitypub`, `atproto`,
|
|
6
|
+
`p2panda`, `ssb`, `didcomm`, and `grasp`.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Dict, Iterable, Mapping, MutableMapping, Optional
|
|
12
|
+
|
|
13
|
+
from fleetmesh_core.identity import identity
|
|
14
|
+
from fleetmesh_core.textile.bip340 import schnorr_sign, xonly_pubkey
|
|
15
|
+
from fleetmesh_core.textile.canonical import canonical_json
|
|
16
|
+
from fleetmesh_core.textile.signing import (
|
|
17
|
+
FLEETMESH_STATEMENT_DOMAIN,
|
|
18
|
+
nostr_event_id,
|
|
19
|
+
sign_statement,
|
|
20
|
+
verify_envelope,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
TEXTILE_CONTEXT = identity.FLEETMESH + 'ns/textile/v1'
|
|
24
|
+
LOCAL_AP_DOMAIN = 'fleetmesh.local'
|
|
25
|
+
|
|
26
|
+
DEFAULT_FALLBACK_SK = '0101010101010101010101010101010101010101010101010101010101010101'
|
|
27
|
+
|
|
28
|
+
PROTOCOL_FAMILIES = ('nostr', 'w3c', 'activitypub', 'atproto', 'p2panda', 'ssb', 'didcomm', 'grasp')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PythonProtocolRouter:
|
|
32
|
+
"""Projects and ingests Textile primitive manifests across all 8 FleetMesh protocol families."""
|
|
33
|
+
|
|
34
|
+
def project(
|
|
35
|
+
self,
|
|
36
|
+
family: str,
|
|
37
|
+
layer: str,
|
|
38
|
+
primitive_id: str,
|
|
39
|
+
bao_root: str,
|
|
40
|
+
manifest: Mapping[str, Any],
|
|
41
|
+
*,
|
|
42
|
+
author: Optional[Mapping[str, Any]] = None,
|
|
43
|
+
visibility: str = 'public',
|
|
44
|
+
sk_hex: str = DEFAULT_FALLBACK_SK,
|
|
45
|
+
timestamp_sec: Optional[int] = None,
|
|
46
|
+
) -> MutableMapping[str, Any]:
|
|
47
|
+
fam = family.strip().lower()
|
|
48
|
+
if fam not in PROTOCOL_FAMILIES:
|
|
49
|
+
raise ValueError(f"Unsupported protocol family: {family}")
|
|
50
|
+
ts = int(timestamp_sec or time.time())
|
|
51
|
+
pubkey = xonly_pubkey(sk_hex)
|
|
52
|
+
resolved_author = dict(author or {'identity_id': pubkey[:16], 'did': f'did:nostr:{pubkey}'})
|
|
53
|
+
sig_env = sign_statement(
|
|
54
|
+
{
|
|
55
|
+
'domain': FLEETMESH_STATEMENT_DOMAIN,
|
|
56
|
+
'kind': f'{layer}_root' if layer != 'tombstone' else 'tombstone',
|
|
57
|
+
'root': bao_root,
|
|
58
|
+
'signer_did': f'did:nostr:{pubkey}',
|
|
59
|
+
'signed_at': ts,
|
|
60
|
+
},
|
|
61
|
+
sk_hex,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if fam == 'nostr':
|
|
65
|
+
content = canonical_json({
|
|
66
|
+
'v': 1,
|
|
67
|
+
'domain': FLEETMESH_STATEMENT_DOMAIN,
|
|
68
|
+
'layer': layer,
|
|
69
|
+
'primitive_id': primitive_id,
|
|
70
|
+
'bao_root': bao_root,
|
|
71
|
+
'author': resolved_author,
|
|
72
|
+
'visibility': visibility,
|
|
73
|
+
'manifest': dict(manifest),
|
|
74
|
+
})
|
|
75
|
+
unsigned_ev = {
|
|
76
|
+
'pubkey': pubkey,
|
|
77
|
+
'created_at': ts,
|
|
78
|
+
'kind': 30899,
|
|
79
|
+
'tags': [['d', f'{layer}:{primitive_id}'], ['bao', bao_root], ['layer', layer]],
|
|
80
|
+
'content': content,
|
|
81
|
+
}
|
|
82
|
+
ev_id = nostr_event_id(unsigned_ev)
|
|
83
|
+
ev_sig = schnorr_sign(bytes.fromhex(ev_id), sk_hex)
|
|
84
|
+
wire: MutableMapping[str, Any] = {
|
|
85
|
+
'protocol': 'nostr',
|
|
86
|
+
'event': {**unsigned_ev, 'id': ev_id, 'sig': ev_sig},
|
|
87
|
+
'layer': layer,
|
|
88
|
+
'primitive_id': primitive_id,
|
|
89
|
+
'bao_root': bao_root,
|
|
90
|
+
'author': resolved_author,
|
|
91
|
+
'visibility': visibility,
|
|
92
|
+
'manifest': dict(manifest),
|
|
93
|
+
'signature': sig_env,
|
|
94
|
+
}
|
|
95
|
+
elif fam == 'w3c':
|
|
96
|
+
wire = {
|
|
97
|
+
'@context': ['https://www.w3.org/ns/credentials/v2', TEXTILE_CONTEXT],
|
|
98
|
+
'id': f'urn:uuid:vc-{layer}-{bao_root[:32]}',
|
|
99
|
+
'type': ['VerifiableCredential', f'FleetMeshTextile{layer.capitalize()}Credential'],
|
|
100
|
+
'issuer': {'id': f'did:nostr:{pubkey}'},
|
|
101
|
+
'credentialSubject': {
|
|
102
|
+
'id': f'mesh://{layer}/{bao_root}',
|
|
103
|
+
'layer': layer,
|
|
104
|
+
'primitive_id': primitive_id,
|
|
105
|
+
'bao_root': bao_root,
|
|
106
|
+
'author': resolved_author,
|
|
107
|
+
'visibility': visibility,
|
|
108
|
+
'manifest': dict(manifest),
|
|
109
|
+
},
|
|
110
|
+
'proof': {'envelope': sig_env},
|
|
111
|
+
}
|
|
112
|
+
elif fam == 'activitypub':
|
|
113
|
+
wire = {
|
|
114
|
+
'@context': ['https://www.w3.org/ns/activitystreams', TEXTILE_CONTEXT],
|
|
115
|
+
'id': f'https://{LOCAL_AP_DOMAIN}/ap/activities/{layer}/{bao_root}',
|
|
116
|
+
'type': 'Delete' if layer == 'tombstone' else 'Create',
|
|
117
|
+
'actor': resolved_author.get('did', f'did:nostr:{pubkey}'),
|
|
118
|
+
'object': {
|
|
119
|
+
'id': f'mesh://{layer}/{bao_root}',
|
|
120
|
+
'fleetmesh:layer': layer,
|
|
121
|
+
'fleetmesh:primitiveId': primitive_id,
|
|
122
|
+
'fleetmesh:baoRoot': bao_root,
|
|
123
|
+
'fleetmesh:author': resolved_author,
|
|
124
|
+
'fleetmesh:visibility': visibility,
|
|
125
|
+
'fleetmesh:manifest': dict(manifest),
|
|
126
|
+
'fleetmesh:signature': sig_env,
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
elif fam == 'atproto':
|
|
130
|
+
collection = f'mesh.fleetmesh.textile.{layer}'
|
|
131
|
+
uri = f"at://{resolved_author.get('did', f'did:nostr:{pubkey}')}/{collection}/{bao_root[:13]}"
|
|
132
|
+
wire = {
|
|
133
|
+
'uri': uri,
|
|
134
|
+
'collection': collection,
|
|
135
|
+
'value': {
|
|
136
|
+
'$type': collection,
|
|
137
|
+
'layer': layer,
|
|
138
|
+
'primitiveId': primitive_id,
|
|
139
|
+
'baoRoot': bao_root,
|
|
140
|
+
'author': resolved_author,
|
|
141
|
+
'visibility': visibility,
|
|
142
|
+
'manifest': dict(manifest),
|
|
143
|
+
},
|
|
144
|
+
'commit': {'signature': sig_env},
|
|
145
|
+
}
|
|
146
|
+
else:
|
|
147
|
+
wire = {
|
|
148
|
+
'protocol': fam,
|
|
149
|
+
'layer': layer,
|
|
150
|
+
'primitive_id': primitive_id,
|
|
151
|
+
'bao_root': bao_root,
|
|
152
|
+
'author': resolved_author,
|
|
153
|
+
'visibility': visibility,
|
|
154
|
+
'manifest': dict(manifest),
|
|
155
|
+
'signature': sig_env,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
'protocol': fam,
|
|
160
|
+
'layer': layer,
|
|
161
|
+
'primitive_id': primitive_id,
|
|
162
|
+
'bao_root': bao_root,
|
|
163
|
+
'wire_envelope': wire,
|
|
164
|
+
'projected_at': ts,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def project_all(
|
|
168
|
+
self,
|
|
169
|
+
layer: str,
|
|
170
|
+
primitive_id: str,
|
|
171
|
+
bao_root: str,
|
|
172
|
+
manifest: Mapping[str, Any],
|
|
173
|
+
*,
|
|
174
|
+
families: Optional[Iterable[str]] = None,
|
|
175
|
+
author: Optional[Mapping[str, Any]] = None,
|
|
176
|
+
visibility: str = 'public',
|
|
177
|
+
sk_hex: str = DEFAULT_FALLBACK_SK,
|
|
178
|
+
) -> Dict[str, MutableMapping[str, Any]]:
|
|
179
|
+
targets = list(families) if families is not None else list(PROTOCOL_FAMILIES)
|
|
180
|
+
return {
|
|
181
|
+
fam: self.project(
|
|
182
|
+
fam,
|
|
183
|
+
layer,
|
|
184
|
+
primitive_id,
|
|
185
|
+
bao_root,
|
|
186
|
+
manifest,
|
|
187
|
+
author=author,
|
|
188
|
+
visibility=visibility,
|
|
189
|
+
sk_hex=sk_hex,
|
|
190
|
+
)
|
|
191
|
+
for fam in targets
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
def ingest(self, wire: Any) -> Optional[MutableMapping[str, Any]]:
|
|
195
|
+
if not isinstance(wire, Mapping):
|
|
196
|
+
return None
|
|
197
|
+
if 'credentialSubject' in wire and 'proof' in wire:
|
|
198
|
+
subj = wire['credentialSubject']
|
|
199
|
+
sig = wire['proof'].get('envelope')
|
|
200
|
+
root = str(subj.get('bao_root') or '')
|
|
201
|
+
if not verify_envelope(sig, root=root):
|
|
202
|
+
return None
|
|
203
|
+
return {
|
|
204
|
+
'protocol': 'w3c',
|
|
205
|
+
'layer': subj.get('layer'),
|
|
206
|
+
'primitive_id': subj.get('primitive_id'),
|
|
207
|
+
'bao_root': root,
|
|
208
|
+
'manifest': subj.get('manifest'),
|
|
209
|
+
'verified': True,
|
|
210
|
+
}
|
|
211
|
+
if 'object' in wire and isinstance(wire.get('object'), Mapping) and 'fleetmesh:baoRoot' in wire['object']:
|
|
212
|
+
obj = wire['object']
|
|
213
|
+
root = str(obj.get('fleetmesh:baoRoot') or '')
|
|
214
|
+
sig = obj.get('fleetmesh:signature')
|
|
215
|
+
if not verify_envelope(sig, root=root):
|
|
216
|
+
return None
|
|
217
|
+
return {
|
|
218
|
+
'protocol': 'activitypub',
|
|
219
|
+
'layer': obj.get('fleetmesh:layer'),
|
|
220
|
+
'primitive_id': obj.get('fleetmesh:primitiveId'),
|
|
221
|
+
'bao_root': root,
|
|
222
|
+
'manifest': obj.get('fleetmesh:manifest'),
|
|
223
|
+
'verified': True,
|
|
224
|
+
}
|
|
225
|
+
if isinstance(wire.get('uri'), str) and wire['uri'].startswith('at://'):
|
|
226
|
+
val = wire.get('value') or {}
|
|
227
|
+
root = str(val.get('baoRoot') or '')
|
|
228
|
+
sig = (wire.get('commit') or {}).get('signature')
|
|
229
|
+
if not verify_envelope(sig, root=root):
|
|
230
|
+
return None
|
|
231
|
+
return {
|
|
232
|
+
'protocol': 'atproto',
|
|
233
|
+
'layer': val.get('layer'),
|
|
234
|
+
'primitive_id': val.get('primitiveId'),
|
|
235
|
+
'bao_root': root,
|
|
236
|
+
'manifest': val.get('manifest'),
|
|
237
|
+
'verified': True,
|
|
238
|
+
}
|
|
239
|
+
root = str(wire.get('bao_root') or '')
|
|
240
|
+
sig = wire.get('signature')
|
|
241
|
+
if root and verify_envelope(sig, root=root):
|
|
242
|
+
return {
|
|
243
|
+
'protocol': str(wire.get('protocol') or 'nostr'),
|
|
244
|
+
'layer': wire.get('layer'),
|
|
245
|
+
'primitive_id': wire.get('primitive_id'),
|
|
246
|
+
'bao_root': root,
|
|
247
|
+
'manifest': wire.get('manifest'),
|
|
248
|
+
'verified': True,
|
|
249
|
+
}
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
default_python_protocol_router = PythonProtocolRouter()
|
|
254
|
+
|
|
255
|
+
__all__ = [
|
|
256
|
+
"PROTOCOL_FAMILIES",
|
|
257
|
+
"PythonProtocolRouter",
|
|
258
|
+
"default_python_protocol_router",
|
|
259
|
+
]
|
node/release.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""What a running node knows about itself: name, semver, commit, protocol.
|
|
2
|
+
|
|
3
|
+
Before this the node had a protocol version (`fleetmesh/0.1-draft@<digest>`) and
|
|
4
|
+
nothing else - "fleetmesh is not installed as a package; the node runs from
|
|
5
|
+
the checkout" (pyproject.toml). An update check needs a left-hand side:
|
|
6
|
+
"0.9.4 is available" means nothing until the node can say "I run 0.9.3".
|
|
7
|
+
This module is that left-hand side, and it imports nothing heavy on purpose:
|
|
8
|
+
`node.config` reads it, and the config layer must stay importable on a bare
|
|
9
|
+
host (see test_config_contract).
|
|
10
|
+
|
|
11
|
+
node/RELEASE one line, the semver. Edited by a reviewed release commit,
|
|
12
|
+
the same way every other pin in this tree moves.
|
|
13
|
+
node/COMMIT written by scripts/release.sh into the packaged tree; a
|
|
14
|
+
checkout has .git instead and asks it.
|
|
15
|
+
node/PROTOCOL the schema digest, stamped likewise; a checkout reads
|
|
16
|
+
schema/DIGEST.
|
|
17
|
+
node/PUBLISHER the pubkey that signed the release this tree came from,
|
|
18
|
+
stamped likewise; a checkout reads it from the package
|
|
19
|
+
manifest. "The key that made me" - the updates compartment
|
|
20
|
+
listens to it unless the operator writes another.
|
|
21
|
+
|
|
22
|
+
`source` says which of those this is. A checkout is a developer's tree, not
|
|
23
|
+
an installed release, and nothing that installs releases should touch it.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import dataclasses
|
|
28
|
+
import os
|
|
29
|
+
import re
|
|
30
|
+
import subprocess
|
|
31
|
+
from typing import Optional
|
|
32
|
+
|
|
33
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
34
|
+
_ROOT = os.path.dirname(_HERE)
|
|
35
|
+
NAME: str = 'fleetmesh-node'
|
|
36
|
+
|
|
37
|
+
# semver grammar and order are a protocol matter (operations plane), so they
|
|
38
|
+
# live in the reference. fleetmesh_core.peering.release is stdlib-only, which keeps this module
|
|
39
|
+
# importable on a bare host (test_config_contract).
|
|
40
|
+
from fleetmesh_core.peering.release import NAME as _REF_NAME, SEMVER, classify, compare, parse_semver # noqa: E402
|
|
41
|
+
|
|
42
|
+
__all__ = ['NAME', 'SEMVER', 'Release', 'classify', 'compare', 'parse_semver', 'running']
|
|
43
|
+
|
|
44
|
+
assert NAME == _REF_NAME, 'node.release.NAME drifted from fleetmesh_core.peering.release.NAME'
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclasses.dataclass(frozen=True)
|
|
48
|
+
class Release:
|
|
49
|
+
name: str
|
|
50
|
+
version: str
|
|
51
|
+
protocol: str # the schema digest this tree speaks
|
|
52
|
+
commit: Optional[str] = None
|
|
53
|
+
source: str = 'checkout' # 'release' | 'checkout'
|
|
54
|
+
publisher: Optional[str] = None # who signed the release this tree came from
|
|
55
|
+
|
|
56
|
+
def as_dict(self) -> dict[str, Optional[str]]:
|
|
57
|
+
return {'name': self.name, 'version': self.version, 'protocol': self.protocol,
|
|
58
|
+
'commit': self.commit, 'source': self.source, 'publisher': self.publisher}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _read(path: str) -> Optional[str]:
|
|
62
|
+
try:
|
|
63
|
+
with open(path, encoding='utf-8') as f:
|
|
64
|
+
v = f.read().strip()
|
|
65
|
+
return v or None
|
|
66
|
+
except OSError:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _git_commit(root: str) -> Optional[str]:
|
|
71
|
+
if not os.path.isdir(os.path.join(root, '.git')):
|
|
72
|
+
return None
|
|
73
|
+
try:
|
|
74
|
+
out = subprocess.run(['git', '-C', root, 'rev-parse', 'HEAD'], capture_output=True, text=True, timeout=5)
|
|
75
|
+
except (OSError, subprocess.SubprocessError):
|
|
76
|
+
return None
|
|
77
|
+
sha = out.stdout.strip()
|
|
78
|
+
return sha if out.returncode == 0 and re.fullmatch(r'[0-9a-f]{40}', sha) else None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _digest() -> str:
|
|
82
|
+
try:
|
|
83
|
+
from fleetmesh_core.wire import schema_files
|
|
84
|
+
return schema_files.digest()
|
|
85
|
+
except Exception:
|
|
86
|
+
return ''
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def running(root: str = _ROOT) -> Release:
|
|
90
|
+
"""The release this tree is. Never raises: a tree with no RELEASE file
|
|
91
|
+
is version 0.0.0 from a checkout, which the update check treats as
|
|
92
|
+
"not a release" rather than "behind"."""
|
|
93
|
+
node_dir = os.path.join(root, 'node')
|
|
94
|
+
version = _read(os.path.join(node_dir, 'RELEASE')) or '0.0.0'
|
|
95
|
+
if parse_semver(version) is None:
|
|
96
|
+
version = '0.0.0'
|
|
97
|
+
# a release tree has node/PROTOCOL stamped from the digest it was built
|
|
98
|
+
# with; a checkout speaks whatever fleetmesh_schema it imports. A tree with
|
|
99
|
+
# neither (a bare directory) speaks nothing - that is what '' means.
|
|
100
|
+
protocol = _read(os.path.join(node_dir, 'PROTOCOL')) or (_digest() if os.path.isdir(node_dir) else '')
|
|
101
|
+
stamped = _read(os.path.join(node_dir, 'COMMIT'))
|
|
102
|
+
publisher = _publisher(root, node_dir)
|
|
103
|
+
if stamped and not os.path.isdir(os.path.join(root, '.git')):
|
|
104
|
+
return Release(NAME, version, protocol, commit=stamped, source='release', publisher=publisher)
|
|
105
|
+
return Release(NAME, version, protocol, commit=_git_commit(root) or stamped, source='checkout', publisher=publisher)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _publisher(root: str, node_dir: str) -> Optional[str]:
|
|
109
|
+
"""node/PUBLISHER in a packaged tree; the package manifest in a checkout."""
|
|
110
|
+
v = _read(os.path.join(node_dir, 'PUBLISHER'))
|
|
111
|
+
if v is None:
|
|
112
|
+
try:
|
|
113
|
+
import json
|
|
114
|
+
with open(os.path.join(root, 'packages', NAME, '.npack', 'manifest.json'), encoding='utf-8') as f:
|
|
115
|
+
v = json.load(f).get('publisher')
|
|
116
|
+
except (OSError, ValueError):
|
|
117
|
+
v = None
|
|
118
|
+
v = str(v).strip().lower() if v else None
|
|
119
|
+
return v if v and re.fullmatch(r'[0-9a-f]{64}', v) else None
|
node/storage/__init__.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Node state storage: one base, one dialect per backend.
|
|
2
|
+
|
|
3
|
+
BaseStore the backend-independent half (node/storage/base.py)
|
|
4
|
+
SqliteDialect the default, and the only backend that needs no operator
|
|
5
|
+
PostgresDialect the option, behind psycopg (node/storage/postgres.py)
|
|
6
|
+
Dialect what a second backend has to implement
|
|
7
|
+
Locator which backend a node runs on, decided once from config
|
|
8
|
+
|
|
9
|
+
The design and the census it rests on are docs/storage-backends.md; the order
|
|
10
|
+
of work is docs/storage-plan-2026-10-08.md. All stores build on `SqliteStore`
|
|
11
|
+
or `BaseStore`.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Optional
|
|
16
|
+
|
|
17
|
+
from .adapter import (DatabaseAdapter, PostgresAdapter, PsqlAdapter,
|
|
18
|
+
SqliteAdapter, adapter_from_locator, create_adapter)
|
|
19
|
+
from .base import BaseStore, LEASE_SECONDS, StoreLease, StoreOwnedError
|
|
20
|
+
from .dialect import Dialect, SqliteDialect, ensure_incremental_vacuum
|
|
21
|
+
from .locator import Locator, StorageBackend, redact
|
|
22
|
+
from .postgres import PostgresDialect
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseSqliteStore(BaseStore):
|
|
26
|
+
"""BaseStore bound to SQLite. The primary store base for SQLite-backed node stores."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, path: str = ':memory:', timeout: float = 5.0,
|
|
29
|
+
dialect: Optional[object] = None, **kw: Any) -> None:
|
|
30
|
+
super().__init__(path, timeout, dialect or SqliteDialect(), **kw) # type: ignore[arg-type]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Canonical name for SQLite store base
|
|
34
|
+
SqliteStore = BaseSqliteStore
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Every backend the node knows how to open. PostgresDialect is listed here
|
|
39
|
+
# whether or not its driver is installed: constructing one imports nothing,
|
|
40
|
+
# and its `errors()` answers with an empty tuple when psycopg is missing, so
|
|
41
|
+
# store_errors() below stays correct on a host that only has SQLite.
|
|
42
|
+
DIALECTS: tuple[type, ...] = (SqliteDialect, PostgresDialect)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def store_errors() -> tuple[type[BaseException], ...]:
|
|
46
|
+
"""Every error type a configured backend's driver raises.
|
|
47
|
+
|
|
48
|
+
For `except store_errors():` where the old code said `except
|
|
49
|
+
sqlite3.Error:` - a narrow catch that stays narrow, and stays correct when
|
|
50
|
+
a second driver with its own exception hierarchy is added. Catching
|
|
51
|
+
`Exception` instead would be portable and would also swallow bugs.
|
|
52
|
+
"""
|
|
53
|
+
out: tuple[type[BaseException], ...] = ()
|
|
54
|
+
for d in DIALECTS:
|
|
55
|
+
out = out + tuple(d().errors())
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
__all__ = ['BaseSqliteStore', 'SqliteStore', 'BaseStore', 'StoreOwnedError', 'StoreLease', 'LEASE_SECONDS',
|
|
59
|
+
'Dialect', 'SqliteDialect', 'PostgresDialect', 'Locator', 'StorageBackend', 'redact',
|
|
60
|
+
'ensure_incremental_vacuum', 'DIALECTS', 'store_errors',
|
|
61
|
+
'DatabaseAdapter', 'SqliteAdapter', 'PsqlAdapter', 'PostgresAdapter',
|
|
62
|
+
'create_adapter', 'adapter_from_locator', 'AccountStore', 'GrantStore',
|
|
63
|
+
'ProjectedNodeStore', 'TableProjector', 'CausalProjector',
|
|
64
|
+
'ShardCoverageManager', 'PeerStandingStore']
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def __getattr__(name: str) -> Any:
|
|
68
|
+
if name == 'AccountStore':
|
|
69
|
+
from .accounts import AccountStore
|
|
70
|
+
return AccountStore
|
|
71
|
+
if name == 'GrantStore':
|
|
72
|
+
from .grants import GrantStore
|
|
73
|
+
return GrantStore
|
|
74
|
+
if name in ('ProjectedNodeStore', 'TableProjector', 'CausalProjector'):
|
|
75
|
+
from . import projected as _proj
|
|
76
|
+
return getattr(_proj, name)
|
|
77
|
+
if name == 'ShardCoverageManager':
|
|
78
|
+
from .sharding import ShardCoverageManager
|
|
79
|
+
return ShardCoverageManager
|
|
80
|
+
if name == 'PeerStandingStore':
|
|
81
|
+
from .standing import PeerStandingStore
|
|
82
|
+
return PeerStandingStore
|
|
83
|
+
if name in ('accounts', 'grants', 'projected', 'sharding', 'standing'):
|
|
84
|
+
import importlib
|
|
85
|
+
return importlib.import_module(f'.{name}', __package__)
|
|
86
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
87
|
+
|
|
88
|
+
|