@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,313 @@
1
+ /**
2
+ * Control-network strand-address RPC.
3
+ *
4
+ * A strand runs as its own libp2p node (`strand-<id>`, random port), separate
5
+ * from the control node (`control-<partyId>`) even though both share the same
6
+ * peerId. To seed a strand's mesh, a node needs a sibling's **strand-network**
7
+ * address — but `CadrePeer.Multiaddr` only stores the **control** node's
8
+ * addresses. This protocol resolves the strand address on demand: a node asks
9
+ * its connected co-cadre siblings "what are your live strand-`X` multiaddrs?" and
10
+ * uses the union of their answers as the seed.
11
+ *
12
+ * Modeled directly on `strand-wake-protocol.ts`: a dedicated libp2p protocol id,
13
+ * 4-byte big-endian length-prefixed JSON frames, `node.handle` for the receiver,
14
+ * `node.dialProtocol` for the client, and the shared `ControlStream` primitives
15
+ * from `control-stream.ts`. The exchange is a single request → single response on
16
+ * one stream, so each side reads to EOF (under a read timeout) and decodes one
17
+ * frame via the shared {@link decodeLengthPrefixedFrame} guard.
18
+ *
19
+ * **Authorization (v1):** the control network is single-party — only this party's
20
+ * cadre peers connect (schema-gated `CadrePeer`). Like wake, the receiver gates
21
+ * on cadre membership (via the injected `isMember`) and requires no further
22
+ * signature; a non-member is refused with an empty address list. This is
23
+ * defense-in-depth (the control mesh already restricts membership) kept for
24
+ * parity with wake. Cross-party strand bootstrap is a different mechanism
25
+ * (strand formation / `MemberPeer`) and is out of scope here.
26
+ */
27
+ import debug from 'debug';
28
+ import { peerIdFromString } from '@libp2p/peer-id';
29
+ import { decodeLengthPrefixedFrame } from './seed-bootstrap.js';
30
+ import { orderSignalingFirst } from './peer-record.js';
31
+ import { writeFrame, withTimeout, readStreamToEnd } from './control-stream.js';
32
+ const log = debug('sereus:cadre:strand-addr');
33
+ /** Protocol id for the control-network strand-address RPC (parallel to `/sereus/strand-wake/1.0.0`). */
34
+ export const STRAND_ADDR_PROTOCOL = '/sereus/strand-addr/1.0.0';
35
+ /**
36
+ * Maximum strand-addr frame size. Responses are tiny (a strand id + a few
37
+ * multiaddrs), so this is a defensive cap — far below the 1MB seed ceiling —
38
+ * that bounds the bytes a peer can make the receiver buffer per stream.
39
+ */
40
+ const MAX_ADDR_SIZE = 64 * 1024;
41
+ /** Default time to wait for the response before abandoning a strand-addr dial (ms). */
42
+ const DEFAULT_ADDR_TIMEOUT_MS = 10000;
43
+ /** Default time the receiver waits for an inbound request frame before aborting (ms). */
44
+ const DEFAULT_ADDR_READ_TIMEOUT_MS = 10000;
45
+ /** Default cap on concurrent inbound strand-addr streams a single peer can pin open. */
46
+ const DEFAULT_MAX_CONCURRENT_ADDRS = 100;
47
+ /**
48
+ * Read a libp2p stream to EOF and decode the single length-prefixed JSON frame
49
+ * it carries. Bounded by `timeoutMs` (a never-half-closing peer is aborted, not
50
+ * awaited forever) and capped at {@link MAX_ADDR_SIZE}, reusing the shared
51
+ * {@link decodeLengthPrefixedFrame} guard for the prefix/length checks.
52
+ */
53
+ async function readFrame(stream, timeoutMs) {
54
+ const data = await readStreamToEnd(stream, { maxBytes: MAX_ADDR_SIZE, timeoutMs, label: 'Strand-addr' });
55
+ const body = decodeLengthPrefixedFrame(data, MAX_ADDR_SIZE);
56
+ return JSON.parse(new TextDecoder().decode(body));
57
+ }
58
+ /** Empty response used on every reject/error path (cap, malformed frame, non-member). */
59
+ function emptyResponse(strandId) {
60
+ return { strandId, multiaddrs: [] };
61
+ }
62
+ /**
63
+ * Receiver side of the strand-address RPC. Registers a `STRAND_ADDR_PROTOCOL`
64
+ * handler on the control node and, for each inbound {@link StrandAddrRequest},
65
+ * gates on cadre membership, then replies with the local strand instance's live
66
+ * multiaddrs (or an empty list when not a member / not running).
67
+ */
68
+ export class StrandAddrService {
69
+ constructor(options) {
70
+ this.node = null;
71
+ /** In-flight inbound strand-addr streams, used to enforce {@link maxConcurrent}. */
72
+ this.activeStreams = 0;
73
+ this.options = options;
74
+ this.readTimeoutMs = options.readTimeoutMs ?? DEFAULT_ADDR_READ_TIMEOUT_MS;
75
+ this.maxConcurrent = options.maxConcurrent ?? DEFAULT_MAX_CONCURRENT_ADDRS;
76
+ }
77
+ /** Number of in-flight inbound strand-addr streams. */
78
+ get activeCount() {
79
+ return this.activeStreams;
80
+ }
81
+ /** Register the strand-addr protocol handler on the control node. */
82
+ initialize(node) {
83
+ this.node = node;
84
+ // `runOnLimitedConnection: true` is REQUIRED for the relay path: a NAT'd
85
+ // sibling is reached over a circuit-relay connection, which libp2p marks
86
+ // "limited" (the relay caps its data/duration). Without this the receiver
87
+ // would refuse the inbound stream on exactly the connection the protocol is
88
+ // designed to use (same reasoning as wake; see the relay note on
89
+ // `collectStrandAddrs`).
90
+ void node.handle(STRAND_ADDR_PROTOCOL, async (rawStream, rawConnection) => {
91
+ const remotePeerId = rawConnection.remotePeer.toString();
92
+ await this.handleStream(rawStream, remotePeerId);
93
+ }, { runOnLimitedConnection: true });
94
+ log('StrandAddrService registered handler: %s', STRAND_ADDR_PROTOCOL);
95
+ }
96
+ /** Unregister the handler and release the node reference. */
97
+ async shutdown() {
98
+ if (this.node) {
99
+ await this.node.unhandle(STRAND_ADDR_PROTOCOL);
100
+ this.node = null;
101
+ log('StrandAddrService shutdown');
102
+ }
103
+ }
104
+ /**
105
+ * Read the inbound request, decide the response, and write it back.
106
+ *
107
+ * Three hardening layers, all reported as an empty {@link StrandAddrResponse}
108
+ * rather than a dropped/hung stream: a concurrency cap (over
109
+ * {@link maxConcurrent}, reply without looking up any address), a read timeout
110
+ * (a peer that never half-closes is aborted inside {@link readFrame}/
111
+ * `readStreamToEnd`), and the existing malformed/oversized-frame guard.
112
+ */
113
+ async handleStream(stream, remotePeerId) {
114
+ log('Incoming strand-addr request from: %s', remotePeerId);
115
+ if (this.activeStreams >= this.maxConcurrent) {
116
+ log('Rejecting strand-addr from %s: %d concurrent streams at cap %d', remotePeerId, this.activeStreams, this.maxConcurrent);
117
+ // The request frame is unread here, so the strand id is unknown — reply
118
+ // with an empty list under an empty strand id; the client treats any empty
119
+ // response as "no addrs" and skips this sibling.
120
+ try {
121
+ writeFrame(stream, emptyResponse(''));
122
+ }
123
+ catch {
124
+ // Ignore send errors on the reject path.
125
+ }
126
+ try {
127
+ await stream.close();
128
+ }
129
+ catch {
130
+ // Ignore close errors.
131
+ }
132
+ return;
133
+ }
134
+ this.activeStreams++;
135
+ try {
136
+ const request = await readFrame(stream, this.readTimeoutMs);
137
+ const response = await this.processAddrRequest(request, remotePeerId);
138
+ writeFrame(stream, response);
139
+ }
140
+ catch (err) {
141
+ log('Error handling strand-addr request from %s: %o', remotePeerId, err);
142
+ // Malformed/oversized/timed-out request: strand id is unknown, so reply
143
+ // with an empty list under an empty strand id rather than hanging.
144
+ try {
145
+ writeFrame(stream, emptyResponse(''));
146
+ }
147
+ catch {
148
+ // Ignore send errors on the error path.
149
+ }
150
+ }
151
+ finally {
152
+ this.activeStreams--;
153
+ try {
154
+ await stream.close();
155
+ }
156
+ catch {
157
+ // Ignore close errors.
158
+ }
159
+ }
160
+ }
161
+ /**
162
+ * Decide the response for a decoded request. Exposed (not private) so the
163
+ * decision matrix can be unit-tested directly (mirrors wake's
164
+ * `processWakeRequest`).
165
+ *
166
+ * - Non-member sender → empty `multiaddrs` (refused).
167
+ * - Strand not running locally → empty `multiaddrs` (`getStrandMultiaddrs` → `[]`).
168
+ * - Member + running strand → the strand's live, signaling-first multiaddrs.
169
+ */
170
+ async processAddrRequest(request, remotePeerId) {
171
+ // Control-network membership is the v1 authorization: only this party's
172
+ // cadre peers may ask us for a strand address.
173
+ if (!(await this.options.isMember(remotePeerId))) {
174
+ log('Refusing strand-addr from non-member %s', remotePeerId);
175
+ return emptyResponse(request.strandId);
176
+ }
177
+ const multiaddrs = this.options.getStrandMultiaddrs(request.strandId);
178
+ log('Strand-addr for %s → %d addr(s)', request.strandId, multiaddrs.length);
179
+ return { strandId: request.strandId, multiaddrs };
180
+ }
181
+ }
182
+ /**
183
+ * Client side: ask each candidate sibling for its live strand-`strandId`
184
+ * multiaddrs and return the **deduplicated union** of every answer, ordered
185
+ * signaling-first.
186
+ *
187
+ * Best-effort per peer: a failed/timed-out/empty sibling is logged and skipped,
188
+ * never fatal — an empty union (no sibling online or running the strand) returns
189
+ * `[]`, which the caller treats as an acceptable seed that self-heals on the next
190
+ * resume/reconcile pass. The local node (`node.peerId`) is excluded so we never
191
+ * RPC ourselves or seed with our own strand address.
192
+ *
193
+ * Dials run concurrently but the union preserves candidate order, so the result
194
+ * is deterministic regardless of which sibling answers first.
195
+ */
196
+ export async function collectStrandAddrs(node, peers, strandId, options = {}) {
197
+ const selfId = node.peerId.toString();
198
+ const request = { strandId };
199
+ const candidates = peers.filter(p => p.peerId !== selfId);
200
+ // Concurrent dials; `dialOneSibling` swallows per-peer failure into `[]`, so
201
+ // `Promise.all` never rejects and the result array stays in candidate order.
202
+ const perPeer = await Promise.all(candidates.map(peer => dialOneSibling(node, peer, request, options)));
203
+ // Deduplicated union in candidate order, then signaling-first for a usable
204
+ // dial sequence (relay/`p2p-circuit` ahead of direct addrs).
205
+ const seen = new Set();
206
+ const union = [];
207
+ for (const addrs of perPeer) {
208
+ for (const addr of addrs) {
209
+ if (!seen.has(addr)) {
210
+ seen.add(addr);
211
+ union.push(addr);
212
+ }
213
+ }
214
+ }
215
+ return orderSignalingFirst(union);
216
+ }
217
+ /**
218
+ * Ask one sibling for its strand address, returning its multiaddrs or `[]` on any
219
+ * failure. Tries each dial target in order (peerId first to reuse an open control
220
+ * connection, then explicit addrs) until one answers; a total failure is logged
221
+ * and folded to `[]` so a single dead sibling never aborts the collection.
222
+ */
223
+ async function dialOneSibling(node, peer, request, options) {
224
+ const protocolId = options.protocolId ?? STRAND_ADDR_PROTOCOL;
225
+ const timeoutMs = options.timeoutMs ?? DEFAULT_ADDR_TIMEOUT_MS;
226
+ const targets = dialTargets(peer);
227
+ if (targets.length === 0) {
228
+ log('No dial target for sibling %s', peer.peerId);
229
+ return [];
230
+ }
231
+ for (const target of targets) {
232
+ // One controller per attempt: on timeout, `withTimeout`'s `onTimeout` aborts
233
+ // it, which aborts the in-flight dialProtocol (via the dial `signal`) and the
234
+ // live stream — so neither the connect nor the unbounded response-read leaks.
235
+ const controller = new AbortController();
236
+ try {
237
+ const response = await withTimeout(timeoutMs, `Strand-addr dial ${peer.peerId}`, () => sendStrandAddr(node, target, protocolId, request, timeoutMs, controller.signal), () => controller.abort());
238
+ return response.multiaddrs;
239
+ }
240
+ catch (err) {
241
+ log('Strand-addr dial to %s via %s failed: %o', peer.peerId, describeTarget(target), err);
242
+ }
243
+ }
244
+ return [];
245
+ }
246
+ /**
247
+ * Dial targets for a sibling, peerId first. Dialing by peerId lets libp2p reuse
248
+ * an already-open control connection (the common case — the wiring ticket passes
249
+ * connected siblings); the explicit control addrs are a fallback for when no
250
+ * connection is open. An unparsable peerId is skipped (rely on addrs).
251
+ */
252
+ function dialTargets(peer) {
253
+ const targets = [];
254
+ try {
255
+ targets.push(peerIdFromString(peer.peerId));
256
+ }
257
+ catch (err) {
258
+ log('Unparsable peer id %s, falling back to addrs: %o', peer.peerId, err);
259
+ }
260
+ if (peer.addrs) {
261
+ targets.push(...peer.addrs);
262
+ }
263
+ return targets;
264
+ }
265
+ /** Short label for a dial target (peerId or multiaddr) for logging. */
266
+ function describeTarget(target) {
267
+ return target.toString();
268
+ }
269
+ /**
270
+ * Open one stream to a target, send the request, half-close, and read the
271
+ * response.
272
+ *
273
+ * `signal` is the per-attempt abort signal from {@link dialOneSibling}: it is
274
+ * passed to `dialProtocol` so a timeout during connect aborts the dial, and once
275
+ * the stream is open an abort listener resets the live stream — releasing the
276
+ * otherwise unbounded response-read. `readFrame` still applies `timeoutMs` as a
277
+ * backstop for the case where an abort does not propagate (e.g. a stream double
278
+ * whose `abort()` is a no-op).
279
+ */
280
+ async function sendStrandAddr(node, target, protocolId, request, timeoutMs, signal) {
281
+ // `runOnLimitedConnection: true`: the sibling may be reachable only over a
282
+ // circuit-relay connection (the signaling-first addr), which libp2p marks
283
+ // "limited". The exchange is a single tiny request→response well within the
284
+ // relay's data/duration cap, so opening it on the limited connection is safe
285
+ // and is the whole point of dialing over the relay.
286
+ const rawStream = await node.dialProtocol(target, protocolId, { runOnLimitedConnection: true, signal });
287
+ const stream = rawStream;
288
+ const abortErr = new Error('Strand-addr dial aborted by timeout');
289
+ const onAbort = () => stream.abort(abortErr);
290
+ // If the timeout already fired during connect, release the freshly-opened stream now.
291
+ if (signal.aborted) {
292
+ onAbort();
293
+ throw abortErr;
294
+ }
295
+ signal.addEventListener('abort', onAbort, { once: true });
296
+ try {
297
+ writeFrame(stream, request);
298
+ // close() half-closes the write end (EOF) while the read end stays open for
299
+ // the response — the same libp2p 3.x pattern as wake/seed delivery.
300
+ await stream.close();
301
+ const response = await readFrame(stream, timeoutMs);
302
+ log('Strand-addr response: %d addr(s) for %s', response.multiaddrs.length, response.strandId);
303
+ return response;
304
+ }
305
+ catch (err) {
306
+ stream.abort(err instanceof Error ? err : new Error(String(err)));
307
+ throw err;
308
+ }
309
+ finally {
310
+ signal.removeEventListener('abort', onAbort);
311
+ }
312
+ }
313
+ //# sourceMappingURL=strand-addr-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strand-addr-protocol.js","sourceRoot":"","sources":["../src/strand-addr-protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { StrandMode } from './types.js';
2
+ /**
3
+ * A single CadrePeer membership row, as returned by
4
+ * `ControlDatabase.queryCadrePeers()`.
5
+ */
6
+ export interface CohortPeerRow {
7
+ peerId: string;
8
+ /** Comma-joined list of multiaddr strings; `''` when no addrs were recorded. */
9
+ multiaddr: string | null;
10
+ }
11
+ /**
12
+ * Cohort-derived discovery seed for a strand's libp2p node. `bootstrapNodes`
13
+ * here are **strand-network** addresses resolved on demand from co-cadre
14
+ * siblings (see `collectStrandAddrs` / `STRAND_ADDR_PROTOCOL`), NOT the control
15
+ * addresses stored in `CadrePeer.Multiaddr`.
16
+ */
17
+ export interface CohortSeed {
18
+ /** Dialable strand-network multiaddr strings for cohort peers other than self (deduplicated). */
19
+ bootstrapNodes: string[];
20
+ /** True when at least one CadrePeer row other than self exists (even if it has no resolvable strand addr). */
21
+ hasOtherPeers: boolean;
22
+ }
23
+ /**
24
+ * Membership-only view of a strand's cohort, derived from the control network's
25
+ * CadrePeer rows. The bootstrap addresses themselves are resolved separately
26
+ * (over the strand-addr RPC) from these peerIds, never from the rows' addr field.
27
+ */
28
+ export interface CohortMembers {
29
+ /** PeerIds of cohort members other than self — the strand-addr RPC fan-out targets (deduplicated, in row order). */
30
+ otherPeerIds: string[];
31
+ /** True when at least one CadrePeer row other than self exists (even if it has no resolvable strand addr). */
32
+ hasOtherPeers: boolean;
33
+ }
34
+ /**
35
+ * Derive the membership view of a strand's cohort from the control network's
36
+ * CadrePeer rows: the peerIds of members other than `selfPeerId` (the strand-addr
37
+ * RPC fan-out targets) and whether any other member exists at all.
38
+ *
39
+ * Deliberately does NOT read `CadrePeer.Multiaddr` — that field carries each
40
+ * node's *control*-network address, which must not seed the *strand* mesh
41
+ * (dialing it reaches the remote's control instance, not its strand instance).
42
+ * The strand-network bootstrap addresses are resolved on demand over the control
43
+ * mesh from these peerIds. `hasOtherPeers` reflects membership presence, NOT
44
+ * dialability, so a cohort whose strand addrs are not yet resolvable still
45
+ * selects `networked` mode.
46
+ */
47
+ export declare function deriveCohortMembers(peers: CohortPeerRow[], selfPeerId?: string): CohortMembers;
48
+ /**
49
+ * Choose the strand mode. An explicit mode (from `addStrand`) always wins. When
50
+ * omitted (the `handleStrandAdded` discovery path, or an `addStrand` caller that
51
+ * leaves it unset), infer: `bootstrap` for a solo/first node (no other peers),
52
+ * `networked` once the cohort has other members.
53
+ */
54
+ export declare function selectStrandMode(explicitMode: StrandMode | undefined, hasOtherPeers: boolean): StrandMode;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Derive the membership view of a strand's cohort from the control network's
3
+ * CadrePeer rows: the peerIds of members other than `selfPeerId` (the strand-addr
4
+ * RPC fan-out targets) and whether any other member exists at all.
5
+ *
6
+ * Deliberately does NOT read `CadrePeer.Multiaddr` — that field carries each
7
+ * node's *control*-network address, which must not seed the *strand* mesh
8
+ * (dialing it reaches the remote's control instance, not its strand instance).
9
+ * The strand-network bootstrap addresses are resolved on demand over the control
10
+ * mesh from these peerIds. `hasOtherPeers` reflects membership presence, NOT
11
+ * dialability, so a cohort whose strand addrs are not yet resolvable still
12
+ * selects `networked` mode.
13
+ */
14
+ export function deriveCohortMembers(peers, selfPeerId) {
15
+ const otherPeerIds = [];
16
+ const seen = new Set();
17
+ for (const peer of peers) {
18
+ if (selfPeerId !== undefined && peer.peerId === selfPeerId) {
19
+ continue;
20
+ }
21
+ if (seen.has(peer.peerId)) {
22
+ continue;
23
+ }
24
+ seen.add(peer.peerId);
25
+ otherPeerIds.push(peer.peerId);
26
+ }
27
+ return { otherPeerIds, hasOtherPeers: otherPeerIds.length > 0 };
28
+ }
29
+ /**
30
+ * Choose the strand mode. An explicit mode (from `addStrand`) always wins. When
31
+ * omitted (the `handleStrandAdded` discovery path, or an `addStrand` caller that
32
+ * leaves it unset), infer: `bootstrap` for a solo/first node (no other peers),
33
+ * `networked` once the cohort has other members.
34
+ */
35
+ export function selectStrandMode(explicitMode, hasOtherPeers) {
36
+ return explicitMode ?? (hasOtherPeers ? 'networked' : 'bootstrap');
37
+ }
38
+ //# sourceMappingURL=strand-cohort.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strand-cohort.js","sourceRoot":"","sources":["../src/strand-cohort.ts"],"names":[],"mappings":"AAqCA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAsB,EAAE,UAAmB;IAC7E,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,SAAS;QACX,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoC,EAAE,aAAsB;IAC3F,OAAO,YAAY,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;AACrE,CAAC"}
@@ -26,28 +26,76 @@ export interface StrandDatabaseConfig {
26
26
  * across the two consumers.
27
27
  */
28
28
  rawStorage?: IRawStorage;
29
+ /**
30
+ * Strand type (`'o'` open / `'c'` closed) from the control-network strand row.
31
+ * Drives the founder bootstrap: open strands get a `Header` only; closed strands
32
+ * also get the founding `Member`+`Manager`.
33
+ */
34
+ strandType: 'o' | 'c';
35
+ /**
36
+ * The closed-strand `MemberPrivateKey` (base64 protobuf) from the strand row.
37
+ * Required when `founder` is true and `strandType` is `'c'` — it derives the
38
+ * founding `Member.Key`/`Manager.MemberKey`. Absent for open strands.
39
+ */
40
+ memberPrivateKey?: string;
41
+ /**
42
+ * Whether this node founds the strand. When true, {@link initialize} runs the
43
+ * one-time founder membership bootstrap after the schema is applied. Joiners
44
+ * leave this false and write nothing (rows arrive via sync). Defaults to false.
45
+ */
46
+ founder?: boolean;
29
47
  }
30
48
  /**
31
- * StrandDatabase manages the sApp schema for a strand using Quereus with Optimystic backend.
32
- * Each strand instance has its own isolated database with the sApp's schema applied.
49
+ * StrandDatabase manages the sApp schema for a strand using Quereus with the
50
+ * Optimystic backend. Each strand instance has its own isolated database with
51
+ * the sApp's schema applied.
52
+ *
53
+ * This class owns the `Database` lifecycle (creation, `getDatabase()`, `close()`)
54
+ * but delegates the actual SQL-surface composition — plugin registration, node
55
+ * wiring, catalog hydration, schema apply — to `connectToStrand` from
56
+ * `@serfab/quereus-plugin-sereus`, the single shared composition. The libp2p
57
+ * node is injected here, so `connectToStrand` never *creates* the node;
58
+ * `StrandInstanceManager` owns the node lifecycle. (The strand connection's
59
+ * `shutdown` still stops the injected node via the collection factory, so the
60
+ * manager's own `node.stop()` is an idempotent second stop — see `close()`.)
33
61
  */
34
62
  export declare class StrandDatabase {
35
63
  private db;
36
- private collectionFactory;
64
+ private shutdownStrand;
37
65
  private readonly config;
38
66
  private initialized;
39
67
  constructor(config: StrandDatabaseConfig);
40
68
  /**
41
- * Initialize the database - register plugins and execute sApp schema
69
+ * Initialize the database — create the `Database` and delegate the strand
70
+ * SQL-surface composition (plugins, node wiring, hydrate, schema apply) to
71
+ * `connectToStrand` with the injected libp2p node.
42
72
  */
43
73
  initialize(): Promise<void>;
44
- private executeSchema;
74
+ /**
75
+ * Run the founder membership bootstrap against the freshly-composed strand DB.
76
+ *
77
+ * Derives the founding keypair from the closed-strand `memberPrivateKey` (the
78
+ * `Member.Key`/`Manager.MemberKey` are its public key); a closed strand with no
79
+ * `memberPrivateKey` throws, because it could never seat a founding manager.
80
+ * Open strands derive no keypair (Header only). Idempotent — see
81
+ * {@link bootstrapFounderMembership}.
82
+ */
83
+ private bootstrapFounder;
84
+ /**
85
+ * Derive the founding keypair for a closed strand from its `memberPrivateKey`,
86
+ * failing loudly when the key is absent (a closed strand with no founding
87
+ * Manager can never admit anyone).
88
+ */
89
+ private deriveFounderKeyPair;
45
90
  /**
46
91
  * Get the underlying database for queries
47
92
  */
48
93
  getDatabase(): Database;
49
94
  /**
50
- * Close the database and cleanup resources
95
+ * Close the database and cleanup resources. Runs the strand-connection
96
+ * shutdown (collection-factory teardown, which also stops the injected node),
97
+ * then closes the `Database`. `StrandInstanceManager.releaseRuntime` issues a
98
+ * further idempotent `node.stop()` after this returns.
51
99
  */
52
100
  close(): Promise<void>;
53
101
  private ensureInitialized;