@serfab/cadre-core 0.13.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -6
- package/dist/bootstrap-peer-store-file.js +2 -2
- package/dist/bootstrap-peer-store.d.ts +32 -12
- package/dist/bootstrap-peer-store.js +41 -15
- package/dist/bootstrap-peer-store.js.map +1 -1
- package/dist/cadre-node.d.ts +558 -80
- package/dist/cadre-node.js +1060 -170
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +2 -2
- package/dist/control-authorization.js +1 -0
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +13 -26
- package/dist/control-cohort.js +0 -26
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +299 -44
- package/dist/control-database.js +469 -69
- package/dist/control-database.js.map +1 -1
- package/dist/control-read-retry.d.ts +40 -11
- package/dist/control-read-retry.js +48 -15
- package/dist/control-read-retry.js.map +1 -1
- package/dist/control-retry.d.ts +58 -3
- package/dist/control-retry.js +58 -3
- package/dist/control-retry.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +103 -19
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +80 -11
- package/dist/control-write-retry.js +176 -29
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store.js +2 -2
- package/dist/index.d.ts +14 -7
- package/dist/index.js +33 -6
- package/dist/index.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +15 -7
- package/dist/node-local-snapshot.js +23 -8
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/peer-dial.d.ts +143 -0
- package/dist/peer-dial.js +176 -0
- package/dist/peer-dial.js.map +1 -0
- package/dist/relay-addrs.d.ts +90 -52
- package/dist/relay-addrs.js +91 -59
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +71 -3
- package/dist/relay-reservation.js +116 -8
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +30 -1
- package/dist/seed-bootstrap.js +62 -17
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/storage-scope.d.ts +62 -0
- package/dist/storage-scope.js +75 -0
- package/dist/storage-scope.js.map +1 -0
- package/dist/strand-database.d.ts +34 -16
- package/dist/strand-database.js +38 -24
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +152 -0
- package/dist/strand-first-sync-gate.js +208 -0
- package/dist/strand-first-sync-gate.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +87 -5
- package/dist/strand-formation-manager.js +109 -5
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +21 -1
- package/dist/strand-formation-protocol.js +30 -0
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +243 -6
- package/dist/strand-instance-manager.js +470 -37
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +20 -12
- package/dist/strand-member-key.js +20 -12
- package/dist/strand-member-key.js.map +1 -1
- package/dist/strand-member-registry.js +3 -0
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +361 -0
- package/dist/strand-membership-reconciler.js +548 -0
- package/dist/strand-membership-reconciler.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +131 -11
- package/dist/strand-membership-writer.js +396 -134
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +46 -14
- package/dist/strand-network-config.js +79 -23
- package/dist/strand-network-config.js.map +1 -1
- package/dist/strand-solicitation.d.ts +10 -1
- package/dist/strand-solicitation.js +5 -0
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +11 -7
- package/dist/strand-wake-protocol.js +20 -52
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.js +6 -0
- package/dist/strand-watcher.js.map +1 -1
- package/dist/timed-step.d.ts +12 -0
- package/dist/timed-step.js +28 -0
- package/dist/timed-step.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +19 -0
- package/dist/timeout-scheduler.js +22 -0
- package/dist/timeout-scheduler.js.map +1 -0
- package/dist/types.d.ts +262 -30
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/bootstrap-peer-store-file.ts +2 -2
- package/src/bootstrap-peer-store.ts +69 -27
- package/src/cadre-node.ts +7301 -6353
- package/src/control-authorization.ts +2 -1
- package/src/control-cohort.ts +122 -130
- package/src/control-database.ts +537 -69
- package/src/control-read-retry.ts +202 -167
- package/src/control-retry.ts +297 -190
- package/src/control-schema.ts +103 -19
- package/src/control-write-retry.ts +533 -383
- package/src/enrolled-machine-store.ts +2 -2
- package/src/index.ts +83 -6
- package/src/node-local-snapshot.ts +25 -8
- package/src/peer-dial.ts +215 -0
- package/src/relay-addrs.ts +458 -436
- package/src/relay-reservation.ts +951 -828
- package/src/seed-bootstrap.ts +76 -18
- package/src/storage-scope.ts +78 -0
- package/src/strand-database.ts +50 -27
- package/src/strand-first-sync-gate.ts +258 -0
- package/src/strand-formation-manager.ts +140 -5
- package/src/strand-formation-protocol.ts +43 -1
- package/src/strand-instance-manager.ts +577 -37
- package/src/strand-member-key.ts +20 -12
- package/src/strand-member-registry.ts +3 -0
- package/src/strand-membership-reconciler.ts +680 -0
- package/src/strand-membership-writer.ts +527 -189
- package/src/strand-network-config.ts +102 -24
- package/src/strand-solicitation.ts +16 -1
- package/src/strand-wake-protocol.ts +21 -66
- package/src/strand-watcher.ts +6 -0
- package/src/timed-step.ts +28 -0
- package/src/timeout-scheduler.ts +28 -0
- package/src/types.ts +1789 -1554
package/src/types.ts
CHANGED
|
@@ -1,1554 +1,1789 @@
|
|
|
1
|
-
import type { ConnectionGater, Libp2p, PeerId, PrivateKey } from '@libp2p/interface';
|
|
2
|
-
import type { IRawStorage, Libp2pTransports } from '@optimystic/db-p2p';
|
|
3
|
-
import type { IPeerNetwork, IRepo } from '@optimystic/db-core';
|
|
4
|
-
import type { PeerJoinBackfillConfig } from './peer-join-backfill.js';
|
|
5
|
-
import type { StrandRevocationEnforcementConfig } from './strand-revocation-enforcer.js';
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
12
|
-
import type {
|
|
13
|
-
import type {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
| { mode: '
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
*
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
*
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* `
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*/
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
*
|
|
344
|
-
* (
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* {@link
|
|
350
|
-
*/
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
* the
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
*/
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
* `
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
*
|
|
623
|
-
* `
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
* `
|
|
627
|
-
*
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
*
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
*
|
|
692
|
-
*
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
*
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
/**
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
*/
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
/**
|
|
894
|
-
*
|
|
895
|
-
*/
|
|
896
|
-
export interface
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
/**
|
|
919
|
-
|
|
920
|
-
*/
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
*
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
968
|
-
*
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1034
|
-
*
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
*
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
/**
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
/**
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
/**
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
*
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
/**
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
|
|
1168
|
-
*/
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
/**
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
/**
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
/**
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
/**
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
*/
|
|
1349
|
-
|
|
1350
|
-
/**
|
|
1351
|
-
|
|
1352
|
-
/**
|
|
1353
|
-
|
|
1354
|
-
/**
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
*
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
*
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
*/
|
|
1408
|
-
|
|
1409
|
-
/** The
|
|
1410
|
-
|
|
1411
|
-
/**
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
*
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
/**
|
|
1447
|
-
|
|
1448
|
-
*/
|
|
1449
|
-
|
|
1450
|
-
/**
|
|
1451
|
-
|
|
1452
|
-
/**
|
|
1453
|
-
|
|
1454
|
-
/**
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1
|
+
import type { ConnectionGater, Libp2p, PeerId, PrivateKey } from '@libp2p/interface';
|
|
2
|
+
import type { IRawStorage, Libp2pTransports } from '@optimystic/db-p2p';
|
|
3
|
+
import type { IPeerNetwork, IRepo } from '@optimystic/db-core';
|
|
4
|
+
import type { PeerJoinBackfillConfig } from './peer-join-backfill.js';
|
|
5
|
+
import type { StrandRevocationEnforcementConfig } from './strand-revocation-enforcer.js';
|
|
6
|
+
import type { StrandMembershipReconciliationConfig } from './strand-membership-reconciler.js';
|
|
7
|
+
import type { StrandFirstSyncConfig } from './strand-first-sync-gate.js';
|
|
8
|
+
import type { StrandDatabase } from './strand-database.js';
|
|
9
|
+
import type { SeedTrustPolicy } from './seed-trust-policy.js';
|
|
10
|
+
import type { KeyStore, KeyId } from './key-store.js';
|
|
11
|
+
import type { TrustedOwnerStore, TrustSource } from './trusted-owner-store.js';
|
|
12
|
+
import type { BootstrapPeerStore } from './bootstrap-peer-store.js';
|
|
13
|
+
import type { EnrolledMachineStore } from './enrolled-machine-store.js';
|
|
14
|
+
import type { PushNotifier } from './push-notifier.js';
|
|
15
|
+
import type { RevocableTable } from './control-authorization.js';
|
|
16
|
+
import type { ControlRetryAbandonment } from './control-retry.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extended Libp2p node with the coordinatedRepo attached by db-p2p's
|
|
20
|
+
* createLibp2pNode after node creation (not surfaced on the base Libp2p type).
|
|
21
|
+
*/
|
|
22
|
+
export interface Libp2pNodeWithRepo extends Libp2p {
|
|
23
|
+
coordinatedRepo: IRepo;
|
|
24
|
+
/**
|
|
25
|
+
* The node's key-addressed peer network, attached by db-p2p's factory alongside
|
|
26
|
+
* `coordinatedRepo` (`libp2p-node-base.ts`). Optional deliberately: the base
|
|
27
|
+
* factory always assigns it, but it is not part of the upstream node type, so
|
|
28
|
+
* declaring it required would be a claim this repo cannot enforce. The strand
|
|
29
|
+
* backfill (`peer-join-backfill.ts`) logs once and stays inert when it is absent.
|
|
30
|
+
*/
|
|
31
|
+
keyNetwork?: IPeerNetwork;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Node profile determines storage participation
|
|
36
|
+
*/
|
|
37
|
+
export type NodeProfile = 'transaction' | 'storage';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Strand filter configuration - determines which strands this node participates in
|
|
41
|
+
*/
|
|
42
|
+
export type StrandFilter =
|
|
43
|
+
| { mode: 'all' } // All strands (default for servers)
|
|
44
|
+
| { mode: 'sAppId'; sAppId: string } // Only strands for specific sApp
|
|
45
|
+
| { mode: 'strandId'; strandId: string } // Single specific strand
|
|
46
|
+
| { mode: 'none' }; // Control network only
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Latency hint for strand hibernation behavior
|
|
50
|
+
*/
|
|
51
|
+
export type LatencyHint = 'realtime' | 'interactive' | 'background' | 'archive';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hibernation timeout configuration per latency hint (in milliseconds)
|
|
55
|
+
*/
|
|
56
|
+
export interface HibernationTimeouts {
|
|
57
|
+
/** Time before transitioning from active to idle */
|
|
58
|
+
idleTimeout: number;
|
|
59
|
+
/** Time before transitioning from idle to hibernating */
|
|
60
|
+
hibernateTimeout: number;
|
|
61
|
+
/**
|
|
62
|
+
* Base interval for the FIRST check-in after hibernating. Successive
|
|
63
|
+
* no-activity check-ins escalate this delay by {@link checkInBackoffFactor}
|
|
64
|
+
* (see {@link HibernationManager}'s self-rescheduling check-in chain).
|
|
65
|
+
*/
|
|
66
|
+
checkInInterval: number;
|
|
67
|
+
/**
|
|
68
|
+
* Multiplier applied to the check-in delay after each check-in that finds no
|
|
69
|
+
* pending activity (must be >= 1; 1 disables escalation). Default 2 →
|
|
70
|
+
* doubling: base, base×2, base×4, … until {@link checkInMaxInterval}.
|
|
71
|
+
*/
|
|
72
|
+
checkInBackoffFactor: number;
|
|
73
|
+
/**
|
|
74
|
+
* Upper bound on the escalating check-in delay — the concrete realization of
|
|
75
|
+
* the architecture's "minutes → hours → days" ceiling for this hint. Backoff
|
|
76
|
+
* never schedules a delay longer than this.
|
|
77
|
+
*/
|
|
78
|
+
checkInMaxInterval: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Default hibernation timeouts per latency hint.
|
|
83
|
+
*
|
|
84
|
+
* `checkInInterval` is the BASE delay; `checkInMaxInterval` is the per-hint
|
|
85
|
+
* ceiling the exponential backoff escalates toward (interactive minutes→~1h,
|
|
86
|
+
* background minutes→~6h, archive ~1h→~3 days).
|
|
87
|
+
*/
|
|
88
|
+
export const HIBERNATION_TIMEOUTS: Record<LatencyHint, HibernationTimeouts> = {
|
|
89
|
+
realtime: {
|
|
90
|
+
idleTimeout: Infinity, // Never idle
|
|
91
|
+
hibernateTimeout: Infinity, // Never hibernate
|
|
92
|
+
checkInInterval: Infinity, // N/A
|
|
93
|
+
checkInBackoffFactor: 2,
|
|
94
|
+
checkInMaxInterval: Infinity // N/A
|
|
95
|
+
},
|
|
96
|
+
interactive: {
|
|
97
|
+
idleTimeout: 5 * 60 * 1000, // 5 minutes
|
|
98
|
+
hibernateTimeout: 15 * 60 * 1000, // 15 minutes after idle
|
|
99
|
+
checkInInterval: 30 * 1000, // base: 30 seconds
|
|
100
|
+
checkInBackoffFactor: 2,
|
|
101
|
+
checkInMaxInterval: 60 * 60 * 1000 // cap: ~1 hour
|
|
102
|
+
},
|
|
103
|
+
background: {
|
|
104
|
+
idleTimeout: 1 * 60 * 1000, // 1 minute
|
|
105
|
+
hibernateTimeout: 5 * 60 * 1000, // 5 minutes after idle
|
|
106
|
+
checkInInterval: 5 * 60 * 1000, // base: 5 minutes
|
|
107
|
+
checkInBackoffFactor: 2,
|
|
108
|
+
checkInMaxInterval: 6 * 60 * 60 * 1000 // cap: ~6 hours
|
|
109
|
+
},
|
|
110
|
+
archive: {
|
|
111
|
+
idleTimeout: 10 * 1000, // 10 seconds
|
|
112
|
+
hibernateTimeout: 30 * 1000, // 30 seconds after idle
|
|
113
|
+
checkInInterval: 60 * 60 * 1000, // base: 1 hour
|
|
114
|
+
checkInBackoffFactor: 2,
|
|
115
|
+
checkInMaxInterval: 3 * 24 * 60 * 60 * 1000 // cap: ~3 days
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Storage provider - either an `IRawStorage` instance or a factory function.
|
|
121
|
+
* Factory functions are useful for creating per-scope storage instances.
|
|
122
|
+
*
|
|
123
|
+
* **The argument is an opaque scope key.** Use it directly as a file name, directory
|
|
124
|
+
* name or database name: every key cadre-core mints stays within `[A-Za-z0-9._-]`, so
|
|
125
|
+
* no escaping is needed and none should be assumed. Do not parse it;
|
|
126
|
+
* `controlStorageScope` / `isControlStorageScope` (`storage-scope.ts`) are the
|
|
127
|
+
* supported way to mint and recognize the control key. The strand arm of that charset
|
|
128
|
+
* rule is intended but not yet checked — a strand id replicated in from another node
|
|
129
|
+
* in the party reaches this callback verbatim; see
|
|
130
|
+
* `tickets/backlog/bug-strand-scope-key-charset-unenforced`.
|
|
131
|
+
*
|
|
132
|
+
* **Called once per scope per runtime lifetime.** The scopes are the control database
|
|
133
|
+
* (once per `CadreNode.start()`) and each strand id (once per `startStrand`).
|
|
134
|
+
* Hibernation — `quiesceStrand` then `resumeStrand` — reuses the store already
|
|
135
|
+
* resolved for that strand and does NOT re-enter this callback.
|
|
136
|
+
*
|
|
137
|
+
* **The control scope is party-specific.** It is `controlStorageScope(partyId)`, not a
|
|
138
|
+
* fixed string, because the control database holds one party's own records — its
|
|
139
|
+
* strands, owner keys, peers, invitations, revocations. Two parties on one device ask
|
|
140
|
+
* for two different keys and MUST get two different stores, or a node started for one
|
|
141
|
+
* party reads the other's rows as its own.
|
|
142
|
+
*
|
|
143
|
+
* It IS re-entered for a scope after that scope's runtime has stopped
|
|
144
|
+
* (`stopStrand`, or a `stop()` then `start()` cycle on one `CadreNode`), so it must
|
|
145
|
+
* be able to hand back a store over the same durable backend a second time. A
|
|
146
|
+
* factory may mint a fresh object per call; it must not *need* to.
|
|
147
|
+
*
|
|
148
|
+
* cadre-core disposes only its own cache wrapper — it never closes the store you
|
|
149
|
+
* returned. Closing the underlying handle stays the embedder's job.
|
|
150
|
+
*
|
|
151
|
+
* **The single-instance form shares data across scopes by construction** — one store
|
|
152
|
+
* for every strand AND for every party's control database. An embedder that can serve
|
|
153
|
+
* more than one party must use the factory form. One instance handed to every scope
|
|
154
|
+
* also shares ONE cache wrapper (`wrapStorageWithCache` memoizes per inner instance),
|
|
155
|
+
* which counts its holders: a scope stopping releases only its own claim, and the
|
|
156
|
+
* wrapper is retired when the last scope releases it. The factory form is the better
|
|
157
|
+
* default for the further reason that it partitions each strand's data, which
|
|
158
|
+
* simplifies cleanup.
|
|
159
|
+
*/
|
|
160
|
+
export type RawStorageProvider = IRawStorage | ((scope: string) => IRawStorage);
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Storage configuration for storage profile nodes
|
|
164
|
+
*/
|
|
165
|
+
export interface StorageConfig {
|
|
166
|
+
/**
|
|
167
|
+
* Storage provider - either an IRawStorage instance or a factory function.
|
|
168
|
+
* See {@link RawStorageProvider} for how often cadre-core calls it, and for why
|
|
169
|
+
* the single-instance form cannot serve more than one party.
|
|
170
|
+
*
|
|
171
|
+
* The `scope` a factory receives is an opaque key already safe as a path or
|
|
172
|
+
* database-name segment (always within `[A-Za-z0-9._-]`), so each example below
|
|
173
|
+
* interpolates it directly.
|
|
174
|
+
*
|
|
175
|
+
* For Node.js environments, use FileRawStorage from @optimystic/db-p2p-storage-fs:
|
|
176
|
+
* ```typescript
|
|
177
|
+
* import { FileRawStorage } from '@optimystic/db-p2p-storage-fs';
|
|
178
|
+
* storage: { provider: (scope) => new FileRawStorage(`./data/${scope}`) }
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* For React Native, use the appropriate storage from @optimystic/db-p2p-storage-rn:
|
|
182
|
+
* ```typescript
|
|
183
|
+
* import { LevelDBRawStorage } from '@optimystic/db-p2p-storage-rn';
|
|
184
|
+
* // `db` is an open LevelDB handle (see `openOptimysticRNDb` over `rn-leveldb`)
|
|
185
|
+
* storage: { provider: (scope) => new LevelDBRawStorage(openDb(`sereus-${scope}`)) }
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* For in-memory storage (testing):
|
|
189
|
+
* ```typescript
|
|
190
|
+
* import { MemoryRawStorage } from '@optimystic/db-p2p';
|
|
191
|
+
* storage: { provider: () => new MemoryRawStorage() }
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
provider: RawStorageProvider;
|
|
195
|
+
quotaBytes?: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Network configuration for libp2p
|
|
200
|
+
*/
|
|
201
|
+
export interface NetworkConfig {
|
|
202
|
+
/**
|
|
203
|
+
* Addresses this machine's libp2p nodes bind. Omitted, `@optimystic/db-p2p`'s own
|
|
204
|
+
* default (`/ip4/0.0.0.0/tcp/0`) applies; `[]` means "cannot listen" and is honored
|
|
205
|
+
* as written (React Native hosts rely on that).
|
|
206
|
+
*
|
|
207
|
+
* The machine runs one control node plus one node per strand, so a FIXED port here
|
|
208
|
+
* can only belong to one of them. The control node binds it as configured; each
|
|
209
|
+
* strand node binds the same entry with its port rewritten to `0`, keeping the
|
|
210
|
+
* interface and transport (`/ws`, a specific-interface bind). A
|
|
211
|
+
* `<relay>/p2p-circuit` entry is untouched — the port inside it is the relay's, not
|
|
212
|
+
* a local bind. See `strand-network-config.ts`.
|
|
213
|
+
*
|
|
214
|
+
* **Only TCP, WebSocket (`/ws`, `/wss`) and circuit-relay addresses are bindable**
|
|
215
|
+
* unless {@link transports} supplies the factories for something else. A `/ws` entry
|
|
216
|
+
* switches the WebSocket transport on by itself; anything else — `/quic-v1`,
|
|
217
|
+
* `/webrtc`, `/webtransport` — refuses node start naming the address and the libp2p
|
|
218
|
+
* package it would need, rather than being dropped in silence by libp2p's transport
|
|
219
|
+
* manager. See `relay-addrs.ts` → `resolveTransportOptions`.
|
|
220
|
+
*/
|
|
221
|
+
listenAddrs?: string[];
|
|
222
|
+
/**
|
|
223
|
+
* Addresses to advertise to peers **instead of** `listenAddrs` — for a node behind
|
|
224
|
+
* NAT or a reverse proxy that is reachable at a different address than it binds
|
|
225
|
+
* (e.g. `mynode.example.com:4001` in front of a `0.0.0.0:4001` listener). Reaches
|
|
226
|
+
* libp2p's `addresses.announce` via `@optimystic/db-p2p`'s `NodeOptions.announceAddrs`.
|
|
227
|
+
*
|
|
228
|
+
* **Applies to this machine's CONTROL node only.** Strand nodes drop it: any concrete
|
|
229
|
+
* entry names a port, and that port is the control node's, so a strand node
|
|
230
|
+
* advertising it would send peers to the wrong node (`strand-network-config.ts`).
|
|
231
|
+
* Entries are still validated at node start whichever node ends up using them.
|
|
232
|
+
*
|
|
233
|
+
* **A non-empty value REPLACES the advertised set entirely.** Observed addresses,
|
|
234
|
+
* and the `/p2p-circuit` address earned from a {@link relayAddrs} reservation, are
|
|
235
|
+
* all dropped from what peers are told — so a node configured with both this and
|
|
236
|
+
* `relayAddrs` stops being reachable through its relay. `CadreNode.start()` warns
|
|
237
|
+
* when it sees that combination; it does not refuse it, since a node whose relay
|
|
238
|
+
* slot is decorative may legitimately want only the announced address.
|
|
239
|
+
*
|
|
240
|
+
* Reach for {@link appendAnnounceAddrs} instead in the common case — one publicly
|
|
241
|
+
* reachable address ADDED to everything else the node advertises. Or for
|
|
242
|
+
* {@link relayAddrs} alone, when the node has no stable public address to name and
|
|
243
|
+
* a relay reservation is what makes it dialable. (`inviteAddressResolver` is a
|
|
244
|
+
* narrower tool again: it substitutes a reachable address into invite payloads
|
|
245
|
+
* only, and changes nothing about what this node advertises on the wire.)
|
|
246
|
+
*
|
|
247
|
+
* An empty array means "unset" — it is dropped rather than forwarded, so it cannot
|
|
248
|
+
* land as an explicit empty announce set. An empty *entry* is a different thing and
|
|
249
|
+
* is rejected: a list of one blank string is non-empty, so it would replace every
|
|
250
|
+
* advertised address with nothing. Malformed entries are rejected on the same terms,
|
|
251
|
+
* both at node start — libp2p itself would not notice either until its first address
|
|
252
|
+
* lookup (see `announce-addrs.ts`), so this repo checks each entry up front.
|
|
253
|
+
*/
|
|
254
|
+
announceAddrs?: string[];
|
|
255
|
+
/**
|
|
256
|
+
* Addresses to advertise **in addition to** `listenAddrs` — the field most
|
|
257
|
+
* deployments actually want, since it makes a node reachable at a public address
|
|
258
|
+
* without discarding the observed and `/p2p-circuit` addresses it would otherwise
|
|
259
|
+
* advertise. Reaches libp2p's `addresses.appendAnnounce` via
|
|
260
|
+
* `@optimystic/db-p2p`'s `NodeOptions.appendAnnounceAddrs`.
|
|
261
|
+
*
|
|
262
|
+
* **Control node only**, on the same terms as {@link announceAddrs}.
|
|
263
|
+
*
|
|
264
|
+
* **Ignored while {@link announceAddrs} is non-empty** — that is libp2p's own
|
|
265
|
+
* precedence, applied upstream; this repo does not merge the two locally. Set one
|
|
266
|
+
* or the other, not both.
|
|
267
|
+
*
|
|
268
|
+
* Empty-array, blank-entry and malformed-entry handling all match {@link announceAddrs}.
|
|
269
|
+
*/
|
|
270
|
+
appendAnnounceAddrs?: string[];
|
|
271
|
+
/**
|
|
272
|
+
* Circuit-relay servers this node reserves a slot on, so peers can reach it
|
|
273
|
+
* from behind NAT. Each entry is the relay's DIRECT dial multiaddr ending in
|
|
274
|
+
* its peerId — `/dns4/relay.example.com/tcp/4001/p2p/12D3KooW…` — with no
|
|
275
|
+
* `/p2p-circuit` suffix (one is appended for you; an entry that already has
|
|
276
|
+
* one is passed through unchanged).
|
|
277
|
+
*
|
|
278
|
+
* On the CONTROL node this adds libp2p's bare `/p2p-circuit` SEARCH listener to
|
|
279
|
+
* {@link listenAddrs} (`relay-addrs.ts`) and `CadreNode.start()` drives the
|
|
280
|
+
* reservation itself once the control database is up — the ordering matters,
|
|
281
|
+
* because a listener that dials its relay from inside `libp2p.start()` put a
|
|
282
|
+
* sibling in this node's cohort before its own database existed, and a sibling
|
|
283
|
+
* that had not yet replicated this node's membership row refused the bring-up.
|
|
284
|
+
* STRAND nodes take the same search shape, one bare listener plus one reservation
|
|
285
|
+
* supervisor PER relay, fail-soft (`strand-network-config.ts`,
|
|
286
|
+
* `strand-instance-manager.ts`). A relay named here also becomes a
|
|
287
|
+
* delegate-announce target, so this node's strand nodes may reserve on it too
|
|
288
|
+
* (see `delegate-admission.ts`).
|
|
289
|
+
*
|
|
290
|
+
* Setting this gives the node a listener even when `listenAddrs` is `[]` —
|
|
291
|
+
* that is the point of naming a relay, but note that React Native hosts
|
|
292
|
+
* (`reference-app-rn`'s `cadre-phone.ts`) deliberately do NOT listen, and
|
|
293
|
+
* must not acquire a listener by accident.
|
|
294
|
+
*
|
|
295
|
+
* FAIL-FAST both ways: a malformed entry throws at node start rather than being
|
|
296
|
+
* silently dropped, and — while {@link requireRelay} stays at its default —
|
|
297
|
+
* a first reservation attempt that lands no `/p2p-circuit` address throws
|
|
298
|
+
* `RelayReservationFailedError` out of `start()`. The fail-soft posture over
|
|
299
|
+
* the same machinery is `CadreNode.reserveRelays()`.
|
|
300
|
+
*/
|
|
301
|
+
relayAddrs?: string[];
|
|
302
|
+
/**
|
|
303
|
+
* Must a relay named in {@link relayAddrs} have granted a reservation before
|
|
304
|
+
* `start()` is allowed to succeed? Default `true`: an operator who names a
|
|
305
|
+
* relay is telling this machine it has no other reachability, so a relay
|
|
306
|
+
* that will not have us on the first attempt should stop the boot loudly
|
|
307
|
+
* rather than leave a node nobody can reach.
|
|
308
|
+
*
|
|
309
|
+
* Set `false` for a node that must still boot with no network — a phone or a
|
|
310
|
+
* browser tab. The first attempt is still driven at the same point in
|
|
311
|
+
* `start()`; a lost reservation just logs instead of throwing, and the
|
|
312
|
+
* retry supervisor keeps trying in the background exactly as it does after a
|
|
313
|
+
* `CadreNode.reserveRelays()` call. A caller on this posture cannot infer
|
|
314
|
+
* dialability from `start()` resolving — read
|
|
315
|
+
* {@link CadreNode.getRelayReservationState} to find out.
|
|
316
|
+
*
|
|
317
|
+
* It buys a node that BOOTS, not a node that boots fast: `start()` still waits
|
|
318
|
+
* out that first attempt, which costs the drive's whole timeout
|
|
319
|
+
* (`DEFAULT_RELAY_RESERVE_TIMEOUT_MS`, 10 s) against a relay that is unreachable
|
|
320
|
+
* rather than merely refusing (`relay-reservation.ts` polls to the deadline, in
|
|
321
|
+
* case libp2p's own discovery lands a reservation independently).
|
|
322
|
+
*
|
|
323
|
+
* Softens only the RESERVATION half of {@link relayAddrs}'s fail-fast
|
|
324
|
+
* contract. A malformed `relayAddrs` entry still throws at config
|
|
325
|
+
* resolution regardless of this setting — a typo is an operator error
|
|
326
|
+
* whatever the posture — and a hand-written `<relay>/p2p-circuit` entry in
|
|
327
|
+
* {@link listenAddrs} is still rejected on the control node, since that
|
|
328
|
+
* rejection is about the listener shape, not about whether reachability is
|
|
329
|
+
* required.
|
|
330
|
+
*
|
|
331
|
+
* Has no effect on STRAND nodes, which are already fail-soft over
|
|
332
|
+
* `relayAddrs` regardless of this field (`strand-network-config.ts`,
|
|
333
|
+
* `strand-instance-manager.ts`).
|
|
334
|
+
*/
|
|
335
|
+
requireRelay?: boolean;
|
|
336
|
+
/**
|
|
337
|
+
* Enable circuit relay server - allows this node to relay connections for other peers.
|
|
338
|
+
* When undefined, defaults to true for storage profile nodes (they typically have
|
|
339
|
+
* better connectivity and uptime), false for transaction profile nodes.
|
|
340
|
+
*/
|
|
341
|
+
enableRelay?: boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Cap on concurrent circuit-relay reservations this node's relay server grants to
|
|
344
|
+
* peers it cannot (yet) recognize as authorized members — the boot-ordering window
|
|
345
|
+
* where a genuine member reserves its relay slot before its membership row has
|
|
346
|
+
* replicated here (see `membership-connection-gater.ts` → "The relay-reservation
|
|
347
|
+
* seam"). Authorized members and announced delegate peers are never counted against
|
|
348
|
+
* it. Default `MAX_UNAUTHORIZED_RELAY_RESERVATIONS` (8); 0 refuses every
|
|
349
|
+
* unauthorized reservation. Only meaningful while {@link enableRelay} is on.
|
|
350
|
+
*/
|
|
351
|
+
unauthorizedRelayReservationCap?: number;
|
|
352
|
+
/**
|
|
353
|
+
* Custom libp2p transports. When omitted, the default transports from
|
|
354
|
+
* `@optimystic/db-p2p` are used (TCP + circuit relay for Node.js).
|
|
355
|
+
*
|
|
356
|
+
* React Native apps must supply WebSocket-based transports because TCP
|
|
357
|
+
* is not available in the RN runtime:
|
|
358
|
+
* ```typescript
|
|
359
|
+
* import { webSockets } from '@libp2p/websockets';
|
|
360
|
+
* import { circuitRelayTransport } from '@libp2p/circuit-relay-v2';
|
|
361
|
+
*
|
|
362
|
+
* network: {
|
|
363
|
+
* transports: [webSockets(), circuitRelayTransport()],
|
|
364
|
+
* listenAddrs: [] // RN nodes typically cannot listen
|
|
365
|
+
* }
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
transports?: Libp2pTransports;
|
|
369
|
+
/**
|
|
370
|
+
* Optional async resolver returning the multiaddrs to embed in invites
|
|
371
|
+
* (and other owner-address contexts). When unset, `libp2pNode.getMultiaddrs()`
|
|
372
|
+
* is used. Hosts behind NAT supply this to substitute their DDNS hostname
|
|
373
|
+
* and externally-mapped port — see `@serfab/cadre-host`'s NatService.
|
|
374
|
+
*
|
|
375
|
+
* Returned addresses need NOT carry a `/p2p/<peerId>` suffix: `CadreNode`
|
|
376
|
+
* appends its own before publishing or dialing any of them, so a resolver
|
|
377
|
+
* cannot leave a sibling with the mixed suffixed/unsuffixed list that
|
|
378
|
+
* `libp2p.dial` rejects. Returning them already suffixed is equally fine.
|
|
379
|
+
*/
|
|
380
|
+
inviteAddressResolver?: () => Promise<string[]>;
|
|
381
|
+
/**
|
|
382
|
+
* Optional libp2p connection gater, threaded to both the control node and every
|
|
383
|
+
* strand's cohort node. libp2p's browser default denies dialing insecure
|
|
384
|
+
* WebSockets and private/loopback addresses; callers that must dial local or
|
|
385
|
+
* unsecured peers (the web reference dialing a `127.0.0.1/.../ws` responder,
|
|
386
|
+
* Playwright e2e, RN simulators) supply a permissive gater here — e.g.
|
|
387
|
+
* `{ denyDialMultiaddr: () => false }`. See `@optimystic/db-p2p`'s
|
|
388
|
+
* `libp2p-node-base` for the underlying option.
|
|
389
|
+
*
|
|
390
|
+
* On the CONTROL node this gater is composed with the built-in membership
|
|
391
|
+
* admission gate (`membership-connection-gater.ts`): every hook supplied here
|
|
392
|
+
* is honored unchanged, and on inbound encrypted connections a deny from
|
|
393
|
+
* either this gater or the membership policy denies.
|
|
394
|
+
*
|
|
395
|
+
* Strand cohort nodes: an OPEN strand's node receives this gater as-is (its
|
|
396
|
+
* peers are legitimately cross-party). A CLOSED strand's node composes
|
|
397
|
+
* revoked-peer denial onto it (`strand-revocation-enforcer.ts`) — every hook
|
|
398
|
+
* supplied here is still honored unchanged, and on the composed hooks
|
|
399
|
+
* (inbound encrypted connection, outbound peer dial, relay reservation) a
|
|
400
|
+
* deny from either this gater or the revocation check denies.
|
|
401
|
+
*/
|
|
402
|
+
connectionGater?: ConnectionGater;
|
|
403
|
+
/**
|
|
404
|
+
* Tuning for the proactive control-cohort dial routine
|
|
405
|
+
* ({@link CadreNode.reconcileControlCohort}), which keeps a party's control
|
|
406
|
+
* nodes connected so the `CadreControl` collections form a replicating cohort.
|
|
407
|
+
* Every field is optional; omit for the defaults
|
|
408
|
+
* ({@link DEFAULT_CONTROL_COHORT_TARGET_DEGREE} /
|
|
409
|
+
* {@link DEFAULT_CONTROL_COHORT_RECONCILE_MS} /
|
|
410
|
+
* {@link DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS} /
|
|
411
|
+
* {@link DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS} / `STRAND_PEER_ADDR_REFRESH_MS`).
|
|
412
|
+
*/
|
|
413
|
+
controlCohort?: {
|
|
414
|
+
/**
|
|
415
|
+
* Cap on the number of NON-owner siblings dialed per reconcile pass
|
|
416
|
+
* (backbone/owner members are always dialed and do not count). Defaults
|
|
417
|
+
* to {@link DEFAULT_CONTROL_COHORT_TARGET_DEGREE}.
|
|
418
|
+
*/
|
|
419
|
+
targetDegree?: number;
|
|
420
|
+
/**
|
|
421
|
+
* Recurring reconcile cadence in ms. Defaults to
|
|
422
|
+
* {@link DEFAULT_CONTROL_COHORT_RECONCILE_MS}.
|
|
423
|
+
*/
|
|
424
|
+
reconcileMs?: number;
|
|
425
|
+
/**
|
|
426
|
+
* Limit on dialing ONE peer — all of its candidate addresses together — in
|
|
427
|
+
* ms. Bounds a reconcile pass at (dialed siblings) × this. The same limit
|
|
428
|
+
* applies to the owner dials of `applySeed` and to `dialInvite`. Defaults to
|
|
429
|
+
* {@link DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS}.
|
|
430
|
+
*/
|
|
431
|
+
dialTimeoutMs?: number;
|
|
432
|
+
/**
|
|
433
|
+
* Limit on ONE candidate address's dial attempt, in ms, inside
|
|
434
|
+
* {@link dialTimeoutMs}. Each address is dialed on its own under this limit,
|
|
435
|
+
* so an address that never answers cannot use up the time the peer's other
|
|
436
|
+
* addresses needed. Defaults to
|
|
437
|
+
* {@link DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS}.
|
|
438
|
+
*/
|
|
439
|
+
perAddressDialTimeoutMs?: number;
|
|
440
|
+
/**
|
|
441
|
+
* How often each running strand re-resolves its siblings' strand-network
|
|
442
|
+
* addresses into its own libp2p address book — a step of the reconcile pass,
|
|
443
|
+
* on its own much longer throttle. Defaults to `STRAND_PEER_ADDR_REFRESH_MS`
|
|
444
|
+
* (10 min); must stay well under the peerStore's one-hour address expiry.
|
|
445
|
+
*/
|
|
446
|
+
strandAddrRefreshMs?: number;
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Hibernation configuration
|
|
452
|
+
*/
|
|
453
|
+
export interface HibernationConfig {
|
|
454
|
+
enabled: boolean;
|
|
455
|
+
defaultLatencyHint?: LatencyHint;
|
|
456
|
+
/** Custom timeouts per latency hint (overrides defaults) */
|
|
457
|
+
customTimeouts?: Partial<Record<LatencyHint, Partial<HibernationTimeouts>>>;
|
|
458
|
+
/**
|
|
459
|
+
* How long a hibernating strand stays resumed during a check-in to let its
|
|
460
|
+
* strand network connect to reachable cohort peers (and the app drive
|
|
461
|
+
* pull-on-read activity) before re-hibernating if still idle. Defaults to
|
|
462
|
+
* {@link DEFAULT_CHECKIN_WINDOW_MS}.
|
|
463
|
+
*/
|
|
464
|
+
checkInWindowMs?: number;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Default duration of the resume-and-probe window during a strand check-in.
|
|
469
|
+
* See {@link HibernationConfig.checkInWindowMs}.
|
|
470
|
+
*/
|
|
471
|
+
export const DEFAULT_CHECKIN_WINDOW_MS = 15 * 1000;
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Control network configuration
|
|
475
|
+
*/
|
|
476
|
+
export interface ControlNetworkConfig {
|
|
477
|
+
partyId: string;
|
|
478
|
+
bootstrapNodes: string[];
|
|
479
|
+
/** Optional path to the control schema file (defaults to bundled schema) */
|
|
480
|
+
schemaPath?: string;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Replication-breadth constants, both networks' cluster policies, and the strand
|
|
485
|
+
* resolver, re-exported so cadre embedders, the SQL plugin and the integration
|
|
486
|
+
* harness share one definition. Defined in `@serfab/quereus-plugin-sereus`
|
|
487
|
+
* because that package also creates libp2p nodes and cannot depend on this one.
|
|
488
|
+
* The control network's breadth is fixed ({@link CONTROL_REPLICATION_BREADTH},
|
|
489
|
+
* not configurable) and so is its consensus policy ({@link CONTROL_CLUSTER_POLICY}
|
|
490
|
+
* — notably the super-majority threshold, which it leaves at Optimystic's default
|
|
491
|
+
* by omission); the strand breadth defaults to
|
|
492
|
+
* {@link CadreNodeConfig.strandClusterSize} while its policy is the fixed
|
|
493
|
+
* {@link STRAND_CLUSTER_POLICY}.
|
|
494
|
+
*
|
|
495
|
+
* The two `*ClusterPolicy` BUILDERS are the same objects with the block-repair
|
|
496
|
+
* corroboration yardstick declared from the machines enrolled in this party
|
|
497
|
+
* ({@link resolveRepairYardstick}); handed no count, each returns its frozen base
|
|
498
|
+
* constant unchanged. A network picks the derived number up when its libp2p node is
|
|
499
|
+
* built, which for a strand is every wake from hibernation.
|
|
500
|
+
*/
|
|
501
|
+
export {
|
|
502
|
+
MIN_CLUSTER_SIZE,
|
|
503
|
+
CONTROL_REPLICATION_BREADTH,
|
|
504
|
+
CONTROL_CLUSTER_POLICY,
|
|
505
|
+
DEFAULT_STRAND_CLUSTER_SIZE,
|
|
506
|
+
STRAND_CLUSTER_POLICY,
|
|
507
|
+
resolveStrandClusterSize,
|
|
508
|
+
resolveRepairYardstick,
|
|
509
|
+
controlClusterPolicy,
|
|
510
|
+
strandClusterPolicy
|
|
511
|
+
} from '@serfab/quereus-plugin-sereus';
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Main configuration for a CadreNode
|
|
515
|
+
*/
|
|
516
|
+
export interface CadreNodeConfig {
|
|
517
|
+
/**
|
|
518
|
+
* If provided, use this keypair for the node identity (direct injection).
|
|
519
|
+
* Mutually exclusive with {@link keyStore} — supplying both is a configuration
|
|
520
|
+
* error (fail closed) thrown by `start()` before any network bring-up.
|
|
521
|
+
*/
|
|
522
|
+
privateKey?: PrivateKey;
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Pluggable secure store for node key material. When set, the node loads its
|
|
526
|
+
* identity from `keyStore` under {@link identityKeyId}, generating + persisting
|
|
527
|
+
* a fresh Ed25519 key on first run (protobuf bytes are the canonical stored
|
|
528
|
+
* form). Mutually exclusive with {@link privateKey}. Absent ⇒ use `privateKey`
|
|
529
|
+
* when set, else libp2p generates an ephemeral key.
|
|
530
|
+
*
|
|
531
|
+
* In the single-key reference model the owner signing key is *derived from*
|
|
532
|
+
* the node identity (see {@link CadreNode.getIdentityOwnerKey}), so
|
|
533
|
+
* protecting the identity in a secure enclave protects the owner key too.
|
|
534
|
+
* A future separate-owner slot (`ownerKeyId`) is anticipated but not
|
|
535
|
+
* built here.
|
|
536
|
+
*/
|
|
537
|
+
keyStore?: KeyStore;
|
|
538
|
+
|
|
539
|
+
/** Slot id for the node identity in {@link keyStore}. Default: `'cadre/identity'`. */
|
|
540
|
+
identityKeyId?: KeyId;
|
|
541
|
+
|
|
542
|
+
/** Control network connection settings */
|
|
543
|
+
controlNetwork: ControlNetworkConfig;
|
|
544
|
+
|
|
545
|
+
/** Node profile: transaction-only or storage */
|
|
546
|
+
profile: NodeProfile;
|
|
547
|
+
|
|
548
|
+
/** Which strands to participate in */
|
|
549
|
+
strandFilter?: StrandFilter;
|
|
550
|
+
|
|
551
|
+
/** Storage configuration (only for storage profile) */
|
|
552
|
+
storage?: StorageConfig;
|
|
553
|
+
|
|
554
|
+
/** Network configuration */
|
|
555
|
+
network?: NetworkConfig;
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Number of nodes Optimystic is told a **strand** network's replication cluster
|
|
559
|
+
* should have, for every strand this node starts. The control network is not
|
|
560
|
+
* configurable — it always replicates to {@link CONTROL_REPLICATION_BREADTH}
|
|
561
|
+
* nodes, which is above any party's node count, because every member reads the
|
|
562
|
+
* whole control database and a member that misses a write may never learn the
|
|
563
|
+
* fact (see that constant for the full reasoning).
|
|
564
|
+
*
|
|
565
|
+
* Every node on the same strand should use the same value: the number bounds the
|
|
566
|
+
* cohort a member independently derives, and Optimystic's cluster-membership gate
|
|
567
|
+
* can reject a coordinator's smaller declared set as a downsize against a larger
|
|
568
|
+
* derived view.
|
|
569
|
+
*
|
|
570
|
+
* Frozen when the strand's libp2p node is created; a change takes effect on
|
|
571
|
+
* restart, not when cadre membership grows. Defaults to
|
|
572
|
+
* {@link DEFAULT_STRAND_CLUSTER_SIZE} (4 — the smallest breadth whose 0.75
|
|
573
|
+
* super-majority still commits with one holder offline); anything below
|
|
574
|
+
* {@link MIN_CLUSTER_SIZE} is rejected before a node is created. The cost of a
|
|
575
|
+
* small value is replication breadth and reliance on read repair, not commit
|
|
576
|
+
* correctness — but at 2 that read repair cannot converge, because a lone
|
|
577
|
+
* corroborator's stale answer is accepted as the cluster's truth.
|
|
578
|
+
*/
|
|
579
|
+
strandClusterSize?: number;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Tuning for the strand peer-join block catch-up (see `peer-join-backfill.ts`):
|
|
583
|
+
* when a strand's libp2p node connects to a peer this runtime has not yet
|
|
584
|
+
* caught up, every block in the strand's own raw store is pushed to that peer,
|
|
585
|
+
* so a machine that joined the strand after blocks were committed still ends
|
|
586
|
+
* up physically holding them. Applies to every strand this node starts.
|
|
587
|
+
* Omit for the defaults (`DEFAULT_PEER_JOIN_BACKFILL`); `{ enabled: false }`
|
|
588
|
+
* restores the no-backfill behaviour. The control network runs its own,
|
|
589
|
+
* separately tuned catch-up — see {@link controlBackfill}.
|
|
590
|
+
*/
|
|
591
|
+
strandBackfill?: PeerJoinBackfillConfig;
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Tuning for the CONTROL network's peer-join block catch-up (same module,
|
|
595
|
+
* `peer-join-backfill.ts`, wired in `CadreNode.start`): when the control
|
|
596
|
+
* libp2p node connects to an AUTHORIZED party member this runtime has not yet
|
|
597
|
+
* caught up, every block in the control database's own raw store is pushed to
|
|
598
|
+
* it. This is what makes a control block committed while the writer was alone
|
|
599
|
+
* (collection headers written at genesis above all) physically reach members
|
|
600
|
+
* that joined later — without it, such a member that restarts offline reads
|
|
601
|
+
* the affected tables as empty. Pushes are gated on
|
|
602
|
+
* `CadreNode.isAuthorizedMember`, checked at push time, because the control
|
|
603
|
+
* network's inbound gate deliberately admits non-members in several states
|
|
604
|
+
* (seed delivery, enrollment, bootstrap/relay peers). Omit for the defaults
|
|
605
|
+
* (`DEFAULT_PEER_JOIN_BACKFILL`); `{ enabled: false }` disables it.
|
|
606
|
+
*/
|
|
607
|
+
controlBackfill?: PeerJoinBackfillConfig;
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Tuning for the CLOSED-strand revoked-peer gate
|
|
611
|
+
* (`strand-revocation-enforcer.ts`), applied to every closed strand this node
|
|
612
|
+
* starts: each strand node materializes the peer ids bound to REMOVED members
|
|
613
|
+
* (orphaned `Strand.MemberPeer` rows) and refuses them at the stream and
|
|
614
|
+
* connection layers, so removing a party cuts the network to its machines
|
|
615
|
+
* rather than only deleting its row. Open strands never arm it. Omit for the
|
|
616
|
+
* defaults (`DEFAULT_REVOCATION_POLL_INTERVAL_MS` refresh cadence);
|
|
617
|
+
* `{ enabled: false }` restores the pre-existing behaviour.
|
|
618
|
+
*/
|
|
619
|
+
strandRevocationEnforcement?: StrandRevocationEnforcementConfig;
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Tuning for the CLOSED-strand membership reconciler
|
|
623
|
+
* (`strand-membership-reconciler.ts`), applied to every closed strand this
|
|
624
|
+
* node launches with a party identity key: at bring-up each machine redeems a
|
|
625
|
+
* pending formation invitation (seating the party's `Strand.Member` row) and
|
|
626
|
+
* writes its own machine→party `Strand.MemberPeer` binding, retrying in the
|
|
627
|
+
* background without blocking bring-up. `pollIntervalMs` is the IDLE cadence
|
|
628
|
+
* (how often a machine nobody has admitted re-checks) and the cap of the
|
|
629
|
+
* unfinished-join retry ladder, NOT the retry cadence itself — an unfinished
|
|
630
|
+
* join retries from 1 s, doubling. Omit for the defaults (it mirrors
|
|
631
|
+
* {@link strandRevocationEnforcement}'s `pollIntervalMs`);
|
|
632
|
+
* `{ enabled: false }` disarms the loop — for test fixtures that hand-drive
|
|
633
|
+
* the membership writers and assert exact row sets.
|
|
634
|
+
*/
|
|
635
|
+
strandMembershipReconciliation?: StrandMembershipReconciliationConfig;
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Tuning for the JOINING machine's first-sync write gate
|
|
639
|
+
* (`strand-first-sync-gate.ts`), applied to every strand this node launches as a
|
|
640
|
+
* non-founder: the strand's database is withheld from the app (`StrandInstance.database`
|
|
641
|
+
* unset, status `'syncing'`) until the strand's `Strand.Header` has been received from
|
|
642
|
+
* another member and every `App` table has been read once, because a machine that writes
|
|
643
|
+
* to a table it has never fetched creates a private copy of it that never merges.
|
|
644
|
+
* `timeoutMs` bounds how long {@link CadreNode.addStrand} waits before rejecting with
|
|
645
|
+
* `StrandAwaitingFirstSyncError` (retryable — the launch stays up and keeps probing);
|
|
646
|
+
* `pollIntervalMs` is the probe cadence. Omit for the defaults (`DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS`, 30 s;
|
|
647
|
+
* `DEFAULT_STRAND_FIRST_SYNC_POLL_MS`, 500 ms). There is deliberately no way to disable
|
|
648
|
+
* the gate: a machine that already holds the Header is never gated, so nothing that works
|
|
649
|
+
* today is blocked by it.
|
|
650
|
+
*/
|
|
651
|
+
strandFirstSync?: StrandFirstSyncConfig;
|
|
652
|
+
|
|
653
|
+
/** Hibernation configuration */
|
|
654
|
+
hibernation?: HibernationConfig;
|
|
655
|
+
|
|
656
|
+
/** Polling interval for strand watcher in ms (default: 5000) */
|
|
657
|
+
strandWatchInterval?: number;
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Require a valid author signature on every sApp schema before a strand is
|
|
661
|
+
* formed or joined. Defaults to true (fail closed): an unsigned schema is
|
|
662
|
+
* rejected before any libp2p node or schema DDL is brought up. Set false ONLY
|
|
663
|
+
* for dev/test where unsigned demo schemas are used.
|
|
664
|
+
*/
|
|
665
|
+
requireSignedSchemas?: boolean;
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Node-wide default trust anchor for INBOUND control-network seeds. Forwarded
|
|
669
|
+
* into every SeedBootstrapService this node constructs (owner, receive-only
|
|
670
|
+
* listener, and the temp service used by applySeed when no service exists), and
|
|
671
|
+
* used as the service-level default the libp2p seed-protocol handler relies on
|
|
672
|
+
* (that path has no per-call override seam). Defaults to anchoredTrustPolicy()
|
|
673
|
+
* inside SeedBootstrapService when unset — a node whose node-local trusted-owner
|
|
674
|
+
* anchor was never seeded then rejects every seed. A per-call
|
|
675
|
+
* `applySeed(seed, { trustPolicy })` override still wins over this default for
|
|
676
|
+
* callers that hold out-of-band material (e.g. a pinned key from a CadreInvite).
|
|
677
|
+
*/
|
|
678
|
+
seedTrustPolicy?: SeedTrustPolicy;
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Node-local trusted-owner anchor (see `trusted-owner-store.ts`): the
|
|
682
|
+
* NON-replicated, per-party record of owner keys established out of band —
|
|
683
|
+
* the anchor membership/seed trust can rest on, since the replicated
|
|
684
|
+
* `OwnerKey` table can be polluted by a stranger's self-genesis. Absent ⇒ an
|
|
685
|
+
* in-memory store is created at start() (ephemeral: anchored trust does not
|
|
686
|
+
* survive the process).
|
|
687
|
+
*/
|
|
688
|
+
trustedOwners?: {
|
|
689
|
+
/**
|
|
690
|
+
* Injected store instance — e.g. a `FileTrustedOwnerStore` from the
|
|
691
|
+
* Node-only subpath `@serfab/cadre-core/trusted-owner-store-file`,
|
|
692
|
+
* persisted in the node's state directory (same injection/isolation pattern
|
|
693
|
+
* as {@link keyStore}). Its `partyId` must match `controlNetwork.partyId`;
|
|
694
|
+
* start() fails closed on a mismatch.
|
|
695
|
+
*/
|
|
696
|
+
store?: TrustedOwnerStore;
|
|
697
|
+
/**
|
|
698
|
+
* Owner keys (base64url ed25519) established out of band and seeded into
|
|
699
|
+
* the store during start(): operator pins (e.g. cadre-cli
|
|
700
|
+
* `--pin-owner-key` / `CADRE_OWNER_KEYS`) or a `CadreInvite.ownerKeys`
|
|
701
|
+
* already known at config time. Seeding is idempotent across restarts.
|
|
702
|
+
*/
|
|
703
|
+
pinnedKeys?: string[];
|
|
704
|
+
/**
|
|
705
|
+
* Provenance recorded for {@link pinnedKeys}. Default: 'operator'.
|
|
706
|
+
* ('genesis' is reserved for the node's own founding key, seeded
|
|
707
|
+
* internally by `initializeSeedBootstrap`.)
|
|
708
|
+
*/
|
|
709
|
+
pinnedSource?: Exclude<TrustSource, 'genesis'>;
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Node-local cold-start bootstrap-peer store (see `bootstrap-peer-store.ts`):
|
|
714
|
+
* the owner addresses an applied seed nominated, kept so
|
|
715
|
+
* `reconcileControlCohort`'s cold-start branch can keep retrying a node that
|
|
716
|
+
* was seeded but never managed to connect. Sibling of {@link trustedOwners} —
|
|
717
|
+
* same NON-replicated, per-party, injected-backend shape — but nothing here is
|
|
718
|
+
* trust-bearing: these are dial hints only, and every address is re-bound to
|
|
719
|
+
* its peer id before the dial. Absent ⇒ an in-memory store is created at
|
|
720
|
+
* start() (ephemeral: the retry set does not survive the process, and such a
|
|
721
|
+
* node is stranded permanently if it restarts before connecting).
|
|
722
|
+
*
|
|
723
|
+
* The Node CLI, the browser, and React Native all inject a durable backend
|
|
724
|
+
* today; only NativeScript stays ephemeral, tracked by the
|
|
725
|
+
* `ns-durable-node-local-stores` ticket (which covers the same gap for
|
|
726
|
+
* {@link trustedOwners}). A new platform needs no new store class: supply a
|
|
727
|
+
* `DurableSlot` for the platform's storage and inject
|
|
728
|
+
* `PersistentBootstrapPeerStore.open(slot, partyId)` /
|
|
729
|
+
* `PersistentTrustedOwnerStore.open(slot, partyId)`.
|
|
730
|
+
*/
|
|
731
|
+
bootstrapPeers?: {
|
|
732
|
+
/**
|
|
733
|
+
* Injected store instance — e.g. a `FileBootstrapPeerStore` from the
|
|
734
|
+
* Node-only subpath `@serfab/cadre-core/bootstrap-peer-store-file`, persisted
|
|
735
|
+
* in the node's state directory (same injection/isolation pattern as
|
|
736
|
+
* {@link keyStore} / {@link trustedOwners}). Its `partyId` must match
|
|
737
|
+
* `controlNetwork.partyId`; start() fails closed on a mismatch.
|
|
738
|
+
*/
|
|
739
|
+
store?: BootstrapPeerStore;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Node-local record of how many machines this party had enrolled the last time
|
|
744
|
+
* this node looked (see `enrolled-machine-store.ts`): the block-repair
|
|
745
|
+
* corroboration yardstick the CONTROL node declares when its libp2p node is
|
|
746
|
+
* built. Sibling of {@link trustedOwners} / {@link bootstrapPeers} — same
|
|
747
|
+
* NON-replicated, per-party, injected-backend shape — and, like
|
|
748
|
+
* {@link bootstrapPeers}, nothing here is trust-bearing: it is a repair hint,
|
|
749
|
+
* recomputed from `CadrePeer` rows the moment the control database is up.
|
|
750
|
+
*
|
|
751
|
+
* It exists only because the control node is created BEFORE that database, so
|
|
752
|
+
* the live count is unreadable at the one moment it is needed; remembering it
|
|
753
|
+
* across the restart is what breaks that deadlock. Absent ⇒ an in-memory store
|
|
754
|
+
* is created at start(), which cold-starts on every launch and therefore
|
|
755
|
+
* declares nothing — byte-for-byte the behaviour before this record existed.
|
|
756
|
+
* No embedder is forced to change.
|
|
757
|
+
*
|
|
758
|
+
* A party that grows applies the larger number on each node's NEXT launch,
|
|
759
|
+
* deliberately: Optimystic freezes the policy at node construction and offers
|
|
760
|
+
* no runtime setter, and a node still holding the old value runs at exactly
|
|
761
|
+
* today's behaviour, so there is no forced rebuild.
|
|
762
|
+
*
|
|
763
|
+
* A new platform needs no new store class: supply a `DurableSlot` for the
|
|
764
|
+
* platform's storage and inject `PersistentEnrolledMachineStore.open(slot, partyId)`.
|
|
765
|
+
*/
|
|
766
|
+
enrolledMachines?: {
|
|
767
|
+
/**
|
|
768
|
+
* Injected store instance — e.g. a `FileEnrolledMachineStore` from the
|
|
769
|
+
* Node-only subpath `@serfab/cadre-core/enrolled-machine-store-file`,
|
|
770
|
+
* persisted in the node's state directory. Its `partyId` must match
|
|
771
|
+
* `controlNetwork.partyId`; start() fails closed on a mismatch.
|
|
772
|
+
*/
|
|
773
|
+
store?: EnrolledMachineStore;
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Platform push-delivery for suspended mobile peers. When present, the node's
|
|
778
|
+
* server fan-out can deliver strand-wake data messages over the platform push
|
|
779
|
+
* channel (FCM/APNs). Absent ⇒ no platform push (control-network push-wake only).
|
|
780
|
+
*
|
|
781
|
+
* The `notifier` is **injected**, not constructed here: a Node host builds it
|
|
782
|
+
* from `@serfab/cadre-core/push-node` (`createPushNotifier(credentials)`) and
|
|
783
|
+
* passes the instance, so the FCM/APNs modules (`node:crypto`/`node:http2`)
|
|
784
|
+
* stay out of the cross-platform core graph. Ownership transfers to the node on
|
|
785
|
+
* injection — `CadreNode.stop` closes the notifier (via `PushFanoutService`),
|
|
786
|
+
* releasing its APNs HTTP/2 session; a host must NOT also close an injected
|
|
787
|
+
* notifier itself. `cooldownMs`/`debounceMs` carry the fan-out anti-spam policy.
|
|
788
|
+
*/
|
|
789
|
+
push?: {
|
|
790
|
+
/** The Node-constructed push router (see `@serfab/cadre-core/push-node`). */
|
|
791
|
+
notifier: PushNotifier;
|
|
792
|
+
/**
|
|
793
|
+
* Per-`(peer, strand)` minimum gap between push-wakes the fan-out emits.
|
|
794
|
+
* Default {@link DEFAULT_PUSH_COOLDOWN_MS} (5 min).
|
|
795
|
+
*/
|
|
796
|
+
cooldownMs?: number;
|
|
797
|
+
/**
|
|
798
|
+
* Per-strand burst-coalescing window: a second trigger within it is dropped.
|
|
799
|
+
* Default {@link DEFAULT_PUSH_DEBOUNCE_MS} (10 s).
|
|
800
|
+
*/
|
|
801
|
+
debounceMs?: number;
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Status of a strand instance.
|
|
807
|
+
*
|
|
808
|
+
* `'syncing'` is a JOINING machine whose runtime is up (strand libp2p node, background
|
|
809
|
+
* loops) but whose database is held back from the app until the strand's `Strand.Header`
|
|
810
|
+
* has been received from another member — see `strand-first-sync-gate.ts`. In that state
|
|
811
|
+
* `StrandInstance.database` is unset; the instance goes `'active'` (and `CadreNode` emits
|
|
812
|
+
* `strand:writable`) the moment the Header is readable. A founder, or a machine that has
|
|
813
|
+
* synced this strand before (restart, hibernation resume), never passes through it.
|
|
814
|
+
*/
|
|
815
|
+
export type StrandStatus =
|
|
816
|
+
| 'starting'
|
|
817
|
+
| 'syncing'
|
|
818
|
+
| 'active'
|
|
819
|
+
| 'idle'
|
|
820
|
+
| 'hibernating'
|
|
821
|
+
| 'stopping'
|
|
822
|
+
| 'stopped'
|
|
823
|
+
| 'error';
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* App information from strand header
|
|
827
|
+
*/
|
|
828
|
+
export interface SAppInfo {
|
|
829
|
+
id: string;
|
|
830
|
+
version: string;
|
|
831
|
+
schema: string;
|
|
832
|
+
signature?: string;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/**
|
|
836
|
+
* Strand instance state
|
|
837
|
+
*/
|
|
838
|
+
export interface StrandInstance {
|
|
839
|
+
strandId: string;
|
|
840
|
+
status: StrandStatus;
|
|
841
|
+
sAppInfo?: SAppInfo;
|
|
842
|
+
|
|
843
|
+
/** The libp2p node for this strand (only when live: syncing/active/idle) */
|
|
844
|
+
libp2pNode?: Libp2p;
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* The Quereus database for this strand — set only while the strand is live AND
|
|
848
|
+
* this machine may write to it. A joining machine that has not yet received the
|
|
849
|
+
* strand's `Strand.Header` from another member runs with `libp2pNode` set and this
|
|
850
|
+
* unset (status `'syncing'`); it is published when the Header arrives
|
|
851
|
+
* (`strand:writable`). Unset while hibernating.
|
|
852
|
+
*/
|
|
853
|
+
database?: StrandDatabase;
|
|
854
|
+
|
|
855
|
+
/** Membership info for closed strands */
|
|
856
|
+
memberKey?: string;
|
|
857
|
+
memberPrivateKey?: string;
|
|
858
|
+
|
|
859
|
+
/** Activity tracking */
|
|
860
|
+
connectedPeers: number;
|
|
861
|
+
lastActivity: Date;
|
|
862
|
+
nextCheckIn?: Date;
|
|
863
|
+
|
|
864
|
+
/** Latency hint from app or override */
|
|
865
|
+
latencyHint: LatencyHint;
|
|
866
|
+
|
|
867
|
+
/** Error message if status is 'error' */
|
|
868
|
+
error?: string;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Outcome of an on-demand {@link CadreNode.serviceWake} — the mobile push-wake
|
|
873
|
+
* cycle (resume → bounded window → re-hibernate-if-idle) run imperatively for a
|
|
874
|
+
* single strand.
|
|
875
|
+
*/
|
|
876
|
+
export interface ServiceWakeResult {
|
|
877
|
+
/** The strand the wake was requested for. */
|
|
878
|
+
strandId: string;
|
|
879
|
+
/**
|
|
880
|
+
* Whether the wake was actually serviced. `false` when the node is not
|
|
881
|
+
* running, or the strand is unknown to this node (not a member-participated
|
|
882
|
+
* strand) — never a thrown error, so a background task can branch on it.
|
|
883
|
+
*/
|
|
884
|
+
serviced: boolean;
|
|
885
|
+
/**
|
|
886
|
+
* Whether activity landed during the wake window. `true` leaves the strand
|
|
887
|
+
* active; `false` re-hibernates it (or the strand was already live, in which
|
|
888
|
+
* case it reflects current liveness). Always `false` when `serviced` is `false`.
|
|
889
|
+
*/
|
|
890
|
+
hadActivity: boolean;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/**
|
|
894
|
+
* Strand row from control network - basic membership info
|
|
895
|
+
*/
|
|
896
|
+
export interface StrandRow {
|
|
897
|
+
Id: string;
|
|
898
|
+
MemberPrivateKey: string | null;
|
|
899
|
+
Type: 'o' | 'c';
|
|
900
|
+
/**
|
|
901
|
+
* ed25519 (base64url) owner key of the MACHINE that published this row — the
|
|
902
|
+
* signer of the owner-signed `Strand` insert (`== context.OwnerKey`, pinned by
|
|
903
|
+
* the schema's `AuthorizedInsert`). Identifies the founding machine, so a
|
|
904
|
+
* launch with no explicit `founder` flag derives "am I the founder?" by
|
|
905
|
+
* comparing it to the node's own owner key (see `CadreNode.launchStrand`).
|
|
906
|
+
* `null` on a consent-seated strand (the unsigned `FormationUsage` branch
|
|
907
|
+
* records no trustworthy signer). Provenance, not content —
|
|
908
|
+
* `strandRowMismatches` excludes it from the identical-content comparison.
|
|
909
|
+
*/
|
|
910
|
+
FounderOwnerKey: string | null;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* sApp configuration provided by the hosting application when creating a strand.
|
|
915
|
+
* This is what the app developer provides - NOT loaded from the network.
|
|
916
|
+
*/
|
|
917
|
+
export interface SAppConfig {
|
|
918
|
+
/** Public key of the sApp author */
|
|
919
|
+
id: string;
|
|
920
|
+
/** Version of the sApp */
|
|
921
|
+
version: string;
|
|
922
|
+
/** The declarative schema DDL */
|
|
923
|
+
schema: string;
|
|
924
|
+
/**
|
|
925
|
+
* Author's signature over the schema for verification. Required under the
|
|
926
|
+
* default `requireSignedSchemas` node policy — a config without it is rejected
|
|
927
|
+
* before strand bring-up. Optional in the type because authoring/serialization
|
|
928
|
+
* must represent an as-yet-unsigned config; omitting it is honored only when
|
|
929
|
+
* the node explicitly relaxes the policy (`requireSignedSchemas: false`, dev/test).
|
|
930
|
+
*/
|
|
931
|
+
signature?: string;
|
|
932
|
+
/** Latency hint for hibernation behavior (optional, defaults to config) */
|
|
933
|
+
latencyHint?: LatencyHint;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/**
|
|
937
|
+
* Full strand configuration when adding a strand via the API
|
|
938
|
+
*/
|
|
939
|
+
export interface StrandConfig {
|
|
940
|
+
/** Strand row from control network */
|
|
941
|
+
strandRow: StrandRow;
|
|
942
|
+
/** sApp configuration provided by the hosting application */
|
|
943
|
+
sAppConfig: SAppConfig;
|
|
944
|
+
/**
|
|
945
|
+
* Whether THIS node is the strand's founder — the party that provisioned and
|
|
946
|
+
* published the strand (the responder in formation, the creator in host/solo
|
|
947
|
+
* paths; the same party that calls {@link CadreNode.publishStrand}). The founder
|
|
948
|
+
* runs the one-time membership bootstrap at bring-up (writes `Strand.Header`, and
|
|
949
|
+
* for a closed strand the founding `Member`+`Manager`). A joiner leaves this
|
|
950
|
+
* unset and writes nothing — it receives those rows via Optimystic sync.
|
|
951
|
+
*
|
|
952
|
+
* When unset, founder-ness is DERIVED from the row: this node founds iff
|
|
953
|
+
* {@link StrandRow.FounderOwnerKey} equals its own owner key. An explicit
|
|
954
|
+
* `true`/`false` wins over the derivation — the formation/responder flows pass
|
|
955
|
+
* it deliberately, since a consent-seated row carries a null column.
|
|
956
|
+
*/
|
|
957
|
+
founder?: boolean;
|
|
958
|
+
/**
|
|
959
|
+
* THIS party's own strand membership private key (base64 protobuf, as
|
|
960
|
+
* `generateStrandMemberKey` mints) for a CLOSED strand — the identity whose public
|
|
961
|
+
* key seats the founding `Member`/`Manager`. The explicit sibling of
|
|
962
|
+
* {@link founder}: normally the launch reads it from the party's control-layer
|
|
963
|
+
* `StrandPartyKey` row (minting one on the founding machine when absent), and an
|
|
964
|
+
* explicit value here WINS over that read — for callers whose row carries no
|
|
965
|
+
* founder provenance to heal against (an explicit `founder: true` over a hand-built
|
|
966
|
+
* row, as the test harness does). Deliberately NOT
|
|
967
|
+
* {@link StrandRow.MemberPrivateKey}, the strand-wide read secret every joining
|
|
968
|
+
* party receives — that key derives nobody's identity.
|
|
969
|
+
*/
|
|
970
|
+
partyMemberPrivateKey?: string;
|
|
971
|
+
/**
|
|
972
|
+
* Whether {@link CadreNode.addStrand} waits for a JOINING machine's first sync before it
|
|
973
|
+
* resolves. Default `true`: the returned instance is writable (`database` set, status
|
|
974
|
+
* `'active'`), or the call rejects with `StrandAwaitingFirstSyncError` after
|
|
975
|
+
* `CadreNodeConfig.strandFirstSync.timeoutMs` — retryable, the launch stays up. `false`
|
|
976
|
+
* returns as soon as the runtime is launched, possibly `'syncing'` with no `database`;
|
|
977
|
+
* the caller then wires its own peers (a hand-dialed test fixture) and awaits
|
|
978
|
+
* {@link CadreNode.whenStrandWritable} or the `strand:writable` event. A founder, or a
|
|
979
|
+
* machine that already holds the strand's `Strand.Header`, is never gated either way.
|
|
980
|
+
*/
|
|
981
|
+
awaitFirstSync?: boolean;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* What {@link CadreNode.foundStrand} needs to publish a strand's control row AND start it
|
|
986
|
+
* locally as its founder — the resumable one-call form of `publishStrand` + `addStrand`.
|
|
987
|
+
*
|
|
988
|
+
* No `strandRow`: the row is the OUTPUT of founding, either freshly published or read back
|
|
989
|
+
* from an interrupted earlier attempt. `StrandConfig` (the join/attach shape) takes one
|
|
990
|
+
* because there the row already exists and came from the control network.
|
|
991
|
+
*/
|
|
992
|
+
export interface FoundStrandConfig {
|
|
993
|
+
/** Unique strand identifier. Trimmed; blank is rejected. */
|
|
994
|
+
strandId: string;
|
|
995
|
+
/** `'o'` for open (default) or `'c'` for closed. */
|
|
996
|
+
type?: 'o' | 'c';
|
|
997
|
+
/**
|
|
998
|
+
* Membership key gating a closed strand. Required in practice for `type: 'c'` (the
|
|
999
|
+
* founder bootstrap rejects a null key), and IGNORED when the strand is already
|
|
1000
|
+
* published — the stored key wins, so read the resolved key back off the returned
|
|
1001
|
+
* instance rather than trusting a freshly minted one.
|
|
1002
|
+
*/
|
|
1003
|
+
memberPrivateKey?: string;
|
|
1004
|
+
/** sApp configuration the hosting application provides, as for {@link StrandConfig}. */
|
|
1005
|
+
sAppConfig: SAppConfig;
|
|
1006
|
+
/**
|
|
1007
|
+
* Forwarded to the attach ({@link StrandConfig.awaitFirstSync}). Matters only when the
|
|
1008
|
+
* call ATTACHES rather than founds — the stored row was published by a sibling machine
|
|
1009
|
+
* that won the founding race — in which case this machine is a joiner whose database
|
|
1010
|
+
* is withheld until that sibling's `Strand.Header` reaches it; default `true` waits
|
|
1011
|
+
* for that inside the call. A founding launch is never gated.
|
|
1012
|
+
*/
|
|
1013
|
+
awaitFirstSync?: boolean;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* What {@link CadreNode.foundStrand} hands back: the running instance PLUS the `Strand` row
|
|
1018
|
+
* the strand actually runs under.
|
|
1019
|
+
*
|
|
1020
|
+
* The row is returned separately rather than left for the caller to read off the instance
|
|
1021
|
+
* because on a resumed founding it is NOT the caller's input — a closed strand adopts the
|
|
1022
|
+
* stored `MemberPrivateKey`, discarding a freshly minted one. A caller that must carry the
|
|
1023
|
+
* membership key onward (to mint an invitation, say) reads it from HERE.
|
|
1024
|
+
*/
|
|
1025
|
+
export interface FoundStrandResult {
|
|
1026
|
+
/** The active local instance, as {@link CadreNode.addStrand} would have returned. */
|
|
1027
|
+
instance: StrandInstance;
|
|
1028
|
+
/** The live control-plane row: freshly published, or the one already there. */
|
|
1029
|
+
strandRow: StrandRow;
|
|
1030
|
+
/**
|
|
1031
|
+
* Whether THIS machine actually founded (ran / will have run the one-time
|
|
1032
|
+
* founder bootstrap). `false` when the resolved row was published by a
|
|
1033
|
+
* DIFFERENT machine's owner key — e.g. a sibling won a concurrent founding
|
|
1034
|
+
* race — in which case the call attached as a joiner instead, which is the
|
|
1035
|
+
* correct outcome (two machines bootstrapping one strand on separate replicas
|
|
1036
|
+
* is the double-`Header` hazard).
|
|
1037
|
+
*/
|
|
1038
|
+
founded: boolean;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* Result of creating a new cadre peer
|
|
1043
|
+
*/
|
|
1044
|
+
export interface CreatePeerResult {
|
|
1045
|
+
peerId: PeerId;
|
|
1046
|
+
privateKey: Uint8Array;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// ============================================================================
|
|
1050
|
+
// Member Registration API Types (from api.md)
|
|
1051
|
+
// ============================================================================
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Registration data for joining a strand as a member.
|
|
1055
|
+
* Sent from invited member to any cadre member to accept invitation.
|
|
1056
|
+
*/
|
|
1057
|
+
export interface MemberRegistration {
|
|
1058
|
+
/** The strand being joined */
|
|
1059
|
+
strandId: string;
|
|
1060
|
+
/** The member's public key for this strand */
|
|
1061
|
+
key: string;
|
|
1062
|
+
/** Peer IDs of the member's cadre nodes that will participate */
|
|
1063
|
+
peerIds: string[];
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Result of member registration
|
|
1068
|
+
*/
|
|
1069
|
+
export interface MemberRegistrationResult {
|
|
1070
|
+
success: boolean;
|
|
1071
|
+
reason?: string;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// ============================================================================
|
|
1075
|
+
// Strand Solicitation API Types (from api.md)
|
|
1076
|
+
// ============================================================================
|
|
1077
|
+
|
|
1078
|
+
/**
|
|
1079
|
+
* Open invitation for strand formation.
|
|
1080
|
+
* Shared out-of-band to allow strangers to form strands.
|
|
1081
|
+
*/
|
|
1082
|
+
export interface OpenInvitation {
|
|
1083
|
+
/** Unique token identifying this invitation */
|
|
1084
|
+
token: string;
|
|
1085
|
+
/** The sApp that will be used for the strand */
|
|
1086
|
+
sAppId: string;
|
|
1087
|
+
/** When this invitation expires */
|
|
1088
|
+
expiration: Date;
|
|
1089
|
+
/** Bootstrap addresses to contact the inviter's cadre */
|
|
1090
|
+
bootstrap: string[];
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* A single-use strand membership invitation carried on a closed-strand formation
|
|
1095
|
+
* result: the `Strand.Invite` keypair the responder (the host strand's founder party)
|
|
1096
|
+
* issued for this redemption. The joiner spends it — `consumeInvite` on its own strand
|
|
1097
|
+
* replica — to seat a `Strand.Member` row under its OWN party key, so joining no longer
|
|
1098
|
+
* hands out the founder's identity. Structurally identical to
|
|
1099
|
+
* `strand-membership-writer`'s `IssuedInvite`; declared here so the wire/type layer does
|
|
1100
|
+
* not import the writer.
|
|
1101
|
+
*/
|
|
1102
|
+
export interface StrandMembershipInvite {
|
|
1103
|
+
/** The invite ed25519 PUBLIC key (base64url) — the `Strand.Invite.Key` row. */
|
|
1104
|
+
inviteKey: string;
|
|
1105
|
+
/**
|
|
1106
|
+
* The invite ed25519 PRIVATE seed (base64url). A single-use bearer credential:
|
|
1107
|
+
* whoever holds it can `consumeInvite` exactly once. Same sensitivity class and
|
|
1108
|
+
* handling as `memberPrivateKey` — delivered only inside the validated,
|
|
1109
|
+
* post-approval formation result, never written to either side's control DB.
|
|
1110
|
+
*/
|
|
1111
|
+
invitePrivateKey: string;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Result of forming a strand via open invitation
|
|
1116
|
+
*/
|
|
1117
|
+
export interface FormStrandResult {
|
|
1118
|
+
/** The member key assigned to the initiator for this strand */
|
|
1119
|
+
memberKey: string;
|
|
1120
|
+
/** Private key for the invitation (for signing future messages) */
|
|
1121
|
+
invitePrivateKey: string;
|
|
1122
|
+
/** The strand that was created */
|
|
1123
|
+
strandId: string;
|
|
1124
|
+
/**
|
|
1125
|
+
* The strand's membership key (closed-strand read-gating secret), delivered through
|
|
1126
|
+
* the formation protocol after consent (provision-then-record). Present only for a
|
|
1127
|
+
* closed strand the responder returned with its key; undefined otherwise. Distinct
|
|
1128
|
+
* from {@link invitePrivateKey} (the initiator's own generated signing key).
|
|
1129
|
+
*/
|
|
1130
|
+
memberPrivateKey?: string;
|
|
1131
|
+
/**
|
|
1132
|
+
* The joiner's own single-use membership invitation into the (closed, bound) host
|
|
1133
|
+
* strand — see {@link StrandMembershipInvite}. Present only when the responder's
|
|
1134
|
+
* host strand is closed and its runtime issued one; absent for open strands and the
|
|
1135
|
+
* responder-provisions (unbound) path. `CadreNode.formStrand` itself persists the
|
|
1136
|
+
* joiner's party key and caches this invitation for the strand bring-up to redeem,
|
|
1137
|
+
* so an embedding app normally never touches it.
|
|
1138
|
+
*/
|
|
1139
|
+
membershipInvite?: StrandMembershipInvite;
|
|
1140
|
+
/**
|
|
1141
|
+
* The responder's live STRAND-network multiaddrs for the formed strand — the only
|
|
1142
|
+
* cross-party discovery seed there is, since the strand-addr RPC that resolves a
|
|
1143
|
+
* sibling's strand addresses answers own-party callers only.
|
|
1144
|
+
*
|
|
1145
|
+
* Always an array, frequently EMPTY: the responder discloses none when it holds no
|
|
1146
|
+
* live strand node for the strand it just provisioned (the responder-provisions path
|
|
1147
|
+
* mints a strand that has not launched yet). `CadreNode.formStrand` records these
|
|
1148
|
+
* itself, so an embedding app only needs them to seed a strand it launches OUTSIDE
|
|
1149
|
+
* that node.
|
|
1150
|
+
*
|
|
1151
|
+
* One-shot and in-memory: they are the responder's addresses at the moment of
|
|
1152
|
+
* formation and are never re-resolved — see `docs/strands.md`.
|
|
1153
|
+
*/
|
|
1154
|
+
strandAddrs: string[];
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Disclosure object provided during strand formation.
|
|
1159
|
+
* Contains identity and context information from the initiator.
|
|
1160
|
+
*/
|
|
1161
|
+
export interface StrandFormationDisclosure {
|
|
1162
|
+
/** Initiator's party identifier */
|
|
1163
|
+
partyId?: string;
|
|
1164
|
+
/** Additional identity bundle (app-specific) */
|
|
1165
|
+
identityBundle?: unknown;
|
|
1166
|
+
/** Human-readable purpose/reason for forming the strand */
|
|
1167
|
+
purpose?: string;
|
|
1168
|
+
/** Additional app-specific metadata */
|
|
1169
|
+
metadata?: Record<string, unknown>;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Arachnode ring participation stub - will be implemented when arachnode is built
|
|
1174
|
+
*/
|
|
1175
|
+
export interface ArachnodeConfig {
|
|
1176
|
+
/** Enable Arachnode subsystem (Ring Zulu + storage rings). Storage-profile only. */
|
|
1177
|
+
enableRingZulu: boolean;
|
|
1178
|
+
/** Storage ring participation (storage profile only) */
|
|
1179
|
+
storageRing?: {
|
|
1180
|
+
/** Which ring to participate in based on capacity */
|
|
1181
|
+
ring: number;
|
|
1182
|
+
/** Partition within the ring */
|
|
1183
|
+
partition?: number;
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* Events emitted by CadreNode
|
|
1189
|
+
*/
|
|
1190
|
+
export interface CadreNodeEvents {
|
|
1191
|
+
'strand:started': { strandId: string };
|
|
1192
|
+
'strand:stopped': { strandId: string };
|
|
1193
|
+
'strand:error': { strandId: string; error: Error };
|
|
1194
|
+
'strand:idle': { strandId: string };
|
|
1195
|
+
'strand:hibernating': { strandId: string };
|
|
1196
|
+
'strand:waking': { strandId: string };
|
|
1197
|
+
/**
|
|
1198
|
+
* Emitted when a strand that came up `'syncing'` — a joining machine whose database was
|
|
1199
|
+
* withheld until it received the strand's `Strand.Header` from another member — becomes
|
|
1200
|
+
* writable: `StrandInstance.database` is now set and the status is `'active'`. A strand
|
|
1201
|
+
* whose `strand:started` already reported `'active'` (a founder, or a machine that had
|
|
1202
|
+
* synced before) never emits this. The event form of what {@link CadreNode.addStrand}
|
|
1203
|
+
* awaits; an app that attaches without waiting (`awaitFirstSync: false`, or a strand the
|
|
1204
|
+
* watcher auto-launched) hangs its "waiting for the other member" screen on the pair.
|
|
1205
|
+
*/
|
|
1206
|
+
'strand:writable': { strandId: string };
|
|
1207
|
+
/**
|
|
1208
|
+
* Emitted when this node discovers it is no longer a member of a CLOSED
|
|
1209
|
+
* strand — its party was removed by a manager, or it left. Detected by the
|
|
1210
|
+
* strand's revoked-peer gate (`strand-revocation-enforcer.ts`) seeing this
|
|
1211
|
+
* node's own peer id in the revoked set, so it is BEST-EFFORT: it fires only
|
|
1212
|
+
* if the removal replicated here before the rest of the strand cut us off,
|
|
1213
|
+
* and a node that was already offline may never see it at all.
|
|
1214
|
+
*
|
|
1215
|
+
* Nothing is stopped or torn down for you. Treat it as "this strand will no
|
|
1216
|
+
* longer sync": remaining members refuse this node's streams, dials, and
|
|
1217
|
+
* connections, so reads keep working against whatever is stored locally while
|
|
1218
|
+
* writes stop propagating. Stopping the strand (`stopStrand`) or deleting its
|
|
1219
|
+
* data is the app's decision. Emitted once per removal — re-armed if a manager
|
|
1220
|
+
* re-admits this party, so a second removal is reported again — and a
|
|
1221
|
+
* hibernation wake rebuilds the gate, which may re-emit for a strand still
|
|
1222
|
+
* revoked.
|
|
1223
|
+
*/
|
|
1224
|
+
'strand:revoked': { strandId: string };
|
|
1225
|
+
/**
|
|
1226
|
+
* Emitted when the control network advertises a strand this node has no
|
|
1227
|
+
* registered `sAppConfig` for — i.e. a strand created by another member, or
|
|
1228
|
+
* one this node ran in a previous session (sApp configs are in-memory only and
|
|
1229
|
+
* do not survive `stop()`). The hosting app decides whether to join it
|
|
1230
|
+
* (register a config + `addStrand`, e.g. via a chat `joinChatStrand` helper).
|
|
1231
|
+
* Carries the full {@link StrandRow} so the app can join without re-querying
|
|
1232
|
+
* the control DB.
|
|
1233
|
+
*
|
|
1234
|
+
* **Fired exactly ONCE per strand per session, and it can fire before your
|
|
1235
|
+
* listener is attached.** The strand watcher's first poll runs inside
|
|
1236
|
+
* `CadreNode.start()` (100 ms after the watcher starts), so every strand
|
|
1237
|
+
* already stored for this party is normally offered while the embedding app is
|
|
1238
|
+
* still inside its own `start()` await. The watcher then records the strand as
|
|
1239
|
+
* seen and no later poll re-offers it, so a listener attached a moment late
|
|
1240
|
+
* misses those strands for the life of the process.
|
|
1241
|
+
*
|
|
1242
|
+
* So an app that auto-joins discovered strands must **subscribe first, then
|
|
1243
|
+
* drain `CadreNode.getDiscoveredStrands()`** — the map of strands no local
|
|
1244
|
+
* config claims. In that order a strand discovered between the two steps is
|
|
1245
|
+
* handled twice rather than not at all, which is why the join handler needs to
|
|
1246
|
+
* be idempotent (guard on an in-flight set, not only on
|
|
1247
|
+
* `getStrands().has(id)` — the strand manager tracks an instance only once
|
|
1248
|
+
* `addStrand` has resolved).
|
|
1249
|
+
*/
|
|
1250
|
+
'strand:discovered': { strandId: string; strand: StrandRow };
|
|
1251
|
+
'control:connected': void;
|
|
1252
|
+
'control:disconnected': void;
|
|
1253
|
+
/**
|
|
1254
|
+
* Emitted when the control-write retry funnel GAVE UP on a local control write — the
|
|
1255
|
+
* classifier declined the failure as non-transient, or every attempt (or the elapsed
|
|
1256
|
+
* budget) ran out. One event per abandoned write, carrying the operation label, how far
|
|
1257
|
+
* it got, why it stopped and the error.
|
|
1258
|
+
*
|
|
1259
|
+
* Exists because an abandoned BACKGROUND write is otherwise invisible: the node's own
|
|
1260
|
+
* self-address republish, the post-connect drain and the replication drain all fire
|
|
1261
|
+
* their writes unawaited with a `debug`-only catch, and that namespace is off unless
|
|
1262
|
+
* something enabled it — so the write is lost and nobody is told. A FOREGROUND write's
|
|
1263
|
+
* caller sees the rethrown error as well as this event.
|
|
1264
|
+
*
|
|
1265
|
+
* What an app does with it is its own call; the node itself only escalates ONE case to
|
|
1266
|
+
* the operator (a self-address republish that has been failing longer than a peer
|
|
1267
|
+
* record stays fresh, at which point other machines are already discarding this node's
|
|
1268
|
+
* address). Treat the rest as telemetry — a control write that was lost, not a strand
|
|
1269
|
+
* or connection state change.
|
|
1270
|
+
*/
|
|
1271
|
+
'control:write-abandoned': ControlRetryAbandonment;
|
|
1272
|
+
/** Emitted when a seed is received via the seed protocol */
|
|
1273
|
+
'seed:received': { partyId: string; peerId: string };
|
|
1274
|
+
/** Emitted when a seed is successfully applied */
|
|
1275
|
+
'seed:applied': { partyId: string; peersAdded: number };
|
|
1276
|
+
/** Emitted when seed application fails */
|
|
1277
|
+
'seed:error': { partyId: string; error: string };
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// ============================================================================
|
|
1281
|
+
// Peer Address Record / Resolution Types
|
|
1282
|
+
// ============================================================================
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* A peer's self-published, freshness-stamped, self-signed address record.
|
|
1286
|
+
*
|
|
1287
|
+
* This is the logical shape of a `CadreControl.CadrePeer` row. A node publishes
|
|
1288
|
+
* its own record (signing with the ed25519 key behind its PeerId); any node can
|
|
1289
|
+
* resolve another member's current signaling/relay multiaddrs from only its
|
|
1290
|
+
* PeerId by reading the record, re-verifying the self-signature against
|
|
1291
|
+
* `publicKey`, checking freshness, and applying a trust gate.
|
|
1292
|
+
*
|
|
1293
|
+
* The signature covers `peerId`, the comma-joined `addrs`, and `updatedAt` — see
|
|
1294
|
+
* `peerRecordSignedPayload`. `publicKey` is NOT inside the signed payload: it is
|
|
1295
|
+
* the key the signature is verified *with*, and its libp2p-identity binding to
|
|
1296
|
+
* `peerId` is checked separately at resolve time, so re-signing it would be
|
|
1297
|
+
* redundant.
|
|
1298
|
+
*/
|
|
1299
|
+
export interface PeerAddressRecord {
|
|
1300
|
+
/** libp2p peer ID (base58btc) — the row key. */
|
|
1301
|
+
peerId: string;
|
|
1302
|
+
/** ed25519 public key (base64url) whose libp2p identity IS `peerId`. */
|
|
1303
|
+
publicKey: string;
|
|
1304
|
+
/** Current dialable multiaddrs, signaling (`/p2p-circuit`) first. */
|
|
1305
|
+
addrs: string[];
|
|
1306
|
+
/** epoch ms — freshness; strictly increasing per peer. */
|
|
1307
|
+
updatedAt: number;
|
|
1308
|
+
/** ed25519 self-signature over the signed payload, base64url. */
|
|
1309
|
+
sig: string;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* One `CadreControl.CadrePeer` row as read by `ControlDatabase.queryCadrePeers`:
|
|
1314
|
+
* the addressing columns plus the persisted membership voucher.
|
|
1315
|
+
*
|
|
1316
|
+
* The voucher triple (`stampId`, `vouchOwner`, `vouchSig`) is what
|
|
1317
|
+
* {@link CadreNode.listAuthorizedMembers} re-checks against the node-local
|
|
1318
|
+
* trusted-owner anchor — it is null on a row written before the voucher columns
|
|
1319
|
+
* existed, and all three must be present for the row to be authorizable. Named
|
|
1320
|
+
* (rather than restated inline at each read site) so a future column addition
|
|
1321
|
+
* cannot reach the query without the predicate seeing it.
|
|
1322
|
+
*/
|
|
1323
|
+
export interface CadrePeerRow {
|
|
1324
|
+
/** libp2p peer ID (base58btc) — the row key. */
|
|
1325
|
+
peerId: string;
|
|
1326
|
+
/** Comma-joined dialable multiaddrs as stored, or null when unpublished. */
|
|
1327
|
+
multiaddr: string | null;
|
|
1328
|
+
/** Single-use anti-replay nonce the voucher signature is bound to. */
|
|
1329
|
+
stampId: string | null;
|
|
1330
|
+
/** ed25519 public key (base64url) of the owner that vouched this row. */
|
|
1331
|
+
vouchOwner: string | null;
|
|
1332
|
+
/** That owner's signature over `digest('CadreControl.CadrePeer', 'vouch', peerId, stampId)`, base64url. */
|
|
1333
|
+
vouchSig: string | null;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/** The voucher-bearing subset of a {@link CadrePeerRow} — what the authorized-membership predicate reads. */
|
|
1337
|
+
export type CadrePeerVoucherFields = Pick<CadrePeerRow, 'peerId' | 'stampId' | 'vouchOwner' | 'vouchSig'>;
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* One `CadreControl.Revocation` tombstone as read by
|
|
1341
|
+
* `ControlDatabase.queryRevocations`: the identity triple plus the `ReissuedAt`
|
|
1342
|
+
* counter an owner bumps (`ControlDatabase.reissueRevocations`) to re-write — and
|
|
1343
|
+
* therefore re-broadcast — a tombstone that committed while the node was alone.
|
|
1344
|
+
* Never the ledger marker: that row names no guarded table, and `queryRevocations`
|
|
1345
|
+
* skips it, which is what keeps `tableName` a {@link RevocableTable}.
|
|
1346
|
+
*/
|
|
1347
|
+
export interface RevocationRow {
|
|
1348
|
+
/** Which guarded table's stamp was retired. */
|
|
1349
|
+
tableName: RevocableTable;
|
|
1350
|
+
/** Primary key of the removed row (peer id / key / strand id). */
|
|
1351
|
+
rowKey: string;
|
|
1352
|
+
/** The retired single-use nonce — the tombstone's identity. */
|
|
1353
|
+
stampId: string;
|
|
1354
|
+
/** Monotonic re-issue counter; carries no semantics (see the schema comment). */
|
|
1355
|
+
reissuedAt: number;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* Outcome of `ControlDatabase.openRevocationLedger`: `'opened'` when this call filed the
|
|
1360
|
+
* singleton `Revocation` ledger marker, `'already-open'` when it was already filed (seen
|
|
1361
|
+
* by the in-lock guard, or filed first by another owner and refused on the primary key).
|
|
1362
|
+
* Either way the marker exists afterwards.
|
|
1363
|
+
*/
|
|
1364
|
+
export type RevocationLedgerOpenResult = 'opened' | 'already-open';
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* Outcome of {@link CadreNode.registerSelf}, surfaced so callers (e.g. the CLI
|
|
1368
|
+
* `--owner` branch) can log what happened:
|
|
1369
|
+
* - `inserted` — the first owner-signed INSERT of this node's `CadrePeer` row.
|
|
1370
|
+
* - `refreshed` — a self-signed UPDATE of an existing row (heartbeat / addr change).
|
|
1371
|
+
* Also reported when a first publish found its row seated by a concurrent
|
|
1372
|
+
* `authorizePeer` of this node's own id and fell through to the UPDATE — the write
|
|
1373
|
+
* really was an UPDATE, even though the caller's intent was a first publish.
|
|
1374
|
+
* - `skipped` — nothing written (no self-signing key, not yet a member with no
|
|
1375
|
+
* owner service to self-insert, or the row was removed mid-publish).
|
|
1376
|
+
*/
|
|
1377
|
+
export type SelfRegistrationOutcome = 'inserted' | 'refreshed' | 'skipped';
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* Options for {@link CadreNode.resolvePeerAddrs}.
|
|
1381
|
+
*/
|
|
1382
|
+
export interface ResolveOpts {
|
|
1383
|
+
/**
|
|
1384
|
+
* Freshness ceiling in ms. A record older than this is filtered out (a
|
|
1385
|
+
* resolver must never hand back a dead relay reservation). Defaults to
|
|
1386
|
+
* {@link DEFAULT_PEER_RECORD_MAX_AGE_MS}.
|
|
1387
|
+
*/
|
|
1388
|
+
maxAgeMs?: number;
|
|
1389
|
+
/** Return only `/p2p-circuit` signaling addrs (the WebRTC dial input). */
|
|
1390
|
+
signalingOnly?: boolean;
|
|
1391
|
+
/**
|
|
1392
|
+
* Pluggable trust gate, evaluated after signature + freshness pass. Defaults
|
|
1393
|
+
* to {@link currentMemberTrustPolicy} (any existing — therefore
|
|
1394
|
+
* owner-vouched — CadrePeer member is trusted). Inject a stricter policy
|
|
1395
|
+
* (e.g. from `seed-signerkey-trust-policy`) to reject otherwise-valid records
|
|
1396
|
+
* before dialing.
|
|
1397
|
+
*/
|
|
1398
|
+
trustPolicy?: PeerResolveTrustPolicy;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Context handed to a {@link PeerResolveTrustPolicy} when gating a resolution.
|
|
1403
|
+
*/
|
|
1404
|
+
export interface PeerResolveContext {
|
|
1405
|
+
/** The peer being resolved. */
|
|
1406
|
+
peerId: string;
|
|
1407
|
+
/** The record's ed25519 public key (already bound to `peerId` and verified). */
|
|
1408
|
+
publicKey: string;
|
|
1409
|
+
/** The party whose control network produced the record. */
|
|
1410
|
+
partyId: string;
|
|
1411
|
+
/** The full verified, fresh record. */
|
|
1412
|
+
record: PeerAddressRecord;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* Decides whether a signature-verified, fresh peer-address record should be
|
|
1417
|
+
* trusted enough to dial. The seam through which a richer trust model
|
|
1418
|
+
* (trust circle, pinned keys) composes with `resolvePeerAddrs`.
|
|
1419
|
+
*/
|
|
1420
|
+
export interface PeerResolveTrustPolicy {
|
|
1421
|
+
evaluate(ctx: PeerResolveContext): Promise<boolean> | boolean;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// ============================================================================
|
|
1425
|
+
// Device Token Registry Types
|
|
1426
|
+
// ============================================================================
|
|
1427
|
+
|
|
1428
|
+
/** Platform push channel a {@link DeviceTokenRecord} targets. */
|
|
1429
|
+
export type PushPlatform = 'fcm' | 'apns';
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* A mobile cadre peer's self-published, freshness-stamped, self-signed platform
|
|
1433
|
+
* push token.
|
|
1434
|
+
*
|
|
1435
|
+
* This is the logical shape of a `CadreControl.DeviceToken` row. A phone publishes
|
|
1436
|
+
* its own record (signing with the ed25519 key behind its PeerId); a server peer
|
|
1437
|
+
* resolves it from the phone's PeerId to deliver a push-wake over FCM/APNs when a
|
|
1438
|
+
* control-network dial cannot reach the OS-suspended process. The signature covers
|
|
1439
|
+
* `peerId`, `platform`, `token`, and `updatedAt` — see `deviceTokenSignedPayload`.
|
|
1440
|
+
*
|
|
1441
|
+
* Unlike {@link PeerAddressRecord}, the record carries NO public key: it is verified
|
|
1442
|
+
* against the `CadrePeer.PublicKey` bound to the same `peerId`, so a `DeviceToken`
|
|
1443
|
+
* with no backing `CadrePeer` row can never be resolved.
|
|
1444
|
+
*/
|
|
1445
|
+
export interface DeviceTokenRecord {
|
|
1446
|
+
/** libp2p peer ID (base58btc) — the row key and the CadrePeer this token belongs to. */
|
|
1447
|
+
peerId: string;
|
|
1448
|
+
/** Push channel: `'fcm'` (Android/Firebase) or `'apns'` (Apple). */
|
|
1449
|
+
platform: PushPlatform;
|
|
1450
|
+
/** Opaque platform device/registration token. */
|
|
1451
|
+
token: string;
|
|
1452
|
+
/** epoch ms — freshness; strictly increasing per self-update (replay guard). */
|
|
1453
|
+
updatedAt: number;
|
|
1454
|
+
/** ed25519 self-signature over the signed payload, base64url. */
|
|
1455
|
+
sig: string;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* A stored `CadreControl.DeviceToken` row: the self-signed
|
|
1460
|
+
* {@link DeviceTokenRecord} plus the row's single-use `StampId` nonce.
|
|
1461
|
+
*
|
|
1462
|
+
* The stamp is deliberately OUTSIDE `DeviceTokenRecord`: the peer's own `Sig`
|
|
1463
|
+
* covers only (peerId, platform, token, updatedAt), while the stamp is bound by
|
|
1464
|
+
* the OWNER's insert/delete approvals. Readers need it to drop a row whose stamp
|
|
1465
|
+
* has been retired into `CadreControl.Revocation` (a token resurrected by an
|
|
1466
|
+
* approval replay on a node that had not yet converged on the tombstone).
|
|
1467
|
+
*/
|
|
1468
|
+
export interface DeviceTokenRow extends DeviceTokenRecord {
|
|
1469
|
+
/** Single-use authorization nonce; rotates on every (re)insert. */
|
|
1470
|
+
stampId: string;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Options for {@link CadreNode.resolveDeviceToken}.
|
|
1475
|
+
*/
|
|
1476
|
+
export interface ResolveDeviceTokenOpts {
|
|
1477
|
+
/**
|
|
1478
|
+
* Optional freshness ceiling in ms. Defaults to NO ceiling (a record is fresh
|
|
1479
|
+
* as long as its `updatedAt` is positive): unlike a relay reservation, a device
|
|
1480
|
+
* push token stays valid until it rotates, so a long-suspended phone must remain
|
|
1481
|
+
* resolvable for push-wake. Set this to bound staleness explicitly.
|
|
1482
|
+
*/
|
|
1483
|
+
maxAgeMs?: number;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
// ============================================================================
|
|
1487
|
+
// Push Credentials (platform push delivery — see push-notifier.ts)
|
|
1488
|
+
// ============================================================================
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* Platform push-delivery credentials injected into a participating node's config
|
|
1492
|
+
* (`CadreNodeConfig.push`). Provisioned per spawned node by `cadre-host` and read
|
|
1493
|
+
* by `createPushNotifier` to construct the FCM and/or APNs senders. A platform
|
|
1494
|
+
* whose block is absent is simply not deliverable (the router returns a
|
|
1495
|
+
* best-effort "no credentials" failure rather than throwing).
|
|
1496
|
+
*
|
|
1497
|
+
* Secret hygiene: every `privateKey` field is a secret — never logged (treated
|
|
1498
|
+
* like the node's startup/seed tokens).
|
|
1499
|
+
*/
|
|
1500
|
+
export interface PushCredentials {
|
|
1501
|
+
/** FCM (Android / Firebase) service-account credentials. */
|
|
1502
|
+
fcm?: FcmCredentials;
|
|
1503
|
+
/** APNs (Apple) auth-key credentials. */
|
|
1504
|
+
apns?: ApnsCredentials;
|
|
1505
|
+
/**
|
|
1506
|
+
* Per-`(peer, strand)` minimum gap between push-wakes the server fan-out emits —
|
|
1507
|
+
* the anti-spam cooldown a chatty strand needs. Default
|
|
1508
|
+
* {@link DEFAULT_PUSH_COOLDOWN_MS} (5 min). In-memory, acceptably lossy across
|
|
1509
|
+
* restarts (`serviceWake` is idempotent, so a duplicate wake is harmless).
|
|
1510
|
+
*/
|
|
1511
|
+
cooldownMs?: number;
|
|
1512
|
+
/**
|
|
1513
|
+
* Per-strand burst-coalescing window for the fan-out: a second activity trigger
|
|
1514
|
+
* within this window is dropped (one wake is enough). Default
|
|
1515
|
+
* {@link DEFAULT_PUSH_DEBOUNCE_MS} (10 s).
|
|
1516
|
+
*/
|
|
1517
|
+
debounceMs?: number;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/** Google service-account fields needed for FCM HTTP v1 OAuth2. */
|
|
1521
|
+
export interface FcmCredentials {
|
|
1522
|
+
/** GCP project id → `POST https://fcm.googleapis.com/v1/projects/{projectId}/messages:send`. */
|
|
1523
|
+
projectId: string;
|
|
1524
|
+
/** Service-account email (JWT `iss`/`sub`). */
|
|
1525
|
+
clientEmail: string;
|
|
1526
|
+
/** Service-account RSA private key, PEM. Secret — never logged. */
|
|
1527
|
+
privateKey: string;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/** Apple APNs auth-key (.p8) fields. */
|
|
1531
|
+
export interface ApnsCredentials {
|
|
1532
|
+
/** APNs key id (JWT header `kid`). */
|
|
1533
|
+
keyId: string;
|
|
1534
|
+
/** Apple team id (JWT `iss`). */
|
|
1535
|
+
teamId: string;
|
|
1536
|
+
/** App bundle id → `apns-topic` header. */
|
|
1537
|
+
bundleId: string;
|
|
1538
|
+
/** `.p8` ES256 private key, PEM. Secret — never logged. */
|
|
1539
|
+
privateKey: string;
|
|
1540
|
+
/** `true` → `api.push.apple.com`; false/undefined → `api.sandbox.push.apple.com`. */
|
|
1541
|
+
production?: boolean;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// ============================================================================
|
|
1545
|
+
// Seed Bootstrap API Types (from architecture.md)
|
|
1546
|
+
// ============================================================================
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* A peer entry in a control network seed.
|
|
1550
|
+
* Contains connection information for a cadre peer.
|
|
1551
|
+
*/
|
|
1552
|
+
export interface SeedPeer {
|
|
1553
|
+
/** Peer ID (libp2p identity) */
|
|
1554
|
+
peerId: string;
|
|
1555
|
+
/** Multiaddrs for connecting to this peer */
|
|
1556
|
+
multiaddrs: string[];
|
|
1557
|
+
/** Whether this peer holds an owner key */
|
|
1558
|
+
isOwner: boolean;
|
|
1559
|
+
/** ed25519 public key (base64url) — present on owner peers for signerKey verification */
|
|
1560
|
+
publicKey?: string;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
/**
|
|
1564
|
+
* Control network seed for bootstrapping new nodes.
|
|
1565
|
+
* Pre-populates a new node's cache to solve the cold-start problem:
|
|
1566
|
+
* new nodes need control data to validate connections, but can't get
|
|
1567
|
+
* data without connecting first.
|
|
1568
|
+
*/
|
|
1569
|
+
export interface ControlNetworkSeed {
|
|
1570
|
+
/** Party ID this seed belongs to */
|
|
1571
|
+
partyId: string;
|
|
1572
|
+
/** Known peers in the cadre */
|
|
1573
|
+
peers: SeedPeer[];
|
|
1574
|
+
/** Signature over the seed by an owner key */
|
|
1575
|
+
signature: string;
|
|
1576
|
+
/** The owner key that signed this seed */
|
|
1577
|
+
signerKey: string;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Message sent from instigator to new node during seed delivery.
|
|
1582
|
+
* Delivered via /sereus/seed/1.0.0 libp2p protocol.
|
|
1583
|
+
*/
|
|
1584
|
+
export interface SeedMessage {
|
|
1585
|
+
/** Party ID for the cadre */
|
|
1586
|
+
partyId: string;
|
|
1587
|
+
/** Known peers in the cadre */
|
|
1588
|
+
peers: SeedPeer[];
|
|
1589
|
+
/** Signature by an owner key */
|
|
1590
|
+
signature: string;
|
|
1591
|
+
/** The owner key that signed this message */
|
|
1592
|
+
signerKey: string;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
/**
|
|
1596
|
+
* Acknowledgment message from new node to instigator.
|
|
1597
|
+
*/
|
|
1598
|
+
export interface SeedAckMessage {
|
|
1599
|
+
/** Whether the seed was accepted */
|
|
1600
|
+
accepted: boolean;
|
|
1601
|
+
/** Reason for rejection (if not accepted) */
|
|
1602
|
+
reason?: string;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// ============================================================================
|
|
1606
|
+
// Strand Wake (control-network push-wake protocol) — see strand-wake-protocol.ts
|
|
1607
|
+
// ============================================================================
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* Request sent over the control network's `WAKE_PROTOCOL` from a same-cadre peer
|
|
1611
|
+
* to a hibernating peer, asking it to bring a strand online and pull pending
|
|
1612
|
+
* activity. The receiver gates this on cadre membership before honoring it.
|
|
1613
|
+
*/
|
|
1614
|
+
export interface WakeRequest {
|
|
1615
|
+
/** The strand the sender knows has pending activity. */
|
|
1616
|
+
strandId: string;
|
|
1617
|
+
/** Optional cause hint, e.g. `"activity"` (a server saw new traffic) or `"manual"`. */
|
|
1618
|
+
reason?: string;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
/**
|
|
1622
|
+
* Acknowledgment of a {@link WakeRequest}, returned on the same stream.
|
|
1623
|
+
*/
|
|
1624
|
+
export interface WakeAck {
|
|
1625
|
+
/** Whether the receiver honored the wake (member + participated strand). */
|
|
1626
|
+
accepted: boolean;
|
|
1627
|
+
/** The strand's status after the wake (present when `accepted`). */
|
|
1628
|
+
status?: StrandStatus;
|
|
1629
|
+
/** Reason for rejection (non-member, unknown strand, malformed frame). */
|
|
1630
|
+
reason?: string;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// ============================================================================
|
|
1634
|
+
// Strand Address (control-network strand-address RPC) — see strand-addr-protocol.ts
|
|
1635
|
+
// ============================================================================
|
|
1636
|
+
|
|
1637
|
+
/**
|
|
1638
|
+
* Control-network request from one of a party's own cadre nodes to a sibling,
|
|
1639
|
+
* asking "what are your live multiaddrs for this strand's separate network?".
|
|
1640
|
+
* Used to seed a strand mesh from co-cadre nodes that already run that strand.
|
|
1641
|
+
* The receiver gates this on cadre membership before answering.
|
|
1642
|
+
*/
|
|
1643
|
+
export interface StrandAddrRequest {
|
|
1644
|
+
/** The strand whose strand-network address the requester wants to seed from. */
|
|
1645
|
+
strandId: string;
|
|
1646
|
+
/**
|
|
1647
|
+
* The derived transport peerId the requester's own strand-`strandId` node
|
|
1648
|
+
* runs as (see `strand-transport-key.ts`). Present → the responder records a
|
|
1649
|
+
* delegate admission grant for it, so its own connection gate (and thus its
|
|
1650
|
+
* circuit-relay server, when it runs one) admits that peerId; absent →
|
|
1651
|
+
* today's behavior exactly. See `delegate-admission.ts` for the trust model.
|
|
1652
|
+
*/
|
|
1653
|
+
delegatePeerId?: string;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
/**
|
|
1657
|
+
* Response to a {@link StrandAddrRequest}, carrying the responder's strand-node
|
|
1658
|
+
* multiaddrs, returned on the same stream.
|
|
1659
|
+
*/
|
|
1660
|
+
export interface StrandAddrResponse {
|
|
1661
|
+
/** Echoes the requested strand id (empty on a reject/error reply). */
|
|
1662
|
+
strandId: string;
|
|
1663
|
+
/**
|
|
1664
|
+
* Dialable strand-network multiaddr strings (signaling/`p2p-circuit` first);
|
|
1665
|
+
* empty when the responder is a non-member, or does not currently run that
|
|
1666
|
+
* strand, or the exchange failed.
|
|
1667
|
+
*/
|
|
1668
|
+
multiaddrs: string[];
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
/**
|
|
1672
|
+
* Options for authorizing a new peer in the cadre.
|
|
1673
|
+
*/
|
|
1674
|
+
export interface AuthorizePeerOptions {
|
|
1675
|
+
/** Peer ID to authorize */
|
|
1676
|
+
peerId: string;
|
|
1677
|
+
/** Optional multiaddrs for the peer */
|
|
1678
|
+
multiaddrs?: string[];
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
/**
|
|
1682
|
+
* Result of applying a seed to a node.
|
|
1683
|
+
*/
|
|
1684
|
+
export interface ApplySeedResult {
|
|
1685
|
+
/** Whether the seed was successfully applied */
|
|
1686
|
+
success: boolean;
|
|
1687
|
+
/** Number of peers added to the peer store */
|
|
1688
|
+
peersAdded: number;
|
|
1689
|
+
/** Error message if not successful */
|
|
1690
|
+
error?: string;
|
|
1691
|
+
/**
|
|
1692
|
+
* Owner-flagged seed peers this apply attempted to dial. Zero when the seed
|
|
1693
|
+
* carried no owner peer with an address, or when the seed was rejected before
|
|
1694
|
+
* the dial loop ran.
|
|
1695
|
+
*/
|
|
1696
|
+
ownerDialsAttempted: number;
|
|
1697
|
+
/**
|
|
1698
|
+
* How many of those dials threw. `ownerDialsAttempted > 0 && ownerDialsFailed
|
|
1699
|
+
* === ownerDialsAttempted` means "seeded but stranded": the seed itself was
|
|
1700
|
+
* accepted (`success: true`) but no owner could be reached, so this node has
|
|
1701
|
+
* no connection to bootstrap its control database from. The node retries those
|
|
1702
|
+
* addresses on every control-cohort reconcile pass (see
|
|
1703
|
+
* `CadreNode.dialColdStartBootstrap`), so this is a signal, not a fatal error.
|
|
1704
|
+
*
|
|
1705
|
+
* Zero failures is NOT proof of a live connection: the receiving node's
|
|
1706
|
+
* membership gate denies AFTER the dialer's upgrade completes (see
|
|
1707
|
+
* `createMembershipConnectionGater`), so a dial can resolve and then be torn
|
|
1708
|
+
* down. Treat a non-zero value as certain failure and zero as "no failure
|
|
1709
|
+
* observed".
|
|
1710
|
+
*/
|
|
1711
|
+
ownerDialsFailed: number;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// ============================================================================
|
|
1715
|
+
// Seed Helper Types
|
|
1716
|
+
// ============================================================================
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* Node network topology type for seed helper scenarios.
|
|
1720
|
+
*/
|
|
1721
|
+
export type NodeTopology = 'public' | 'nat';
|
|
1722
|
+
|
|
1723
|
+
/**
|
|
1724
|
+
* Invite code for phone-to-server enrollment.
|
|
1725
|
+
* Used when a server invites a phone to join the cadre.
|
|
1726
|
+
*/
|
|
1727
|
+
export interface CadreInvite {
|
|
1728
|
+
/** Party ID of the cadre */
|
|
1729
|
+
partyId: string;
|
|
1730
|
+
/** Multiaddrs to dial the owner */
|
|
1731
|
+
ownerAddrs: string[];
|
|
1732
|
+
/**
|
|
1733
|
+
* Owner ed25519 public keys (base64url) of the cadre, carried out-of-band
|
|
1734
|
+
* so a cold-start invitee can pin the trusted owner set before applying
|
|
1735
|
+
* any seed (the seed itself cannot vouch for its own signer). Populated by
|
|
1736
|
+
* `createInvite` from the issuer's OWN node-local trusted-owner anchor —
|
|
1737
|
+
* not its replicated `OwnerKey` table, because the invitee anchors whatever
|
|
1738
|
+
* arrives here and a stranger's genesis-inserted key must not ride an
|
|
1739
|
+
* otherwise-legitimate invite into a fresh node's anchor.
|
|
1740
|
+
*/
|
|
1741
|
+
ownerKeys?: string[];
|
|
1742
|
+
/** Optional invite token for validation */
|
|
1743
|
+
token?: string;
|
|
1744
|
+
/** Timestamp when invite was created */
|
|
1745
|
+
createdAt: number;
|
|
1746
|
+
/** Optional expiration timestamp */
|
|
1747
|
+
expiresAt?: number;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* Options for adding a drone via provider API.
|
|
1752
|
+
*/
|
|
1753
|
+
export interface AddDroneOptions {
|
|
1754
|
+
/** Peer ID of the new drone (returned from provider) */
|
|
1755
|
+
dronePeerId: string;
|
|
1756
|
+
/** Multiaddrs of the drone (returned from provider) */
|
|
1757
|
+
droneMultiaddrs: string[];
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
/**
|
|
1761
|
+
* Options for adding a phone via invite flow.
|
|
1762
|
+
*/
|
|
1763
|
+
export interface AddPhoneOptions {
|
|
1764
|
+
/** Peer ID of the phone (sent by phone when it connects) */
|
|
1765
|
+
phonePeerId: string;
|
|
1766
|
+
/** Invite token for validation (must match issued invite) */
|
|
1767
|
+
token?: string;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
/**
|
|
1771
|
+
* Result of preparing a seed for drone initialization.
|
|
1772
|
+
*/
|
|
1773
|
+
export interface DroneInitResult {
|
|
1774
|
+
/** The seed to send to the provider API */
|
|
1775
|
+
seed: ControlNetworkSeed;
|
|
1776
|
+
/** Base64url encoded seed for transport */
|
|
1777
|
+
encodedSeed: string;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
/**
|
|
1781
|
+
* Result of creating an invite for a phone.
|
|
1782
|
+
*/
|
|
1783
|
+
export interface InviteResult {
|
|
1784
|
+
/** The invite to share out-of-band */
|
|
1785
|
+
invite: CadreInvite;
|
|
1786
|
+
/** Base64url encoded invite for QR/link */
|
|
1787
|
+
encodedInvite: string;
|
|
1788
|
+
}
|
|
1789
|
+
|