@serfab/cadre-core 0.7.1 → 0.9.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 (170) hide show
  1. package/README.md +11 -10
  2. package/dist/cadre-node.d.ts +818 -19
  3. package/dist/cadre-node.js +1952 -99
  4. package/dist/cadre-node.js.map +1 -1
  5. package/dist/canonical-datetime.d.ts +33 -0
  6. package/dist/canonical-datetime.js +38 -0
  7. package/dist/canonical-datetime.js.map +1 -0
  8. package/dist/canonical-json.d.ts +12 -0
  9. package/dist/canonical-json.js +33 -0
  10. package/dist/canonical-json.js.map +1 -0
  11. package/dist/control-cohort.d.ts +63 -0
  12. package/dist/control-cohort.js +80 -0
  13. package/dist/control-cohort.js.map +1 -0
  14. package/dist/control-database.d.ts +279 -8
  15. package/dist/control-database.js +563 -134
  16. package/dist/control-database.js.map +1 -1
  17. package/dist/control-formation-recorder.d.ts +89 -0
  18. package/dist/control-formation-recorder.js +139 -0
  19. package/dist/control-formation-recorder.js.map +1 -0
  20. package/dist/control-schema.d.ts +12 -0
  21. package/dist/control-schema.js +223 -0
  22. package/dist/control-schema.js.map +1 -0
  23. package/dist/control-stream.d.ts +54 -0
  24. package/dist/control-stream.js +104 -0
  25. package/dist/control-stream.js.map +1 -0
  26. package/dist/device-token.d.ts +50 -0
  27. package/dist/device-token.js +66 -0
  28. package/dist/device-token.js.map +1 -0
  29. package/dist/diagnostics/connection-path.d.ts +118 -0
  30. package/dist/diagnostics/connection-path.js +204 -0
  31. package/dist/diagnostics/connection-path.js.map +1 -0
  32. package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
  33. package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
  34. package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
  35. package/dist/ed25519-key.d.ts +42 -0
  36. package/dist/ed25519-key.js +51 -0
  37. package/dist/ed25519-key.js.map +1 -0
  38. package/dist/hibernation-manager.d.ts +79 -0
  39. package/dist/hibernation-manager.js +173 -23
  40. package/dist/hibernation-manager.js.map +1 -1
  41. package/dist/index.d.ts +25 -4
  42. package/dist/index.js +59 -4
  43. package/dist/index.js.map +1 -1
  44. package/dist/key-store-file.d.ts +52 -0
  45. package/dist/key-store-file.js +194 -0
  46. package/dist/key-store-file.js.map +1 -0
  47. package/dist/key-store.d.ts +75 -0
  48. package/dist/key-store.js +66 -0
  49. package/dist/key-store.js.map +1 -0
  50. package/dist/peer-authorization.d.ts +48 -0
  51. package/dist/peer-authorization.js +66 -0
  52. package/dist/peer-authorization.js.map +1 -0
  53. package/dist/peer-record.d.ts +89 -0
  54. package/dist/peer-record.js +117 -0
  55. package/dist/peer-record.js.map +1 -0
  56. package/dist/push-credentials.d.ts +34 -0
  57. package/dist/push-credentials.js +81 -0
  58. package/dist/push-credentials.js.map +1 -0
  59. package/dist/push-fanout.d.ts +137 -0
  60. package/dist/push-fanout.js +197 -0
  61. package/dist/push-fanout.js.map +1 -0
  62. package/dist/push-node.d.ts +38 -0
  63. package/dist/push-node.js +48 -0
  64. package/dist/push-node.js.map +1 -0
  65. package/dist/push-notifier-apns.d.ts +61 -0
  66. package/dist/push-notifier-apns.js +199 -0
  67. package/dist/push-notifier-apns.js.map +1 -0
  68. package/dist/push-notifier-fcm.d.ts +44 -0
  69. package/dist/push-notifier-fcm.js +182 -0
  70. package/dist/push-notifier-fcm.js.map +1 -0
  71. package/dist/push-notifier-shared.d.ts +18 -0
  72. package/dist/push-notifier-shared.js +25 -0
  73. package/dist/push-notifier-shared.js.map +1 -0
  74. package/dist/push-notifier.d.ts +52 -0
  75. package/dist/push-notifier.js +22 -0
  76. package/dist/push-notifier.js.map +1 -0
  77. package/dist/schema-verification.d.ts +11 -2
  78. package/dist/schema-verification.js +15 -4
  79. package/dist/schema-verification.js.map +1 -1
  80. package/dist/seed-bootstrap.d.ts +228 -21
  81. package/dist/seed-bootstrap.js +489 -168
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +63 -0
  84. package/dist/seed-trust-policy.js +56 -0
  85. package/dist/seed-trust-policy.js.map +1 -0
  86. package/dist/strand-addr-protocol.d.ts +132 -0
  87. package/dist/strand-addr-protocol.js +313 -0
  88. package/dist/strand-addr-protocol.js.map +1 -0
  89. package/dist/strand-cohort.d.ts +54 -0
  90. package/dist/strand-cohort.js +38 -0
  91. package/dist/strand-cohort.js.map +1 -0
  92. package/dist/strand-database.d.ts +54 -6
  93. package/dist/strand-database.js +88 -100
  94. package/dist/strand-database.js.map +1 -1
  95. package/dist/strand-formation-manager.d.ts +79 -19
  96. package/dist/strand-formation-manager.js +196 -104
  97. package/dist/strand-formation-manager.js.map +1 -1
  98. package/dist/strand-formation-protocol.d.ts +162 -0
  99. package/dist/strand-formation-protocol.js +279 -0
  100. package/dist/strand-formation-protocol.js.map +1 -0
  101. package/dist/strand-instance-manager.d.ts +73 -4
  102. package/dist/strand-instance-manager.js +167 -41
  103. package/dist/strand-instance-manager.js.map +1 -1
  104. package/dist/strand-member-key.d.ts +29 -0
  105. package/dist/strand-member-key.js +38 -0
  106. package/dist/strand-member-key.js.map +1 -0
  107. package/dist/strand-member-registry.d.ts +98 -0
  108. package/dist/strand-member-registry.js +129 -0
  109. package/dist/strand-member-registry.js.map +1 -0
  110. package/dist/strand-membership-writer.d.ts +361 -0
  111. package/dist/strand-membership-writer.js +449 -0
  112. package/dist/strand-membership-writer.js.map +1 -0
  113. package/dist/strand-solicitation.d.ts +77 -2
  114. package/dist/strand-solicitation.js +25 -4
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-wake-payload.d.ts +30 -0
  117. package/dist/strand-wake-payload.js +20 -0
  118. package/dist/strand-wake-payload.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +114 -0
  120. package/dist/strand-wake-protocol.js +257 -0
  121. package/dist/strand-wake-protocol.js.map +1 -0
  122. package/dist/strand-watcher.d.ts +6 -2
  123. package/dist/strand-watcher.js +57 -17
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/types.d.ts +436 -33
  126. package/dist/types.js +22 -5
  127. package/dist/types.js.map +1 -1
  128. package/package.json +18 -8
  129. package/src/cadre-node.ts +2981 -948
  130. package/src/canonical-datetime.ts +39 -0
  131. package/src/canonical-json.ts +30 -0
  132. package/src/control-cohort.ts +103 -0
  133. package/src/control-database.ts +948 -383
  134. package/src/control-formation-recorder.ts +151 -0
  135. package/src/control-schema.ts +222 -0
  136. package/src/control-stream.ts +128 -0
  137. package/src/device-token.ts +89 -0
  138. package/src/diagnostics/connection-path.ts +277 -0
  139. package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
  140. package/src/ed25519-key.ts +69 -0
  141. package/src/hibernation-manager.ts +197 -28
  142. package/src/index.ts +275 -79
  143. package/src/key-store-file.ts +203 -0
  144. package/src/key-store.ts +99 -0
  145. package/src/peer-authorization.ts +81 -0
  146. package/src/peer-record.ts +146 -0
  147. package/src/push-credentials.ts +84 -0
  148. package/src/push-fanout.ts +272 -0
  149. package/src/push-node.ts +64 -0
  150. package/src/push-notifier-apns.ts +247 -0
  151. package/src/push-notifier-fcm.ts +220 -0
  152. package/src/push-notifier-shared.ts +28 -0
  153. package/src/push-notifier.ts +52 -0
  154. package/src/schema-verification.ts +18 -4
  155. package/src/seed-bootstrap.ts +563 -223
  156. package/src/seed-trust-policy.ts +107 -0
  157. package/src/strand-addr-protocol.ts +410 -0
  158. package/src/strand-cohort.ts +77 -0
  159. package/src/strand-database.ts +113 -141
  160. package/src/strand-formation-manager.ts +233 -142
  161. package/src/strand-formation-protocol.ts +391 -0
  162. package/src/strand-instance-manager.ts +207 -56
  163. package/src/strand-member-key.ts +39 -0
  164. package/src/strand-member-registry.ts +177 -0
  165. package/src/strand-membership-writer.ts +640 -0
  166. package/src/strand-solicitation.ts +93 -4
  167. package/src/strand-wake-payload.ts +32 -0
  168. package/src/strand-wake-protocol.ts +331 -0
  169. package/src/strand-watcher.ts +61 -17
  170. package/src/types.ts +1032 -582
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Native cadre-core strand-formation transport.
3
+ *
4
+ * Mirrors the non-deprecated `seed-bootstrap.ts` protocol service: a dedicated
5
+ * libp2p protocol id, length-prefixed JSON frames, and small single-purpose
6
+ * session helpers. It replaces the deprecated `@serfab/strand-proto` transport,
7
+ * carrying the caller's REAL invitation token + disclosure and BOTH parties'
8
+ * real cadre peer addresses end-to-end (no `{ partyId: sessionId }` / `cadre-*.local`
9
+ * placeholders), and validating the responder's result on the initiator side.
10
+ *
11
+ * Provisioning flow (`responderCreates`, 2 messages): the responder provisions (or, for
12
+ * provision-then-record, resolves + records consent against) the strand and returns the
13
+ * result on approval.
14
+ *
15
+ * Cadre-disclosure timing (see docs/strand-proto.md "Security & Privacy"): the
16
+ * responder reveals its own party id + cadre addresses — and, for a closed strand
17
+ * returned via provision-then-record, that strand's membership key — ONLY after the
18
+ * token and disclosure validate; a rejection discloses none of them.
19
+ */
20
+ import debug from 'debug';
21
+ import { multiaddr } from '@multiformats/multiaddr';
22
+ import { writeFrame, withTimeout } from './control-stream.js';
23
+ const log = debug('sereus:cadre:formation-proto');
24
+ /** Protocol id for the native formation transport (parallel to `/sereus/seed/1.0.0`). */
25
+ export const FORMATION_PROTOCOL = '/sereus/formation/1.0.0';
26
+ /** Maximum formation message size (1MB). */
27
+ const MAX_FORMATION_MSG_SIZE = 1024 * 1024;
28
+ /** Default whole-session timeout (ms). */
29
+ const DEFAULT_SESSION_TIMEOUT_MS = 30000;
30
+ /** Default per-step (single read/write) timeout (ms). */
31
+ const DEFAULT_STEP_TIMEOUT_MS = 5000;
32
+ /** Default cap on concurrent inbound formation sessions. */
33
+ const DEFAULT_MAX_CONCURRENT_SESSIONS = 100;
34
+ // ── Stream framing helpers ───────────────────────────────────────────────────
35
+ /**
36
+ * Reads length-prefixed JSON frames from a libp2p stream one frame at a time.
37
+ *
38
+ * Unlike seed delivery (which reads a stream to EOF then parses once), formation
39
+ * is request/response on a single live stream, so reading must stop at each frame
40
+ * boundary. The reader buffers across chunk boundaries and retains any overflow
41
+ * (a following frame coalesced into the same chunk) for the next read.
42
+ */
43
+ class FrameReader {
44
+ constructor(stream, maxLength = MAX_FORMATION_MSG_SIZE) {
45
+ this.buffer = new Uint8Array(0);
46
+ this.iterator = stream[Symbol.asyncIterator]();
47
+ this.maxLength = maxLength;
48
+ }
49
+ append(chunk) {
50
+ const next = new Uint8Array(this.buffer.length + chunk.length);
51
+ next.set(this.buffer, 0);
52
+ next.set(chunk, this.buffer.length);
53
+ this.buffer = next;
54
+ }
55
+ /** Pull chunks until the buffer holds ≥ `need` bytes; false if the stream ended first. */
56
+ async fill(need) {
57
+ while (this.buffer.length < need) {
58
+ const { value, done } = await this.iterator.next();
59
+ if (done)
60
+ return false;
61
+ const bytes = value instanceof Uint8Array
62
+ ? value
63
+ : value.subarray();
64
+ this.append(bytes);
65
+ }
66
+ return true;
67
+ }
68
+ async read() {
69
+ if (!(await this.fill(4))) {
70
+ throw new Error('Formation stream closed before length prefix');
71
+ }
72
+ const length = new DataView(this.buffer.buffer, this.buffer.byteOffset, this.buffer.byteLength).getUint32(0, false);
73
+ if (length > this.maxLength) {
74
+ throw new Error(`Formation frame declares length ${length} exceeding max ${this.maxLength}`);
75
+ }
76
+ if (!(await this.fill(4 + length))) {
77
+ throw new Error(`Formation frame declares length ${length} but stream ended early`);
78
+ }
79
+ const json = new TextDecoder().decode(this.buffer.subarray(4, 4 + length));
80
+ this.buffer = this.buffer.subarray(4 + length);
81
+ return JSON.parse(json);
82
+ }
83
+ }
84
+ // ── Result validation ────────────────────────────────────────────────────────
85
+ /** Matches the deprecated strand-proto placeholder cadre addrs (`cadre-a-1.local`, …). */
86
+ const PLACEHOLDER_CADRE_ADDR = /^cadre-[ab]-\d+\.local$/;
87
+ /**
88
+ * Structural check on a `responderCreates` result: the responder must have approved,
89
+ * disclosed a non-empty real identity + cadre (not the deprecated `cadre-*.local`
90
+ * placeholders), and returned a strand vouched for by the responder party with a real id.
91
+ *
92
+ * `createdBy: 'responder'` means "the responder party vouches for / returns this strand."
93
+ * Under provision-then-record that strand was minted owner-signed earlier (not created
94
+ * in-session), but it is still the responder party returning its own strand, so the marker
95
+ * — and this structural validator — stay unchanged.
96
+ *
97
+ * The behavioral floor for the initiator: a responder that returns an arbitrary/empty
98
+ * `strandId` or omits its disclosed identity is rejected, not silently accepted.
99
+ */
100
+ export function isValidResponderCreatesResult(response) {
101
+ if (!response.approved)
102
+ return false;
103
+ if (!response.partyId)
104
+ return false;
105
+ const addrs = response.cadrePeerAddrs;
106
+ if (!addrs || addrs.length === 0)
107
+ return false;
108
+ if (addrs.some((a) => PLACEHOLDER_CADRE_ADDR.test(a)))
109
+ return false;
110
+ const provision = response.provisionResult;
111
+ if (!provision?.strand?.strandId)
112
+ return false;
113
+ if (provision.strand.createdBy !== 'responder')
114
+ return false;
115
+ return true;
116
+ }
117
+ /**
118
+ * Responder side of the native formation protocol. Registers a libp2p handler and
119
+ * drives each inbound stream through token + disclosure validation, provisioning,
120
+ * and result delivery — enforcing the cadre-disclosure timing rule (responder cadre
121
+ * is revealed only after validation; rejections disclose nothing).
122
+ */
123
+ export class FormationListener {
124
+ constructor(options) {
125
+ this.registered = new Set();
126
+ this.activeSessions = 0;
127
+ this.sessionCounter = 0;
128
+ this.options = options;
129
+ this.sessionTimeoutMs = options.sessionTimeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;
130
+ this.stepTimeoutMs = options.stepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
131
+ this.maxConcurrentSessions = options.maxConcurrentSessions ?? DEFAULT_MAX_CONCURRENT_SESSIONS;
132
+ }
133
+ /** Number of in-flight inbound sessions. */
134
+ get activeCount() {
135
+ return this.activeSessions;
136
+ }
137
+ register(node, protocolId = FORMATION_PROTOCOL) {
138
+ if (this.registered.has(node)) {
139
+ log('node already registered');
140
+ return;
141
+ }
142
+ void node.handle(protocolId, async (rawStream, _connection) => {
143
+ await this.handleStream(rawStream);
144
+ });
145
+ this.registered.add(node);
146
+ log('formation listener registered (%s)', protocolId);
147
+ }
148
+ unregister(node, protocolId = FORMATION_PROTOCOL) {
149
+ if (!this.registered.has(node))
150
+ return;
151
+ void node.unhandle(protocolId);
152
+ this.registered.delete(node);
153
+ log('formation listener unregistered (%s)', protocolId);
154
+ }
155
+ async handleStream(stream) {
156
+ if (this.activeSessions >= this.maxConcurrentSessions) {
157
+ const rejection = { approved: false, reason: 'Too many concurrent formation sessions' };
158
+ try {
159
+ writeFrame(stream, rejection);
160
+ }
161
+ catch { /* best effort */ }
162
+ try {
163
+ await stream.close();
164
+ }
165
+ catch { /* ignore */ }
166
+ return;
167
+ }
168
+ const id = ++this.sessionCounter;
169
+ this.activeSessions++;
170
+ try {
171
+ await withTimeout(this.sessionTimeoutMs, `Formation session#${id}`, () => this.runSession(id, stream));
172
+ }
173
+ catch (err) {
174
+ log('formation session #%d failed: %o', id, err);
175
+ }
176
+ finally {
177
+ this.activeSessions--;
178
+ try {
179
+ await stream.close();
180
+ }
181
+ catch { /* ignore */ }
182
+ }
183
+ }
184
+ async runSession(id, stream) {
185
+ // Track whether ANY frame has been written so the catch below can convert an
186
+ // unexpected internal error into a non-disclosing rejection ONLY when nothing has
187
+ // gone out yet. This closes the "stream closed with no result frame" class of bug.
188
+ let wroteFrame = false;
189
+ const send = (msg) => {
190
+ writeFrame(stream, msg);
191
+ wroteFrame = true;
192
+ };
193
+ try {
194
+ const reader = new FrameReader(stream);
195
+ const contact = await withTimeout(this.stepTimeoutMs, `Formation await-contact#${id}`, () => reader.read());
196
+ log('formation session #%d contact: token=%s party=%s', id, contact.token, contact.partyId);
197
+ const tokenResult = await this.options.validateToken(contact.token);
198
+ if (!tokenResult.valid) {
199
+ send({ approved: false, reason: 'Invalid token' });
200
+ return;
201
+ }
202
+ const disclosureOk = await this.options.validateDisclosure(contact.token, contact.disclosure);
203
+ if (!disclosureOk) {
204
+ send({ approved: false, reason: 'Invalid disclosure' });
205
+ return;
206
+ }
207
+ const outcome = await this.options.provisionStrand(contact.token, contact.partyId, contact.disclosure);
208
+ if (!outcome.approved) {
209
+ // A post-validation rejection still discloses NEITHER identity NOR cadre,
210
+ // exactly like the token/disclosure rejections above.
211
+ send({ approved: false, reason: outcome.reason });
212
+ return;
213
+ }
214
+ // Validation + provisioning passed → safe to disclose responder identity/cadre.
215
+ const identity = this.options.getResponderIdentity();
216
+ send({
217
+ approved: true,
218
+ partyId: identity.partyId,
219
+ cadrePeerAddrs: identity.cadrePeerAddrs,
220
+ provisionResult: outcome.result
221
+ });
222
+ }
223
+ catch (err) {
224
+ // Defense-in-depth: if an unexpected internal error escapes BEFORE any frame was
225
+ // written (e.g. a future provisionStrand-hook bug), convert it into a non-disclosing
226
+ // protocol rejection so the initiator sees a clean error instead of a read-error/
227
+ // timeout. If a frame was already sent, leave the stream as-is and let handleStream
228
+ // log + close. Re-throw either way so the failure is still recorded — this is a
229
+ // deliberate, logged conversion, not silent.
230
+ if (!wroteFrame) {
231
+ const internalError = { approved: false, reason: 'Internal formation error' };
232
+ try {
233
+ writeFrame(stream, internalError);
234
+ }
235
+ catch { /* stream already broken */ }
236
+ }
237
+ throw err;
238
+ }
239
+ }
240
+ }
241
+ /**
242
+ * Initiator side of the native formation protocol. Dials the responder, sends the
243
+ * contact (carrying the real disclosure/token/cadre), validates the responder's
244
+ * result, and returns the strand the responder provisioned.
245
+ */
246
+ export async function dialFormation(node, options) {
247
+ if (options.responderAddrs.length === 0) {
248
+ throw new Error('No responder addresses available for formation');
249
+ }
250
+ const protocolId = options.protocolId ?? FORMATION_PROTOCOL;
251
+ const sessionTimeoutMs = options.sessionTimeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS;
252
+ const stepTimeoutMs = options.stepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
253
+ const addr = multiaddr(options.responderAddrs[0]);
254
+ return withTimeout(sessionTimeoutMs, 'Formation dial', async () => {
255
+ const rawStream = await withTimeout(stepTimeoutMs, 'Formation dial-connect', async () => node.dialProtocol(addr, protocolId));
256
+ const stream = rawStream;
257
+ try {
258
+ const reader = new FrameReader(stream);
259
+ writeFrame(stream, options.contact);
260
+ const response = await withTimeout(stepTimeoutMs, 'Formation await-response', () => reader.read());
261
+ if (!response.approved) {
262
+ throw new Error(`Formation rejected: ${response.reason ?? 'no reason provided'}`);
263
+ }
264
+ const ok = await options.validateResponse(response);
265
+ if (!ok)
266
+ throw new Error('Responder result failed validation');
267
+ if (!response.provisionResult)
268
+ throw new Error('Missing provision result for responderCreates mode');
269
+ return response.provisionResult;
270
+ }
271
+ finally {
272
+ try {
273
+ await stream.close();
274
+ }
275
+ catch { /* ignore */ }
276
+ }
277
+ });
278
+ }
279
+ //# sourceMappingURL=strand-formation-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strand-formation-protocol.js","sourceRoot":"","sources":["../src/strand-formation-protocol.ts"],"names":[],"mappings":"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"}
@@ -12,18 +12,46 @@ export interface StartStrandConfig {
12
12
  profile: NodeProfile;
13
13
  defaultLatencyHint: LatencyHint;
14
14
  privateKey?: PrivateKey;
15
+ /** Cohort-derived discovery seed (multiaddr strings). Defaults to [] when omitted. */
16
+ bootstrapNodes?: string[];
15
17
  /**
16
18
  * Lifecycle mode for this strand; selects the default transactor used by the
17
- * StrandDatabase. Defaults to `'networked'` when omitted.
19
+ * StrandDatabase. When omitted, the StrandDatabase falls back to `'networked'`;
20
+ * callers (CadreNode) infer it from cohort membership before reaching here.
18
21
  */
19
22
  mode?: StrandMode;
23
+ /**
24
+ * Require a valid author signature on the sApp schema before bring-up.
25
+ * Defaults to true (fail closed) when omitted; set false only for dev/test
26
+ * with unsigned demo schemas. Mirrors {@link CadreNodeConfig.requireSignedSchemas}.
27
+ */
28
+ requireSignedSchemas?: boolean;
29
+ /**
30
+ * Whether this node founds the strand (vs. joins it). Forwarded to the
31
+ * StrandDatabase so the founder bootstrap (Header, founding Member/Manager)
32
+ * runs once at bring-up. Joiners leave this unset and write nothing. See
33
+ * {@link StrandConfig.founder}.
34
+ */
35
+ founder?: boolean;
36
+ }
37
+ /**
38
+ * Volatile inputs re-resolved when resuming a quiesced strand. These may have
39
+ * changed since the strand first launched — the cohort discovery seed grows as
40
+ * peers are learned, and cohort membership can push a strand `bootstrap → networked`.
41
+ */
42
+ export interface ResumeStrandOverrides {
43
+ /** Freshly-resolved cohort discovery seed (multiaddr strings). */
44
+ bootstrapNodes?: string[];
45
+ /** Freshly-resolved lifecycle mode. */
46
+ mode?: StrandMode;
20
47
  }
