@serfab/cadre-core 0.9.0 → 0.11.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 (193) hide show
  1. package/README.md +255 -253
  2. package/dist/bootstrap-peer-store-file.d.ts +13 -0
  3. package/dist/bootstrap-peer-store-file.js +42 -0
  4. package/dist/bootstrap-peer-store-file.js.map +1 -0
  5. package/dist/bootstrap-peer-store.d.ts +73 -0
  6. package/dist/bootstrap-peer-store.js +128 -0
  7. package/dist/bootstrap-peer-store.js.map +1 -0
  8. package/dist/cadre-node.d.ts +1121 -81
  9. package/dist/cadre-node.js +2276 -262
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/control-authorization.d.ts +93 -0
  12. package/dist/control-authorization.js +57 -0
  13. package/dist/control-authorization.js.map +1 -0
  14. package/dist/control-cohort.d.ts +26 -0
  15. package/dist/control-cohort.js +26 -0
  16. package/dist/control-cohort.js.map +1 -1
  17. package/dist/control-database.d.ts +959 -54
  18. package/dist/control-database.js +1421 -148
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +90 -10
  21. package/dist/control-formation-recorder.js +130 -14
  22. package/dist/control-formation-recorder.js.map +1 -1
  23. package/dist/control-schema.d.ts +1 -1
  24. package/dist/control-schema.js +651 -72
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-stream.d.ts +38 -4
  27. package/dist/control-stream.js +70 -4
  28. package/dist/control-stream.js.map +1 -1
  29. package/dist/control-write-retry.d.ts +161 -0
  30. package/dist/control-write-retry.js +412 -0
  31. package/dist/control-write-retry.js.map +1 -0
  32. package/dist/delegate-admission.d.ts +134 -0
  33. package/dist/delegate-admission.js +244 -0
  34. package/dist/delegate-admission.js.map +1 -0
  35. package/dist/device-token.d.ts +11 -11
  36. package/dist/device-token.js +14 -13
  37. package/dist/device-token.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +26 -0
  39. package/dist/ed25519-key.js +56 -1
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/file-durable-slot.d.ts +20 -0
  42. package/dist/file-durable-slot.js +53 -0
  43. package/dist/file-durable-slot.js.map +1 -0
  44. package/dist/formation-approval.d.ts +157 -0
  45. package/dist/formation-approval.js +375 -0
  46. package/dist/formation-approval.js.map +1 -0
  47. package/dist/fs-atomic.d.ts +38 -0
  48. package/dist/fs-atomic.js +97 -0
  49. package/dist/fs-atomic.js.map +1 -0
  50. package/dist/identity-key.d.ts +49 -0
  51. package/dist/identity-key.js +81 -0
  52. package/dist/identity-key.js.map +1 -0
  53. package/dist/index.d.ts +17 -7
  54. package/dist/index.js +40 -6
  55. package/dist/index.js.map +1 -1
  56. package/dist/key-store-file.d.ts +2 -24
  57. package/dist/key-store-file.js +10 -77
  58. package/dist/key-store-file.js.map +1 -1
  59. package/dist/membership-connection-gater.d.ts +146 -0
  60. package/dist/membership-connection-gater.js +180 -0
  61. package/dist/membership-connection-gater.js.map +1 -0
  62. package/dist/node-local-snapshot.d.ts +109 -0
  63. package/dist/node-local-snapshot.js +182 -0
  64. package/dist/node-local-snapshot.js.map +1 -0
  65. package/dist/peer-addr-book.d.ts +92 -0
  66. package/dist/peer-addr-book.js +220 -0
  67. package/dist/peer-addr-book.js.map +1 -0
  68. package/dist/peer-authorization.d.ts +135 -18
  69. package/dist/peer-authorization.js +152 -21
  70. package/dist/peer-authorization.js.map +1 -1
  71. package/dist/peer-record.d.ts +21 -8
  72. package/dist/peer-record.js +26 -10
  73. package/dist/peer-record.js.map +1 -1
  74. package/dist/relay-addrs.d.ts +45 -0
  75. package/dist/relay-addrs.js +103 -0
  76. package/dist/relay-addrs.js.map +1 -0
  77. package/dist/relay-reservation.d.ts +266 -0
  78. package/dist/relay-reservation.js +613 -0
  79. package/dist/relay-reservation.js.map +1 -0
  80. package/dist/seed-bootstrap.d.ts +228 -56
  81. package/dist/seed-bootstrap.js +340 -198
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +47 -21
  84. package/dist/seed-trust-policy.js +24 -15
  85. package/dist/seed-trust-policy.js.map +1 -1
  86. package/dist/strand-addr-protocol.d.ts +42 -13
  87. package/dist/strand-addr-protocol.js +58 -52
  88. package/dist/strand-addr-protocol.js.map +1 -1
  89. package/dist/strand-backfill.d.ts +109 -0
  90. package/dist/strand-backfill.js +279 -0
  91. package/dist/strand-backfill.js.map +1 -0
  92. package/dist/strand-cohort.d.ts +4 -37
  93. package/dist/strand-cohort.js +4 -15
  94. package/dist/strand-cohort.js.map +1 -1
  95. package/dist/strand-database.d.ts +11 -16
  96. package/dist/strand-database.js +19 -7
  97. package/dist/strand-database.js.map +1 -1
  98. package/dist/strand-formation-manager.d.ts +36 -7
  99. package/dist/strand-formation-manager.js +115 -15
  100. package/dist/strand-formation-manager.js.map +1 -1
  101. package/dist/strand-formation-protocol.d.ts +103 -4
  102. package/dist/strand-formation-protocol.js +232 -4
  103. package/dist/strand-formation-protocol.js.map +1 -1
  104. package/dist/strand-instance-manager.d.ts +88 -19
  105. package/dist/strand-instance-manager.js +178 -34
  106. package/dist/strand-instance-manager.js.map +1 -1
  107. package/dist/strand-member-registry.d.ts +5 -5
  108. package/dist/strand-member-registry.js +18 -10
  109. package/dist/strand-member-registry.js.map +1 -1
  110. package/dist/strand-membership-writer.d.ts +484 -88
  111. package/dist/strand-membership-writer.js +751 -128
  112. package/dist/strand-membership-writer.js.map +1 -1
  113. package/dist/strand-solicitation.d.ts +99 -33
  114. package/dist/strand-solicitation.js +100 -52
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-transport-key.d.ts +40 -0
  117. package/dist/strand-transport-key.js +56 -0
  118. package/dist/strand-transport-key.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +9 -7
  120. package/dist/strand-wake-protocol.js +22 -46
  121. package/dist/strand-wake-protocol.js.map +1 -1
  122. package/dist/strand-watcher.d.ts +17 -2
  123. package/dist/strand-watcher.js +47 -3
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/trusted-owner-store-file.d.ts +12 -0
  126. package/dist/trusted-owner-store-file.js +39 -0
  127. package/dist/trusted-owner-store-file.js.map +1 -0
  128. package/dist/trusted-owner-store.d.ts +64 -0
  129. package/dist/trusted-owner-store.js +126 -0
  130. package/dist/trusted-owner-store.js.map +1 -0
  131. package/dist/types.d.ts +307 -44
  132. package/dist/types.js +13 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +20 -8
  135. package/src/arachnode-stub.ts +159 -159
  136. package/src/bootstrap-peer-store-file.ts +44 -0
  137. package/src/bootstrap-peer-store.ts +172 -0
  138. package/src/cadre-node.ts +5158 -2981
  139. package/src/canonical-json.ts +30 -30
  140. package/src/control-authorization.ts +114 -0
  141. package/src/control-cohort.ts +130 -103
  142. package/src/control-database.ts +2406 -948
  143. package/src/control-formation-recorder.ts +305 -151
  144. package/src/control-schema.ts +651 -72
  145. package/src/control-stream.ts +205 -128
  146. package/src/control-write-retry.ts +483 -0
  147. package/src/delegate-admission.ts +302 -0
  148. package/src/device-token.ts +94 -89
  149. package/src/diagnostics/connection-path.ts +277 -277
  150. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  151. package/src/ed25519-key.ts +66 -1
  152. package/src/enrollment.ts +199 -199
  153. package/src/file-durable-slot.ts +61 -0
  154. package/src/formation-approval.ts +604 -0
  155. package/src/fs-atomic.ts +107 -0
  156. package/src/hibernation-manager.ts +437 -437
  157. package/src/identity-key.ts +109 -0
  158. package/src/index.ts +137 -5
  159. package/src/key-store-file.ts +132 -203
  160. package/src/key-store.ts +99 -99
  161. package/src/membership-connection-gater.ts +204 -0
  162. package/src/node-local-snapshot.ts +260 -0
  163. package/src/peer-addr-book.ts +250 -0
  164. package/src/peer-authorization.ts +259 -81
  165. package/src/peer-record.ts +164 -146
  166. package/src/push-credentials.ts +84 -84
  167. package/src/push-node.ts +64 -64
  168. package/src/push-notifier-apns.ts +247 -247
  169. package/src/push-notifier-fcm.ts +220 -220
  170. package/src/push-notifier-shared.ts +28 -28
  171. package/src/push-notifier.ts +52 -52
  172. package/src/relay-addrs.ts +111 -0
  173. package/src/relay-reservation.ts +823 -0
  174. package/src/seed-bootstrap.ts +409 -209
  175. package/src/seed-trust-policy.ts +140 -107
  176. package/src/strand-addr-protocol.ts +441 -410
  177. package/src/strand-backfill.ts +366 -0
  178. package/src/strand-cohort.ts +38 -77
  179. package/src/strand-database.ts +202 -204
  180. package/src/strand-formation-manager.ts +491 -368
  181. package/src/strand-formation-protocol.ts +692 -391
  182. package/src/strand-instance-manager.ts +201 -46
  183. package/src/strand-member-key.ts +39 -39
  184. package/src/strand-member-registry.ts +182 -177
  185. package/src/strand-membership-writer.ts +938 -146
  186. package/src/strand-solicitation.ts +472 -407
  187. package/src/strand-transport-key.ts +58 -0
  188. package/src/strand-wake-payload.ts +32 -32
  189. package/src/strand-wake-protocol.ts +314 -331
  190. package/src/strand-watcher.ts +314 -251
  191. package/src/trusted-owner-store-file.ts +41 -0
  192. package/src/trusted-owner-store.ts +171 -0
  193. package/src/types.ts +1309 -1032
