@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.
- package/README.md +11 -10
- package/dist/cadre-node.d.ts +818 -19
- package/dist/cadre-node.js +1952 -99
- 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 +279 -8
- package/dist/control-database.js +563 -134
- 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 +223 -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/ed25519-key.d.ts +42 -0
- package/dist/ed25519-key.js +51 -0
- package/dist/ed25519-key.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 +48 -0
- package/dist/peer-authorization.js +66 -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-node.d.ts +38 -0
- package/dist/push-node.js +48 -0
- package/dist/push-node.js.map +1 -0
- package/dist/push-notifier-apns.d.ts +61 -0
- package/dist/push-notifier-apns.js +199 -0
- package/dist/push-notifier-apns.js.map +1 -0
- package/dist/push-notifier-fcm.d.ts +44 -0
- package/dist/push-notifier-fcm.js +182 -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 +52 -0
- package/dist/push-notifier.js +22 -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 +228 -21
- package/dist/seed-bootstrap.js +489 -168
- 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 -100
- 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 +436 -33
- package/dist/types.js +22 -5
- package/dist/types.js.map +1 -1
- package/package.json +18 -8
- package/src/cadre-node.ts +2981 -948
- 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 +948 -383
- package/src/control-formation-recorder.ts +151 -0
- package/src/control-schema.ts +222 -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/ed25519-key.ts +69 -0
- package/src/hibernation-manager.ts +197 -28
- package/src/index.ts +275 -79
- package/src/key-store-file.ts +203 -0
- package/src/key-store.ts +99 -0
- package/src/peer-authorization.ts +81 -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-node.ts +64 -0
- package/src/push-notifier-apns.ts +247 -0
- package/src/push-notifier-fcm.ts +220 -0
- package/src/push-notifier-shared.ts +28 -0
- package/src/push-notifier.ts +52 -0
- package/src/schema-verification.ts +18 -4
- package/src/seed-bootstrap.ts +563 -223
- 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 -141
- 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 +1032 -582
|
@@ -16,6 +16,17 @@ export interface HibernationCallbacks {
|
|
|
16
16
|
onIdle: (strandId: string) => Promise<void>;
|
|
17
17
|
onHibernate: (strandId: string) => Promise<void>;
|
|
18
18
|
onWake: (strandId: string) => Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Perform a real cohort check-in for a hibernating strand. The implementation
|
|
21
|
+
* (`CadreNode.handleStrandCheckIn`) resumes the strand, gives it a bounded
|
|
22
|
+
* window to connect to reachable cohort peers and surface pending activity,
|
|
23
|
+
* then re-hibernates if still idle. The manager AWAITS this before scheduling
|
|
24
|
+
* the next (longer-delayed) check-in, so a slow check-in never overlaps the
|
|
25
|
+
* next tick. After it resolves the manager inspects `instance.status`: a
|
|
26
|
+
* strand left non-`hibernating` is treated as woken (backoff resets on the
|
|
27
|
+
* next hibernation); a strand left `hibernating` escalates the backoff.
|
|
28
|
+
*/
|
|
29
|
+
onCheckIn: (strandId: string) => Promise<void>;
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
/**
|
|
@@ -31,7 +42,20 @@ export class HibernationManager {
|
|
|
31
42
|
private readonly config: HibernationConfig;
|
|
32
43
|
private readonly callbacks: HibernationCallbacks;
|
|
33
44
|
private readonly timers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
34
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Pending check-in timers, one per hibernating strand. Unlike the old fixed
|
|
47
|
+
* `setInterval`, these are single-shot `setTimeout`s rescheduled by
|
|
48
|
+
* {@link runCheckIn} with an escalating delay — so a long-running `onCheckIn`
|
|
49
|
+
* can never overlap the next tick, and the period adapts to the backoff.
|
|
50
|
+
*/
|
|
51
|
+
private readonly checkInTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
52
|
+
/**
|
|
53
|
+
* In-flight wake promises keyed by strandId. Coalesces overlapping wake
|
|
54
|
+
* triggers (two near-simultaneous activities, or activity racing a force wake)
|
|
55
|
+
* so `onWake` — and the libp2p-node rebuild it drives — runs at most once per
|
|
56
|
+
* concurrent wake.
|
|
57
|
+
*/
|
|
58
|
+
private readonly wakePromises: Map<string, Promise<void>> = new Map();
|
|
35
59
|
private running = false;
|
|
36
60
|
|
|
37
61
|
constructor(config: HibernationConfig, callbacks: HibernationCallbacks) {
|
|
@@ -48,11 +72,13 @@ export class HibernationManager {
|
|
|
48
72
|
const custom = this.config.customTimeouts?.[hint];
|
|
49
73
|
|
|
50
74
|
if (!custom) return defaults;
|
|
51
|
-
|
|
75
|
+
|
|
52
76
|
return {
|
|
53
77
|
idleTimeout: custom.idleTimeout ?? defaults.idleTimeout,
|
|
54
78
|
hibernateTimeout: custom.hibernateTimeout ?? defaults.hibernateTimeout,
|
|
55
|
-
checkInInterval: custom.checkInInterval ?? defaults.checkInInterval
|
|
79
|
+
checkInInterval: custom.checkInInterval ?? defaults.checkInInterval,
|
|
80
|
+
checkInBackoffFactor: custom.checkInBackoffFactor ?? defaults.checkInBackoffFactor,
|
|
81
|
+
checkInMaxInterval: custom.checkInMaxInterval ?? defaults.checkInMaxInterval
|
|
56
82
|
};
|
|
57
83
|
}
|
|
58
84
|
|
|
@@ -79,13 +105,58 @@ export class HibernationManager {
|
|
|
79
105
|
this.timers.clear();
|
|
80
106
|
|
|
81
107
|
for (const timer of this.checkInTimers.values()) {
|
|
82
|
-
|
|
108
|
+
clearTimeout(timer);
|
|
83
109
|
}
|
|
84
110
|
this.checkInTimers.clear();
|
|
85
|
-
|
|
111
|
+
|
|
112
|
+
// In-flight wakes clean themselves up via their finally; drop the references
|
|
113
|
+
// so a fresh start coalesces cleanly.
|
|
114
|
+
this.wakePromises.clear();
|
|
115
|
+
|
|
86
116
|
log('HibernationManager stopped');
|
|
87
117
|
}
|
|
88
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Whether a strand with this instance's latency hint ever hibernates — `false`
|
|
121
|
+
* for realtime (Infinity idle timeout, see {@link HIBERNATION_TIMEOUTS}), also
|
|
122
|
+
* honouring any per-hint `customTimeouts` override. Imperative callers
|
|
123
|
+
* (`CadreNode.hibernateStrand` / `hibernateAll`) use this as the single source
|
|
124
|
+
* of truth for "skip realtime", consistent with {@link trackStrand} declining
|
|
125
|
+
* to track Infinity-timeout strands.
|
|
126
|
+
*/
|
|
127
|
+
hibernates(instance: StrandInstance): boolean {
|
|
128
|
+
return this.getTimeouts(instance.latencyHint).idleTimeout !== Infinity;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Imperatively hibernate a tracked strand now, bypassing the idle/hibernate
|
|
133
|
+
* timers — the mobile background-entry path. Cancels the strand's pending
|
|
134
|
+
* idle/hibernate AND check-in timers so none can later re-fire `onHibernate`
|
|
135
|
+
* on the already-quiesced strand or resurrect one the caller means to keep
|
|
136
|
+
* down, then invokes `onHibernate` (quiesce + mark hibernating).
|
|
137
|
+
*
|
|
138
|
+
* Unlike the timer path ({@link handleHibernateTimeout}) it deliberately does
|
|
139
|
+
* NOT re-arm the check-in chain: a force-hibernate keeps the strand down until
|
|
140
|
+
* the caller drives the next wake on demand (push-delivered on mobile), so a
|
|
141
|
+
* stray check-in timer must not bring it back up.
|
|
142
|
+
*
|
|
143
|
+
* @returns `true` if the strand was hibernated, `false` (no-op) for a realtime
|
|
144
|
+
* strand that never hibernates.
|
|
145
|
+
*/
|
|
146
|
+
async forceHibernate(instance: StrandInstance): Promise<boolean> {
|
|
147
|
+
if (!this.hibernates(instance)) {
|
|
148
|
+
log('forceHibernate: strand %s is realtime; no-op', instance.strandId);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
// Cancel idle/hibernate + check-in timers BEFORE quiescing so nothing fights
|
|
152
|
+
// the imperative hibernate (a stale hibernate timer firing on the quiesced
|
|
153
|
+
// strand, or a check-in resuming a strand the caller wants kept down).
|
|
154
|
+
this.clearTimers(instance.strandId);
|
|
155
|
+
await this.callbacks.onHibernate(instance.strandId);
|
|
156
|
+
log('forceHibernate: strand %s hibernated (timers cancelled, not re-armed)', instance.strandId);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
89
160
|
/**
|
|
90
161
|
* Register a strand for hibernation management
|
|
91
162
|
*/
|
|
@@ -122,13 +193,33 @@ export class HibernationManager {
|
|
|
122
193
|
const { strandId, status, latencyHint } = instance;
|
|
123
194
|
instance.lastActivity = new Date();
|
|
124
195
|
|
|
125
|
-
// If idle or hibernating, wake up
|
|
196
|
+
// If idle or hibernating, wake up. Coalesce so two near-simultaneous
|
|
197
|
+
// activities don't each fire onWake (which would rebuild two libp2p nodes).
|
|
126
198
|
if (status === 'idle' || status === 'hibernating') {
|
|
127
199
|
log('Activity on %s strand %s - waking', status, strandId);
|
|
128
200
|
this.clearTimers(strandId);
|
|
129
|
-
|
|
201
|
+
// Fire-and-forget; force-wake awaiters see errors, so swallow (and log)
|
|
202
|
+
// here to avoid an unhandled rejection on this best-effort path.
|
|
203
|
+
//
|
|
204
|
+
// Once the wake settles (CadreNode rebuilds the runtime and marks the
|
|
205
|
+
// strand `active`), re-arm the idle→hibernate→check-in cycle. Without this
|
|
206
|
+
// a strand that wakes then goes quiet stays `active` forever and never
|
|
207
|
+
// re-hibernates — so the check-in backoff could never restart at base.
|
|
208
|
+
// Guarded on `active` so a wake that did not transition (a coalesced
|
|
209
|
+
// no-op, or a still-mid-flight rebuild) never arms a stray timer.
|
|
210
|
+
void this.beginWake(strandId).then(() => {
|
|
211
|
+
if (this.running && instance.status === 'active') {
|
|
212
|
+
const timeouts = this.getTimeouts(instance.latencyHint);
|
|
213
|
+
if (timeouts.idleTimeout !== Infinity) {
|
|
214
|
+
this.scheduleIdleTransition(instance);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}).catch((err) => {
|
|
218
|
+
log('Activity-driven wake failed for strand %s: %o', strandId, err);
|
|
219
|
+
});
|
|
220
|
+
return;
|
|
130
221
|
}
|
|
131
|
-
|
|
222
|
+
|
|
132
223
|
// Reschedule idle transition if active
|
|
133
224
|
if (status === 'active') {
|
|
134
225
|
const timeouts = this.getTimeouts(latencyHint);
|
|
@@ -143,7 +234,30 @@ export class HibernationManager {
|
|
|
143
234
|
*/
|
|
144
235
|
async wakeStrand(strandId: string): Promise<void> {
|
|
145
236
|
this.clearTimers(strandId);
|
|
146
|
-
await this.
|
|
237
|
+
await this.beginWake(strandId);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Begin a wake for a strand, or coalesce with one already in flight. Ensures
|
|
242
|
+
* `onWake` runs at most once per concurrent wake — the returned promise is
|
|
243
|
+
* shared by all overlapping callers and cleared once it settles. Force-wake
|
|
244
|
+
* callers await it; activity-driven callers fire-and-forget.
|
|
245
|
+
*/
|
|
246
|
+
private beginWake(strandId: string): Promise<void> {
|
|
247
|
+
const existing = this.wakePromises.get(strandId);
|
|
248
|
+
if (existing) {
|
|
249
|
+
return existing;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const wake = (async () => {
|
|
253
|
+
try {
|
|
254
|
+
await this.callbacks.onWake(strandId);
|
|
255
|
+
} finally {
|
|
256
|
+
this.wakePromises.delete(strandId);
|
|
257
|
+
}
|
|
258
|
+
})();
|
|
259
|
+
this.wakePromises.set(strandId, wake);
|
|
260
|
+
return wake;
|
|
147
261
|
}
|
|
148
262
|
|
|
149
263
|
private scheduleIdleTransition(instance: StrandInstance): void {
|
|
@@ -168,13 +282,16 @@ export class HibernationManager {
|
|
|
168
282
|
|
|
169
283
|
log('Idle timeout for strand %s', strandId);
|
|
170
284
|
|
|
171
|
-
// Transition to idle
|
|
285
|
+
// Transition to idle. The callback can reject (e.g. a future idle handler
|
|
286
|
+
// that releases resources); catch so the timer chain never unhandled-rejects.
|
|
172
287
|
void this.callbacks.onIdle(strandId).then(() => {
|
|
173
288
|
// Schedule hibernate transition
|
|
174
289
|
const timeouts = this.getTimeouts(latencyHint);
|
|
175
290
|
if (timeouts.hibernateTimeout !== Infinity) {
|
|
176
291
|
this.scheduleHibernateTransition(instance);
|
|
177
292
|
}
|
|
293
|
+
}).catch((err) => {
|
|
294
|
+
log('onIdle failed for strand %s: %o', strandId, err);
|
|
178
295
|
});
|
|
179
296
|
}
|
|
180
297
|
|
|
@@ -200,42 +317,94 @@ export class HibernationManager {
|
|
|
200
317
|
|
|
201
318
|
log('Hibernate timeout for strand %s', strandId);
|
|
202
319
|
|
|
203
|
-
// Transition to hibernating
|
|
320
|
+
// Transition to hibernating. onHibernate now releases strand-network
|
|
321
|
+
// resources (quiesce), so its close()/stop() can reject — catch so a failed
|
|
322
|
+
// hibernate logs instead of producing an unhandled rejection.
|
|
204
323
|
void this.callbacks.onHibernate(strandId).then(() => {
|
|
205
324
|
// Schedule periodic check-ins
|
|
206
325
|
const timeouts = this.getTimeouts(latencyHint);
|
|
207
326
|
if (timeouts.checkInInterval !== Infinity) {
|
|
208
327
|
this.scheduleCheckIn(instance);
|
|
209
328
|
}
|
|
329
|
+
}).catch((err) => {
|
|
330
|
+
log('onHibernate failed for strand %s: %o', strandId, err);
|
|
210
331
|
});
|
|
211
332
|
}
|
|
212
333
|
|
|
213
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Arm the next check-in for a hibernating strand. Each call is a single-shot
|
|
336
|
+
* `setTimeout` (not a fixed `setInterval`) so the period escalates per
|
|
337
|
+
* {@link runCheckIn} and a slow `onCheckIn` never overlaps the next tick.
|
|
338
|
+
*
|
|
339
|
+
* `delay` is omitted by the chain start ({@link handleHibernateTimeout}),
|
|
340
|
+
* defaulting to the base `checkInInterval` — which is why backoff naturally
|
|
341
|
+
* resets to base each fresh hibernation cycle, with no per-strand counter to
|
|
342
|
+
* clear on wake. Subsequent ticks pass the escalated, capped delay.
|
|
343
|
+
*/
|
|
344
|
+
private scheduleCheckIn(instance: StrandInstance, delay?: number): void {
|
|
214
345
|
const { strandId, latencyHint } = instance;
|
|
215
346
|
const timeouts = this.getTimeouts(latencyHint);
|
|
347
|
+
const currentDelay = delay ?? timeouts.checkInInterval;
|
|
216
348
|
|
|
217
|
-
//
|
|
349
|
+
// Replace any existing check-in timer (no-op if the firing timer rescheduled us).
|
|
218
350
|
const existing = this.checkInTimers.get(strandId);
|
|
219
351
|
if (existing) {
|
|
220
|
-
|
|
352
|
+
clearTimeout(existing);
|
|
221
353
|
}
|
|
222
354
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
355
|
+
const timer = setTimeout(() => {
|
|
356
|
+
void this.runCheckIn(instance, currentDelay);
|
|
357
|
+
}, currentDelay);
|
|
358
|
+
|
|
359
|
+
this.checkInTimers.set(strandId, timer);
|
|
360
|
+
instance.nextCheckIn = new Date(Date.now() + currentDelay);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Run a single check-in tick: invoke `onCheckIn` (a real resume → bounded
|
|
365
|
+
* sync window → re-hibernate-if-idle cycle in `CadreNode`) and AWAIT it before
|
|
366
|
+
* deciding the next step.
|
|
367
|
+
*
|
|
368
|
+
* - If the strand woke during the check-in (`onCheckIn` left it non-
|
|
369
|
+
* `hibernating`), stop the chain — the activity path has re-armed the
|
|
370
|
+
* idle/hibernate timers, and the next hibernation restarts the chain at the
|
|
371
|
+
* base delay (backoff reset).
|
|
372
|
+
* - Otherwise escalate the delay by `checkInBackoffFactor`, capped at
|
|
373
|
+
* `checkInMaxInterval`, and reschedule.
|
|
374
|
+
*/
|
|
375
|
+
private async runCheckIn(instance: StrandInstance, currentDelay: number): Promise<void> {
|
|
376
|
+
const { strandId, latencyHint } = instance;
|
|
377
|
+
if (!this.running) return;
|
|
229
378
|
|
|
230
|
-
|
|
231
|
-
instance.nextCheckIn = new Date(Date.now() + timeouts.checkInInterval);
|
|
379
|
+
log('Check-in for hibernating strand %s (delay=%dms)', strandId, currentDelay);
|
|
232
380
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
381
|
+
try {
|
|
382
|
+
await this.callbacks.onCheckIn(strandId);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
// A failed check-in (e.g. resume threw) must not break the chain — log and
|
|
385
|
+
// fall through to reschedule the next, longer-delayed attempt.
|
|
386
|
+
log('onCheckIn failed for strand %s: %o', strandId, err);
|
|
387
|
+
}
|
|
236
388
|
|
|
237
|
-
this.
|
|
238
|
-
|
|
389
|
+
if (!this.running) return;
|
|
390
|
+
|
|
391
|
+
// The check-in either woke the strand (CadreNode left it active) or left it
|
|
392
|
+
// hibernating. Inspect the shared instance the callback just mutated.
|
|
393
|
+
if (instance.status !== 'hibernating') {
|
|
394
|
+
log('Check-in woke strand %s; backoff resets on next hibernation', strandId);
|
|
395
|
+
this.checkInTimers.delete(strandId);
|
|
396
|
+
// The chain is stopping; drop the now-stale next-check-in advertisement so
|
|
397
|
+
// `getStrand` doesn't report a phantom check-in for a strand that is awake.
|
|
398
|
+
instance.nextCheckIn = undefined;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const timeouts = this.getTimeouts(latencyHint);
|
|
403
|
+
const nextDelay = Math.min(
|
|
404
|
+
currentDelay * timeouts.checkInBackoffFactor,
|
|
405
|
+
timeouts.checkInMaxInterval
|
|
406
|
+
);
|
|
407
|
+
this.scheduleCheckIn(instance, nextDelay);
|
|
239
408
|
}
|
|
240
409
|
|
|
241
410
|
private clearTimer(strandId: string): void {
|
|
@@ -251,7 +420,7 @@ export class HibernationManager {
|
|
|
251
420
|
|
|
252
421
|
const checkInTimer = this.checkInTimers.get(strandId);
|
|
253
422
|
if (checkInTimer) {
|
|
254
|
-
|
|
423
|
+
clearTimeout(checkInTimer);
|
|
255
424
|
this.checkInTimers.delete(strandId);
|
|
256
425
|
}
|
|
257
426
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,79 +1,275 @@
|
|
|
1
|
-
// Core types
|
|
2
|
-
export * from './types.js';
|
|
3
|
-
|
|
4
|
-
//
|
|
5
|
-
export {
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
export {
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
export {
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
export {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
type
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
type
|
|
44
|
-
|
|
45
|
-
} from './
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
export {
|
|
49
|
-
|
|
50
|
-
type
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
type
|
|
78
|
-
|
|
79
|
-
|
|
1
|
+
// Core types
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
|
|
4
|
+
// Canonical JSON serialization (shared signing payload format)
|
|
5
|
+
export { canonicalJson } from './canonical-json.js';
|
|
6
|
+
|
|
7
|
+
// Main CadreNode class
|
|
8
|
+
export { CadreNode } from './cadre-node.js';
|
|
9
|
+
|
|
10
|
+
// Control database
|
|
11
|
+
export { ControlDatabase, buildAuthorizationMessage, type ControlDatabaseConfig, type ControlTable } from './control-database.js';
|
|
12
|
+
|
|
13
|
+
// Ed25519 key bridge (libp2p Ed25519 -> base64url keypair)
|
|
14
|
+
export { ed25519KeyPairFromLibp2p, ed25519PublicKeyFromPrivate, type Ed25519KeyPair } from './ed25519-key.js';
|
|
15
|
+
|
|
16
|
+
// Pluggable key store (backend-agnostic identity/owner key material seam).
|
|
17
|
+
// Dependency-free: the interface, error, default slot id, and in-memory backend
|
|
18
|
+
// are safe in every (RN/browser/Node) entry graph. The Node FileKeyStore is a
|
|
19
|
+
// separate subpath module ('@serfab/cadre-core/key-store-file') so its node:fs
|
|
20
|
+
// import never lands here.
|
|
21
|
+
export {
|
|
22
|
+
InMemoryKeyStore,
|
|
23
|
+
KeyStoreAccessError,
|
|
24
|
+
DEFAULT_IDENTITY_KEY_ID,
|
|
25
|
+
type KeyStore,
|
|
26
|
+
type KeyId
|
|
27
|
+
} from './key-store.js';
|
|
28
|
+
|
|
29
|
+
// Strand database
|
|
30
|
+
export { StrandDatabase, type StrandDatabaseConfig } from './strand-database.js';
|
|
31
|
+
|
|
32
|
+
// Strand management
|
|
33
|
+
export {
|
|
34
|
+
StrandWatcher,
|
|
35
|
+
type StrandWatcherCallbacks,
|
|
36
|
+
type StrandQueryable,
|
|
37
|
+
type SAppIdLookup,
|
|
38
|
+
type StrandRowWithApp
|
|
39
|
+
} from './strand-watcher.js';
|
|
40
|
+
export {
|
|
41
|
+
StrandInstanceManager,
|
|
42
|
+
type StartStrandConfig,
|
|
43
|
+
type ResumeStrandOverrides,
|
|
44
|
+
getStrandStoragePath
|
|
45
|
+
} from './strand-instance-manager.js';
|
|
46
|
+
|
|
47
|
+
// Hibernation
|
|
48
|
+
export {
|
|
49
|
+
HibernationManager,
|
|
50
|
+
type HibernationCallbacks
|
|
51
|
+
} from './hibernation-manager.js';
|
|
52
|
+
|
|
53
|
+
// Arachnode (stub)
|
|
54
|
+
export {
|
|
55
|
+
ArachnodeStub,
|
|
56
|
+
createArachnodeStub,
|
|
57
|
+
type RingConfig
|
|
58
|
+
} from './arachnode-stub.js';
|
|
59
|
+
|
|
60
|
+
// Enrollment
|
|
61
|
+
export {
|
|
62
|
+
EnrollmentService,
|
|
63
|
+
type MemberVerifier,
|
|
64
|
+
type MemberRegistry
|
|
65
|
+
} from './enrollment.js';
|
|
66
|
+
|
|
67
|
+
// Strand Solicitation
|
|
68
|
+
export {
|
|
69
|
+
StrandSolicitationService,
|
|
70
|
+
createDefaultFormationResponseValidator,
|
|
71
|
+
type DisclosureValidator,
|
|
72
|
+
type FormationUsageRecorder,
|
|
73
|
+
type ResolvedHostStrand,
|
|
74
|
+
type StrandProvisioner,
|
|
75
|
+
type FormationSigner,
|
|
76
|
+
type FormationResponseValidator,
|
|
77
|
+
type StrandSolicitationServiceOptions
|
|
78
|
+
} from './strand-solicitation.js';
|
|
79
|
+
|
|
80
|
+
// DB-backed FormationUsageRecorder (reads/writes the real CadreControl tables)
|
|
81
|
+
export { ControlFormationUsageRecorder } from './control-formation-recorder.js';
|
|
82
|
+
|
|
83
|
+
// Closed-strand member key generation (ed25519 protobuf, base64) + the
|
|
84
|
+
// protobuf->base64url bridge that derives the founding Member/Manager key.
|
|
85
|
+
export { generateStrandMemberKey, strandMemberKeyPair } from './strand-member-key.js';
|
|
86
|
+
|
|
87
|
+
// Strand membership writer (founder bootstrap, invite issuance/consumption,
|
|
88
|
+
// manager-admit, member-peer registration, manager rotation, + shared signing
|
|
89
|
+
// primitives reused across the flows).
|
|
90
|
+
export {
|
|
91
|
+
signStrandPayload,
|
|
92
|
+
verifyStrandPayload,
|
|
93
|
+
bootstrapFounderMembership,
|
|
94
|
+
issueInvite,
|
|
95
|
+
consumeInvite,
|
|
96
|
+
addMemberByManager,
|
|
97
|
+
registerMemberPeer,
|
|
98
|
+
addManager,
|
|
99
|
+
removeManager,
|
|
100
|
+
STRAND_ENGINE,
|
|
101
|
+
STRAND_ENGINE_VERSION,
|
|
102
|
+
type FounderBootstrapParams,
|
|
103
|
+
type IssueInviteParams,
|
|
104
|
+
type IssuedInvite,
|
|
105
|
+
type ConsumeInviteParams,
|
|
106
|
+
type AddMemberByManagerParams,
|
|
107
|
+
type RegisterMemberPeerParams,
|
|
108
|
+
type AddManagerParams,
|
|
109
|
+
type RemoveManagerParams
|
|
110
|
+
} from './strand-membership-writer.js';
|
|
111
|
+
|
|
112
|
+
// Engine-canonical datetime helper (shared by control + strand signed-write flows
|
|
113
|
+
// so a signed timestamp byte-matches the datetime-coerced column the CHECK sees).
|
|
114
|
+
export { canonicalDatetime } from './canonical-datetime.js';
|
|
115
|
+
|
|
116
|
+
// Strand-DB-backed EnrollmentService backing: concrete MemberRegistry +
|
|
117
|
+
// MemberVerifier that write/read the real Strand.* membership tables.
|
|
118
|
+
export {
|
|
119
|
+
StrandMemberRegistry,
|
|
120
|
+
StrandMemberVerifier,
|
|
121
|
+
memberRegistrationPayload,
|
|
122
|
+
type StrandAdmission
|
|
123
|
+
} from './strand-member-registry.js';
|
|
124
|
+
|
|
125
|
+
// Seed Bootstrap
|
|
126
|
+
export {
|
|
127
|
+
SeedBootstrapService,
|
|
128
|
+
SEED_PROTOCOL,
|
|
129
|
+
ed25519PublicKeyB64FromPeerId,
|
|
130
|
+
type SeedBootstrapConfig,
|
|
131
|
+
type SeedEventCallbacks
|
|
132
|
+
} from './seed-bootstrap.js';
|
|
133
|
+
|
|
134
|
+
// Peer Authorization (shared owner-signature digest + offline verifier)
|
|
135
|
+
export {
|
|
136
|
+
peerAuthorizationDigest,
|
|
137
|
+
cadrePeerVoucherDigest,
|
|
138
|
+
cadrePeerRemoveDigest,
|
|
139
|
+
verifyPeerAuthorization
|
|
140
|
+
} from './peer-authorization.js';
|
|
141
|
+
|
|
142
|
+
// Strand Wake (control-network push-wake protocol)
|
|
143
|
+
export {
|
|
144
|
+
StrandWakeService,
|
|
145
|
+
dialWake,
|
|
146
|
+
WAKE_PROTOCOL,
|
|
147
|
+
type StrandWakeServiceOptions,
|
|
148
|
+
type DialWakeOptions
|
|
149
|
+
} from './strand-wake-protocol.js';
|
|
150
|
+
|
|
151
|
+
// Strand Address (control-network strand-address RPC) — wire types come via
|
|
152
|
+
// `export * from './types.js'` (StrandAddrRequest / StrandAddrResponse).
|
|
153
|
+
export {
|
|
154
|
+
StrandAddrService,
|
|
155
|
+
collectStrandAddrs,
|
|
156
|
+
STRAND_ADDR_PROTOCOL,
|
|
157
|
+
type StrandAddrServiceOptions,
|
|
158
|
+
type StrandAddrPeer,
|
|
159
|
+
type CollectStrandAddrsOptions
|
|
160
|
+
} from './strand-addr-protocol.js';
|
|
161
|
+
|
|
162
|
+
// Peer-address record (self-published, signed, freshness-stamped CadrePeer row)
|
|
163
|
+
export {
|
|
164
|
+
peerRecordSignedPayload,
|
|
165
|
+
signPeerRecord,
|
|
166
|
+
verifyPeerRecordSignature,
|
|
167
|
+
isPeerRecordFresh,
|
|
168
|
+
isSignalingAddr,
|
|
169
|
+
orderSignalingFirst,
|
|
170
|
+
currentMemberTrustPolicy,
|
|
171
|
+
DEFAULT_PEER_RECORD_MAX_AGE_MS,
|
|
172
|
+
DEFAULT_PEER_RECORD_HEARTBEAT_MS
|
|
173
|
+
} from './peer-record.js';
|
|
174
|
+
|
|
175
|
+
// Device-token record (self-published, signed FCM/APNs push token — CadrePeer sibling)
|
|
176
|
+
export {
|
|
177
|
+
deviceTokenSignedPayload,
|
|
178
|
+
signDeviceTokenRecord,
|
|
179
|
+
verifyDeviceTokenSignature,
|
|
180
|
+
isPushPlatform
|
|
181
|
+
} from './device-token.js';
|
|
182
|
+
|
|
183
|
+
// Strand-wake payload contract (canonical; shared by the server sender + RN receiver)
|
|
184
|
+
export { STRAND_WAKE_TYPE, type StrandWakePayload } from './strand-wake-payload.js';
|
|
185
|
+
|
|
186
|
+
// Platform push delivery — the cross-platform `PushNotifier` *contract* only.
|
|
187
|
+
// The FCM/APNs implementations (node:http2/node:crypto) and the
|
|
188
|
+
// `createPushNotifier` router that builds them live behind the Node-only subpath
|
|
189
|
+
// `@serfab/cadre-core/push-node` (`push-node.ts`), never in this graph. A Node
|
|
190
|
+
// host constructs a notifier from that subpath and injects the instance into
|
|
191
|
+
// `CadreNodeConfig.push.notifier`. These re-exports are type-only (erased at
|
|
192
|
+
// emit), so the RN/browser entry graph never pulls the Node modules in.
|
|
193
|
+
export type { PushNotifier, PushMessage, PushSendResult } from './push-notifier.js';
|
|
194
|
+
|
|
195
|
+
// Push-credential validation + log redaction. Dependency-free (type-only imports),
|
|
196
|
+
// so a provisioner (cadre-host / cadre-provider) can reject a partial credential
|
|
197
|
+
// set and produce a key-safe log view without pulling the FCM/APNs sender graph.
|
|
198
|
+
export { validatePushCredentials, redactPushCredentials, REDACTED } from './push-credentials.js';
|
|
199
|
+
|
|
200
|
+
// Server-side push-wake trigger policy + fan-out (who/when to wake). Cross-platform
|
|
201
|
+
// clean — it imports only the PushNotifier *type*, so exporting it as a runtime
|
|
202
|
+
// value pulls no node:http2/node:crypto edge into the RN/browser graph.
|
|
203
|
+
export {
|
|
204
|
+
PushFanoutService,
|
|
205
|
+
DEFAULT_PUSH_COOLDOWN_MS,
|
|
206
|
+
DEFAULT_PUSH_DEBOUNCE_MS,
|
|
207
|
+
type PushFanoutOptions,
|
|
208
|
+
type FanoutMember
|
|
209
|
+
} from './push-fanout.js';
|
|
210
|
+
|
|
211
|
+
// Control-cohort dial selection (backbone-preferential, bounded out-degree) +
|
|
212
|
+
// the cadence/degree defaults the proactive reconcile routine reads.
|
|
213
|
+
export {
|
|
214
|
+
selectControlCohortDials,
|
|
215
|
+
DEFAULT_CONTROL_COHORT_RECONCILE_MS,
|
|
216
|
+
DEFAULT_CONTROL_COHORT_TARGET_DEGREE,
|
|
217
|
+
type ControlCohortSelection
|
|
218
|
+
} from './control-cohort.js';
|
|
219
|
+
|
|
220
|
+
// Seed trust policy (trust anchor for incoming seeds)
|
|
221
|
+
export {
|
|
222
|
+
dbAnchoredTrustPolicy,
|
|
223
|
+
pinnedKeyTrustPolicy,
|
|
224
|
+
tofuTrustPolicy,
|
|
225
|
+
type SeedTrustPolicy,
|
|
226
|
+
type SeedTrustContext,
|
|
227
|
+
type SeedTrustDecision
|
|
228
|
+
} from './seed-trust-policy.js';
|
|
229
|
+
|
|
230
|
+
// Schema Verification
|
|
231
|
+
export {
|
|
232
|
+
signSchema,
|
|
233
|
+
verifySchema,
|
|
234
|
+
assertSchemaSignature,
|
|
235
|
+
SchemaVerificationError
|
|
236
|
+
} from './schema-verification.js';
|
|
237
|
+
|
|
238
|
+
// Strand Formation transport (native cadre-core protocol)
|
|
239
|
+
export {
|
|
240
|
+
FormationListener,
|
|
241
|
+
dialFormation,
|
|
242
|
+
isValidResponderCreatesResult,
|
|
243
|
+
FORMATION_PROTOCOL,
|
|
244
|
+
type FormationParty,
|
|
245
|
+
type FormationContactMessage,
|
|
246
|
+
type FormationResultMessage,
|
|
247
|
+
type FormationProvisionResult,
|
|
248
|
+
type FormationStrandInfo,
|
|
249
|
+
type FormationDbConnectionInfo,
|
|
250
|
+
type FormationListenerOptions,
|
|
251
|
+
type FormationDialOptions,
|
|
252
|
+
type ResponderProvisionOutcome
|
|
253
|
+
} from './strand-formation-protocol.js';
|
|
254
|
+
|
|
255
|
+
// Strand Formation manager (drives the native transport)
|
|
256
|
+
export {
|
|
257
|
+
StrandFormationManager,
|
|
258
|
+
createStrandFormationManager,
|
|
259
|
+
type StrandFormationManagerConfig,
|
|
260
|
+
type StrandFormationManagerOptions
|
|
261
|
+
} from './strand-formation-manager.js';
|
|
262
|
+
|
|
263
|
+
// Connection-path diagnostics (relayed vs direct classification + summary)
|
|
264
|
+
export {
|
|
265
|
+
classifyTransport,
|
|
266
|
+
classifyConnectionPath,
|
|
267
|
+
summarizeConnectionPaths,
|
|
268
|
+
emptyConnectionPathSummary,
|
|
269
|
+
DEFAULT_SETTLE_WINDOW_MS,
|
|
270
|
+
type ConnectionPathKind,
|
|
271
|
+
type ConnectionTransport,
|
|
272
|
+
type ConnectionPath,
|
|
273
|
+
type ConnectionPathSummary,
|
|
274
|
+
type ConnectionLike
|
|
275
|
+
} from './diagnostics/connection-path.js';
|