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,270 @@
1
+ """The schema.org face of a node: what it publishes for strangers, projected
2
+ as JSON-LD and served read-only over the relay face's HTTP fallthrough.
3
+
4
+ Three documents, each a projection of a signed event (fleetmesh_core
5
+ .linked_data) and never a second source of truth:
6
+
7
+ GET /.well-known/fleetmesh/service.jsonld the node's kind 11801 as a
8
+ schema:Service, floor from
9
+ its kind 10050
10
+ GET /.well-known/fleetmesh/blob/<sha256>.jsonld
11
+ one stored blob as a
12
+ schema:MediaObject
13
+ GET /.well-known/fleetmesh/standing.jsonld the node's own tier, as this
14
+ node's accounts have it - an
15
+ Organization with one
16
+ PropertyValue, not a Review
17
+
18
+ Nothing here is signed and nothing here goes on a relay: the signed thing
19
+ is the event, this is a reading of it. A consumer that wants proof fetches
20
+ the event. That is why JSON-LD does not ride inside events (see
21
+ CONTRIBUTING, "linked data").
22
+
23
+ Every answer carries `Cache-Control: no-store` and the id of the event it
24
+ was read from in `X-Fleetmesh-Source`, so a reader can go and check. No
25
+ POST, no query strings, unknown paths fall through to the rest of the face.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import json
31
+ import re
32
+ from typing import Any, Callable, Mapping, Optional, cast
33
+
34
+ from fleetmesh_core.storage import linked_data
35
+ from fleetmesh_core.wire.interfaces import NostrEvent
36
+
37
+ from node.compartment import stores as st
38
+ from node.compartment import Command, Compartment, Need
39
+ from node.transport import ws
40
+
41
+
42
+ from .types import (
43
+ LinkedTotals,
44
+ LinkedState,
45
+ BlobFacts,
46
+ )
47
+
48
+ PREFIX = '/.well-known/fleetmesh/'
49
+ CTYPE = 'application/ld+json'
50
+ _SHA = re.compile(r'^[0-9a-f]{64}$')
51
+
52
+
53
+ import dataclasses
54
+ from node.channels import Channel, INFO
55
+ from node.metrics import COUNTER, Metric
56
+ from node.config.parameter import Parameter, ParamBlock
57
+
58
+
59
+ @dataclasses.dataclass(frozen=True)
60
+ class LinkedConfig(ParamBlock):
61
+ key = 'linked'
62
+ name = 'linked'
63
+ DERIVED: bool = False
64
+ PARAMS = (
65
+ Parameter(key='enabled', name='Linked Data Enabled', description='Whether JSON-LD schema.org projections are served', type=bool, default=True, tags=('linked', 'schema')),
66
+ )
67
+ enabled: bool = True
68
+
69
+
70
+ class LinkedFace(Compartment):
71
+ """Mounted on the relay face's HTTP fallthrough, before nsite."""
72
+
73
+ name: str = 'linked'
74
+ optional: bool = False
75
+ requires: tuple[str, ...] = ('presence',) # the kind 10050 it last published is the floor
76
+ uses: tuple[str, ...] = ()
77
+ reads: tuple[str, ...] = ()
78
+ live: tuple[str, ...] = ()
79
+ config_cls = LinkedConfig
80
+ CHANNELS = (
81
+ Channel('doc_served', 'JSON-LD document served', level=INFO, say='served JSON-LD {path}', tags=('linked', 'schema')),
82
+ )
83
+ METRICS = (
84
+ Metric(name='linked_docs_served', description='Times JSON-LD projections were served.', kind=COUNTER, counts='doc_served', tags=('linked', 'schema')),
85
+ )
86
+ NEEDS = (
87
+ Need('face'),
88
+ Need('blobs', 'stores.blobs'),
89
+ Need('descriptor'),
90
+ Need('presence'),
91
+ Need('ledger'),
92
+ Need('ks'),
93
+ )
94
+ COMMANDS = (
95
+ Command(
96
+ key='show',
97
+ name='Linked Data Status',
98
+ description='the projections this node serves and how often each was read',
99
+ tags=('linked', 'status'),
100
+ permission='linked/show',
101
+ run='state',
102
+ reply='paths and counts',
103
+ ),
104
+ Command(
105
+ key='service',
106
+ name='Service Document',
107
+ description="this node's descriptor as a schema:Service (JSON-LD)",
108
+ tags=('linked', 'service'),
109
+ permission='linked/service',
110
+ run='service_cmd',
111
+ reply='the document, or refused when no descriptor yet',
112
+ ),
113
+ Command(
114
+ key='standing',
115
+ name='Standing Document',
116
+ description="this node's own tier as a schema:Organization (JSON-LD)",
117
+ tags=('linked', 'standing'),
118
+ permission='linked/standing',
119
+ run='standing_cmd',
120
+ reply='the document, or refused when the ledger has nothing to say',
121
+ ),
122
+ Command(
123
+ key='blob',
124
+ name='Blob Document',
125
+ description='one stored blob as a schema:MediaObject (JSON-LD)',
126
+ tags=('linked', 'blob'),
127
+ parameters=(
128
+ Parameter(key='sha256', name='sha256', description='sha256 hex digest of blob', type=str, default='', required=True),
129
+ ),
130
+ permission='linked/blob',
131
+ run='blob_cmd',
132
+ reply='the document, or refused when the blob is not here',
133
+ ),
134
+ )
135
+ STORES = (
136
+ st.Store(key='served', description='linked data requests served by category',
137
+ kind=st.COUNTERS, shape=LinkedTotals),
138
+ )
139
+
140
+ def __init__(self, node: Optional[Any] = None, face: Optional[Any] = None,
141
+ blobs: Optional[Any] = None,
142
+ descriptor: Optional[Callable[[], Optional[NostrEvent]]] = None,
143
+ inbox: Optional[Callable[[], Optional[NostrEvent]]] = None,
144
+ standing: Optional[Callable[[], Optional[Mapping[str, Any]]]] = None,
145
+ clock: Optional[Callable[[], int]] = None,
146
+ blossom_url: Optional[str] = None,
147
+ presence: Optional[Any] = None,
148
+ ledger: Optional[Any] = None,
149
+ ks: Optional[Any] = None) -> None:
150
+ super().__init__(node, clock)
151
+ self.face = face
152
+ self.blobs = blobs
153
+ self._descriptor = descriptor or (lambda: None)
154
+ self._inbox: Optional[Callable[[], Optional[NostrEvent]]]
155
+ if inbox is not None:
156
+ self._inbox = inbox
157
+ elif presence is not None:
158
+ self._inbox = lambda: getattr(presence, 'last_inbox', None)
159
+ else:
160
+ self._inbox = None
161
+
162
+ self._standing: Optional[Callable[[], Optional[Mapping[str, Any]]]]
163
+ if standing is not None:
164
+ self._standing = standing
165
+ elif ledger is not None and ks is not None:
166
+ me = getattr(ks, 'node_pk', '')
167
+ self._standing = (lambda: cast(Optional[Mapping[str, Any]], ledger.tell(me))) if me else None
168
+ else:
169
+ self._standing = None
170
+
171
+ self.blossom_url = blossom_url if blossom_url is not None else getattr(node, 'blossom_url', None)
172
+ self.served: st.Counters
173
+
174
+ # ---- lifecycle ----------------------------------------------------------
175
+ def start(self) -> None:
176
+ if self.face is not None and hasattr(self.face, 'http_handlers'):
177
+ # First in line: fixed paths, cheap, never host-routed.
178
+ self.face.http_handlers.insert(0, self.handle_request)
179
+
180
+ def tick(self, now: int) -> list[tuple[Any, ...]]:
181
+ return []
182
+
183
+ def state(self) -> LinkedState:
184
+ return {'served': dict(self.served), 'paths': self.paths()}
185
+
186
+ def service_cmd(self) -> Mapping[str, Any]:
187
+ got = self.service()
188
+ if got is None:
189
+ return {'refused': 'nothing to say'}
190
+ doc, source = got
191
+ return {'source': source, 'document': doc}
192
+
193
+ def standing_cmd(self) -> Mapping[str, Any]:
194
+ got = self.standing()
195
+ if got is None:
196
+ return {'refused': 'nothing to say'}
197
+ doc, source = got
198
+ return {'source': source, 'document': doc}
199
+
200
+ def blob_cmd(self, args: Mapping[str, Any]) -> Mapping[str, Any]:
201
+ got = self.blob(str(args.get('sha256', '')).lower())
202
+ if got is None:
203
+ return {'refused': 'nothing to say'}
204
+ doc, source = got
205
+ return {'source': source, 'document': doc}
206
+
207
+ @staticmethod
208
+ def paths() -> list[str]:
209
+ return [PREFIX + 'service.jsonld', PREFIX + 'blob/<sha256>.jsonld', PREFIX + 'standing.jsonld']
210
+
211
+ # ---- the documents --------------------------------------------------------
212
+ def service(self) -> Optional[tuple[linked_data.LinkedData, str]]:
213
+ desc = self._descriptor()
214
+ if not desc:
215
+ return None
216
+ inbox = self._inbox() if self._inbox is not None else None
217
+ return linked_data.service(desc, inbox), desc['id']
218
+
219
+ def blob(self, sha: str) -> Optional[tuple[linked_data.LinkedData, str]]:
220
+ if self.blobs is None or not _SHA.match(sha):
221
+ return None
222
+ st = self.blobs.stat(sha)
223
+ if not st:
224
+ return None
225
+ url = self.blossom_url
226
+ d: BlobFacts = {'sha256': sha, 'size': st.get('size'), 'type': st.get('type')}
227
+ if st.get('uploaded') is not None:
228
+ d['uploaded'] = st['uploaded']
229
+ if url:
230
+ d['url'] = '%s/%s' % (url.rstrip('/'), sha)
231
+ return linked_data.media_object(d), sha
232
+
233
+ def standing(self) -> Optional[tuple[linked_data.LinkedData, str]]:
234
+ desc = self._descriptor()
235
+ tell = self._standing() if self._standing is not None else None
236
+ if not desc or not tell:
237
+ return None
238
+ return linked_data.standing(desc['pubkey'], tell), desc['id']
239
+
240
+ # ---- the hook ---------------------------------------------------------------
241
+ def handle_request(self, conn: Any, req: Any) -> bool:
242
+ path = getattr(req, 'path', '/') or '/'
243
+ if not path.startswith(PREFIX):
244
+ return False
245
+ method = (getattr(req, 'method', 'GET') or 'GET').upper()
246
+ if method not in ('GET', 'HEAD'):
247
+ ws.http_response(conn, '405 Method Not Allowed', b'', 'text/plain', {'Allow': 'GET, HEAD'})
248
+ return True
249
+ rest = path[len(PREFIX):].split('?', 1)[0]
250
+ got: Optional[tuple[linked_data.LinkedData, str]] = None
251
+ which = ''
252
+ if rest == 'service.jsonld':
253
+ which, got = 'service', self.service()
254
+ elif rest == 'standing.jsonld':
255
+ which, got = 'standing', self.standing()
256
+ elif rest.startswith('blob/') and rest.endswith('.jsonld'):
257
+ which, got = 'blob', self.blob(rest[5:-7].lower())
258
+ else:
259
+ ws.http_response(conn, '404 Not Found', b'no such projection\n', 'text/plain')
260
+ return True
261
+ if got is None:
262
+ ws.http_response(conn, '404 Not Found', b'nothing to say\n', 'text/plain',
263
+ {'Cache-Control': 'no-store'})
264
+ return True
265
+ doc, source = got
266
+ body = json.dumps(doc, sort_keys=True, separators=(',', ':')).encode()
267
+ self.served[which] += 1
268
+ ws.http_response(conn, '200 OK', b'' if method == 'HEAD' else body, CTYPE,
269
+ {'Cache-Control': 'no-store', 'X-Fleetmesh-Source': source})
270
+ return True
@@ -0,0 +1,25 @@
1
+ """Types and records for the Linked Data compartment."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Optional, TypedDict
5
+
6
+
7
+ class LinkedTotals(TypedDict):
8
+ service: int
9
+ blob: int
10
+ standing: int
11
+
12
+
13
+ class LinkedState(TypedDict):
14
+ served: dict[str, int]
15
+ paths: list[str]
16
+
17
+
18
+ class BlobFacts(TypedDict, total=False):
19
+ """What blobstore.stat() says about one blob, in the field names
20
+ linked_data.media_object() reads (a blossom_auth.BlobDescriptor)."""
21
+ sha256: str
22
+ size: Optional[int]
23
+ type: Optional[str]
24
+ uploaded: int
25
+ url: str
@@ -0,0 +1,7 @@
1
+ """What this node says it is alive, and what it makes of a peer saying so.
2
+
3
+ One-module compartment: everything is in compartment.py.
4
+ """
5
+ from .compartment import Presence
6
+
7
+ __all__ = ['Presence']