@optimystic/db-p2p 0.13.5 → 0.14.1

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 (342) hide show
  1. package/README.md +6 -6
  2. package/dist/src/cluster/block-transfer-service.d.ts +67 -8
  3. package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
  4. package/dist/src/cluster/block-transfer-service.js +101 -62
  5. package/dist/src/cluster/block-transfer-service.js.map +1 -1
  6. package/dist/src/cluster/client.d.ts +18 -1
  7. package/dist/src/cluster/client.d.ts.map +1 -1
  8. package/dist/src/cluster/client.js +55 -39
  9. package/dist/src/cluster/client.js.map +1 -1
  10. package/dist/src/cluster/cluster-error.d.ts +38 -0
  11. package/dist/src/cluster/cluster-error.d.ts.map +1 -0
  12. package/dist/src/cluster/cluster-error.js +51 -0
  13. package/dist/src/cluster/cluster-error.js.map +1 -0
  14. package/dist/src/cluster/cluster-repo.d.ts +135 -3
  15. package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
  16. package/dist/src/cluster/cluster-repo.js +333 -58
  17. package/dist/src/cluster/cluster-repo.js.map +1 -1
  18. package/dist/src/cluster/commit-cert.d.ts +63 -0
  19. package/dist/src/cluster/commit-cert.d.ts.map +1 -0
  20. package/dist/src/cluster/commit-cert.js +108 -0
  21. package/dist/src/cluster/commit-cert.js.map +1 -0
  22. package/dist/src/cluster/service.d.ts +46 -11
  23. package/dist/src/cluster/service.d.ts.map +1 -1
  24. package/dist/src/cluster/service.js +97 -33
  25. package/dist/src/cluster/service.js.map +1 -1
  26. package/dist/src/cluster/spread-on-churn.d.ts +13 -0
  27. package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
  28. package/dist/src/cluster/spread-on-churn.js +34 -4
  29. package/dist/src/cluster/spread-on-churn.js.map +1 -1
  30. package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts +59 -0
  31. package/dist/src/cohort-topic/bootstrap-evidence-builder.d.ts.map +1 -0
  32. package/dist/src/cohort-topic/bootstrap-evidence-builder.js +79 -0
  33. package/dist/src/cohort-topic/bootstrap-evidence-builder.js.map +1 -0
  34. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts +81 -0
  35. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.d.ts.map +1 -0
  36. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js +93 -0
  37. package/dist/src/cohort-topic/bootstrap-evidence-verifiers.js.map +1 -0
  38. package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts +108 -0
  39. package/dist/src/cohort-topic/bootstrap-parent-reference.d.ts.map +1 -0
  40. package/dist/src/cohort-topic/bootstrap-parent-reference.js +108 -0
  41. package/dist/src/cohort-topic/bootstrap-parent-reference.js.map +1 -0
  42. package/dist/src/cohort-topic/change-bridge.d.ts +65 -0
  43. package/dist/src/cohort-topic/change-bridge.d.ts.map +1 -0
  44. package/dist/src/cohort-topic/change-bridge.js +76 -0
  45. package/dist/src/cohort-topic/change-bridge.js.map +1 -0
  46. package/dist/src/cohort-topic/cohort-gossip-driver.d.ts +74 -0
  47. package/dist/src/cohort-topic/cohort-gossip-driver.d.ts.map +1 -0
  48. package/dist/src/cohort-topic/cohort-gossip-driver.js +87 -0
  49. package/dist/src/cohort-topic/cohort-gossip-driver.js.map +1 -0
  50. package/dist/src/cohort-topic/cohort-gossip-transport.d.ts +42 -0
  51. package/dist/src/cohort-topic/cohort-gossip-transport.d.ts.map +1 -0
  52. package/dist/src/cohort-topic/cohort-gossip-transport.js +57 -0
  53. package/dist/src/cohort-topic/cohort-gossip-transport.js.map +1 -0
  54. package/dist/src/cohort-topic/fret-trust-anchor.d.ts +100 -0
  55. package/dist/src/cohort-topic/fret-trust-anchor.d.ts.map +1 -0
  56. package/dist/src/cohort-topic/fret-trust-anchor.js +115 -0
  57. package/dist/src/cohort-topic/fret-trust-anchor.js.map +1 -0
  58. package/dist/src/cohort-topic/host.d.ts +573 -0
  59. package/dist/src/cohort-topic/host.d.ts.map +1 -0
  60. package/dist/src/cohort-topic/host.js +1460 -0
  61. package/dist/src/cohort-topic/host.js.map +1 -0
  62. package/dist/src/cohort-topic/index.d.ts +14 -0
  63. package/dist/src/cohort-topic/index.d.ts.map +1 -0
  64. package/dist/src/cohort-topic/index.js +14 -0
  65. package/dist/src/cohort-topic/index.js.map +1 -0
  66. package/dist/src/cohort-topic/membership-publish-sink.d.ts +14 -0
  67. package/dist/src/cohort-topic/membership-publish-sink.d.ts.map +1 -0
  68. package/dist/src/cohort-topic/membership-publish-sink.js +17 -0
  69. package/dist/src/cohort-topic/membership-publish-sink.js.map +1 -0
  70. package/dist/src/cohort-topic/membership-source.d.ts +36 -0
  71. package/dist/src/cohort-topic/membership-source.d.ts.map +1 -0
  72. package/dist/src/cohort-topic/membership-source.js +58 -0
  73. package/dist/src/cohort-topic/membership-source.js.map +1 -0
  74. package/dist/src/cohort-topic/peer-codec.d.ts +18 -0
  75. package/dist/src/cohort-topic/peer-codec.d.ts.map +1 -0
  76. package/dist/src/cohort-topic/peer-codec.js +26 -0
  77. package/dist/src/cohort-topic/peer-codec.js.map +1 -0
  78. package/dist/src/cohort-topic/peer-sig.d.ts +52 -0
  79. package/dist/src/cohort-topic/peer-sig.d.ts.map +1 -0
  80. package/dist/src/cohort-topic/peer-sig.js +83 -0
  81. package/dist/src/cohort-topic/peer-sig.js.map +1 -0
  82. package/dist/src/cohort-topic/protocols.d.ts +49 -0
  83. package/dist/src/cohort-topic/protocols.d.ts.map +1 -0
  84. package/dist/src/cohort-topic/protocols.js +58 -0
  85. package/dist/src/cohort-topic/protocols.js.map +1 -0
  86. package/dist/src/cohort-topic/reactivity-membership-gate.d.ts +49 -0
  87. package/dist/src/cohort-topic/reactivity-membership-gate.d.ts.map +1 -0
  88. package/dist/src/cohort-topic/reactivity-membership-gate.js +42 -0
  89. package/dist/src/cohort-topic/reactivity-membership-gate.js.map +1 -0
  90. package/dist/src/cohort-topic/size-estimator.d.ts +16 -0
  91. package/dist/src/cohort-topic/size-estimator.d.ts.map +1 -0
  92. package/dist/src/cohort-topic/size-estimator.js +16 -0
  93. package/dist/src/cohort-topic/size-estimator.js.map +1 -0
  94. package/dist/src/cohort-topic/stream-util.d.ts +19 -0
  95. package/dist/src/cohort-topic/stream-util.d.ts.map +1 -0
  96. package/dist/src/cohort-topic/stream-util.js +76 -0
  97. package/dist/src/cohort-topic/stream-util.js.map +1 -0
  98. package/dist/src/cohort-topic/threshold-crypto.d.ts +94 -0
  99. package/dist/src/cohort-topic/threshold-crypto.d.ts.map +1 -0
  100. package/dist/src/cohort-topic/threshold-crypto.js +178 -0
  101. package/dist/src/cohort-topic/threshold-crypto.js.map +1 -0
  102. package/dist/src/cohort-topic/topic-router.d.ts +41 -0
  103. package/dist/src/cohort-topic/topic-router.d.ts.map +1 -0
  104. package/dist/src/cohort-topic/topic-router.js +63 -0
  105. package/dist/src/cohort-topic/topic-router.js.map +1 -0
  106. package/dist/src/dispute/cascade.d.ts +179 -0
  107. package/dist/src/dispute/cascade.d.ts.map +1 -0
  108. package/dist/src/dispute/cascade.js +315 -0
  109. package/dist/src/dispute/cascade.js.map +1 -0
  110. package/dist/src/dispute/client.d.ts +2 -1
  111. package/dist/src/dispute/client.d.ts.map +1 -1
  112. package/dist/src/dispute/client.js +12 -3
  113. package/dist/src/dispute/client.js.map +1 -1
  114. package/dist/src/dispute/dispute-service.d.ts +41 -1
  115. package/dist/src/dispute/dispute-service.d.ts.map +1 -1
  116. package/dist/src/dispute/dispute-service.js +147 -21
  117. package/dist/src/dispute/dispute-service.js.map +1 -1
  118. package/dist/src/dispute/index.d.ts +2 -0
  119. package/dist/src/dispute/index.d.ts.map +1 -1
  120. package/dist/src/dispute/index.js +2 -0
  121. package/dist/src/dispute/index.js.map +1 -1
  122. package/dist/src/dispute/invalidation.d.ts +266 -0
  123. package/dist/src/dispute/invalidation.d.ts.map +1 -0
  124. package/dist/src/dispute/invalidation.js +405 -0
  125. package/dist/src/dispute/invalidation.js.map +1 -0
  126. package/dist/src/dispute/types.d.ts +14 -0
  127. package/dist/src/dispute/types.d.ts.map +1 -1
  128. package/dist/src/dispute/types.js.map +1 -1
  129. package/dist/src/index.d.ts +4 -0
  130. package/dist/src/index.d.ts.map +1 -1
  131. package/dist/src/index.js +4 -0
  132. package/dist/src/index.js.map +1 -1
  133. package/dist/src/libp2p-key-network.d.ts +7 -0
  134. package/dist/src/libp2p-key-network.d.ts.map +1 -1
  135. package/dist/src/libp2p-key-network.js +49 -5
  136. package/dist/src/libp2p-key-network.js.map +1 -1
  137. package/dist/src/libp2p-node-base.d.ts +45 -3
  138. package/dist/src/libp2p-node-base.d.ts.map +1 -1
  139. package/dist/src/libp2p-node-base.js +522 -5
  140. package/dist/src/libp2p-node-base.js.map +1 -1
  141. package/dist/src/matchmaking/aggregate-counts.d.ts +67 -0
  142. package/dist/src/matchmaking/aggregate-counts.d.ts.map +1 -0
  143. package/dist/src/matchmaking/aggregate-counts.js +60 -0
  144. package/dist/src/matchmaking/aggregate-counts.js.map +1 -0
  145. package/dist/src/matchmaking/index.d.ts +20 -0
  146. package/dist/src/matchmaking/index.d.ts.map +1 -0
  147. package/dist/src/matchmaking/index.js +20 -0
  148. package/dist/src/matchmaking/index.js.map +1 -0
  149. package/dist/src/matchmaking/module.d.ts +181 -0
  150. package/dist/src/matchmaking/module.d.ts.map +1 -0
  151. package/dist/src/matchmaking/module.js +221 -0
  152. package/dist/src/matchmaking/module.js.map +1 -0
  153. package/dist/src/matchmaking/protocols.d.ts +37 -0
  154. package/dist/src/matchmaking/protocols.d.ts.map +1 -0
  155. package/dist/src/matchmaking/protocols.js +42 -0
  156. package/dist/src/matchmaking/protocols.js.map +1 -0
  157. package/dist/src/matchmaking/provider-manager.d.ts +52 -0
  158. package/dist/src/matchmaking/provider-manager.d.ts.map +1 -0
  159. package/dist/src/matchmaking/provider-manager.js +74 -0
  160. package/dist/src/matchmaking/provider-manager.js.map +1 -0
  161. package/dist/src/matchmaking/query-handler.d.ts +34 -0
  162. package/dist/src/matchmaking/query-handler.d.ts.map +1 -0
  163. package/dist/src/matchmaking/query-handler.js +62 -0
  164. package/dist/src/matchmaking/query-handler.js.map +1 -0
  165. package/dist/src/matchmaking/query-transport.d.ts +180 -0
  166. package/dist/src/matchmaking/query-transport.d.ts.map +1 -0
  167. package/dist/src/matchmaking/query-transport.js +315 -0
  168. package/dist/src/matchmaking/query-transport.js.map +1 -0
  169. package/dist/src/matchmaking/seeker-manager.d.ts +39 -0
  170. package/dist/src/matchmaking/seeker-manager.d.ts.map +1 -0
  171. package/dist/src/matchmaking/seeker-manager.js +49 -0
  172. package/dist/src/matchmaking/seeker-manager.js.map +1 -0
  173. package/dist/src/matchmaking/seeker-walk-client.d.ts +127 -0
  174. package/dist/src/matchmaking/seeker-walk-client.d.ts.map +1 -0
  175. package/dist/src/matchmaking/seeker-walk-client.js +189 -0
  176. package/dist/src/matchmaking/seeker-walk-client.js.map +1 -0
  177. package/dist/src/matchmaking/traffic-validation.d.ts +128 -0
  178. package/dist/src/matchmaking/traffic-validation.d.ts.map +1 -0
  179. package/dist/src/matchmaking/traffic-validation.js +105 -0
  180. package/dist/src/matchmaking/traffic-validation.js.map +1 -0
  181. package/dist/src/network/network-manager-service.d.ts +4 -2
  182. package/dist/src/network/network-manager-service.d.ts.map +1 -1
  183. package/dist/src/network/network-manager-service.js +9 -3
  184. package/dist/src/network/network-manager-service.js.map +1 -1
  185. package/dist/src/protocol-client.d.ts +13 -0
  186. package/dist/src/protocol-client.d.ts.map +1 -1
  187. package/dist/src/protocol-client.js +74 -2
  188. package/dist/src/protocol-client.js.map +1 -1
  189. package/dist/src/reactivity/forwarder-host.d.ts +198 -0
  190. package/dist/src/reactivity/forwarder-host.d.ts.map +1 -0
  191. package/dist/src/reactivity/forwarder-host.js +355 -0
  192. package/dist/src/reactivity/forwarder-host.js.map +1 -0
  193. package/dist/src/reactivity/index.d.ts +19 -0
  194. package/dist/src/reactivity/index.d.ts.map +1 -0
  195. package/dist/src/reactivity/index.js +19 -0
  196. package/dist/src/reactivity/index.js.map +1 -0
  197. package/dist/src/reactivity/notify-transport.d.ts +70 -0
  198. package/dist/src/reactivity/notify-transport.d.ts.map +1 -0
  199. package/dist/src/reactivity/notify-transport.js +111 -0
  200. package/dist/src/reactivity/notify-transport.js.map +1 -0
  201. package/dist/src/reactivity/origination-manager.d.ts +110 -0
  202. package/dist/src/reactivity/origination-manager.d.ts.map +1 -0
  203. package/dist/src/reactivity/origination-manager.js +129 -0
  204. package/dist/src/reactivity/origination-manager.js.map +1 -0
  205. package/dist/src/reactivity/protocols.d.ts +43 -0
  206. package/dist/src/reactivity/protocols.d.ts.map +1 -0
  207. package/dist/src/reactivity/protocols.js +50 -0
  208. package/dist/src/reactivity/protocols.js.map +1 -0
  209. package/dist/src/reactivity/push-state-gossip.d.ts +132 -0
  210. package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -0
  211. package/dist/src/reactivity/push-state-gossip.js +231 -0
  212. package/dist/src/reactivity/push-state-gossip.js.map +1 -0
  213. package/dist/src/reactivity/recover-transport.d.ts +167 -0
  214. package/dist/src/reactivity/recover-transport.d.ts.map +1 -0
  215. package/dist/src/reactivity/recover-transport.js +308 -0
  216. package/dist/src/reactivity/recover-transport.js.map +1 -0
  217. package/dist/src/reactivity/rotation-rereg-scheduler.d.ts +142 -0
  218. package/dist/src/reactivity/rotation-rereg-scheduler.d.ts.map +1 -0
  219. package/dist/src/reactivity/rotation-rereg-scheduler.js +223 -0
  220. package/dist/src/reactivity/rotation-rereg-scheduler.js.map +1 -0
  221. package/dist/src/reactivity/subscriber-registry.d.ts +47 -0
  222. package/dist/src/reactivity/subscriber-registry.d.ts.map +1 -0
  223. package/dist/src/reactivity/subscriber-registry.js +87 -0
  224. package/dist/src/reactivity/subscriber-registry.js.map +1 -0
  225. package/dist/src/reactivity/subscription-manager.d.ts +221 -0
  226. package/dist/src/reactivity/subscription-manager.d.ts.map +1 -0
  227. package/dist/src/reactivity/subscription-manager.js +302 -0
  228. package/dist/src/reactivity/subscription-manager.js.map +1 -0
  229. package/dist/src/reactivity/topic-bytes.d.ts +32 -0
  230. package/dist/src/reactivity/topic-bytes.d.ts.map +1 -0
  231. package/dist/src/reactivity/topic-bytes.js +34 -0
  232. package/dist/src/reactivity/topic-bytes.js.map +1 -0
  233. package/dist/src/repo/client.d.ts.map +1 -1
  234. package/dist/src/repo/client.js +51 -23
  235. package/dist/src/repo/client.js.map +1 -1
  236. package/dist/src/repo/service.d.ts +46 -1
  237. package/dist/src/repo/service.d.ts.map +1 -1
  238. package/dist/src/repo/service.js +79 -45
  239. package/dist/src/repo/service.js.map +1 -1
  240. package/dist/src/rpc-deadline.d.ts +37 -0
  241. package/dist/src/rpc-deadline.d.ts.map +1 -0
  242. package/dist/src/rpc-deadline.js +31 -0
  243. package/dist/src/rpc-deadline.js.map +1 -0
  244. package/dist/src/storage/block-storage.d.ts +2 -0
  245. package/dist/src/storage/block-storage.d.ts.map +1 -1
  246. package/dist/src/storage/block-storage.js +95 -2
  247. package/dist/src/storage/block-storage.js.map +1 -1
  248. package/dist/src/storage/i-block-storage.d.ts +25 -0
  249. package/dist/src/storage/i-block-storage.d.ts.map +1 -1
  250. package/dist/src/storage/storage-repo.d.ts +53 -2
  251. package/dist/src/storage/storage-repo.d.ts.map +1 -1
  252. package/dist/src/storage/storage-repo.js +193 -9
  253. package/dist/src/storage/storage-repo.js.map +1 -1
  254. package/dist/src/sync/client.d.ts +4 -1
  255. package/dist/src/sync/client.d.ts.map +1 -1
  256. package/dist/src/sync/client.js +5 -2
  257. package/dist/src/sync/client.js.map +1 -1
  258. package/dist/src/testing/cohort-topic-mesh-harness.d.ts +219 -0
  259. package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -0
  260. package/dist/src/testing/cohort-topic-mesh-harness.js +467 -0
  261. package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -0
  262. package/dist/src/testing/matchmaking-mesh-harness.d.ts +187 -0
  263. package/dist/src/testing/matchmaking-mesh-harness.d.ts.map +1 -0
  264. package/dist/src/testing/matchmaking-mesh-harness.js +358 -0
  265. package/dist/src/testing/matchmaking-mesh-harness.js.map +1 -0
  266. package/dist/src/testing/mesh-harness.d.ts.map +1 -1
  267. package/dist/src/testing/mesh-harness.js +23 -1
  268. package/dist/src/testing/mesh-harness.js.map +1 -1
  269. package/dist/src/testing/reactivity-mesh-harness.d.ts +298 -0
  270. package/dist/src/testing/reactivity-mesh-harness.d.ts.map +1 -0
  271. package/dist/src/testing/reactivity-mesh-harness.js +663 -0
  272. package/dist/src/testing/reactivity-mesh-harness.js.map +1 -0
  273. package/package.json +7 -4
  274. package/src/cluster/block-transfer-service.ts +133 -68
  275. package/src/cluster/client.ts +64 -45
  276. package/src/cluster/cluster-error.ts +64 -0
  277. package/src/cluster/cluster-repo.ts +405 -55
  278. package/src/cluster/commit-cert.ts +139 -0
  279. package/src/cluster/service.ts +116 -44
  280. package/src/cluster/spread-on-churn.ts +328 -285
  281. package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -0
  282. package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -0
  283. package/src/cohort-topic/bootstrap-parent-reference.ts +159 -0
  284. package/src/cohort-topic/change-bridge.ts +109 -0
  285. package/src/cohort-topic/cohort-gossip-driver.ts +144 -0
  286. package/src/cohort-topic/cohort-gossip-transport.ts +74 -0
  287. package/src/cohort-topic/fret-trust-anchor.ts +153 -0
  288. package/src/cohort-topic/host.ts +2175 -0
  289. package/src/cohort-topic/index.ts +13 -0
  290. package/src/cohort-topic/membership-publish-sink.ts +20 -0
  291. package/src/cohort-topic/membership-source.ts +68 -0
  292. package/src/cohort-topic/peer-codec.ts +31 -0
  293. package/src/cohort-topic/peer-sig.ts +86 -0
  294. package/src/cohort-topic/protocols.ts +71 -0
  295. package/src/cohort-topic/reactivity-membership-gate.ts +77 -0
  296. package/src/cohort-topic/size-estimator.ts +16 -0
  297. package/src/cohort-topic/stream-util.ts +87 -0
  298. package/src/cohort-topic/threshold-crypto.ts +239 -0
  299. package/src/cohort-topic/topic-router.ts +77 -0
  300. package/src/dispute/cascade.ts +517 -0
  301. package/src/dispute/client.ts +12 -2
  302. package/src/dispute/dispute-service.ts +171 -24
  303. package/src/dispute/index.ts +42 -0
  304. package/src/dispute/invalidation.ts +587 -0
  305. package/src/dispute/types.ts +14 -0
  306. package/src/index.ts +4 -0
  307. package/src/libp2p-key-network.ts +49 -6
  308. package/src/libp2p-node-base.ts +620 -12
  309. package/src/matchmaking/aggregate-counts.ts +104 -0
  310. package/src/matchmaking/index.ts +20 -0
  311. package/src/matchmaking/module.ts +363 -0
  312. package/src/matchmaking/protocols.ts +51 -0
  313. package/src/matchmaking/provider-manager.ts +95 -0
  314. package/src/matchmaking/query-handler.ts +88 -0
  315. package/src/matchmaking/query-transport.ts +483 -0
  316. package/src/matchmaking/seeker-manager.ts +64 -0
  317. package/src/matchmaking/seeker-walk-client.ts +293 -0
  318. package/src/matchmaking/traffic-validation.ts +195 -0
  319. package/src/network/network-manager-service.ts +9 -2
  320. package/src/protocol-client.ts +64 -3
  321. package/src/reactivity/forwarder-host.ts +438 -0
  322. package/src/reactivity/index.ts +19 -0
  323. package/src/reactivity/notify-transport.ts +144 -0
  324. package/src/reactivity/origination-manager.ts +192 -0
  325. package/src/reactivity/protocols.ts +61 -0
  326. package/src/reactivity/push-state-gossip.ts +291 -0
  327. package/src/reactivity/recover-transport.ts +408 -0
  328. package/src/reactivity/rotation-rereg-scheduler.ts +256 -0
  329. package/src/reactivity/subscriber-registry.ts +96 -0
  330. package/src/reactivity/subscription-manager.ts +450 -0
  331. package/src/reactivity/topic-bytes.ts +37 -0
  332. package/src/repo/client.ts +54 -23
  333. package/src/repo/service.ts +90 -42
  334. package/src/rpc-deadline.ts +45 -0
  335. package/src/storage/block-storage.ts +106 -3
  336. package/src/storage/i-block-storage.ts +27 -0
  337. package/src/storage/storage-repo.ts +209 -13
  338. package/src/sync/client.ts +5 -2
  339. package/src/testing/cohort-topic-mesh-harness.ts +613 -0
  340. package/src/testing/matchmaking-mesh-harness.ts +475 -0
  341. package/src/testing/mesh-harness.ts +22 -2
  342. package/src/testing/reactivity-mesh-harness.ts +922 -0
