@optimystic/db-core 0.13.5 → 0.14.0

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 (447) hide show
  1. package/dist/src/cluster/structs.d.ts +13 -0
  2. package/dist/src/cluster/structs.d.ts.map +1 -1
  3. package/dist/src/cohort-topic/addressing.d.ts +53 -0
  4. package/dist/src/cohort-topic/addressing.d.ts.map +1 -0
  5. package/dist/src/cohort-topic/addressing.js +95 -0
  6. package/dist/src/cohort-topic/addressing.js.map +1 -0
  7. package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts +105 -0
  8. package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.d.ts.map +1 -0
  9. package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js +197 -0
  10. package/dist/src/cohort-topic/antidos/bootstrap-evidence-envelope.js.map +1 -0
  11. package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +50 -0
  12. package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -0
  13. package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +62 -0
  14. package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -0
  15. package/dist/src/cohort-topic/antidos/index.d.ts +6 -0
  16. package/dist/src/cohort-topic/antidos/index.d.ts.map +1 -0
  17. package/dist/src/cohort-topic/antidos/index.js +6 -0
  18. package/dist/src/cohort-topic/antidos/index.js.map +1 -0
  19. package/dist/src/cohort-topic/antidos/rate-limiter.d.ts +95 -0
  20. package/dist/src/cohort-topic/antidos/rate-limiter.d.ts.map +1 -0
  21. package/dist/src/cohort-topic/antidos/rate-limiter.js +146 -0
  22. package/dist/src/cohort-topic/antidos/rate-limiter.js.map +1 -0
  23. package/dist/src/cohort-topic/antidos/replay-guard.d.ts +37 -0
  24. package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -0
  25. package/dist/src/cohort-topic/antidos/replay-guard.js +78 -0
  26. package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -0
  27. package/dist/src/cohort-topic/antidos/topic-budget.d.ts +50 -0
  28. package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -0
  29. package/dist/src/cohort-topic/antidos/topic-budget.js +95 -0
  30. package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -0
  31. package/dist/src/cohort-topic/antiflood/index.d.ts +3 -0
  32. package/dist/src/cohort-topic/antiflood/index.d.ts.map +1 -0
  33. package/dist/src/cohort-topic/antiflood/index.js +3 -0
  34. package/dist/src/cohort-topic/antiflood/index.js.map +1 -0
  35. package/dist/src/cohort-topic/antiflood/invariants.d.ts +70 -0
  36. package/dist/src/cohort-topic/antiflood/invariants.d.ts.map +1 -0
  37. package/dist/src/cohort-topic/antiflood/invariants.js +86 -0
  38. package/dist/src/cohort-topic/antiflood/invariants.js.map +1 -0
  39. package/dist/src/cohort-topic/antiflood/jitter.d.ts +65 -0
  40. package/dist/src/cohort-topic/antiflood/jitter.d.ts.map +1 -0
  41. package/dist/src/cohort-topic/antiflood/jitter.js +75 -0
  42. package/dist/src/cohort-topic/antiflood/jitter.js.map +1 -0
  43. package/dist/src/cohort-topic/coldstart.d.ts +117 -0
  44. package/dist/src/cohort-topic/coldstart.d.ts.map +1 -0
  45. package/dist/src/cohort-topic/coldstart.js +127 -0
  46. package/dist/src/cohort-topic/coldstart.js.map +1 -0
  47. package/dist/src/cohort-topic/dmax.d.ts +44 -0
  48. package/dist/src/cohort-topic/dmax.d.ts.map +1 -0
  49. package/dist/src/cohort-topic/dmax.js +70 -0
  50. package/dist/src/cohort-topic/dmax.js.map +1 -0
  51. package/dist/src/cohort-topic/gossip/bus.d.ts +85 -0
  52. package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -0
  53. package/dist/src/cohort-topic/gossip/bus.js +146 -0
  54. package/dist/src/cohort-topic/gossip/bus.js.map +1 -0
  55. package/dist/src/cohort-topic/gossip/index.d.ts +4 -0
  56. package/dist/src/cohort-topic/gossip/index.d.ts.map +1 -0
  57. package/dist/src/cohort-topic/gossip/index.js +4 -0
  58. package/dist/src/cohort-topic/gossip/index.js.map +1 -0
  59. package/dist/src/cohort-topic/gossip/records.d.ts +12 -0
  60. package/dist/src/cohort-topic/gossip/records.d.ts.map +1 -0
  61. package/dist/src/cohort-topic/gossip/records.js +41 -0
  62. package/dist/src/cohort-topic/gossip/records.js.map +1 -0
  63. package/dist/src/cohort-topic/gossip/view.d.ts +39 -0
  64. package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -0
  65. package/dist/src/cohort-topic/gossip/view.js +30 -0
  66. package/dist/src/cohort-topic/gossip/view.js.map +1 -0
  67. package/dist/src/cohort-topic/index.d.ts +21 -0
  68. package/dist/src/cohort-topic/index.d.ts.map +1 -0
  69. package/dist/src/cohort-topic/index.js +21 -0
  70. package/dist/src/cohort-topic/index.js.map +1 -0
  71. package/dist/src/cohort-topic/load/barometer.d.ts +78 -0
  72. package/dist/src/cohort-topic/load/barometer.d.ts.map +1 -0
  73. package/dist/src/cohort-topic/load/barometer.js +93 -0
  74. package/dist/src/cohort-topic/load/barometer.js.map +1 -0
  75. package/dist/src/cohort-topic/load/index.d.ts +2 -0
  76. package/dist/src/cohort-topic/load/index.d.ts.map +1 -0
  77. package/dist/src/cohort-topic/load/index.js +2 -0
  78. package/dist/src/cohort-topic/load/index.js.map +1 -0
  79. package/dist/src/cohort-topic/member-engine.d.ts +134 -0
  80. package/dist/src/cohort-topic/member-engine.d.ts.map +1 -0
  81. package/dist/src/cohort-topic/member-engine.js +264 -0
  82. package/dist/src/cohort-topic/member-engine.js.map +1 -0
  83. package/dist/src/cohort-topic/membership/index.d.ts +4 -0
  84. package/dist/src/cohort-topic/membership/index.d.ts.map +1 -0
  85. package/dist/src/cohort-topic/membership/index.js +4 -0
  86. package/dist/src/cohort-topic/membership/index.js.map +1 -0
  87. package/dist/src/cohort-topic/membership/publisher.d.ts +82 -0
  88. package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -0
  89. package/dist/src/cohort-topic/membership/publisher.js +107 -0
  90. package/dist/src/cohort-topic/membership/publisher.js.map +1 -0
  91. package/dist/src/cohort-topic/membership/source.d.ts +31 -0
  92. package/dist/src/cohort-topic/membership/source.d.ts.map +1 -0
  93. package/dist/src/cohort-topic/membership/source.js +32 -0
  94. package/dist/src/cohort-topic/membership/source.js.map +1 -0
  95. package/dist/src/cohort-topic/membership/verifier.d.ts +96 -0
  96. package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -0
  97. package/dist/src/cohort-topic/membership/verifier.js +250 -0
  98. package/dist/src/cohort-topic/membership/verifier.js.map +1 -0
  99. package/dist/src/cohort-topic/ports.d.ts +149 -0
  100. package/dist/src/cohort-topic/ports.d.ts.map +1 -0
  101. package/dist/src/cohort-topic/ports.js +25 -0
  102. package/dist/src/cohort-topic/ports.js.map +1 -0
  103. package/dist/src/cohort-topic/promotion.d.ts +135 -0
  104. package/dist/src/cohort-topic/promotion.d.ts.map +1 -0
  105. package/dist/src/cohort-topic/promotion.js +269 -0
  106. package/dist/src/cohort-topic/promotion.js.map +1 -0
  107. package/dist/src/cohort-topic/registration/bytes.d.ts +16 -0
  108. package/dist/src/cohort-topic/registration/bytes.d.ts.map +1 -0
  109. package/dist/src/cohort-topic/registration/bytes.js +32 -0
  110. package/dist/src/cohort-topic/registration/bytes.js.map +1 -0
  111. package/dist/src/cohort-topic/registration/handoff.d.ts +69 -0
  112. package/dist/src/cohort-topic/registration/handoff.d.ts.map +1 -0
  113. package/dist/src/cohort-topic/registration/handoff.js +98 -0
  114. package/dist/src/cohort-topic/registration/handoff.js.map +1 -0
  115. package/dist/src/cohort-topic/registration/index.d.ts +7 -0
  116. package/dist/src/cohort-topic/registration/index.d.ts.map +1 -0
  117. package/dist/src/cohort-topic/registration/index.js +7 -0
  118. package/dist/src/cohort-topic/registration/index.js.map +1 -0
  119. package/dist/src/cohort-topic/registration/renewal.d.ts +115 -0
  120. package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -0
  121. package/dist/src/cohort-topic/registration/renewal.js +306 -0
  122. package/dist/src/cohort-topic/registration/renewal.js.map +1 -0
  123. package/dist/src/cohort-topic/registration/sharding.d.ts +31 -0
  124. package/dist/src/cohort-topic/registration/sharding.d.ts.map +1 -0
  125. package/dist/src/cohort-topic/registration/sharding.js +48 -0
  126. package/dist/src/cohort-topic/registration/sharding.js.map +1 -0
  127. package/dist/src/cohort-topic/registration/store.d.ts +13 -0
  128. package/dist/src/cohort-topic/registration/store.d.ts.map +1 -0
  129. package/dist/src/cohort-topic/registration/store.js +72 -0
  130. package/dist/src/cohort-topic/registration/store.js.map +1 -0
  131. package/dist/src/cohort-topic/registration/types.d.ts +71 -0
  132. package/dist/src/cohort-topic/registration/types.d.ts.map +1 -0
  133. package/dist/src/cohort-topic/registration/types.js +27 -0
  134. package/dist/src/cohort-topic/registration/types.js.map +1 -0
  135. package/dist/src/cohort-topic/ring-hash.d.ts +27 -0
  136. package/dist/src/cohort-topic/ring-hash.d.ts.map +1 -0
  137. package/dist/src/cohort-topic/ring-hash.js +45 -0
  138. package/dist/src/cohort-topic/ring-hash.js.map +1 -0
  139. package/dist/src/cohort-topic/service.d.ts +157 -0
  140. package/dist/src/cohort-topic/service.d.ts.map +1 -0
  141. package/dist/src/cohort-topic/service.js +237 -0
  142. package/dist/src/cohort-topic/service.js.map +1 -0
  143. package/dist/src/cohort-topic/sig/index.d.ts +3 -0
  144. package/dist/src/cohort-topic/sig/index.d.ts.map +1 -0
  145. package/dist/src/cohort-topic/sig/index.js +3 -0
  146. package/dist/src/cohort-topic/sig/index.js.map +1 -0
  147. package/dist/src/cohort-topic/sig/payloads.d.ts +25 -0
  148. package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -0
  149. package/dist/src/cohort-topic/sig/payloads.js +25 -0
  150. package/dist/src/cohort-topic/sig/payloads.js.map +1 -0
  151. package/dist/src/cohort-topic/sig/threshold.d.ts +33 -0
  152. package/dist/src/cohort-topic/sig/threshold.d.ts.map +1 -0
  153. package/dist/src/cohort-topic/sig/threshold.js +51 -0
  154. package/dist/src/cohort-topic/sig/threshold.js.map +1 -0
  155. package/dist/src/cohort-topic/tiers.d.ts +58 -0
  156. package/dist/src/cohort-topic/tiers.d.ts.map +1 -0
  157. package/dist/src/cohort-topic/tiers.js +49 -0
  158. package/dist/src/cohort-topic/tiers.js.map +1 -0
  159. package/dist/src/cohort-topic/traffic.d.ts +90 -0
  160. package/dist/src/cohort-topic/traffic.d.ts.map +1 -0
  161. package/dist/src/cohort-topic/traffic.js +146 -0
  162. package/dist/src/cohort-topic/traffic.js.map +1 -0
  163. package/dist/src/cohort-topic/walk.d.ts +123 -0
  164. package/dist/src/cohort-topic/walk.d.ts.map +1 -0
  165. package/dist/src/cohort-topic/walk.js +150 -0
  166. package/dist/src/cohort-topic/walk.js.map +1 -0
  167. package/dist/src/cohort-topic/willingness.d.ts +123 -0
  168. package/dist/src/cohort-topic/willingness.d.ts.map +1 -0
  169. package/dist/src/cohort-topic/willingness.js +143 -0
  170. package/dist/src/cohort-topic/willingness.js.map +1 -0
  171. package/dist/src/cohort-topic/wire/codec.d.ts +42 -0
  172. package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -0
  173. package/dist/src/cohort-topic/wire/codec.js +163 -0
  174. package/dist/src/cohort-topic/wire/codec.js.map +1 -0
  175. package/dist/src/cohort-topic/wire/index.d.ts +6 -0
  176. package/dist/src/cohort-topic/wire/index.d.ts.map +1 -0
  177. package/dist/src/cohort-topic/wire/index.js +6 -0
  178. package/dist/src/cohort-topic/wire/index.js.map +1 -0
  179. package/dist/src/cohort-topic/wire/payloads.d.ts +38 -0
  180. package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -0
  181. package/dist/src/cohort-topic/wire/payloads.js +102 -0
  182. package/dist/src/cohort-topic/wire/payloads.js.map +1 -0
  183. package/dist/src/cohort-topic/wire/types.d.ts +304 -0
  184. package/dist/src/cohort-topic/wire/types.d.ts.map +1 -0
  185. package/dist/src/cohort-topic/wire/types.js +16 -0
  186. package/dist/src/cohort-topic/wire/types.js.map +1 -0
  187. package/dist/src/cohort-topic/wire/validate.d.ts +25 -0
  188. package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -0
  189. package/dist/src/cohort-topic/wire/validate.js +427 -0
  190. package/dist/src/cohort-topic/wire/validate.js.map +1 -0
  191. package/dist/src/collection/collection.d.ts +58 -1
  192. package/dist/src/collection/collection.d.ts.map +1 -1
  193. package/dist/src/collection/collection.js +76 -0
  194. package/dist/src/collection/collection.js.map +1 -1
  195. package/dist/src/collections/tree/tree.d.ts +58 -2
  196. package/dist/src/collections/tree/tree.d.ts.map +1 -1
  197. package/dist/src/collections/tree/tree.js +59 -2
  198. package/dist/src/collections/tree/tree.js.map +1 -1
  199. package/dist/src/index.d.ts +3 -0
  200. package/dist/src/index.d.ts.map +1 -1
  201. package/dist/src/index.js +3 -0
  202. package/dist/src/index.js.map +1 -1
  203. package/dist/src/log/log.d.ts +42 -4
  204. package/dist/src/log/log.d.ts.map +1 -1
  205. package/dist/src/log/log.js +71 -3
  206. package/dist/src/log/log.js.map +1 -1
  207. package/dist/src/log/struct.d.ts +122 -2
  208. package/dist/src/log/struct.d.ts.map +1 -1
  209. package/dist/src/log/struct.js.map +1 -1
  210. package/dist/src/matchmaking/capability-filter.d.ts +24 -0
  211. package/dist/src/matchmaking/capability-filter.d.ts.map +1 -0
  212. package/dist/src/matchmaking/capability-filter.js +37 -0
  213. package/dist/src/matchmaking/capability-filter.js.map +1 -0
  214. package/dist/src/matchmaking/config.d.ts +77 -0
  215. package/dist/src/matchmaking/config.d.ts.map +1 -0
  216. package/dist/src/matchmaking/config.js +69 -0
  217. package/dist/src/matchmaking/config.js.map +1 -0
  218. package/dist/src/matchmaking/index.d.ts +21 -0
  219. package/dist/src/matchmaking/index.d.ts.map +1 -0
  220. package/dist/src/matchmaking/index.js +21 -0
  221. package/dist/src/matchmaking/index.js.map +1 -0
  222. package/dist/src/matchmaking/multi-cohort-seeker.d.ts +133 -0
  223. package/dist/src/matchmaking/multi-cohort-seeker.d.ts.map +1 -0
  224. package/dist/src/matchmaking/multi-cohort-seeker.js +142 -0
  225. package/dist/src/matchmaking/multi-cohort-seeker.js.map +1 -0
  226. package/dist/src/matchmaking/provider.d.ts +70 -0
  227. package/dist/src/matchmaking/provider.d.ts.map +1 -0
  228. package/dist/src/matchmaking/provider.js +93 -0
  229. package/dist/src/matchmaking/provider.js.map +1 -0
  230. package/dist/src/matchmaking/query-eval.d.ts +53 -0
  231. package/dist/src/matchmaking/query-eval.d.ts.map +1 -0
  232. package/dist/src/matchmaking/query-eval.js +69 -0
  233. package/dist/src/matchmaking/query-eval.js.map +1 -0
  234. package/dist/src/matchmaking/seeker-walk.d.ts +95 -0
  235. package/dist/src/matchmaking/seeker-walk.d.ts.map +1 -0
  236. package/dist/src/matchmaking/seeker-walk.js +75 -0
  237. package/dist/src/matchmaking/seeker-walk.js.map +1 -0
  238. package/dist/src/matchmaking/seeker.d.ts +51 -0
  239. package/dist/src/matchmaking/seeker.d.ts.map +1 -0
  240. package/dist/src/matchmaking/seeker.js +62 -0
  241. package/dist/src/matchmaking/seeker.js.map +1 -0
  242. package/dist/src/matchmaking/topic-anchor.d.ts +54 -0
  243. package/dist/src/matchmaking/topic-anchor.d.ts.map +1 -0
  244. package/dist/src/matchmaking/topic-anchor.js +67 -0
  245. package/dist/src/matchmaking/topic-anchor.js.map +1 -0
  246. package/dist/src/matchmaking/voting-quorum.d.ts +233 -0
  247. package/dist/src/matchmaking/voting-quorum.d.ts.map +1 -0
  248. package/dist/src/matchmaking/voting-quorum.js +238 -0
  249. package/dist/src/matchmaking/voting-quorum.js.map +1 -0
  250. package/dist/src/matchmaking/wire.d.ts +259 -0
  251. package/dist/src/matchmaking/wire.d.ts.map +1 -0
  252. package/dist/src/matchmaking/wire.js +484 -0
  253. package/dist/src/matchmaking/wire.js.map +1 -0
  254. package/dist/src/network/i-repo.d.ts +9 -0
  255. package/dist/src/network/i-repo.d.ts.map +1 -1
  256. package/dist/src/network/repo-protocol.d.ts +3 -1
  257. package/dist/src/network/repo-protocol.d.ts.map +1 -1
  258. package/dist/src/network/struct.d.ts +23 -1
  259. package/dist/src/network/struct.d.ts.map +1 -1
  260. package/dist/src/reactivity/backfill.d.ts +118 -0
  261. package/dist/src/reactivity/backfill.d.ts.map +1 -0
  262. package/dist/src/reactivity/backfill.js +137 -0
  263. package/dist/src/reactivity/backfill.js.map +1 -0
  264. package/dist/src/reactivity/backpressure.d.ts +99 -0
  265. package/dist/src/reactivity/backpressure.d.ts.map +1 -0
  266. package/dist/src/reactivity/backpressure.js +153 -0
  267. package/dist/src/reactivity/backpressure.js.map +1 -0
  268. package/dist/src/reactivity/checkpoint.d.ts +165 -0
  269. package/dist/src/reactivity/checkpoint.d.ts.map +1 -0
  270. package/dist/src/reactivity/checkpoint.js +232 -0
  271. package/dist/src/reactivity/checkpoint.js.map +1 -0
  272. package/dist/src/reactivity/config.d.ts +131 -0
  273. package/dist/src/reactivity/config.d.ts.map +1 -0
  274. package/dist/src/reactivity/config.js +135 -0
  275. package/dist/src/reactivity/config.js.map +1 -0
  276. package/dist/src/reactivity/dedupe.d.ts +51 -0
  277. package/dist/src/reactivity/dedupe.d.ts.map +1 -0
  278. package/dist/src/reactivity/dedupe.js +90 -0
  279. package/dist/src/reactivity/dedupe.js.map +1 -0
  280. package/dist/src/reactivity/forwarder.d.ts +50 -0
  281. package/dist/src/reactivity/forwarder.d.ts.map +1 -0
  282. package/dist/src/reactivity/forwarder.js +52 -0
  283. package/dist/src/reactivity/forwarder.js.map +1 -0
  284. package/dist/src/reactivity/index.d.ts +34 -0
  285. package/dist/src/reactivity/index.d.ts.map +1 -0
  286. package/dist/src/reactivity/index.js +34 -0
  287. package/dist/src/reactivity/index.js.map +1 -0
  288. package/dist/src/reactivity/notification.d.ts +72 -0
  289. package/dist/src/reactivity/notification.d.ts.map +1 -0
  290. package/dist/src/reactivity/notification.js +92 -0
  291. package/dist/src/reactivity/notification.js.map +1 -0
  292. package/dist/src/reactivity/policy.d.ts +59 -0
  293. package/dist/src/reactivity/policy.d.ts.map +1 -0
  294. package/dist/src/reactivity/policy.js +64 -0
  295. package/dist/src/reactivity/policy.js.map +1 -0
  296. package/dist/src/reactivity/push-state.d.ts +140 -0
  297. package/dist/src/reactivity/push-state.d.ts.map +1 -0
  298. package/dist/src/reactivity/push-state.js +270 -0
  299. package/dist/src/reactivity/push-state.js.map +1 -0
  300. package/dist/src/reactivity/recover.d.ts +75 -0
  301. package/dist/src/reactivity/recover.d.ts.map +1 -0
  302. package/dist/src/reactivity/recover.js +105 -0
  303. package/dist/src/reactivity/recover.js.map +1 -0
  304. package/dist/src/reactivity/replay-buffer.d.ts +57 -0
  305. package/dist/src/reactivity/replay-buffer.d.ts.map +1 -0
  306. package/dist/src/reactivity/replay-buffer.js +88 -0
  307. package/dist/src/reactivity/replay-buffer.js.map +1 -0
  308. package/dist/src/reactivity/resume.d.ts +214 -0
  309. package/dist/src/reactivity/resume.d.ts.map +1 -0
  310. package/dist/src/reactivity/resume.js +358 -0
  311. package/dist/src/reactivity/resume.js.map +1 -0
  312. package/dist/src/reactivity/rotation.d.ts +283 -0
  313. package/dist/src/reactivity/rotation.d.ts.map +1 -0
  314. package/dist/src/reactivity/rotation.js +265 -0
  315. package/dist/src/reactivity/rotation.js.map +1 -0
  316. package/dist/src/reactivity/subscriber.d.ts +66 -0
  317. package/dist/src/reactivity/subscriber.d.ts.map +1 -0
  318. package/dist/src/reactivity/subscriber.js +75 -0
  319. package/dist/src/reactivity/subscriber.js.map +1 -0
  320. package/dist/src/reactivity/subscription.d.ts +52 -0
  321. package/dist/src/reactivity/subscription.d.ts.map +1 -0
  322. package/dist/src/reactivity/subscription.js +28 -0
  323. package/dist/src/reactivity/subscription.js.map +1 -0
  324. package/dist/src/reactivity/topic-anchor.d.ts +46 -0
  325. package/dist/src/reactivity/topic-anchor.d.ts.map +1 -0
  326. package/dist/src/reactivity/topic-anchor.js +61 -0
  327. package/dist/src/reactivity/topic-anchor.js.map +1 -0
  328. package/dist/src/reactivity/verify.d.ts +41 -0
  329. package/dist/src/reactivity/verify.d.ts.map +1 -0
  330. package/dist/src/reactivity/verify.js +48 -0
  331. package/dist/src/reactivity/verify.js.map +1 -0
  332. package/dist/src/reactivity/wire-validate.d.ts +28 -0
  333. package/dist/src/reactivity/wire-validate.d.ts.map +1 -0
  334. package/dist/src/reactivity/wire-validate.js +79 -0
  335. package/dist/src/reactivity/wire-validate.js.map +1 -0
  336. package/dist/src/reactivity/wire.d.ts +102 -0
  337. package/dist/src/reactivity/wire.d.ts.map +1 -0
  338. package/dist/src/reactivity/wire.js +129 -0
  339. package/dist/src/reactivity/wire.js.map +1 -0
  340. package/dist/src/transaction/coordinator.d.ts +9 -5
  341. package/dist/src/transaction/coordinator.d.ts.map +1 -1
  342. package/dist/src/transaction/coordinator.js +48 -25
  343. package/dist/src/transaction/coordinator.js.map +1 -1
  344. package/dist/src/transactor/change-notifier.d.ts +74 -0
  345. package/dist/src/transactor/change-notifier.d.ts.map +1 -0
  346. package/dist/src/transactor/change-notifier.js +4 -0
  347. package/dist/src/transactor/change-notifier.js.map +1 -0
  348. package/dist/src/transactor/index.d.ts +1 -0
  349. package/dist/src/transactor/index.d.ts.map +1 -1
  350. package/dist/src/transactor/index.js +1 -0
  351. package/dist/src/transactor/index.js.map +1 -1
  352. package/dist/src/transactor/network-transactor.d.ts +34 -2
  353. package/dist/src/transactor/network-transactor.d.ts.map +1 -1
  354. package/dist/src/transactor/network-transactor.js +113 -13
  355. package/dist/src/transactor/network-transactor.js.map +1 -1
  356. package/package.json +2 -2
  357. package/src/cluster/structs.ts +13 -0
  358. package/src/cohort-topic/addressing.ts +112 -0
  359. package/src/cohort-topic/antidos/bootstrap-evidence-envelope.ts +253 -0
  360. package/src/cohort-topic/antidos/bootstrap-evidence.ts +94 -0
  361. package/src/cohort-topic/antidos/index.ts +5 -0
  362. package/src/cohort-topic/antidos/rate-limiter.ts +210 -0
  363. package/src/cohort-topic/antidos/replay-guard.ts +109 -0
  364. package/src/cohort-topic/antidos/topic-budget.ts +145 -0
  365. package/src/cohort-topic/antiflood/index.ts +2 -0
  366. package/src/cohort-topic/antiflood/invariants.ts +108 -0
  367. package/src/cohort-topic/antiflood/jitter.ts +117 -0
  368. package/src/cohort-topic/coldstart.ts +204 -0
  369. package/src/cohort-topic/dmax.ts +88 -0
  370. package/src/cohort-topic/gossip/bus.ts +217 -0
  371. package/src/cohort-topic/gossip/index.ts +3 -0
  372. package/src/cohort-topic/gossip/records.ts +45 -0
  373. package/src/cohort-topic/gossip/view.ts +66 -0
  374. package/src/cohort-topic/index.ts +20 -0
  375. package/src/cohort-topic/load/barometer.ts +134 -0
  376. package/src/cohort-topic/load/index.ts +1 -0
  377. package/src/cohort-topic/member-engine.ts +409 -0
  378. package/src/cohort-topic/membership/index.ts +3 -0
  379. package/src/cohort-topic/membership/publisher.ts +171 -0
  380. package/src/cohort-topic/membership/source.ts +41 -0
  381. package/src/cohort-topic/membership/verifier.ts +345 -0
  382. package/src/cohort-topic/ports.ts +157 -0
  383. package/src/cohort-topic/promotion.ts +384 -0
  384. package/src/cohort-topic/registration/bytes.ts +37 -0
  385. package/src/cohort-topic/registration/handoff.ts +154 -0
  386. package/src/cohort-topic/registration/index.ts +6 -0
  387. package/src/cohort-topic/registration/renewal.ts +423 -0
  388. package/src/cohort-topic/registration/sharding.ts +61 -0
  389. package/src/cohort-topic/registration/store.ts +81 -0
  390. package/src/cohort-topic/registration/types.ts +76 -0
  391. package/src/cohort-topic/ring-hash.ts +50 -0
  392. package/src/cohort-topic/service.ts +390 -0
  393. package/src/cohort-topic/sig/index.ts +2 -0
  394. package/src/cohort-topic/sig/payloads.ts +39 -0
  395. package/src/cohort-topic/sig/threshold.ts +64 -0
  396. package/src/cohort-topic/tiers.ts +74 -0
  397. package/src/cohort-topic/traffic.ts +211 -0
  398. package/src/cohort-topic/walk.ts +255 -0
  399. package/src/cohort-topic/willingness.ts +237 -0
  400. package/src/cohort-topic/wire/codec.ts +204 -0
  401. package/src/cohort-topic/wire/index.ts +16 -0
  402. package/src/cohort-topic/wire/payloads.ts +117 -0
  403. package/src/cohort-topic/wire/types.ts +343 -0
  404. package/src/cohort-topic/wire/validate.ts +478 -0
  405. package/src/collection/collection.ts +93 -1
  406. package/src/collections/tree/tree.ts +84 -3
  407. package/src/index.ts +3 -0
  408. package/src/log/log.ts +86 -7
  409. package/src/log/struct.ts +126 -2
  410. package/src/matchmaking/capability-filter.ts +45 -0
  411. package/src/matchmaking/config.ts +98 -0
  412. package/src/matchmaking/index.ts +21 -0
  413. package/src/matchmaking/multi-cohort-seeker.ts +234 -0
  414. package/src/matchmaking/provider.ts +123 -0
  415. package/src/matchmaking/query-eval.ts +105 -0
  416. package/src/matchmaking/seeker-walk.ts +127 -0
  417. package/src/matchmaking/seeker.ts +86 -0
  418. package/src/matchmaking/topic-anchor.ts +90 -0
  419. package/src/matchmaking/voting-quorum.ts +394 -0
  420. package/src/matchmaking/wire.ts +680 -0
  421. package/src/network/i-repo.ts +9 -0
  422. package/src/network/repo-protocol.ts +3 -2
  423. package/src/network/struct.ts +24 -1
  424. package/src/reactivity/backfill.ts +220 -0
  425. package/src/reactivity/backpressure.ts +191 -0
  426. package/src/reactivity/checkpoint.ts +308 -0
  427. package/src/reactivity/config.ts +172 -0
  428. package/src/reactivity/dedupe.ts +132 -0
  429. package/src/reactivity/forwarder.ts +87 -0
  430. package/src/reactivity/index.ts +34 -0
  431. package/src/reactivity/notification.ts +123 -0
  432. package/src/reactivity/policy.ts +79 -0
  433. package/src/reactivity/push-state.ts +343 -0
  434. package/src/reactivity/recover.ts +153 -0
  435. package/src/reactivity/replay-buffer.ts +141 -0
  436. package/src/reactivity/resume.ts +549 -0
  437. package/src/reactivity/rotation.ts +415 -0
  438. package/src/reactivity/subscriber.ts +132 -0
  439. package/src/reactivity/subscription.ts +66 -0
  440. package/src/reactivity/topic-anchor.ts +71 -0
  441. package/src/reactivity/verify.ts +73 -0
  442. package/src/reactivity/wire-validate.ts +87 -0
  443. package/src/reactivity/wire.ts +224 -0
  444. package/src/transaction/coordinator.ts +53 -27
  445. package/src/transactor/change-notifier.ts +80 -0
  446. package/src/transactor/index.ts +1 -0
  447. package/src/transactor/network-transactor.ts +133 -15
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Cohort-topic substrate — per-member willingness / admission control.
3
+ *
4
+ * Transcribed from `docs/cohort-topic.md` §Willingness and §Tier ladder, folded back from the
5
+ * simulator-validated `packages/substrate-simulator/src/{willingness,backoff}.ts`. When FRET's
6
+ * `RouteAndMaybeAct` lands a `RegisterV1` on a member, that member runs this check and returns one
7
+ * of three outcomes:
8
+ *
9
+ * - **{@link AcceptedOutcome}** — the routed member is willing; it becomes `primary`.
10
+ * - **{@link UnwillingMemberOutcome}** — the routed member is personally unwilling, but the gossiped
11
+ * willingness vector says siblings will serve this tier; the caller retries a named sibling at the
12
+ * *same* coord (spatial move within the cohort).
13
+ * - **{@link UnwillingCohortOutcome}** — fewer than a quorum of members are willing to serve the
14
+ * tier, so the cohort declines the tier entirely; the caller backs off in *time* (no spatial
15
+ * move — see §Why the caller doesn't walk on UnwillingCohort). The `retryAfterMs` follows the
16
+ * capped-doubling curve in {@link backoffRetryMs}.
17
+ *
18
+ * A member's *live* willingness for a tier is: its profile serves the tier, the tier's load bucket
19
+ * is below `overloadBucket`, **and** it is under its per-tier primary-topic budget. The coarse 1-bit
20
+ * gossiped willingness vector ({@link selfWillingnessBits}) carries only profile-∧-load — the budget
21
+ * is a per-registration gate applied live at the routed member. (Resolved open question: willingness
22
+ * stays at **1 bit per tier** — no finer T3 gradations; the load bucket already supplies coarse load.)
23
+ */
24
+
25
+ import { createLoadBarometer, DEFAULT_OVERLOAD_BUCKET, type LoadBarometerState } from "./load/barometer.js";
26
+ import type { CohortView } from "./gossip/view.js";
27
+ import { b64urlToBytes } from "./wire/codec.js";
28
+ import type { RegisterV1 } from "./wire/types.js";
29
+ import { ALL_TIERS, type NodeProfile, type Tier } from "./tiers.js";
30
+
31
+ /** The routed member is willing and will serve as `primary`. */
32
+ export interface AcceptedOutcome {
33
+ readonly kind: "accepted";
34
+ }
35
+
36
+ /** Routed member unwilling; these siblings (raw peer-id bytes) will serve — retry one at the same coord. */
37
+ export interface UnwillingMemberOutcome {
38
+ readonly kind: "unwilling_member";
39
+ readonly candidateMembers: Uint8Array[];
40
+ }
41
+
42
+ /** No quorum of willing members; back off `retryAfterMs` in time and retry from `d_max`. */
43
+ export interface UnwillingCohortOutcome {
44
+ readonly kind: "unwilling_cohort";
45
+ readonly retryAfterMs: number;
46
+ }
47
+
48
+ export type WillingnessOutcome = AcceptedOutcome | UnwillingMemberOutcome | UnwillingCohortOutcome;
49
+
50
+ /** Per-member willingness / admission check. */
51
+ export interface WillingnessCheck {
52
+ /** Classify `reg` routed onto this member, given the member's static profile and the clock. */
53
+ evaluate(reg: RegisterV1, self: NodeProfile, now: number): WillingnessOutcome;
54
+ }
55
+
56
+ // --- willingness-vector bit packing (folded back from simulator `willingnessBits`) ---
57
+
58
+ /** Test bit `tier` (T0 = bit 0 … T3 = bit 3) of a packed 4-bit willingness vector. */
59
+ export function tierBit(bits: number, tier: Tier): boolean {
60
+ return (bits & (1 << tier)) !== 0;
61
+ }
62
+
63
+ /** Pack a per-tier predicate into a 4-bit willingness vector (bit `t` set iff `willing(t)`). */
64
+ export function packWillingnessBits(willing: (tier: Tier) => boolean): number {
65
+ let bits = 0;
66
+ for (const t of ALL_TIERS) {
67
+ if (willing(t)) bits |= 1 << t;
68
+ }
69
+ return bits;
70
+ }
71
+
72
+ /** The packed vector as a single hex nibble — the `CohortGossipV1.willingnessBits` wire form. */
73
+ export function willingnessBitsHex(bits: number): string {
74
+ return (bits & 0xf).toString(16);
75
+ }
76
+
77
+ /**
78
+ * This member's gossiped (coarse) willingness vector: bit `t` set iff the profile serves `t` and the
79
+ * tier is not shed under load. The per-tier primary-topic budget is deliberately **not** folded in
80
+ * — it is a live per-registration gate, not a gossiped signal (§Willingness: the gossiped vector is
81
+ * "one bit per tier per member… refreshed every gossip round").
82
+ */
83
+ export function selfWillingnessBits(self: NodeProfile, barometer: LoadBarometerState): number {
84
+ return packWillingnessBits((t) => self.willingTiers.has(t) && barometer.loadWilling(t));
85
+ }
86
+
87
+ // --- exponential UnwillingCohort back-off (settled params, docs §Willingness L260-263) ---
88
+
89
+ export interface BackoffConfig {
90
+ /** First-rejection delay (ms). Default 1000. */
91
+ readonly baseMs: number;
92
+ /** Geometric growth per rejection. Default 2 (doubling). */
93
+ readonly factor: number;
94
+ /** Hard ceiling on a single delay (ms). Default 60000. */
95
+ readonly capMs: number;
96
+ }
97
+
98
+ /** Settled back-off curve (`docs/cohort-topic.md` §Willingness): `base = 1 s`, `factor = 2`, `cap = 60 s`. */
99
+ export const DEFAULT_BACKOFF_CONFIG: BackoffConfig = {
100
+ baseMs: 1000,
101
+ factor: 2,
102
+ capMs: 60_000,
103
+ };
104
+
105
+ /**
106
+ * `retryAfter` for the `attempt`-th rejection (0-based): `⌊base · factor^attempt⌋` capped at `capMs`.
107
+ * The capped doubling bounds the rejections a participant suffers across an overload window at
108
+ * `O(log(window/base))` (≤ ~6 to span 60 s) rather than the `window/base` a fixed interval incurs.
109
+ * As survivors of a burst back off geometrically, offered load sheds and accepted/sec holds at the
110
+ * willing-quorum capacity without a cascade.
111
+ */
112
+ export function backoffRetryMs(attempt: number, config: BackoffConfig = DEFAULT_BACKOFF_CONFIG): number {
113
+ if (!Number.isInteger(attempt) || attempt < 0) {
114
+ throw new RangeError(`attempt must be a non-negative integer, got ${attempt}`);
115
+ }
116
+ const raw = config.baseMs * Math.pow(config.factor, attempt);
117
+ return Math.min(Math.floor(raw), config.capMs);
118
+ }
119
+
120
+ // --- willingness check ---
121
+
122
+ export interface WillingnessConfig {
123
+ /** Cohort size `k` (drives the default quorum). Default 16. */
124
+ cohortSize?: number;
125
+ /**
126
+ * Members that must be willing to serve a tier for the cohort to take it on. Default: strict
127
+ * majority of `cohortSize` (`⌊k/2⌋ + 1`). Not pinned by `docs/cohort-topic.md` — §Tier ladder
128
+ * only requires "a quorum"; configurable here, revisitable by the Edge/Core policy ticket.
129
+ */
130
+ quorum?: number;
131
+ /** Load bucket at/above which a tier is shed. Default {@link DEFAULT_OVERLOAD_BUCKET} (6). */
132
+ overloadBucket?: number;
133
+ /**
134
+ * Max topics this member may be `primary` for at a single tier. Default 2048 (`topics_max`).
135
+ * The doc names the per-member per-tier budget but pins no number; configurable here.
136
+ */
137
+ maxPrimaryTopicsPerTier?: number;
138
+ /** Back-off curve for `unwilling_cohort` retries. Default {@link DEFAULT_BACKOFF_CONFIG}. */
139
+ backoff?: BackoffConfig;
140
+ }
141
+
142
+ export interface WillingnessDeps {
143
+ /** This member's own per-tier load barometer (live willingness uses its buckets). */
144
+ barometer: LoadBarometerState;
145
+ /** Merged per-member gossip view — the sibling willingness vectors. */
146
+ view: CohortView;
147
+ /** This member's own id, base64url (the `fromMember` key) — excluded from the sibling scan. */
148
+ selfMember: string;
149
+ /** Count of topics this member is already `primary` for at `tier` (the budget gate input). */
150
+ primaryTopicCount: (tier: Tier) => number;
151
+ /**
152
+ * Rejection-attempt count for `reg`, driving the `retryAfter` curve. The anti-DoS rate limiter
153
+ * owns the counter; the *curve* lives here. Default `() => 0` (first-rejection delay).
154
+ */
155
+ attempts?: (reg: RegisterV1) => number;
156
+ config?: WillingnessConfig;
157
+ }
158
+
159
+ /** Default quorum: strict majority of the cohort. */
160
+ export function defaultQuorum(cohortSize: number): number {
161
+ return Math.floor(cohortSize / 2) + 1;
162
+ }
163
+
164
+ class GossipWillingnessCheck implements WillingnessCheck {
165
+ private readonly quorum: number;
166
+ private readonly overloadBucket: number;
167
+ private readonly maxPrimaryTopicsPerTier: number;
168
+ private readonly backoff: BackoffConfig;
169
+ private readonly attempts: (reg: RegisterV1) => number;
170
+
171
+ constructor(private readonly deps: WillingnessDeps) {
172
+ const cfg = deps.config ?? {};
173
+ const cohortSize = cfg.cohortSize ?? 16;
174
+ this.quorum = cfg.quorum ?? defaultQuorum(cohortSize);
175
+ this.overloadBucket = cfg.overloadBucket ?? DEFAULT_OVERLOAD_BUCKET;
176
+ this.maxPrimaryTopicsPerTier = cfg.maxPrimaryTopicsPerTier ?? 2048;
177
+ this.backoff = cfg.backoff ?? DEFAULT_BACKOFF_CONFIG;
178
+ this.attempts = deps.attempts ?? ((): number => 0);
179
+ }
180
+
181
+ evaluate(reg: RegisterV1, self: NodeProfile, now: number): WillingnessOutcome {
182
+ const tier = reg.tier as Tier;
183
+ if (!ALL_TIERS.includes(tier)) {
184
+ // An op tier outside T0..T3 is not serviceable; treat as a cohort-level decline.
185
+ return { kind: "unwilling_cohort", retryAfterMs: backoffRetryMs(this.attempts(reg), this.backoff) };
186
+ }
187
+
188
+ const selfWilling = this.selfLiveWilling(self, tier);
189
+ const siblings = this.willingSiblings(tier);
190
+ const willingCount = siblings.length + (selfWilling ? 1 : 0);
191
+
192
+ // Quorum gate (§Tier ladder): the cohort takes on tier-T duties only if a quorum of members
193
+ // is willing; otherwise registrations get UnwillingCohort and the caller backs off in time.
194
+ if (willingCount < this.quorum) {
195
+ return { kind: "unwilling_cohort", retryAfterMs: backoffRetryMs(this.attempts(reg), this.backoff) };
196
+ }
197
+ if (selfWilling) {
198
+ return { kind: "accepted" };
199
+ }
200
+ // Quorum holds and self is unwilling → some sibling will serve.
201
+ return { kind: "unwilling_member", candidateMembers: siblings.map((m) => b64urlToBytes(m)) };
202
+ }
203
+
204
+ /** Live willingness of *this* member for `tier`: profile ∧ load-under-threshold ∧ under budget. */
205
+ private selfLiveWilling(self: NodeProfile, tier: Tier): boolean {
206
+ return (
207
+ self.willingTiers.has(tier) &&
208
+ this.deps.barometer.bucket(tier) < this.overloadBucket &&
209
+ this.deps.primaryTopicCount(tier) < this.maxPrimaryTopicsPerTier
210
+ );
211
+ }
212
+
213
+ /** Sibling member keys (base64url) whose *gossiped* willingness vector serves `tier`. */
214
+ private willingSiblings(tier: Tier): string[] {
215
+ const out: string[] = [];
216
+ for (const [member, contribution] of this.deps.view.all()) {
217
+ if (member === this.deps.selfMember) continue; // self counted via live willingness
218
+ if (tierBit(contribution.willingness, tier)) {
219
+ out.push(member);
220
+ }
221
+ }
222
+ return out;
223
+ }
224
+ }
225
+
226
+ /** Build a {@link WillingnessCheck} over the injected barometer + gossip view + budget source. */
227
+ export function createWillingnessCheck(deps: WillingnessDeps): WillingnessCheck {
228
+ return new GossipWillingnessCheck(deps);
229
+ }
230
+
231
+ /** Convenience: a willingness check with a fresh idle barometer (tests / single-tier callers). */
232
+ export function createWillingnessCheckWithIdleBarometer(
233
+ deps: Omit<WillingnessDeps, "barometer">,
234
+ ): { check: WillingnessCheck; barometer: LoadBarometerState } {
235
+ const barometer = createLoadBarometer({ overloadBucket: deps.config?.overloadBucket });
236
+ return { check: createWillingnessCheck({ ...deps, barometer }), barometer };
237
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Cohort-topic substrate — wire codec.
3
+ *
4
+ * Framing: every message is a 4-byte big-endian unsigned length prefix followed by the UTF-8
5
+ * JSON body, so a framed message is self-delimiting on a stream. Frames whose declared body
6
+ * length exceeds {@link DEFAULT_MAX_MESSAGE_BYTES} (or a caller-supplied ceiling) are rejected
7
+ * with {@link CohortWireError} before any allocation against the declared size.
8
+ *
9
+ * Byte fields travel as base64url (no padding); {@link bytesToB64url} / {@link b64urlToBytes}
10
+ * are the shared, cross-platform (browser/node/RN) helpers — no Node `Buffer`.
11
+ */
12
+
13
+ import { CohortWireError } from "./validate.js";
14
+ import {
15
+ validateCohortGossipV1,
16
+ validateDemotionNoticeV1,
17
+ validateMembershipCertV1,
18
+ validatePromotionNoticeV1,
19
+ validateRegisterReplyV1,
20
+ validateRegisterV1,
21
+ validateRenewReplyV1,
22
+ validateRenewV1,
23
+ validateSignRequestV1,
24
+ validateSignReplyV1,
25
+ } from "./validate.js";
26
+ import type {
27
+ CohortGossipV1,
28
+ DemotionNoticeV1,
29
+ MembershipCertV1,
30
+ PromotionNoticeV1,
31
+ RegisterReplyV1,
32
+ RegisterV1,
33
+ RenewReplyV1,
34
+ RenewV1,
35
+ SignRequestV1,
36
+ SignReplyV1,
37
+ } from "./types.js";
38
+
39
+ /**
40
+ * Default ceiling on a framed body, in bytes. Sized conservatively at 1 MiB until the
41
+ * simulator-validated cohort-gossip worst case (`topics_max` topic summaries, see
42
+ * `docs/cohort-topic.md` §Configuration) is converted into an exact bound.
43
+ *
44
+ * TODO(cohort-topic): derive the exact ceiling from `topics_max` (2048) × the per-summary JSON
45
+ * size plus the willingness/load header, rather than this flat 1 MiB cap.
46
+ */
47
+ export const DEFAULT_MAX_MESSAGE_BYTES = 1024 * 1024;
48
+
49
+ const LENGTH_PREFIX_BYTES = 4;
50
+
51
+ const utf8Encoder = new TextEncoder();
52
+ const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
53
+
54
+ // --- base64url helpers (no padding, cross-platform) ---
55
+
56
+ const B64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
57
+
58
+ const B64URL_LOOKUP: ReadonlyArray<number> = ((): number[] => {
59
+ const table = new Array<number>(128).fill(-1);
60
+ for (let i = 0; i < B64URL_ALPHABET.length; i++) {
61
+ table[B64URL_ALPHABET.charCodeAt(i)] = i;
62
+ }
63
+ return table;
64
+ })();
65
+
66
+ /** Encode bytes as base64url with no padding. */
67
+ export function bytesToB64url(b: Uint8Array): string {
68
+ let out = "";
69
+ let i = 0;
70
+ for (; i + 3 <= b.length; i += 3) {
71
+ const n = (b[i]! << 16) | (b[i + 1]! << 8) | b[i + 2]!;
72
+ out += B64URL_ALPHABET[(n >> 18) & 63]! + B64URL_ALPHABET[(n >> 12) & 63]! + B64URL_ALPHABET[(n >> 6) & 63]! + B64URL_ALPHABET[n & 63]!;
73
+ }
74
+ const rem = b.length - i;
75
+ if (rem === 1) {
76
+ const n = b[i]! << 16;
77
+ out += B64URL_ALPHABET[(n >> 18) & 63]! + B64URL_ALPHABET[(n >> 12) & 63]!;
78
+ } else if (rem === 2) {
79
+ const n = (b[i]! << 16) | (b[i + 1]! << 8);
80
+ out += B64URL_ALPHABET[(n >> 18) & 63]! + B64URL_ALPHABET[(n >> 12) & 63]! + B64URL_ALPHABET[(n >> 6) & 63]!;
81
+ }
82
+ return out;
83
+ }
84
+
85
+ /** Decode a base64url string (no padding) to bytes. Throws {@link CohortWireError} on bad input. */
86
+ export function b64urlToBytes(s: string): Uint8Array {
87
+ const len = s.length;
88
+ if (len % 4 === 1) {
89
+ throw new CohortWireError("base64url: invalid length");
90
+ }
91
+ const fullGroups = len >> 2;
92
+ const rem = len & 3;
93
+ const outLen = fullGroups * 3 + (rem === 0 ? 0 : rem - 1);
94
+ const out = new Uint8Array(outLen);
95
+ let si = 0;
96
+ let oi = 0;
97
+ const sext = (ch: number): number => {
98
+ const v = ch < 128 ? B64URL_LOOKUP[ch]! : -1;
99
+ if (v < 0) {
100
+ throw new CohortWireError("base64url: invalid character");
101
+ }
102
+ return v;
103
+ };
104
+ for (let g = 0; g < fullGroups; g++) {
105
+ const n = (sext(s.charCodeAt(si)) << 18) | (sext(s.charCodeAt(si + 1)) << 12) | (sext(s.charCodeAt(si + 2)) << 6) | sext(s.charCodeAt(si + 3));
106
+ out[oi++] = (n >> 16) & 0xff;
107
+ out[oi++] = (n >> 8) & 0xff;
108
+ out[oi++] = n & 0xff;
109
+ si += 4;
110
+ }
111
+ if (rem === 2) {
112
+ const n = (sext(s.charCodeAt(si)) << 18) | (sext(s.charCodeAt(si + 1)) << 12);
113
+ out[oi++] = (n >> 16) & 0xff;
114
+ } else if (rem === 3) {
115
+ const n = (sext(s.charCodeAt(si)) << 18) | (sext(s.charCodeAt(si + 1)) << 12) | (sext(s.charCodeAt(si + 2)) << 6);
116
+ out[oi++] = (n >> 16) & 0xff;
117
+ out[oi++] = (n >> 8) & 0xff;
118
+ }
119
+ return out;
120
+ }
121
+
122
+ // --- framing ---
123
+
124
+ /** Encode a V1 message as a length-prefixed UTF-8 JSON frame. */
125
+ export function encodeCohortMessage<T extends { v: 1 }>(msg: T, maxMessageBytes: number = DEFAULT_MAX_MESSAGE_BYTES): Uint8Array {
126
+ const body = utf8Encoder.encode(JSON.stringify(msg));
127
+ if (body.length > maxMessageBytes) {
128
+ throw new CohortWireError(`message body ${body.length} exceeds max_message_bytes ${maxMessageBytes}`);
129
+ }
130
+ const frame = new Uint8Array(LENGTH_PREFIX_BYTES + body.length);
131
+ const view = new DataView(frame.buffer);
132
+ view.setUint32(0, body.length, false);
133
+ frame.set(body, LENGTH_PREFIX_BYTES);
134
+ return frame;
135
+ }
136
+
137
+ /** Strip the length prefix and `JSON.parse` the body. Throws {@link CohortWireError} on a malformed frame. */
138
+ export function decodeCohortMessage(bytes: Uint8Array, maxMessageBytes: number = DEFAULT_MAX_MESSAGE_BYTES): unknown {
139
+ if (bytes.length < LENGTH_PREFIX_BYTES) {
140
+ throw new CohortWireError("frame too short for length prefix");
141
+ }
142
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
143
+ const declared = view.getUint32(0, false);
144
+ if (declared > maxMessageBytes) {
145
+ throw new CohortWireError(`declared body length ${declared} exceeds max_message_bytes ${maxMessageBytes}`);
146
+ }
147
+ if (LENGTH_PREFIX_BYTES + declared !== bytes.length) {
148
+ throw new CohortWireError(`frame length mismatch: declared ${declared}, have ${bytes.length - LENGTH_PREFIX_BYTES}`);
149
+ }
150
+ const body = bytes.subarray(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + declared);
151
+ let text: string;
152
+ try {
153
+ text = utf8Decoder.decode(body);
154
+ } catch {
155
+ throw new CohortWireError("frame body is not valid UTF-8");
156
+ }
157
+ try {
158
+ return JSON.parse(text) as unknown;
159
+ } catch {
160
+ throw new CohortWireError("frame body is not valid JSON");
161
+ }
162
+ }
163
+
164
+ // --- typed decoders (decode frame → validate → narrow) ---
165
+
166
+ export function decodeRegisterV1(bytes: Uint8Array, maxMessageBytes?: number): RegisterV1 {
167
+ return validateRegisterV1(decodeCohortMessage(bytes, maxMessageBytes));
168
+ }
169
+
170
+ export function decodeRegisterReplyV1(bytes: Uint8Array, maxMessageBytes?: number): RegisterReplyV1 {
171
+ return validateRegisterReplyV1(decodeCohortMessage(bytes, maxMessageBytes));
172
+ }
173
+
174
+ export function decodeRenewV1(bytes: Uint8Array, maxMessageBytes?: number): RenewV1 {
175
+ return validateRenewV1(decodeCohortMessage(bytes, maxMessageBytes));
176
+ }
177
+
178
+ export function decodeRenewReplyV1(bytes: Uint8Array, maxMessageBytes?: number): RenewReplyV1 {
179
+ return validateRenewReplyV1(decodeCohortMessage(bytes, maxMessageBytes));
180
+ }
181
+
182
+ export function decodePromotionNoticeV1(bytes: Uint8Array, maxMessageBytes?: number): PromotionNoticeV1 {
183
+ return validatePromotionNoticeV1(decodeCohortMessage(bytes, maxMessageBytes));
184
+ }
185
+
186
+ export function decodeDemotionNoticeV1(bytes: Uint8Array, maxMessageBytes?: number): DemotionNoticeV1 {
187
+ return validateDemotionNoticeV1(decodeCohortMessage(bytes, maxMessageBytes));
188
+ }
189
+
190
+ export function decodeCohortGossipV1(bytes: Uint8Array, maxMessageBytes?: number): CohortGossipV1 {
191
+ return validateCohortGossipV1(decodeCohortMessage(bytes, maxMessageBytes));
192
+ }
193
+
194
+ export function decodeMembershipCertV1(bytes: Uint8Array, maxMessageBytes?: number): MembershipCertV1 {
195
+ return validateMembershipCertV1(decodeCohortMessage(bytes, maxMessageBytes));
196
+ }
197
+
198
+ export function decodeSignRequestV1(bytes: Uint8Array, maxMessageBytes?: number): SignRequestV1 {
199
+ return validateSignRequestV1(decodeCohortMessage(bytes, maxMessageBytes));
200
+ }
201
+
202
+ export function decodeSignReplyV1(bytes: Uint8Array, maxMessageBytes?: number): SignReplyV1 {
203
+ return validateSignReplyV1(decodeCohortMessage(bytes, maxMessageBytes));
204
+ }
@@ -0,0 +1,16 @@
1
+ export * from "./types.js";
2
+ export * from "./codec.js";
3
+ export * from "./payloads.js";
4
+ export { CohortWireError } from "./validate.js";
5
+ export {
6
+ validateRegisterV1,
7
+ validateRegisterReplyV1,
8
+ validateRenewV1,
9
+ validateRenewReplyV1,
10
+ validatePromotionNoticeV1,
11
+ validateDemotionNoticeV1,
12
+ validateCohortGossipV1,
13
+ validateMembershipCertV1,
14
+ validateSignRequestV1,
15
+ validateSignReplyV1,
16
+ } from "./validate.js";
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Cohort-topic substrate — canonical participant-signature payloads.
3
+ *
4
+ * A participant peer-key-signs its outbound `RegisterV1` / `RenewV1` bodies (db-p2p supplies the
5
+ * libp2p peer key); a cohort member recomputes the identical byte image to verify that signature
6
+ * before admitting a register (anti-DoS) or honoring a crash-failover `reattach` attestation (so a
7
+ * stray/MITM'd ping cannot usurp a live primary — `docs/cohort-topic.md` §TTL and renewal).
8
+ *
9
+ * Signer and verifier must agree byte-for-byte. Exactly like {@link import("../sig/payloads.js")}
10
+ * for the threshold-signed notices, determinism comes from encoding an explicitly-ordered JSON
11
+ * array (array order is stable, unlike object key order) as UTF-8 — never the `signature` envelope.
12
+ * Optional fields are normalized to a fixed placeholder (`bootstrap`→`false`, `probe`→`false`,
13
+ * `appPayload`→`null`, `bootstrapEvidence`→`null` with an empty string treated as absent,
14
+ * `reattach`→`false`, `withdraw`→`false`) so an absent optional and a present-but-default optional can
15
+ * never disagree across the wire round-trip.
16
+ */
17
+
18
+ import type { CohortGossipV1, RegisterV1, RenewV1 } from "./types.js";
19
+
20
+ const utf8 = new TextEncoder();
21
+
22
+ /** A `RegisterV1` minus its `signature` envelope — the bytes the participant peer-key-signs. */
23
+ export type RegisterSignable = Omit<RegisterV1, "signature">;
24
+
25
+ /** A `RenewV1` minus its `signature` envelope — the bytes the participant peer-key-signs. */
26
+ export type RenewSignable = Omit<RenewV1, "signature">;
27
+
28
+ /** Normalize the optional bootstrap-evidence slot: absent and empty-string both map to `null`. */
29
+ function normalizeEvidence(bootstrapEvidence: string | undefined): string | null {
30
+ return bootstrapEvidence === undefined || bootstrapEvidence === "" ? null : bootstrapEvidence;
31
+ }
32
+
33
+ /** Canonical signed byte image of a `RegisterV1` body (every field except `signature`). */
34
+ export function registerSigningPayload(body: RegisterSignable): Uint8Array {
35
+ return utf8.encode(JSON.stringify([
36
+ "RegisterV1",
37
+ body.v,
38
+ body.topicId,
39
+ body.tier,
40
+ body.treeTier,
41
+ body.participantCoord,
42
+ body.ttl,
43
+ body.bootstrap ?? false,
44
+ body.appPayload ?? null,
45
+ normalizeEvidence(body.bootstrapEvidence),
46
+ body.timestamp,
47
+ body.correlationId,
48
+ // A read-only probe signs `probe: true`; a normal register signs `false`. Both sides recompute
49
+ // this image identically, so a probe and a register over the same fields produce distinct signatures.
50
+ body.probe ?? false,
51
+ ]));
52
+ }
53
+
54
+ /** Canonical signed byte image of a `RenewV1` body (every field except `signature`). */
55
+ export function renewSigningPayload(body: RenewSignable): Uint8Array {
56
+ return utf8.encode(JSON.stringify([
57
+ "RenewV1",
58
+ body.v,
59
+ body.topicId,
60
+ body.participantId,
61
+ body.correlationId,
62
+ body.timestamp,
63
+ body.reattach ?? false,
64
+ // A withdraw tombstone signs `withdraw: true`, a ping/reattach signs `false`. Strictly appended
65
+ // (array length 7→8) so the reattach-vs-ping distinction is preserved and a third party cannot
66
+ // evict someone else's registration. Signer (participant) + verifier (db-p2p) move together; no
67
+ // signatures are persisted, so there is no cross-version concern.
68
+ body.withdraw ?? false,
69
+ ]));
70
+ }
71
+
72
+ /** A `CohortGossipV1` minus its `signature` envelope — the bytes the gossiping member peer-key-signs. */
73
+ export type CohortGossipSignable = Omit<CohortGossipV1, "signature">;
74
+
75
+ /**
76
+ * Canonical signed byte image of a `CohortGossipV1` (every field except `signature`). Intra-cohort
77
+ * gossip is peer-key-signed by the originating member so a receiver can drop a frame whose `fromMember`
78
+ * signature does not verify or that comes from a non-cohort member (it can never spoof willingness/load
79
+ * or replicate forged records). Like the other payload helpers, determinism comes from an
80
+ * explicitly-ordered array — nested records/summaries/evictions are emitted as fixed ordered tuples so
81
+ * the signer and the receiver (which re-derives this image from the validated frame) agree byte-for-byte.
82
+ * Absent optionals (`records`/`evicted`) normalize to `[]`, and `appState` to `null`.
83
+ */
84
+ export function cohortGossipSigningPayload(g: CohortGossipSignable): Uint8Array {
85
+ return utf8.encode(JSON.stringify([
86
+ "CohortGossipV1",
87
+ g.v,
88
+ g.fromMember,
89
+ g.coord,
90
+ g.cohortEpoch,
91
+ g.willingnessBits,
92
+ g.loadBuckets,
93
+ g.windowSeconds,
94
+ g.topicSummaries.map((s) => [
95
+ s.topicId,
96
+ s.tier,
97
+ s.directParticipants,
98
+ s.arrivalsPerMin,
99
+ s.queriesPerMin,
100
+ s.promoted,
101
+ s.childCohortCount,
102
+ ]),
103
+ (g.records ?? []).map((r) => [
104
+ r.topicId,
105
+ r.participantId,
106
+ r.tier,
107
+ r.primary,
108
+ r.backups,
109
+ r.attachedAt,
110
+ r.lastPing,
111
+ r.ttl,
112
+ r.appState ?? null,
113
+ ]),
114
+ (g.evicted ?? []).map((e) => [e.topicId, e.participantId]),
115
+ g.timestamp,
116
+ ]));
117
+ }