21
48
  /**
22
49
  * Get the isolated storage path for a specific strand.
23
50
  *
24
- * @deprecated This function uses Node.js path module which is not available in React Native.
25
- * Use a storage provider factory function instead, which receives the strandId and can
26
- * create strand-specific storage paths using platform-appropriate methods.
51
+ * @deprecated This helper is Node-only and throws in React Native (it assumes a
52
+ * filesystem layout). Use a storage provider factory function instead, which
53
+ * receives the strandId and can create strand-specific storage paths using
54
+ * platform-appropriate methods.
27
55
  *
28
56
  * @example
29
57
  * // Instead of using getStrandStoragePath, use a storage provider factory:
@@ -38,6 +66,12 @@ export declare function getStrandStoragePath(basePath: string, strandId: string)
38
66
  */
39
67
  export declare class StrandInstanceManager {
40
68
  private instances;
69
+ /**
70
+ * Retained launch config per strand, captured in `startStrand` and cleared in
71
+ * `stopStrand`. `resumeStrand` reuses it to rebuild a quiesced strand's runtime
72
+ * without the caller re-threading storage/network/profile/key/sApp config.
73
+ */
74
+ private launchConfigs;
41
75
  private stopping;
42
76
  constructor();
43
77
  /**
@@ -56,6 +90,41 @@ export declare class StrandInstanceManager {
56
90
  * Start a new strand instance
57
91
  */
58
92
  startStrand(config: StartStrandConfig): Promise<StrandInstance>;
93
+ /**
94
+ * Build (or rebuild) the libp2p node + StrandDatabase for an instance and
95
+ * attach them, transitioning it to `active`. Shared by `startStrand` (fresh
96
+ * launch) and `resumeStrand` (rehydrating a quiesced instance). Reads all
97
+ * volatile inputs (bootstrapNodes, mode, storage, network, profile,
98
+ * privateKey, sApp config) from `config`, so the caller controls the
99
+ * cohort-derived values.
100
+ */
101
+ private buildStrandRuntime;
102
+ /**
103
+ * Release an instance's strand-network runtime: close the StrandDatabase, then
104
+ * stop the libp2p node (construction order in reverse), clearing both fields
105
+ * and zeroing connectedPeers. Tolerant of partially-built state — either handle
106
+ * may be absent — so it doubles as rollback for a failed `buildStrandRuntime`.
107
+ * Shared by `quiesceStrand`, `stopStrand`, and that rollback path.
108
+ */
109
+ private releaseRuntime;
110
+ /**
111
+ * Quiesce a strand: release its strand-network resources (stop the libp2p node,
112
+ * close the StrandDatabase) while RETAINING the instance record — identity,
113
+ * sAppInfo, keys, latency hint, metadata — and its launch config so it can be
114
+ * resumed later. Mechanically this is `stopStrand` minus the instance/config
115
+ * deletion. The caller sets the post-quiesce status (e.g. `hibernating`).
116
+ * No-ops when the strand is missing or already quiesced.
117
+ */
118
+ quiesceStrand(strandId: string): Promise<void>;
119
+ /**
120
+ * Resume a previously-quiesced strand: rebuild its libp2p node + StrandDatabase
121
+ * from the retained launch config and re-attach them, transitioning it back to
122
+ * `active`. `overrides` re-applies volatile inputs that may have changed since
123
+ * launch (cohort `bootstrapNodes`, lifecycle `mode`) and updates the retained
124
+ * config so a later resume reuses the latest values. Returns the live instance
125
+ * unchanged if it is already running.
126
+ */
127
+ resumeStrand(strandId: string, overrides?: ResumeStrandOverrides): Promise<StrandInstance>;
59
128
  /**
60
129
  * Stop a strand instance
61
130
  */