@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,384 @@
1
+ /**
2
+ * Cohort-topic substrate — promotion / demotion lifecycle.
3
+ *
4
+ * Transcribed from `docs/cohort-topic.md` §Promotion and demotion lifecycle and folded back from the
5
+ * simulator-validated `packages/substrate-simulator/src/topic-tree.ts`. A forwarder cohort grows
6
+ * (promotes) and shrinks (demotes) for a topic `T` based on its per-topic direct-participant count
7
+ * and the per-tier load barometer; both transitions are **threshold-signed** (via the gossip ticket's
8
+ * {@link CohortSigner}) so participants can verify them.
9
+ *
10
+ * **Promote** when, for a quorum (the quorum is enforced by the threshold signature — `minSigs`
11
+ * signers must agree to the notice):
12
+ * - `directParticipants(T) ≥ cap_promote` (64), OR
13
+ * - `loadBucket[tier(T)] ≥ bucket_overload` (6) AND `directParticipants(T) ≥ cap_promote_fast` (32)
14
+ * — the hot-load fast path from the capacity-barometer ticket, OR
15
+ * - the growth **slope** predicts crossing `cap_promote` within `T_promote_lookahead` (30s) — fires
16
+ * early to avoid the gossip-lag overshoot (`§Promotion`: pre-promotion on slope).
17
+ *
18
+ * Once promoted, the state is **sticky** for ≥ `T_promote_sticky` (60s) before it can be reconsidered
19
+ * for demotion, so transient count drops don't flap a cohort back to accepting.
20
+ *
21
+ * **Demote** when, for a quorum: `directParticipants(T) ≤ cap_demote` (16) AND that has held for
22
+ * ≥ `T_demote` (5min) AND the cohort has no live child cohorts AND it has a parent (the root, tree
23
+ * tier 0, never demotes — it has nowhere to hand off). The `4×` gap between `cap_promote` and
24
+ * `cap_demote` plus `T_demote` is the hysteresis that prevents thrash.
25
+ *
26
+ * **Deviation from the doc's interface sketch (documented).** `docs/cohort-topic.md` sketches
27
+ * `onParticipantCountChange` as returning `void` and "may emit `PromotionNoticeV1`". Because
28
+ * threshold signing is asynchronous ({@link CohortSigner.thresholdSign} resolves a Promise), the
29
+ * methods here are `async` and **return** the signed notice (or `undefined`) instead of emitting it
30
+ * through a side channel — the caller broadcasts whatever it gets back. This keeps the module pure
31
+ * and unit-testable and avoids an awkward async callback. The op-tier `tier(T)` the barometer is
32
+ * indexed by is supplied through the injected {@link PromotionDeps.loadBucket} resolver, so this
33
+ * module never needs to know a topic's tier.
34
+ *
35
+ * **Remote apply path.** `promote()` / `demote()` set this cohort's per-topic state as a side effect of
36
+ * the **local** signing path — only the member that originates a notice adopts the new state that way. A
37
+ * member that *learns* of a promotion/demotion via gossip or a broadcast notice adopts it through
38
+ * {@link PromotionLifecycle.applyPromotionNotice} / {@link PromotionLifecycle.applyDemotionNotice},
39
+ * which set the same {@link PromotionState} **without re-signing** (the notice is already a verified
40
+ * quorum decision). **Precondition: the caller has already verified the notice's threshold signature**
41
+ * (db-p2p's `MembershipVerifier`); this module is crypto-free and trusts that gate. The apply path is
42
+ * idempotent and `effectiveAt`-ordered via the monotonic {@link PromotionState.lastEffectiveAt}
43
+ * high-water mark, so re-applying a notice (including this member's own echoed broadcast) or a replayed
44
+ * older notice is a no-op — a stale promotion can never un-demote a cohort that has since demoted.
45
+ */
46
+
47
+ import { b64urlToBytes, bytesToB64url } from "./wire/codec.js";
48
+ import type { CohortSigner } from "./sig/threshold.js";
49
+ import { demotionNoticeSigningPayload, promotionNoticeSigningPayload } from "./sig/payloads.js";
50
+ import type { DemotionNoticeV1, PromotionNoticeV1 } from "./wire/types.js";
51
+ import type { RegistrationStore } from "./registration/types.js";
52
+ import { bytesKey } from "./registration/bytes.js";
53
+
54
+ // --- defaults (docs/cohort-topic.md §Configuration; simulator-confirmed) ---
55
+
56
+ /** Direct-participant cap before promotion. */
57
+ export const DEFAULT_CAP_PROMOTE = 64;
58
+ /** Direct-participant cap when the load barometer is hot (fast path). */
59
+ export const DEFAULT_CAP_PROMOTE_FAST = 32;
60
+ /** Load-barometer bucket at/above which the fast promote path is armed. */
61
+ export const DEFAULT_BUCKET_OVERLOAD = 6;
62
+ /** Direct-participant floor for demotion (= `cap_promote / 4`). */
63
+ export const DEFAULT_CAP_DEMOTE = 16;
64
+ /** Hysteresis window the low-load condition must hold before demotion (ms). */
65
+ export const DEFAULT_T_DEMOTE_MS = 300_000;
66
+ /** Pre-promotion slope lookahead (ms). */
67
+ export const DEFAULT_T_PROMOTE_LOOKAHEAD_MS = 30_000;
68
+ /** Minimum time a cohort stays promoted before it can be reconsidered for demotion (ms). */
69
+ export const DEFAULT_T_PROMOTE_STICKY_MS = 60_000;
70
+ /**
71
+ * Slope window over which the growth rate is measured for pre-promotion. **Not pinned by
72
+ * `docs/cohort-topic.md`** — the simulator (`topic-tree.ts` `DEFAULT_LIFECYCLE_CONFIG`) uses 10s and
73
+ * this matches it.
74
+ */
75
+ export const DEFAULT_GROWTH_WINDOW_MS = 10_000;
76
+
77
+ export interface PromotionConfig {
78
+ capPromote?: number;
79
+ capPromoteFast?: number;
80
+ bucketOverload?: number;
81
+ capDemote?: number;
82
+ tDemoteMs?: number;
83
+ tPromoteLookaheadMs?: number;
84
+ tPromoteStickyMs?: number;
85
+ growthWindowMs?: number;
86
+ }
87
+
88
+ /** A sampled `(time, count)` growth point, for slope-based pre-promotion. */
89
+ interface GrowthSample {
90
+ readonly t: number;
91
+ readonly count: number;
92
+ }
93
+
94
+ /** Per-topic promotion bookkeeping held by this cohort. */
95
+ interface PromotionState {
96
+ promoted: boolean;
97
+ /** When the cohort last entered promoted mode (sticky-window anchor). */
98
+ promotedAt?: number;
99
+ /** When `directParticipants` last dropped to ≤ `cap_demote` (demotion hysteresis anchor). */
100
+ lowLoadSince?: number;
101
+ /** Recent growth samples within `growthWindowMs`, for slope extrapolation. */
102
+ samples: GrowthSample[];
103
+ /**
104
+ * `effectiveAt` of the most recent promotion/demotion this cohort has adopted — locally originated
105
+ * (`promote()` / `demote()`) or remotely applied (`applyPromotionNotice` / `applyDemotionNotice`).
106
+ * Monotonic and **never cleared** (a demotion clears `promoted`/`promotedAt` but keeps this), so it
107
+ * is the high-water mark the remote-apply path orders against: a notice whose `effectiveAt` is not
108
+ * strictly newer is a no-op — re-applied (incl. this member's own echoed broadcast) or stale-replayed.
109
+ */
110
+ lastEffectiveAt?: number;
111
+ }
112
+
113
+ export interface PromotionDeps {
114
+ /** Replicated registration store — supplies the per-topic `directParticipants` stock count. */
115
+ store: Pick<RegistrationStore, "directParticipants">;
116
+ /**
117
+ * Current load-barometer bucket (0..7) for the topic's op tier `tier(T)`. The caller resolves the
118
+ * topic→tier mapping and the barometer lookup, keeping this module tier-agnostic and FRET-free.
119
+ */
120
+ loadBucket: (topicId: Uint8Array) => number;
121
+ /** Live child-cohort count for the topic (0 if none); `> 0` blocks demotion. */
122
+ childCohortCount: (topicId: Uint8Array) => number;
123
+ /** Tree tier `d` this cohort serves the topic at — `fromTier` on a notice; `toTier = d + 1`. */
124
+ treeTier: (topicId: Uint8Array) => number;
125
+ /** The tier-`(d − 1)` parent cohort coord, target of a {@link DemotionNoticeV1}. */
126
+ parentCoord: (topicId: Uint8Array) => Uint8Array;
127
+ /** Cohort epoch (raw bytes) the notices are signed under. */
128
+ cohortEpoch: () => Uint8Array;
129
+ /** Threshold signer (the gossip ticket's `k − x` cohort signer). */
130
+ signer: CohortSigner;
131
+ config?: PromotionConfig;
132
+ }
133
+
134
+ /**
135
+ * Promotion / demotion lifecycle for one cohort across all the topics it serves. Keyed by `topicId`;
136
+ * each topic carries its own promoted/low-load/sticky state.
137
+ */
138
+ export interface PromotionLifecycle {
139
+ /**
140
+ * React to a change in `topicId`'s direct-participant count (call after every arrival/eviction).
141
+ * Refreshes the growth + hysteresis clocks and, if a promote trigger fires and the cohort is not
142
+ * already promoted, threshold-signs and returns a {@link PromotionNoticeV1}. Returns `undefined`
143
+ * when no promotion fires.
144
+ */
145
+ onParticipantCountChange(topicId: Uint8Array, now: number): Promise<PromotionNoticeV1 | undefined>;
146
+ /**
147
+ * Time-driven demotion check (call on the gossip tick). Returns a threshold-signed
148
+ * {@link DemotionNoticeV1} for the parent when every demotion condition holds, else `undefined`.
149
+ */
150
+ maybeDemote(topicId: Uint8Array, now: number): Promise<DemotionNoticeV1 | undefined>;
151
+ /** Whether `topicId` is currently in promoted mode (new registrations get `Promoted(d+1)`). */
152
+ isPromoted(topicId: Uint8Array): boolean;
153
+ /**
154
+ * Adopt a promotion this member did **not** originate (learned via a verified broadcast notice).
155
+ * Sets `promoted = true` for `n.topicId` without re-signing. **Precondition: the caller has verified
156
+ * `n`'s threshold signature** — this module performs no crypto. Idempotent and `effectiveAt`-ordered
157
+ * (see {@link PromotionState.lastEffectiveAt}): a notice not strictly newer than the last adopted
158
+ * transition is a no-op, so this member's own echoed broadcast and stale replays are absorbed.
159
+ */
160
+ applyPromotionNotice(n: PromotionNoticeV1, now: number): void;
161
+ /**
162
+ * Adopt a demotion this member did **not** originate (learned via a verified broadcast notice).
163
+ * Clears `promoted` state for `n.topicId` without re-signing. Same precondition, idempotency, and
164
+ * `effectiveAt` ordering as {@link applyPromotionNotice} — a stale promotion can never un-demote.
165
+ */
166
+ applyDemotionNotice(n: DemotionNoticeV1, now: number): void;
167
+ }
168
+
169
+ class CohortPromotionLifecycle implements PromotionLifecycle {
170
+ private readonly states = new Map<string, PromotionState>();
171
+ private readonly capPromote: number;
172
+ private readonly capPromoteFast: number;
173
+ private readonly bucketOverload: number;
174
+ private readonly capDemote: number;
175
+ private readonly tDemoteMs: number;
176
+ private readonly tPromoteLookaheadMs: number;
177
+ private readonly tPromoteStickyMs: number;
178
+ private readonly growthWindowMs: number;
179
+
180
+ constructor(private readonly deps: PromotionDeps) {
181
+ const cfg = deps.config ?? {};
182
+ this.capPromote = cfg.capPromote ?? DEFAULT_CAP_PROMOTE;
183
+ this.capPromoteFast = cfg.capPromoteFast ?? DEFAULT_CAP_PROMOTE_FAST;
184
+ this.bucketOverload = cfg.bucketOverload ?? DEFAULT_BUCKET_OVERLOAD;
185
+ this.capDemote = cfg.capDemote ?? DEFAULT_CAP_DEMOTE;
186
+ this.tDemoteMs = cfg.tDemoteMs ?? DEFAULT_T_DEMOTE_MS;
187
+ this.tPromoteLookaheadMs = cfg.tPromoteLookaheadMs ?? DEFAULT_T_PROMOTE_LOOKAHEAD_MS;
188
+ this.tPromoteStickyMs = cfg.tPromoteStickyMs ?? DEFAULT_T_PROMOTE_STICKY_MS;
189
+ this.growthWindowMs = cfg.growthWindowMs ?? DEFAULT_GROWTH_WINDOW_MS;
190
+ }
191
+
192
+ async onParticipantCountChange(topicId: Uint8Array, now: number): Promise<PromotionNoticeV1 | undefined> {
193
+ const state = this.stateFor(topicId);
194
+ const count = this.deps.store.directParticipants(topicId);
195
+ this.pushGrowthSample(state, count, now);
196
+ this.refreshLowLoadClock(state, count, now);
197
+ if (state.promoted) {
198
+ return undefined; // already promoted — sticky until a demotion clears it
199
+ }
200
+ if (!this.promotionTriggered(topicId, state, count, now)) {
201
+ return undefined;
202
+ }
203
+ return this.promote(topicId, state, now);
204
+ }
205
+
206
+ async maybeDemote(topicId: Uint8Array, now: number): Promise<DemotionNoticeV1 | undefined> {
207
+ const state = this.states.get(bytesKey(topicId));
208
+ if (state === undefined) {
209
+ return undefined;
210
+ }
211
+ if (!this.demotionTriggered(topicId, state, now)) {
212
+ return undefined;
213
+ }
214
+ return this.demote(topicId, state, now);
215
+ }
216
+
217
+ isPromoted(topicId: Uint8Array): boolean {
218
+ return this.states.get(bytesKey(topicId))?.promoted ?? false;
219
+ }
220
+
221
+ // --- remote apply (verified notices this member did not originate) ---
222
+
223
+ applyPromotionNotice(n: PromotionNoticeV1, now: number): void {
224
+ const state = this.stateFor(b64urlToBytes(n.topicId));
225
+ if (!this.isNewerTransition(state, n.effectiveAt)) {
226
+ return; // already adopted, our own echoed broadcast, or a stale replay
227
+ }
228
+ state.promoted = true;
229
+ state.promotedAt = now;
230
+ state.lastEffectiveAt = n.effectiveAt;
231
+ }
232
+
233
+ applyDemotionNotice(n: DemotionNoticeV1, _now: number): void {
234
+ const state = this.stateFor(b64urlToBytes(n.topicId));
235
+ if (!this.isNewerTransition(state, n.effectiveAt)) {
236
+ return;
237
+ }
238
+ // Mirror the local demote() release so a later re-growth re-evaluates cleanly.
239
+ state.promoted = false;
240
+ state.promotedAt = undefined;
241
+ state.lowLoadSince = undefined;
242
+ state.lastEffectiveAt = n.effectiveAt;
243
+ }
244
+
245
+ /** A notice is adopted only if strictly newer than the last transition we adopted (the high-water mark). */
246
+ private isNewerTransition(state: PromotionState, effectiveAt: number): boolean {
247
+ return state.lastEffectiveAt === undefined || effectiveAt > state.lastEffectiveAt;
248
+ }
249
+
250
+ // --- promotion ---
251
+
252
+ private promotionTriggered(topicId: Uint8Array, state: PromotionState, count: number, now: number): boolean {
253
+ if (count >= this.capPromote) {
254
+ return true;
255
+ }
256
+ if (this.deps.loadBucket(topicId) >= this.bucketOverload && count >= this.capPromoteFast) {
257
+ return true;
258
+ }
259
+ return this.slopePredictsCrossing(state, count, now);
260
+ }
261
+
262
+ /** Linear extrapolation over the growth window: will `directParticipants` cross `cap_promote` within lookahead? */
263
+ private slopePredictsCrossing(state: PromotionState, count: number, now: number): boolean {
264
+ const samples = state.samples;
265
+ if (samples.length < 2) {
266
+ return false;
267
+ }
268
+ const first = samples[0]!;
269
+ const last = samples[samples.length - 1]!;
270
+ const span = last.t - first.t;
271
+ if (span <= 0) {
272
+ return false;
273
+ }
274
+ const slope = (last.count - first.count) / span; // participants per ms
275
+ if (slope <= 0) {
276
+ return false;
277
+ }
278
+ const predicted = count + slope * this.tPromoteLookaheadMs;
279
+ return predicted >= this.capPromote;
280
+ }
281
+
282
+ private async promote(topicId: Uint8Array, state: PromotionState, now: number): Promise<PromotionNoticeV1> {
283
+ const fromTier = this.deps.treeTier(topicId);
284
+ const topicB64 = bytesToB64url(topicId);
285
+ const epochB64 = bytesToB64url(this.deps.cohortEpoch());
286
+ const signable = { topicId: topicB64, fromTier, toTier: fromTier + 1, effectiveAt: now, cohortEpoch: epochB64 };
287
+ const { thresholdSig, signers } = await this.deps.signer.thresholdSign(promotionNoticeSigningPayload(signable));
288
+ state.promoted = true;
289
+ state.promotedAt = now;
290
+ state.lastEffectiveAt = now; // local effectiveAt — so our own echoed broadcast applies as a no-op
291
+ return {
292
+ v: 1,
293
+ topicId: topicB64,
294
+ fromTier,
295
+ toTier: fromTier + 1,
296
+ effectiveAt: now,
297
+ thresholdSig: bytesToB64url(thresholdSig),
298
+ signers: signers.map(bytesToB64url),
299
+ cohortEpoch: epochB64,
300
+ };
301
+ }
302
+
303
+ // --- demotion ---
304
+
305
+ private demotionTriggered(topicId: Uint8Array, state: PromotionState, now: number): boolean {
306
+ // The root (tree tier 0) has no parent to hand off to — it never demotes.
307
+ if (this.deps.treeTier(topicId) <= 0) {
308
+ return false;
309
+ }
310
+ // Sticky: a freshly-promoted cohort holds promoted mode through transient drops.
311
+ if (state.promoted && state.promotedAt !== undefined && now - state.promotedAt < this.tPromoteStickyMs) {
312
+ return false;
313
+ }
314
+ // Never collapse a cohort that still has live children beneath it.
315
+ if (this.deps.childCohortCount(topicId) > 0) {
316
+ return false;
317
+ }
318
+ if (this.deps.store.directParticipants(topicId) > this.capDemote) {
319
+ return false;
320
+ }
321
+ if (state.lowLoadSince === undefined) {
322
+ return false;
323
+ }
324
+ return now - state.lowLoadSince >= this.tDemoteMs;
325
+ }
326
+
327
+ private async demote(topicId: Uint8Array, state: PromotionState, now: number): Promise<DemotionNoticeV1> {
328
+ const tier = this.deps.treeTier(topicId);
329
+ const topicB64 = bytesToB64url(topicId);
330
+ const epochB64 = bytesToB64url(this.deps.cohortEpoch());
331
+ const parentB64 = bytesToB64url(this.deps.parentCoord(topicId));
332
+ const signable = { topicId: topicB64, tier, parentCohortCoord: parentB64, effectiveAt: now, cohortEpoch: epochB64 };
333
+ const { thresholdSig, signers } = await this.deps.signer.thresholdSign(demotionNoticeSigningPayload(signable));
334
+ // Release forwarder state: a demoted cohort leaves promoted mode and resets its clocks so a
335
+ // later re-growth re-evaluates cleanly.
336
+ state.promoted = false;
337
+ state.promotedAt = undefined;
338
+ state.lowLoadSince = undefined;
339
+ state.lastEffectiveAt = now; // local effectiveAt — so our own echoed broadcast applies as a no-op
340
+ return {
341
+ v: 1,
342
+ topicId: topicB64,
343
+ tier,
344
+ parentCohortCoord: parentB64,
345
+ effectiveAt: now,
346
+ thresholdSig: bytesToB64url(thresholdSig),
347
+ signers: signers.map(bytesToB64url),
348
+ cohortEpoch: epochB64,
349
+ };
350
+ }
351
+
352
+ // --- bookkeeping ---
353
+
354
+ private stateFor(topicId: Uint8Array): PromotionState {
355
+ const key = bytesKey(topicId);
356
+ let state = this.states.get(key);
357
+ if (state === undefined) {
358
+ state = { promoted: false, samples: [] };
359
+ this.states.set(key, state);
360
+ }
361
+ return state;
362
+ }
363
+
364
+ private pushGrowthSample(state: PromotionState, count: number, now: number): void {
365
+ state.samples.push({ t: now, count });
366
+ const cutoff = now - this.growthWindowMs;
367
+ while (state.samples.length > 1 && state.samples[0]!.t < cutoff) {
368
+ state.samples.shift();
369
+ }
370
+ }
371
+
372
+ private refreshLowLoadClock(state: PromotionState, count: number, now: number): void {
373
+ if (count <= this.capDemote) {
374
+ state.lowLoadSince ??= now;
375
+ } else {
376
+ state.lowLoadSince = undefined;
377
+ }
378
+ }
379
+ }
380
+
381
+ /** Build a {@link PromotionLifecycle} over the injected store, barometer resolver, and signer. */
382
+ export function createPromotionLifecycle(deps: PromotionDeps): PromotionLifecycle {
383
+ return new CohortPromotionLifecycle(deps);
384
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Cohort-topic substrate — byte-array helpers shared by the registration store, sharding, and
3
+ * handoff. Peer ids and topic ids are raw {@link Uint8Array}; these give them stable string map
4
+ * keys, value equality, and the ascending order the sharding hash relies on.
5
+ */
6
+
7
+ import { compare as compareU8 } from "uint8arrays/compare";
8
+ import { equals as equalsU8 } from "uint8arrays/equals";
9
+ import { bytesToB64url } from "../wire/codec.js";
10
+
11
+ /** Stable, collision-free string map key for a byte array (base64url, the package's canonical form). */
12
+ export function bytesKey(b: Uint8Array): string {
13
+ return bytesToB64url(b);
14
+ }
15
+
16
+ /** Composite key for a registration, `topicKey|participantKey`. */
17
+ export function recordKey(topicId: Uint8Array, participantId: Uint8Array): string {
18
+ return `${bytesKey(topicId)}|${bytesKey(participantId)}`;
19
+ }
20
+
21
+ /** Value equality over byte arrays. */
22
+ export function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
23
+ return equalsU8(a, b);
24
+ }
25
+
26
+ /** Lexicographic byte order — the `sort(... by PeerId ascending)` of §Primary and backup sharding. */
27
+ export function compareBytes(a: Uint8Array, b: Uint8Array): number {
28
+ return compareU8(a, b);
29
+ }
30
+
31
+ /** Concatenate two byte arrays into a fresh buffer (the `participantId ‖ cohortEpoch` hash input). */
32
+ export function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
33
+ const out = new Uint8Array(a.length + b.length);
34
+ out.set(a, 0);
35
+ out.set(b, a.length);
36
+ return out;
37
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Cohort-topic substrate — membership-rotation primary handoff.
3
+ *
4
+ * Per `docs/cohort-topic.md` §Membership rotation and primary handoff. When membership changes the
5
+ * deterministic slot may move a registration's primary. Each member runs this state machine:
6
+ *
7
+ * 1. New membership stabilizes; all members compute the new `cohortEpoch`.
8
+ * 2. Members exchange a "primary inventory" listing the records they hold as primary ({@link start}).
9
+ * 3. For each record, the new computed primary either already holds it (no-op) or **pulls** it from
10
+ * the previous holder ({@link onInventory} → `transport.pull`).
11
+ * 4. **Resolved (GROUNDING):** the previous primary keeps serving (**dual-serve**) until the new
12
+ * primary acknowledges receipt ({@link onAck}) — closing the delivery gap.
13
+ * 5. Participants discover the new primary on their next ping/delivery (renewal module).
14
+ *
15
+ * The gossip *transport* for the inventory comes from a later ticket; it is injected here so the
16
+ * machine is unit-testable with a mock. The machine is purely cohort-local — FRET is unaware of it.
17
+ */
18
+
19
+ import { bytesEqual, recordKey } from "./bytes.js";
20
+ import type { SlotAssigner } from "./sharding.js";
21
+ import type { RegistrationRecord, RegistrationStore } from "./types.js";
22
+
23
+ /** Identifies one registration in a {@link PrimaryInventory} or pull/ack exchange. */
24
+ export interface RecordRef {
25
+ topicId: Uint8Array;
26
+ participantId: Uint8Array;
27
+ }
28
+
29
+ /** A member's list of registrations it currently holds as primary, under the new `cohortEpoch`. */
30
+ export interface PrimaryInventory {
31
+ /** The advertising member. */
32
+ from: Uint8Array;
33
+ /** The new cohort epoch this inventory was computed under. */
34
+ epoch: Uint8Array;
35
+ entries: readonly RecordRef[];
36
+ }
37
+
38
+ /** Transport the handoff drives; supplied by the cohort-gossip ticket, mocked in tests. */
39
+ export interface HandoffTransport {
40
+ /** Broadcast this member's primary inventory to the cohort. */
41
+ sendInventory(inv: PrimaryInventory): void;
42
+ /** Pull a record this member is now primary for from its previous holder. */
43
+ pull(from: Uint8Array, ref: RecordRef): Promise<RegistrationRecord | undefined>;
44
+ /** Acknowledge receipt to the previous holder, ending its dual-serve for `ref`. */
45
+ ack(to: Uint8Array, ref: RecordRef): void;
46
+ }
47
+
48
+ export interface HandoffDeps {
49
+ store: RegistrationStore;
50
+ /** This member's own peer id. */
51
+ self: Uint8Array;
52
+ slots: SlotAssigner;
53
+ /** The new (post-rotation) cohort snapshot. */
54
+ cohort: () => { members: readonly Uint8Array[]; cohortEpoch: Uint8Array };
55
+ transport: HandoffTransport;
56
+ }
57
+
58
+ /** Per-member primary-handoff driver. One instance handles one rotation. */
59
+ export interface MembershipHandoff {
60
+ /** Step 1+2: recompute under the new epoch, publish this member's inventory, mark hand-offs. */
61
+ start(): void;
62
+ /** Step 3: react to a peer's inventory — pull every record now assigned to this member, then ack. */
63
+ onInventory(inv: PrimaryInventory): Promise<void>;
64
+ /** Serve a pull from the new primary (this member is the previous holder); stays dual-serving. */
65
+ onPull(ref: RecordRef): RegistrationRecord | undefined;
66
+ /** New primary acked receipt: stop dual-serving `ref`. */
67
+ onAck(ref: RecordRef): void;
68
+ /** Whether this member should still answer renews for `(topicId, participantId)` right now. */
69
+ isServing(topicId: Uint8Array, participantId: Uint8Array): boolean;
70
+ }
71
+
72
+ class StoreMembershipHandoff implements MembershipHandoff {
73
+ /** Records this member held as primary but no longer is — kept serving until acked. */
74
+ private readonly dualServing = new Set<string>();
75
+ /** In-flight pulls, so a duplicate inventory does not pull twice. */
76
+ private readonly pulling = new Set<string>();
77
+
78
+ constructor(private readonly deps: HandoffDeps) {}
79
+
80
+ start(): void {
81
+ const { members, cohortEpoch } = this.deps.cohort();
82
+ const entries: RecordRef[] = [];
83
+ for (const rec of this.deps.store.listAll()) {
84
+ if (!bytesEqual(rec.primary, this.deps.self)) {
85
+ continue; // only records this member currently holds as primary
86
+ }
87
+ entries.push({ topicId: rec.topicId, participantId: rec.participantId });
88
+ const { primary } = this.deps.slots.assignSlots(rec.participantId, cohortEpoch, members);
89
+ if (!bytesEqual(primary, this.deps.self)) {
90
+ // Primary moved away from this member: keep serving until the new primary acks.
91
+ this.dualServing.add(recordKey(rec.topicId, rec.participantId));
92
+ }
93
+ }
94
+ this.deps.transport.sendInventory({ from: this.deps.self, epoch: cohortEpoch, entries });
95
+ }
96
+
97
+ async onInventory(inv: PrimaryInventory): Promise<void> {
98
+ if (bytesEqual(inv.from, this.deps.self)) {
99
+ return; // ignore our own inventory
100
+ }
101
+ const { members, cohortEpoch } = this.deps.cohort();
102
+ for (const ref of inv.entries) {
103
+ await this.maybePull(inv.from, ref, members, cohortEpoch);
104
+ }
105
+ }
106
+
107
+ private async maybePull(from: Uint8Array, ref: RecordRef, members: readonly Uint8Array[], cohortEpoch: Uint8Array): Promise<void> {
108
+ const { primary, backups } = this.deps.slots.assignSlots(ref.participantId, cohortEpoch, members);
109
+ if (!bytesEqual(primary, this.deps.self)) {
110
+ return; // not assigned to this member now
111
+ }
112
+ if (this.deps.store.getByParticipant(ref.topicId, ref.participantId) !== undefined) {
113
+ return; // already hold it (no-op handoff)
114
+ }
115
+ const key = recordKey(ref.topicId, ref.participantId);
116
+ if (this.pulling.has(key)) {
117
+ return;
118
+ }
119
+ this.pulling.add(key);
120
+ try {
121
+ const rec = await this.deps.transport.pull(from, ref);
122
+ if (rec !== undefined) {
123
+ // Re-stamp the assignment so a later rotation's `start()` recognises this member as primary.
124
+ this.deps.store.put({ ...rec, primary, backups });
125
+ this.deps.transport.ack(from, ref); // ends dual-serve at the previous holder
126
+ }
127
+ } finally {
128
+ this.pulling.delete(key);
129
+ }
130
+ }
131
+
132
+ onPull(ref: RecordRef): RegistrationRecord | undefined {
133
+ // Previous holder serves the record; it remains in dualServing until the ack arrives.
134
+ return this.deps.store.getByParticipant(ref.topicId, ref.participantId);
135
+ }
136
+
137
+ onAck(ref: RecordRef): void {
138
+ this.dualServing.delete(recordKey(ref.topicId, ref.participantId));
139
+ }
140
+
141
+ isServing(topicId: Uint8Array, participantId: Uint8Array): boolean {
142
+ const { members, cohortEpoch } = this.deps.cohort();
143
+ const { primary } = this.deps.slots.assignSlots(participantId, cohortEpoch, members);
144
+ if (bytesEqual(primary, this.deps.self)) {
145
+ return true;
146
+ }
147
+ return this.dualServing.has(recordKey(topicId, participantId));
148
+ }
149
+ }
150
+
151
+ /** Build a {@link MembershipHandoff} for one rotation. */
152
+ export function createMembershipHandoff(deps: HandoffDeps): MembershipHandoff {
153
+ return new StoreMembershipHandoff(deps);
154
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./bytes.js";
3
+ export * from "./store.js";
4
+ export * from "./sharding.js";
5
+ export * from "./renewal.js";
6
+ export * from "./handoff.js";