@serfab/cadre-core 1.17.0 → 1.18.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 (144) hide show
  1. package/README.md +3 -2
  2. package/dist/cadre-invite-protocol.d.ts +35 -13
  3. package/dist/cadre-invite-protocol.js +90 -25
  4. package/dist/cadre-invite-protocol.js.map +1 -1
  5. package/dist/cadre-node.d.ts +586 -255
  6. package/dist/cadre-node.js +1176 -564
  7. package/dist/cadre-node.js.map +1 -1
  8. package/dist/control-cohort.d.ts +66 -9
  9. package/dist/control-cohort.js +67 -9
  10. package/dist/control-cohort.js.map +1 -1
  11. package/dist/control-database.d.ts +451 -193
  12. package/dist/control-database.js +701 -359
  13. package/dist/control-database.js.map +1 -1
  14. package/dist/control-formation-recorder.d.ts +46 -43
  15. package/dist/control-formation-recorder.js +77 -78
  16. package/dist/control-formation-recorder.js.map +1 -1
  17. package/dist/control-protocol-guard.d.ts +6 -0
  18. package/dist/control-protocol-guard.js +14 -2
  19. package/dist/control-protocol-guard.js.map +1 -1
  20. package/dist/control-schema-version.d.ts +48 -31
  21. package/dist/control-schema-version.js +67 -45
  22. package/dist/control-schema-version.js.map +1 -1
  23. package/dist/control-schema.d.ts +8 -8
  24. package/dist/control-schema.js +16 -16
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-write-retry.d.ts +42 -7
  27. package/dist/control-write-retry.js +87 -19
  28. package/dist/control-write-retry.js.map +1 -1
  29. package/dist/enrolled-machine-store.d.ts +3 -3
  30. package/dist/enrolled-machine-store.js +3 -3
  31. package/dist/formation-token.d.ts +48 -0
  32. package/dist/formation-token.js +26 -0
  33. package/dist/formation-token.js.map +1 -0
  34. package/dist/index.d.ts +14 -13
  35. package/dist/index.js +16 -13
  36. package/dist/index.js.map +1 -1
  37. package/dist/joined-strand-store.d.ts +26 -9
  38. package/dist/joined-strand-store.js +63 -24
  39. package/dist/joined-strand-store.js.map +1 -1
  40. package/dist/link-budget.d.ts +9 -7
  41. package/dist/link-budget.js +9 -7
  42. package/dist/link-budget.js.map +1 -1
  43. package/dist/member-redemption.d.ts +15 -9
  44. package/dist/member-redemption.js +13 -8
  45. package/dist/member-redemption.js.map +1 -1
  46. package/dist/membership-view.d.ts +91 -0
  47. package/dist/membership-view.js +110 -0
  48. package/dist/membership-view.js.map +1 -0
  49. package/dist/owner-grant-protocol.d.ts +11 -7
  50. package/dist/owner-grant-protocol.js +31 -17
  51. package/dist/owner-grant-protocol.js.map +1 -1
  52. package/dist/peer-addr-book.d.ts +10 -1
  53. package/dist/peer-addr-book.js +4 -3
  54. package/dist/peer-addr-book.js.map +1 -1
  55. package/dist/peer-join-backfill.d.ts +89 -20
  56. package/dist/peer-join-backfill.js +265 -108
  57. package/dist/peer-join-backfill.js.map +1 -1
  58. package/dist/pending-join-runner.d.ts +48 -18
  59. package/dist/pending-join-runner.js +74 -56
  60. package/dist/pending-join-runner.js.map +1 -1
  61. package/dist/seed-bootstrap.d.ts +47 -40
  62. package/dist/seed-bootstrap.js +66 -61
  63. package/dist/seed-bootstrap.js.map +1 -1
  64. package/dist/strand-database.d.ts +23 -2
  65. package/dist/strand-database.js +26 -6
  66. package/dist/strand-database.js.map +1 -1
  67. package/dist/strand-first-sync-gate.d.ts +176 -89
  68. package/dist/strand-first-sync-gate.js +288 -122
  69. package/dist/strand-first-sync-gate.js.map +1 -1
  70. package/dist/strand-formation-deadlines.d.ts +9 -9
  71. package/dist/strand-formation-deadlines.js +9 -9
  72. package/dist/strand-formation-manager.d.ts +23 -17
  73. package/dist/strand-formation-manager.js +46 -44
  74. package/dist/strand-formation-manager.js.map +1 -1
  75. package/dist/strand-formation-protocol.d.ts +5 -8
  76. package/dist/strand-formation-protocol.js +101 -16
  77. package/dist/strand-formation-protocol.js.map +1 -1
  78. package/dist/strand-instance-manager.d.ts +26 -17
  79. package/dist/strand-instance-manager.js +119 -50
  80. package/dist/strand-instance-manager.js.map +1 -1
  81. package/dist/strand-membership-reconciler.d.ts +16 -13
  82. package/dist/strand-membership-reconciler.js +30 -22
  83. package/dist/strand-membership-reconciler.js.map +1 -1
  84. package/dist/strand-membership-writer.d.ts +40 -28
  85. package/dist/strand-membership-writer.js +212 -153
  86. package/dist/strand-membership-writer.js.map +1 -1
  87. package/dist/strand-move.js +5 -2
  88. package/dist/strand-move.js.map +1 -1
  89. package/dist/strand-revocation-enforcer.d.ts +134 -60
  90. package/dist/strand-revocation-enforcer.js +144 -71
  91. package/dist/strand-revocation-enforcer.js.map +1 -1
  92. package/dist/strand-solicitation.d.ts +50 -39
  93. package/dist/strand-solicitation.js +10 -3
  94. package/dist/strand-solicitation.js.map +1 -1
  95. package/dist/strand-watcher.d.ts +80 -13
  96. package/dist/strand-watcher.js +152 -48
  97. package/dist/strand-watcher.js.map +1 -1
  98. package/dist/table-change-watch.d.ts +63 -0
  99. package/dist/table-change-watch.js +209 -0
  100. package/dist/table-change-watch.js.map +1 -0
  101. package/dist/timeout-scheduler.d.ts +4 -3
  102. package/dist/timeout-scheduler.js +4 -3
  103. package/dist/timeout-scheduler.js.map +1 -1
  104. package/dist/types.d.ts +90 -49
  105. package/dist/types.js +4 -4
  106. package/dist/types.js.map +1 -1
  107. package/package.json +8 -8
  108. package/src/cadre-invite-protocol.ts +119 -31
  109. package/src/cadre-node.ts +1282 -584
  110. package/src/control-cohort.ts +86 -9
  111. package/src/control-database.ts +848 -403
  112. package/src/control-formation-recorder.ts +359 -353
  113. package/src/control-protocol-guard.ts +16 -2
  114. package/src/control-schema-version.ts +101 -51
  115. package/src/control-schema.ts +16 -16
  116. package/src/control-write-retry.ts +91 -19
  117. package/src/delegate-admission.ts +351 -351
  118. package/src/enrolled-machine-store.ts +3 -3
  119. package/src/formation-token.ts +63 -0
  120. package/src/index.ts +30 -17
  121. package/src/joined-strand-store.ts +69 -24
  122. package/src/link-budget.ts +9 -7
  123. package/src/member-redemption.ts +20 -13
  124. package/src/membership-view.ts +144 -0
  125. package/src/owner-grant-protocol.ts +33 -18
  126. package/src/peer-addr-book.ts +4 -3
  127. package/src/peer-join-backfill.ts +971 -761
  128. package/src/pending-join-runner.ts +76 -53
  129. package/src/seed-bootstrap.ts +72 -67
  130. package/src/strand-database.ts +352 -322
  131. package/src/strand-first-sync-gate.ts +334 -134
  132. package/src/strand-formation-deadlines.ts +227 -227
  133. package/src/strand-formation-manager.ts +731 -725
  134. package/src/strand-formation-protocol.ts +122 -24
  135. package/src/strand-instance-manager.ts +2092 -2026
  136. package/src/strand-membership-reconciler.ts +33 -25
  137. package/src/strand-membership-writer.ts +2093 -2019
  138. package/src/strand-move.ts +5 -2
  139. package/src/strand-revocation-enforcer.ts +740 -654
  140. package/src/strand-solicitation.ts +61 -34
  141. package/src/strand-watcher.ts +175 -56
  142. package/src/table-change-watch.ts +285 -0
  143. package/src/timeout-scheduler.ts +29 -28
  144. package/src/types.ts +89 -51
