@optimystic/db-core 0.14.1 → 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
@@ -8,8 +8,28 @@
8
8
  * narrow cleanly or throw.
9
9
  */
10
10
 
11
- import { b64urlToBytes } from "./codec.js";
11
+ import { DEFAULT_D_MAX_CAP } from "../dmax.js";
12
+ import {
13
+ assignDefined,
14
+ asObject,
15
+ b64urlField,
16
+ b64urlFixedLen,
17
+ failWire as fail,
18
+ optBool,
19
+ optFiniteNumber,
20
+ optString,
21
+ optStringArray,
22
+ reqBool,
23
+ reqEnum,
24
+ reqFiniteNumber,
25
+ reqString,
26
+ reqStringArray,
27
+ requireV1,
28
+ } from "./primitives.js";
12
29
  import type {
30
+ ChildLinkRefV1,
31
+ ChildLinkReplyV1,
32
+ ChildLinkV1,
13
33
  CohortGossipV1,
14
34
  CohortTopicSummary,
15
35
  DemotionNoticeV1,
@@ -28,109 +48,11 @@ import type {
28
48
  TopicTrafficV1,
29
49
  } from "./types.js";
30
50
 
31
- /** Thrown for any malformed, oversized, or structurally invalid cohort-topic frame. */
32
- export class CohortWireError extends Error {
33
- constructor(message: string) {
34
- super(message);
35
- this.name = "CohortWireError";
36
- }
37
- }
38
-
39
- function fail(message: string): never {
40
- throw new CohortWireError(message);
41
- }
42
-
43
- /** Set `obj[key]` only when `value` is defined — keeps absent optionals off the decoded object. */
44
- function assignDefined<T extends object, K extends keyof T>(obj: T, key: K, value: T[K] | undefined): void {
45
- if (value !== undefined) {
46
- obj[key] = value;
47
- }
48
- }
49
-
50
- function asObject(value: unknown, what: string): Record<string, unknown> {
51
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
52
- fail(`${what}: expected an object`);
53
- }
54
- return value as Record<string, unknown>;
55
- }
56
-
57
- function requireV1(obj: Record<string, unknown>, what: string): void {
58
- if (obj["v"] !== 1) {
59
- fail(`${what}: expected v === 1, got ${JSON.stringify(obj["v"])}`);
60
- }
61
- }
62
-
63
- function reqString(obj: Record<string, unknown>, key: string, what: string): string {
64
- const value = obj[key];
65
- if (typeof value !== "string") {
66
- fail(`${what}: field "${key}" must be a string`);
67
- }
68
- return value;
69
- }
70
-
71
- function optString(obj: Record<string, unknown>, key: string, what: string): string | undefined {
72
- const value = obj[key];
73
- if (value === undefined) {
74
- return undefined;
75
- }
76
- if (typeof value !== "string") {
77
- fail(`${what}: field "${key}" must be a string when present`);
78
- }
79
- return value;
80
- }
81
-
82
- function reqFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number {
83
- const value = obj[key];
84
- if (typeof value !== "number" || !Number.isFinite(value)) {
85
- fail(`${what}: field "${key}" must be a finite number`);
86
- }
87
- return value;
88
- }
89
-
90
- function optFiniteNumber(obj: Record<string, unknown>, key: string, what: string): number | undefined {
91
- const value = obj[key];
92
- if (value === undefined) {
93
- return undefined;
94
- }
95
- if (typeof value !== "number" || !Number.isFinite(value)) {
96
- fail(`${what}: field "${key}" must be a finite number when present`);
97
- }
98
- return value;
99
- }
100
-
101
- function reqBool(obj: Record<string, unknown>, key: string, what: string): boolean {
102
- const value = obj[key];
103
- if (typeof value !== "boolean") {
104
- fail(`${what}: field "${key}" must be a boolean`);
105
- }
106
- return value;
107
- }
108
-
109
- function optBool(obj: Record<string, unknown>, key: string, what: string): boolean | undefined {
110
- const value = obj[key];
111
- if (value === undefined) {
112
- return undefined;
113
- }
114
- if (typeof value !== "boolean") {
115
- fail(`${what}: field "${key}" must be a boolean when present`);
116
- }
117
- return value;
118
- }
119
-
120
- function reqStringArray(obj: Record<string, unknown>, key: string, what: string): string[] {
121
- const value = obj[key];
122
- if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
123
- fail(`${what}: field "${key}" must be an array of strings`);
124
- }
125
- return value as string[];
126
- }
127
-
128
- function optStringArray(obj: Record<string, unknown>, key: string, what: string): string[] | undefined {
129
- if (obj[key] === undefined) {
130
- return undefined;
131
- }
132
- return reqStringArray(obj, key, what);
133
- }
51
+ // The generic structural-validation primitives (`fail`/`asObject`/`req*`/`opt*`/`b64url*`/`reqEnum`/…)
52
+ // live in ./primitives.js and are imported above; only the cohort-topic domain validators and the
53
+ // tier-semantics helpers below stay local. `CohortWireError` is re-exported here so existing importers
54
+ // of this module (codec, wire/index, matchmaking, reactivity) are unaffected by the move.
55
+ export { CohortWireError } from "./primitives.js";
134
56
 
