@serfab/cadre-core 1.8.0 → 1.10.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 (126) hide show
  1. package/dist/cadre-node.d.ts +301 -121
  2. package/dist/cadre-node.js +588 -244
  3. package/dist/cadre-node.js.map +1 -1
  4. package/dist/control-authorization.d.ts +2 -2
  5. package/dist/control-authorization.js +1 -0
  6. package/dist/control-authorization.js.map +1 -1
  7. package/dist/control-cohort.d.ts +1 -1
  8. package/dist/control-cohort.js +1 -1
  9. package/dist/control-database.d.ts +105 -14
  10. package/dist/control-database.js +242 -73
  11. package/dist/control-database.js.map +1 -1
  12. package/dist/control-formation-recorder.d.ts +6 -6
  13. package/dist/control-formation-recorder.js +7 -8
  14. package/dist/control-formation-recorder.js.map +1 -1
  15. package/dist/control-schema.d.ts +1 -1
  16. package/dist/control-schema.js +90 -3
  17. package/dist/control-schema.js.map +1 -1
  18. package/dist/control-write-retry.d.ts +21 -15
  19. package/dist/control-write-retry.js +21 -15
  20. package/dist/control-write-retry.js.map +1 -1
  21. package/dist/index.d.ts +7 -7
  22. package/dist/index.js +16 -13
  23. package/dist/index.js.map +1 -1
  24. package/dist/invitation-bootstrap.d.ts +32 -0
  25. package/dist/invitation-bootstrap.js +30 -0
  26. package/dist/invitation-bootstrap.js.map +1 -0
  27. package/dist/link-budget.d.ts +204 -119
  28. package/dist/link-budget.js +208 -120
  29. package/dist/link-budget.js.map +1 -1
  30. package/dist/membership-connection-gater.d.ts +3 -3
  31. package/dist/membership-connection-gater.js +3 -3
  32. package/dist/node-local-snapshot.d.ts +6 -4
  33. package/dist/node-local-snapshot.js +7 -3
  34. package/dist/node-local-snapshot.js.map +1 -1
  35. package/dist/peer-dial.d.ts +18 -14
  36. package/dist/peer-dial.js +18 -14
  37. package/dist/peer-dial.js.map +1 -1
  38. package/dist/peer-join-backfill.d.ts +3 -12
  39. package/dist/peer-join-backfill.js +2 -3
  40. package/dist/peer-join-backfill.js.map +1 -1
  41. package/dist/pending-join-runner.d.ts +186 -0
  42. package/dist/pending-join-runner.js +649 -0
  43. package/dist/pending-join-runner.js.map +1 -0
  44. package/dist/push-fanout.d.ts +1 -1
  45. package/dist/push-fanout.js +1 -1
  46. package/dist/seed-bootstrap.d.ts +2 -2
  47. package/dist/seed-bootstrap.js +1 -1
  48. package/dist/strand-addr-protocol.js +2 -2
  49. package/dist/strand-formation-deadlines.d.ts +5 -5
  50. package/dist/strand-formation-deadlines.js +5 -5
  51. package/dist/strand-formation-manager.d.ts +18 -4
  52. package/dist/strand-formation-manager.js +48 -33
  53. package/dist/strand-formation-manager.js.map +1 -1
  54. package/dist/strand-formation-protocol.d.ts +71 -25
  55. package/dist/strand-formation-protocol.js +225 -51
  56. package/dist/strand-formation-protocol.js.map +1 -1
  57. package/dist/strand-formation-rejection.d.ts +94 -0
  58. package/dist/strand-formation-rejection.js +77 -0
  59. package/dist/strand-formation-rejection.js.map +1 -0
  60. package/dist/strand-fret-addrs.d.ts +54 -0
  61. package/dist/strand-fret-addrs.js +108 -0
  62. package/dist/strand-fret-addrs.js.map +1 -0
  63. package/dist/strand-instance-manager.d.ts +9 -38
  64. package/dist/strand-instance-manager.js +17 -74
  65. package/dist/strand-instance-manager.js.map +1 -1
  66. package/dist/strand-network-state-file.d.ts +12 -0
  67. package/dist/strand-network-state-file.js +38 -0
  68. package/dist/strand-network-state-file.js.map +1 -0
  69. package/dist/strand-network-state.d.ts +111 -0
  70. package/dist/strand-network-state.js +191 -0
  71. package/dist/strand-network-state.js.map +1 -0
  72. package/dist/strand-solicitation.d.ts +11 -3
  73. package/dist/strand-solicitation.js +9 -0
  74. package/dist/strand-solicitation.js.map +1 -1
  75. package/dist/types.d.ts +123 -40
  76. package/dist/types.js +5 -9
  77. package/dist/types.js.map +1 -1
  78. package/package.json +25 -24
  79. package/src/cadre-node.ts +637 -251
  80. package/src/control-authorization.ts +117 -116
  81. package/src/control-cohort.ts +1 -1
  82. package/src/control-database.ts +303 -82
  83. package/src/control-formation-recorder.ts +8 -9
  84. package/src/control-schema.ts +90 -3
  85. package/src/control-write-retry.ts +21 -15
  86. package/src/index.ts +36 -56
  87. package/src/invitation-bootstrap.ts +40 -0
  88. package/src/link-budget.ts +221 -120
  89. package/src/membership-connection-gater.ts +3 -3
  90. package/src/node-local-snapshot.ts +12 -7
  91. package/src/peer-dial.ts +18 -14
  92. package/src/peer-join-backfill.ts +5 -6
  93. package/src/pending-join-runner.ts +735 -0
  94. package/src/push-fanout.ts +1 -1
  95. package/src/seed-bootstrap.ts +2 -2
  96. package/src/strand-addr-protocol.ts +2 -2
  97. package/src/strand-formation-deadlines.ts +5 -5
  98. package/src/strand-formation-manager.ts +62 -37
  99. package/src/strand-formation-protocol.ts +1036 -803
  100. package/src/strand-formation-rejection.ts +129 -0
  101. package/src/strand-fret-addrs.ts +145 -0
  102. package/src/strand-instance-manager.ts +24 -97
  103. package/src/strand-network-state-file.ts +40 -0
  104. package/src/strand-network-state.ts +243 -0
  105. package/src/strand-solicitation.ts +17 -4
  106. package/src/types.ts +119 -40
  107. package/dist/strand-peer-book-file.d.ts +0 -12
  108. package/dist/strand-peer-book-file.js +0 -37
  109. package/dist/strand-peer-book-file.js.map +0 -1
  110. package/dist/strand-peer-book-protocol.d.ts +0 -168
  111. package/dist/strand-peer-book-protocol.js +0 -356
  112. package/dist/strand-peer-book-protocol.js.map +0 -1
  113. package/dist/strand-peer-book-swap.d.ts +0 -104
  114. package/dist/strand-peer-book-swap.js +0 -284
  115. package/dist/strand-peer-book-swap.js.map +0 -1
  116. package/dist/strand-peer-book.d.ts +0 -159
  117. package/dist/strand-peer-book.js +0 -361
  118. package/dist/strand-peer-book.js.map +0 -1
  119. package/dist/strand-peer-observer.d.ts +0 -88
  120. package/dist/strand-peer-observer.js +0 -180
  121. package/dist/strand-peer-observer.js.map +0 -1
  122. package/src/strand-peer-book-file.ts +0 -39
  123. package/src/strand-peer-book-protocol.ts +0 -466
  124. package/src/strand-peer-book-swap.ts +0 -338
  125. package/src/strand-peer-book.ts +0 -475
  126. package/src/strand-peer-observer.ts +0 -235
