@serfab/cadre-core 1.5.0 → 1.7.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 (68) hide show
  1. package/dist/cadre-node.d.ts +206 -67
  2. package/dist/cadre-node.js +399 -98
  3. package/dist/cadre-node.js.map +1 -1
  4. package/dist/index.d.ts +6 -1
  5. package/dist/index.js +17 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/joined-strand-store.d.ts +112 -0
  8. package/dist/joined-strand-store.js +263 -0
  9. package/dist/joined-strand-store.js.map +1 -0
  10. package/dist/key-store.d.ts +6 -0
  11. package/dist/key-store.js.map +1 -1
  12. package/dist/link-budget.d.ts +95 -53
  13. package/dist/link-budget.js +97 -53
  14. package/dist/link-budget.js.map +1 -1
  15. package/dist/node-local-snapshot.d.ts +5 -4
  16. package/dist/node-local-snapshot.js +6 -3
  17. package/dist/node-local-snapshot.js.map +1 -1
  18. package/dist/peer-dial.d.ts +3 -3
  19. package/dist/peer-dial.js +3 -3
  20. package/dist/peer-join-backfill.d.ts +35 -16
  21. package/dist/peer-join-backfill.js +117 -57
  22. package/dist/peer-join-backfill.js.map +1 -1
  23. package/dist/relay-reservation.d.ts +2 -2
  24. package/dist/relay-reservation.js +2 -2
  25. package/dist/seed-bootstrap.js +1 -1
  26. package/dist/strand-first-sync-gate.d.ts +88 -43
  27. package/dist/strand-first-sync-gate.js +92 -46
  28. package/dist/strand-first-sync-gate.js.map +1 -1
  29. package/dist/strand-instance-manager.d.ts +39 -0
  30. package/dist/strand-instance-manager.js +80 -3
  31. package/dist/strand-instance-manager.js.map +1 -1
  32. package/dist/strand-peer-book-file.d.ts +12 -0
  33. package/dist/strand-peer-book-file.js +37 -0
  34. package/dist/strand-peer-book-file.js.map +1 -0
  35. package/dist/strand-peer-book-protocol.d.ts +159 -0
  36. package/dist/strand-peer-book-protocol.js +360 -0
  37. package/dist/strand-peer-book-protocol.js.map +1 -0
  38. package/dist/strand-peer-book-swap.d.ts +104 -0
  39. package/dist/strand-peer-book-swap.js +284 -0
  40. package/dist/strand-peer-book-swap.js.map +1 -0
  41. package/dist/strand-peer-book.d.ts +159 -0
  42. package/dist/strand-peer-book.js +354 -0
  43. package/dist/strand-peer-book.js.map +1 -0
  44. package/dist/strand-peer-observer.d.ts +88 -0
  45. package/dist/strand-peer-observer.js +180 -0
  46. package/dist/strand-peer-observer.js.map +1 -0
  47. package/dist/types.d.ts +84 -17
  48. package/dist/types.js +6 -0
  49. package/dist/types.js.map +1 -1
  50. package/package.json +11 -7
  51. package/src/cadre-node.ts +7776 -7445
  52. package/src/index.ts +68 -0
  53. package/src/joined-strand-store.ts +308 -0
  54. package/src/key-store.ts +6 -0
  55. package/src/link-budget.ts +100 -53
  56. package/src/node-local-snapshot.ts +293 -289
  57. package/src/peer-dial.ts +3 -3
  58. package/src/peer-join-backfill.ts +709 -647
  59. package/src/relay-reservation.ts +2 -2
  60. package/src/seed-bootstrap.ts +1 -1
  61. package/src/strand-first-sync-gate.ts +352 -306
  62. package/src/strand-instance-manager.ts +106 -3
  63. package/src/strand-peer-book-file.ts +39 -0
  64. package/src/strand-peer-book-protocol.ts +463 -0
  65. package/src/strand-peer-book-swap.ts +338 -0
  66. package/src/strand-peer-book.ts +468 -0
  67. package/src/strand-peer-observer.ts +235 -0
  68. package/src/types.ts +86 -17
@@ -35,62 +35,78 @@
35
35
  *
36
36
  * | operation | link round trips | at the declared default |
37
37
  * | ---------------------------------------------------- | ---------------- | ----------------------- |
