@serfab/cadre-core 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/cadre-node.d.ts +38 -15
  2. package/dist/cadre-node.js +81 -29
  3. package/dist/cadre-node.js.map +1 -1
  4. package/dist/control-database.d.ts +24 -10
  5. package/dist/control-database.js +48 -16
  6. package/dist/control-database.js.map +1 -1
  7. package/dist/control-read-retry.d.ts +12 -0
  8. package/dist/control-read-retry.js +12 -0
  9. package/dist/control-read-retry.js.map +1 -1
  10. package/dist/control-write-retry.d.ts +5 -4
  11. package/dist/control-write-retry.js +24 -7
  12. package/dist/control-write-retry.js.map +1 -1
  13. package/dist/index.d.ts +3 -2
  14. package/dist/index.js +5 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/link-budget.d.ts +224 -0
  17. package/dist/link-budget.js +239 -0
  18. package/dist/link-budget.js.map +1 -0
  19. package/dist/peer-dial.d.ts +36 -17
  20. package/dist/peer-dial.js +37 -17
  21. package/dist/peer-dial.js.map +1 -1
  22. package/dist/peer-join-backfill.d.ts +58 -4
  23. package/dist/peer-join-backfill.js +166 -20
  24. package/dist/peer-join-backfill.js.map +1 -1
  25. package/dist/relay-reservation.d.ts +59 -11
  26. package/dist/relay-reservation.js +169 -43
  27. package/dist/relay-reservation.js.map +1 -1
  28. package/dist/seed-bootstrap.js +2 -1
  29. package/dist/seed-bootstrap.js.map +1 -1
  30. package/dist/strand-first-sync-gate.d.ts +105 -22
  31. package/dist/strand-first-sync-gate.js +121 -27
  32. package/dist/strand-first-sync-gate.js.map +1 -1
  33. package/dist/strand-instance-manager.d.ts +27 -2
  34. package/dist/strand-instance-manager.js +60 -13
  35. package/dist/strand-instance-manager.js.map +1 -1
  36. package/dist/strand-membership-reconciler.d.ts +200 -45
  37. package/dist/strand-membership-reconciler.js +302 -86
  38. package/dist/strand-membership-reconciler.js.map +1 -1
  39. package/dist/strand-membership-writer.d.ts +3 -2
  40. package/dist/strand-membership-writer.js +67 -90
  41. package/dist/strand-membership-writer.js.map +1 -1
  42. package/dist/strand-network-config.d.ts +2 -2
  43. package/dist/strand-network-config.js +2 -2
  44. package/dist/types.d.ts +105 -9
  45. package/dist/types.js +20 -4
  46. package/dist/types.js.map +1 -1
  47. package/package.json +8 -8
  48. package/src/cadre-node.ts +7449 -7397
  49. package/src/control-database.ts +48 -16
  50. package/src/control-read-retry.ts +214 -202
  51. package/src/control-write-retry.ts +24 -7
  52. package/src/index.ts +582 -563
  53. package/src/link-budget.ts +264 -0
  54. package/src/peer-dial.ts +39 -17
  55. package/src/peer-join-backfill.ts +648 -478
  56. package/src/relay-reservation.ts +1119 -951
  57. package/src/seed-bootstrap.ts +2 -1
  58. package/src/strand-first-sync-gate.ts +352 -258
  59. package/src/strand-instance-manager.ts +1520 -1458
  60. package/src/strand-membership-reconciler.ts +334 -92
  61. package/src/strand-membership-writer.ts +1962 -1987
  62. package/src/strand-network-config.ts +2 -2
  63. package/src/types.ts +104 -8
@@ -40,8 +40,8 @@
40
40
  * absorbed by the per-relay entries when one is.
41
41
  *
42
42
  * Everything else in `NetworkConfig` — `transports`, `noiseCrypto`, `connectionMonitor`,