@@ -77,11 +77,12 @@ export const SCHEMA_INIT_ATTEMPTS = 5;
77
77
  * This budget — not the attempt count — is what makes the policy safe to sit under every
78
78
  * control write. A transient failure (stream reset while a connection is still forming)
79
79
  * surfaces in well under a second, so it is retried and the loop adds at most ~2.2 s. A
80
- * genuinely silent cohort member fails at ~21 s per pend round (two `ClusterClient`
81
- * response-deadline attempts, 10.5 s each at the default declared link; a two-round write
82
- * measured 42.2 s in `control-write-degraded-cohort-member.integration.ts`), which
83
- * already exceeds this budget when attempt 1 returns — so that case is surfaced immediately
84
- * and retry adds ZERO latency to the case where it cannot help.
80
+ * genuinely silent cohort member costs ~21 s per consensus round (two `ClusterClient`
81
+ * response-deadline attempts, 10.5 s each at the default declared link): one pend round plus
82
+ * the cancel discharge's three rounds, about 84 s for one write in
83
+ * `control-write-degraded-cohort-member.integration.ts`, which already exceeds this budget
84
+ * when attempt 1 returns — so that case is surfaced immediately and retry adds ZERO latency
85
+ * to the case where it cannot help.
85
86
  *
86
87
  * It cuts the loop off by design, so it does not grow with the link: its job is to end the