38
- * | open a relayed connection to another machine | 4 | 8 000 ms |
39
- * | dial the relay itself | 1 | 2 000 ms |
40
- * | request a reservation on an open relay connection | 1 | 2 000 ms |
41
- * | negotiate a protocol over an established circuit | 1 | 2 000 ms |
42
- *
43
- * **Measured** 2026-09-26, one Windows machine, loopback dedicated relay, re-run for this
44
- * change: a relayed dial took 20-25 ms at no delay, **7 255-7 279 ms at 900 ms one-way**, and
45
- * **12 066-12 094 ms at 1 500 ms one-way**. Four link round trips of pure delay would be 7 200
46
- * and 12 000, so the real cost sits about 60-95 ms ABOVE the arithmetic — the handshakes and a
47
- * phone's pure-JS crypto, which no delay figure contains. That residue is small but it is why
48
- * {@link DECLARED_LINK_ROUND_TRIP_MS} carries headroom over the band sereus supports rather
49
- * than matching it exactly: a declaration equal to the measured round trip would derive a
50
- * budget marginally BELOW the dial it has to contain.
51
- *
52
- * ── The ceiling this does NOT lift ──
53
- *
54
- * Above roughly 1 250 ms one-way — a 2.5-second link round trip — no relayed connection can be
55
- * established by this stack whatever cadre declares here. Two budgets of 10 000 ms each inside
56
- * libp2p bound the same dial and cannot be reached from sereus at all:
57
- * `connectionManager.dialTimeout` (libp2p's own default, which `@optimystic/db-p2p`'s
58
- * `libp2p-node-base.ts` neither sets nor exposes) and `connectionManager.inboundUpgradeTimeout`
59
- * (which it sets to 10 000). Both appear in the same re-run: at 1 500 ms one-way the dial under
60
- * libp2p's default failed with `The operation was aborted due to timeout`, and with the shipped
61
- * inbound-upgrade budget the DIALER's own 12 094 ms dial resolved while the LISTENER had
62
- * already thrown the half-built connection away — so the first stream over it died with
38
+ * | open a relayed connection to another machine | 4 | 14 000 ms |
39
+ * | dial the relay itself | 1 | 3 500 ms |
40
+ * | request a reservation on an open relay connection | 1 | 3 500 ms |
41
+ * | negotiate a protocol over an established circuit | 1 | 3 500 ms |
42
+ *
43
+ * **Measured** 2026-09-26, one Windows machine, loopback dedicated relay: a relayed dial took
44
+ * 20-25 ms at no delay, **7 255-7 279 ms at 900 ms one-way**, and **12 066-12 094 ms at 1 500 ms
45
+ * one-way**. Four link round trips of pure delay would be 7 200 and 12 000, so the real cost
46
+ * sits about 60-95 ms ABOVE the arithmetic — the handshakes, which no delay figure contains.
47
+ * That residue is why {@link DECLARED_LINK_ROUND_TRIP_MS} carries headroom over the link sereus
48
+ * supports rather than matching it exactly: a declaration equal to the supported round trip
49
+ * would derive a budget marginally BELOW the dial it has to contain.
50
+ *
51
+ * ── libp2p's own two limits ──
52
+ *
53
+ * Two limits inside libp2p bound the same relayed dial as cadre's budgets do, and both were
54
+ * 10 000 ms until `@optimystic/db-p2p` 1.7.0 let an embedder set them: the DIALER's
55
+ * `connectionManager.dialTimeout`, which bounds every dial that carries no abort signal of its
56
+ * own, and the LISTENER's `connectionManager.inboundUpgradeTimeout`, which is how long the
57
+ * machine being called lets a half-built connection finish its handshakes. At 10 000 ms each,
58
+ * no relayed connection could be opened above about 1 250 ms one-way (a 2.5-second link round
59
+ * trip) whatever cadre declared. The listener's limit is the one that makes that failure
60
+ * silent: at 1 500 ms one-way the dialer's own 12 094 ms dial RESOLVED while the listener had
61
+ * already thrown the connection away at 10 s, so the first stream over it died with
63
62
  * `Unexpected EOF - stream closed while reading 0/1 bytes` and the listener reported no peer at
64
- * all. That is why a too-slow link produces silence rather than an error.
65
- *
66
- * Two consequences for a reader choosing a declaration. Below that ceiling, the DEFAULT
67
- * declaration of 2 000 ms does not reach it: four round trips of 2 000 ms is 8 000 ms, which
68
- * covers a one-way delay up to 1 000 ms, so a deployment somewhere between 1 000 and 1 250 ms
69
- * one-way has to declare its own (about 2 200-2 500) to use the band libp2p still allows. And
70
- * declaring much ABOVE 2 500 buys nothing: the budgets here grow, and
71
- * the connection still fails inside libp2p. Lifting that needs an upstream change and a
72
- * decision about how slow a link sereus intends to carry —
73
- * `tickets/blocked/how-slow-a-relayed-link-does-sereus-carry`. A reader who raised these
74
- * numbers and still cannot connect at 1 500 ms one-way has met that ceiling, not this module.
63
+ * all. cadre now declares both from this module ({@link connectionManagerTimeouts}) on the
64
+ * control node and every strand node.
65
+ *
66
+ * ── What still fails at the supported link ──
67
+ *
68
+ * - **Optimystic's own request dials.** `@optimystic/db-p2p`'s RPC clients dial with fixed
69
+ * 3 000 ms deadlines of their own (`DEFAULT_DIAL_TIMEOUT_MS`, the `pushDialTimeoutMs`
70
+ * defaults), which neither limit above reaches because a caller's signal replaces
71
+ * `dialTimeout`. So an Optimystic request that has to OPEN a relayed connection fails above
72
+ * 375 ms one-way. A request over a connection that is already open does not dial, and the
73
+ * connections cadre opens itself are budgeted here. Upstream:
74
+ * `debt-rpc-dial-deadlines-cannot-open-a-slow-relayed-connection` in optimystic.
75
+ * - **Cadre deadlines still typed as milliseconds.** The strand wake, strand-address and seed
76
+ * delivery deadlines, and the cohort read deadline, bound exchanges over the same link but do
77
+ * not derive from it: `tickets/backlog/debt-three-more-dial-deadlines-ignore-the-declared-link`.
78
+ * - **A machine that declares a faster link than its peers.** Every machine is the listener
79
+ * for the others, so its `inboundUpgradeTimeout` — derived from ITS declaration — bounds
80
+ * connections other machines open to it. A peer declaring 3 500 ms dialing a machine that
81
+ * declared 500 ms gets exactly the silent failure above. Declare the same link on every
82
+ * machine of a party.
75
83
  */