135
57
  function tier(value: number, what: string): number {
136
58
  if (!Number.isInteger(value) || value < 0 || value > 3) {
@@ -139,23 +61,24 @@ function tier(value: number, what: string): number {
139
61
  return value;
140
62
  }
141
63
 
142
- /** Assert a base64url string decodes cleanly; returns it unchanged. */
143
- function b64urlField(value: string, key: string, what: string): string {
144
- try {
145
- b64urlToBytes(value);
146
- } catch {
147
- fail(`${what}: field "${key}" is not valid base64url`);
64
+ /**
65
+ * Validate a `treeTier` (the walk-toward-root start tier `d` a register/redirect targets): an integer in
66
+ * `0..DEFAULT_D_MAX_CAP`. `d_max_cap` (60) is the substrate's own ceiling on useful walk depth, so a
67
+ * `treeTier` above it cannot correspond to a real walk position. Rejecting here keeps an out-of-range value
68
+ * from reaching `addressing.coord()` downstream — whose `coordD` throws a raw `RangeError` for a
69
+ * non-integer / negative / `> 255` tier, an unclassified crash rather than a clean malformed-frame rejection.
70
+ */
71
+ function treeTier(value: number, what: string): number {
72
+ if (!Number.isInteger(value) || value < 0 || value > DEFAULT_D_MAX_CAP) {
73
+ fail(`${what}: treeTier must be an integer in 0..${DEFAULT_D_MAX_CAP}, got ${value}`);
148
74
  }
149
75
  return value;
150
76
  }
151
77
 
152
- function reqEnum<T extends string>(obj: Record<string, unknown>, key: string, allowed: readonly T[], what: string): T {
153
- const value = obj[key];
154
- if (typeof value !== "string" || !(allowed as readonly string[]).includes(value)) {
155
- fail(`${what}: field "${key}" must be one of ${allowed.join(" | ")}`);
156
- }
157
- return value as T;
158
- }
78
+ /** Ring-coord / topic-id / epoch byte width (SHA-256 truncated to the ring width). */
79
+ const COORD_BYTES = 32;
80
+ /** Correlation-id byte width (a 16-byte nonce minted per walk probe / renew). */
81
+ const CORRELATION_BYTES = 16;
159
82
 
160
83
  const REGISTER_RESULTS: readonly RegisterResult[] = [
161
84
  "accepted",
@@ -171,13 +94,13 @@ export function validateRegisterV1(value: unknown): RegisterV1 {
171
94
  requireV1(obj, what);
172
95
  const out: RegisterV1 = {
173
96
  v: 1,
174
- topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
97
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
175
98
  tier: tier(reqFiniteNumber(obj, "tier", what), what),
176
- treeTier: reqFiniteNumber(obj, "treeTier", what),
99
+ treeTier: treeTier(reqFiniteNumber(obj, "treeTier", what), what),
177
100
  participantCoord: b64urlField(reqString(obj, "participantCoord", what), "participantCoord", what),
178
101
  ttl: reqFiniteNumber(obj, "ttl", what),
179
102
  timestamp: reqFiniteNumber(obj, "timestamp", what),
180
- correlationId: b64urlField(reqString(obj, "correlationId", what), "correlationId", what),
103
+ correlationId: b64urlFixedLen(reqString(obj, "correlationId", what), "correlationId", CORRELATION_BYTES, what),
181
104
  signature: b64urlField(reqString(obj, "signature", what), "signature", what),
182
105
  };
183
106
  const bootstrap = optBool(obj, "bootstrap", what);
@@ -188,6 +111,21 @@ export function validateRegisterV1(value: unknown): RegisterV1 {
188
111
  if (probe !== undefined) {
189
112
  out.probe = probe;
190
113
  }
114
+ const followOn = optBool(obj, "followOn", what);
115
+ if (followOn !== undefined) {
116
+ out.followOn = followOn;
117
+ }
118
+ // `bootstrap`, `followOn`, and `probe` are pairwise mutually exclusive — the walk sets at most one
119
+ // (bootstrap only at the tier-0 root re-issue, followOn only at a `treeTier >= 1` redirect target,
120
+ // probe never instantiates). A frame that sets more than one is malformed / adversarial.
121
+ if ([bootstrap, followOn, probe].filter((flag) => flag === true).length > 1) {
122
+ fail(`${what}: at most one of bootstrap, followOn, probe may be set`);
123
+ }
124
+ // A follow-on is by definition a deeper-than-root growth point, so `treeTier >= 1`. A `followOn: true`
125
+ // at the root (treeTier 0) would be a bootstrap, not a follow-on — reject it as malformed.
126
+ if (followOn === true && out.treeTier < 1) {
127
+ fail(`${what}: followOn requires treeTier >= 1, got ${out.treeTier}`);
128
+ }
191
129
  const appPayload = optString(obj, "appPayload", what);
192
130
  if (appPayload !== undefined) {
193
131
  out.appPayload = b64urlField(appPayload, "appPayload", what);
@@ -226,12 +164,17 @@ export function validateRegisterReplyV1(value: unknown): RegisterReplyV1 {
226
164
  assignDefined(out, "backups", optStringArray(obj, "backups", what));
227
165
  const cohortEpoch = optString(obj, "cohortEpoch", what);
228
166
  if (cohortEpoch !== undefined) {
167
+ // NOTE: cohortEpoch is NOT length-pinned though real epochs are a 32-byte SHA-256 — several
168
+ // test fixtures (db-core reactivity + db-p2p) feed 1-byte synthetic epochs. Pinning is tracked
169
+ // by debt-cohort-topic-pin-cohort-epoch. See b64urlField's note.
229
170
  out.cohortEpoch = b64urlField(cohortEpoch, "cohortEpoch", what);
230
171
  }
231
172
  assignDefined(out, "cohortMembers", optStringArray(obj, "cohortMembers", what));
232
173
  if (obj["topicTraffic"] !== undefined) {
233
174
  out.topicTraffic = validateTopicTrafficV1(obj["topicTraffic"]);
234
175
  }
176
+ // NOTE: `targetTier` is range-checked in the walk loop (walk.ts, case "promoted"), not here — an
177
+ // out-of-range redirect must surface as a `retry_later` outcome, not a decode-time throw.
235
178
  assignDefined(out, "targetTier", optFiniteNumber(obj, "targetTier", what));
236
179
  assignDefined(out, "candidateMembers", optStringArray(obj, "candidateMembers", what));
237
180
  assignDefined(out, "retryAfterMs", optFiniteNumber(obj, "retryAfterMs", what));
@@ -245,9 +188,9 @@ export function validateRenewV1(value: unknown): RenewV1 {
245
188
  requireV1(obj, what);
246
189
  const out: RenewV1 = {
247
190
  v: 1,
248
- topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
191
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
249
192
  participantId: reqString(obj, "participantId", what),
250
- correlationId: b64urlField(reqString(obj, "correlationId", what), "correlationId", what),
193
+ correlationId: b64urlFixedLen(reqString(obj, "correlationId", what), "correlationId", CORRELATION_BYTES, what),
251
194
  timestamp: reqFiniteNumber(obj, "timestamp", what),
252
195
  signature: b64urlField(reqString(obj, "signature", what), "signature", what),
253
196
  };
@@ -268,6 +211,8 @@ export function validateRenewReplyV1(value: unknown): RenewReplyV1 {
268
211
  assignDefined(out, "newBackups", optStringArray(obj, "newBackups", what));
269
212
  const cohortEpoch = optString(obj, "cohortEpoch", what);
270
213
  if (cohortEpoch !== undefined) {
214
+ // NOTE: cohortEpoch is NOT length-pinned — see validateRegisterReplyV1's note and
215
+ // debt-cohort-topic-pin-cohort-epoch.
271
216
  out.cohortEpoch = b64urlField(cohortEpoch, "cohortEpoch", what);
272
217
  }
273
218
  return out;
@@ -277,11 +222,17 @@ export function validatePromotionNoticeV1(value: unknown): PromotionNoticeV1 {
277
222
  const what = "PromotionNoticeV1";
278
223
  const obj = asObject(value, what);
279
224
  requireV1(obj, what);
225
+ const fromTier = treeTier(reqFiniteNumber(obj, "fromTier", what), what);
226
+ const toTier = treeTier(reqFiniteNumber(obj, "toTier", what), what);
227
+ if (toTier !== fromTier + 1) {
228
+ fail(`${what}: toTier must equal fromTier + 1, got fromTier=${fromTier} toTier=${toTier}`);
229
+ }
280
230
  return {
281
231
  v: 1,
282
- topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
283
- fromTier: tier(reqFiniteNumber(obj, "fromTier", what), what),
284
- toTier: tier(reqFiniteNumber(obj, "toTier", what), what),
232
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
233
+ fromTier,
234
+ toTier,
235
+ cohortCoord: b64urlFixedLen(reqString(obj, "cohortCoord", what), "cohortCoord", COORD_BYTES, what),
285
236
  effectiveAt: reqFiniteNumber(obj, "effectiveAt", what),
286
237
  thresholdSig: b64urlField(reqString(obj, "thresholdSig", what), "thresholdSig", what),
287
238
  signers: reqStringArray(obj, "signers", what),
@@ -295,9 +246,10 @@ export function validateDemotionNoticeV1(value: unknown): DemotionNoticeV1 {
295
246
  requireV1(obj, what);
296
247
  return {
297
248
  v: 1,
298
- topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
299
- tier: tier(reqFiniteNumber(obj, "tier", what), what),
300
- parentCohortCoord: b64urlField(reqString(obj, "parentCohortCoord", what), "parentCohortCoord", what),
249
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
250
+ tier: treeTier(reqFiniteNumber(obj, "tier", what), what),
251
+ parentCohortCoord: b64urlFixedLen(reqString(obj, "parentCohortCoord", what), "parentCohortCoord", COORD_BYTES, what),
252
+ cohortCoord: b64urlFixedLen(reqString(obj, "cohortCoord", what), "cohortCoord", COORD_BYTES, what),
301
253
  effectiveAt: reqFiniteNumber(obj, "effectiveAt", what),
302
254
  thresholdSig: b64urlField(reqString(obj, "thresholdSig", what), "thresholdSig", what),
303
255
  signers: reqStringArray(obj, "signers", what),
@@ -305,6 +257,56 @@ export function validateDemotionNoticeV1(value: unknown): DemotionNoticeV1 {
305
257
  };
306
258
  }
307
259
 
260
+ /**
261
+ * Validate a {@link ChildLinkV1}. Enforces `childTier >= 1` (the root never links), `tier` in 0..3, and
262
+ * well-formed base64url on every byte field. The hash-derived fields (`topicId` / `childCohortCoord` /
263
+ * `cohortEpoch`) are additionally length-checked to exactly 32 bytes (each is a SHA-256 truncation); the
264
+ * `childParticipantCoord` follows the lenient `RegisterV1` convention (base64url only — a participant
265
+ * coord is not always 32 raw bytes, e.g. a multihash-encoded peer id in tests). When `minSigs`
266
+ * is supplied AND the frame carries a threshold signature (`thresholdSig` non-empty), `signers.length` must
267
+ * be `>= minSigs`; a key-less-interim frame carries neither, so that cross-field bound is skipped. `minSigs`
268
+ * is optional so a bare structural decode (no quorum context) still narrows the frame.
269
+ */
270
+ export function validateChildLinkV1(value: unknown, minSigs?: number): ChildLinkV1 {
271
+ const what = "ChildLinkV1";
272
+ const obj = asObject(value, what);
273
+ requireV1(obj, what);
274
+ const childTier = reqFiniteNumber(obj, "childTier", what);
275
+ if (!Number.isInteger(childTier) || childTier < 1) {
276
+ fail(`${what}: childTier must be an integer >= 1, got ${childTier}`);
277
+ }
278
+ const thresholdSig = b64urlField(reqString(obj, "thresholdSig", what), "thresholdSig", what);
279
+ const signers = reqStringArray(obj, "signers", what).map((s) => b64urlField(s, "signers", what));
280
+ if (minSigs !== undefined && thresholdSig.length > 0 && signers.length < minSigs) {
281
+ fail(`${what}: a signed child-link needs signers.length >= ${minSigs}, got ${signers.length}`);
282
+ }
283
+ return {
284
+ v: 1,
285
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
286
+ childCohortCoord: b64urlFixedLen(reqString(obj, "childCohortCoord", what), "childCohortCoord", COORD_BYTES, what),
287
+ childParticipantCoord: b64urlField(reqString(obj, "childParticipantCoord", what), "childParticipantCoord", what),
288
+ childTier,
289
+ tier: tier(reqFiniteNumber(obj, "tier", what), what),
290
+ effectiveAt: reqFiniteNumber(obj, "effectiveAt", what),
291
+ thresholdSig,
292
+ signers,
293
+ cohortEpoch: b64urlField(reqString(obj, "cohortEpoch", what), "cohortEpoch", what),
294
+ };
295
+ }
296
+
297
+ /** Validate a {@link ChildLinkReplyV1}: `result` in `linked | rejected`, optional human-readable `reason`. */
298
+ export function validateChildLinkReplyV1(value: unknown): ChildLinkReplyV1 {
299
+ const what = "ChildLinkReplyV1";
300
+ const obj = asObject(value, what);
301
+ requireV1(obj, what);
302
+ const out: ChildLinkReplyV1 = {
303
+ v: 1,
304
+ result: reqEnum(obj, "result", ["linked", "rejected"] as const, what),
305
+ };
306
+ assignDefined(out, "reason", optString(obj, "reason", what));
307
+ return out;
308
+ }
309
+
308
310
  function validateCohortTopicSummary(value: unknown): CohortTopicSummary {
309
311
  const what = "CohortTopicSummary";
310
312
  const obj = asObject(value, what);
@@ -323,7 +325,7 @@ function validateGossipRecordV1(value: unknown): GossipRecordV1 {
323
325
  const what = "GossipRecordV1";
324
326
  const obj = asObject(value, what);
325
327
  const out: GossipRecordV1 = {
326
- topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
328
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
327
329
  participantId: b64urlField(reqString(obj, "participantId", what), "participantId", what),
328
330
  tier: tier(reqFiniteNumber(obj, "tier", what), what),
329
331
  primary: b64urlField(reqString(obj, "primary", what), "primary", what),
@@ -343,8 +345,19 @@ function validateGossipRecordRefV1(value: unknown): GossipRecordRefV1 {
343
345
  const what = "GossipRecordRefV1";
344
346
  const obj = asObject(value, what);
345
347
  return {
346
- topicId: b64urlField(reqString(obj, "topicId", what), "topicId", what),
348
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
347
349
  participantId: b64urlField(reqString(obj, "participantId", what), "participantId", what),
350
+ lastPing: reqFiniteNumber(obj, "lastPing", what),
351
+ };
352
+ }
353
+
354
+ function validateChildLinkRefV1(value: unknown): ChildLinkRefV1 {
355
+ const what = "ChildLinkRefV1";
356
+ const obj = asObject(value, what);
357
+ return {
358
+ topicId: b64urlFixedLen(reqString(obj, "topicId", what), "topicId", COORD_BYTES, what),
359
+ childCohortCoord: b64urlField(reqString(obj, "childCohortCoord", what), "childCohortCoord", what),
360
+ effectiveAt: reqFiniteNumber(obj, "effectiveAt", what),
348
361
  };
349
362
  }
350
363
 
@@ -372,11 +385,16 @@ export function validateCohortGossipV1(value: unknown): CohortGossipV1 {
372
385
  if (!Array.isArray(summaries)) {
373
386
  fail(`${what}: field "topicSummaries" must be an array`);
374
387
  }
388
+ const treeTier = reqFiniteNumber(obj, "treeTier", what);
389
+ if (!Number.isInteger(treeTier) || treeTier < 0) {
390
+ fail(`${what}: field "treeTier" must be a non-negative integer, got ${treeTier}`);
391
+ }
375
392
  const out: CohortGossipV1 = {
376
393
  v: 1,
377
394
  fromMember: reqString(obj, "fromMember", what),
378
- coord: b64urlField(reqString(obj, "coord", what), "coord", what),
395
+ coord: b64urlFixedLen(reqString(obj, "coord", what), "coord", COORD_BYTES, what),
379
396
  cohortEpoch: b64urlField(reqString(obj, "cohortEpoch", what), "cohortEpoch", what),
397
+ treeTier,
380
398
  willingnessBits,
381
399
  loadBuckets: loadBuckets as number[],
382
400
  windowSeconds: reqFiniteNumber(obj, "windowSeconds", what),
@@ -398,10 +416,24 @@ export function validateCohortGossipV1(value: unknown): CohortGossipV1 {
398
416
  }
399
417
  out.evicted = evicted.map(validateGossipRecordRefV1);
400
418
  }
419
+ const childLinks = obj["childLinks"];
420
+ if (childLinks !== undefined) {
421
+ if (!Array.isArray(childLinks)) {
422
+ fail(`${what}: field "childLinks" must be an array when present`);
423
+ }
424
+ out.childLinks = childLinks.map(validateChildLinkRefV1);
425
+ }
426
+ const childUnlinks = obj["childUnlinks"];
427
+ if (childUnlinks !== undefined) {
428
+ if (!Array.isArray(childUnlinks)) {
429
+ fail(`${what}: field "childUnlinks" must be an array when present`);
430
+ }
431
+ out.childUnlinks = childUnlinks.map(validateChildLinkRefV1);
432
+ }
401
433
  return out;
402
434
  }
403
435
 
404
- const SIGN_KINDS: readonly SignKind[] = ["membership", "promotion", "demotion", "rotation"];
436
+ const SIGN_KINDS: readonly SignKind[] = ["membership", "promotion", "demotion", "rotation", "childlink"];
405
437
 
406
438
  export function validateSignRequestV1(value: unknown): SignRequestV1 {
407
439
  const what = "SignRequestV1";
@@ -410,7 +442,7 @@ export function validateSignRequestV1(value: unknown): SignRequestV1 {
410
442
  return {
411
443
  v: 1,
412
444
  kind: reqEnum(obj, "kind", SIGN_KINDS, what),
413
- coord: b64urlField(reqString(obj, "coord", what), "coord", what),
445
+ coord: b64urlFixedLen(reqString(obj, "coord", what), "coord", COORD_BYTES, what),
414
446
  cohortEpoch: b64urlField(reqString(obj, "cohortEpoch", what), "cohortEpoch", what),
415
447
  payload: b64urlField(reqString(obj, "payload", what), "payload", what),
416
448
  };
@@ -440,7 +472,7 @@ export function validateMembershipCertV1(value: unknown): MembershipCertV1 {
440
472
  requireV1(obj, what);
441
473
  const out: MembershipCertV1 = {
442
474
  v: 1,
443
- cohortCoord: b64urlField(reqString(obj, "cohortCoord", what), "cohortCoord", what),
475
+ cohortCoord: b64urlFixedLen(reqString(obj, "cohortCoord", what), "cohortCoord", COORD_BYTES, what),
444
476
  cohortEpoch: b64urlField(reqString(obj, "cohortEpoch", what), "cohortEpoch", what),
445
477
  members: reqStringArray(obj, "members", what),
446
478
  stabilizedAt: reqFiniteNumber(obj, "stabilizedAt", what),
@@ -472,6 +504,8 @@ function validateRotationAttestation(obj: Record<string, unknown>, out: Membersh
472
504
  if (presentCount !== 3) {
473
505
  fail(`${what}: rotation attestation requires all of prevEpoch, rotationSig, rotationSigners — or none`);
474
506
  }
507
+ // prevEpoch is a prior cohortEpoch, so it inherits cohortEpoch's leniency (see the b64urlField note
508
+ // and debt-cohort-topic-pin-cohort-epoch).
475
509
  out.prevEpoch = b64urlField(prevEpoch!, "prevEpoch", what);
476
510
  out.rotationSig = b64urlField(rotationSig!, "rotationSig", what);
477
511
  out.rotationSigners = rotationSigners!;