43
- * `connectionGater`, `enableRelay` — is inherited by the caller; this module only owns
44
- * the two host-endpoint fields above and the relay listen shape. One caveat on
43
+ * `cohortQueryTimeoutMs`, `connectionGater`, `enableRelay` — is inherited by the caller; this
44
+ * module only owns the two host-endpoint fields above and the relay listen shape. One caveat on
45
45
  * `connectionGater`: an OPEN strand's node gets it unchanged, while a CLOSED strand's
46
46
  * node composes revoked-peer denial onto it in `strand-instance-manager.ts` (every
47
47
  * supplied hook still honored — see `strand-revocation-enforcer.ts`).
package/src/types.ts CHANGED
@@ -316,7 +316,8 @@ export interface NetworkConfig {
316
316
  *
317
317
  * It buys a node that BOOTS, not a node that boots fast: `start()` still waits
318
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
319
+ * (`DEFAULT_RELAY_RESERVE_TIMEOUT_MS`, four link round trips at
320
+ * {@link linkRoundTripMs} — 14 s at its default) against a relay that is unreachable
320
321
  * rather than merely refusing (`relay-reservation.ts` polls to the deadline, in
321
322
  * case libp2p's own discovery lands a reservation independently).
322
323
  *
@@ -406,6 +407,59 @@ export interface NetworkConfig {
406
407
  * see {@link DEFAULT_CONNECTION_MONITOR} for why.
407
408
  */
408
409
  connectionMonitor?: Libp2pConnectionMonitorInit;
410
+ /**
411
+ * How long ONE cohort peer gets to answer ONE read-path request, in milliseconds, for the
412
+ * control node and every strand node — as {@link connectionMonitor} is, and for the same
413
+ * reason: the setting describes the LINK, and a phone's control node and its strand nodes
414
+ * ride the same one. Omitted takes {@link COHORT_READ_DEADLINE_MS} (5000 ms), chosen for two
415
+ * parties reaching each other only through a relay; Optimystic's own default is 1000 ms.
416
+ *
417
+ * Raise it for a link slower still, lower it for a deployment that is all LAN and wants a
418
+ * departed peer to stop holding up a read sooner. The cost of a larger value is that a peer
419
+ * which is truly gone holds a read of a block missing locally for that long before the read
420
+ * is declined and retried, and a joining machine's first sync runs several such consults —
421
+ * so a change here should be weighed against
422
+ * {@link CadreNodeConfig.strandFirstSync}'s budget. The measurement behind the default, and
423
+ * what it costs, are on {@link COHORT_READ_DEADLINE_MS}.
424
+ *
425
+ * Handed to db-p2p's `clusterPolicy.cohortQueryTimeoutMs` unchanged and NOT re-validated
426
+ * here. Optimystic refuses a value that is not a finite number above zero, or is above its
427
+ * `MAX_COHORT_QUERY_TIMEOUT_MS` (about 4.97 days — the ceiling exists because a unit mix-up
428
+ * is the one way to exceed it), by throwing where the libp2p node is built: inside
429
+ * `CadreNode.start()` for the control network, and inside `CadreNode.addStrand` for a strand.
430
+ * Fractional values are accepted — this is a duration, not a count of peers.
431
+ */
432
+ cohortQueryTimeoutMs?: number;
433
+ /**
434
+ * The round trip this node assumes between itself and another machine, in milliseconds, for
435
+ * the control node and every strand node — as {@link cohortQueryTimeoutMs} is, and for the
436
+ * same reason: the setting describes the LINK, and a phone's control node and its strand
437
+ * nodes ride the same one. Omitted takes {@link DECLARED_LINK_ROUND_TRIP_MS} (3500 ms).
438
+ *
439
+ * This is NOT a timeout. It is the one stated assumption that cadre's own dial and
440
+ * reservation deadlines are DERIVED from, each by the number of round trips that operation
441
+ * was measured to cost: a peer-join catch-up's dial to one peer, its push response, one relay
442
+ * reservation drive, the control-cohort dial budgets, and libp2p's own `dialTimeout` and
443
+ * `inboundUpgradeTimeout` on every node. Reaching another machine through a relay costs a
444
+ * fixed number of exchanges, so a deadline written as milliseconds has a link speed above
445
+ * which it can never open a connection — which is the defect this declaration exists to make
446
+ * impossible to reintroduce one budget at a time. The counts, the measurement behind them,
447
+ * and what still fails at the supported link are in `link-budget.ts`.
448
+ *
449
+ * The default covers the slowest link sereus supports, a 3-second round trip through a relay.
450
+ * Raise it for a link slower still; the cost is the ordinary cost of longer deadlines, a peer
451
+ * that is genuinely gone holding each operation that much longer before it is abandoned and
452
+ * retried. Lower it only if EVERY machine of the party is that close: this node is also the
453
+ * listener for connections other machines open to it, and its `inboundUpgradeTimeout` comes
454
+ * from this value, so a node declaring a faster link than its peers discards their
455
+ * half-built connections — silently, from the dialer's side.
456
+ *
457
+ * Refused where the libp2p node is built — inside `CadreNode.start()` for the control
458
+ * network, inside `CadreNode.addStrand` for a strand — if it is not a finite number above
459
+ * zero, because every consumer multiplies it into a deadline where a zero means "give up at
460
+ * once" and a `NaN` means "never".
461
+ */
462
+ linkRoundTripMs?: number;
409
463
  /**
410
464
  * Optional async resolver returning the multiaddrs to embed in invites
411
465
  * (and other owner-address contexts). When unset, `libp2pNode.getMultiaddrs()`
@@ -518,6 +572,12 @@ export interface NetworkConfig {
518
572
  * kept it (the same pair aborted at a 200ms stall only when the deadline was 300ms). An
519
573
  * interval strictly above the deadline is what makes the 30 seconds real.
520
574
  *
575
+ * WHAT THE LINK NEEDS OF IT. One ping opens a fresh stream and echoes over it: a protocol
576
+ * negotiation plus the echo, two link round trips by `link-budget.ts`'s counts (a `newStream`
577
+ * over a relayed circuit measured 3016 ms at 1500 ms one-way). At the slowest link sereus
578
+ * supports, a 3-second relayed round trip, that is about 6 s — well inside the 30 s deadline,
579
+ * which exists for the phone's CPU rather than the link.
580
+ *
521
581
  * WHAT IT COSTS. A dead peer is reclaimed 30 to 65 seconds after it stops answering — the
522
582
  * deadline, plus up to one interval of waiting for the ping that will fail — where
523
583
  * libp2p's defaults took about 5 to 15 seconds. `db-p2p` caps a node at 16 connections,
@@ -594,12 +654,16 @@ export interface ControlNetworkConfig {
594
654
  *
595
655
  * The two `*ClusterPolicy` BUILDERS are the same objects with the block-repair
596
656
  * corroboration yardstick declared from the machines enrolled in this party
597
- * ({@link resolveRepairYardstick}); handed no count, each returns its frozen base
598
- * constant unchanged. A network picks the derived number up when its libp2p node is
599
- * built, which for a strand is every wake from hibernation.
657
+ * ({@link resolveRepairYardstick}) and, if the host set one, its own per-peer read
658
+ * deadline in place of {@link COHORT_READ_DEADLINE_MS}. Both arrive in one named
659
+ * declarations object, because both are plain numbers meaning unrelated things;
660
+ * declaring neither returns the frozen base constant unchanged. A network picks the
661
+ * derived numbers up when its libp2p node is built, which for a strand is every wake
662
+ * from hibernation.
600
663
  */
601
664
  export {
602
665
  MIN_CLUSTER_SIZE,
666
+ COHORT_READ_DEADLINE_MS,
603
667
  CONTROL_REPLICATION_BREADTH,
604
668
  CONTROL_CLUSTER_POLICY,
605
669
  DEFAULT_STRAND_CLUSTER_SIZE,
@@ -610,6 +674,14 @@ export {
610
674
  strandClusterPolicy
611
675
  } from '@serfab/quereus-plugin-sereus';
612
676
 
677
+ /**
678
+ * The declared link round trip every cadre-owned dial and reservation deadline is derived from,
679
+ * re-exported beside {@link NetworkConfig.linkRoundTripMs} so a host reading the setting finds
680
+ * the default it replaces. The counts, the measurement and the derivation live in
681
+ * `link-budget.ts`.
682
+ */
683
+ export { DECLARED_LINK_ROUND_TRIP_MS } from './link-budget.js';
684
+
613
685
  /**
614
686
  * Main configuration for a CadreNode
615
687
  */
@@ -743,10 +815,12 @@ export interface CadreNodeConfig {
743
815
  * to a table it has never fetched creates a private copy of it that never merges.
744
816
  * `timeoutMs` bounds how long {@link CadreNode.addStrand} waits before rejecting with
745
817
  * `StrandAwaitingFirstSyncError` (retryable — the launch stays up and keeps probing);
746
- * `pollIntervalMs` is the probe cadence. Omit for the defaults (`DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS`, 30 s;
747
- * `DEFAULT_STRAND_FIRST_SYNC_POLL_MS`, 500 ms). There is deliberately no way to disable
748
- * the gate: a machine that already holds the Header is never gated, so nothing that works
749
- * today is blocked by it.
818
+ * `pollIntervalMs` is the probe cadence. Omit for the defaults (`DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS`,
819
+ * sized for a machine re-attaching through a relay on a slow link, the slowest attach
820
+ * measured; that constant's doc comment carries the number, the measurement behind it and
821
+ * what the budget costs, and is the only copy of them; `DEFAULT_STRAND_FIRST_SYNC_POLL_MS`,
822
+ * 500 ms). There is deliberately no way to disable the gate: a machine whose store already
823
+ * holds the Header is never gated, so nothing that works today is blocked by it.
750
824
  */
751
825
  strandFirstSync?: StrandFirstSyncConfig;
752
826
 
@@ -1322,6 +1396,28 @@ export interface CadreNodeEvents {
1322
1396
  * revoked.
1323
1397
  */
1324
1398
  'strand:revoked': { strandId: string };
1399
+ /**
1400
+ * Emitted when this node holds a staged membership invitation for a CLOSED strand
1401
+ * that its own writes cannot redeem — the shape a REMOVED party hits when a manager
1402
+ * hands it a fresh invitation. Redeeming means writing this party's `Strand.Member`
1403
+ * row into the strand, and the machines that would carry that write are the ones the
1404
+ * remaining members refuse, so the attempt is made and fails. Two triggers, reported
1405
+ * at most once per re-arm of the membership loop (a further invitation staged while the
1406
+ * loop is still running does not reset the report):
1407
+ *
1408
+ * - CONFIRMED: the revoked-peer gate already flags this node as removed (the
1409
+ * `strand:revoked` case) and an invitation is staged.
1410
+ * - PROBABLE: `UNFINISHED_PASSES_BEFORE_ESCALATION` consecutive attempts left the
1411
+ * invitation staged. This one is a SUSPICION, not a verdict — the invitation's row
1412
+ * may simply not have replicated here yet on a slow strand — and the accompanying
1413
+ * warning names both causes.
1414
+ *
1415
+ * Nothing is stopped or torn down. A fresh invitation cannot re-admit a removed
1416
+ * party by itself: the remedy is a remaining manager admitting this party's member
1417
+ * key directly (`addMemberByManager`), after which the membership loop, which keeps
1418
+ * retrying, finishes the join on its own.
1419
+ */
1420
+ 'strand:rejoin-blocked': { strandId: string };
1325
1421
  /**
1326
1422
  * Emitted when the control network advertises a strand this node has no
1327
1423
  * registered `sAppConfig` for — i.e. a strand created by another member, or