fleetmesh-node 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
  2. fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
  3. fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
  4. fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
  5. fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
  6. fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
  7. node/RELEASE +1 -0
  8. node/__init__.py +35 -0
  9. node/app/__init__.py +49 -0
  10. node/app/commands.py +265 -0
  11. node/app/config.py +131 -0
  12. node/app/control.py +670 -0
  13. node/app/core.py +867 -0
  14. node/app/plugins.py +76 -0
  15. node/app/router.py +83 -0
  16. node/app/socket.py +431 -0
  17. node/app/systemd.py +36 -0
  18. node/app/views.py +204 -0
  19. node/channels/__init__.py +378 -0
  20. node/channels/bus.py +331 -0
  21. node/channels/journal.py +46 -0
  22. node/commands/__init__.py +140 -0
  23. node/commands/adapter.py +210 -0
  24. node/commands/base.py +278 -0
  25. node/commands/cluster_cmds.py +183 -0
  26. node/commands/compute_cmds.py +401 -0
  27. node/commands/core.py +474 -0
  28. node/commands/gossip_cmds.py +106 -0
  29. node/commands/nsite_cmds.py +141 -0
  30. node/commands/peering.py +402 -0
  31. node/commands/plugin_cmds.py +135 -0
  32. node/commands/sidecar_cmds.py +120 -0
  33. node/commands/sync.py +388 -0
  34. node/commands/transport_cmds.py +451 -0
  35. node/commands/video_cmds.py +99 -0
  36. node/compartment/__init__.py +124 -0
  37. node/compartment/base.py +868 -0
  38. node/compartment/command.py +458 -0
  39. node/compartment/context.py +163 -0
  40. node/compartment/every.py +108 -0
  41. node/compartment/handler.py +131 -0
  42. node/compartment/lifecycle.py +460 -0
  43. node/compartment/lint.py +388 -0
  44. node/compartment/need.py +158 -0
  45. node/compartment/problem.py +521 -0
  46. node/compartment/refusals.py +120 -0
  47. node/compartment/registry.py +790 -0
  48. node/compartment/schema.py +290 -0
  49. node/compartment/sidecar.py +499 -0
  50. node/compartment/stores.py +662 -0
  51. node/compartment/wire.py +225 -0
  52. node/compartment/worker.py +61 -0
  53. node/config/__init__.py +43 -0
  54. node/config/base.py +116 -0
  55. node/config/change.py +441 -0
  56. node/config/changestore.py +201 -0
  57. node/config/compartments.py +1144 -0
  58. node/config/node.py +318 -0
  59. node/config/parameter.py +563 -0
  60. node/config/protocols.py +274 -0
  61. node/config/records.py +171 -0
  62. node/examples/__init__.py +1 -0
  63. node/examples/demo.py +226 -0
  64. node/examples/follower.py +46 -0
  65. node/interfaces.py +235 -0
  66. node/keystore/__init__.py +27 -0
  67. node/keystore/core.py +103 -0
  68. node/keystore/enclave.py +688 -0
  69. node/meshnode.py +111 -0
  70. node/metrics/__init__.py +396 -0
  71. node/metrics/adapters.py +102 -0
  72. node/metrics/face.py +110 -0
  73. node/planes/__init__.py +21 -0
  74. node/planes/identity/__init__.py +20 -0
  75. node/planes/identity/announce/__init__.py +9 -0
  76. node/planes/identity/announce/compartment.py +447 -0
  77. node/planes/identity/announce/types.py +26 -0
  78. node/planes/identity/discovery/__init__.py +9 -0
  79. node/planes/identity/discovery/compartment.py +522 -0
  80. node/planes/identity/discovery/types.py +13 -0
  81. node/planes/identity/linked/__init__.py +1 -0
  82. node/planes/identity/linked/compartment.py +270 -0
  83. node/planes/identity/linked/types.py +25 -0
  84. node/planes/identity/presence/__init__.py +7 -0
  85. node/planes/identity/presence/compartment.py +409 -0
  86. node/planes/identity/presence/types.py +14 -0
  87. node/planes/operations/__init__.py +13 -0
  88. node/planes/operations/scheduler/__init__.py +9 -0
  89. node/planes/operations/scheduler/compartment.py +353 -0
  90. node/planes/operations/scheduler/types.py +21 -0
  91. node/planes/operations/updates/__init__.py +17 -0
  92. node/planes/operations/updates/core.py +310 -0
  93. node/planes/operations/updates/store.py +79 -0
  94. node/planes/operations/updates/types.py +20 -0
  95. node/planes/peering/__init__.py +28 -0
  96. node/planes/peering/budget/__init__.py +43 -0
  97. node/planes/peering/budget/chain.py +43 -0
  98. node/planes/peering/budget/core.py +537 -0
  99. node/planes/peering/budget/evaluation.py +80 -0
  100. node/planes/peering/budget/meter.py +100 -0
  101. node/planes/peering/budget/receipts.py +439 -0
  102. node/planes/peering/budget/shape.py +101 -0
  103. node/planes/peering/budget/tick.py +110 -0
  104. node/planes/peering/budget/types.py +67 -0
  105. node/planes/peering/budget/windows.py +346 -0
  106. node/planes/peering/capacity/__init__.py +7 -0
  107. node/planes/peering/capacity/compartment.py +342 -0
  108. node/planes/peering/capacity/types.py +24 -0
  109. node/planes/peering/peering/__init__.py +25 -0
  110. node/planes/peering/peering/core.py +464 -0
  111. node/planes/peering/peering/envelope.py +142 -0
  112. node/planes/peering/peering/handshake.py +198 -0
  113. node/planes/peering/peering/shape.py +80 -0
  114. node/planes/peering/peering/types.py +39 -0
  115. node/planes/peering/reporter/__init__.py +42 -0
  116. node/planes/peering/reporter/constants.py +29 -0
  117. node/planes/peering/reporter/core.py +299 -0
  118. node/planes/peering/reporter/finding.py +74 -0
  119. node/planes/peering/reporter/findings.py +171 -0
  120. node/planes/peering/reporter/publish.py +285 -0
  121. node/planes/peering/reporter/reading.py +193 -0
  122. node/planes/peering/reporter/shape.py +86 -0
  123. node/planes/peering/reporter/types.py +50 -0
  124. node/planes/peering/strain/__init__.py +9 -0
  125. node/planes/peering/strain/compartment.py +204 -0
  126. node/planes/peering/strain/types.py +21 -0
  127. node/planes/peering/trust/__init__.py +9 -0
  128. node/planes/peering/trust/compartment.py +239 -0
  129. node/planes/peering/trust/types.py +30 -0
  130. node/planes/services/__init__.py +33 -0
  131. node/planes/services/compute/__init__.py +160 -0
  132. node/planes/services/compute/common.py +134 -0
  133. node/planes/services/compute/compartment.py +681 -0
  134. node/planes/services/compute/executors/__init__.py +121 -0
  135. node/planes/services/compute/executors/agent.py +840 -0
  136. node/planes/services/compute/executors/bacalhau.py +605 -0
  137. node/planes/services/compute/executors/base.py +127 -0
  138. node/planes/services/compute/executors/harness.py +439 -0
  139. node/planes/services/compute/executors/mcp.py +454 -0
  140. node/planes/services/compute/executors/media.py +898 -0
  141. node/planes/services/compute/executors/nondet.py +283 -0
  142. node/planes/services/compute/executors/providers.py +499 -0
  143. node/planes/services/compute/executors/registry.py +140 -0
  144. node/planes/services/compute/executors/wasm.py +244 -0
  145. node/planes/services/compute/executors/zk.py +604 -0
  146. node/planes/services/compute/jobs.py +227 -0
  147. node/planes/services/compute/load.py +180 -0
  148. node/planes/services/compute/market.py +433 -0
  149. node/planes/services/compute/sandbox.py +282 -0
  150. node/planes/services/compute/types.py +84 -0
  151. node/planes/services/compute_wire/__init__.py +58 -0
  152. node/planes/services/compute_wire/compartment.py +256 -0
  153. node/planes/services/compute_wire/messages.py +320 -0
  154. node/planes/services/compute_wire/sealing.py +147 -0
  155. node/planes/services/compute_wire/session.py +572 -0
  156. node/planes/services/compute_wire/types.py +49 -0
  157. node/planes/services/compute_wire/verify.py +331 -0
  158. node/planes/services/dvm/__init__.py +24 -0
  159. node/planes/services/dvm/gateway.py +526 -0
  160. node/planes/services/dvm/protocol.py +35 -0
  161. node/planes/services/dvm/translate.py +15 -0
  162. node/planes/services/dvm/types.py +25 -0
  163. node/planes/services/gateway/__init__.py +40 -0
  164. node/planes/services/gateway/settle.py +279 -0
  165. node/planes/services/gateway/translate.py +644 -0
  166. node/planes/services/gossip/__init__.py +22 -0
  167. node/planes/services/gossip/core.py +929 -0
  168. node/planes/services/gossip/envelope.py +154 -0
  169. node/planes/services/gossip/mesh.py +127 -0
  170. node/planes/services/gossip/types.py +50 -0
  171. node/planes/services/nsite/__init__.py +7 -0
  172. node/planes/services/nsite/client.py +62 -0
  173. node/planes/services/nsite/compartment.py +752 -0
  174. node/planes/services/nsite/types.py +44 -0
  175. node/planes/services/paidfetch/__init__.py +7 -0
  176. node/planes/services/paidfetch/compartment.py +862 -0
  177. node/planes/services/paidfetch/types.py +99 -0
  178. node/planes/services/settle/__init__.py +27 -0
  179. node/planes/services/settle/compartment.py +384 -0
  180. node/planes/services/settle/engine.py +456 -0
  181. node/planes/services/settle/gateways.py +541 -0
  182. node/planes/services/settle/payment.py +512 -0
  183. node/planes/services/settle/store.py +74 -0
  184. node/planes/services/settle/types.py +58 -0
  185. node/planes/services/sidecars/__init__.py +8 -0
  186. node/planes/services/sidecars/compartment.py +461 -0
  187. node/planes/services/sidecars/manifest.py +304 -0
  188. node/planes/services/sidecars/manifests/10-grasp.json +37 -0
  189. node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
  190. node/planes/services/sidecars/manifests/30-magpie.json +28 -0
  191. node/planes/services/sidecars/types.py +43 -0
  192. node/planes/services/video/__init__.py +106 -0
  193. node/planes/services/video/barter.py +407 -0
  194. node/planes/services/video/cli.py +158 -0
  195. node/planes/services/video/compartment.py +932 -0
  196. node/planes/services/video/ingest.py +524 -0
  197. node/planes/services/video/ledger.py +705 -0
  198. node/planes/services/video/packager.py +847 -0
  199. node/planes/services/video/player.py +456 -0
  200. node/planes/services/video/timed_segments.py +124 -0
  201. node/planes/services/video/transit.py +478 -0
  202. node/planes/store/__init__.py +24 -0
  203. node/planes/store/blob_replication/__init__.py +7 -0
  204. node/planes/store/blob_replication/compartment.py +496 -0
  205. node/planes/store/blob_replication/types.py +35 -0
  206. node/planes/store/blob_wire/__init__.py +48 -0
  207. node/planes/store/blob_wire/client.py +57 -0
  208. node/planes/store/blob_wire/core.py +359 -0
  209. node/planes/store/blob_wire/intake.py +74 -0
  210. node/planes/store/blob_wire/messages.py +85 -0
  211. node/planes/store/projected/__init__.py +7 -0
  212. node/planes/store/projected/compartment.py +258 -0
  213. node/planes/store/projected/types.py +29 -0
  214. node/planes/store/retention/__init__.py +7 -0
  215. node/planes/store/retention/compartment.py +214 -0
  216. node/planes/store/retention/types.py +12 -0
  217. node/planes/store/sync/__init__.py +35 -0
  218. node/planes/store/sync/core.py +311 -0
  219. node/planes/store/sync/envelope.py +106 -0
  220. node/planes/store/sync/handover.py +206 -0
  221. node/planes/store/sync/initiator.py +128 -0
  222. node/planes/store/sync/responder.py +114 -0
  223. node/planes/store/sync/rules.py +179 -0
  224. node/planes/store/sync/shape.py +80 -0
  225. node/planes/store/sync/types.py +51 -0
  226. node/planes/transport/__init__.py +23 -0
  227. node/planes/transport/bridge/__init__.py +13 -0
  228. node/planes/transport/bridge/compartment.py +785 -0
  229. node/planes/transport/bridge/ratelimit.py +44 -0
  230. node/planes/transport/bridge/types.py +43 -0
  231. node/planes/transport/lan/__init__.py +9 -0
  232. node/planes/transport/lan/compartment.py +446 -0
  233. node/planes/transport/lan/types.py +25 -0
  234. node/planes/transport/link/__init__.py +7 -0
  235. node/planes/transport/link/compartment.py +257 -0
  236. node/planes/transport/link/types.py +27 -0
  237. node/planes/transport/mediation/__init__.py +45 -0
  238. node/planes/transport/mediation/compartment.py +238 -0
  239. node/planes/transport/mediation/leaf.py +107 -0
  240. node/planes/transport/mediation/messages.py +302 -0
  241. node/planes/transport/mediation/service.py +233 -0
  242. node/planes/transport/mediation/store.py +37 -0
  243. node/planes/transport/mediation/types.py +20 -0
  244. node/planes/transport/p2p/__init__.py +7 -0
  245. node/planes/transport/p2p/compartment.py +349 -0
  246. node/planes/transport/p2p/types.py +22 -0
  247. node/protocols/__init__.py +277 -0
  248. node/protocols/activitypub/__init__.py +56 -0
  249. node/protocols/activitypub/actor.py +230 -0
  250. node/protocols/activitypub/federation.py +490 -0
  251. node/protocols/activitypub/media.py +145 -0
  252. node/protocols/activitypub/models.py +286 -0
  253. node/protocols/activitypub/package.py +271 -0
  254. node/protocols/activitypub/store.py +564 -0
  255. node/protocols/arcade/__init__.py +26 -0
  256. node/protocols/atproto/__init__.py +13 -0
  257. node/protocols/atproto/firehose.py +126 -0
  258. node/protocols/atproto/metering.py +165 -0
  259. node/protocols/atproto/package.py +155 -0
  260. node/protocols/atproto/repo.py +250 -0
  261. node/protocols/atproto/xrpc.py +285 -0
  262. node/protocols/base.py +109 -0
  263. node/protocols/circles/__init__.py +29 -0
  264. node/protocols/dm/__init__.py +50 -0
  265. node/protocols/grasp/__init__.py +22 -0
  266. node/protocols/isotime.py +15 -0
  267. node/protocols/nostr/__init__.py +51 -0
  268. node/protocols/nostr/blobstore.py +383 -0
  269. node/protocols/nostr/blossom.py +627 -0
  270. node/protocols/nostr/client.py +377 -0
  271. node/protocols/nostr/descriptor.py +211 -0
  272. node/protocols/nostr/eventstore.py +304 -0
  273. node/protocols/nostr/package.py +182 -0
  274. node/protocols/nostr/peers.py +109 -0
  275. node/protocols/nostr/relayserver.py +656 -0
  276. node/protocols/p2panda/__init__.py +22 -0
  277. node/protocols/p2panda/identity.py +19 -0
  278. node/protocols/p2panda/package.py +236 -0
  279. node/protocols/p2panda/rung.py +152 -0
  280. node/protocols/p2panda/store.py +273 -0
  281. node/protocols/rhio/__init__.py +33 -0
  282. node/protocols/rhio/package.py +275 -0
  283. node/protocols/rhio/rung.py +136 -0
  284. node/protocols/rhio/store.py +1423 -0
  285. node/protocols/ssb/muxrpc.py +256 -0
  286. node/protocols/ssb/package.py +267 -0
  287. node/protocols/textile/__init__.py +111 -0
  288. node/protocols/textile/transducers.py +259 -0
  289. node/release.py +119 -0
  290. node/storage/__init__.py +88 -0
  291. node/storage/accounts.py +305 -0
  292. node/storage/adapter.py +161 -0
  293. node/storage/base.py +385 -0
  294. node/storage/dialect.py +209 -0
  295. node/storage/grants.py +317 -0
  296. node/storage/locator.py +150 -0
  297. node/storage/postgres.py +294 -0
  298. node/storage/projected.py +953 -0
  299. node/storage/sharding.py +448 -0
  300. node/storage/standing.py +568 -0
  301. node/testnet/__init__.py +9 -0
  302. node/testnet/cluster.py +731 -0
  303. node/testnet/fips_daemon.py +230 -0
  304. node/transport/__init__.py +94 -0
  305. node/transport/const.py +110 -0
  306. node/transport/didcomm_inbound.py +251 -0
  307. node/transport/dnstxt.py +169 -0
  308. node/transport/fips_rung/__init__.py +67 -0
  309. node/transport/fips_rung/core.py +666 -0
  310. node/transport/fips_rung/listener.py +96 -0
  311. node/transport/fips_rung/types.py +12 -0
  312. node/transport/host.py +334 -0
  313. node/transport/iroh_rung/__init__.py +29 -0
  314. node/transport/iroh_rung/core.py +477 -0
  315. node/transport/iroh_rung/quic.py +942 -0
  316. node/transport/iroh_rung/types.py +22 -0
  317. node/transport/ladder.py +145 -0
  318. node/transport/lineproto.py +49 -0
  319. node/transport/mdns.py +303 -0
  320. node/transport/netfetch.py +283 -0
  321. node/transport/relay_rung/__init__.py +23 -0
  322. node/transport/relay_rung/cooldown.py +87 -0
  323. node/transport/relay_rung/core.py +138 -0
  324. node/transport/relay_rung/inbound.py +161 -0
  325. node/transport/relay_rung/ledger.py +104 -0
  326. node/transport/relay_rung/listen.py +121 -0
  327. node/transport/relay_rung/outbound.py +181 -0
  328. node/transport/relay_rung/shape.py +71 -0
  329. node/transport/relay_rung/types.py +58 -0
  330. node/transport/relay_rung/wire.py +63 -0
  331. node/transport/router.py +761 -0
  332. node/transport/rung.py +276 -0
  333. node/transport/ssb_rung.py +166 -0
  334. node/transport/ws.py +502 -0
  335. sidecar/fleetmesh_announce.py +338 -0
  336. sidecar/fleetmesh_p2p.py +373 -0
  337. sidecar/fleetmesh_rhio.py +352 -0
  338. sidecar/fleetmesh_ssb.py +603 -0
