@optimystic/db-core 0.14.0 → 0.16.2

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 (301) hide show
  1. package/dist/src/blocks/block-store.d.ts +18 -1
  2. package/dist/src/blocks/block-store.d.ts.map +1 -1
  3. package/dist/src/blocks/helpers.d.ts +2 -2
  4. package/dist/src/blocks/helpers.d.ts.map +1 -1
  5. package/dist/src/blocks/helpers.js +2 -2
  6. package/dist/src/blocks/helpers.js.map +1 -1
  7. package/dist/src/btree/btree.d.ts +27 -6
  8. package/dist/src/btree/btree.d.ts.map +1 -1
  9. package/dist/src/btree/btree.js +104 -61
  10. package/dist/src/btree/btree.js.map +1 -1
  11. package/dist/src/chain/chain.d.ts.map +1 -1
  12. package/dist/src/chain/chain.js +11 -1
  13. package/dist/src/chain/chain.js.map +1 -1
  14. package/dist/src/cluster/index.d.ts +1 -0
  15. package/dist/src/cluster/index.d.ts.map +1 -1
  16. package/dist/src/cluster/index.js +1 -0
  17. package/dist/src/cluster/index.js.map +1 -1
  18. package/dist/src/cluster/membership.d.ts +49 -0
  19. package/dist/src/cluster/membership.d.ts.map +1 -0
  20. package/dist/src/cluster/membership.js +75 -0
  21. package/dist/src/cluster/membership.js.map +1 -0
  22. package/dist/src/cluster/structs.d.ts +50 -1
  23. package/dist/src/cluster/structs.d.ts.map +1 -1
  24. package/dist/src/cluster/structs.js +9 -1
  25. package/dist/src/cluster/structs.js.map +1 -1
  26. package/dist/src/cohort-topic/addressing.d.ts +8 -2
  27. package/dist/src/cohort-topic/addressing.d.ts.map +1 -1
  28. package/dist/src/cohort-topic/addressing.js +11 -3
  29. package/dist/src/cohort-topic/addressing.js.map +1 -1
  30. package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts +13 -7
  31. package/dist/src/cohort-topic/antidos/bootstrap-evidence.d.ts.map +1 -1
  32. package/dist/src/cohort-topic/antidos/bootstrap-evidence.js +17 -6
  33. package/dist/src/cohort-topic/antidos/bootstrap-evidence.js.map +1 -1
  34. package/dist/src/cohort-topic/antidos/replay-guard.d.ts +17 -0
  35. package/dist/src/cohort-topic/antidos/replay-guard.d.ts.map +1 -1
  36. package/dist/src/cohort-topic/antidos/replay-guard.js +33 -0
  37. package/dist/src/cohort-topic/antidos/replay-guard.js.map +1 -1
  38. package/dist/src/cohort-topic/antidos/topic-budget.d.ts +7 -0
  39. package/dist/src/cohort-topic/antidos/topic-budget.d.ts.map +1 -1
  40. package/dist/src/cohort-topic/antidos/topic-budget.js +8 -2
  41. package/dist/src/cohort-topic/antidos/topic-budget.js.map +1 -1
  42. package/dist/src/cohort-topic/coldstart.d.ts +34 -14
  43. package/dist/src/cohort-topic/coldstart.d.ts.map +1 -1
  44. package/dist/src/cohort-topic/coldstart.js +25 -11
  45. package/dist/src/cohort-topic/coldstart.js.map +1 -1
  46. package/dist/src/cohort-topic/gossip/bus.d.ts +12 -1
  47. package/dist/src/cohort-topic/gossip/bus.d.ts.map +1 -1
  48. package/dist/src/cohort-topic/gossip/bus.js +27 -1
  49. package/dist/src/cohort-topic/gossip/bus.js.map +1 -1
  50. package/dist/src/cohort-topic/gossip/view.d.ts +8 -0
  51. package/dist/src/cohort-topic/gossip/view.d.ts.map +1 -1
  52. package/dist/src/cohort-topic/gossip/view.js +17 -1
  53. package/dist/src/cohort-topic/gossip/view.js.map +1 -1
  54. package/dist/src/cohort-topic/member-engine.d.ts +10 -3
  55. package/dist/src/cohort-topic/member-engine.d.ts.map +1 -1
  56. package/dist/src/cohort-topic/member-engine.js +29 -14
  57. package/dist/src/cohort-topic/member-engine.js.map +1 -1
  58. package/dist/src/cohort-topic/membership/publisher.d.ts +14 -7
  59. package/dist/src/cohort-topic/membership/publisher.d.ts.map +1 -1
  60. package/dist/src/cohort-topic/membership/publisher.js +15 -27
  61. package/dist/src/cohort-topic/membership/publisher.js.map +1 -1
  62. package/dist/src/cohort-topic/membership/verifier.d.ts +35 -0
  63. package/dist/src/cohort-topic/membership/verifier.d.ts.map +1 -1
  64. package/dist/src/cohort-topic/membership/verifier.js +88 -3
  65. package/dist/src/cohort-topic/membership/verifier.js.map +1 -1
  66. package/dist/src/cohort-topic/promotion.d.ts +7 -0
  67. package/dist/src/cohort-topic/promotion.d.ts.map +1 -1
  68. package/dist/src/cohort-topic/promotion.js +16 -2
  69. package/dist/src/cohort-topic/promotion.js.map +1 -1
  70. package/dist/src/cohort-topic/registration/renewal.d.ts +18 -0
  71. package/dist/src/cohort-topic/registration/renewal.d.ts.map +1 -1
  72. package/dist/src/cohort-topic/registration/renewal.js +54 -0
  73. package/dist/src/cohort-topic/registration/renewal.js.map +1 -1
  74. package/dist/src/cohort-topic/registration/types.d.ts +12 -0
  75. package/dist/src/cohort-topic/registration/types.d.ts.map +1 -1
  76. package/dist/src/cohort-topic/registration/types.js +14 -0
  77. package/dist/src/cohort-topic/registration/types.js.map +1 -1
  78. package/dist/src/cohort-topic/service.d.ts +4 -3
  79. package/dist/src/cohort-topic/service.d.ts.map +1 -1
  80. package/dist/src/cohort-topic/service.js +38 -12
  81. package/dist/src/cohort-topic/service.js.map +1 -1
  82. package/dist/src/cohort-topic/sig/payloads.d.ts +23 -7
  83. package/dist/src/cohort-topic/sig/payloads.d.ts.map +1 -1
  84. package/dist/src/cohort-topic/sig/payloads.js +22 -6
  85. package/dist/src/cohort-topic/sig/payloads.js.map +1 -1
  86. package/dist/src/cohort-topic/traffic.d.ts +6 -0
  87. package/dist/src/cohort-topic/traffic.d.ts.map +1 -1
  88. package/dist/src/cohort-topic/traffic.js +16 -1
  89. package/dist/src/cohort-topic/traffic.js.map +1 -1
  90. package/dist/src/cohort-topic/walk.d.ts +22 -7
  91. package/dist/src/cohort-topic/walk.d.ts.map +1 -1
  92. package/dist/src/cohort-topic/walk.js +78 -18
  93. package/dist/src/cohort-topic/walk.js.map +1 -1
  94. package/dist/src/cohort-topic/wire/codec.d.ts +3 -1
  95. package/dist/src/cohort-topic/wire/codec.d.ts.map +1 -1
  96. package/dist/src/cohort-topic/wire/codec.js +7 -1
  97. package/dist/src/cohort-topic/wire/codec.js.map +1 -1
  98. package/dist/src/cohort-topic/wire/index.d.ts +1 -1
  99. package/dist/src/cohort-topic/wire/index.d.ts.map +1 -1
  100. package/dist/src/cohort-topic/wire/index.js +1 -1
  101. package/dist/src/cohort-topic/wire/index.js.map +1 -1
  102. package/dist/src/cohort-topic/wire/payloads.d.ts +5 -4
  103. package/dist/src/cohort-topic/wire/payloads.d.ts.map +1 -1
  104. package/dist/src/cohort-topic/wire/payloads.js +14 -5
  105. package/dist/src/cohort-topic/wire/payloads.js.map +1 -1
  106. package/dist/src/cohort-topic/wire/primitives.d.ts +65 -0
  107. package/dist/src/cohort-topic/wire/primitives.d.ts.map +1 -0
  108. package/dist/src/cohort-topic/wire/primitives.js +173 -0
  109. package/dist/src/cohort-topic/wire/primitives.js.map +1 -0
  110. package/dist/src/cohort-topic/wire/types.d.ts +132 -5
  111. package/dist/src/cohort-topic/wire/types.d.ts.map +1 -1
  112. package/dist/src/cohort-topic/wire/validate.d.ts +15 -5
  113. package/dist/src/cohort-topic/wire/validate.d.ts.map +1 -1
  114. package/dist/src/cohort-topic/wire/validate.js +146 -123
  115. package/dist/src/cohort-topic/wire/validate.js.map +1 -1
  116. package/dist/src/collection/collection.d.ts +23 -12
  117. package/dist/src/collection/collection.d.ts.map +1 -1
  118. package/dist/src/collection/collection.js +111 -35
  119. package/dist/src/collection/collection.js.map +1 -1
  120. package/dist/src/collection/struct.d.ts +37 -2
  121. package/dist/src/collection/struct.d.ts.map +1 -1
  122. package/dist/src/collection/struct.js +16 -1
  123. package/dist/src/collection/struct.js.map +1 -1
  124. package/dist/src/collections/tree/tree.d.ts +18 -1
  125. package/dist/src/collections/tree/tree.d.ts.map +1 -1
  126. package/dist/src/collections/tree/tree.js +36 -6
  127. package/dist/src/collections/tree/tree.js.map +1 -1
  128. package/dist/src/index.d.ts +1 -0
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +1 -0
  131. package/dist/src/index.js.map +1 -1
  132. package/dist/src/log/log.d.ts +26 -3
  133. package/dist/src/log/log.d.ts.map +1 -1
  134. package/dist/src/log/log.js +34 -7
  135. package/dist/src/log/log.js.map +1 -1
  136. package/dist/src/log/struct.d.ts +5 -4
  137. package/dist/src/log/struct.d.ts.map +1 -1
  138. package/dist/src/log/struct.js.map +1 -1
  139. package/dist/src/matchmaking/wire.d.ts +2 -1
  140. package/dist/src/matchmaking/wire.d.ts.map +1 -1
  141. package/dist/src/matchmaking/wire.js +13 -92
  142. package/dist/src/matchmaking/wire.js.map +1 -1
  143. package/dist/src/network/i-key-network.d.ts +9 -0
  144. package/dist/src/network/i-key-network.d.ts.map +1 -1
  145. package/dist/src/network/i-repo.d.ts +8 -0
  146. package/dist/src/network/i-repo.d.ts.map +1 -1
  147. package/dist/src/network/struct.d.ts +10 -0
  148. package/dist/src/network/struct.d.ts.map +1 -1
  149. package/dist/src/reactivity/push-state.d.ts.map +1 -1
  150. package/dist/src/reactivity/push-state.js +4 -33
  151. package/dist/src/reactivity/push-state.js.map +1 -1
  152. package/dist/src/reactivity/wire-validate.d.ts +7 -22
  153. package/dist/src/reactivity/wire-validate.d.ts.map +1 -1
  154. package/dist/src/reactivity/wire-validate.js +7 -73
  155. package/dist/src/reactivity/wire-validate.js.map +1 -1
  156. package/dist/src/reactivity/wire.js +3 -3
  157. package/dist/src/reactivity/wire.js.map +1 -1
  158. package/dist/src/testing/async-wait.d.ts +23 -0
  159. package/dist/src/testing/async-wait.d.ts.map +1 -0
  160. package/dist/src/testing/async-wait.js +42 -0
  161. package/dist/src/testing/async-wait.js.map +1 -0
  162. package/dist/src/testing/index.d.ts +3 -0
  163. package/dist/src/testing/index.d.ts.map +1 -0
  164. package/dist/src/testing/index.js +3 -0
  165. package/dist/src/testing/index.js.map +1 -0
  166. package/dist/src/testing/test-transactor.d.ts +44 -0
  167. package/dist/src/testing/test-transactor.d.ts.map +1 -0
  168. package/dist/src/testing/test-transactor.js +423 -0
  169. package/dist/src/testing/test-transactor.js.map +1 -0
  170. package/dist/src/transaction/actions-engine.d.ts +6 -3
  171. package/dist/src/transaction/actions-engine.d.ts.map +1 -1
  172. package/dist/src/transaction/actions-engine.js +8 -7
  173. package/dist/src/transaction/actions-engine.js.map +1 -1
  174. package/dist/src/transaction/coordinator.d.ts +43 -22
  175. package/dist/src/transaction/coordinator.d.ts.map +1 -1
  176. package/dist/src/transaction/coordinator.js +443 -178
  177. package/dist/src/transaction/coordinator.js.map +1 -1
  178. package/dist/src/transaction/errors.d.ts +82 -0
  179. package/dist/src/transaction/errors.d.ts.map +1 -0
  180. package/dist/src/transaction/errors.js +93 -0
  181. package/dist/src/transaction/errors.js.map +1 -0
  182. package/dist/src/transaction/index.d.ts +6 -3
  183. package/dist/src/transaction/index.d.ts.map +1 -1
  184. package/dist/src/transaction/index.js +4 -2
  185. package/dist/src/transaction/index.js.map +1 -1
  186. package/dist/src/transaction/operations-hash.d.ts +119 -0
  187. package/dist/src/transaction/operations-hash.d.ts.map +1 -0
  188. package/dist/src/transaction/operations-hash.js +180 -0
  189. package/dist/src/transaction/operations-hash.js.map +1 -0
  190. package/dist/src/transaction/read-dependency-collector.d.ts +43 -0
  191. package/dist/src/transaction/read-dependency-collector.d.ts.map +1 -0
  192. package/dist/src/transaction/read-dependency-collector.js +69 -0
  193. package/dist/src/transaction/read-dependency-collector.js.map +1 -0
  194. package/dist/src/transaction/session.d.ts +12 -3
  195. package/dist/src/transaction/session.d.ts.map +1 -1
  196. package/dist/src/transaction/session.js +35 -9
  197. package/dist/src/transaction/session.js.map +1 -1
  198. package/dist/src/transaction/transaction.d.ts +162 -1
  199. package/dist/src/transaction/transaction.d.ts.map +1 -1
  200. package/dist/src/transaction/transaction.js +85 -3
  201. package/dist/src/transaction/transaction.js.map +1 -1
  202. package/dist/src/transaction/validator.d.ts +3 -11
  203. package/dist/src/transaction/validator.d.ts.map +1 -1
  204. package/dist/src/transaction/validator.js +69 -28
  205. package/dist/src/transaction/validator.js.map +1 -1
  206. package/dist/src/transactor/network-transactor.d.ts +35 -0
  207. package/dist/src/transactor/network-transactor.d.ts.map +1 -1
  208. package/dist/src/transactor/network-transactor.js +146 -40
  209. package/dist/src/transactor/network-transactor.js.map +1 -1
  210. package/dist/src/transactor/transactor-source.d.ts +19 -5
  211. package/dist/src/transactor/transactor-source.d.ts.map +1 -1
  212. package/dist/src/transactor/transactor-source.js +43 -11
  213. package/dist/src/transactor/transactor-source.js.map +1 -1
  214. package/dist/src/transform/atomic-proxy.d.ts +35 -5
  215. package/dist/src/transform/atomic-proxy.d.ts.map +1 -1
  216. package/dist/src/transform/atomic-proxy.js +39 -7
  217. package/dist/src/transform/atomic-proxy.js.map +1 -1
  218. package/dist/src/transform/cache-source.d.ts +36 -5
  219. package/dist/src/transform/cache-source.d.ts.map +1 -1
  220. package/dist/src/transform/cache-source.js +84 -8
  221. package/dist/src/transform/cache-source.js.map +1 -1
  222. package/dist/src/transform/helpers.d.ts.map +1 -1
  223. package/dist/src/transform/helpers.js +8 -2
  224. package/dist/src/transform/helpers.js.map +1 -1
  225. package/dist/src/transform/tracker.d.ts +17 -2
  226. package/dist/src/transform/tracker.d.ts.map +1 -1
  227. package/dist/src/transform/tracker.js +66 -11
  228. package/dist/src/transform/tracker.js.map +1 -1
  229. package/dist/src/utility/backoff.d.ts +48 -0
  230. package/dist/src/utility/backoff.d.ts.map +1 -0
  231. package/dist/src/utility/backoff.js +60 -0
  232. package/dist/src/utility/backoff.js.map +1 -0
  233. package/dist/src/utility/pending.d.ts +1 -0
  234. package/dist/src/utility/pending.d.ts.map +1 -1
  235. package/dist/src/utility/pending.js +3 -1
  236. package/dist/src/utility/pending.js.map +1 -1
  237. package/package.json +4 -4
  238. package/src/blocks/block-store.ts +19 -1
  239. package/src/blocks/helpers.ts +3 -3
  240. package/src/btree/btree.ts +108 -63
  241. package/src/chain/chain.ts +11 -1
  242. package/src/cluster/index.ts +1 -0
  243. package/src/cluster/membership.ts +85 -0
  244. package/src/cluster/structs.ts +51 -1
  245. package/src/cohort-topic/addressing.ts +12 -4
  246. package/src/cohort-topic/antidos/bootstrap-evidence.ts +21 -9
  247. package/src/cohort-topic/antidos/replay-guard.ts +37 -0
  248. package/src/cohort-topic/antidos/topic-budget.ts +17 -2
  249. package/src/cohort-topic/coldstart.ts +48 -15
  250. package/src/cohort-topic/gossip/bus.ts +39 -2
  251. package/src/cohort-topic/gossip/view.ts +26 -1
  252. package/src/cohort-topic/member-engine.ts +36 -15
  253. package/src/cohort-topic/membership/publisher.ts +25 -33
  254. package/src/cohort-topic/membership/verifier.ts +119 -3
  255. package/src/cohort-topic/promotion.ts +23 -2
  256. package/src/cohort-topic/registration/renewal.ts +73 -1
  257. package/src/cohort-topic/registration/types.ts +15 -0
  258. package/src/cohort-topic/service.ts +41 -15
  259. package/src/cohort-topic/sig/payloads.ts +29 -9
  260. package/src/cohort-topic/traffic.ts +23 -1
  261. package/src/cohort-topic/walk.ts +95 -24
  262. package/src/cohort-topic/wire/codec.ts +12 -0
  263. package/src/cohort-topic/wire/index.ts +2 -0
  264. package/src/cohort-topic/wire/payloads.ts +14 -5
  265. package/src/cohort-topic/wire/primitives.ts +188 -0
  266. package/src/cohort-topic/wire/types.ts +136 -4
  267. package/src/cohort-topic/wire/validate.ts +168 -134
  268. package/src/collection/collection.ts +123 -40
  269. package/src/collection/struct.ts +44 -2
  270. package/src/collections/tree/tree.ts +42 -5
  271. package/src/index.ts +1 -0
  272. package/src/log/log.ts +37 -9
  273. package/src/log/struct.ts +5 -4
  274. package/src/matchmaking/wire.ts +25 -102
  275. package/src/network/i-key-network.ts +10 -0
  276. package/src/network/i-repo.ts +8 -0
  277. package/src/network/struct.ts +10 -0
  278. package/src/reactivity/push-state.ts +4 -37
  279. package/src/reactivity/wire-validate.ts +7 -81
  280. package/src/reactivity/wire.ts +3 -3
  281. package/src/testing/async-wait.ts +65 -0
  282. package/src/testing/index.ts +2 -0
  283. package/src/testing/test-transactor.ts +468 -0
  284. package/src/transaction/actions-engine.ts +8 -6
  285. package/src/transaction/coordinator.ts +498 -229
  286. package/src/transaction/errors.ts +91 -0
  287. package/src/transaction/index.ts +23 -3
  288. package/src/transaction/operations-hash.ts +196 -0
  289. package/src/transaction/read-dependency-collector.ts +78 -0
  290. package/src/transaction/session.ts +37 -9
  291. package/src/transaction/transaction.ts +211 -4
  292. package/src/transaction/validator.ts +73 -48
  293. package/src/transactor/network-transactor.ts +164 -49
  294. package/src/transactor/transactor-source.ts +47 -12
  295. package/src/transform/atomic-proxy.ts +92 -49
  296. package/src/transform/cache-source.ts +87 -9
  297. package/src/transform/helpers.ts +8 -2
  298. package/src/transform/tracker.ts +69 -11
  299. package/src/utility/backoff.ts +95 -0
  300. package/src/utility/pending.ts +3 -1
  301. package/src/transaction/context.ts +0 -103
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Cohort-topic substrate — bootstrap evidence policy (anti-DoS).
3
3
  *
