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,705 @@
1
+ """Bilateral Transit IOU Ledger & Barter Settlement (Phase 4).
2
+
3
+ Implements Record-Then-Answer persistence for `video-transit/1.0` grants,
4
+ cumulative hash-chained receipts (`transit_bytes_out`), gap-tolerant receipt
5
+ verification, soft debt ceilings (`degraded-audio-only`), and bartered network
6
+ utility credits (Blob Shoring, Verified Compute / DVM, and DIDComm Mediation).
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import json
12
+ import time
13
+ from typing import Any, Callable, Literal, Mapping, Optional, Sequence, TypedDict
14
+
15
+ from fleetmesh_core.accounting import accounting
16
+ from node.compartment.schema import Column, Index, Table
17
+ from node.storage import SqliteStore
18
+ from fleetmesh_core.wire.video_transit import (
19
+ COUNTER_TRANSIT_BYTES_OUT,
20
+ TransitAckStatus,
21
+ TransitGrantBody,
22
+ TransitMode,
23
+ TransitReceiptAckBody,
24
+ TransitReceiptBody,
25
+ check_grant,
26
+ check_transit_close,
27
+ check_transit_receipt,
28
+ receipt_link,
29
+ register_video_transit_checks,
30
+ sign_transit_grant,
31
+ sign_transit_receipt,
32
+ verify_transit_grant,
33
+ verify_transit_receipt,
34
+ )
35
+
36
+ UtilityKind = Literal['blob_shoring', 'verified_compute', 'dvm', 'mediation']
37
+ VALID_UTILITY_KINDS: tuple[str, ...] = ('blob_shoring', 'verified_compute', 'dvm', 'mediation')
38
+
39
+ # Default conversion rates from bartered network utility units to transit bytes
40
+ DEFAULT_UTILITY_BYTES_RATES: Mapping[str, int] = {
41
+ 'blob_shoring': 1, # 1 byte shored = 1 byte transit credit
42
+ 'verified_compute': 1024, # 1 compute fuel unit = 1 KB transit credit
43
+ 'dvm': 1024 * 1024, # 1 DVM job unit = 1 MB transit credit
44
+ 'mediation': 4096, # 1 mediated envelope byte/msg unit = 4 KB transit credit
45
+ }
46
+
47
+
48
+ class BarterCreditRow(TypedDict):
49
+ credit_id: str
50
+ debtor: str
51
+ creditor: str
52
+ utility_kind: str
53
+ units: int
54
+ equivalent_transit_bytes: int
55
+ proof_ref: str
56
+ created_at: int
57
+
58
+
59
+ class DebtSummary(TypedDict):
60
+ debtor: str
61
+ gross_transit_bytes: int
62
+ bartered_bytes: int
63
+ cashu_settled_bytes: int
64
+ unsettled_bytes: int
65
+ max_unsettled_transit_bytes: int
66
+ degraded_audio_only: bool
67
+
68
+
69
+ class OutboundChainState(TypedDict):
70
+ grant_id: str
71
+ window: int
72
+ seq: int
73
+ prev: str
74
+ heads: list[str]
75
+ sent_at: int
76
+ last_bytes_out: int
77
+
78
+
79
+ _GRANT_COLS = ('grant_id, stream_id, broadcaster, operator, window_seconds, '
80
+ 'receipt_cadence_seconds, max_unsettled_transit_bytes, mode, '
81
+ 'reflector_endpoints_json, status, created_at, sig')
82
+ _CHAIN_COLS = 'grant_id, window, seq, prev, heads_json, sent_at, last_bytes_out'
83
+ _CREDIT_COLS = ('credit_id, debtor, creditor, utility_kind, units, '
84
+ 'equivalent_transit_bytes, proof_ref, created_at')
85
+ """The column orders the positional reads below depend on.
86
+
87
+ The selects said `SELECT *` and the rows were read by name through a
88
+ `sqlite3.Row` factory - which is one of the driver leaks the dialect layer
89
+ exists to remove, since a second driver has its own row type. The orders are
90
+ written down once here instead.
91
+ """
92
+
93
+
94
+ class TransitLedgerStore(SqliteStore):
95
+ """Transit grants, receipt chains and bartered utility repayments.
96
+
97
+ `DURABILITY = 'commit'` is declared, not decorative: this store holds
98
+ bilateral hash-chained IOUs, and `record_outbound_receipt` commits the
99
+ chain advance *before* the receipt goes on the wire - Record-Then-Answer.
100
+ The class docstring claimed that durability while the store declared no
101
+ class at all and was not a `BaseStore`, which is the shape of defect R0
102
+ (I15) was: a meter whose state was asserted durable, lost across a
103
+ restart, and cost a peer its standing. Naming it is what lets
104
+ test_durability_boundary hold it to the claim.
105
+
106
+ A `SqliteStore` rather than a hand-rolled connection, so the base owns the
107
+ connection, the lock, the per-store schema version, transaction control and
108
+ the dialect - see docs/storage-backends.md.
109
+ """
110
+
111
+ DURABILITY = 'commit'
112
+
113
+ SCHEMA = (
114
+ Table('transit_grants', (
115
+ Column('grant_id', 'TEXT', primary_key=True),
116
+ Column('stream_id', 'TEXT', not_null=True),
117
+ Column('broadcaster', 'TEXT', not_null=True),
118
+ Column('operator', 'TEXT', not_null=True),
119
+ Column('window_seconds', 'INTEGER', not_null=True),
120
+ Column('receipt_cadence_seconds', 'INTEGER', not_null=True),
121
+ Column('max_unsettled_transit_bytes', 'INTEGER', not_null=True),
122
+ Column('mode', 'TEXT', not_null=True),
123
+ Column('reflector_endpoints_json', 'TEXT', not_null=True),
124
+ Column('status', 'TEXT', not_null=True),
125
+ Column('created_at', 'INTEGER', not_null=True),
126
+ Column('sig', 'TEXT', not_null=True),
127
+ )),
128
+ Table('transit_chains_out', (
129
+ Column('grant_id', 'TEXT', not_null=True),
130
+ Column('window', 'INTEGER', not_null=True),
131
+ Column('seq', 'INTEGER', not_null=True),
132
+ Column('prev', 'TEXT', not_null=True),
133
+ Column('heads_json', 'TEXT', not_null=True),
134
+ Column('sent_at', 'INTEGER', not_null=True),
135
+ Column('last_bytes_out', 'INTEGER', not_null=True),
136
+ ), primary_key=('grant_id', 'window')),
137
+ Table('transit_receipts_in', (
138
+ Column('grant_id', 'TEXT', not_null=True),
139
+ Column('window', 'INTEGER', not_null=True),
140
+ Column('seq', 'INTEGER', not_null=True),
141
+ Column('head', 'TEXT', not_null=True),
142
+ Column('broadcaster', 'TEXT', not_null=True),
143
+ Column('transit_bytes_out', 'INTEGER', not_null=True),
144
+ Column('body_json', 'TEXT', not_null=True),
145
+ Column('received_at', 'INTEGER', not_null=True),
146
+ ), primary_key=('grant_id', 'window', 'seq')),
147
+ Table('transit_barter_credits', (
148
+ Column('credit_id', 'TEXT', primary_key=True),
149
+ Column('debtor', 'TEXT', not_null=True),
150
+ Column('creditor', 'TEXT', not_null=True),
151
+ Column('utility_kind', 'TEXT', not_null=True),
152
+ Column('units', 'INTEGER', not_null=True),
153
+ Column('equivalent_transit_bytes', 'INTEGER', not_null=True),
154
+ Column('proof_ref', 'TEXT', not_null=True),
155
+ Column('created_at', 'INTEGER', not_null=True),
156
+ ), indices=(
157
+ Index('idx_transit_barter_debtor', ('debtor',)),
158
+ )),
159
+ )
160
+
161
+ def __init__(self, db_path: str = ':memory:',
162
+ clock: Optional[Callable[[], int]] = None) -> None:
163
+ register_video_transit_checks()
164
+ # An injected clock, defaulting to wall time. The money path - windows,
165
+ # receipt cadence, debt ceilings - is decided at time boundaries, and a
166
+ # direct time.time() makes those boundaries untestable. The compartment
167
+ # passes its own clock down, so a suite that moves the clock moves this too.
168
+ self._clock: Callable[[], int] = clock or (lambda: int(time.time()))
169
+ self.findings: list[tuple[str, int, str, str]] = []
170
+ self.receipt_gaps: list[tuple[str, int, int, int, int]] = []
171
+ super().__init__(db_path)
172
+
173
+ def close(self) -> None:
174
+ super().close()
175
+
176
+ # ---- Grant Issuance & Storage -------------------------------------------
177
+
178
+ def issue_grant(
179
+ self,
180
+ stream_id: str,
181
+ broadcaster: str,
182
+ operator_pk: str,
183
+ operator_sk: str,
184
+ window_seconds: int = 600,
185
+ receipt_cadence_seconds: int = 60,
186
+ max_unsettled_transit_bytes: int = 500 * 1024 * 1024,
187
+ reflector_endpoints: Sequence[str] = (),
188
+ mode: TransitMode = 'pro-bono',
189
+ now: Optional[int] = None,
190
+ ) -> TransitGrantBody:
191
+ """Create, sign, and durably commit a transit grant before wire emission."""
192
+ now_val = self._clock() if now is None else int(now)
193
+ seed = f"{stream_id}:{broadcaster.lower()}:{operator_pk.lower()}:{now_val}".encode('utf-8')
194
+ grant_id = hashlib.sha256(seed).hexdigest()
195
+ unsigned: TransitGrantBody = {
196
+ 'grant_id': grant_id,
197
+ 'stream_id': stream_id,
198
+ 'broadcaster': broadcaster.lower(),
199
+ 'operator': operator_pk.lower(),
200
+ 'window_seconds': int(window_seconds),
201
+ 'receipt_cadence_seconds': int(receipt_cadence_seconds),
202
+ 'max_unsettled_transit_bytes': int(max_unsettled_transit_bytes),
203
+ 'reflector_endpoints': list(reflector_endpoints),
204
+ 'mode': mode,
205
+ 'at': now_val,
206
+ }
207
+ signed = sign_transit_grant(unsigned, operator_sk)
208
+ self.save_grant(signed)
209
+ return signed
210
+
211
+ def save_grant(self, grant: TransitGrantBody, status: str = 'live') -> None:
212
+ """Persist a verified TransitGrantBody to SQLite."""
213
+ why = check_grant(grant)
214
+ if why is not None:
215
+ raise ValueError(f"Invalid transit grant: {why}")
216
+ operator_pk = grant.get('operator', '')
217
+ if not verify_transit_grant(grant, operator_pk):
218
+ raise ValueError("Transit grant signature verification failed")
219
+
220
+ with self._lock:
221
+ self.execute(
222
+ """
223
+ INSERT OR REPLACE INTO transit_grants (
224
+ grant_id, stream_id, broadcaster, operator,
225
+ window_seconds, receipt_cadence_seconds, max_unsettled_transit_bytes,
226
+ mode, reflector_endpoints_json, status, created_at, sig
227
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
228
+ """,
229
+ (
230
+ grant['grant_id'],
231
+ grant['stream_id'],
232
+ grant['broadcaster'].lower(),
233
+ grant['operator'].lower(),
234
+ int(grant['window_seconds']),
235
+ int(grant['receipt_cadence_seconds']),
236
+ int(grant['max_unsettled_transit_bytes']),
237
+ grant.get('mode', 'pro-bono'),
238
+ json.dumps(list(grant.get('reflector_endpoints', []))),
239
+ status,
240
+ int(grant.get('at', self._clock())),
241
+ grant['sig'],
242
+ ),
243
+ )
244
+ self.commit()
245
+
246
+ def get_grant(self, grant_id: str) -> Optional[TransitGrantBody]:
247
+ with self._lock:
248
+ row = self._one(
249
+ 'SELECT ' + _GRANT_COLS + ' FROM transit_grants WHERE grant_id = ?',
250
+ (grant_id,),
251
+ )
252
+ if row is None:
253
+ return None
254
+ mode_val: TransitMode = 'commercial' if row[7] == 'commercial' else 'pro-bono'
255
+ return {
256
+ 'grant_id': str(row[0]),
257
+ 'stream_id': str(row[1]),
258
+ 'broadcaster': str(row[2]),
259
+ 'operator': str(row[3]),
260
+ 'window_seconds': int(row[4]),
261
+ 'receipt_cadence_seconds': int(row[5]),
262
+ 'max_unsettled_transit_bytes': int(row[6]),
263
+ 'mode': mode_val,
264
+ 'reflector_endpoints': list(json.loads(row[8])),
265
+ 'at': int(row[10]),
266
+ 'sig': str(row[11]),
267
+ }
268
+
269
+ def close_grant(self, grant_id: str) -> None:
270
+ with self._lock:
271
+ self.execute(
272
+ "UPDATE transit_grants SET status = 'closed' WHERE grant_id = ?",
273
+ (grant_id,),
274
+ )
275
+ self.commit()
276
+
277
+ # ---- Record-Then-Answer Outbound Receipt Chain --------------------------
278
+
279
+ def get_outbound_chain(self, grant_id: str, window: int) -> Optional[OutboundChainState]:
280
+ with self._lock:
281
+ row = self._one(
282
+ 'SELECT ' + _CHAIN_COLS + ' FROM transit_chains_out WHERE grant_id = ? AND window = ?',
283
+ (grant_id, int(window)),
284
+ )
285
+ if row is None:
286
+ return None
287
+ return {
288
+ 'grant_id': str(row[0]),
289
+ 'window': int(row[1]),
290
+ 'seq': int(row[2]),
291
+ 'prev': str(row[3]),
292
+ 'heads': list(json.loads(row[4])),
293
+ 'sent_at': int(row[5]),
294
+ 'last_bytes_out': int(row[6]),
295
+ }
296
+
297
+ def record_outbound_receipt(
298
+ self,
299
+ grant_id: str,
300
+ stream_id: str,
301
+ window: int,
302
+ transit_bytes_out: int,
303
+ sk_hex: str,
304
+ now: Optional[int] = None,
305
+ ) -> tuple[TransitReceiptBody, str]:
306
+ """Produce, sign, and durably persist the next cumulative TransitReceiptBody
307
+ in the hash chain BEFORE it is emitted on the wire (Record-Then-Answer).
308
+ """
309
+ now_val = self._clock() if now is None else int(now)
310
+ with self._lock:
311
+ state = self.get_outbound_chain(grant_id, window)
312
+ if state is None:
313
+ seq = 0
314
+ prev = accounting.ZERO
315
+ heads: list[str] = []
316
+ last_bytes = 0
317
+ else:
318
+ seq = state['seq']
319
+ prev = state['prev']
320
+ heads = list(state['heads'])
321
+ last_bytes = state['last_bytes_out']
322
+
323
+ if transit_bytes_out < last_bytes:
324
+ raise ValueError(
325
+ f"Cumulative transit_bytes_out cannot decrease ({transit_bytes_out} < {last_bytes})"
326
+ )
327
+
328
+ unsigned: TransitReceiptBody = {
329
+ 'grant': grant_id,
330
+ 'stream_id': stream_id,
331
+ 'window': int(window),
332
+ 'seq': seq,
333
+ 'prev': prev,
334
+ 'counters': {COUNTER_TRANSIT_BYTES_OUT: int(transit_bytes_out)},
335
+ 'at': now_val,
336
+ }
337
+ signed = sign_transit_receipt(unsigned, sk_hex)
338
+ head = receipt_link(signed)
339
+ heads.append(head)
340
+
341
+ # Record-Then-Answer: commit chain advancement to SQLite BEFORE returning
342
+ self.execute(
343
+ """
344
+ INSERT OR REPLACE INTO transit_chains_out (
345
+ grant_id, window, seq, prev, heads_json, sent_at, last_bytes_out
346
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
347
+ """,
348
+ (
349
+ grant_id,
350
+ int(window),
351
+ seq + 1,
352
+ head,
353
+ json.dumps(heads),
354
+ now_val,
355
+ int(transit_bytes_out),
356
+ ),
357
+ )
358
+ self.commit()
359
+ return signed, head
360
+
361
+ # ---- Inbound Receipt Verification & Gap-Tolerant Chain Intake -----------
362
+
363
+ def get_inbound_receipts(self, grant_id: str, window: int) -> list[TransitReceiptBody]:
364
+ with self._lock:
365
+ rows = self.execute(
366
+ 'SELECT body_json FROM transit_receipts_in WHERE grant_id = ? AND window = ? ORDER BY seq ASC',
367
+ (grant_id, int(window)),
368
+ ).fetchall()
369
+ out: list[TransitReceiptBody] = []
370
+ for r in rows:
371
+ out.append(json.loads(r[0]))
372
+ return out
373
+
374
+ def on_inbound_receipt(
375
+ self,
376
+ signer_pubkey: str,
377
+ body: Mapping[str, Any],
378
+ now: Optional[int] = None,
379
+ ) -> TransitReceiptAckBody:
380
+ """Validate and record an inbound TransitReceiptBody.
381
+
382
+ Enforces:
383
+ - Door shape check (`check_transit_receipt`)
384
+ - Schnorr signature verification (`verify_transit_receipt`)
385
+ - Gap tolerance vs. rewrite detection (matching `budget/receipts.py`)
386
+ - Monotonic cumulative `transit_bytes_out` counters
387
+ - Soft debt ceiling evaluation (`degraded-audio-only` when unsettled debt
388
+ reaches `max_unsettled_transit_bytes`).
389
+ """
390
+ now_val = self._clock() if now is None else int(now)
391
+ why = check_transit_receipt(body)
392
+ if why is not None:
393
+ seq_raw = body.get('seq') if isinstance(body, Mapping) else 0
394
+ seq_val = seq_raw if isinstance(seq_raw, int) and not isinstance(seq_raw, bool) else 0
395
+ return {
396
+ 'grant': str(body.get('grant', accounting.ZERO)),
397
+ 'seq': seq_val,
398
+ 'head': accounting.ZERO,
399
+ 'status': 'divergent',
400
+ 'unsettled_bytes': 0,
401
+ }
402
+
403
+ grant_id = str(body['grant'])
404
+ window = int(body['window'])
405
+ seq = int(body['seq'])
406
+ grant = self.get_grant(grant_id)
407
+ if grant is None:
408
+ return {
409
+ 'grant': grant_id,
410
+ 'seq': seq,
411
+ 'head': accounting.ZERO,
412
+ 'status': 'divergent',
413
+ 'unsettled_bytes': 0,
414
+ }
415
+
416
+ broadcaster = grant['broadcaster']
417
+ with self._lock:
418
+ chain = self.get_inbound_receipts(grant_id, window)
419
+ expected_seq = int(chain[-1]['seq']) + 1 if chain else 0
420
+ expected_prev = receipt_link(chain[-1]) if chain else accounting.ZERO
421
+ problems: list[str] = []
422
+
423
+ if signer_pubkey.strip().lower() != broadcaster.strip().lower():
424
+ problems.append('receipt signer does not match grant broadcaster')
425
+ elif not verify_transit_receipt(body, broadcaster):
426
+ problems.append('no valid signature over receipt chain link')
427
+
428
+ gap = 0
429
+ if seq < expected_seq:
430
+ problems.append(f'seq {seq}, expected {expected_seq} or later (rewrite attempt)')
431
+ elif seq > expected_seq:
432
+ gap = seq - expected_seq
433
+
434
+ if not gap and body.get('prev') != expected_prev:
435
+ problems.append('prev does not chain')
436
+
437
+ new_bytes = int(body['counters'][COUNTER_TRANSIT_BYTES_OUT])
438
+ if chain:
439
+ prev_bytes = int(chain[-1]['counters'].get(COUNTER_TRANSIT_BYTES_OUT, 0))
440
+ if new_bytes < prev_bytes:
441
+ problems.append(f'counter {COUNTER_TRANSIT_BYTES_OUT} went backwards ({new_bytes} < {prev_bytes})')
442
+
443
+ if problems:
444
+ self.findings.append((signer_pubkey.lower(), window, 'S2', 'transit receipt chain: ' + '; '.join(problems)))
445
+ summary = self.debt_summary(broadcaster, grant_id)
446
+ return {
447
+ 'grant': grant_id,
448
+ 'seq': seq,
449
+ 'head': expected_prev,
450
+ 'status': 'divergent',
451
+ 'unsettled_bytes': summary['unsettled_bytes'],
452
+ }
453
+
454
+ if gap > 0:
455
+ self.receipt_gaps.append((signer_pubkey.lower(), window, expected_seq, seq, now_val))
456
+
457
+ head = receipt_link(body)
458
+ self.execute(
459
+ """
460
+ INSERT OR REPLACE INTO transit_receipts_in (
461
+ grant_id, window, seq, head, broadcaster, transit_bytes_out, body_json, received_at
462
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
463
+ """,
464
+ (
465
+ grant_id,
466
+ window,
467
+ seq,
468
+ head,
469
+ broadcaster.lower(),
470
+ new_bytes,
471
+ json.dumps(dict(body), sort_keys=True),
472
+ now_val,
473
+ ),
474
+ )
475
+ self.commit()
476
+
477
+ summary = self.debt_summary(broadcaster, grant_id)
478
+ status: TransitAckStatus = (
479
+ 'degraded-audio-only' if summary['degraded_audio_only'] else 'accepted'
480
+ )
481
+ return {
482
+ 'grant': grant_id,
483
+ 'seq': seq,
484
+ 'head': head,
485
+ 'status': status,
486
+ 'unsettled_bytes': summary['unsettled_bytes'],
487
+ }
488
+
489
+ def on_inbound_close(
490
+ self,
491
+ body: Mapping[str, Any],
492
+ now: Optional[int] = None,
493
+ ) -> TransitReceiptAckBody:
494
+ """Validate a TransitCloseBody and close the grant if final head matches."""
495
+ why = check_transit_close(body)
496
+ if why is not None:
497
+ return {
498
+ 'grant': str(body.get('grant', accounting.ZERO)),
499
+ 'seq': 0,
500
+ 'head': accounting.ZERO,
501
+ 'status': 'divergent',
502
+ 'unsettled_bytes': 0,
503
+ }
504
+ grant_id = str(body['grant'])
505
+ window = int(body['window'])
506
+ grant = self.get_grant(grant_id)
507
+ with self._lock:
508
+ chain = self.get_inbound_receipts(grant_id, window)
509
+ head = receipt_link(chain[-1]) if chain else accounting.ZERO
510
+ is_final = body.get('final_receipt_head') == head
511
+ if is_final:
512
+ self.close_grant(grant_id)
513
+ broadcaster = grant['broadcaster'] if grant else ''
514
+ summary = self.debt_summary(broadcaster, grant_id) if broadcaster else {
515
+ 'unsettled_bytes': 0,
516
+ }
517
+ return {
518
+ 'grant': grant_id,
519
+ 'seq': len(chain),
520
+ 'head': head,
521
+ 'status': 'final' if is_final else 'accepted',
522
+ 'unsettled_bytes': int(summary['unsettled_bytes']),
523
+ }
524
+
525
+ # ---- Bartered Network Utility & Debt Accounting -------------------------
526
+
527
+ def credit_barter(
528
+ self,
529
+ debtor: str,
530
+ creditor: str,
531
+ utility_kind: UtilityKind,
532
+ units: int,
533
+ proof_ref: str,
534
+ equivalent_transit_bytes: Optional[int] = None,
535
+ now: Optional[int] = None,
536
+ ) -> BarterCreditRow:
537
+ """Credit bartered network utility (Blob Shoring, Verified Compute, DVM,
538
+ or DIDComm Mediation) against a debtor's unsettled transit IOU balance.
539
+ """
540
+ if utility_kind not in VALID_UTILITY_KINDS:
541
+ raise ValueError(f"Unknown utility_kind: {utility_kind!r}")
542
+ if units <= 0:
543
+ raise ValueError(f"units must be positive, got {units}")
544
+
545
+ now_val = self._clock() if now is None else int(now)
546
+ if equivalent_transit_bytes is None:
547
+ rate = DEFAULT_UTILITY_BYTES_RATES.get(utility_kind, 1)
548
+ equiv_bytes = int(units) * rate
549
+ else:
550
+ equiv_bytes = int(equivalent_transit_bytes)
551
+
552
+ seed = f"{debtor.lower()}:{creditor.lower()}:{utility_kind}:{proof_ref}".encode('utf-8')
553
+ credit_id = hashlib.sha256(seed).hexdigest()
554
+
555
+ row: BarterCreditRow = {
556
+ 'credit_id': credit_id,
557
+ 'debtor': debtor.lower(),
558
+ 'creditor': creditor.lower(),
559
+ 'utility_kind': utility_kind,
560
+ 'units': int(units),
561
+ 'equivalent_transit_bytes': equiv_bytes,
562
+ 'proof_ref': proof_ref,
563
+ 'created_at': now_val,
564
+ }
565
+ with self._lock:
566
+ self.execute(
567
+ """
568
+ INSERT OR REPLACE INTO transit_barter_credits (
569
+ credit_id, debtor, creditor, utility_kind,
570
+ units, equivalent_transit_bytes, proof_ref, created_at
571
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
572
+ """,
573
+ (
574
+ row['credit_id'],
575
+ row['debtor'],
576
+ row['creditor'],
577
+ row['utility_kind'],
578
+ row['units'],
579
+ row['equivalent_transit_bytes'],
580
+ row['proof_ref'],
581
+ row['created_at'],
582
+ ),
583
+ )
584
+ self.commit()
585
+ return row
586
+
587
+ def list_barter_credits(self, debtor: str) -> list[BarterCreditRow]:
588
+ with self._lock:
589
+ rows = self._all(
590
+ 'SELECT ' + _CREDIT_COLS + ' FROM transit_barter_credits '
591
+ 'WHERE debtor = ? ORDER BY created_at ASC',
592
+ (debtor.lower(),),
593
+ )
594
+ return [
595
+ {
596
+ 'credit_id': str(r[0]),
597
+ 'debtor': str(r[1]),
598
+ 'creditor': str(r[2]),
599
+ 'utility_kind': str(r[3]),
600
+ 'units': int(r[4]),
601
+ 'equivalent_transit_bytes': int(r[5]),
602
+ 'proof_ref': str(r[6]),
603
+ 'created_at': int(r[7]),
604
+ }
605
+ for r in rows
606
+ ]
607
+
608
+ def gross_transit_bytes(self, debtor: str, grant_id: Optional[str] = None) -> int:
609
+ """Sum the maximum cumulative `transit_bytes_out` per (grant_id, window) for a debtor."""
610
+ with self._lock:
611
+ if grant_id is not None:
612
+ rows = self.execute(
613
+ """
614
+ SELECT MAX(transit_bytes_out) AS win_max
615
+ FROM transit_receipts_in
616
+ WHERE broadcaster = ? AND grant_id = ?
617
+ GROUP BY grant_id, window
618
+ """,
619
+ (debtor.lower(), grant_id),
620
+ ).fetchall()
621
+ else:
622
+ rows = self.execute(
623
+ """
624
+ SELECT MAX(transit_bytes_out) AS win_max
625
+ FROM transit_receipts_in
626
+ WHERE broadcaster = ?
627
+ GROUP BY grant_id, window
628
+ """,
629
+ (debtor.lower(),),
630
+ ).fetchall()
631
+ return sum(int(r[0] or 0) for r in rows)
632
+
633
+ def total_bartered_bytes(self, debtor: str) -> int:
634
+ with self._lock:
635
+ row = self.execute(
636
+ 'SELECT COALESCE(SUM(equivalent_transit_bytes), 0) AS total FROM transit_barter_credits WHERE debtor = ?',
637
+ (debtor.lower(),),
638
+ ).fetchone()
639
+ return int(row[0]) if row is not None else 0
640
+
641
+ def debt_summary(self, debtor: str, grant_id: Optional[str] = None) -> DebtSummary:
642
+ """Calculate gross transit bytes, bartered credits, unsettled debt, and
643
+ whether soft debt degradation (`degraded_audio_only`) is active.
644
+ """
645
+ gross = self.gross_transit_bytes(debtor, grant_id=grant_id)
646
+ bartered = self.total_bartered_bytes(debtor)
647
+ unsettled = max(0, gross - bartered)
648
+
649
+ ceiling = 0
650
+ if grant_id is not None:
651
+ grant = self.get_grant(grant_id)
652
+ if grant is not None:
653
+ ceiling = int(grant['max_unsettled_transit_bytes'])
654
+ else:
655
+ with self._lock:
656
+ row = self.execute(
657
+ 'SELECT COALESCE(MAX(max_unsettled_transit_bytes), 0) AS cap FROM transit_grants WHERE broadcaster = ?',
658
+ (debtor.lower(),),
659
+ ).fetchone()
660
+ ceiling = int(row[0]) if row is not None else 0
661
+
662
+ degraded = bool(ceiling > 0 and unsettled >= ceiling)
663
+ return {
664
+ 'debtor': debtor.lower(),
665
+ 'gross_transit_bytes': gross,
666
+ 'bartered_bytes': bartered,
667
+ 'cashu_settled_bytes': 0,
668
+ 'unsettled_bytes': unsettled,
669
+ 'max_unsettled_transit_bytes': ceiling,
670
+ 'degraded_audio_only': degraded,
671
+ }
672
+
673
+ def summary(self) -> dict[str, list[Any]]:
674
+ """Return a summary of all known debtors and barter credit rows."""
675
+ with self._lock:
676
+ rows = self.execute(
677
+ """
678
+ SELECT DISTINCT broadcaster AS debtor FROM transit_grants
679
+ UNION
680
+ SELECT DISTINCT broadcaster AS debtor FROM transit_receipts_in
681
+ UNION
682
+ SELECT DISTINCT debtor FROM transit_barter_credits
683
+ ORDER BY debtor ASC
684
+ """
685
+ ).fetchall()
686
+ debtors = [str(r[0]) for r in rows if r[0]]
687
+ debts = [self.debt_summary(d) for d in debtors]
688
+ credits: list[BarterCreditRow] = []
689
+ for d in debtors:
690
+ credits.extend(self.list_barter_credits(d))
691
+ return {
692
+ 'debts': debts,
693
+ 'utility_credits': credits,
694
+ }
695
+
696
+
697
+ __all__ = [
698
+ 'UtilityKind',
699
+ 'VALID_UTILITY_KINDS',
700
+ 'DEFAULT_UTILITY_BYTES_RATES',
701
+ 'BarterCreditRow',
702
+ 'DebtSummary',
703
+ 'OutboundChainState',
704
+ 'TransitLedgerStore',
705
+ ]