87
88
  * retries of slow attempts, and the caller gets the last attempt's error unchanged. An attempt
@@ -92,16 +93,21 @@ export const SCHEMA_INIT_ATTEMPTS = 5;
92
93
  * (10.5 s at the default declared link) still gets none. See `docs/cadre-consistency.md` →
93
94
  * "Deadlines Over Optimystic's Reads and Commits".
94
95
  *
95
- * NOTE: a failed commit attempt now also pays a cancel discharge before its error returns
96
- * (optimystic `TransactorSource.transact`'s catch, added by upstream
97
- * `1-a-failed-attempt-must-discharge-its-own-pend`) — bounded by six rounds and
98
- * `abortOrCancelTimeoutMs`, which every collection this repo opens sets to 5 s
99
- * (`../optimystic/packages/quereus-plugin-optimystic/src/optimystic-adapter/collection-factory.ts`).
100
- * Two failed attempts whose cancels each run their full budget would consume this whole 10 s
101
- * ceiling and cut the three-attempt policy to two. Not observed — every measured round of the
102
- * transient-reset case committed on attempt 3 of 3 — so this is a condition to watch, not work
103
- * to do: if that case ever starts failing with `failed after 2/3 attempt(s)`, the cancel
104
- * discharge is where the time went.
96
+ * NOTE: a failed commit attempt also pays a cancel discharge before its error returns
97
+ * (optimystic `TransactorSource.transact`'s catch) — bounded by six rounds and
98
+ * `abortOrCancelTimeoutMs`, which every collection this repo opens sets to the larger of 5 s
99
+ * and the RPC dial deadline: 42.5 s at the default declared link (`optimysticDialLimits` in
100
+ * `link-budget.ts`;
101
+ * `../optimystic/packages/quereus-plugin-optimystic/src/optimystic-adapter/collection-factory.ts`).
102
+ * One cancel that runs its full budget therefore ends the retries on its own, cutting the
103
+ * three-attempt policy to however many attempts came before it. A cancel only runs that long
104
+ * when a peer stays silent, and that attempt has already spent its pend round past this
105
+ * budget, so the cut loses nothing there. After a transient fault the cancel is either
106
+ * answered or gives up after its six rounds against a dead transport, in about a second
107
+ * either way (optimystic `NetworkTransactor.MAX_CANCEL_ROUNDS`). Not observed — every
108
+ * measured round of the transient-reset case committed on attempt 3 of 3 — so this is a
109
+ * condition to watch, not work to do: if that case ever starts failing with
110
+ * `failed after 2/3 attempt(s)`, the cancel discharge is where the time went.
105
111
  */
106
112
  // eslint-disable-next-line no-restricted-syntax -- cuts off by design: it ends the retries of slow attempts rather than waiting them out; see docs/cadre-consistency.md → "Deadlines Over Optimystic's Reads and Commits"
107
113
  export const CONTROL_WRITE_RETRY_BUDGET_MS = 10_000;
package/src/index.ts CHANGED
@@ -128,61 +128,21 @@ export {
128
128
  type JoinedStrandRecord
129
129
  } from './joined-strand-store.js';
130
130
 
131
- // Node-local strand peer book: per strand, the strand peers this node has met and
132
- // their last-known addresses, dialed first on every launch so a restarted machine
133
- // re-finds the other parties. Same cross-platform split as the bootstrap-peer store
134
- // above — interface + in-memory + slot-backed stores here, Node-only file backend
135
- // behind the subpath '@serfab/cadre-core/strand-peer-book-file'.
136
- export {
137
- MemoryStrandPeerBookStore,
138
- PersistentStrandPeerBookStore,
139
- mergeStrandPeerEntry,
140
- sanitizeStrandPeerEntry,
141
- strandPeerFreshness,
142
- MAX_STRAND_PEERS,
143
- STRAND_PEER_MAX_AGE_MS,
144
- type StrandPeerBookStore,
145
- type StrandPeerBookOptions,
146
- type StrandPeerEntry
147
- } from './strand-peer-book.js';
148
- export {
149
- StrandPeerObserver,
150
- dialableAddrs,
151
- connectedIdentifiedPeers,
152
- STRAND_PEER_OBSERVE_THROTTLE_MS,
153
- type StrandPeerObservation,
154
- type StrandPeerObserverDeps,
155
- type StrandPeerObserverOptions,
156
- type ConnectedIdentifiedPeer
157
- } from './strand-peer-observer.js';
158
- // The signed book SWAP between strand peers (`/sereus/strand-peers/1.0.0`): the wire
159
- // protocol, signing and verification, and the per-strand driver that arms it.
160
- export {
161
- STRAND_PEER_BOOK_PROTOCOL,
162
- SIGNED_STRAND_PEER_ENTRY_VERSION,
163
- STRAND_PEER_ISSUED_AT_SKEW_MS,
164
- MAX_BOOK_FRAME_SIZE,
165
- StrandPeerBookService,
166
- exchangeStrandPeerBook,
167
- signStrandPeerEntry,
168
- signedStrandPeerEntryPayload,
169
- verifySignedStrandPeerEntry,
170
- verifyStrandPeerBookFrame,
171
- trimBookFrameToFit,
172
- assertBookFrameFits,
173
- type SignedStrandPeerEntry,
174
- type StrandPeerBookFrame,
175
- type StrandPeerEntryVerdict,
176
- type StrandPeerBookServiceDeps,
177
- type ExchangeStrandPeerBookOptions
178
- } from './strand-peer-book-protocol.js';
179
- export {
180
- StrandPeerBookSwap,
181
- STRAND_PEER_BOOK_SWAP_THROTTLE_MS,
182
- OWN_ENTRY_RESIGN_DEBOUNCE_MS,
183
- type StrandPeerBookSwapDeps,
184
- type StrandPeerBookSwapOptions
185
- } from './strand-peer-book-swap.js';
131
+ // Node-local strand network state: per strand, the state Optimystic's db-p2p saves for
132
+ // the strand node (its FRET routing table with each peer's signed address record), so a
133
+ // restarted strand node re-imports the peers it was talking to. Same cross-platform
134
+ // split as the bootstrap-peer store above — interface + in-memory + slot-backed stores
135
+ // here, Node-only file backend behind '@serfab/cadre-core/strand-network-state-file'.
136
+ export {
137
+ MemoryStrandNetworkStateStore,
138
+ PersistentStrandNetworkStateStore,
139
+ strandNetworkStatePersistence,
140
+ type StrandNetworkStateStore
141
+ } from './strand-network-state.js';
142
+ // The signed address records a running strand node's FRET table holds, as per-peer dial
143
+ // addresses: what the periodic refresh re-merges into the node's address book, and a
144
+ // diagnostic for "whom could this strand node redial right now?".
145
+ export { strandFretPeerAddrs, type FretAddrHost, type FretPeerAddrs } from './strand-fret-addrs.js';
186
146
 
187
147
  // Storage scope keys — what `CadreNodeConfig.storage.provider` is called with. A
188
148
  // strand's key is its strand id; the control database's key carries the party id,
@@ -524,6 +484,8 @@ export {
524
484
  // one declared assumption about the link moves them all — see `NetworkConfig.linkRoundTripMs`.
525
485
  export {
526
486
  DECLARED_LINK_ROUND_TRIP_MS,
487
+ CIRCUIT_DIAL_ROUND_TRIPS,
488
+ RELAY_DIAL_ROUND_TRIPS,
527
489
  RELAYED_DIAL_ROUND_TRIPS,
528
490
  RELAY_RESERVATION_ROUND_TRIPS,
529
491
  CIRCUIT_REQUEST_ROUND_TRIPS,
@@ -536,7 +498,10 @@ export {
536
498
  circuitRequestBudgetMs,
537
499
  relayedRequestBudgetMs,
538
500
  peerJoinPushBudget,
539
- type PeerJoinPushBudget
501
+ type PeerJoinPushBudget,
502
+ DIAL_ADMISSION_DECISIONS,
503
+ optimysticDialLimits,
504
+ type OptimysticDialLimits
540
505
  } from './link-budget.js';
541
506
 
542
507
  // Seed trust policy (trust anchor for incoming seeds)
@@ -561,6 +526,7 @@ export {
561
526
  export {
562
527
  FormationListener,
563
528
  dialFormation,
529
+ dialFormationByMachine,
564
530
  isValidResponderCreatesResult,
565
531
  isWellFormedMembershipInvite,
566
532
  sanitizeStrandAddrs,
@@ -574,9 +540,22 @@ export {
574
540
  type FormationListenerOptions,
575
541
  type FormationDialOptions,
576
542
  type FormationDialResult,
543
+ type FormationRejection,
544
+ type FormationTokenCheck,
577
545
  type ResponderProvisionOutcome
578
546
  } from './strand-formation-protocol.js';
547
+ // How a formation dial fails: a typed refusal (with a fixed code) or no answer at all
548
+ export {
549
+ FormationRejectedError,
550
+ FormationUnreachableError,
551
+ FormationPostApprovalError,
552
+ FORMATION_REJECTION_RETRYABLE,
553
+ isFormationRejectionCode,
554
+ type FormationRejectionCode
555
+ } from './strand-formation-rejection.js';
579
556
  export { formationDeadlines, type FormationDeadlines } from './strand-formation-deadlines.js';
557
+ // The pending-join retry loop behind CadreNode.requestJoin (PendingJoinStatus is in types.ts)
558
+ export { PENDING_JOIN_POLL_MS, PENDING_JOIN_MAX_BACKOFF_MS, MAX_PENDING_JOIN_MS } from './pending-join-runner.js';
580
559
 
581
560
  // Strand Formation manager (drives the native transport)
582
561
  export {
@@ -585,6 +564,7 @@ export {
585
564
  MEMBERSHIP_INVITE_TTL_MS,
586
565
  MEMBERSHIP_INVITE_UNAVAILABLE_REASON,
587
566
  HOST_STRAND_MUST_BE_RECREATED_REASON,
567
+ type CadrePeerAddrsSource,
588
568
  type MembershipInviteIssuer,
589
569
  type StrandFormationManagerConfig,
590
570
  type StrandFormationManagerOptions
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Which of the inviting party's other machines an open invitation names, so a joiner can still
3
+ * form while the machine that minted it is offline. The minting machine's own addresses go
4
+ * first and are not chosen here (`CadreNode.createOpenInvitation`).
5
+ */
6
+
7
+ /**
8
+ * At most this many of the party's other machines are named, each with at most
9
+ * {@link INVITATION_ADDRS_PER_SIBLING} addresses: 12 extra addresses of roughly 100–200
10
+ * characters, base64url-encoded into an invitation shared as text.
11
+ *
12
+ * NOTE: if invitations are ever carried in a QR code, lower these caps or name the machines by
13
+ * peer id only.
14
+ * NOTE: the per-machine cap keeps the record's order (relay first, then direct addresses as the
15
+ * machine published them, loopback included). If a sibling with many interfaces is seen to have
16
+ * its reachable address cut, rank loopback and private addresses last before slicing.
17
+ */
18
+ export const INVITATION_SIBLING_MACHINES = 3;
19
+ export const INVITATION_ADDRS_PER_SIBLING = 4;
20
+
21
+ /** One other machine of the party whose signed address record resolved. */
22
+ export interface InvitationSibling {
23
+ peerId: string;
24
+ /** The record's `UpdatedAt`: when the machine last published its addresses. */
25
+ updatedAt: number;
26
+ /** Relay (signaling) addresses first, each ending in `/p2p/<peerId>`. */
27
+ addrs: string[];
28
+ }
29
+
30
+ /**
31
+ * The addresses of the siblings an invitation names. Machines this node is connected to right
32
+ * now come first, since a live connection is the best evidence that a machine is up; a record
33
+ * shows only that it was up within the freshness window. Within each half, newest record first.
34
+ */
35
+ export function selectInvitationSiblingAddrs(siblings: InvitationSibling[], connected: ReadonlySet<string>): string[] {
36
+ return [...siblings]
37
+ .sort((a, b) => Number(connected.has(b.peerId)) - Number(connected.has(a.peerId)) || b.updatedAt - a.updatedAt)
38
+ .slice(0, INVITATION_SIBLING_MACHINES)
39
+ .flatMap((sibling) => sibling.addrs.slice(0, INVITATION_ADDRS_PER_SIBLING));
40
+ }