@@ -1 +1 @@
1
- {"version":3,"file":"control-schema.js","sourceRoot":"","sources":["../src/control-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAkNH,CAAC"}
1
+ {"version":3,"file":"control-schema.js","sourceRoot":"","sources":["../src/control-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAqxBH,CAAC"}
@@ -9,6 +9,12 @@
9
9
  * abort-on-timeout read-to-EOF helper so a misbehaving peer that opens a stream
10
10
  * and never half-closes its write end cannot pin a receiver forever.
11
11
  *
12
+ * The sender side of all three protocols is the same exchange — dial under a
13
+ * deadline, write one frame, half-close, read one response, reset the stream on
14
+ * any failure — so that shape lives here too, as {@link withDeadline} +
15
+ * {@link exchangeFrame}; each protocol supplies only its dial options, its
16
+ * request object, and its response decoder.
17
+ *
12
18
  * Dependency-free by design: it imports nothing from the protocol modules, so
13
19
  * the import graph stays acyclic (`control-stream` ← `seed-bootstrap` ←
14
20
  * `strand-wake-protocol`). In particular it returns RAW bytes from
@@ -28,12 +34,40 @@ export interface ControlStream extends AsyncIterable<Uint8Array> {
28
34
  export declare function writeFrame(stream: ControlStream, obj: unknown): void;
29
35
  /**
30
36
  * Reject if `op` does not settle within `ms`. On timeout, invoke `onTimeout`
31
- * (used by the sender to abort the in-flight dial/stream so it does not leak)
32
- * before rejecting. A throwing `onTimeout` is swallowed so it cannot mask the
33
- * timeout rejection. The rejection message is exactly `<label> timed out after
34
- * <ms>ms`, so callers fold any domain prefix into `label`.
37
+ * before rejecting — a hook for releasing whatever the op is parked on. A
38
+ * throwing `onTimeout` is swallowed so it cannot mask the timeout rejection. The
39
+ * rejection message is exactly `<label> timed out after <ms>ms`, so callers fold
40
+ * any domain prefix into `label`.
41
+ *
42
+ * Senders want {@link withDeadline}, which wires `onTimeout` to an
43
+ * `AbortController` so the in-flight dial/stream is cancelled rather than leaked.
35
44
  */
36
45
  export declare function withTimeout<T>(ms: number, label: string, op: () => Promise<T>, onTimeout?: () => void): Promise<T>;
46
+ /**
47
+ * Run `op` under a `withTimeout` deadline that also *cancels* it: the signal
48
+ * handed to `op` is aborted immediately before the timeout rejection, so a
49
+ * sender can pass it to `dialProtocol` (cancelling an in-flight connect) and to
50
+ * {@link exchangeFrame} (resetting a live stream) instead of leaking either.
51
+ */
52
+ export declare function withDeadline<T>(ms: number, label: string, op: (signal: AbortSignal) => Promise<T>): Promise<T>;
53
+ /**
54
+ * Send one request frame on an open stream, half-close the write end, and read
55
+ * the response through `readResponse`.
56
+ *
57
+ * `signal` is the caller's deadline (see {@link withDeadline}): an abort resets
58
+ * the stream, releasing the otherwise unbounded response read. If the deadline
59
+ * already fired while the stream was being dialed, the freshly-opened stream is
60
+ * reset here rather than left dangling. Every reset — deadline, response-decode
61
+ * failure, or a `readResponse` that rejects without aborting (the size-cap path
62
+ * of {@link readStreamToEnd}) — goes through one idempotent `abort`, so the
63
+ * deadline listener and the error path cannot double-reset a stream.
64
+ *
65
+ * `readResponse` owns its own read bound, which is a backstop rather than the
66
+ * primary one: the caller's deadline started first and therefore fires first.
67
+ * It matters only when an abort does not release the read (e.g. a stream double
68
+ * whose `abort()` is a no-op).
69
+ */
70
+ export declare function exchangeFrame<T>(stream: ControlStream, signal: AbortSignal, request: unknown, readResponse: (stream: ControlStream) => Promise<T>, abortMessage: string): Promise<T>;
37
71
  /**
38
72
  * Read a stream to EOF, capped at `maxBytes`, bounded by `timeoutMs`, returning
39
73
  * the assembled bytes (the caller decodes via `decodeLengthPrefixedFrame`).
@@ -9,6 +9,12 @@
9
9
  * abort-on-timeout read-to-EOF helper so a misbehaving peer that opens a stream
10
10
  * and never half-closes its write end cannot pin a receiver forever.
11
11
  *
12
+ * The sender side of all three protocols is the same exchange — dial under a
13
+ * deadline, write one frame, half-close, read one response, reset the stream on
14
+ * any failure — so that shape lives here too, as {@link withDeadline} +
15
+ * {@link exchangeFrame}; each protocol supplies only its dial options, its
16
+ * request object, and its response decoder.
17
+ *
12
18
  * Dependency-free by design: it imports nothing from the protocol modules, so
13
19
  * the import graph stays acyclic (`control-stream` ← `seed-bootstrap` ←
14
20
  * `strand-wake-protocol`). In particular it returns RAW bytes from
@@ -25,10 +31,13 @@ export function writeFrame(stream, obj) {
25
31
  }
26
32
  /**
27
33
  * Reject if `op` does not settle within `ms`. On timeout, invoke `onTimeout`
28
- * (used by the sender to abort the in-flight dial/stream so it does not leak)
29
- * before rejecting. A throwing `onTimeout` is swallowed so it cannot mask the
30
- * timeout rejection. The rejection message is exactly `<label> timed out after
31
- * <ms>ms`, so callers fold any domain prefix into `label`.
34
+ * before rejecting — a hook for releasing whatever the op is parked on. A
35
+ * throwing `onTimeout` is swallowed so it cannot mask the timeout rejection. The
36
+ * rejection message is exactly `<label> timed out after <ms>ms`, so callers fold
37
+ * any domain prefix into `label`.
38
+ *
39
+ * Senders want {@link withDeadline}, which wires `onTimeout` to an
40
+ * `AbortController` so the in-flight dial/stream is cancelled rather than leaked.
32
41
  */
33
42
  export function withTimeout(ms, label, op, onTimeout) {
34
43
  return new Promise((resolve, reject) => {
@@ -44,6 +53,63 @@ export function withTimeout(ms, label, op, onTimeout) {
44
53
  op().then(resolve, reject).finally(() => clearTimeout(timer));
45
54
  });
46
55
  }
56
+ /**
57
+ * Run `op` under a `withTimeout` deadline that also *cancels* it: the signal
58
+ * handed to `op` is aborted immediately before the timeout rejection, so a
59
+ * sender can pass it to `dialProtocol` (cancelling an in-flight connect) and to
60
+ * {@link exchangeFrame} (resetting a live stream) instead of leaking either.
61
+ */
62
+ export function withDeadline(ms, label, op) {
63
+ const controller = new AbortController();
64
+ return withTimeout(ms, label, () => op(controller.signal), () => controller.abort());
65
+ }
66
+ /**
67
+ * Send one request frame on an open stream, half-close the write end, and read
68
+ * the response through `readResponse`.
69
+ *
70
+ * `signal` is the caller's deadline (see {@link withDeadline}): an abort resets
71
+ * the stream, releasing the otherwise unbounded response read. If the deadline
72
+ * already fired while the stream was being dialed, the freshly-opened stream is
73
+ * reset here rather than left dangling. Every reset — deadline, response-decode
74
+ * failure, or a `readResponse` that rejects without aborting (the size-cap path
75
+ * of {@link readStreamToEnd}) — goes through one idempotent `abort`, so the
76
+ * deadline listener and the error path cannot double-reset a stream.
77
+ *
78
+ * `readResponse` owns its own read bound, which is a backstop rather than the
79
+ * primary one: the caller's deadline started first and therefore fires first.
80
+ * It matters only when an abort does not release the read (e.g. a stream double
81
+ * whose `abort()` is a no-op).
82
+ */
83
+ export async function exchangeFrame(stream, signal, request, readResponse, abortMessage) {
84
+ let reset = false;
85
+ const resetStream = (err) => {
86
+ if (reset)
87
+ return;
88
+ reset = true;
89
+ stream.abort(err);
90
+ };
91
+ const abortErr = new Error(abortMessage);
92
+ const onAbort = () => resetStream(abortErr);
93
+ if (signal.aborted) {
94
+ onAbort();
95
+ throw abortErr;
96
+ }
97
+ signal.addEventListener('abort', onAbort, { once: true });
98
+ try {
99
+ writeFrame(stream, request);
100
+ // close() half-closes the write end (EOF) while the read end stays open for
101
+ // the response — the libp2p 3.x request/response pattern.
102
+ await stream.close();
103
+ return await readResponse(stream);
104
+ }
105
+ catch (err) {
106
+ resetStream(err instanceof Error ? err : new Error(String(err)));
107
+ throw err;
108
+ }
109
+ finally {
110
+ signal.removeEventListener('abort', onAbort);
111
+ }
112
+ }
47
113
  /** Normalize a libp2p chunk (Uint8Array or Uint8ArrayList) to a flat Uint8Array. */
48
114
  function toBytes(chunk) {
49
115
  return chunk instanceof Uint8Array ? chunk : chunk.subarray();
@@ -1 +1 @@
1
- {"version":3,"file":"control-stream.js","sourceRoot":"","sources":["../src/control-stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"control-stream.js","sourceRoot":"","sources":["../src/control-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAYH,+EAA+E;AAC/E,MAAM,UAAU,UAAU,CAAC,MAAqB,EAAE,GAAY;IAC5D,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,EAAU,EACV,KAAa,EACb,EAAoB,EACpB,SAAsB;IAEtB,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC;gBACH,SAAS,EAAE,EAAE,CAAC;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,wEAAwE;YAC1E,CAAC;YACD,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,EAAE,IAAI,CAAC,CAAC,CAAC;QACxD,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,EAAU,EACV,KAAa,EACb,EAAuC;IAEvC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,OAAO,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;AACvF,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAqB,EACrB,MAAmB,EACnB,OAAgB,EAChB,YAAmD,EACnD,YAAoB;IAEpB,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,MAAM,WAAW,GAAG,CAAC,GAAU,EAAQ,EAAE;QACvC,IAAI,KAAK;YAAE,OAAO;QAClB,KAAK,GAAG,IAAI,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,GAAS,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;QACV,MAAM,QAAQ,CAAC;IACjB,CAAC;IACD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1D,IAAI,CAAC;QACH,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5B,4EAA4E;QAC5E,0DAA0D;QAC1D,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,WAAW,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,SAAS,OAAO,CAAC,KAA8C;IAC7D,OAAO,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;AAChE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAqB,EACrB,IAA4D;IAE5D,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,KAAgD,CAAC;IAErD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;QACtD,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,KAAK,yBAAyB,SAAS,IAAI,CAAC,CAAC;YACtE,uEAAuE;YACvE,kDAAkD;YAClD,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,wDAAwD;YAC1D,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAElD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,0FAA0F;AAC1F,KAAK,UAAU,OAAO,CAAC,MAAqB,EAAE,QAAgB,EAAE,KAAa;IAC3E,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;QACtB,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uBAAuB,KAAK,sBAAsB,QAAQ,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Bounded retry for TRANSIENT control-write failures — the classifier and loop behind
3
+ * `ControlDatabase.lockedWithRetry`, the single funnel every local control write passes
4
+ * through.
5
+ *
6
+ * Why this exists: the control database replicates every block to the whole party, so a
7
+ * three-machine party commits control writes unanimously (`ceil(3 × 0.75) = 3` approvals) —
8
+ * and no `superMajorityThreshold` can lower that bar without violating Optimystic's
9
+ * partition-safety condition at the shipped admission fraction (the arithmetic lives in
10
+ * `docs/architecture.md` → "Replication cluster size"). Cadre therefore accepts unanimity
11
+ * and absorbs the transient half of its cost here: a write that failed because the cohort
12
+ * did not ANSWER (a stream reset mid-connection-formation, zero approvals) is re-presented
13
+ * a moment later, which the degraded-cohort-member scenario measured as safe (a failed
14
+ * write rolls back, nothing half-commits) and effective (the next write commits in ~1 s).
15
+ *
16
+ * A write somebody actually REJECTED is never retried — re-presenting it would re-present
17
+ * a spent signature against a cohort that already said no.
18
+ *
19
+ * TWO policies ship from here, and the split is deliberate: the default
20
+ * ({@link isRetriableControlWriteFailure}, {@link CONTROL_WRITE_ATTEMPTS}) sits under every
21
+ * control write, and {@link SCHEMA_INIT_RETRY_POLICY} is opted into by
22
+ * `ControlDatabase.loadSchema` alone. The widened one absorbs one extra failure class whose
23
+ * re-run safety holds for schema DDL and NOT for writes in general; anything added to the default
24
+ * list must be safe for all ~19 callers.
25
+ *
26
+ * Lives outside `control-database.ts` so a spec can drive the classifier against real engine
27
+ * errors without importing the whole database class.
28
+ */
29
+ /**
30
+ * Attempts allowed per control write: the first, plus two retries of a transiently-failed
31
+ * cluster commit. Bounded so a genuinely unreachable cohort terminates in the transactor's
32
+ * own error rather than spinning; two retries covers the observed failure (a stream still
33
+ * forming when the write fired) with margin.
34
+ */
35
+ export declare const CONTROL_WRITE_ATTEMPTS = 3;
36
+ /**
37
+ * Attempts allowed for {@link SCHEMA_INIT_RETRY_POLICY}, the one call site that also absorbs a
38
+ * self-coordination refusal.
39
+ *
40
+ * Two more than {@link CONTROL_WRITE_ATTEMPTS} because what this retry buys is wall-clock for a
41
+ * cold-starting node's bootstrap dials to land, and because the refusal is cheap to re-provoke
42
+ * relative to a cluster round-trip: `findCoordinator` already spends its own bounded wait before
43
+ * raising (3 attempts, 500 ms apart, `db-p2p/src/libp2p-key-network.ts` ~422) and then decides
44
+ * LOCALLY that it may not elect itself, so no peer is contacted and no cohort deadline is
45
+ * consumed.
46
+ *
47
+ * Note that the ~1 s `findCoordinator` spends internally is real, on top of this policy's own
48
+ * backoff — which is exactly why {@link CONTROL_WRITE_RETRY_BUDGET_MS} and not this count is what
49
+ * actually terminates the loop in the worst case.
50
+ */
51
+ export declare const SCHEMA_INIT_ATTEMPTS = 5;
52
+ /**
53
+ * Elapsed-time ceiling on the whole retry loop, measured from the START of the first
54
+ * attempt and checked after a failed attempt BEFORE sleeping.
55
+ *
56
+ * This budget — not the attempt count — is what makes the policy safe to sit under every
57
+ * control write. A transient failure (stream reset while a connection is still forming)
58
+ * surfaces in well under a second, so it is retried and the loop adds at most ~2.2 s. A
59
+ * genuinely silent cohort member fails at ~20 s (two 10 s `ClusterClient` response-deadline
60
+ * attempts, measured in `control-write-degraded-cohort-member.integration.ts`), which
61
+ * already exceeds this budget when attempt 1 returns — so that case is surfaced immediately
62
+ * and retry adds ZERO latency to the case where it cannot help.
63
+ */
64
+ export declare const CONTROL_WRITE_RETRY_BUDGET_MS = 10000;
65
+ /**
66
+ * Did this control write fail because the cluster cohort did not ANSWER — i.e. is
67
+ * re-presenting the SAME signed write a moment later the right response?
68
+ *
69
+ * Classifies by MESSAGE, walking the `cause` chain with Quereus' own {@link unwrapError} —
70
+ * the failure surface is not recognisable by type. The transactor and the coordinator both throw bare `Error`s, and by
71
+ * the time one reaches `ControlDatabase` it is wrapped in a `QuereusError` (the real chain
72
+ * is `QuereusError` → `Error` → `Error`), so the match must work at any depth. Anything
73
+ * that is not an `Error` is never retried.
74
+ *
75
+ * {@link reportsIndeterminateCommit} vetoes the whole chain before any matcher runs, so a
76
+ * failure that reports a commit-phase batch anywhere is never retried on the strength of some
77
+ * other level looking transient.
78
+ *
79
+ * NOTE: this depends on engine/transactor error TEXT. The messages producible without a
80
+ * network are driven from the REAL engine in `control-formation-seat-budget.spec.ts`, so
81
+ * a rewording reddens a spec rather than silently disabling the retry. The two RETRIABLE
82
+ * messages need a real multi-node cluster to produce; the degraded-cohort scenario
83
+ * (`control-write-degraded-cohort-member.integration.ts`) asserts this classifier against
84
+ * both LIVE — the super-majority shortfall from a real silent member, and the transactor
85
+ * aggregate from a real stream reset, which the retry there absorbs end to end.
86
+ */
87
+ export declare function isRetriableControlWriteFailure(error: unknown): boolean;
88
+ /**
89
+ * {@link isRetriableControlWriteFailure} widened by exactly one class — the self-coordination
90
+ * grace refusal ({@link RETRIABLE_SCHEMA_INIT_MATCHERS}) — for `ControlDatabase.loadSchema`.
91
+ *
92
+ * Opt-in per call site, via {@link SCHEMA_INIT_RETRY_POLICY}. Every other control write keeps the
93
+ * default classifier untouched.
94
+ */
95
+ export declare function isRetriableSchemaInitFailure(error: unknown): boolean;
96
+ /**
97
+ * Policy and pacing for {@link retryControlWrite}. Production callers pass nothing (or a named
98
+ * policy such as {@link SCHEMA_INIT_RETRY_POLICY}); specs inject a recorded `sleep`, a shorter
99
+ * delay list, or a fake clock so no test ever waits out a real backoff.
100
+ *
101
+ * Every field defaults to the shipped control-write policy, so an omitted field is byte-identical
102
+ * to the behaviour before per-call-site policies existed.
103
+ */
104
+ export interface ControlWriteRetryOptions {
105
+ /**
106
+ * Total attempts, first included. Default {@link CONTROL_WRITE_ATTEMPTS}, floored at 1 —
107
+ * below that the loop would run the body zero times and rethrow a `lastError` nobody set.
108
+ */
109
+ attempts?: number;
110
+ /** Backoff base before retry N (last entry repeats). Default {@link CONTROL_WRITE_RETRY_DELAYS_MS}. */
111
+ delaysMs?: readonly number[];
112
+ /**
113
+ * Which failures are transient enough to re-present. Default
114
+ * {@link isRetriableControlWriteFailure}; the only other shipped value is
115
+ * {@link isRetriableSchemaInitFailure}, via {@link SCHEMA_INIT_RETRY_POLICY}.
116
+ */
117
+ isRetriable?: (error: unknown) => boolean;
118
+ /**
119
+ * Operation label stamped into every log line this loop emits (rendered as
120
+ * `Control write [<label>] …`), and NOTHING else — no behavioural effect. The debug
121
+ * log is the only surface where the loop's decisions are observable (the rethrown
122
+ * error is unchanged and no attempt counter is exposed), and several writes retry
123
+ * CONCURRENTLY in a real party, so an unlabelled line cannot be attributed to a
124
+ * write. The degraded-cohort scenario asserts on these lines per-operation.
125
+ */
126
+ label?: string;
127
+ /** The sleep primitive. Default: `setTimeout`. */
128
+ sleep?: (ms: number) => Promise<void>;
129
+ /** Clock for the elapsed-budget check. Default: `Date.now`. */
130
+ now?: () => number;
131
+ }
132
+ /**
133
+ * The one non-default retry policy: `ControlDatabase.loadSchema`'s distributed DDL, which
134
+ * additionally absorbs optimystic's self-coordination grace refusal and gets more attempts over a
135
+ * longer backoff to do it (see {@link SCHEMA_INIT_ATTEMPTS} and
136
+ * {@link SCHEMA_INIT_RETRY_DELAYS_MS}). Still bounded by the shared
137
+ * {@link CONTROL_WRITE_RETRY_BUDGET_MS}.
138
+ *
139
+ * NOTE: exactly one call site opts in today, and the re-run safety this policy assumes is that
140
+ * site's, not a general property — `apply schema` is a diff and a failed `create table` leaves the
141
+ * catalog clean. A second opt-in must re-derive that argument for its own write body first; if this
142
+ * policy ever grows a third consumer, rename it for what the callers share rather than widening it
143
+ * by default.
144
+ */
145
+ export declare const SCHEMA_INIT_RETRY_POLICY: Readonly<ControlWriteRetryOptions>;
146
+ /**
147
+ * Run `attempt` up to {@link CONTROL_WRITE_ATTEMPTS} times (or `options.attempts`), retrying only
148
+ * failures the policy's classifier — {@link isRetriableControlWriteFailure} by default —
149
+ * calls transient, and only while {@link CONTROL_WRITE_RETRY_BUDGET_MS} has not elapsed.
150
+ *
151
+ * `attempt` must be atomic and re-runnable: a failed cluster write rolls back (nothing is
152
+ * half-applied), and re-running the body re-runs its reads too — `ControlDatabase`'s
153
+ * locked write bodies all satisfy this (see the contract on
154
+ * `ControlDatabase.withWriteLock`). The backoff sleeps happen with NO lock held; the caller
155
+ * takes and releases its lock inside `attempt`.
156
+ *
157
+ * On exhaustion (attempts or budget) the LAST error is rethrown unchanged — never wrapped,
158
+ * so the exact messages downstream code and the degraded-cohort-member scenario assert on
159
+ * survive. A non-retriable failure propagates from the attempt that raised it.
160
+ */
161
+ export declare function retryControlWrite<T>(attempt: () => Promise<T>, options?: ControlWriteRetryOptions): Promise<T>;