84
+ import type { Libp2pConnectionTimeouts } from '@optimystic/db-p2p';
76
85
  /**
77
86
  * The link round trip cadre assumes when a host declares none, in milliseconds. A deployment
78
87
  * that knows its own link states it with `NetworkConfig.linkRoundTripMs` instead.
79
88
  *
80
- * **Why 2 000.** It states the slowest link sereus already claims to support, and two other
81
- * budgets fix that claim independently: `COHORT_READ_DEADLINE_MS`
82
- * (`quereus-plugin-sereus/src/cluster-size.ts`, 5 000 ms) and
83
- * `DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS` (`strand-first-sync-gate.ts`, 120 000 ms) were both
84
- * sized against two parties reaching each other only through a public relay at a round trip
85
- * near 1.8 s. Declaring 2 000 rounds that band up and leaves about 720 ms over the worst
86
- * relayed dial measured at it (7 279 ms against a derived 8 000 ms).
87
- *
88
- * Raising it is how a deployment that KNOWS its link is slower moves every derived budget at
89
- * once. The cost is the ordinary cost of a longer deadline: a peer that is genuinely gone holds
90
- * the operation for that much longer before it is abandoned and retried. Above about 2 500 it
91
- * buys nothing — see the module doc's ceiling paragraph.
89
+ * **Why 3 500.** Sereus supports two machines that reach each other only through a relay up to
90
+ * a **3-second link round trip** (1 500 ms each way — a congested mobile or satellite link;
91
+ * `docs/architecture.md` → "Relay Integration"). The worst relayed dial measured at that link
92
+ * was 12 094 ms, above the 12 000 ms a declaration of exactly 3 000 would derive, so the
93
+ * declaration rounds the supported link up to the next half second. 3 500 derives 14 000 ms
94
+ * and leaves about 1.9 s over that dial: room for a phone's Noise handshake, which the
95
+ * loopback measurement on a desktop does not contain. (The previous 2 000 followed the same
96
+ * convention for the 1.8 s band sereus supported before, with about 720 ms to spare.)
97
+ *
98
+ * Two budgets outside this module were sized against the 1.8 s band and do NOT move with it:
99
+ * `COHORT_READ_DEADLINE_MS` (`quereus-plugin-sereus/src/cluster-size.ts`, 5 000 ms) and
100
+ * `DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS` (`strand-first-sync-gate.ts`). The module doc's
101
+ * "What still fails" list carries the first.
102
+ *
103
+ * What 3 500 costs, against 2 000: a peer that is genuinely gone holds each operation longer
104
+ * before it is abandoned and retried — a relayed dial or a reservation drive 14 s instead of
105
+ * 8 s, and a control-cohort dial of one peer across all its addresses 56 s instead of 32 s.
106
+ * Raise it further for a link slower still; lower it only on a deployment where EVERY machine
107
+ * of the party is that close, for the reason the "What still fails" list gives.
92
108
  */
93
- export declare const DECLARED_LINK_ROUND_TRIP_MS = 2000;
109
+ export declare const DECLARED_LINK_ROUND_TRIP_MS = 3500;
94
110
  /**
95
111
  * Link round trips one relayed connection setup costs: transport, encryption and multiplexer
96
112
  * handshakes to the relay, the circuit open, then both handshakes again end to end through it.
@@ -127,10 +143,10 @@ export declare const CIRCUIT_REQUEST_ROUND_TRIPS = 2;
127
143
  *
128
144
  * NOTE: 6 000 ms is not a measurement of any throughput — it is the residue of the 10 000 ms
129
145
  * this deadline shipped as before it was split into a latency part and a transfer part, chosen
130
- * so that at the default declaration the derived value is the same 10 000 and no fast link got
131
- * slower. Nobody has measured how long 1 MiB takes to cross a relayed mobile link. If pushes
132
- * ever time out with the transfer only part done, measure that and raise THIS, not the declared
133
- * round trip.
146
+ * so that at the declaration then in force (2 000 ms) the derived value stayed 10 000 and no
147
+ * fast link got slower. Nobody has measured how long 1 MiB takes to cross a relayed mobile
148
+ * link. If pushes ever time out with the transfer only part done, measure that and raise THIS,
149
+ * not the declared round trip.
134
150
  */
135
151
  export declare const PUSH_TRANSFER_ALLOWANCE_MS = 6000;
136
152
  /**
@@ -155,6 +171,32 @@ export declare function resolveLinkRoundTripMs(linkRoundTripMs?: number): number
155
171
  * relay: {@link RELAYED_DIAL_ROUND_TRIPS} at the declared link round trip.
156
172
  */
157
173
  export declare function relayedDialBudgetMs(linkRoundTripMs?: number): number;