@@ -0,0 +1,304 @@
1
+ """The sidecar manifest: one JSON document that says what a fronted service
2
+ is, which capability its presence declares, and the self-tests that have to
3
+ pass before the node says so. `node/sidecars.py` runs them; this module only
4
+ reads and checks the document, so a developer can lint one without a node.
5
+
6
+ {
7
+ "sidecar": 1,
8
+ "name": "grasp",
9
+ "capability": "<origin>/grasp/1.0",
10
+ "endpoint_tag": "grasp-endpoint",
11
+ "checks": [
12
+ {"name": "http", "method": "HEAD", "path": "/"},
13
+ {"name": "git-smart-http", "method": "GET",
14
+ "path": "/fleetmesh-selftest/none.git/info/refs?service=git-upload-pack",
15
+ "expect": [200, 404],
16
+ "content_type": "application/x-git-upload-pack-advertisement"}
17
+ ],
18
+ "docs": "https://..."
19
+ }
20
+
21
+ Three sources feed the same loader: the manifests shipped in
22
+ `node/sidecar_manifests/` (the built-ins: grasp, ncontainer, magpie), a
23
+ manifest an operator names or inlines in the `sidecars` config block, and
24
+ the ones a plugin class declares. The rules are the same for all three but
25
+ one: only a built-in may declare a capability under the node's own
26
+ origin (the one schema/messages.json's identifiers share), because those identifiers are governed by the schema digest.
27
+ Everyone else uses their own origin, as a dialect does (docs/extending.md).
28
+
29
+ A manifest with no checks is refused. A listener that answers HTTP is not
30
+ evidence that it speaks the protocol, and a capability declared on that
31
+ evidence is a lie a peer acts on.
32
+ """
33
+ from __future__ import annotations
34
+
35
+ import json
36
+ import os
37
+ import re
38
+ from typing import Any, Mapping, NamedTuple, Optional
39
+
40
+ from fleetmesh_core.identity import origin as origin_mod
41
+
42
+ VERSION = 1
43
+ METHODS = ('HEAD', 'GET')
44
+ # A GET/HEAD that answered with any of these is a live handler for the route:
45
+ # 200 is the happy path, 401/402 a gate in front of it, 404 the route saying
46
+ # "no such object" - which only a handler that owns the route can say. A 5xx,
47
+ # a refused connection or a timeout is not; nor is a 404 from a server that
48
+ # never heard of the path, which is why a check's path is the protocol's own.
49
+ LIVE = frozenset({200, 401, 402, 404})
50
+ _LOCAL_MANIFESTS = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'manifests'))
51
+ BUILTIN_DIR = _LOCAL_MANIFESTS if os.path.isdir(_LOCAL_MANIFESTS) else os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../sidecar_manifests'))
52
+
53
+ _NAME = re.compile(r'^[a-z][a-z0-9_-]{0,31}$')
54
+ _TAG = re.compile(r'^[a-z][a-z0-9-]{0,63}$')
55
+ _CAPABILITY = re.compile(r'^https://[^/\s]+/[A-Za-z0-9_.-]+/\d+\.\d+$')
56
+ _KEYS = frozenset({'sidecar', 'name', 'capability', 'endpoint_tag', 'checks', 'docs', 'mount', 'pricing', 'run'})
57
+ # How to run it, for whoever deploys it (ops/roles/meshnode reads this;
58
+ # the node never does - it only asks whether the configured endpoint answers
59
+ # the checks). An image published to 127.0.0.1:<port> on the host, the path
60
+ # the container's /data is kept at, env pairs and arguments after the image.
61
+ # Which tag of the image is a host's business (an operator pins a reviewed
62
+ # one in its inventory); the manifest names the image and the default tag.
63
+ _RUN_KEYS = frozenset({'image', 'port', 'data', 'env', 'cmd'})
64
+ _IMAGE = re.compile(r'^[a-z0-9][a-z0-9._/-]*(:[A-Za-z0-9._-]+)?(@sha256:[0-9a-f]{64})?$')
65
+ _ENV_KEY = re.compile(r'^[A-Z][A-Z0-9_]*$')
66
+ _PRICING_KEYS = frozenset({'mode', 'blob_route'})
67
+ # How access is metered, if at all. The proxy in front of a sidecar can only
68
+ # enforce what it can see: one 402 before a whole session (git smart-http is
69
+ # many small requests per clone), or one per content-addressed object whose
70
+ # sha256 is in the URL (then the node's own per-blob route is reused). A
71
+ # protocol whose objects are already fetched through the node's Blossom
72
+ # face (ncontainer) needs nothing here.
73
+ PRICING_MODES = ('none', 'session', 'blob')
74
+ _MOUNT = re.compile(r'^/[A-Za-z0-9_.-]+$')
75
+ _CHECK_KEYS = frozenset({'name', 'method', 'path', 'expect', 'content_type'})
76
+
77
+
78
+ class ManifestError(ValueError):
79
+ """The document is not a sidecar manifest; the message says why."""
80
+
81
+
82
+ from .types import (
83
+ CheckDict,
84
+ RunDict,
85
+ PricingDict,
86
+ SidecarDict,
87
+ )
88
+
89
+
90
+ class Check(NamedTuple):
91
+ """One self-test: a request the sidecar's protocol must answer."""
92
+ name: str
93
+ method: str
94
+ path: str
95
+ expect: frozenset[int] = LIVE
96
+ content_type: Optional[str] = None # required prefix on a 200, when the protocol fixes one
97
+
98
+ def to_dict(self) -> CheckDict:
99
+ d: CheckDict = {'name': self.name, 'method': self.method, 'path': self.path, 'expect': sorted(self.expect)}
100
+ if self.content_type:
101
+ d['content_type'] = self.content_type
102
+ return d
103
+
104
+
105
+ class Run(NamedTuple):
106
+ """How a deployer runs the sidecar: a container image published to
107
+ 127.0.0.1:<port>, its data at `data` (default /var/lib/meshnode-<name>)."""
108
+ image: str
109
+ port: int
110
+ data: str
111
+ env: tuple[tuple[str, str], ...] = ()
112
+ cmd: str = ''
113
+
114
+ def to_dict(self) -> RunDict:
115
+ d: RunDict = {'image': self.image, 'port': self.port, 'data': self.data}
116
+ if self.env:
117
+ d['env'] = dict(self.env)
118
+ if self.cmd:
119
+ d['cmd'] = self.cmd
120
+ return d
121
+
122
+
123
+ class Sidecar(NamedTuple):
124
+ """One fronted service: the name its config block uses, the capability
125
+ its presence declares, the descriptor tag that carries its URL and the
126
+ checks that have to pass before any of that is said."""
127
+ name: str
128
+ capability: str
129
+ tag: str
130
+ checks: tuple[Check, ...]
131
+ docs: str = ''
132
+ builtin: bool = False
133
+ mount: str = '' # public path prefix the proxy fronts it at; '' = '/' + name
134
+ pricing: str = 'none' # one of PRICING_MODES
135
+ blob_route: Optional[str] = None # pricing blob: anchored regex, group 1 = sha256
136
+ run: Optional[Run] = None # how a deployer runs it; the node does not read it
137
+
138
+ @property
139
+ def mount_path(self) -> str:
140
+ return self.mount or '/' + self.name
141
+
142
+ def to_dict(self) -> SidecarDict:
143
+ d: SidecarDict = {'sidecar': VERSION, 'name': self.name, 'capability': self.capability,
144
+ 'endpoint_tag': self.tag, 'checks': [c.to_dict() for c in self.checks]}
145
+ if self.docs:
146
+ d['docs'] = self.docs
147
+ if self.mount:
148
+ d['mount'] = self.mount
149
+ if self.pricing != 'none':
150
+ pr: PricingDict = {'mode': self.pricing}
151
+ if self.blob_route:
152
+ pr['blob_route'] = self.blob_route
153
+ d['pricing'] = pr
154
+ if self.run:
155
+ d['run'] = self.run.to_dict()
156
+ return d
157
+
158
+
159
+ origin_of = origin_mod.origin_of # one spelling of the rule; see node/origin.py
160
+
161
+
162
+ def _check(raw: Any, i: int) -> Check:
163
+ where = 'checks[%d]' % i
164
+ if not isinstance(raw, Mapping):
165
+ raise ManifestError('%s must be an object' % where)
166
+ extra = set(raw) - _CHECK_KEYS
167
+ if extra:
168
+ raise ManifestError('%s has unknown keys: %s' % (where, ', '.join(sorted(extra))))
169
+ name = raw.get('name')
170
+ if not isinstance(name, str) or not _TAG.match(name):
171
+ raise ManifestError('%s.name must be a short lowercase identifier' % where)
172
+ method = raw.get('method', 'GET')
173
+ if method not in METHODS:
174
+ raise ManifestError('%s.method must be one of %s' % (where, '/'.join(METHODS)))
175
+ path = raw.get('path')
176
+ if not isinstance(path, str) or not path.startswith('/') or any(ch.isspace() for ch in path):
177
+ raise ManifestError('%s.path must start with / and is appended to the endpoint' % where)
178
+ expect: frozenset[int] = LIVE
179
+ if 'expect' in raw:
180
+ e = raw['expect']
181
+ if (not isinstance(e, list) or not e
182
+ or not all(isinstance(s, int) and not isinstance(s, bool) and 100 <= s <= 599 for s in e)):
183
+ raise ManifestError('%s.expect must be a non-empty list of HTTP statuses' % where)
184
+ if any(s >= 500 for s in e):
185
+ raise ManifestError('%s.expect accepts a 5xx: a server error is never proof of a protocol' % where)
186
+ expect = frozenset(e)
187
+ ctype = raw.get('content_type')
188
+ if ctype is not None and (not isinstance(ctype, str) or '/' not in ctype or any(ch.isspace() for ch in ctype)):
189
+ raise ManifestError('%s.content_type must be a media type such as application/json' % where)
190
+ return Check(name, method, path, expect, ctype)
191
+
192
+
193
+ def parse(raw: Any, *, builtin: bool = False, origin: Optional[str] = None) -> Sidecar:
194
+ """Read one manifest. `origin` is the reserved origin (the node's own,
195
+ the schema's) that only a built-in may declare under."""
196
+ if not isinstance(raw, Mapping):
197
+ raise ManifestError('a sidecar manifest is a JSON object')
198
+ if raw.get('sidecar') != VERSION:
199
+ raise ManifestError('"sidecar": %d is the manifest version this node reads' % VERSION)
200
+ extra = set(raw) - _KEYS
201
+ if extra:
202
+ raise ManifestError('unknown keys: %s' % ', '.join(sorted(extra)))
203
+ name = raw.get('name')
204
+ if not isinstance(name, str) or not _NAME.match(name):
205
+ raise ManifestError('name must be a short lowercase identifier (it is the config block and the systemd unit)')
206
+ cap = raw.get('capability')
207
+ if not isinstance(cap, str) or not _CAPABILITY.match(cap):
208
+ raise ManifestError('capability must be https://<origin>/<name>/<major>.<minor>')
209
+ if origin and not builtin and origin_mod.reserved(cap, origin):
210
+ raise ManifestError('capability %s is under %s, which only a built-in manifest may declare; '
211
+ 'use your own origin' % (cap, origin))
212
+ tag = raw.get('endpoint_tag', name + '-endpoint')
213
+ if not isinstance(tag, str) or not _TAG.match(tag):
214
+ raise ManifestError('endpoint_tag must be a short lowercase identifier')
215
+ checks_raw = raw.get('checks')
216
+ if not isinstance(checks_raw, list) or not checks_raw:
217
+ raise ManifestError('checks must be a non-empty list: a sidecar ships its own self-tests')
218
+ checks = tuple(_check(c, i) for i, c in enumerate(checks_raw))
219
+ seen = [c.name for c in checks]
220
+ if len(set(seen)) != len(seen):
221
+ raise ManifestError('check names repeat: %s' % ', '.join(sorted({n for n in seen if seen.count(n) > 1})))
222
+ docs = raw.get('docs', '')
223
+ if not isinstance(docs, str):
224
+ raise ManifestError('docs must be a URL string')
225
+ mount = raw.get('mount', '')
226
+ if not isinstance(mount, str) or (mount and not _MOUNT.match(mount)):
227
+ raise ManifestError('mount must be a single path segment such as /git (the proxy fronts the sidecar there)')
228
+ pricing, blob_route = _pricing(raw.get('pricing'))
229
+ run = _run(raw.get('run'), name)
230
+ return Sidecar(name, cap, tag, checks, docs, builtin, mount, pricing, blob_route, run)
231
+
232
+
233
+ def _run(raw: Any, name: str) -> Optional[Run]:
234
+ if raw is None:
235
+ return None
236
+ if not isinstance(raw, Mapping):
237
+ raise ManifestError('run must be an object {image, port[, data, env, cmd]}')
238
+ extra = set(raw) - _RUN_KEYS
239
+ if extra:
240
+ raise ManifestError('run has unknown keys: %s' % ', '.join(sorted(extra)))
241
+ image = raw.get('image')
242
+ if not isinstance(image, str) or not _IMAGE.match(image):
243
+ raise ManifestError('run.image must be a container image reference such as ghcr.io/org/name:tag')
244
+ port = raw.get('port')
245
+ if not isinstance(port, int) or isinstance(port, bool) or not 1024 <= port <= 65535:
246
+ raise ManifestError('run.port must be an unprivileged TCP port (1024-65535); the host publishes it on loopback')
247
+ data = raw.get('data', '/var/lib/meshnode-' + name)
248
+ if not isinstance(data, str) or not data.startswith('/') or any(ch.isspace() for ch in data):
249
+ raise ManifestError('run.data must be an absolute path; the container sees it as /data')
250
+ env_raw = raw.get('env', {})
251
+ if not isinstance(env_raw, Mapping) or not all(
252
+ isinstance(k, str) and _ENV_KEY.match(k) and isinstance(v, str) for k, v in env_raw.items()):
253
+ raise ManifestError('run.env must map UPPER_CASE names to strings')
254
+ cmd = raw.get('cmd', '')
255
+ if not isinstance(cmd, str):
256
+ raise ManifestError('run.cmd must be a string: the arguments after the image')
257
+ return Run(image, port, data, tuple(sorted(env_raw.items())), cmd)
258
+
259
+
260
+ def _pricing(raw: Any) -> tuple[str, Optional[str]]:
261
+ if raw is None:
262
+ return 'none', None
263
+ if not isinstance(raw, Mapping):
264
+ raise ManifestError('pricing must be an object {mode[, blob_route]}')
265
+ extra = set(raw) - _PRICING_KEYS
266
+ if extra:
267
+ raise ManifestError('pricing has unknown keys: %s' % ', '.join(sorted(extra)))
268
+ mode = raw.get('mode', 'none')
269
+ if mode not in PRICING_MODES:
270
+ raise ManifestError('pricing.mode must be one of %s' % '/'.join(PRICING_MODES))
271
+ route = raw.get('blob_route')
272
+ if mode == 'blob':
273
+ if not isinstance(route, str) or not route.startswith('^'):
274
+ raise ManifestError('pricing.blob_route is required for mode blob: an anchored regex whose first group is the sha256')
275
+ try:
276
+ if re.compile(route).groups < 1:
277
+ raise ManifestError('pricing.blob_route needs a capturing group for the sha256')
278
+ except re.error as e:
279
+ raise ManifestError('pricing.blob_route is not a regex: %s' % e) from e
280
+ elif route is not None:
281
+ raise ManifestError('pricing.blob_route only applies to mode blob')
282
+ return mode, route if mode == 'blob' else None
283
+
284
+
285
+ def load_file(path: str, **kw: Any) -> Sidecar:
286
+ with open(path, 'r', encoding='utf-8') as f:
287
+ try:
288
+ raw = json.load(f)
289
+ except ValueError as e:
290
+ raise ManifestError('%s: not JSON (%s)' % (path, e)) from e
291
+ try:
292
+ return parse(raw, **kw)
293
+ except ManifestError as e:
294
+ raise ManifestError('%s: %s' % (path, e)) from e
295
+
296
+
297
+ def builtins(directory: str = BUILTIN_DIR) -> tuple[Sidecar, ...]:
298
+ """The manifests shipped with the node, in file-name order - that order
299
+ is the order their capabilities appear on the descriptor."""
300
+ out = []
301
+ for fn in sorted(os.listdir(directory)):
302
+ if fn.endswith('.json') and not fn.startswith('.'):
303
+ out.append(load_file(os.path.join(directory, fn), builtin=True))
304
+ return tuple(out)
@@ -0,0 +1,37 @@
1
+ {
2
+ "sidecar": 1,
3
+ "name": "grasp",
4
+ "capability": "https://fleetmesh.org/grasp/1.0",
5
+ "endpoint_tag": "grasp-endpoint",
6
+ "checks": [
7
+ {
8
+ "name": "http",
9
+ "method": "HEAD",
10
+ "path": "/"
11
+ },
12
+ {
13
+ "name": "git-smart-http",
14
+ "method": "GET",
15
+ "path": "/fleetmesh-selftest/none.git/info/refs?service=git-upload-pack",
16
+ "expect": [
17
+ 200,
18
+ 401,
19
+ 402,
20
+ 404
21
+ ],
22
+ "content_type": "application/x-git-upload-pack-advertisement"
23
+ }
24
+ ],
25
+ "mount": "/git",
26
+ "pricing": {
27
+ "mode": "session"
28
+ },
29
+ "run": {
30
+ "image": "ghcr.io/nostr-protocol/ngit-grasp:latest",
31
+ "port": 7861,
32
+ "env": {
33
+ "GRASP_LISTEN": "0.0.0.0:7861",
34
+ "GRASP_DATA_DIR": "/data"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "sidecar": 1,
3
+ "name": "ncontainer",
4
+ "capability": "https://fleetmesh.org/ncontainer/1.0",
5
+ "endpoint_tag": "ncontainer-endpoint",
6
+ "checks": [
7
+ {
8
+ "name": "http",
9
+ "method": "HEAD",
10
+ "path": "/"
11
+ },
12
+ {
13
+ "name": "oci-v2",
14
+ "method": "GET",
15
+ "path": "/v2/",
16
+ "expect": [
17
+ 200,
18
+ 401
19
+ ]
20
+ }
21
+ ],
22
+ "mount": "/oci",
23
+ "run": {
24
+ "image": "ghcr.io/ncontainer/ncontainer-gateway:latest",
25
+ "port": 7862,
26
+ "env": {
27
+ "NCONTAINER_LISTEN": "0.0.0.0:7862",
28
+ "NCONTAINER_DATA_DIR": "/data"
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "sidecar": 1,
3
+ "name": "magpie",
4
+ "capability": "https://fleetmesh.org/magpie/1.0",
5
+ "endpoint_tag": "magpie-endpoint",
6
+ "checks": [
7
+ {
8
+ "name": "http",
9
+ "method": "HEAD",
10
+ "path": "/"
11
+ },
12
+ {
13
+ "name": "files-api",
14
+ "method": "HEAD",
15
+ "path": "/api/v1/files/0000000000000000000000000000000000000000000000000000000000000000/content"
16
+ }
17
+ ],
18
+ "mount": "/magpie",
19
+ "pricing": {
20
+ "mode": "blob",
21
+ "blob_route": "^/api/v1/files/([0-9a-f]{64})/content$"
22
+ },
23
+ "run": {
24
+ "image": "magpie:latest",
25
+ "port": 7863,
26
+ "cmd": "serve --data /data --listen 0.0.0.0:7863 --allow-public"
27
+ }
28
+ }
@@ -0,0 +1,43 @@
1
+ """Types and records for the Sidecars compartment."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Mapping, Optional, TypedDict
5
+
6
+
7
+ class CheckDict(TypedDict, total=False):
8
+ name: str
9
+ method: str
10
+ path: str
11
+ expect: list[int]
12
+ content_type: str
13
+
14
+
15
+ class RunDict(TypedDict, total=False):
16
+ image: str
17
+ port: int
18
+ data: str
19
+ env: Mapping[str, str]
20
+ cmd: str
21
+
22
+
23
+ class PricingDict(TypedDict, total=False):
24
+ mode: str
25
+ blob_route: str
26
+
27
+
28
+ class SidecarDict(TypedDict, total=False):
29
+ sidecar: int
30
+ name: str
31
+ capability: str
32
+ endpoint_tag: str
33
+ checks: list[CheckDict]
34
+ docs: str
35
+ mount: str
36
+ pricing: PricingDict
37
+ run: RunDict
38
+
39
+
40
+ class SidecarStatusReport(TypedDict):
41
+ verified: Optional[bool]
42
+ last_checked: Optional[int]
43
+ checks: list[Mapping[str, Any]]
@@ -0,0 +1,106 @@
1
+ """Fleetmesh Video Delivery & High-Demand Transit Routing subsystem.
2
+
3
+ Composes:
4
+ - `VideoStreamPackager` (Phase 2): CMAF/LL-HLS segment packaging, `RhioBaoTree`
5
+ 1024-byte chunked Merkle verification, `p2panda` CBOR manifest logs,
6
+ cross-protocol stream announcements, and `PaidFetch` / `CrossProtocolSettlementGateway`
7
+ HTTP 402 commercial settlement.
8
+ - `TransitReflectorManager` & `MoQFrame` (Phase 3): Automated `Capacity` shed
9
+ handovers (`narrow_scope` / `amend_down` vs. `restore_below`), HTTP 307 viewer
10
+ redirection, MoQ ring-buffer reflection over FIPS port 30820 and Iroh UDP,
11
+ and Rhio NATS JetStream cluster fanout.
12
+ - `TransitLedgerStore` (Phase 4): Record-Then-Answer SQLite persistence for
13
+ bilateral hash-chained `TransitReceipt` IOUs, soft debt ceiling degradation
14
+ (`degraded-audio-only`), and bartered utility repayments (`blob_shoring`,
15
+ `verified_compute`, `dvm`, `mediation`).
16
+ - `VideoStreamCommandProvider` & `register_stream_cli` (Phase 5): CLI commands
17
+ for starting streams, inspecting transit debt, and settling bartered IOUs.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ from .barter import (
22
+ BarterHookResult,
23
+ BarterSettlementBridge,
24
+ normalize_peer_pubkey,
25
+ )
26
+ from .cli import (
27
+ VideoStreamCommandProvider,
28
+ cmd_stream,
29
+ register_stream_cli,
30
+ run_stream_cli,
31
+ )
32
+ from .compartment import (
33
+ VideoConfig,
34
+ VideoTransitCompartment,
35
+ )
36
+ from .ingest import (
37
+ CMAFBoxInfo,
38
+ IngestSegmentReport,
39
+ LiveMediaIngestBridge,
40
+ SpoolPollReport,
41
+ make_synthetic_cmaf_init,
42
+ make_synthetic_cmaf_segment,
43
+ parse_cmaf_boxes,
44
+ )
45
+ from .ledger import (
46
+ DEFAULT_UTILITY_BYTES_RATES,
47
+ VALID_UTILITY_KINDS,
48
+ BarterCreditRow,
49
+ DebtSummary,
50
+ OutboundChainState,
51
+ TransitLedgerStore,
52
+ UtilityKind,
53
+ )
54
+ from .packager import (
55
+ SegmentMultiAddress,
56
+ StreamStateRecord,
57
+ VideoStreamPackager,
58
+ )
59
+ from .player import (
60
+ mount_player_routes,
61
+ render_directory_html,
62
+ render_player_html,
63
+ )
64
+ from .transit import (
65
+ MoQFrame,
66
+ MoQFrameDict,
67
+ ReflectionResult,
68
+ TransitReflectorManager,
69
+ )
70
+
71
+ __all__ = [
72
+ 'BarterHookResult',
73
+ 'BarterSettlementBridge',
74
+ 'normalize_peer_pubkey',
75
+ 'VideoConfig',
76
+ 'VideoTransitCompartment',
77
+ 'CMAFBoxInfo',
78
+ 'IngestSegmentReport',
79
+ 'SpoolPollReport',
80
+ 'parse_cmaf_boxes',
81
+ 'make_synthetic_cmaf_init',
82
+ 'make_synthetic_cmaf_segment',
83
+ 'LiveMediaIngestBridge',
84
+ 'VideoStreamCommandProvider',
85
+ 'cmd_stream',
86
+ 'register_stream_cli',
87
+ 'run_stream_cli',
88
+ 'UtilityKind',
89
+ 'VALID_UTILITY_KINDS',
90
+ 'DEFAULT_UTILITY_BYTES_RATES',
91
+ 'BarterCreditRow',
92
+ 'DebtSummary',
93
+ 'OutboundChainState',
94
+ 'TransitLedgerStore',
95
+ 'StreamStateRecord',
96
+ 'SegmentMultiAddress',
97
+ 'VideoStreamPackager',
98
+ 'render_directory_html',
99
+ 'render_player_html',
100
+ 'mount_player_routes',
101
+ 'MoQFrameDict',
102
+ 'ReflectionResult',
103
+ 'MoQFrame',
104
+ 'TransitReflectorManager',
105
+ ]
106
+