@@ -1,6 +1,7 @@
1
1
  import type { Libp2p, PeerId } from '@libp2p/interface';
2
2
  import type { IPeerNetwork } from '@optimystic/db-core';
3
3
  import { BlockTransferClient, type IRawStorage } from '@optimystic/db-p2p';
4
+ import type { Libp2pNodeWithRepo } from './types.js';
4
5
  /**
5
6
  * The block-transfer protocol's hard cap on one length-prefixed message. Mirrors
6
7
  * `MAX_BLOCK_MESSAGE_BYTES` in `@optimystic/db-p2p`'s `protocol-limits.ts` (8 MiB), which
@@ -19,7 +20,10 @@ export declare const PEER_JOIN_BACKFILL_WARN_AFTER_FAILURES = 3;
19
20
  export interface PeerJoinBackfillConfig {
20
21
  /** Default true. False disables the catch-up entirely (the pre-existing behaviour). */
21
22
  enabled?: boolean;
22
- /** Settle time after a connection opens before catching that peer up, ms. Default 1000. */
23
+ /**
24
+ * Settle time before re-running a peer this instance has already run for (a denied or failed
25
+ * peer's next schedule), ms. Default 1000. A peer never run for is run at once.
26
+ */
23
27
  debounceMs?: number;
24
28
  /** Ceiling on blocks copied in one catch-up. Default 10_000. Reaching it is LOGGED, never silent. */