174
+ /**
175
+ * libp2p's two connection-manager limits for a node at the declared link, handed to
176
+ * `@optimystic/db-p2p`'s `NodeOptions.connectionManager` on the control node and every strand
177
+ * node.
178
+ *
179
+ * Both bound the same thing — opening a relayed connection, measured at 8 one-way link delays,
180
+ * which is {@link RELAYED_DIAL_ROUND_TRIPS} (4) link round trips — so both are
181
+ * {@link relayedDialBudgetMs}: 4 x 3 500 = 14 000 ms at the default declaration, covering the
182
+ * 12 094 ms measured at the supported 3-second link.
183
+ *
184
+ * The listener's limit is deliberately NOT smaller than the dialer's. The listener's clock
185
+ * starts only when the relay hands it the circuit, a few one-way delays after the dialer's
186
+ * started, and both sides finish the handshakes at about the same moment; so with equal limits
187
+ * the listener never discards a connection that the dialer's own dial would still accept. A
188
+ * listener limit below the dialer's is what produced the silent failure in the module doc.
189
+ *
190
+ * The cost of a longer `inboundUpgradeTimeout` is that a peer which opens a connection and then
191
+ * stalls its handshake holds that half-built connection 14 s instead of 10 s.
192
+ *
193
+ * NOTE: derived from the declaration alone, so a per-field override that raises a cadre dial
194
+ * budget above it (`controlCohort.perAddressDialTimeoutMs`, `strandBackfill.dialTimeoutMs`)
195
+ * does not raise these; a dial that outlasts the listener's limit gets the silent failure
196
+ * again. If such overrides start being used for slow links, raise the declaration instead or
197
+ * take the largest configured dial budget here.
198
+ */
199
+ export declare function connectionManagerTimeouts(linkRoundTripMs?: number): Libp2pConnectionTimeouts;
158
200
  /**
159
201
  * Deadline for one whole relay reservation drive: {@link RELAY_RESERVATION_ROUND_TRIPS} at the
160
202
  * declared link round trip.
@@ -35,62 +35,77 @@
35
35
  *
36
36
  * | operation | link round trips | at the declared default |
37
37
  * | ---------------------------------------------------- | ---------------- | ----------------------- |
38
- * | open a relayed connection to another machine | 4 | 8 000 ms |
39
- * | dial the relay itself | 1 | 2 000 ms |
40
- * | request a reservation on an open relay connection | 1 | 2 000 ms |
41
- * | negotiate a protocol over an established circuit | 1 | 2 000 ms |
42
- *
43
- * **Measured** 2026-09-26, one Windows machine, loopback dedicated relay, re-run for this
44
- * change: a relayed dial took 20-25 ms at no delay, **7 255-7 279 ms at 900 ms one-way**, and
45
- * **12 066-12 094 ms at 1 500 ms one-way**. Four link round trips of pure delay would be 7 200
46
- * and 12 000, so the real cost sits about 60-95 ms ABOVE the arithmetic — the handshakes and a
47
- * phone's pure-JS crypto, which no delay figure contains. That residue is small but it is why
48
- * {@link DECLARED_LINK_ROUND_TRIP_MS} carries headroom over the band sereus supports rather
49
- * than matching it exactly: a declaration equal to the measured round trip would derive a
50
- * budget marginally BELOW the dial it has to contain.
51
- *
52
- * ── The ceiling this does NOT lift ──
53
- *
54
- * Above roughly 1 250 ms one-way — a 2.5-second link round trip — no relayed connection can be
55
- * established by this stack whatever cadre declares here. Two budgets of 10 000 ms each inside
56
- * libp2p bound the same dial and cannot be reached from sereus at all:
57
- * `connectionManager.dialTimeout` (libp2p's own default, which `@optimystic/db-p2p`'s
58
- * `libp2p-node-base.ts` neither sets nor exposes) and `connectionManager.inboundUpgradeTimeout`
59
- * (which it sets to 10 000). Both appear in the same re-run: at 1 500 ms one-way the dial under
60
- * libp2p's default failed with `The operation was aborted due to timeout`, and with the shipped
61
- * inbound-upgrade budget the DIALER's own 12 094 ms dial resolved while the LISTENER had
62
- * already thrown the half-built connection away — so the first stream over it died with
38
+ * | open a relayed connection to another machine | 4 | 14 000 ms |
39
+ * | dial the relay itself | 1 | 3 500 ms |
40
+ * | request a reservation on an open relay connection | 1 | 3 500 ms |
41
+ * | negotiate a protocol over an established circuit | 1 | 3 500 ms |
42
+ *
43
+ * **Measured** 2026-09-26, one Windows machine, loopback dedicated relay: a relayed dial took
44
+ * 20-25 ms at no delay, **7 255-7 279 ms at 900 ms one-way**, and **12 066-12 094 ms at 1 500 ms
45
+ * one-way**. Four link round trips of pure delay would be 7 200 and 12 000, so the real cost
46
+ * sits about 60-95 ms ABOVE the arithmetic — the handshakes, which no delay figure contains.
47
+ * That residue is why {@link DECLARED_LINK_ROUND_TRIP_MS} carries headroom over the link sereus
48
+ * supports rather than matching it exactly: a declaration equal to the supported round trip
49
+ * would derive a budget marginally BELOW the dial it has to contain.
50
+ *
51
+ * ── libp2p's own two limits ──
52
+ *
53
+ * Two limits inside libp2p bound the same relayed dial as cadre's budgets do, and both were
54
+ * 10 000 ms until `@optimystic/db-p2p` 1.7.0 let an embedder set them: the DIALER's
55
+ * `connectionManager.dialTimeout`, which bounds every dial that carries no abort signal of its
56
+ * own, and the LISTENER's `connectionManager.inboundUpgradeTimeout`, which is how long the
57
+ * machine being called lets a half-built connection finish its handshakes. At 10 000 ms each,
58
+ * no relayed connection could be opened above about 1 250 ms one-way (a 2.5-second link round
59
+ * trip) whatever cadre declared. The listener's limit is the one that makes that failure
60
+ * silent: at 1 500 ms one-way the dialer's own 12 094 ms dial RESOLVED while the listener had
61
+ * already thrown the connection away at 10 s, so the first stream over it died with
63
62
  * `Unexpected EOF - stream closed while reading 0/1 bytes` and the listener reported no peer at
64
- * all. That is why a too-slow link produces silence rather than an error.
65
- *
66
- * Two consequences for a reader choosing a declaration. Below that ceiling, the DEFAULT
67
- * declaration of 2 000 ms does not reach it: four round trips of 2 000 ms is 8 000 ms, which
68
- * covers a one-way delay up to 1 000 ms, so a deployment somewhere between 1 000 and 1 250 ms
69
- * one-way has to declare its own (about 2 200-2 500) to use the band libp2p still allows. And
70
- * declaring much ABOVE 2 500 buys nothing: the budgets here grow, and
71
- * the connection still fails inside libp2p. Lifting that needs an upstream change and a
72
- * decision about how slow a link sereus intends to carry —
73
- * `tickets/blocked/how-slow-a-relayed-link-does-sereus-carry`. A reader who raised these
74
- * numbers and still cannot connect at 1 500 ms one-way has met that ceiling, not this module.
63
+ * all. cadre now declares both from this module ({@link connectionManagerTimeouts}) on the
64
+ * control node and every strand node.
65
+ *
66
+ * ── What still fails at the supported link ──
67
+ *
68
+ * - **Optimystic's own request dials.** `@optimystic/db-p2p`'s RPC clients dial with fixed
69
+ * 3 000 ms deadlines of their own (`DEFAULT_DIAL_TIMEOUT_MS`, the `pushDialTimeoutMs`
70
+ * defaults), which neither limit above reaches because a caller's signal replaces
71
+ * `dialTimeout`. So an Optimystic request that has to OPEN a relayed connection fails above
72
+ * 375 ms one-way. A request over a connection that is already open does not dial, and the
73
+ * connections cadre opens itself are budgeted here. Upstream:
74
+ * `debt-rpc-dial-deadlines-cannot-open-a-slow-relayed-connection` in optimystic.
75
+ * - **Cadre deadlines still typed as milliseconds.** The strand wake, strand-address and seed
76
+ * delivery deadlines, and the cohort read deadline, bound exchanges over the same link but do
77
+ * not derive from it: `tickets/backlog/debt-three-more-dial-deadlines-ignore-the-declared-link`.
78
+ * - **A machine that declares a faster link than its peers.** Every machine is the listener
79
+ * for the others, so its `inboundUpgradeTimeout` — derived from ITS declaration — bounds
80
+ * connections other machines open to it. A peer declaring 3 500 ms dialing a machine that
81
+ * declared 500 ms gets exactly the silent failure above. Declare the same link on every
82
+ * machine of a party.
75
83
  */
