@serfab/cadre-core 0.6.0 → 0.8.1
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.
- package/README.md +9 -8
- package/dist/authority-key.d.ts +41 -0
- package/dist/authority-key.js +52 -0
- package/dist/authority-key.js.map +1 -0
- package/dist/cadre-node.d.ts +777 -11
- package/dist/cadre-node.js +1912 -82
- package/dist/cadre-node.js.map +1 -1
- package/dist/canonical-datetime.d.ts +33 -0
- package/dist/canonical-datetime.js +38 -0
- package/dist/canonical-datetime.js.map +1 -0
- package/dist/canonical-json.d.ts +12 -0
- package/dist/canonical-json.js +33 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/control-cohort.d.ts +63 -0
- package/dist/control-cohort.js +80 -0
- package/dist/control-cohort.js.map +1 -0
- package/dist/control-database.d.ts +259 -5
- package/dist/control-database.js +541 -128
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +89 -0
- package/dist/control-formation-recorder.js +139 -0
- package/dist/control-formation-recorder.js.map +1 -0
- package/dist/control-schema.d.ts +12 -0
- package/dist/control-schema.js +199 -0
- package/dist/control-schema.js.map +1 -0
- package/dist/control-stream.d.ts +54 -0
- package/dist/control-stream.js +104 -0
- package/dist/control-stream.js.map +1 -0
- package/dist/device-token.d.ts +50 -0
- package/dist/device-token.js +66 -0
- package/dist/device-token.js.map +1 -0
- package/dist/diagnostics/connection-path.d.ts +118 -0
- package/dist/diagnostics/connection-path.js +204 -0
- package/dist/diagnostics/connection-path.js.map +1 -0
- package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
- package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
- package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
- package/dist/hibernation-manager.d.ts +79 -0
- package/dist/hibernation-manager.js +173 -23
- package/dist/hibernation-manager.js.map +1 -1
- package/dist/index.d.ts +25 -4
- package/dist/index.js +59 -4
- package/dist/index.js.map +1 -1
- package/dist/key-store-file.d.ts +52 -0
- package/dist/key-store-file.js +194 -0
- package/dist/key-store-file.js.map +1 -0
- package/dist/key-store.d.ts +75 -0
- package/dist/key-store.js +66 -0
- package/dist/key-store.js.map +1 -0
- package/dist/peer-authorization.d.ts +26 -0
- package/dist/peer-authorization.js +40 -0
- package/dist/peer-authorization.js.map +1 -0
- package/dist/peer-record.d.ts +89 -0
- package/dist/peer-record.js +117 -0
- package/dist/peer-record.js.map +1 -0
- package/dist/push-credentials.d.ts +34 -0
- package/dist/push-credentials.js +81 -0
- package/dist/push-credentials.js.map +1 -0
- package/dist/push-fanout.d.ts +137 -0
- package/dist/push-fanout.js +197 -0
- package/dist/push-fanout.js.map +1 -0
- package/dist/push-notifier-apns.d.ts +53 -0
- package/dist/push-notifier-apns.js +195 -0
- package/dist/push-notifier-apns.js.map +1 -0
- package/dist/push-notifier-fcm.d.ts +38 -0
- package/dist/push-notifier-fcm.js +183 -0
- package/dist/push-notifier-fcm.js.map +1 -0
- package/dist/push-notifier-shared.d.ts +18 -0
- package/dist/push-notifier-shared.js +25 -0
- package/dist/push-notifier-shared.js.map +1 -0
- package/dist/push-notifier.d.ts +71 -0
- package/dist/push-notifier.js +50 -0
- package/dist/push-notifier.js.map +1 -0
- package/dist/schema-verification.d.ts +11 -2
- package/dist/schema-verification.js +15 -4
- package/dist/schema-verification.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +210 -7
- package/dist/seed-bootstrap.js +429 -131
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +63 -0
- package/dist/seed-trust-policy.js +56 -0
- package/dist/seed-trust-policy.js.map +1 -0
- package/dist/strand-addr-protocol.d.ts +132 -0
- package/dist/strand-addr-protocol.js +313 -0
- package/dist/strand-addr-protocol.js.map +1 -0
- package/dist/strand-cohort.d.ts +54 -0
- package/dist/strand-cohort.js +38 -0
- package/dist/strand-cohort.js.map +1 -0
- package/dist/strand-database.d.ts +54 -6
- package/dist/strand-database.js +88 -89
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +79 -19
- package/dist/strand-formation-manager.js +196 -104
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +162 -0
- package/dist/strand-formation-protocol.js +279 -0
- package/dist/strand-formation-protocol.js.map +1 -0
- package/dist/strand-instance-manager.d.ts +73 -4
- package/dist/strand-instance-manager.js +167 -41
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +29 -0
- package/dist/strand-member-key.js +38 -0
- package/dist/strand-member-key.js.map +1 -0
- package/dist/strand-member-registry.d.ts +98 -0
- package/dist/strand-member-registry.js +129 -0
- package/dist/strand-member-registry.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +361 -0
- package/dist/strand-membership-writer.js +449 -0
- package/dist/strand-membership-writer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +77 -2
- package/dist/strand-solicitation.js +25 -4
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-payload.d.ts +30 -0
- package/dist/strand-wake-payload.js +20 -0
- package/dist/strand-wake-payload.js.map +1 -0
- package/dist/strand-wake-protocol.d.ts +114 -0
- package/dist/strand-wake-protocol.js +257 -0
- package/dist/strand-wake-protocol.js.map +1 -0
- package/dist/strand-watcher.d.ts +6 -2
- package/dist/strand-watcher.js +57 -17
- package/dist/strand-watcher.js.map +1 -1
- package/dist/types.d.ts +413 -23
- package/dist/types.js +22 -5
- package/dist/types.js.map +1 -1
- package/package.json +14 -8
- package/src/authority-key.ts +68 -0
- package/src/cadre-node.ts +2937 -929
- package/src/canonical-datetime.ts +39 -0
- package/src/canonical-json.ts +30 -0
- package/src/control-cohort.ts +103 -0
- package/src/control-database.ts +931 -383
- package/src/control-formation-recorder.ts +151 -0
- package/src/control-schema.ts +198 -0
- package/src/control-stream.ts +128 -0
- package/src/device-token.ts +89 -0
- package/src/diagnostics/connection-path.ts +277 -0
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
- package/src/hibernation-manager.ts +197 -28
- package/src/index.ts +273 -79
- package/src/key-store-file.ts +203 -0
- package/src/key-store.ts +99 -0
- package/src/peer-authorization.ts +53 -0
- package/src/peer-record.ts +146 -0
- package/src/push-credentials.ts +84 -0
- package/src/push-fanout.ts +272 -0
- package/src/push-notifier-apns.ts +243 -0
- package/src/push-notifier-fcm.ts +221 -0
- package/src/push-notifier-shared.ts +28 -0
- package/src/push-notifier.ts +93 -0
- package/src/schema-verification.ts +18 -4
- package/src/seed-bootstrap.ts +504 -171
- package/src/seed-trust-policy.ts +107 -0
- package/src/strand-addr-protocol.ts +410 -0
- package/src/strand-cohort.ts +77 -0
- package/src/strand-database.ts +113 -126
- package/src/strand-formation-manager.ts +233 -142
- package/src/strand-formation-protocol.ts +391 -0
- package/src/strand-instance-manager.ts +207 -56
- package/src/strand-member-key.ts +39 -0
- package/src/strand-member-registry.ts +177 -0
- package/src/strand-membership-writer.ts +640 -0
- package/src/strand-solicitation.ts +93 -4
- package/src/strand-wake-payload.ts +32 -0
- package/src/strand-wake-protocol.ts +331 -0
- package/src/strand-watcher.ts +61 -17
- package/src/types.ts +1012 -575
|
@@ -7,9 +7,10 @@ const timing = debug('sereus:cadre:timing');
|
|
|
7
7
|
/**
|
|
8
8
|
* Get the isolated storage path for a specific strand.
|
|
9
9
|
*
|
|
10
|
-
* @deprecated This
|
|
11
|
-
* Use a storage provider factory function instead, which
|
|
12
|
-
* create strand-specific storage paths using
|
|
10
|
+
* @deprecated This helper is Node-only and throws in React Native (it assumes a
|
|
11
|
+
* filesystem layout). Use a storage provider factory function instead, which
|
|
12
|
+
* receives the strandId and can create strand-specific storage paths using
|
|
13
|
+
* platform-appropriate methods.
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* // Instead of using getStrandStoragePath, use a storage provider factory:
|
|
@@ -23,12 +24,15 @@ export function getStrandStoragePath(basePath, strandId) {
|
|
|
23
24
|
throw new Error('getStrandStoragePath is not available in React Native. ' +
|
|
24
25
|
'Use a storage provider factory function instead.');
|
|
25
26
|
}
|
|
26
|
-
// Dynamically require path only in Node.js
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
|
-
const path = require('path');
|
|
29
27
|
// Sanitize strandId for filesystem safety (UUIDs should be safe, but just in case)
|
|
30
28
|
const safeId = strandId.replace(/[^a-zA-Z0-9-]/g, '_');
|
|
31
|
-
|
|
29
|
+
// Build the path with plain string joins rather than the Node `path` module.
|
|
30
|
+
// A static `require('path')` forces RN bundlers (e.g. Metro) to *resolve* the
|
|
31
|
+
// module at bundle time even though this Node-only helper throws above before
|
|
32
|
+
// ever reaching here — joining by hand keeps the module free of any Node
|
|
33
|
+
// built-in reference, so RN bundles need no `path` shim.
|
|
34
|
+
const trimmedBase = basePath.replace(/[\\/]+$/, '');
|
|
35
|
+
return `${trimmedBase}/strands/${safeId}`;
|
|
32
36
|
}
|
|
33
37
|
/**
|
|
34
38
|
* Resolve a storage provider for a specific strand.
|
|
@@ -51,6 +55,12 @@ function resolveStrandStorage(provider, strandId) {
|
|
|
51
55
|
export class StrandInstanceManager {
|
|
52
56
|
constructor() {
|
|
53
57
|
this.instances = new Map();
|
|
58
|
+
/**
|
|
59
|
+
* Retained launch config per strand, captured in `startStrand` and cleared in
|
|
60
|
+
* `stopStrand`. `resumeStrand` reuses it to rebuild a quiesced strand's runtime
|
|
61
|
+
* without the caller re-threading storage/network/profile/key/sApp config.
|
|
62
|
+
*/
|
|
63
|
+
this.launchConfigs = new Map();
|
|
54
64
|
this.stopping = false;
|
|
55
65
|
log('StrandInstanceManager created');
|
|
56
66
|
}
|
|
@@ -87,8 +97,9 @@ export class StrandInstanceManager {
|
|
|
87
97
|
}
|
|
88
98
|
log('Starting strand instance: %s (sApp: %s v%s)', strandId, sAppConfig.id, sAppConfig.version);
|
|
89
99
|
const tTotal = performance.now();
|
|
90
|
-
// Verify schema signature before proceeding
|
|
91
|
-
|
|
100
|
+
// Verify schema signature before proceeding (fail-closed by default)
|
|
101
|
+
const requireSignature = config.requireSignedSchemas ?? true;
|
|
102
|
+
assertSchemaSignature(sAppConfig, { requireSignature });
|
|
92
103
|
log('Strand %s sApp schema signature verified (author: %s)', strandId, sAppConfig.id);
|
|
93
104
|
// Convert SAppConfig to SAppInfo for the instance
|
|
94
105
|
const sAppInfo = {
|
|
@@ -109,25 +120,49 @@ export class StrandInstanceManager {
|
|
|
109
120
|
latencyHint
|
|
110
121
|
};
|
|
111
122
|
this.instances.set(strandId, instance);
|
|
123
|
+
this.launchConfigs.set(strandId, config);
|
|
124
|
+
try {
|
|
125
|
+
await this.buildStrandRuntime(instance, config);
|
|
126
|
+
timing('[startStrand:%s] total: %dms', strandId, Math.round(performance.now() - tTotal));
|
|
127
|
+
log('Strand %s started successfully with sApp %s', strandId, sAppConfig.id);
|
|
128
|
+
return instance;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
instance.status = 'error';
|
|
132
|
+
instance.error = error instanceof Error ? error.message : String(error);
|
|
133
|
+
log('Failed to start strand %s: %s', strandId, instance.error);
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build (or rebuild) the libp2p node + StrandDatabase for an instance and
|
|
139
|
+
* attach them, transitioning it to `active`. Shared by `startStrand` (fresh
|
|
140
|
+
* launch) and `resumeStrand` (rehydrating a quiesced instance). Reads all
|
|
141
|
+
* volatile inputs (bootstrapNodes, mode, storage, network, profile,
|
|
142
|
+
* privateKey, sApp config) from `config`, so the caller controls the
|
|
143
|
+
* cohort-derived values.
|
|
144
|
+
*/
|
|
145
|
+
async buildStrandRuntime(instance, config) {
|
|
146
|
+
const strandId = instance.strandId;
|
|
147
|
+
const { sAppConfig } = config;
|
|
148
|
+
// Resolve storage for this strand. If a factory function is provided, it is
|
|
149
|
+
// called with the strandId to create strand-specific storage (e.g.,
|
|
150
|
+
// strand-isolated directories).
|
|
151
|
+
const strandStorage = resolveStrandStorage(config.storage?.provider, strandId);
|
|
152
|
+
if (strandStorage) {
|
|
153
|
+
log('Strand %s using provided storage provider', strandId);
|
|
154
|
+
}
|
|
155
|
+
// Note: protocolPrefix would be used by libp2p services, but createLibp2pNode
|
|
156
|
+
// may not support it yet - tracked for future implementation.
|
|
157
|
+
const _protocolPrefix = `/sereus/strand/${strandId}`;
|
|
158
|
+
// Determine relay mode: if explicitly set in config, use that;
|
|
159
|
+
// otherwise default to true for storage profile nodes.
|
|
160
|
+
const enableRelay = config.network?.enableRelay ?? (config.profile === 'storage');
|
|
112
161
|
try {
|
|
113
|
-
// Resolve storage for this strand
|
|
114
|
-
// If a factory function is provided, it will be called with the strandId
|
|
115
|
-
// to create strand-specific storage (e.g., strand-isolated directories)
|
|
116
|
-
const strandStorage = resolveStrandStorage(config.storage?.provider, strandId);
|
|
117
|
-
if (strandStorage) {
|
|
118
|
-
log('Strand %s using provided storage provider', strandId);
|
|
119
|
-
}
|
|
120
|
-
// Create isolated libp2p node for this strand
|
|
121
|
-
// Note: protocolPrefix would be used by libp2p services, but createLibp2pNode
|
|
122
|
-
// may not support it yet - tracked for future implementation
|
|
123
|
-
const _protocolPrefix = `/sereus/strand/${strandId}`;
|
|
124
|
-
// Determine relay mode: if explicitly set in config, use that;
|
|
125
|
-
// otherwise default to true for storage profile nodes
|
|
126
|
-
const enableRelay = config.network?.enableRelay ?? (config.profile === 'storage');
|
|
127
162
|
let t0 = performance.now();
|
|
128
163
|
const node = await createLibp2pNode({
|
|
129
164
|
port: 0, // Random port
|
|
130
|
-
bootstrapNodes: [],
|
|
165
|
+
bootstrapNodes: config.bootstrapNodes ?? [],
|
|
131
166
|
networkName: `strand-${strandId}`,
|
|
132
167
|
storage: strandStorage,
|
|
133
168
|
fretProfile: config.profile === 'storage' ? 'core' : 'edge',
|
|
@@ -142,15 +177,19 @@ export class StrandInstanceManager {
|
|
|
142
177
|
},
|
|
143
178
|
...(config.privateKey && { privateKey: config.privateKey }),
|
|
144
179
|
...(config.network?.transports && { transports: config.network.transports }),
|
|
145
|
-
...(config.network?.listenAddrs && { listenAddrs: config.network.listenAddrs })
|
|
180
|
+
...(config.network?.listenAddrs && { listenAddrs: config.network.listenAddrs }),
|
|
181
|
+
...(config.network?.connectionGater && { connectionGater: config.network.connectionGater })
|
|
146
182
|
});
|
|
147
|
-
timing('[
|
|
183
|
+
timing('[buildStrandRuntime:%s] createLibp2pNode: %dms', strandId, Math.round(performance.now() - t0));
|
|
148
184
|
instance.libp2pNode = node;
|
|
149
185
|
// Create and initialize the StrandDatabase. In bootstrap mode the same
|
|
150
186
|
// strandStorage instance also backs the optimystic local transactor so
|
|
151
187
|
// DML lands on the host's persistent storage (e.g. MMKV on RN). Sharing
|
|
152
188
|
// the instance — not creating a second one over the same id+prefix —
|
|
153
189
|
// avoids cache divergence between the libp2p and database paths.
|
|
190
|
+
//
|
|
191
|
+
// Attach before initialize so a failed init is cleaned up by
|
|
192
|
+
// releaseRuntime below (close() is safe on a partially-initialized db).
|
|
154
193
|
t0 = performance.now();
|
|
155
194
|
const strandDb = new StrandDatabase({
|
|
156
195
|
strandId,
|
|
@@ -158,21 +197,115 @@ export class StrandInstanceManager {
|
|
|
158
197
|
libp2pNode: node,
|
|
159
198
|
coordinatedRepo: node.coordinatedRepo,
|
|
160
199
|
mode: config.mode ?? 'networked',
|
|
161
|
-
rawStorage: strandStorage
|
|
200
|
+
rawStorage: strandStorage,
|
|
201
|
+
// Founder bootstrap inputs: the strand's type drives which membership rows
|
|
202
|
+
// are written, and the closed-strand MemberPrivateKey derives the founding
|
|
203
|
+
// Member/Authority key. Both come off the control-network strand row.
|
|
204
|
+
strandType: config.strandRow.Type,
|
|
205
|
+
memberPrivateKey: config.strandRow.MemberPrivateKey ?? undefined,
|
|
206
|
+
founder: config.founder
|
|
162
207
|
});
|
|
163
|
-
await strandDb.initialize();
|
|
164
|
-
timing('[startStrand:%s] strandDatabase.initialize: %dms', strandId, Math.round(performance.now() - t0));
|
|
165
208
|
instance.database = strandDb;
|
|
209
|
+
await strandDb.initialize();
|
|
210
|
+
timing('[buildStrandRuntime:%s] strandDatabase.initialize: %dms', strandId, Math.round(performance.now() - t0));
|
|
166
211
|
instance.status = 'active';
|
|
167
212
|
instance.lastActivity = new Date();
|
|
168
|
-
|
|
169
|
-
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
// Roll back any partially-attached runtime so the instance is left with
|
|
216
|
+
// NEITHER handle. Otherwise the `libp2pNode || database` "already live"
|
|
217
|
+
// guard in resumeStrand/handleStrandWake would treat a half-built strand
|
|
218
|
+
// as healthy — leaking the libp2p node and never retrying the rebuild.
|
|
219
|
+
await this.releaseRuntime(instance).catch((cleanupErr) => {
|
|
220
|
+
log('buildStrandRuntime cleanup for strand %s also failed: %o', strandId, cleanupErr);
|
|
221
|
+
});
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Release an instance's strand-network runtime: close the StrandDatabase, then
|
|
227
|
+
* stop the libp2p node (construction order in reverse), clearing both fields
|
|
228
|
+
* and zeroing connectedPeers. Tolerant of partially-built state — either handle
|
|
229
|
+
* may be absent — so it doubles as rollback for a failed `buildStrandRuntime`.
|
|
230
|
+
* Shared by `quiesceStrand`, `stopStrand`, and that rollback path.
|
|
231
|
+
*/
|
|
232
|
+
async releaseRuntime(instance) {
|
|
233
|
+
if (instance.database) {
|
|
234
|
+
await instance.database.close();
|
|
235
|
+
instance.database = undefined;
|
|
236
|
+
}
|
|
237
|
+
if (instance.libp2pNode) {
|
|
238
|
+
await instance.libp2pNode.stop();
|
|
239
|
+
instance.libp2pNode = undefined;
|
|
240
|
+
}
|
|
241
|
+
instance.connectedPeers = 0;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Quiesce a strand: release its strand-network resources (stop the libp2p node,
|
|
245
|
+
* close the StrandDatabase) while RETAINING the instance record — identity,
|
|
246
|
+
* sAppInfo, keys, latency hint, metadata — and its launch config so it can be
|
|
247
|
+
* resumed later. Mechanically this is `stopStrand` minus the instance/config
|
|
248
|
+
* deletion. The caller sets the post-quiesce status (e.g. `hibernating`).
|
|
249
|
+
* No-ops when the strand is missing or already quiesced.
|
|
250
|
+
*/
|
|
251
|
+
async quiesceStrand(strandId) {
|
|
252
|
+
const instance = this.instances.get(strandId);
|
|
253
|
+
if (!instance) {
|
|
254
|
+
log('quiesceStrand: strand %s not found', strandId);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (!instance.libp2pNode && !instance.database) {
|
|
258
|
+
log('quiesceStrand: strand %s already quiesced', strandId);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
log('Quiescing strand instance: %s', strandId);
|
|
262
|
+
await this.releaseRuntime(instance);
|
|
263
|
+
log('Strand %s quiesced (resources released, instance retained)', strandId);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Resume a previously-quiesced strand: rebuild its libp2p node + StrandDatabase
|
|
267
|
+
* from the retained launch config and re-attach them, transitioning it back to
|
|
268
|
+
* `active`. `overrides` re-applies volatile inputs that may have changed since
|
|
269
|
+
* launch (cohort `bootstrapNodes`, lifecycle `mode`) and updates the retained
|
|
270
|
+
* config so a later resume reuses the latest values. Returns the live instance
|
|
271
|
+
* unchanged if it is already running.
|
|
272
|
+
*/
|
|
273
|
+
async resumeStrand(strandId, overrides) {
|
|
274
|
+
if (this.stopping) {
|
|
275
|
+
throw new Error('StrandInstanceManager is stopping');
|
|
276
|
+
}
|
|
277
|
+
const instance = this.instances.get(strandId);
|
|
278
|
+
if (!instance) {
|
|
279
|
+
throw new Error(`Cannot resume strand ${strandId}: not tracked`);
|
|
280
|
+
}
|
|
281
|
+
const launchConfig = this.launchConfigs.get(strandId);
|
|
282
|
+
if (!launchConfig) {
|
|
283
|
+
throw new Error(`Cannot resume strand ${strandId}: no retained launch config`);
|
|
284
|
+
}
|
|
285
|
+
if (instance.libp2pNode || instance.database) {
|
|
286
|
+
log('resumeStrand: strand %s already live', strandId);
|
|
287
|
+
return instance;
|
|
288
|
+
}
|
|
289
|
+
log('Resuming strand instance: %s', strandId);
|
|
290
|
+
const tTotal = performance.now();
|
|
291
|
+
// Re-apply volatile inputs and persist them so a subsequent resume reuses them.
|
|
292
|
+
const resumeConfig = {
|
|
293
|
+
...launchConfig,
|
|
294
|
+
bootstrapNodes: overrides?.bootstrapNodes ?? launchConfig.bootstrapNodes,
|
|
295
|
+
mode: overrides?.mode ?? launchConfig.mode
|
|
296
|
+
};
|
|
297
|
+
this.launchConfigs.set(strandId, resumeConfig);
|
|
298
|
+
instance.status = 'starting';
|
|
299
|
+
try {
|
|
300
|
+
await this.buildStrandRuntime(instance, resumeConfig);
|
|
301
|
+
timing('[resumeStrand:%s] total: %dms', strandId, Math.round(performance.now() - tTotal));
|
|
302
|
+
log('Strand %s resumed successfully', strandId);
|
|
170
303
|
return instance;
|
|
171
304
|
}
|
|
172
305
|
catch (error) {
|
|
173
306
|
instance.status = 'error';
|
|
174
307
|
instance.error = error instanceof Error ? error.message : String(error);
|
|
175
|
-
log('Failed to
|
|
308
|
+
log('Failed to resume strand %s: %s', strandId, instance.error);
|
|
176
309
|
throw error;
|
|
177
310
|
}
|
|
178
311
|
}
|
|
@@ -188,17 +321,10 @@ export class StrandInstanceManager {
|
|
|
188
321
|
log('Stopping strand instance: %s', strandId);
|
|
189
322
|
instance.status = 'stopping';
|
|
190
323
|
try {
|
|
191
|
-
|
|
192
|
-
if (instance.database) {
|
|
193
|
-
await instance.database.close();
|
|
194
|
-
instance.database = undefined;
|
|
195
|
-
}
|
|
196
|
-
if (instance.libp2pNode) {
|
|
197
|
-
await instance.libp2pNode.stop();
|
|
198
|
-
instance.libp2pNode = undefined;
|
|
199
|
-
}
|
|
324
|
+
await this.releaseRuntime(instance);
|
|
200
325
|
instance.status = 'stopped';
|
|
201
326
|
this.instances.delete(strandId);
|
|
327
|
+
this.launchConfigs.delete(strandId);
|
|
202
328
|
log('Strand %s stopped successfully', strandId);
|
|
203
329
|
}
|
|
204
330
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-instance-manager.js","sourceRoot":"","sources":["../src/strand-instance-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,gBAAgB,EAAoB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"strand-instance-manager.js","sourceRoot":"","sources":["../src/strand-instance-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type AuthorityKeyPair } from './authority-key.js';
|
|
2
|
+
/**
|
|
3
|
+
* Mint a fresh ed25519 strand member private key, encoded as base64 protobuf.
|
|
4
|
+
*
|
|
5
|
+
* Closed strands (`Type:'c'`) gate membership on a `MemberPrivateKey` column
|
|
6
|
+
* (see the `Strand` control-schema table). This produces one in the SAME
|
|
7
|
+
* encoding {@link StrandSolicitationService.formStrand} uses for its
|
|
8
|
+
* `invitePrivateKey`, so a host-minted key and a formation-issued key are
|
|
9
|
+
* interchangeable when attaching a closed strand.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateStrandMemberKey(): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Bridge a strand's `MemberPrivateKey` (base64 protobuf libp2p ed25519 key, as
|
|
14
|
+
* minted by {@link generateStrandMemberKey} or issued by formation) into the
|
|
15
|
+
* base64url keypair the strand RBAC constraints consume.
|
|
16
|
+
*
|
|
17
|
+
* The `Strand` control-layer `MemberPrivateKey` is the closed-strand read-gating
|
|
18
|
+
* secret; the founding `Member.Key`/`Authority.MemberKey` (the strand RBAC layer)
|
|
19
|
+
* are *derived from it* — they are the `publicKeyB64` of the keypair this returns.
|
|
20
|
+
* Decode the protobuf to a libp2p private key, then reuse
|
|
21
|
+
* {@link authorityKeyFromLibp2p} so the same seed→public derivation used for node
|
|
22
|
+
* authority keys yields a stable `{ privateKeyB64, publicKeyB64 }` whose public
|
|
23
|
+
* key a later strand signature verifies against.
|
|
24
|
+
*
|
|
25
|
+
* @param memberPrivateKey - The strand's `MemberPrivateKey` (base64 protobuf).
|
|
26
|
+
* @returns The base64url seed/public-key pair for founding membership/authority.
|
|
27
|
+
* @throws If the decoded key is not Ed25519 or the raw bytes are malformed.
|
|
28
|
+
*/
|
|
29
|
+
export declare function strandMemberKeyPair(memberPrivateKey: string): AuthorityKeyPair;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { generateKeyPair, privateKeyToProtobuf, privateKeyFromProtobuf } from '@libp2p/crypto/keys';
|
|
2
|
+
import { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';
|
|
3
|
+
import { authorityKeyFromLibp2p } from './authority-key.js';
|
|
4
|
+
/**
|
|
5
|
+
* Mint a fresh ed25519 strand member private key, encoded as base64 protobuf.
|
|
6
|
+
*
|
|
7
|
+
* Closed strands (`Type:'c'`) gate membership on a `MemberPrivateKey` column
|
|
8
|
+
* (see the `Strand` control-schema table). This produces one in the SAME
|
|
9
|
+
* encoding {@link StrandSolicitationService.formStrand} uses for its
|
|
10
|
+
* `invitePrivateKey`, so a host-minted key and a formation-issued key are
|
|
11
|
+
* interchangeable when attaching a closed strand.
|
|
12
|
+
*/
|
|
13
|
+
export async function generateStrandMemberKey() {
|
|
14
|
+
const privateKey = await generateKeyPair('Ed25519');
|
|
15
|
+
return uint8ArrayToString(privateKeyToProtobuf(privateKey), 'base64');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Bridge a strand's `MemberPrivateKey` (base64 protobuf libp2p ed25519 key, as
|
|
19
|
+
* minted by {@link generateStrandMemberKey} or issued by formation) into the
|
|
20
|
+
* base64url keypair the strand RBAC constraints consume.
|
|
21
|
+
*
|
|
22
|
+
* The `Strand` control-layer `MemberPrivateKey` is the closed-strand read-gating
|
|
23
|
+
* secret; the founding `Member.Key`/`Authority.MemberKey` (the strand RBAC layer)
|
|
24
|
+
* are *derived from it* — they are the `publicKeyB64` of the keypair this returns.
|
|
25
|
+
* Decode the protobuf to a libp2p private key, then reuse
|
|
26
|
+
* {@link authorityKeyFromLibp2p} so the same seed→public derivation used for node
|
|
27
|
+
* authority keys yields a stable `{ privateKeyB64, publicKeyB64 }` whose public
|
|
28
|
+
* key a later strand signature verifies against.
|
|
29
|
+
*
|
|
30
|
+
* @param memberPrivateKey - The strand's `MemberPrivateKey` (base64 protobuf).
|
|
31
|
+
* @returns The base64url seed/public-key pair for founding membership/authority.
|
|
32
|
+
* @throws If the decoded key is not Ed25519 or the raw bytes are malformed.
|
|
33
|
+
*/
|
|
34
|
+
export function strandMemberKeyPair(memberPrivateKey) {
|
|
35
|
+
const privateKey = privateKeyFromProtobuf(uint8ArrayFromString(memberPrivateKey, 'base64'));
|
|
36
|
+
return authorityKeyFromLibp2p(privateKey);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=strand-member-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strand-member-key.js","sourceRoot":"","sources":["../src/strand-member-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,UAAU,IAAI,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACjG,OAAO,EAAE,sBAAsB,EAAyB,MAAM,oBAAoB,CAAC;AAEnF;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,kBAAkB,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CAAC,gBAAwB;IAC1D,MAAM,UAAU,GAAG,sBAAsB,CAAC,oBAAoB,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC5F,OAAO,sBAAsB,CAAC,UAAU,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Database } from '@quereus/quereus';
|
|
2
|
+
import type { MemberRegistration } from './types.js';
|
|
3
|
+
import type { MemberRegistry, MemberVerifier } from './enrollment.js';
|
|
4
|
+
import type { AuthorityKeyPair } from './authority-key.js';
|
|
5
|
+
/**
|
|
6
|
+
* The canonical payload a joining member signs to prove ownership of its strand
|
|
7
|
+
* member key, verified by {@link StrandMemberVerifier.verifyMember}.
|
|
8
|
+
*
|
|
9
|
+
* Binds the member key to the strand id so a registration signature cannot be
|
|
10
|
+
* replayed against a different strand. Peer-id binding is intentionally NOT part
|
|
11
|
+
* of this payload — `MemberPeer` rows carry their OWN per-peer self-proofs
|
|
12
|
+
* (`MemberKey || '|' || PeerId`, signed by the member) and are written by the
|
|
13
|
+
* dedicated `registerMemberPeer` writer; see {@link StrandMemberRegistry}.
|
|
14
|
+
*/
|
|
15
|
+
export declare function memberRegistrationPayload(registration: MemberRegistration): string;
|
|
16
|
+
/** How a {@link StrandMemberRegistry} admits a member into the strand. */
|
|
17
|
+
export type StrandAdmission = {
|
|
18
|
+
/** Redeem a specific single-use invite (the invitee-side join). */
|
|
19
|
+
readonly mode: 'invite';
|
|
20
|
+
/** The `Invite.Key` (invite public key, base64url) to redeem. */
|
|
21
|
+
readonly inviteKey: string;
|
|
22
|
+
/** The matching invite private seed (base64url), received out-of-band. */
|
|
23
|
+
readonly invitePrivateKey: string;
|
|
24
|
+
} | {
|
|
25
|
+
/** Admit directly by authority signature (the authority-side join). */
|
|
26
|
+
readonly mode: 'authority';
|
|
27
|
+
/** The admitting authority's strand keypair. */
|
|
28
|
+
readonly authorityKeyPair: AuthorityKeyPair;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Strand-`Database`-backed {@link MemberVerifier} — the reconciliation that lets
|
|
32
|
+
* {@link EnrollmentService} make real authorization decisions against a strand's
|
|
33
|
+
* `Invite`/`ConsumedInvite`/`Member` tables instead of a placeholder.
|
|
34
|
+
*
|
|
35
|
+
* Each instance is scoped to ONE strand's database (the `Strand.*` tables live
|
|
36
|
+
* inside the connected strand DB), so the `strandId` arguments on the interface
|
|
37
|
+
* are accepted for signature compatibility but the queries run against this db.
|
|
38
|
+
*/
|
|
39
|
+
export declare class StrandMemberVerifier implements MemberVerifier {
|
|
40
|
+
private readonly db;
|
|
41
|
+
constructor(db: Database);
|
|
42
|
+
/**
|
|
43
|
+
* Verify the member's self-proof over {@link memberRegistrationPayload} against
|
|
44
|
+
* the registration's own `key` (the member's ed25519 public key). This is the
|
|
45
|
+
* off-engine counterpart to the on-engine signature checks — a pre-flight gate
|
|
46
|
+
* before the constraint-enforced write.
|
|
47
|
+
*/
|
|
48
|
+
verifyMember(registration: MemberRegistration, signature: string): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* A member is authorized to join when either it has already consumed an invite
|
|
51
|
+
* (a `ConsumedInvite` row bears its key) OR the strand has at least one
|
|
52
|
+
* outstanding `Invite` it could redeem.
|
|
53
|
+
*
|
|
54
|
+
* Strand invites are anonymous — `Invite.Key` is an invite public key, not a
|
|
55
|
+
* member key, so an un-consumed invite cannot be bound to a specific member
|
|
56
|
+
* here. This check is therefore a "door is open" pre-flight; the binding,
|
|
57
|
+
* single-use, and cryptographic gates are all enforced by the deferred
|
|
58
|
+
* `Strand.*` constraints when the member is actually written.
|
|
59
|
+
*
|
|
60
|
+
* Expired invites are filtered out so this pre-flight matches the on-engine
|
|
61
|
+
* `ConsumedInvite.NotExpired` gate — `Now` is canonicalised the same way the
|
|
62
|
+
* writer canonicalises it, so `Expiration > ?` compares like-for-like canonical
|
|
63
|
+
* strings. A member that already holds a `ConsumedInvite` short-circuits above,
|
|
64
|
+
* regardless of any invite's expiry.
|
|
65
|
+
*/
|
|
66
|
+
isAuthorizedToJoin(_strandId: string, memberKey: string): Promise<boolean>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Strand-`Database`-backed {@link MemberRegistry} — writes real `Strand.Member`
|
|
70
|
+
* (and, in invite mode, `Strand.ConsumedInvite`) rows through the signed writer
|
|
71
|
+
* primitives, so {@link EnrollmentService.registerMember} performs the actual
|
|
72
|
+
* per-strand join handshake rather than returning "MemberRegistry not configured".
|
|
73
|
+
*
|
|
74
|
+
* The {@link StrandAdmission} chosen at construction selects which branch of
|
|
75
|
+
* `Member.Authorized` is taken:
|
|
76
|
+
* - `invite` → {@link consumeInvite} (atomic `Member` + `ConsumedInvite`), the
|
|
77
|
+
* invitee-side flow that redeems a single-use invite.
|
|
78
|
+
* - `authority` → {@link addMemberByAuthority}, the authority-side flow that
|
|
79
|
+
* seats a member already trusted by an authority.
|
|
80
|
+
*
|
|
81
|
+
* Scoped to one strand's db; the `strandId` argument is accepted for interface
|
|
82
|
+
* compatibility but writes target this db's `Strand.*` tables.
|
|
83
|
+
*
|
|
84
|
+
* NOTE: `peerIds` are NOT written as `MemberPeer` rows here. The signed peer path
|
|
85
|
+
* now exists as the standalone `registerMemberPeer` writer (each peer self-proof is
|
|
86
|
+
* signed by the member's own key), but wiring the enrollment `peerIds` through it is
|
|
87
|
+
* a deliberate follow-on — the member self-proof available here does not carry the
|
|
88
|
+
* per-peer signatures `MemberPeer.Authorized` requires. A non-empty `peerIds` is
|
|
89
|
+
* logged and otherwise ignored so the member still lands; peer rows are reconciled
|
|
90
|
+
* separately via `registerMemberPeer`.
|
|
91
|
+
*/
|
|
92
|
+
export declare class StrandMemberRegistry implements MemberRegistry {
|
|
93
|
+
private readonly db;
|
|
94
|
+
private readonly admission;
|
|
95
|
+
constructor(db: Database, admission: StrandAdmission);
|
|
96
|
+
registerMember(strandId: string, memberKey: string, peerIds: string[]): Promise<void>;
|
|
97
|
+
isMemberRegistered(_strandId: string, memberKey: string): Promise<boolean>;
|
|
98
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
import { verifyStrandPayload, consumeInvite, addMemberByAuthority, } from './strand-membership-writer.js';
|
|
3
|
+
import { canonicalDatetime } from './canonical-datetime.js';
|
|
4
|
+
const log = debug('sereus:cadre:strand-member-registry');
|
|
5
|
+
/**
|
|
6
|
+
* The canonical payload a joining member signs to prove ownership of its strand
|
|
7
|
+
* member key, verified by {@link StrandMemberVerifier.verifyMember}.
|
|
8
|
+
*
|
|
9
|
+
* Binds the member key to the strand id so a registration signature cannot be
|
|
10
|
+
* replayed against a different strand. Peer-id binding is intentionally NOT part
|
|
11
|
+
* of this payload — `MemberPeer` rows carry their OWN per-peer self-proofs
|
|
12
|
+
* (`MemberKey || '|' || PeerId`, signed by the member) and are written by the
|
|
13
|
+
* dedicated `registerMemberPeer` writer; see {@link StrandMemberRegistry}.
|
|
14
|
+
*/
|
|
15
|
+
export function memberRegistrationPayload(registration) {
|
|
16
|
+
return `${registration.strandId}|${registration.key}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Strand-`Database`-backed {@link MemberVerifier} — the reconciliation that lets
|
|
20
|
+
* {@link EnrollmentService} make real authorization decisions against a strand's
|
|
21
|
+
* `Invite`/`ConsumedInvite`/`Member` tables instead of a placeholder.
|
|
22
|
+
*
|
|
23
|
+
* Each instance is scoped to ONE strand's database (the `Strand.*` tables live
|
|
24
|
+
* inside the connected strand DB), so the `strandId` arguments on the interface
|
|
25
|
+
* are accepted for signature compatibility but the queries run against this db.
|
|
26
|
+
*/
|
|
27
|
+
export class StrandMemberVerifier {
|
|
28
|
+
constructor(db) {
|
|
29
|
+
this.db = db;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Verify the member's self-proof over {@link memberRegistrationPayload} against
|
|
33
|
+
* the registration's own `key` (the member's ed25519 public key). This is the
|
|
34
|
+
* off-engine counterpart to the on-engine signature checks — a pre-flight gate
|
|
35
|
+
* before the constraint-enforced write.
|
|
36
|
+
*/
|
|
37
|
+
async verifyMember(registration, signature) {
|
|
38
|
+
const ok = verifyStrandPayload(memberRegistrationPayload(registration), signature, registration.key);
|
|
39
|
+
if (!ok) {
|
|
40
|
+
log('verifyMember: bad self-proof for member %s on strand %s', registration.key, registration.strandId);
|
|
41
|
+
}
|
|
42
|
+
return ok;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A member is authorized to join when either it has already consumed an invite
|
|
46
|
+
* (a `ConsumedInvite` row bears its key) OR the strand has at least one
|
|
47
|
+
* outstanding `Invite` it could redeem.
|
|
48
|
+
*
|
|
49
|
+
* Strand invites are anonymous — `Invite.Key` is an invite public key, not a
|
|
50
|
+
* member key, so an un-consumed invite cannot be bound to a specific member
|
|
51
|
+
* here. This check is therefore a "door is open" pre-flight; the binding,
|
|
52
|
+
* single-use, and cryptographic gates are all enforced by the deferred
|
|
53
|
+
* `Strand.*` constraints when the member is actually written.
|
|
54
|
+
*
|
|
55
|
+
* Expired invites are filtered out so this pre-flight matches the on-engine
|
|
56
|
+
* `ConsumedInvite.NotExpired` gate — `Now` is canonicalised the same way the
|
|
57
|
+
* writer canonicalises it, so `Expiration > ?` compares like-for-like canonical
|
|
58
|
+
* strings. A member that already holds a `ConsumedInvite` short-circuits above,
|
|
59
|
+
* regardless of any invite's expiry.
|
|
60
|
+
*/
|
|
61
|
+
async isAuthorizedToJoin(_strandId, memberKey) {
|
|
62
|
+
const consumed = await scalarCount(this.db, 'select count(1) as c from Strand.ConsumedInvite where MemberKey = ?', [memberKey]);
|
|
63
|
+
if (consumed > 0) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const nowCanonical = await canonicalDatetime(this.db, Date.now());
|
|
67
|
+
const invites = await scalarCount(this.db, 'select count(1) as c from Strand.Invite where Expiration is null or Expiration > ?', [nowCanonical]);
|
|
68
|
+
return invites > 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Strand-`Database`-backed {@link MemberRegistry} — writes real `Strand.Member`
|
|
73
|
+
* (and, in invite mode, `Strand.ConsumedInvite`) rows through the signed writer
|
|
74
|
+
* primitives, so {@link EnrollmentService.registerMember} performs the actual
|
|
75
|
+
* per-strand join handshake rather than returning "MemberRegistry not configured".
|
|
76
|
+
*
|
|
77
|
+
* The {@link StrandAdmission} chosen at construction selects which branch of
|
|
78
|
+
* `Member.Authorized` is taken:
|
|
79
|
+
* - `invite` → {@link consumeInvite} (atomic `Member` + `ConsumedInvite`), the
|
|
80
|
+
* invitee-side flow that redeems a single-use invite.
|
|
81
|
+
* - `authority` → {@link addMemberByAuthority}, the authority-side flow that
|
|
82
|
+
* seats a member already trusted by an authority.
|
|
83
|
+
*
|
|
84
|
+
* Scoped to one strand's db; the `strandId` argument is accepted for interface
|
|
85
|
+
* compatibility but writes target this db's `Strand.*` tables.
|
|
86
|
+
*
|
|
87
|
+
* NOTE: `peerIds` are NOT written as `MemberPeer` rows here. The signed peer path
|
|
88
|
+
* now exists as the standalone `registerMemberPeer` writer (each peer self-proof is
|
|
89
|
+
* signed by the member's own key), but wiring the enrollment `peerIds` through it is
|
|
90
|
+
* a deliberate follow-on — the member self-proof available here does not carry the
|
|
91
|
+
* per-peer signatures `MemberPeer.Authorized` requires. A non-empty `peerIds` is
|
|
92
|
+
* logged and otherwise ignored so the member still lands; peer rows are reconciled
|
|
93
|
+
* separately via `registerMemberPeer`.
|
|
94
|
+
*/
|
|
95
|
+
export class StrandMemberRegistry {
|
|
96
|
+
constructor(db, admission) {
|
|
97
|
+
this.db = db;
|
|
98
|
+
this.admission = admission;
|
|
99
|
+
}
|
|
100
|
+
async registerMember(strandId, memberKey, peerIds) {
|
|
101
|
+
if (peerIds.length > 0) {
|
|
102
|
+
log('registerMember: %d peerId(s) for member %s on strand %s deferred to the registerMemberPeer path (needs per-peer self-proofs); registering member only', peerIds.length, memberKey, strandId);
|
|
103
|
+
}
|
|
104
|
+
if (this.admission.mode === 'invite') {
|
|
105
|
+
await consumeInvite(this.db, {
|
|
106
|
+
inviteKey: this.admission.inviteKey,
|
|
107
|
+
invitePrivateKey: this.admission.invitePrivateKey,
|
|
108
|
+
memberKey,
|
|
109
|
+
});
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await addMemberByAuthority(this.db, {
|
|
113
|
+
authorityKeyPair: this.admission.authorityKeyPair,
|
|
114
|
+
memberKey,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async isMemberRegistered(_strandId, memberKey) {
|
|
118
|
+
const count = await scalarCount(this.db, 'select count(1) as c from Strand.Member where Key = ?', [memberKey]);
|
|
119
|
+
return count > 0;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Run a `select count(1) as c ...` and return the scalar (0 if no row). */
|
|
123
|
+
async function scalarCount(db, sql, params) {
|
|
124
|
+
for await (const row of db.eval(sql, params)) {
|
|
125
|
+
return row.c ?? 0;
|
|
126
|
+
}
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=strand-member-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strand-member-registry.js","sourceRoot":"","sources":["../src/strand-member-registry.ts"],"names":[],"mappings":"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"}
|