25
29
  maxBlocks?: number;
@@ -76,6 +80,12 @@ export interface PeerJoinBackfillDeps {
76
80
  * see the module comment for why the strand no-gate argument does not carry over.
77
81
  */
78
82
  authorizePeer?: (peerId: string) => Promise<boolean>;
83
+ /**
84
+ * The node's `reportBlockHeld` ({@link Libp2pNodeWithRepo.reportBlockHeld}), told of each block a
85
+ * peer accepted so Optimystic's under-replication drain does not push the peer the same block
86
+ * again, one request per block. Absent = nothing reported, and the drain re-sends.
87
+ */
88
+ reportBlockHeld?: Libp2pNodeWithRepo['reportBlockHeld'];
79
89
  /**
80
90
  * Test seam: build the per-peer push client. Defaults to a real
81
91
  * `BlockTransferClient` over {@link peerNetwork} + {@link protocolPrefix}.
@@ -84,11 +94,21 @@ export interface PeerJoinBackfillDeps {
84
94
  }
85
95
  /** What one peer's catch-up actually did. Returned for tests and logged at the end of each run. */
86
96
  export interface PeerJoinBackfillResult {
87
- /** Blocks offered: had a committed `latest` AND materialized content locally. */
97
+ /** Blocks offered: had a committed `latest` AND materialized content locally, and the peer was not known to hold it. */
88
98
  offered: number;
99
+ /**
100
+ * Skipped: the peer already accepted or refused this revision from this node, or signed its
101
+ * commit (see "What is pushed" in the module comment). Not offered, so not counted toward
102
+ * `maxBlocks`.
103
+ */
104
+ alreadyHeld: number;
89
105
  /** Blocks the remote reported it persisted. */
90
106
  accepted: number;
91
- /** Block ids the remote reported in `missing` (parse or persist failure on its side). */
107
+ /**
108
+ * Block ids the remote reported in `missing` (unparseable, not persisted, or pushed without a
109
+ * commit proof it accepts). Remembered per revision; not offered to that peer again until the
110
+ * local revision moves.
111
+ */
92
112
  rejected: string[];
93
113
  /** Skipped: metadata has no `latest` (pending-only — not yet a durability claim here). */
94
114
  uncommitted: number;
@@ -109,8 +129,8 @@ export interface PeerJoinBackfillResult {
109
129
  *
110
130
  * Best-effort throughout: nothing here throws into a libp2p event handler or into a
111
131
  * runtime bring-up; a failed chunk is logged and the run continues. A peer is marked
112
- * fully caught up ONLY after a run with no thrown chunk and an empty `missing` list, so
113
- * the next `peer:identify` from a peer whose catch-up failed retries it.
132
+ * caught up only after a run with no thrown chunk, so a peer whose catch-up failed is
133
+ * retried; each block is pushed to each peer once (see "What is pushed" in the module comment).
114
134
  */
115
135
  export declare class PeerJoinBackfill {
116
136
  private readonly deps;
@@ -119,10 +139,22 @@ export declare class PeerJoinBackfill {
119
139
  private started;
120
140
  private stopped;
121
141
  /**
122
- * Peers fully caught up this runtime (never rescheduled until the runtime is rebuilt; only
123
- * {@link forceCatchUpPeer} pushes to one again).
142
+ * Peers caught up since their current connection opened. Cleared for a peer when its last
143
+ * connection closes, so a reconnect re-runs; {@link forceCatchUpPeer} runs past it.
124
144
  */
125
145
  private readonly done;
146
+ /**
147
+ * Peers that disconnected while their run was in flight, so that run does not mark them
148
+ * {@link done} after the disconnect cleared it. Cleared when the run ends.
149
+ */
150
+ private readonly disconnectedInFlight;
151
+ /** Per-peer, per-block verdicts from push replies; see {@link PeerMemo}. */
152
+ private readonly memos;
153
+ /**
154
+ * Peers a run has been started for. A peer not in it is run at once on its first schedule;
155
+ * later schedules wait out `debounceMs`.
156
+ */
157
+ private readonly contacted;
126
158
  /**
127
159
  * Peers with a catch-up currently running, each with that run (suppresses concurrent
128
160
  * duplicates; {@link forceCatchUpPeer} waits on the run).
@@ -130,9 +162,9 @@ export declare class PeerJoinBackfill {
130
162
  private readonly inFlight;
131
163
  /**
132
164
  * Peers whose (re-)schedule arrived while their OWN run was in flight, replayed once
133
- * that run finishes. Load-bearing for the gated path: the gate's authorization check is
134
- * a control-database read, so the window between it and the run's end is wide enough for
135
- * the very membership commit that would have authorized the peer to land inside it —
165
+ * that run finishes. Load-bearing for the gated path: the gate is judged once, at the start
166
+ * of a run, and the run's pushes take long enough for the very membership commit that would
167
+ * have authorized the peer to land after that check —
136
168
  * dropping that schedule (rather than deferring it) leaves the denied peer waiting for a
137
169
  * reconnect, which is exactly what the re-arm exists to avoid.
138
170
  */
@@ -150,17 +182,32 @@ export declare class PeerJoinBackfill {
150
182
  /** Peers already reported on `console.warn`; reset when a run finally lands cleanly. */
151
183
  private readonly warned;
152
184
  private readonly onPeerIdentify;
185
+ private readonly onPeerDisconnect;
153
186
  private loggedNoListBlockIds;
154
187
  constructor(deps: PeerJoinBackfillDeps, config?: PeerJoinBackfillConfig);
155
188
  /**
156
189
  * Subscribe to `peer:identify` AND schedule a catch-up for already-connected peers whose
157
190
  * stored protocols already name this network's block-transfer protocol — a peer that does
158
- * not speak it (a bare circuit relay, a bootstrap node) is never scheduled at all. See
159
- * {@link speaksBlockTransfer} and the module comment.
191
+ * not speak it (a bare circuit relay, a bootstrap node) is never scheduled at all. Also
192
+ * subscribes to `peer:disconnect`, so a peer caught up before it went away is run again on
193
+ * its return. See {@link speaksBlockTransfer} and the module comment.
160
194
  */
161
195
  start(): void;
162
196
  /** Unsubscribe, clear timers; in-flight runs observe the stopped flag and bail. */
163
197
  stop(): void;
198
+ /**
199
+ * A peer's last connection closed: it is no longer caught up, so its next `peer:identify`
200
+ * re-runs. Only `done` is cleared. The memo stays (it keeps the re-run to what moved),
201
+ * `contacted` stays (a link flapping inside one settle window costs one run), and the
202
+ * backoff state stays (clearing it re-dials an unreachable peer on every reconnect; see the
203
+ * module comment's retry paragraph).
204
+ *
205
+ * When the returning peer wrote while away with a cohort that named this node, its own
206
+ * re-run pushes those blocks here and reports them through `reportBlockHeld`, so its
207
+ * under-replication drain does not push them again. Revisit if a measurement shows reconnect
208
+ * re-runs dominating push traffic.
209
+ */
210
+ private forgetCaughtUp;
164
211
  /**
165
212
  * (Re-)schedule a debounced catch-up for every currently-connected peer whose protocols, as
166
213
  * already stored in the libp2p peer store, name this network's block-transfer protocol. A
@@ -177,7 +224,11 @@ export declare class PeerJoinBackfill {
177
224
  scheduleConnectedPeers(): Promise<number>;
178
225
  /** Schedules `peerId` only if the peer store already knows it speaks this network's protocol. */
179
226
  private scheduleIfKnownToSpeak;
180
- /** Debounced entry point for connection churn: one run per peer per settle window. */
227
+ /**
228
+ * Entry point for connection churn: one run per peer per settle window, except that a peer
229
+ * never run for is run at once (on a zero-delay timer, so a burst of identify events in one
230
+ * turn still collapses, and no work happens inside the libp2p event dispatch).
231
+ */
181
232
  private schedulePeer;
182
233
  /**
183
234
  * Run one peer's catch-up now, bypassing the debounce. Never rejects. Returns an
@@ -186,14 +237,16 @@ export declare class PeerJoinBackfill {
186
237
  */
187
238
  catchUpPeer(peerId: PeerId): Promise<PeerJoinBackfillResult>;
188
239
  /**
189
- * Run one peer's catch-up now and wait for it, whatever this instance remembers about the
190
- * peer: wait out a run already in flight for it, then push the whole store again even when
191
- * the peer is memoized as caught up. Never rejects; an all-zero result only when stopped.
240
+ * Run one peer's catch-up now and wait for it, even when the peer is memoized as caught up:
241
+ * wait out a run already in flight for it, then run again. The run still skips every block the
242
+ * peer is known to hold (it accepted or refused that revision, or signed it), so for a peer that
243
+ * holds the whole store it pushes nothing and returns `alreadyHeld` equal to the store size.
244
+ * Never rejects; an all-zero result only when stopped.
192
245
  *
193
246
  * For a caller that must know the peer holds this store before it proceeds, where
194
247
  * {@link catchUpPeer}'s empty answer would hide a run that was denied (a `peer:identify`
195
- * run can pass the gate a moment before the caller authorized the peer) or a memo older
196
- * than the commits since.
248
+ * run can pass the gate a moment before the caller authorized the peer) or commits made
249
+ * since the peer was caught up.
197
250
  */
198
251
  forceCatchUpPeer(peerId: PeerId): Promise<PeerJoinBackfillResult>;
199
252
  /**
@@ -218,10 +271,26 @@ export declare class PeerJoinBackfill {
218
271
  * timeout. Reset when a run finally lands cleanly, so a peer that recovers can report again.
219
272
  */
220
273
  private warnPersistentFailure;
274
+ /** This peer's memo, created empty on first use. */
275
+ private memoFor;
276
+ /**
277
+ * Tell the node which blocks of one push reply the peer accepted, at the revision pushed.
278
+ * Best-effort: a failed report is logged and costs only the drain's duplicate push.
279
+ */
280
+ private reportAccepted;
221
281
  /**
222
- * The actual copy. `clean` = every chunk pushed and the remote persisted every block.
282
+ * The actual copy. `clean` = the gate admitted the peer, the store is listable and no push
283
+ * threw (a block the receiver refused is remembered per block instead, see {@link PeerMemo}).
223
284
  * `pushFailed` = at least one push THREW, which is the only non-clean outcome a retry can
224
- * change; the others are verdicts (denied, inert store, blocks the receiver refused).
285
+ * change; the others are verdicts (denied, inert store).
225
286
  */
226
287
  private runCatchUp;
288
+ /**
289
+ * Read one block for the push to `key`, or count why it is skipped in `result` and return
290
+ * nothing: not committed, already held by the peer (memo or its own commit signature), not
291
+ * materialized, or too large for one message. The proof is read for EXACTLY the revision the
292
+ * meta will name, from the same `latest`; absent is normal (nothing retained a proof for that
293
+ * revision), and the block still ships with its meta for the receiver to decide.
294
+ */
295
+ private readOffer;
227
296
  }