76
84
  /**
77
85
  * The link round trip cadre assumes when a host declares none, in milliseconds. A deployment
78
86
  * that knows its own link states it with `NetworkConfig.linkRoundTripMs` instead.
79
87
  *
80
- * **Why 2 000.** It states the slowest link sereus already claims to support, and two other
81
- * budgets fix that claim independently: `COHORT_READ_DEADLINE_MS`
82
- * (`quereus-plugin-sereus/src/cluster-size.ts`, 5 000 ms) and
83
- * `DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS` (`strand-first-sync-gate.ts`, 120 000 ms) were both
84
- * sized against two parties reaching each other only through a public relay at a round trip
85
- * near 1.8 s. Declaring 2 000 rounds that band up and leaves about 720 ms over the worst
86
- * relayed dial measured at it (7 279 ms against a derived 8 000 ms).
87
- *
88
- * Raising it is how a deployment that KNOWS its link is slower moves every derived budget at
89
- * once. The cost is the ordinary cost of a longer deadline: a peer that is genuinely gone holds
90
- * the operation for that much longer before it is abandoned and retried. Above about 2 500 it
91
- * buys nothing — see the module doc's ceiling paragraph.
88
+ * **Why 3 500.** Sereus supports two machines that reach each other only through a relay up to
89
+ * a **3-second link round trip** (1 500 ms each way — a congested mobile or satellite link;
90
+ * `docs/architecture.md` → "Relay Integration"). The worst relayed dial measured at that link
91
+ * was 12 094 ms, above the 12 000 ms a declaration of exactly 3 000 would derive, so the
92
+ * declaration rounds the supported link up to the next half second. 3 500 derives 14 000 ms
93
+ * and leaves about 1.9 s over that dial: room for a phone's Noise handshake, which the
94
+ * loopback measurement on a desktop does not contain. (The previous 2 000 followed the same
95
+ * convention for the 1.8 s band sereus supported before, with about 720 ms to spare.)
96
+ *
97
+ * Two budgets outside this module were sized against the 1.8 s band and do NOT move with it:
98
+ * `COHORT_READ_DEADLINE_MS` (`quereus-plugin-sereus/src/cluster-size.ts`, 5 000 ms) and
99
+ * `DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS` (`strand-first-sync-gate.ts`). The module doc's
100
+ * "What still fails" list carries the first.
101
+ *
102
+ * What 3 500 costs, against 2 000: a peer that is genuinely gone holds each operation longer
103
+ * before it is abandoned and retried — a relayed dial or a reservation drive 14 s instead of
104
+ * 8 s, and a control-cohort dial of one peer across all its addresses 56 s instead of 32 s.
105
+ * Raise it further for a link slower still; lower it only on a deployment where EVERY machine
106
+ * of the party is that close, for the reason the "What still fails" list gives.
92
107
  */
93
- export const DECLARED_LINK_ROUND_TRIP_MS = 2000;
108
+ export const DECLARED_LINK_ROUND_TRIP_MS = 3500;
94
109
  /**
95
110
  * Link round trips one relayed connection setup costs: transport, encryption and multiplexer
96
111
  * handshakes to the relay, the circuit open, then both handshakes again end to end through it.
@@ -127,10 +142,10 @@ export const CIRCUIT_REQUEST_ROUND_TRIPS = 2;
127
142
  *
128
143
  * NOTE: 6 000 ms is not a measurement of any throughput — it is the residue of the 10 000 ms
129
144
  * this deadline shipped as before it was split into a latency part and a transfer part, chosen
130
- * so that at the default declaration the derived value is the same 10 000 and no fast link got
131
- * slower. Nobody has measured how long 1 MiB takes to cross a relayed mobile link. If pushes
132
- * ever time out with the transfer only part done, measure that and raise THIS, not the declared
133
- * round trip.
145
+ * so that at the declaration then in force (2 000 ms) the derived value stayed 10 000 and no
146
+ * fast link got slower. Nobody has measured how long 1 MiB takes to cross a relayed mobile
147
+ * link. If pushes ever time out with the transfer only part done, measure that and raise THIS,
148
+ * not the declared round trip.
134
149
  */
135
150
  export const PUSH_TRANSFER_ALLOWANCE_MS = 6000;