@@ -0,0 +1,663 @@
1
+ /**
2
+ * Reactivity **mock-transport mesh harness** — the in-process, many-logical-node substrate for the
3
+ * reactivity *e2e* tier (`docs/reactivity.md`), layered on the cohort-topic mesh harness
4
+ * ({@link import("./cohort-topic-mesh-harness.js")}) rather than forking it (the same relationship the
5
+ * {@link import("./matchmaking-mesh-harness.js").MatchmakingMesh} has to it). It drives the **real**
6
+ * reactivity hot path end-to-end over a network of real-Ed25519-keyed cohort-topic hosts:
7
+ *
8
+ * - **Origination** rides the real {@link makeCohortTopicChangeNotifier} over a real {@link StorageRepo}:
9
+ * a `pend`+`commit` fires the catch-all change feed → the bridge hands the {@link CollectionChangeEvent}
10
+ * + a **real** threshold {@link CommitCert} (each tail-cohort member signs the commit-vote payload with
11
+ * its real Ed25519 key, assembled by {@link buildCommitCert}) to the real
12
+ * {@link ReactivityOriginationManager}, which builds the {@link NotificationV1} reusing the cert's
13
+ * threshold signature **unchanged** — exactly as in production (`local-change-notifier-bridge`).
14
+ * - **Forwarding** runs the real {@link createReactivityForwarder} receive path (verify → dedupe → buffer)
15
+ * over a real {@link PushState} (replay ring + rolling checkpoint + per-subscriber backpressure).
16
+ * - **Delivery** runs the real {@link ReactivitySubscriptionManager} (register at cohort-topic tier **T3**,
17
+ * verify against the cached tail-cohort {@link MembershipCertV1} with **real** collected-multisig crypto,
18
+ * contiguity check, gap → backfill, `(collectionId, revision)` dedupe, surface).
19
+ * - **Recovery** serves real {@link serveBackfill} / {@link serveResume} from the tail `PushState`, applied
20
+ * subscriber-side by the manager's `resume()` / backfill seam.
21
+ * - **Rotation** drives the real {@link BlockFillTracker} / {@link buildRotationHint} /
22
+ * {@link planReRegistrationWave} / {@link buildRotationHandoffCheckpoint} lifecycle; **backpressure**
23
+ * drives the real {@link PushState.enqueueForSubscribers}.
24
+ *
25
+ * **What is real vs. modeled (honest).** Every signature, registration record, replay-buffer entry, dedupe
26
+ * decision, checkpoint fold, and resume classification is the real db-core/db-p2p code. What the harness
27
+ * *models* is the **notification transport** (the application protocol that would dial each subscriber's
28
+ * primary and each child cohort) — it fans the originated notification to the tracked direct subscribers
29
+ * in-process via the real per-subscriber bounded queues — and, like the matchmaking harness, the
30
+ * **single-tier-0 reach**: the cohort-topic substrate serves a single tier-0 cohort (multi-tier *serving*
31
+ * promotion to a tier-`d ≥ 1` forwarder cohort is gated on the cohort-topic follow-ons), so a deep
32
+ * forwarder *tree* fan-out is tagged-unimplemented in the suites, not faked here. The tail cohort fanning
33
+ * to every direct subscriber — the heart of contiguous/verified delivery — is fully real.
34
+ *
35
+ * **Virtual time.** Like the sibling harnesses this is not a wall-clock simulator: `now` is an explicit
36
+ * virtual clock the harness advances ({@link ReactivityMesh.advanceTime}), so `T_drain` / rotation timing
37
+ * resolve deterministically without sleeping. The only real-time waits are the cohort harness's tiny
38
+ * async-settle polls.
39
+ */
40
+ import { Tier, reactivityTopicId, createReactivityForwarder, createNotificationVerifier, PushState, serveBackfill, serveResume, buildRotationHint, BlockFillTracker, TailDrainGate, planReRegistrationWave, buildRotationHandoffCheckpoint, applyRotationHandoff, createRejoinJitter, mayServeAsReactivityForwarder, deltaMaxForProfile, coreProfile, edgeProfile, bytesToB64url, DEFAULT_CAP_PROMOTE_FAST, T_REJOIN_JITTER_MS, WARM_THRESHOLD_DEFAULT, } from "@optimystic/db-core";
41
+ import { StorageRepo } from "../storage/storage-repo.js";
42
+ import { BlockStorage } from "../storage/block-storage.js";
43
+ import { MemoryRawStorage } from "../storage/memory-storage.js";
44
+ import { makeCohortTopicChangeNotifier } from "../cohort-topic/change-bridge.js";
45
+ import { buildCommitCert } from "../cluster/commit-cert.js";
46
+ import { ReactivitySubscriptionManager } from "../reactivity/subscription-manager.js";
47
+ import { ReactivityOriginationManager } from "../reactivity/origination-manager.js";
48
+ import { RotationReRegistrationScheduler } from "../reactivity/rotation-rereg-scheduler.js";
49
+ import { RotationRedirectError } from "../reactivity/recover-transport.js";
50
+ import { addressing, buildMesh, delay, makeMembers, setupTopic, } from "./cohort-topic-mesh-harness.js";
51
+ export { addressing, delay };
52
+ const utf8 = new TextEncoder();
53
+ /**
54
+ * The reactivity integration mesh. Build with {@link buildReactivityMesh}; register a collection with
55
+ * {@link registerCollection}; drive subscribers/commits/recovery with {@link subscribe} / {@link commit} /
56
+ * {@link resume} / {@link rotateTail}.
57
+ */
58
+ export class ReactivityMesh {
59
+ mesh;
60
+ members;
61
+ wantK;
62
+ minSigs;
63
+ profiles;
64
+ collections = new Map();
65
+ vtime = 1_700_000_000_000;
66
+ corr = 0;
67
+ /** Pending one-shot timers armed against the virtual clock (the rotation scheduler's `setTimer` binding). */
68
+ virtualTimers = [];
69
+ constructor(mesh, members, wantK, minSigs, profiles) {
70
+ this.mesh = mesh;
71
+ this.members = members;
72
+ this.wantK = wantK;
73
+ this.minSigs = minSigs;
74
+ this.profiles = profiles;
75
+ }
76
+ /** Stand up an N-node reactivity mesh and start every host. */
77
+ static async build(opts = {}) {
78
+ const nodeCount = opts.nodeCount ?? 12;
79
+ const wantK = opts.wantK ?? Math.min(nodeCount, 8);
80
+ const minSigs = opts.minSigs ?? Math.max(1, wantK - 2);
81
+ const sizeEstimate = opts.sizeEstimate ?? 256;
82
+ const members = await makeMembers(nodeCount);
83
+ const meshOpts = {
84
+ wantK,
85
+ minSigs,
86
+ sizeEstimate,
87
+ // Single-tier-0 by default (same rationale as the matchmaking harness): test artifacts register
88
+ // many subscribers within a few ms, which would otherwise trip the pre-promotion slope predictor.
89
+ capPromote: opts.capPromote ?? 1_000_000,
90
+ // This mesh is virtual-time: a subscribe batch lands at one fixed virtual instant, but the cohort
91
+ // host stamps each register with real `Date.now()`. The growth-slope pre-promotion is a
92
+ // wall-clock-rate heuristic, so those real-time-separated samples make it fire *before* the
93
+ // `cap_promote` count is reached (redirecting the rest of the batch up a tier-1 tree the
94
+ // single-tier-0 milestone never instantiates). Disable slope here so promotion is driven purely by
95
+ // the count threshold — the same posture the cohort-topic scale specs get by stamping a constant
96
+ // `now` on every `handleRegister`. The slope heuristic itself is covered by the promotion unit specs.
97
+ promotion: { tPromoteLookaheadMs: 0 },
98
+ // Permissive register ceiling — the harness re-probes a cohort across many subscribes/commits.
99
+ // Also widen the replay-guard freshness window. The cohort host stamps each register with real
100
+ // `Date.now()` and evaluates the anti-DoS freshness gate against real `Date.now()` at processing
101
+ // time, but this mesh drives the real participant walk in-process: under full-suite CPU load the
102
+ // event loop can stall for far longer than the production 60 s staleness window between a register
103
+ // being stamped and the deciding cohort processing it. A stalled-but-legitimate register then
104
+ // trips the freshness gate (`no_state`), the cold-start bootstrap walk exhausts, and `register`
105
+ // throws `CohortBackoffError` — a pure load-induced harness flake. The wall-clock freshness
106
+ // defense is meaningless here (same posture as the disabled rate limiter above), so make the
107
+ // window effectively unbounded so test-induced stalls never read as stale/future replays.
108
+ // Disable the cold-start proof-of-work cost. Reactivity subscribers register at tier T3, so the
109
+ // participant-side bootstrap-evidence builder mints a PoW puzzle (default 20 bits ≈ 1 M hashes) on
110
+ // *every* register/re-probe — by far the dominant cost in this mesh (a 6-subscriber promotion test
111
+ // burns ~60-100 s almost entirely in `randomFillSync`/SHA-256 minting nonces). The cohort's
112
+ // bootstrap-evidence policy is unconfigured here, so the evidence is never even required; minting it
113
+ // is pure wasted work. `bits: 0` solves on the first nonce (the documented test escape hatch in
114
+ // `meetsDifficulty`) — same posture as the neutralized rate limiter / replay guard above, and it
115
+ // removes the timing-sensitivity that tips these tests into a timeout under full-suite load.
116
+ antiDos: { rateLimiter: { ratePerWindow: 1_000_000 }, replayGuard: { maxAgeMs: 86_400_000, maxFutureSkewMs: 86_400_000 }, powDifficultyBits: 0 },
117
+ ...(opts.profiles === undefined ? {} : { profiles: opts.profiles }),
118
+ };
119
+ const mesh = await buildMesh(members, meshOpts);
120
+ const profiles = Array.from({ length: nodeCount }, (_v, i) => opts.profiles?.[i] ?? "core");
121
+ return new ReactivityMesh(mesh, members, wantK, minSigs, profiles);
122
+ }
123
+ /** The current virtual time (unix ms). */
124
+ get now() {
125
+ return this.vtime;
126
+ }
127
+ /**
128
+ * Advance the virtual clock by `ms` (drives `T_drain` / rotation timing deterministically), then fire any
129
+ * virtual timers now due. A fired timer may arm another (a chained rotation), so fire one-at-a-time until
130
+ * the queue is quiescent — the rotation re-registration scheduler's `setTimer` binding rides this.
131
+ */
132
+ advanceTime(ms) {
133
+ this.vtime += ms;
134
+ for (;;) {
135
+ const idx = this.virtualTimers.findIndex((t) => !t.cancelled && t.fireAt <= this.vtime);
136
+ if (idx === -1) {
137
+ break;
138
+ }
139
+ const [timer] = this.virtualTimers.splice(idx, 1);
140
+ if (!timer.cancelled) {
141
+ timer.fn();
142
+ }
143
+ }
144
+ }
145
+ /** Arm a one-shot timer against the virtual clock; the cancel handle drops it (the scheduler's `setTimer`). */
146
+ armVirtualTimer(fn, delayMs) {
147
+ const timer = { fireAt: this.vtime + Math.max(0, delayMs), fn, cancelled: false };
148
+ this.virtualTimers.push(timer);
149
+ return () => { timer.cancelled = true; };
150
+ }
151
+ profileOf(index) {
152
+ return this.profiles[index] === "edge" ? edgeProfile() : coreProfile();
153
+ }
154
+ /**
155
+ * Register a collection on the mesh: instantiate its tail-anchored tier-0 cohort (+willingness quorum),
156
+ * install the real origination manager behind the real change-notifier bridge over a fresh `StorageRepo`,
157
+ * build the tail `PushState` (replay ring + rolling checkpoint + backpressure), and cache the tail
158
+ * cohort's `MembershipCertV1` into every node's verifier so end-to-end verification is real Ed25519.
159
+ */
160
+ async registerCollection(name, opts = {}) {
161
+ const collectionId = utf8.encode(`reactivity:${name}`);
162
+ const collectionIdB64 = bytesToB64url(collectionId);
163
+ const tailId = this.pinTailToCore(`${name}:tail-0`);
164
+ const topicId = reactivityTopicId(tailId);
165
+ const coord0 = addressing.coord0(topicId);
166
+ const setup = await setupTopic(this.mesh, topicId);
167
+ const tailCohort = this.cohortMembersAround(coord0);
168
+ const deltaMaxBytes = opts.deltaMaxBytes ?? deltaMaxForProfile(coreProfile());
169
+ const w = opts.w ?? 256;
170
+ const wCheckpoint = opts.wCheckpoint ?? 4096;
171
+ const queueMax = opts.queueMax ?? 32;
172
+ const rawStorage = new MemoryRawStorage();
173
+ const repo = new StorageRepo((blockId) => new BlockStorage(blockId, rawStorage));
174
+ // Origination is the tail cohort primary's responsibility — installed on the node nearest coord_0
175
+ // (the routed tail primary), exactly the node a commit on the tail cluster lands on in production.
176
+ const originationService = setup.deciding.host.service;
177
+ const certByAction = new Map();
178
+ const emitQueue = [];
179
+ const pushState = this.makePushState(collectionIdB64, topicId, tailId, w, wCheckpoint, queueMax, deltaMaxBytes);
180
+ const state = {
181
+ name,
182
+ collectionId,
183
+ collectionIdB64,
184
+ tailId,
185
+ topicId,
186
+ coord0,
187
+ tailCohort,
188
+ setup,
189
+ repo,
190
+ originationService,
191
+ pushState,
192
+ forwarder: createReactivityForwarder({ state: pushState, verifier: this.notificationVerifierFor(originationService) }),
193
+ emitQueue,
194
+ rev: 0,
195
+ blockSeq: 0,
196
+ deltaMaxBytes,
197
+ w,
198
+ wCheckpoint,
199
+ queueMax,
200
+ fillTracker: new BlockFillTracker({
201
+ ...(opts.blockFillSize === undefined ? {} : { blockFillSize: opts.blockFillSize }),
202
+ // warm_threshold must be < block_fill_size; clamp the default down for small (test-scaled) blocks.
203
+ warmThreshold: opts.warmThreshold ?? (opts.blockFillSize === undefined ? WARM_THRESHOLD_DEFAULT : Math.min(WARM_THRESHOLD_DEFAULT, Math.max(0, opts.blockFillSize - 1))),
204
+ }),
205
+ subscribers: [],
206
+ certByAction,
207
+ };
208
+ const origination = new ReactivityOriginationManager({
209
+ service: originationService,
210
+ resolveContext: () => ({
211
+ tailId: state.tailId,
212
+ deltaMaxBytes: state.deltaMaxBytes,
213
+ ...(state.rotationHint === undefined ? {} : { rotationHint: state.rotationHint }),
214
+ }),
215
+ emit: (n) => { emitQueue.push(n); },
216
+ clock: () => this.vtime,
217
+ });
218
+ origination.install();
219
+ state.origination = origination;
220
+ // The real local-change-notifier bridge: a StorageRepo commit → onLocalCommit → origination.
221
+ makeCohortTopicChangeNotifier({
222
+ source: repo,
223
+ service: originationService,
224
+ selfIsCohortMember: (e) => e.collectionId === collectionIdB64,
225
+ extractCommitCert: (e) => certByAction.get(e.actionId),
226
+ });
227
+ this.collections.set(name, state);
228
+ this.cacheTailCert(state);
229
+ }
230
+ /** The reactivity notification verifier over a node's service verifier (real collected-multisig at T3). */
231
+ notificationVerifierFor(service) {
232
+ return createNotificationVerifier({ verifier: service.verifier(), tier: Tier.T3 });
233
+ }
234
+ makePushState(collectionIdB64, topicId, tailId, w, wCheckpoint, queueMax, deltaMaxBytes) {
235
+ return new PushState({
236
+ collectionId: collectionIdB64,
237
+ topicId: bytesToB64url(topicId),
238
+ tailIdAtJoin: bytesToB64url(tailId),
239
+ w,
240
+ wCheckpoint,
241
+ queueMax,
242
+ deltaMaxBytes,
243
+ });
244
+ }
245
+ /** The FRET cohort (the `wantK` nearest members) around a coord. */
246
+ cohortMembersAround(coord) {
247
+ const ids = new Set(this.mesh.assembleCohort(coord, this.wantK));
248
+ return this.members.filter((m) => ids.has(m.idStr));
249
+ }
250
+ /** Whether the node FRET-routes nearest to `coord` (the cohort's routed primary) is an Edge profile. */
251
+ nearestIsEdge(coord) {
252
+ const nearest = this.mesh.nearest(coord);
253
+ const idx = this.members.findIndex((m) => m.idStr === nearest.idStr);
254
+ return idx >= 0 && this.profiles[idx] === "edge";
255
+ }
256
+ /**
257
+ * Choose a reactivity tail id derived from `baseTail` whose `coord_0` routes nearest a **Core** node.
258
+ * Reactivity topics serve at tier T3, and an Edge node declines a T3 cold-start by design (it serves no
259
+ * forwarder duty — pinned by `cohort-topic-scale-lifecycle` "Edge serves no T3"), so a topic whose
260
+ * `coord_0` routes nearest an Edge node can never instantiate its tail cohort: the routed primary returns
261
+ * `no_state`, the walk exhausts, and `register` throws `CohortBackoffError`. Because {@link makeMembers}
262
+ * re-randomizes the member ring layout every run, an Edge-profile mesh would otherwise *intermittently*
263
+ * seat the Edge node as a collection's tail primary (~1/nodeCount of runs) and flake. In production a
264
+ * reactivity tail cohort IS the Core cluster that served the tail, so pinning the primary to a Core node
265
+ * models reality. A no-op (returns `utf8(baseTail)` at the first try) when the mesh has no Edge nodes —
266
+ * the common case — so every all-Core suite is byte-for-byte unaffected.
267
+ */
268
+ pinTailToCore(baseTail) {
269
+ for (let nonce = 0;; nonce++) {
270
+ const tailId = utf8.encode(nonce === 0 ? baseTail : `${baseTail}#${nonce}`);
271
+ if (!this.nearestIsEdge(addressing.coord0(reactivityTopicId(tailId)))) {
272
+ return tailId;
273
+ }
274
+ }
275
+ }
276
+ collection(name) {
277
+ const c = this.collections.get(name);
278
+ if (c === undefined) {
279
+ throw new Error(`reactivity mesh: collection "${name}" is not registered (call registerCollection first)`);
280
+ }
281
+ return c;
282
+ }
283
+ /**
284
+ * Cache the tail cohort's `MembershipCertV1` (over the FRET cohort around the current tail's coord_0) into
285
+ * every node's participant verifier, so a subscriber's notification verify is real Ed25519 against the
286
+ * tail membership — mirroring `reactivity-real-crypto.spec.ts`, now over the mesh's real cohort members.
287
+ */
288
+ cacheTailCert(c) {
289
+ const members = c.tailCohort.map((m) => bytesToB64url(m.bytes));
290
+ const cert = {
291
+ v: 1,
292
+ cohortCoord: bytesToB64url(c.coord0),
293
+ cohortEpoch: bytesToB64url(utf8.encode(`${c.name}:${bytesToB64url(c.tailId)}`)),
294
+ members,
295
+ stabilizedAt: this.vtime,
296
+ thresholdSig: bytesToB64url(new Uint8Array([0])),
297
+ signers: members,
298
+ };
299
+ for (const node of this.mesh.nodes) {
300
+ node.host.service.verifier().cache(cert);
301
+ }
302
+ }
303
+ /** The tail cohort member ids (base64url member bytes) serving a collection's current tail. */
304
+ tailCohortIds(name) {
305
+ return this.collection(name).tailCohort.map((m) => bytesToB64url(m.bytes));
306
+ }
307
+ /** This collection's tier-0 cohort registration records carrying a reactivity subscribe payload. */
308
+ cohortSubscriberCount(name) {
309
+ const c = this.collection(name);
310
+ return c.setup.decidingEngine.records(c.topicId).filter((r) => r.appState !== undefined).length;
311
+ }
312
+ /** The current committed revision for a collection. */
313
+ currentRevision(name) {
314
+ return this.collection(name).rev;
315
+ }
316
+ /**
317
+ * Drive the deciding cohort's membership stabilization (publish its `MembershipCertV1`). Promotion is
318
+ * fire-and-forget off the register path; stabilizing publishes the cohort cert so a crossed-`cap_promote`
319
+ * decision lands deterministically (no wall-clock wait) — the same pre-step the cohort-topic scale specs use.
320
+ */
321
+ async stabilizeCohort(name) {
322
+ await this.collection(name).setup.decidingEngine.onStabilized(this.vtime);
323
+ }
324
+ /** Whether a collection's tier-0 cohort has promoted (its direct participants crossed `cap_promote`). */
325
+ isPromoted(name) {
326
+ const c = this.collection(name);
327
+ return c.setup.decidingEngine.isPromoted(c.topicId);
328
+ }
329
+ /** Whether a node profile may serve as a reactivity forwarder (T3 producer) — Edge never can. */
330
+ mayForward(nodeIndex) {
331
+ return mayServeAsReactivityForwarder(this.profileOf(nodeIndex));
332
+ }
333
+ /**
334
+ * Register node `nodeIndex` as a reactivity subscriber for `collection` via the **real**
335
+ * {@link ReactivitySubscriptionManager} → `CohortTopicService.register` walk at tier T3. The subscribe
336
+ * record lands in the real tier-0 cohort store; the manager owns the real verify/deliver/backfill/resume
337
+ * path. Returns a handle whose `delivered` array accumulates the surfaced notifications.
338
+ */
339
+ async subscribe(nodeIndex, collection, opts = {}) {
340
+ const c = this.collection(collection);
341
+ const member = this.members[nodeIndex];
342
+ const service = this.mesh.nodes[nodeIndex].host.service;
343
+ const profile = opts.profile ?? this.profileOf(nodeIndex);
344
+ const subId = bytesToB64url(member.bytes);
345
+ const handle = {
346
+ nodeIndex,
347
+ member,
348
+ subId,
349
+ manager: undefined,
350
+ delivered: [],
351
+ attachedTailB64: bytesToB64url(c.tailId),
352
+ collectionName: collection,
353
+ autoDrain: opts.autoDrain ?? true,
354
+ asleep: false,
355
+ backfills: 0,
356
+ chainRead: false,
357
+ checkpointDigests: [],
358
+ rotationNotices: [],
359
+ scheduler: undefined,
360
+ };
361
+ // The host re-registration scheduler bound to this subscriber, driven over the harness virtual clock. On
362
+ // fire it re-attaches the subscriber under the rotated tail — the production move the deferred Quereus
363
+ // `Database.watch` factory performs (`reactivity-rotation-host-wiring-e2e` §C). The single-subscriber
364
+ // `planReRegistration` path draws `fireAt` over `T_rejoin_jitter`, so a `advanceTime` past that window
365
+ // fires it deterministically.
366
+ const scheduler = new RotationReRegistrationScheduler({
367
+ reRegister: (plan) => { this.reAttachToNewTail(handle, plan); return Promise.resolve(); },
368
+ setTimer: (fn, delayMs) => this.armVirtualTimer(fn, delayMs),
369
+ now: () => this.vtime,
370
+ });
371
+ handle.scheduler = scheduler;
372
+ const manager = new ReactivitySubscriptionManager({
373
+ service,
374
+ collectionId: c.collectionId,
375
+ tailIdAtAttach: c.tailId,
376
+ deliver: (n) => { handle.delivered.push(n); },
377
+ profile,
378
+ lastKnownRev: opts.lastKnownRev ?? 0,
379
+ clock: () => this.vtime,
380
+ // Backfill RPC served from the tail cohort's live replay buffer (real serveBackfill).
381
+ signBackfill: (req) => bytesToB64url(utf8.encode(`bf:${req.fromRevision}:${req.toRevision}`)),
382
+ backfillTransport: (req) => { handle.backfills += 1; return Promise.resolve(serveBackfill(c.pushState.replayBuffer, req, c.collectionIdB64)); },
383
+ // Resume RPC served from the tail cohort's stacked windows (real serveResume).
384
+ signResume: () => bytesToB64url(utf8.encode("resume")),
385
+ subscriberCoord: subId,
386
+ resumeTransport: (req) => {
387
+ // Model the live recover serve's drain redirect (the running node's `markRotated` →
388
+ // `rotationRedirectFor` → `RotationRedirectError`): a resume whose `latestKnownTailId` anchors a
389
+ // rotated, still-draining tail is bounced to the new tree instead of served stale data.
390
+ const gate = c.rotationGate;
391
+ if (gate !== undefined && req.latestKnownTailId === c.rotatedFromTailB64 && gate.isDraining(this.vtime)) {
392
+ return Promise.reject(new RotationRedirectError(gate.rotationRedirect));
393
+ }
394
+ return Promise.resolve(serveResume(req, {
395
+ buffer: c.pushState.replayBuffer,
396
+ checkpoint: c.pushState.checkpoint,
397
+ // The cross-rotation inherited window the new tail holds after a handoff: a resume below the new
398
+ // ring but within it is served `checkpoint_window` (not `out_of_window`) while it abuts the ring.
399
+ inheritedCheckpoint: c.pushState.inheritedCheckpoint,
400
+ currentTailId: bytesToB64url(c.tailId),
401
+ currentRevision: c.rev,
402
+ rotationRevision: c.rev,
403
+ expectedCollectionId: c.collectionIdB64,
404
+ }));
405
+ },
406
+ onChainRead: () => { handle.chainRead = true; },
407
+ onTailRotated: (newTailId, rev) => { handle.tailRotated = [newTailId, rev]; },
408
+ onCheckpointDigest: (summary) => { handle.checkpointDigests.push(summary); },
409
+ // Surface the jittered re-registration plan from a delivered rotation pre-announce / hard rotation.
410
+ rejoinJitter: createRejoinJitter({ capPromote: DEFAULT_CAP_PROMOTE_FAST, random: this.deterministicRandom() }),
411
+ onRotation: (notice) => { handle.rotationNotices.push(notice); scheduler.schedule(notice); },
412
+ });
413
+ handle.manager = manager;
414
+ handle.registration = await manager.register();
415
+ c.subscribers.push(handle);
416
+ return handle;
417
+ }
418
+ /**
419
+ * Commit `count` transactions to `collection` through the real `StorageRepo` → change-bridge →
420
+ * origination path, then fan the originated notifications out to current-tail subscribers. Returns the
421
+ * new current revision. Each commit pre-signs a **real** threshold {@link CommitCert} with the tail
422
+ * cohort's Ed25519 keys (the bridge's synchronous extractor resolves it by `actionId`).
423
+ */
424
+ async commit(collection, count = 1) {
425
+ const c = this.collection(collection);
426
+ for (let i = 0; i < count; i++) {
427
+ const rev = c.rev + 1;
428
+ const actionId = `rx-${c.collectionIdB64}-${rev}-${this.corr++}`;
429
+ const blockId = `${c.name}-blk-${c.blockSeq++}`;
430
+ // Anticipatory warm-up / block-filling: the filling commit carries the rotation pre-announce.
431
+ const signal = c.fillTracker.onCommit();
432
+ if (signal.kind === "filling") {
433
+ const nextTail = utf8.encode(`${c.name}:tail-${rev}`);
434
+ c.rotationHint = buildRotationHint(bytesToB64url(nextTail), rev);
435
+ }
436
+ const cert = await this.buildTailCert(c, rev);
437
+ c.certByAction.set(actionId, cert);
438
+ await c.repo.pend({ actionId: actionId, transforms: this.insertTransforms(blockId, c.collectionIdB64), policy: "c" });
439
+ const result = await c.repo.commit({ actionId: actionId, blockIds: [blockId], tailId: blockId, rev });
440
+ if (!result.success) {
441
+ throw new Error(`reactivity mesh: commit failed for ${collection} rev ${rev}`);
442
+ }
443
+ c.rev = rev;
444
+ // The pre-announce is one-shot: it rode the filling notification; clear it for subsequent commits.
445
+ c.rotationHint = undefined;
446
+ }
447
+ await this.drainFanOut(c);
448
+ return c.rev;
449
+ }
450
+ /** Build a real threshold commit cert: every tail-cohort member signs `utf8(commitHash + ":approve")`. */
451
+ async buildTailCert(c, rev) {
452
+ const commitHash = `${c.collectionIdB64}:${rev}`;
453
+ const signedPayload = utf8.encode(`${commitHash}:approve`);
454
+ const commits = {};
455
+ for (const m of c.tailCohort) {
456
+ const sig = await m.key.sign(signedPayload);
457
+ commits[m.idStr] = { type: "approve", signature: bytesToB64url(sig) };
458
+ }
459
+ const record = {
460
+ messageHash: `mh-${commitHash}`,
461
+ message: { operations: [{ get: { blockIds: [] } }], expiration: this.vtime + 30_000 },
462
+ peers: this.clusterPeers(c.tailCohort),
463
+ promises: {},
464
+ commits,
465
+ };
466
+ return buildCommitCert(record, this.minSigs, signedPayload);
467
+ }
468
+ clusterPeers(cohort) {
469
+ const peers = {};
470
+ for (const m of cohort) {
471
+ peers[m.idStr] = { multiaddrs: ["/ip4/127.0.0.1/tcp/8000"], publicKey: bytesToB64url(m.peerId.publicKey.raw) };
472
+ }
473
+ return peers;
474
+ }
475
+ insertTransforms(blockId, collectionId) {
476
+ const block = { header: { id: blockId, type: "test", collectionId: collectionId } };
477
+ return { inserts: { [blockId]: block }, updates: {}, deletes: [] };
478
+ }
479
+ /**
480
+ * Run the originated notifications through the real forwarder receive path (populating the tail replay
481
+ * ring + rolling checkpoint + dedupe), then fan each out to every current-tail subscriber via its real
482
+ * per-subscriber bounded queue. A subscriber with `autoDrain` drains immediately; a slow subscriber's
483
+ * queue accumulates (drop-oldest under pressure) until it is woken.
484
+ */
485
+ async drainFanOut(c) {
486
+ const notifs = c.emitQueue.splice(0, c.emitQueue.length);
487
+ const tailB64 = bytesToB64url(c.tailId);
488
+ for (const n of notifs) {
489
+ // Forwarder receive: verify (real) → dedupe → append to the replay ring (and roll the checkpoint).
490
+ await c.forwarder.receive(n, this.vtime);
491
+ for (const s of c.subscribers) {
492
+ if (s.attachedTailB64 !== tailB64 || s.asleep) {
493
+ continue;
494
+ }
495
+ c.pushState.perSubscriberQueue.enqueue(s.subId, n);
496
+ if (s.autoDrain) {
497
+ await this.drainSubscriber(s);
498
+ }
499
+ }
500
+ }
501
+ }
502
+ /** Drain a subscriber's bounded queue (FIFO) through the real manager delivery path. */
503
+ async drainSubscriber(s) {
504
+ const c = this.collection(s.collectionName);
505
+ const queue = c.pushState.perSubscriberQueue.peekQueue(s.subId);
506
+ if (queue === undefined) {
507
+ return;
508
+ }
509
+ for (const n of queue.drain()) {
510
+ await s.manager.onNotification(n);
511
+ }
512
+ }
513
+ /** Wake a slow subscriber: drain its accumulated (drop-oldest) queue through the delivery path. */
514
+ async wakeSubscriber(s) {
515
+ await this.drainSubscriber(s);
516
+ }
517
+ /** Put a subscriber to sleep: fan-out skips it (its tail replay buffer still fills) until it resumes. */
518
+ sleepSubscriber(s) {
519
+ s.asleep = true;
520
+ }
521
+ /** The drop counter on a subscriber's bounded queue (drop-oldest under backpressure). */
522
+ droppedFor(s) {
523
+ const c = this.collection(s.collectionName);
524
+ return c.pushState.perSubscriberQueue.peekQueue(s.subId)?.dropped ?? 0;
525
+ }
526
+ /** Drive the manager's real `resume()` (one `ResumeV1` → `serveResume` → `applyResumeReply`). */
527
+ async resume(s) {
528
+ s.asleep = false;
529
+ return s.manager.resume();
530
+ }
531
+ /**
532
+ * The re-registration move the scheduler fires on a rotation notice: re-attach the subscriber under the
533
+ * rotated tail so subsequent fan-out targets it. The production factory builds a *fresh* manager under
534
+ * `plan.newTopicId`; the harness keeps the same manager (its `lastRevision` is already contiguous and its
535
+ * `rotationHandledFor` guard dedupes the new-tail deliveries' re-detection), so re-attaching the existing
536
+ * subscriber is the equivalent move with no gap.
537
+ */
538
+ reAttachToNewTail(s, plan) {
539
+ const c = this.collection(s.collectionName);
540
+ s.attachedTailB64 = bytesToB64url(plan.newTailId);
541
+ // Defensive: the plan's successor must be the collection's current tail (single-rotation harness model).
542
+ if (s.attachedTailB64 !== bytesToB64url(c.tailId)) {
543
+ s.attachedTailB64 = bytesToB64url(c.tailId);
544
+ }
545
+ }
546
+ /**
547
+ * Rotate a collection's tail: advance `tailId` → new `topicId`/coord/cohort, re-establish the new cohort
548
+ * (re-seed willingness so a post-rotation subscribe can register), re-cache the tail cert, rebuild the tail
549
+ * `PushState`, fold the outgoing ring into a handoff checkpoint onto the new tail, and plan the jittered
550
+ * re-registration wave bounded by `cap_promote_fast`. Async because re-forming the new cohort is (the real
551
+ * willingness convergence). With `{ autoReattach: false }` it instead models the running node's
552
+ * `markRotated` (drain gate) so subscribers move via the recover-redirect path. Models §Tail rotation steps 2–5.
553
+ */
554
+ async rotateTail(collection, opts = {}) {
555
+ const c = this.collection(collection);
556
+ const autoReattach = opts.autoReattach ?? true;
557
+ const rotationRevision = c.rev;
558
+ // Pin the rotated tail's routed primary to a Core node too (Edge nodes decline a T3 cold-start); a
559
+ // no-op for the all-Core rotation suites, matching the initial-tail pinning in `registerCollection`.
560
+ const newTailId = this.pinTailToCore(`${c.name}:tail-${rotationRevision}`);
561
+ const newTailIdB64 = bytesToB64url(newTailId);
562
+ const oldTailB64 = bytesToB64url(c.tailId);
563
+ // Buffer-to-checkpoint handoff — the ONLY state migrated across a rotation (§Tail rotation step 5).
564
+ const handoff = buildRotationHandoffCheckpoint(c.pushState, { rotationRevision });
565
+ // Active subscribers re-register under the new tree, carrying their lastRevision (continuity).
566
+ const live = c.subscribers.filter((s) => s.attachedTailB64 === oldTailB64);
567
+ const jitter = createRejoinJitter({ capPromote: DEFAULT_CAP_PROMOTE_FAST, random: this.deterministicRandom() });
568
+ const plans = planReRegistrationWave({
569
+ hint: { newTailId: newTailIdB64 },
570
+ subscribers: live.map((s) => ({ lastRevision: s.manager.lastRevision })),
571
+ now: this.vtime,
572
+ jitter,
573
+ });
574
+ // Roll the topic to the new tail.
575
+ c.tailId = newTailId;
576
+ c.topicId = reactivityTopicId(newTailId);
577
+ c.coord0 = addressing.coord0(c.topicId);
578
+ c.tailCohort = this.cohortMembersAround(c.coord0);
579
+ // Re-establish the new tail's cohort (re-seed willingness) so a subscriber can register under it after the
580
+ // rotation — the new tree forming, modeled deterministically (the real cohort-topic willingness convergence).
581
+ c.setup = await setupTopic(this.mesh, c.topicId);
582
+ this.cacheTailCert(c);
583
+ c.pushState = this.makePushState(c.collectionIdB64, c.topicId, c.tailId, c.w, c.wCheckpoint, c.queueMax, c.deltaMaxBytes);
584
+ c.forwarder = createReactivityForwarder({ state: c.pushState, verifier: this.notificationVerifierFor(c.originationService) });
585
+ if (handoff !== undefined) {
586
+ applyRotationHandoff(c.pushState, handoff);
587
+ }
588
+ if (autoReattach) {
589
+ // Re-attach the live subscribers under the new tail so subsequent fan-out targets them (the
590
+ // continuity/handoff model — subscribers migrate directly, no redirect involved).
591
+ for (const s of live) {
592
+ s.attachedTailB64 = newTailIdB64;
593
+ }
594
+ }
595
+ else {
596
+ // Transport-driven: model `markRotated` by arming the outgoing tail's drain gate. Live subscribers
597
+ // stay on the old tail until each resumes, gets the `kind:"rotated"` redirect, and re-attaches via its
598
+ // own onRotation → scheduler → reRegister path (§Tail rotation step 2–3).
599
+ c.rotationGate = new TailDrainGate({ rotatedAt: this.vtime, newTailId: newTailIdB64, effectiveAtRevision: rotationRevision + 1 });
600
+ c.rotatedFromTailB64 = oldTailB64;
601
+ }
602
+ return {
603
+ newTailId,
604
+ newTailIdB64,
605
+ rotationRevision,
606
+ plans,
607
+ peakWindowArrivals: this.peakWindowArrivals(plans),
608
+ ...(handoff?.checkpoint === undefined ? {} : { handoff: handoff.checkpoint }),
609
+ };
610
+ }
611
+ /** Peak re-registration arrivals in any `T_rejoin_jitter`-long window of a planned wave. */
612
+ peakWindowArrivals(plans) {
613
+ if (plans.length === 0) {
614
+ return 0;
615
+ }
616
+ const fireAts = plans.map((p) => p.fireAt).sort((a, b) => a - b);
617
+ const window = T_REJOIN_JITTER_MS; // the same jitter span `createRejoinJitter` spreads the wave over
618
+ let peak = 0;
619
+ for (let i = 0; i < fireAts.length; i++) {
620
+ let j = i;
621
+ while (j < fireAts.length && fireAts[j] - fireAts[i] < window) {
622
+ j++;
623
+ }
624
+ peak = Math.max(peak, j - i);
625
+ }
626
+ return peak;
627
+ }
628
+ /** A deterministic pseudo-random in [0,1) so the jitter wave is reproducible across runs. */
629
+ deterministicRandom() {
630
+ let seed = 0x2545f491;
631
+ return () => {
632
+ seed = (seed * 1103515245 + 12345) & 0x7fffffff;
633
+ return seed / 0x7fffffff;
634
+ };
635
+ }
636
+ /**
637
+ * The tail cohort `PushState` for a collection (its live replay ring / checkpoint / backpressure). Exposed
638
+ * so a suite can assert the replay/dedupe/partition state directly.
639
+ */
640
+ pushStateOf(collection) {
641
+ return this.collection(collection).pushState;
642
+ }
643
+ /** A real forwarder receive over the tail `PushState` — used to model a forwarder-hop / partition replay. */
644
+ async forwarderReceive(collection, n) {
645
+ const c = this.collection(collection);
646
+ return c.forwarder.receive(n, this.vtime);
647
+ }
648
+ async stop() {
649
+ // Tear down every subscriber's re-registration scheduler (drops pending virtual timers + the idempotence
650
+ // ledger) before stopping the mesh.
651
+ for (const c of this.collections.values()) {
652
+ for (const s of c.subscribers) {
653
+ s.scheduler?.stop();
654
+ }
655
+ }
656
+ await this.mesh.stop();
657
+ }
658
+ }
659
+ /** Build and start a reactivity integration mesh. */
660
+ export async function buildReactivityMesh(opts = {}) {
661
+ return ReactivityMesh.build(opts);
662
+ }
663
+ //# sourceMappingURL=reactivity-mesh-harness.js.map