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