136
151
  /**
@@ -165,6 +180,35 @@ export function resolveLinkRoundTripMs(linkRoundTripMs) {
165
180
  export function relayedDialBudgetMs(linkRoundTripMs) {
166
181
  return RELAYED_DIAL_ROUND_TRIPS * resolveLinkRoundTripMs(linkRoundTripMs);
167
182
  }
183
+ /**
184
+ * libp2p's two connection-manager limits for a node at the declared link, handed to
185
+ * `@optimystic/db-p2p`'s `NodeOptions.connectionManager` on the control node and every strand
186
+ * node.
187
+ *
188
+ * Both bound the same thing — opening a relayed connection, measured at 8 one-way link delays,
189
+ * which is {@link RELAYED_DIAL_ROUND_TRIPS} (4) link round trips — so both are
190
+ * {@link relayedDialBudgetMs}: 4 x 3 500 = 14 000 ms at the default declaration, covering the
191
+ * 12 094 ms measured at the supported 3-second link.
192
+ *
193
+ * The listener's limit is deliberately NOT smaller than the dialer's. The listener's clock
194
+ * starts only when the relay hands it the circuit, a few one-way delays after the dialer's
195
+ * started, and both sides finish the handshakes at about the same moment; so with equal limits
196
+ * the listener never discards a connection that the dialer's own dial would still accept. A
197
+ * listener limit below the dialer's is what produced the silent failure in the module doc.
198
+ *
199
+ * The cost of a longer `inboundUpgradeTimeout` is that a peer which opens a connection and then
200
+ * stalls its handshake holds that half-built connection 14 s instead of 10 s.
201
+ *
202
+ * NOTE: derived from the declaration alone, so a per-field override that raises a cadre dial
203
+ * budget above it (`controlCohort.perAddressDialTimeoutMs`, `strandBackfill.dialTimeoutMs`)
204
+ * does not raise these; a dial that outlasts the listener's limit gets the silent failure
205
+ * again. If such overrides start being used for slow links, raise the declaration instead or
206
+ * take the largest configured dial budget here.
207
+ */
208
+ export function connectionManagerTimeouts(linkRoundTripMs) {
209
+ const budgetMs = relayedDialBudgetMs(linkRoundTripMs);
210
+ return { dialTimeout: budgetMs, inboundUpgradeTimeout: budgetMs };
211
+ }
168
212
  /**
169
213
  * Deadline for one whole relay reservation drive: {@link RELAY_RESERVATION_ROUND_TRIPS} at the
170
214
  * declared link round trip.
@@ -1 +1 @@
1
- {"version":3,"file":"link-budget.js","sourceRoot":"","sources":["../src/link-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0EG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,eAAwB;IAC9D,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,2BAA2B,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CACd,mFAAmF,MAAM,CAAC,eAAe,CAAC,EAAE,CAC5G,CAAC;IACH,CAAC;IACD,OAAO,eAAe,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,eAAwB;IAC3D,OAAO,wBAAwB,GAAG,sBAAsB,CAAC,eAAe,CAAC,CAAC;AAC3E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,eAAwB;IAChE,OAAO,6BAA6B,GAAG,sBAAsB,CAAC,eAAe,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACrC,eAAwB,EACxB,mBAAmB,GAAG,0BAA0B;IAEhD,OAAO,2BAA2B,GAAG,sBAAsB,CAAC,eAAe,CAAC,GAAG,mBAAmB,CAAC;AACpG,CAAC;AAUD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,eAAwB;IAC1D,OAAO;QACN,aAAa,EAAE,mBAAmB,CAAC,eAAe,CAAC;QACnD,iBAAiB,EAAE,sBAAsB,CAAC,eAAe,CAAC;KAC1D,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"link-budget.js","sourceRoot":"","sources":["../src/link-budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkFG;AAIH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAE1C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAE7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,IAAI,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,sBAAsB,CAAC,eAAwB;IAC9D,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,2BAA2B,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CACd,mFAAmF,MAAM,CAAC,eAAe,CAAC,EAAE,CAC5G,CAAC;IACH,CAAC;IACD,OAAO,eAAe,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,eAAwB;IAC3D,OAAO,wBAAwB,GAAG,sBAAsB,CAAC,eAAe,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,yBAAyB,CAAC,eAAwB;IACjE,MAAM,QAAQ,GAAG,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACtD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,qBAAqB,EAAE,QAAQ,EAAE,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,eAAwB;IAChE,OAAO,6BAA6B,GAAG,sBAAsB,CAAC,eAAe,CAAC,CAAC;AAChF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACrC,eAAwB,EACxB,mBAAmB,GAAG,0BAA0B;IAEhD,OAAO,2BAA2B,GAAG,sBAAsB,CAAC,eAAe,CAAC,GAAG,mBAAmB,CAAC;AACpG,CAAC;AAUD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,eAAwB;IAC1D,OAAO;QACN,aAAa,EAAE,mBAAmB,CAAC,eAAe,CAAC;QACnD,iBAAiB,EAAE,sBAAsB,CAAC,eAAe,CAAC;KAC1D,CAAC;AACH,CAAC"}
@@ -28,15 +28,16 @@ export interface DurableSlot {
28
28
  }
29
29
  /**
30
30
  * What one unusable entry means for its siblings — the single intentional
31
- * difference between the two records:
31
+ * difference between the records:
32
32
  *
33
33
  * - `'discard-all'` (the trusted-owner anchor): a record that cannot be read
34
34
  * in full is not an anchor. Trusting a *subset* of the keys a file claims is
35
35
  * a silent, security-relevant downgrade, so the whole record is discarded
36
36
  * and the node trusts no one until re-seeded out of band.
37
- * - `'drop-entry'` (the bootstrap-peer store): the record is a best-effort
38
- * retry list and nothing in it is trust-bearing, so one junk entry must not
39
- * discard a stranded node's only remaining way back into its party.
37
+ * - `'drop-entry'` (the bootstrap-peer store and the strand peer book): the
38
+ * record is a best-effort dial list and nothing in it is trust-bearing, so one
39
+ * junk entry must not discard a stranded node's only remaining way back to
40
+ * its party or its strand's peers.
40
41
  */
41
42
  export type UnusableEntryPolicy = 'discard-all' | 'drop-entry';
42
43
  /** What a particular node-local record persists, and how it validates it. */
@@ -4,15 +4,18 @@
4
4
  * from shared group state:
5
5
  *
6
6
  * - the trusted-owner anchor (`trusted-owner-store.ts`),
7
- * - the cold-start bootstrap-peer store (`bootstrap-peer-store.ts`).
7
+ * - the cold-start bootstrap-peer store (`bootstrap-peer-store.ts`),
8
+ * - the strand peer book (`strand-peer-book.ts`), whose entries are one map per
9
+ * strand rather than one value per key — the validation hook below is what
10
+ * lets it drop a single junk peer without dropping the strand.
8
11
  *