4
- * Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold root that accepts a
5
- * `bootstrap: true` registration must demand evidence, so a cold topic cannot be instantiated for
6
- * free by an attacker. The accepted evidence is **tier-dependent**:
4
+ * Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold cohort that accepts a cold-start
5
+ * registration must demand evidence, so a cold topic cannot be instantiated for free by an attacker.
6
+ * **A follow-on cold-start (`followOn: true`) is gated identically to a root bootstrap
7
+ * (`bootstrap: true`)**: the cold tier-`(d+1)` child a `Promoted` redirect points at is participant-
8
+ * asserted (the child cannot infer the redirect — the routing key decorrelates tiers and it holds no
9
+ * breadcrumb of where the participant came from), so its safety cannot rest on provenance; it rests on
10
+ * this gate paying the same anti-abuse cost the root already pays. The accepted evidence is
11
+ * **tier-dependent**:
7
12
  *
8
13
  * - **T0 / T1** — generally no proof-of-work, because these tiers correspond to committed work; a
9
14
  * *signed reference to a committed parent topic that does exist* is sufficient.
@@ -18,7 +23,7 @@
18
23
  * `bootstrapEvidence` field — a versioned `BootstrapEvidenceEnvelopeV1`
19
24
  * (`./bootstrap-evidence-envelope.js`), parsed crypto-free here and checked by the injected verifiers;