9
- * A THIRD node-local record — the enrolled-machine count
12
+ * ANOTHER node-local record — the enrolled-machine count
10
13
  * (`enrolled-machine-store.ts`) — reuses {@link DurableSlot} but deliberately
11
14
  * NOT this module's snapshot machinery: it is one scalar rather than an entry
12
15
  * map, and it must cold-start rather than throw on an unreadable slot. Its
13
16
  * module comment carries the reasoning; do not fold it in here.
14
17
  *
15
- * Both records here are the same mechanism with a different payload: a
18
+ * Every record here is the same mechanism with a different payload: a
16
19
  * `{ version, partyId, <entries> }` envelope, snapshot-written whole on every
17
20
  * change, loaded under one fail-safe-but-not-fail-silent policy. The only part
18
21
  * that differs per platform is WHERE the bytes are kept — Node writes a file,
@@ -1 +1 @@
1
- {"version":3,"file":"node-local-snapshot.js","sourceRoot":"","sources":["../src/node-local-snapshot.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-local-snapshot.js","sourceRoot":"","sources":["../src/node-local-snapshot.ts"],"names":[],"mappings":"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"}
@@ -26,8 +26,8 @@ import type { Connection } from '@libp2p/interface';
26
26
  import type { Multiaddr } from '@multiformats/multiaddr';
27
27
  /**
28
28
  * Default limit on ONE address's dial attempt, in ms, at the DEFAULT declared link round trip:
29
- * `RELAYED_DIAL_ROUND_TRIPS` (4) x `DECLARED_LINK_ROUND_TRIP_MS` (2000 ms) = 8000 ms, which is
30
- * what this constant was already set to as a fixed number.
29
+ * `RELAYED_DIAL_ROUND_TRIPS` (4) x `DECLARED_LINK_ROUND_TRIP_MS` (3500 ms) = 14 000 ms. It was
30
+ * a fixed 8000 ms before it was derived.
31
31
  *
32
32
  * It has to cover the slowest address that should succeed, because an attempt
33
33
  * that needs longer fails the same way on every retry. That is a relayed dial on
@@ -35,7 +35,7 @@ import type { Multiaddr } from '@multiformats/multiaddr';
35
35
  * handshakes, when no relay connection is open yet), open the circuit, then run
36
36
  * both handshakes again end to end through the relay. `link-budget.ts` is where that
37
37
  * reasoning is now a measured count instead of an estimate — four link round trips, measured at
38
- * 7 279 ms on a link whose round trip is 1.8 s — so the value MOVES with a host's
38
+ * 12 094 ms on a link whose round trip is 3 s — so the value MOVES with a host's
39
39
  * `NetworkConfig.linkRoundTripMs` rather than pinning the band this node can reach to whatever
40
40
  * number was typed here. A direct dial that works is far quicker — a phone's WebSocket dial to
41
41
  * a node forwarded over `adb reverse` measured 1.6 s end to end.
package/dist/peer-dial.js CHANGED
@@ -28,8 +28,8 @@ import { relayedDialBudgetMs } from './link-budget.js';
28
28
  const log = debug('sereus:cadre:peer-dial');
29
29
  /**
30
30
  * Default limit on ONE address's dial attempt, in ms, at the DEFAULT declared link round trip:
31
- * `RELAYED_DIAL_ROUND_TRIPS` (4) x `DECLARED_LINK_ROUND_TRIP_MS` (2000 ms) = 8000 ms, which is
32
- * what this constant was already set to as a fixed number.
31
+ * `RELAYED_DIAL_ROUND_TRIPS` (4) x `DECLARED_LINK_ROUND_TRIP_MS` (3500 ms) = 14 000 ms. It was
32
+ * a fixed 8000 ms before it was derived.
33
33
  *
34
34
  * It has to cover the slowest address that should succeed, because an attempt
35
35
  * that needs longer fails the same way on every retry. That is a relayed dial on
@@ -37,7 +37,7 @@ const log = debug('sereus:cadre:peer-dial');
37
37
  * handshakes, when no relay connection is open yet), open the circuit, then run
38
38
  * both handshakes again end to end through the relay. `link-budget.ts` is where that
39
39
  * reasoning is now a measured count instead of an estimate — four link round trips, measured at
40
- * 7 279 ms on a link whose round trip is 1.8 s — so the value MOVES with a host's
40
+ * 12 094 ms on a link whose round trip is 3 s — so the value MOVES with a host's
41
41
  * `NetworkConfig.linkRoundTripMs` rather than pinning the band this node can reach to whatever
42
42
  * number was typed here. A direct dial that works is far quicker — a phone's WebSocket dial to
43
43
  * a node forwarded over `adb reverse` measured 1.6 s end to end.
@@ -8,6 +8,15 @@ import { BlockTransferClient, type IRawStorage } from '@optimystic/db-p2p';
8
8
  * changes it or starts exporting it.
9
9
  */
10
10
  export declare const MAX_BLOCK_MESSAGE_BYTES: number;
11
+ /**
12
+ * Whether an identified peer's protocol list names this network's own block-transfer
13
+ * protocol — the one gate that decides whether {@link PeerJoinBackfill} schedules a peer at
14
+ * all. A peer that lacks it (a bare circuit relay, a bootstrap node, a stranger the control
15
+ * network's inbound gate admitted without membership) cannot receive a push no matter how
16
+ * long it is dialed, since the protocol id is namespaced per network. Exported because a
17
+ * later strand peer-address ticket's identify-driven observation needs the identical test.
18
+ */
19
+ export declare function speaksBlockTransfer(protocols: readonly string[], protocolPrefix: string): boolean;
11
20
  /**
12
21
  * Consecutive failed catch-up runs against ONE peer before the module says so on
13
22
  * `console.warn` rather than only in its DEBUG log. At the default backoff that is reached
@@ -29,7 +38,7 @@ export interface PeerJoinBackfillConfig {
29
38
  maxChunkBlocks?: number;
30
39
  /**
31
40
  * Per-push dial deadline, ms. Default {@link peerJoinPushBudget}'s `dialTimeoutMs` — four link
32
- * round trips at the declared link, 8000 ms as shipped. NOT a fixed number: a relayed dial
41
+ * round trips at the declared link, 14 000 ms as shipped. NOT a fixed number: a relayed dial
33
42
  * costs a fixed number of exchanges, so a host on a slower link moves this (and every other
34
43
  * cadre dial budget) by declaring `NetworkConfig.linkRoundTripMs`. Naming it here still wins
35
44
  * over the derived value — `link-budget.ts` has the counts and the measurement.
@@ -38,7 +47,7 @@ export interface PeerJoinBackfillConfig {
38
47
  /**
39
48
  * Per-push response deadline, ms. Default {@link peerJoinPushBudget}'s `responseTimeoutMs` —
40
49
  * two link round trips at the declared link plus a transfer allowance for the chunk's own
41
- * bytes, 10_000 ms as shipped. Derived differently from {@link dialTimeoutMs} because it
50
+ * bytes, 13 000 ms as shipped. Derived differently from {@link dialTimeoutMs} because it
42
51
  * bounds a data transfer over a connection that is already open, not a dial.
43
52
  */
44
53
  responseTimeoutMs?: number;
@@ -109,7 +118,7 @@ export interface PeerJoinBackfillResult {
109
118
  * Best-effort throughout: nothing here throws into a libp2p event handler or into a
110
119
  * runtime bring-up; a failed chunk is logged and the run continues. A peer is marked
111
120
  * fully caught up ONLY after a run with no thrown chunk and an empty `missing` list, so
112
- * the next `connection:open` from a peer whose catch-up failed retries it.
121
+ * the next `peer:identify` from a peer whose catch-up failed retries it.
113
122
  */
114
123
  export declare class PeerJoinBackfill {
115
124
  private readonly deps;
@@ -136,30 +145,40 @@ export declare class PeerJoinBackfill {
136
145
  private readonly failures;
137
146
  /**
138
147
  * Epoch ms before which a peer's catch-up must not be re-run, set alongside a backoff timer
139
- * that will run it. Its presence is what makes `connection:open` churn free: a schedule
148
+ * that will run it. Its presence is what makes `peer:identify` churn free: a schedule
140
149
  * arriving inside the wait is dropped, rather than collapsing the backoff to the debounce.
141
150
  */
142
151
  private readonly retryAfter;
143
152
  /** Peers already reported on `console.warn`; reset when a run finally lands cleanly. */
144
153
  private readonly warned;
145
- private readonly onConnectionOpen;
154
+ private readonly onPeerIdentify;
146
155
  private loggedNoListBlockIds;
147
156
  constructor(deps: PeerJoinBackfillDeps, config?: PeerJoinBackfillConfig);
148
- /** Subscribe to connection:open AND schedule a catch-up for peers already connected. */
157
+ /**
158
+ * Subscribe to `peer:identify` AND schedule a catch-up for already-connected peers whose
159
+ * stored protocols already name this network's block-transfer protocol — a peer that does
160
+ * not speak it (a bare circuit relay, a bootstrap node) is never scheduled at all. See
161
+ * {@link speaksBlockTransfer} and the module comment.
162
+ */
149
163
  start(): void;
150
164
  /** Unsubscribe, clear timers; in-flight runs observe the stopped flag and bail. */
151
165
  stop(): void;
152
166
  /**
153
- * (Re-)schedule a debounced catch-up for every currently-connected peer not yet caught
154
- * up. Idempotent and cheap (caught-up peers are skipped before any timer is set, and a
155
- * peer whose run is in flight is deferred to the end of that run rather than dropped).
156
- * Driven by {@link start}, and — on a gated network — by the embedder whenever
157
- * membership changes, so a peer whose first pass was denied (connected before it was
158
- * authorized: the production join order) is retried without waiting for a reconnect.
159
- * Returns how many distinct peers were considered (for the start() log) — a peer holding
160
- * several connections is one peer, and is scheduled once.
167
+ * (Re-)schedule a debounced catch-up for every currently-connected peer whose protocols, as
168
+ * already stored in the libp2p peer store, name this network's block-transfer protocol. A
169
+ * peer not yet in the peer store — its identify has not finished — is left to the
170
+ * `peer:identify` handler rather than dialed blind; one whose stored protocols are known
171
+ * and lack ours is skipped outright. Idempotent and cheap otherwise (caught-up peers are
172
+ * skipped before any timer is set, and a peer whose run is in flight is deferred to the end
173
+ * of that run rather than dropped). Driven by {@link start}, and — on a gated network — by
174
+ * the embedder whenever membership changes, so a peer whose first pass was denied (connected
175
+ * before it was authorized: the production join order) is retried without waiting for a
176
+ * reconnect. Returns how many distinct peers were considered (for the start() log) — a peer
177
+ * holding several connections is one peer, and is scheduled once.
161
178
  */
162
- scheduleConnectedPeers(): number;
179
+ scheduleConnectedPeers(): Promise<number>;
180
+ /** Schedules `peerId` only if the peer store already knows it speaks this network's protocol. */
181
+ private scheduleIfKnownToSpeak;
163
182
  /** Debounced entry point for connection churn: one run per peer per settle window. */
164
183
  private schedulePeer;
165
184
  /**
@@ -173,7 +192,7 @@ export declare class PeerJoinBackfill {
173
192
  /**
174
193
  * Re-arm one peer's catch-up after a run whose push failed, on a wait that doubles
175
194
  * per consecutive failure up to `maxRetryBackoffMs`. Sets {@link retryAfter} alongside the
176
- * timer, which is what makes `connection:open` churn inside the wait free.
195
+ * timer, which is what makes `peer:identify` churn inside the wait free.
177
196
  */
178
197
  private scheduleRetryWithBackoff;
179
198
  /**