20
25
  * NOT the opaque `appPayload` slot (which the cohort copies verbatim into the registration's appState).
21
- * A registration that is not a bootstrap needs no evidence and is admitted.
26
+ * A registration that is neither a bootstrap nor a follow-on cold-start needs no evidence and is admitted.
22
27
  */
23
28
  import type { RegisterV1 } from "../wire/types.js";
24
29
  /** Tiers at/below this need no proof-of-work — a signed parent reference suffices (T0/T1). */
@@ -39,9 +44,10 @@ export interface BootstrapEvidenceConfig {
39
44
  /** Tier-dependent bootstrap-evidence gate for cold-root registrations. */
40
45
  export interface BootstrapEvidence {
41
46
  /**
42
- * Whether `reg` carries acceptable bootstrap evidence for `tier`. A non-bootstrap registration is
43
- * always acceptable (nothing to prove). For a bootstrap: T0/T1 require a signed parent reference;
44
- * T2/T3 accept proof-of-work OR a reputation signature OR a signed parent reference.
47
+ * Whether `reg` carries acceptable cold-start evidence for `tier`. A registration that is neither a
48
+ * `bootstrap` nor a `followOn` is always acceptable (nothing to prove). For a cold-start (either flag):
49
+ * T0/T1 require a signed parent reference; T2/T3 accept proof-of-work OR a reputation signature OR a
50
+ * signed parent reference.
45
51
  */
46
52
  verify(reg: RegisterV1, tier: number): boolean;
47
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-evidence.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,8FAA8F;AAC9F,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,MAAM,WAAW,qBAAqB;IACrC,gGAAgG;IAChG,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IACzC,kGAAkG;IAClG,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IAChD,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IACrD,MAAM,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACvC,mGAAmG;IACnG,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IACjC;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/C;AAoCD,uGAAuG;AACvG,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,qBAA0B,GAAG,iBAAiB,CAE3F"}
1
+ {"version":3,"file":"bootstrap-evidence.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,8FAA8F;AAC9F,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,MAAM,WAAW,qBAAqB;IACrC,gGAAgG;IAChG,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IACzC,kGAAkG;IAClG,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IAChD,+GAA+G;IAC/G,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC;IACrD,MAAM,CAAC,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACvC,mGAAmG;IACnG,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IACjC;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/C;AA0CD,uGAAuG;AACvG,wBAAgB,uBAAuB,CAAC,IAAI,GAAE,qBAA0B,GAAG,iBAAiB,CAE3F"}
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Cohort-topic substrate — bootstrap evidence policy (anti-DoS).
3
3
  *
4
- * Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold root that accepts a
5
- * `bootstrap: true` registration must demand evidence, so a cold topic cannot be instantiated for
6
- * free by an attacker. The accepted evidence is **tier-dependent**:
4
+ * Transcribed from `docs/cohort-topic.md` §Anti-DoS bullet 4. A cold cohort that accepts a cold-start
5
+ * registration must demand evidence, so a cold topic cannot be instantiated for free by an attacker.
6
+ * **A follow-on cold-start (`followOn: true`) is gated identically to a root bootstrap
7
+ * (`bootstrap: true`)**: the cold tier-`(d+1)` child a `Promoted` redirect points at is participant-
8
+ * asserted (the child cannot infer the redirect — the routing key decorrelates tiers and it holds no
9
+ * breadcrumb of where the participant came from), so its safety cannot rest on provenance; it rests on
10
+ * this gate paying the same anti-abuse cost the root already pays. The accepted evidence is
11
+ * **tier-dependent**:
7
12
  *
8
13
  * - **T0 / T1** — generally no proof-of-work, because these tiers correspond to committed work; a
9
14
  * *signed reference to a committed parent topic that does exist* is sufficient.
@@ -18,7 +23,7 @@
18
23
  * `bootstrapEvidence` field — a versioned `BootstrapEvidenceEnvelopeV1`
19
24
  * (`./bootstrap-evidence-envelope.js`), parsed crypto-free here and checked by the injected verifiers;
20
25
  * NOT the opaque `appPayload` slot (which the cohort copies verbatim into the registration's appState).
21
- * A registration that is not a bootstrap needs no evidence and is admitted.
26
+ * A registration that is neither a bootstrap nor a follow-on cold-start needs no evidence and is admitted.
22
27
  */
23
28
  import { createLogger } from "../../logger.js";
24
29
  const log = createLogger("cohort-topic:antidos");
@@ -36,8 +41,14 @@ class TieredBootstrapEvidence {
36
41
  this.verifyParentReference = deps.verifyParentReference ?? (() => false);
37
42
  }
38
43
  verify(reg, tier) {
39
- if (reg.bootstrap !== true) {
40
- return true; // only a cold-root bootstrap must carry evidence
44
+ // A follow-on cold-start is gated identically to a root bootstrap both must carry evidence.
45
+ // NOTE: follow-on cold-start is PoW-gated exactly like a root bootstrap; if a spoofed, PoW-paid
46
+ // follow-on instantiation ever shows up as real abuse, upgrade to a parent-vouched redirect —
47
+ // echo the parent cohort's threshold-signed PromotionNoticeV1 on the follow-on and verify it
48
+ // against the parent's MembershipCertV1 (needs a RegisterReplyV1 field + admission-path membership
49
+ // verify). See docs/cohort-topic.md §Anti-DoS "follow-on hardening".
50
+ if (reg.bootstrap !== true && reg.followOn !== true) {
51
+ return true; // only a cold-start (bootstrap or follow-on) must carry evidence
41
52
  }
42
53
  if (tier <= this.maxNoPowTier) {
43
54
  // T0/T1: a signed reference to a committed parent topic is sufficient (no PoW expected).
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap-evidence.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAEjD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AA2BzC,MAAM,uBAAuB;IACX,YAAY,CAAS;IACrB,SAAS,CAA+B;IACxC,gBAAgB,CAA+B;IAC/C,qBAAqB,CAA+B;IAErE,YAAY,OAA8B,EAAE;QAC3C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,IAAI,uBAAuB,CAAC;QACzE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;QACxE,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,CAAC,GAAe,EAAE,IAAY;QACnC,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,CAAC,iDAAiD;QAC/D,CAAC;QACD,IAAI,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/B,yFAAyF;YACzF,MAAM,EAAE,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,CAAC,EAAE,EAAE,CAAC;gBACT,GAAG,CAAC,oEAAoE,EAAE,IAAI,CAAC,CAAC;YACjF,CAAC;YACD,OAAO,EAAE,CAAC;QACX,CAAC;QACD,+EAA+E;QAC/E,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAChG,IAAI,CAAC,EAAE,EAAE,CAAC;YACT,GAAG,CAAC,8EAA8E,EAAE,IAAI,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,EAAE,CAAC;IACX,CAAC;CACD;AAED,uGAAuG;AACvG,MAAM,UAAU,uBAAuB,CAAC,OAA8B,EAAE;IACvE,OAAO,IAAI,uBAAuB,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"bootstrap-evidence.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/bootstrap-evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAEjD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AA4BzC,MAAM,uBAAuB;IACX,YAAY,CAAS;IACrB,SAAS,CAA+B;IACxC,gBAAgB,CAA+B;IAC/C,qBAAqB,CAA+B;IAErE,YAAY,OAA8B,EAAE;QAC3C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,IAAI,uBAAuB,CAAC;QACzE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;QACxE,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC,qBAAqB,IAAI,CAAC,GAAY,EAAE,CAAC,KAAK,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,CAAC,GAAe,EAAE,IAAY;QACnC,8FAA8F;QAC9F,gGAAgG;QAChG,8FAA8F;QAC9F,6FAA6F;QAC7F,mGAAmG;QACnG,qEAAqE;QACrE,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC,CAAC,iEAAiE;QAC/E,CAAC;QACD,IAAI,IAAI,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/B,yFAAyF;YACzF,MAAM,EAAE,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,CAAC,EAAE,EAAE,CAAC;gBACT,GAAG,CAAC,oEAAoE,EAAE,IAAI,CAAC,CAAC;YACjF,CAAC;YACD,OAAO,EAAE,CAAC;QACX,CAAC;QACD,+EAA+E;QAC/E,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAChG,IAAI,CAAC,EAAE,EAAE,CAAC;YACT,GAAG,CAAC,8EAA8E,EAAE,IAAI,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,EAAE,CAAC;IACX,CAAC;CACD;AAED,uGAAuG;AACvG,MAAM,UAAU,uBAAuB,CAAC,OAA8B,EAAE;IACvE,OAAO,IAAI,uBAAuB,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC"}
@@ -11,16 +11,31 @@
11
11
  * Because a stale registration is rejected outright, the guard only needs to remember correlation ids
12
12
  * for one `maxAgeMs` window: an id older than that would be rejected on timestamp alone, so its record
13
13
  * can be pruned. Pruning runs on access, bounding memory to the live window's worth of registrations.
14
+ *
15
+ * On top of the age-based prune, the map carries a **hard LRU `maxKeys` cap** (mirroring the sibling
16
+ * {@link import("./rate-limiter.js").RegisterRateLimiter}) so a flood of genuinely-fresh, admitted
17
+ * correlationIds cannot grow `seen` without bound before the age prune fires. When a new id would
18
+ * exceed the cap, the **oldest-inserted** entries are evicted until within cap. That victim is the
19
+ * least-bad one: replay entries are inserted once and never refreshed, so `Map` insertion order tracks
20
+ * timestamp order — the oldest entry is the one nearest to aging out of the window and being pruned as
21
+ * stale anyway. Evicting it forgives at most that entry's remaining replay-protection window: a bounded,
22
+ * documented tradeoff (unlike the rate limiter's fully penalty-free eviction), and one that only
23
+ * triggers under a flood of admitted ids — which, in the register pipeline, must also have passed the
24
+ * signature, rate, and bootstrap gates before ever reaching this guard.
14
25
  */
15
26
  /** Default acceptance window: a registration timestamped older than this (relative to `now`) is stale. */
16
27
  export declare const DEFAULT_REPLAY_MAX_AGE_MS = 60000;
17
28
  /** Default tolerated forward clock skew: a timestamp this far past `now` is rejected as implausible. */
18
29
  export declare const DEFAULT_REPLAY_MAX_FUTURE_SKEW_MS = 5000;
30
+ /** Default hard cap on remembered correlationIds; the oldest-inserted are evicted beyond this. */
31
+ export declare const DEFAULT_REPLAY_GUARD_MAX_KEYS = 100000;
19
32
  export interface CorrelationReplayGuardConfig {
20
33
  /** A timestamp older than `now − maxAgeMs` is stale. Default {@link DEFAULT_REPLAY_MAX_AGE_MS}. */
21
34
  maxAgeMs?: number;
22
35
  /** A timestamp newer than `now + maxFutureSkewMs` is rejected. Default {@link DEFAULT_REPLAY_MAX_FUTURE_SKEW_MS}. */
23
36
  maxFutureSkewMs?: number;
37
+ /** Hard LRU cap on remembered correlationIds; oldest-inserted (≈ oldest-timestamp) evicted beyond this. Default {@link DEFAULT_REPLAY_GUARD_MAX_KEYS}. */
38
+ maxKeys?: number;
24
39
  }
25
40
  /** Freshness + anti-replay gate over registration `correlationId`s and timestamps. */
26
41
  export interface CorrelationReplayGuard {
@@ -31,6 +46,8 @@ export interface CorrelationReplayGuard {
31
46
  * id on first sight of a fresh registration.
32
47
  */
33
48
  accept(correlationId: Uint8Array, peerId: Uint8Array, timestamp: number, now: number): boolean;
49
+ /** Remembered correlationId count (test/diagnostic introspection). */
50
+ readonly size: number;
34
51
  }
35
52
  /** Build a {@link CorrelationReplayGuard} over the configured staleness window and forward skew. */
36
53
  export declare function createCorrelationReplayGuard(config?: CorrelationReplayGuardConfig): CorrelationReplayGuard;
@@ -1 +1 @@
1
- {"version":3,"file":"replay-guard.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,0GAA0G;AAC1G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAChD,wGAAwG;AACxG,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AAEvD,MAAM,WAAW,4BAA4B;IAC5C,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qHAAqH;IACrH,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,sFAAsF;AACtF,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/F;AAgED,oGAAoG;AACpG,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,4BAAiC,GAAG,sBAAsB,CAE9G"}
1
+ {"version":3,"file":"replay-guard.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAOH,0GAA0G;AAC1G,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAChD,wGAAwG;AACxG,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AACvD,kGAAkG;AAClG,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAErD,MAAM,WAAW,4BAA4B;IAC5C,mGAAmG;IACnG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qHAAqH;IACrH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0JAA0J;IAC1J,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,sFAAsF;AACtF,MAAM,WAAW,sBAAsB;IACtC;;;;;OAKG;IACH,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/F,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAoFD,oGAAoG;AACpG,wBAAgB,4BAA4B,CAAC,MAAM,GAAE,4BAAiC,GAAG,sBAAsB,CAE9G"}
@@ -11,6 +11,17 @@
11
11
  * Because a stale registration is rejected outright, the guard only needs to remember correlation ids
12
12
  * for one `maxAgeMs` window: an id older than that would be rejected on timestamp alone, so its record
13
13
  * can be pruned. Pruning runs on access, bounding memory to the live window's worth of registrations.
14
+ *
15
+ * On top of the age-based prune, the map carries a **hard LRU `maxKeys` cap** (mirroring the sibling
16
+ * {@link import("./rate-limiter.js").RegisterRateLimiter}) so a flood of genuinely-fresh, admitted
17
+ * correlationIds cannot grow `seen` without bound before the age prune fires. When a new id would
18
+ * exceed the cap, the **oldest-inserted** entries are evicted until within cap. That victim is the
19
+ * least-bad one: replay entries are inserted once and never refreshed, so `Map` insertion order tracks
20
+ * timestamp order — the oldest entry is the one nearest to aging out of the window and being pruned as
21
+ * stale anyway. Evicting it forgives at most that entry's remaining replay-protection window: a bounded,
22
+ * documented tradeoff (unlike the rate limiter's fully penalty-free eviction), and one that only
23
+ * triggers under a flood of admitted ids — which, in the register pipeline, must also have passed the
24
+ * signature, rate, and bootstrap gates before ever reaching this guard.
14
25
  */
15
26
  import { bytesKey } from "../registration/bytes.js";
16
27
  import { createLogger } from "../../logger.js";
@@ -19,10 +30,13 @@ const log = createLogger("cohort-topic:antidos");
19
30
  export const DEFAULT_REPLAY_MAX_AGE_MS = 60_000;
20
31
  /** Default tolerated forward clock skew: a timestamp this far past `now` is rejected as implausible. */
21
32
  export const DEFAULT_REPLAY_MAX_FUTURE_SKEW_MS = 5_000;
33
+ /** Default hard cap on remembered correlationIds; the oldest-inserted are evicted beyond this. */
34
+ export const DEFAULT_REPLAY_GUARD_MAX_KEYS = 100_000;
22
35
  class WindowedReplayGuard {
23
36
  seen = new Map();
24
37
  maxAgeMs;
25
38
  maxFutureSkewMs;
39
+ maxKeys;
26
40
  /** `now` of the last prune, so pruning amortizes rather than scanning on every call. */
27
41
  lastPruneAt = -Infinity;
28
42
  constructor(config = {}) {
@@ -34,6 +48,10 @@ class WindowedReplayGuard {
34
48
  if (!(this.maxFutureSkewMs >= 0)) {
35
49
  throw new RangeError(`maxFutureSkewMs must be >= 0, got ${this.maxFutureSkewMs}`);
36
50
  }
51
+ this.maxKeys = config.maxKeys ?? DEFAULT_REPLAY_GUARD_MAX_KEYS;
52
+ if (!Number.isInteger(this.maxKeys) || this.maxKeys <= 0) {
53
+ throw new RangeError(`maxKeys must be a positive integer, got ${this.maxKeys}`);
54
+ }
37
55
  }
38
56
  accept(correlationId, peerId, timestamp, now) {
39
57
  if (timestamp < now - this.maxAgeMs) {
@@ -53,6 +71,18 @@ class WindowedReplayGuard {
53
71
  log("replay-guard reject: replayed correlationId (first seen from peer=%s)", prior.peer);
54
72
  return false; // replay
55
73
  }
74
+ // New id: enforce the hard cap by evicting the oldest-inserted entries (oldest by `Map` insertion
75
+ // order) until this insertion stays within `maxKeys`. Since entries are inserted once and never
76
+ // refreshed, insertion order closely tracks timestamp order, so the victim is ≈ the oldest-timestamp
77
+ // — nearest to aging out as stale. (It is only ≈: ids from distinct peers can arrive out of
78
+ // timestamp order within the skew window, so a victim may retain slightly more window than a
79
+ // strictly-oldest pick. The forgiveness bound — at most one entry's remaining window — holds either way.)
80
+ while (this.seen.size >= this.maxKeys) {
81
+ const oldest = this.seen.keys().next().value;
82
+ if (oldest === undefined)
83
+ break;
84
+ this.seen.delete(oldest);
85
+ }
56
86
  this.seen.set(key, { timestamp, peer: bytesKey(peerId) });
57
87
  return true;
58
88
  }
@@ -70,6 +100,9 @@ class WindowedReplayGuard {
70
100
  }
71
101
  }
72
102
  }
103
+ get size() {
104
+ return this.seen.size;
105
+ }
73
106
  }
74
107
  /** Build a {@link CorrelationReplayGuard} over the configured staleness window and forward skew. */
75
108
  export function createCorrelationReplayGuard(config = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"replay-guard.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"replay-guard.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/replay-guard.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,13 @@ export declare const DEFAULT_TOPICS_MAX = 2048;
19
19
  export interface TopicBudgetConfig {
20
20
  /** Max topics with forwarder state. Default {@link DEFAULT_TOPICS_MAX}. */
21
21
  topicsMax?: number;
22
+ /**
23
+ * Called with the evicted topic's id just before its slot is freed in {@link TopicBudget.admit}, so
24
+ * the caller can tear down the now-unbacked forwarder + traffic window that outlives the slot. Fires
25
+ * only for a genuine eviction (a zero-participant victim), never for a plain admit, an already-resident
26
+ * re-admit, or a refusal. Absent → no teardown hook.
27
+ */
28
+ onEvict?: (topicId: Uint8Array) => void;
22
29
  }
23
30
  /** Per-cohort cap on the number of topics with forwarder state, with LRU eviction of cold topics. */
24
31
  export interface TopicBudget {
@@ -1 +1 @@
1
- {"version":3,"file":"topic-budget.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qGAAqG;AACrG,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,yDAAyD;IACzD,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IAClC;;;;OAIG;IACH,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1D,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAuFD,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,iBAAsB,GAAG,WAAW,CAE7E"}
1
+ {"version":3,"file":"topic-budget.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;CACxC;AAED,qGAAqG;AACrG,MAAM,WAAW,WAAW;IAC3B;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IACpC;;;OAGG;IACH,KAAK,CAAC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,yDAAyD;IACzD,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC;IAClC;;;;OAIG;IACH,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1D,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AA+FD,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,iBAAsB,GAAG,WAAW,CAE7E"}
@@ -22,12 +22,14 @@ export const DEFAULT_TOPICS_MAX = 2048;
22
22
  class LruTopicBudget {
23
23
  residents = new Map();
24
24
  topicsMax;
25
+ onEvict;
25
26
  seqCounter = 0;
26
27
  constructor(config = {}) {
27
28
  this.topicsMax = config.topicsMax ?? DEFAULT_TOPICS_MAX;
28
29
  if (!Number.isInteger(this.topicsMax) || this.topicsMax <= 0) {
29
30
  throw new RangeError(`topicsMax must be a positive integer, got ${this.topicsMax}`);
30
31
  }
32
+ this.onEvict = config.onEvict;
31
33
  }
32
34
  get size() {
33
35
  return this.residents.size;
@@ -46,7 +48,7 @@ class LruTopicBudget {
46
48
  return true;
47
49
  }
48
50
  if (this.residents.size < this.topicsMax) {
49
- this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter });
51
+ this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter, topicId });
50
52
  return true;
51
53
  }
52
54
  // Full: only a zero-participant resident may be evicted to make room for a new topic.
@@ -55,8 +57,12 @@ class LruTopicBudget {
55
57
  log("topic-budget full size=%d max=%d — refuse new topic", this.residents.size, this.topicsMax);
56
58
  return false;
57
59
  }
60
+ // Fire the teardown hook with the victim's real bytes BEFORE freeing the slot, so the callback
61
+ // (drop the now-unbacked forwarder + traffic window) sees a coherent state. Only a genuine
62
+ // eviction reaches here — `coldestEvictable()` already refused a full-of-populated budget above.
63
+ this.onEvict?.(this.residents.get(victim).topicId);
58
64
  this.residents.delete(victim);
59
- this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter });
65
+ this.residents.set(key, { participantCount: 0, seq: ++this.seqCounter, topicId });
60
66
  log("topic-budget evicted cold topic to admit new (size=%d max=%d)", this.residents.size, this.topicsMax);
61
67
  return true;
62
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"topic-budget.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAEjD,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAyCvC,MAAM,cAAc;IACF,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC7C,SAAS,CAAS;IAC3B,UAAU,GAAG,CAAC,CAAC;IAEvB,YAAY,SAA4B,EAAE;QACzC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACxD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,UAAU,CAAC,6CAA6C,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACrF,CAAC;IACF,CAAC;IAED,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;IAC5B,CAAC;IAED,GAAG,CAAC,OAAmB;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,gBAAgB,CAAC,OAAmB;QACnC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,gBAAgB,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,OAAmB;QACxB,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,QAAQ,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,qDAAqD;YACvF,OAAO,IAAI,CAAC;QACb,CAAC;QACD,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC1C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,gBAAgB,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;YACzE,OAAO,IAAI,CAAC;QACb,CAAC;QACD,sFAAsF;QACtF,MAAM,MAAM,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YAC1B,GAAG,CAAC,qDAAqD,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAChG,OAAO,KAAK,CAAC;QACd,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,gBAAgB,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACzE,GAAG,CAAC,+DAA+D,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1G,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,OAAmB,EAAE,gBAAwB;QAClD,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO;QACR,CAAC;QACD,KAAK,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;QAC1C,KAAK,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACK,gBAAgB;QACvB,IAAI,MAA0B,CAAC;QAC/B,IAAI,SAAS,GAAG,QAAQ,CAAC;QACzB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC3C,IAAI,KAAK,CAAC,gBAAgB,GAAG,CAAC,EAAE,CAAC;gBAChC,SAAS,CAAC,oEAAoE;YAC/E,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,GAAG,SAAS,EAAE,CAAC;gBAC3B,MAAM,GAAG,GAAG,CAAC;gBACb,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC;YACvB,CAAC;QACF,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;CACD;AAED,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,SAA4B,EAAE;IAC/D,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC"}
1
+ {"version":3,"file":"topic-budget.js","sourceRoot":"","sources":["../../../../src/cohort-topic/antidos/topic-budget.ts"],"names":[],"mappings":"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"}
@@ -11,18 +11,22 @@
11
11
  * cold-start case) **or** it arrived as a follow-on to a parent cohort's `Promoted` redirect, AND
12
12
  * - a quorum of cohort members is willing to serve `T` at the registration's tier.
13
13
  *
14
- * The `followOn` signal is **not** carried on the wire (`RegisterV1` has only `bootstrap`); a cold
15
- * high-tier cohort returns `NoState` to a *speculative* `d_max` probe, but the single-direction walk
16
- * means the only registrations that reach a cold tier-`(d+1)` cohort are those that followed the
17
- * parent's `Promoted` redirect. Determining `followOn` for a given inbound register therefore belongs
18
- * to the db-p2p cohort host (it has the routing context); db-core takes it as an explicit input to
19
- * {@link shouldInstantiate}, keeping this module FRET-free. (See the implement handoff for the wiring
20
- * gap this leaves for the db-p2p binding.)
14
+ * The `followOn` signal **is** carried on the wire (`RegisterV1.followOn`), set only on the dedicated
15
+ * re-issue a participant sends after a parent cohort's `Promoted` redirect target answered `NoState`
16
+ * (the deeper child is cold). A cold high-tier cohort still returns `NoState` to a *speculative* `d_max`
17
+ * probe (neither flag set), but the single-direction walk means the only registrations that reach a cold
18
+ * tier-`(d+1)` cohort carrying `followOn: true` are those that followed the parent's `Promoted` redirect.
19
+ * The db-p2p cohort host derives `ctx.followOn` from the wire flag (`followOn: reg.followOn === true`) and
20
+ * db-core takes it as an explicit input to {@link shouldInstantiate}, keeping this module FRET-free. Because
21
+ * the flag is participant-forgeable, a `followOn: true` register is gated by the **same** tier-dependent
22
+ * bootstrap-evidence policy a `bootstrap: true` cold-root register passes (§Anti-DoS), so an unbacked
23
+ * follow-on never reaches this instantiation decision.
21
24
  *
22
25
  * Once instantiated, the new forwarder **registers itself with its tier-`(d − 1)` parent at first
23
- * opportunity**; until that registration is acked it {@link Forwarder.acceptsParticipants | accepts
24
- * participants} but {@link Forwarder.servesParentOps | holds} notifications/queries that need parent
25
- * involvement. The root (tree tier 0) has no parent and is serving immediately.
26
+ * opportunity** by sending a child-link the parent authenticates + records; until that link is acked
27
+ * (`linked`) it {@link Forwarder.acceptsParticipants | accepts participants} but
28
+ * {@link Forwarder.servesParentOps | holds} notifications/queries that need parent involvement. The root
29
+ * (tree tier 0) has no parent and is serving immediately.
26
30
  *
27
31
  * **Just-promoted burst (GROUNDING-resolved: bounce, don't buffer).** A cohort that has just promoted
28
32
  * but whose tier-`(d+1)` isn't fully instantiated yet, on a burst of new same-tier registrations,
@@ -82,13 +86,18 @@ export declare function createForwarder(tier: number): Forwarder;
82
86
  * redirected participant can estimate whether the target tier is hot (§Topic traffic signal).
83
87
  */
84
88
  export declare function promotedRedirectReply(targetTier: number, traffic?: TopicTrafficV1): RegisterReplyV1;
85
- /** Registers a newly-instantiated forwarder with its tier-`(d − 1)` parent; resolves on the parent ack. */
89
+ /**
90
+ * Registers a newly-instantiated forwarder with its tier-`(d − 1)` parent; resolves only when the parent
91
+ * authenticates + records the child and returns a `linked` ack. A `rejected` reply (bad coord binding /
92
+ * failed threshold verify) or an unreachable parent rejects the promise, so the forwarder stays
93
+ * `awaiting_parent` for a later retry.
94
+ */
86
95
  export interface ParentRegistrar {
87
96
  /**
88
97
  * Register the forwarder for `topicId` (served at tree tier `tier`, i.e. `d`) with the cohort at
89
- * `parentCoord`; resolves on ack. `opTier` is the topic's *capacity* tier (T0–T3), threaded so the
90
- * transport can stamp a well-formed forwarder-link frame; absent when the caller has no op-tier
91
- * context (the cohort host always supplies it from the instantiating `RegisterV1`).
98
+ * `parentCoord`; resolves on a `linked` ack. `opTier` is the topic's *capacity* tier (T0–T3), threaded so
99
+ * the transport can stamp a well-formed child-link frame; absent when the caller has no op-tier context
100
+ * (the cohort host always supplies it from the instantiating `RegisterV1`).
92
101
  */
93
102
  registerWithParent(topicId: Uint8Array, parentCoord: Uint8Array, tier: number, opTier?: number): Promise<void>;
94
103
  }
@@ -111,6 +120,17 @@ export interface ColdStartManager {
111
120
  instantiate(topicId: Uint8Array, tier: number, parentCoord?: Uint8Array, opTier?: number): Forwarder;
112
121
  /** The tracked forwarder for `topicId`, or `undefined`. */
113
122
  get(topicId: Uint8Array): Forwarder | undefined;
123
+ /**
124
+ * Drop the forwarder for `topicId` (budget eviction / teardown). Idempotent; a no-op if the topic is
125
+ * not tracked. After this, {@link get} returns `undefined` for `topicId`, so the cohort no longer
126
+ * reports serving it via cold-start state.
127
+ */
128
+ remove(topicId: Uint8Array): void;
129
+ /**
130
+ * Whether this manager currently tracks any forwarder. The host's idle-engine eviction reads this to
131
+ * avoid reclaiming a cohort that holds a live (possibly `awaiting_parent`) cold-start forwarder.
132
+ */
133
+ hasForwarders(): boolean;
114
134
  }
115
135
  /** Build a {@link ColdStartManager} over the injected parent registrar. */
116
136
  export declare function createColdStartManager(deps: ColdStartManagerDeps): ColdStartManager;
@@ -1 +1 @@
1
- {"version":3,"file":"coldstart.d.ts","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEvE,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAChC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAEpE;AAED,2DAA2D;AAC3D,MAAM,MAAM,cAAc;AACzB,mGAAmG;AACjG,iBAAiB;AACnB,8EAA8E;GAC5E,SAAS,CAAC;AAEb;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,KAAK,IAAI,cAAc,CAAC;IACxB,4FAA4F;IAC5F,mBAAmB,IAAI,OAAO,CAAC;IAC/B,kGAAkG;IAClG,eAAe,IAAI,OAAO,CAAC;IAC3B,uGAAuG;IACvG,WAAW,IAAI,IAAI,CAAC;CACpB;AA2BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAKvD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,eAAe,CAMnG;AAED,2GAA2G;AAC3G,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/G;AAED,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,eAAe,EAAE,eAAe,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrG,2DAA2D;IAC3D,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;CAChD;AAqCD,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAEnF"}
1
+ {"version":3,"file":"coldstart.d.ts","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKvE,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAChC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAEpE;AAED,2DAA2D;AAC3D,MAAM,MAAM,cAAc;AACzB,mGAAmG;AACjG,iBAAiB;AACnB,8EAA8E;GAC5E,SAAS,CAAC;AAEb;;;GAGG;AACH,MAAM,WAAW,SAAS;IACzB,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,KAAK,IAAI,cAAc,CAAC;IACxB,4FAA4F;IAC5F,mBAAmB,IAAI,OAAO,CAAC;IAC/B,kGAAkG;IAClG,eAAe,IAAI,OAAO,CAAC;IAC3B,uGAAuG;IACvG,WAAW,IAAI,IAAI,CAAC;CACpB;AA2BD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAKvD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,eAAe,CAMnG;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;OAKG;IACH,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/G;AAED,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,eAAe,EAAE,eAAe,CAAC;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAChC;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrG,2DAA2D;IAC3D,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAChD;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC;CACzB;AA+CD,2EAA2E;AAC3E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,oBAAoB,GAAG,gBAAgB,CAEnF"}
@@ -11,18 +11,22 @@
11
11
  * cold-start case) **or** it arrived as a follow-on to a parent cohort's `Promoted` redirect, AND
12
12
  * - a quorum of cohort members is willing to serve `T` at the registration's tier.
13
13
  *
14
- * The `followOn` signal is **not** carried on the wire (`RegisterV1` has only `bootstrap`); a cold
15
- * high-tier cohort returns `NoState` to a *speculative* `d_max` probe, but the single-direction walk
16
- * means the only registrations that reach a cold tier-`(d+1)` cohort are those that followed the
17
- * parent's `Promoted` redirect. Determining `followOn` for a given inbound register therefore belongs
18
- * to the db-p2p cohort host (it has the routing context); db-core takes it as an explicit input to
19
- * {@link shouldInstantiate}, keeping this module FRET-free. (See the implement handoff for the wiring
20
- * gap this leaves for the db-p2p binding.)
14
+ * The `followOn` signal **is** carried on the wire (`RegisterV1.followOn`), set only on the dedicated
15
+ * re-issue a participant sends after a parent cohort's `Promoted` redirect target answered `NoState`
16
+ * (the deeper child is cold). A cold high-tier cohort still returns `NoState` to a *speculative* `d_max`
17
+ * probe (neither flag set), but the single-direction walk means the only registrations that reach a cold
18
+ * tier-`(d+1)` cohort carrying `followOn: true` are those that followed the parent's `Promoted` redirect.
19
+ * The db-p2p cohort host derives `ctx.followOn` from the wire flag (`followOn: reg.followOn === true`) and
20
+ * db-core takes it as an explicit input to {@link shouldInstantiate}, keeping this module FRET-free. Because
21
+ * the flag is participant-forgeable, a `followOn: true` register is gated by the **same** tier-dependent
22
+ * bootstrap-evidence policy a `bootstrap: true` cold-root register passes (§Anti-DoS), so an unbacked
23
+ * follow-on never reaches this instantiation decision.
21
24
  *
22
25
  * Once instantiated, the new forwarder **registers itself with its tier-`(d − 1)` parent at first
23
- * opportunity**; until that registration is acked it {@link Forwarder.acceptsParticipants | accepts
24
- * participants} but {@link Forwarder.servesParentOps | holds} notifications/queries that need parent
25
- * involvement. The root (tree tier 0) has no parent and is serving immediately.
26
+ * opportunity** by sending a child-link the parent authenticates + records; until that link is acked
27
+ * (`linked`) it {@link Forwarder.acceptsParticipants | accepts participants} but
28
+ * {@link Forwarder.servesParentOps | holds} notifications/queries that need parent involvement. The root
29
+ * (tree tier 0) has no parent and is serving immediately.
26
30
  *
27
31
  * **Just-promoted burst (GROUNDING-resolved: bounce, don't buffer).** A cohort that has just promoted
28
32
  * but whose tier-`(d+1)` isn't fully instantiated yet, on a burst of new same-tier registrations,
@@ -32,6 +36,8 @@
32
36
  */
33
37
  import { attachTopicTraffic } from "./traffic.js";
34
38
  import { bytesKey } from "./registration/bytes.js";
39
+ import { createLogger } from "../logger.js";
40
+ const log = createLogger('cohort-topic:coldstart');
35
41
  /**
36
42
  * Whether a cold cohort should instantiate forwarder state for an inbound register: it must be a
37
43
  * legitimate growth point (`bootstrap` root case **or** a `Promoted` follow-on) **and** have a willing
@@ -111,7 +117,7 @@ class TrackingColdStartManager {
111
117
  .registerWithParent(topicId, parentCoord, tier, opTier)
112
118
  .then(() => forwarder.onParentAck())
113
119
  .catch((err) => {
114
- console.warn(`cohort-topic cold-start: parent registration for tier-${tier} forwarder failed`, err);
120
+ log('WARN: parent registration for tier-%s forwarder failed %o', tier, err);
115
121
  });
116
122
  }
117
123
  return forwarder;
@@ -119,6 +125,14 @@ class TrackingColdStartManager {
119
125
  get(topicId) {
120
126
  return this.forwarders.get(bytesKey(topicId));
121
127
  }
128
+ remove(topicId) {
129
+ // Idempotent: `Map.delete` on an absent key is a safe no-op, so a double-remove (or a remove of a
130
+ // never-instantiated topic) does nothing.
131
+ this.forwarders.delete(bytesKey(topicId));
132
+ }
133
+ hasForwarders() {
134
+ return this.forwarders.size > 0;
135
+ }
122
136
  }
123
137
  /** Build a {@link ColdStartManager} over the injected parent registrar. */
124
138
  export function createColdStartManager(deps) {
@@ -1 +1 @@
1
- {"version":3,"file":"coldstart.js","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAanD;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAyB;IAC1D,OAAO,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,aAAa,CAAC;AACzE,CAAC;AA0BD,MAAM,cAAc;IAGE;IAFb,OAAO,CAAU;IAEzB,YAAqB,IAAY,EAAE,SAAkB;QAAhC,SAAI,GAAJ,IAAI,CAAQ;QAChC,yFAAyF;QACzF,IAAI,CAAC,OAAO,GAAG,CAAC,SAAS,CAAC;IAC3B,CAAC;IAED,KAAK;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrD,CAAC;IAED,mBAAmB;QAClB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,eAAe;QACd,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;IAED,WAAW;QACV,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACrB,CAAC;CACD;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,UAAU,CAAC,sDAAsD,IAAI,EAAE,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,IAAI,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAAkB,EAAE,OAAwB;IACjF,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,UAAU,CAAC,mDAAmD,UAAU,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,KAAK,GAAoB,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;IACxE,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC3E,CAAC;AAmCD,MAAM,wBAAwB;IAGA;IAFZ,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;IAE3D,YAA6B,IAA0B;QAA1B,SAAI,GAAJ,IAAI,CAAsB;IAAG,CAAC;IAE3D,WAAW,CAAC,OAAmB,EAAE,IAAY,EAAE,WAAwB,EAAE,MAAe;QACvF,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,QAAQ,CAAC;QACjB,CAAC;QACD,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACpC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;YACd,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,mCAAmC,CAAC,CAAC;YAClF,CAAC;YACD,yFAAyF;YACzF,6FAA6F;YAC7F,4FAA4F;YAC5F,KAAK,IAAI,CAAC,IAAI,CAAC,eAAe;iBAC5B,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,CAAC;iBACtD,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC;iBACnC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;gBACvB,OAAO,CAAC,IAAI,CAAC,yDAAyD,IAAI,mBAAmB,EAAE,GAAG,CAAC,CAAC;YACrG,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,GAAG,CAAC,OAAmB;QACtB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,CAAC;CACD;AAED,2EAA2E;AAC3E,MAAM,UAAU,sBAAsB,CAAC,IAA0B;IAChE,OAAO,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"coldstart.js","sourceRoot":"","sources":["../../../src/cohort-topic/coldstart.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@
24
24
  */
25
25
  import type { RegistrationStore } from "../registration/types.js";
26
26
  import type { ICohortGossipTransport, RingCoord } from "../ports.js";
27
- import type { CohortGossipV1 } from "../wire/types.js";
27
+ import type { ChildLinkRefV1, CohortGossipV1 } from "../wire/types.js";
28
28
  import { type CohortView } from "./view.js";
29
29
  /** Handler for inbound gossip (after merge). */
30
30
  export type GossipHandler = (g: CohortGossipV1) => void;
@@ -64,6 +64,17 @@ export interface CohortGossipBusDeps {
64
64
  * callback so this gossip/replication module carries no anti-DoS dependency. Absent → skipped.
65
65
  */
66
66
  onRecordsEvicted?: (topicIds: readonly Uint8Array[]) => void;
67
+ /**
68
+ * Optional hook fired for the child-cohort link/unlink deltas on an inbound frame (db-p2p merges them into
69
+ * this coord's child registry, last-writer-wins by `effectiveAt`), so every parent member converges on the
70
+ * same child set — not only the FRET-routed member that recorded the child. Fired for **our-coord** frames
71
+ * regardless of epoch match: the child set is keyed by child coord, not the parent's epoch, so a parent
72
+ * membership rotation (an epoch drift) must not skip the merge (unlike record deltas, whose slot
73
+ * assignments are epoch-scoped). The merge is a direct registry write — it does **not** re-enqueue the
74
+ * delta, so a received link/unlink is not re-gossiped (one broadcast reaches the whole cohort). Kept as a
75
+ * decoupled callback so this module carries no child-registry dependency. Absent → skipped.
76
+ */
77
+ onChildDeltas?: (childLinks: readonly ChildLinkRefV1[], childUnlinks: readonly ChildLinkRefV1[]) => void;
67
78
  }
68
79
  /** Intra-cohort gossip bus (merge logic over an injected transport). */
69
80
  export interface CohortGossipBus {
@@ -1 +1 @@
1
- {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/gossip/bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,sBAAsB,EAAW,SAAS,EAAE,MAAM,aAAa,CAAC;AAE9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAoB,KAAK,UAAU,EAA0B,MAAM,WAAW,CAAC;AAEtF,gDAAgD;AAChD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;AACxD,sEAAsE;AACtE,MAAM,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AACpG,qGAAqG;AACrG,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IACnC,mEAAmE;IACnE,SAAS,EAAE,sBAAsB,CAAC;IAClC,yDAAyD;IACzD,KAAK,EAAE,iBAAiB,CAAC;IACzB,kHAAkH;IAClH,KAAK,EAAE,SAAS,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,UAAU,CAAC;IAC7B,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,KAAK,IAAI,CAAC;CAC7D;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC/B,mCAAmC;IACnC,SAAS,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,IAAI,CAAC;IAC7C,0EAA0E;IAC1E,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;IAC3C,sFAAsF;IACtF,YAAY,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,mEAAmE;IACnE,IAAI,IAAI,UAAU,CAAC;IACnB,yCAAyC;IACzC,KAAK,IAAI,IAAI,CAAC;CACd;AA6HD,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,eAAe,CAEhF"}
1
+ {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../../../../src/cohort-topic/gossip/bus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAElE,OAAO,KAAK,EAAE,sBAAsB,EAAW,SAAS,EAAE,MAAM,aAAa,CAAC;AAE9E,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvE,OAAO,EAAoB,KAAK,UAAU,EAA0B,MAAM,WAAW,CAAC;AAEtF,gDAAgD;AAChD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,EAAE,cAAc,KAAK,IAAI,CAAC;AACxD,sEAAsE;AACtE,MAAM,MAAM,YAAY,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AACpG,qGAAqG;AACrG,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IACnC,mEAAmE;IACnE,SAAS,EAAE,sBAAsB,CAAC;IAClC,yDAAyD;IACzD,KAAK,EAAE,iBAAiB,CAAC;IACzB,kHAAkH;IAClH,KAAK,EAAE,SAAS,CAAC;IACjB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,UAAU,CAAC;IAC7B,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7D;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,SAAS,cAAc,EAAE,EAAE,YAAY,EAAE,SAAS,cAAc,EAAE,KAAK,IAAI,CAAC;CACzG;AAED,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC/B,mCAAmC;IACnC,SAAS,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,IAAI,CAAC;IAC7C,0EAA0E;IAC1E,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,IAAI,CAAC;IAC3C,sFAAsF;IACtF,YAAY,CAAC,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnD,mEAAmE;IACnE,IAAI,IAAI,UAAU,CAAC;IACnB,yCAAyC;IACzC,KAAK,IAAI,IAAI,CAAC;CACd;AAsJD,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,GAAG,eAAe,CAEhF"}
@@ -23,6 +23,7 @@
23
23
  * members — one {@link CohortGossipBus.applyInbound} of that gossip makes it visible locally.
24
24
  */
25
25
  import { bytesEqual, bytesKey } from "../registration/bytes.js";
26
+ import { clampTtl } from "../registration/types.js";
26
27
  import { b64urlToBytes, decodeCohortGossipV1, encodeCohortMessage } from "../wire/codec.js";
27
28
  import { fromGossipRecord } from "./records.js";
28
29
  import { createCohortView } from "./view.js";
@@ -69,6 +70,14 @@ class TransportCohortGossipBus {
69
70
  if (epochMatches) {
70
71
  this.mergeRecords(g, now);
71
72
  }
73
+ // Child link/unlink deltas merge regardless of epoch match: the child set is keyed by child coord, not
74
+ // the parent's epoch, so a rotation (epoch drift) must not drop it. Freshness (last-writer-wins by
75
+ // effectiveAt) lives in the registry the callback writes.
76
+ const childLinks = g.childLinks ?? [];
77
+ const childUnlinks = g.childUnlinks ?? [];
78
+ if (this.deps.onChildDeltas !== undefined && (childLinks.length > 0 || childUnlinks.length > 0)) {
79
+ this.deps.onChildDeltas(childLinks, childUnlinks);
80
+ }
72
81
  }
73
82
  view() {
74
83
  return this.memberView;
@@ -109,6 +118,11 @@ class TransportCohortGossipBus {
109
118
  mergeRecords(g, now) {
110
119
  for (const gr of g.records ?? []) {
111
120
  const incoming = fromGossipRecord(gr);
121
+ // Gossip is a second admission path into the store — a peer (buggy, unpatched, or hostile)
122
+ // can replicate a record whose TTL never passed local `accept()`'s clamp. Re-clamp here so
123
+ // no replica can hold an unbounded lifetime that wedges the topic budget. Clamp BEFORE the
124
+ // dead-check below so a poison TTL cannot dodge staleness eviction.
125
+ incoming.ttl = clampTtl(incoming.ttl);
112
126
  // A record already past its TTL is dead; merging it would resurrect a registration the
113
127
  // owner has (or soon will have) evicted. Drop it, matching `store.evictStale`'s predicate
114
128
  // so replication can never reintroduce what local eviction removes.
@@ -129,7 +143,19 @@ class TransportCohortGossipBus {
129
143
  let evictedTopics;
130
144
  for (const ref of g.evicted ?? []) {
131
145
  const topicId = b64urlToBytes(ref.topicId);
132
- this.deps.store.delete(topicId, b64urlToBytes(ref.participantId));
146
+ const participantId = b64urlToBytes(ref.participantId);
147
+ // NOTE: freshness guard adds one getByParticipant per eviction ref (on top of the delete's own
148
+ // lookup); evictions are low-volume, but if this path ever shows as hot, fold the guard into a
149
+ // single conditional-delete store op.
150
+ const held = this.deps.store.getByParticipant(topicId, participantId);
151
+ // A stale eviction (older than the held record) must not delete a fresher re-registration.
152
+ // Evictions are otherwise NOT last-writer-wins like merges are, so a reordered/slow delta would
153
+ // win against a record the participant already renewed. Skip it — and, since nothing drained,
154
+ // do NOT add the topic to `evictedTopics` (no delete → no budget re-touch).
155
+ if (held !== undefined && held.lastPing > ref.lastPing) {
156
+ continue;
157
+ }
158
+ this.deps.store.delete(topicId, participantId);
133
159
  if (this.deps.onRecordsEvicted !== undefined) {
134
160
  (evictedTopics ??= new Map()).set(bytesKey(topicId), topicId);
135
161
  }