@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
package/dist/types.d.ts CHANGED
@@ -1,6 +1,17 @@
1
- import type { Libp2p, PeerId, PrivateKey } from '@libp2p/interface';
1
+ import type { ConnectionGater, Libp2p, PeerId, PrivateKey } from '@libp2p/interface';
2
2
  import type { IRawStorage, Libp2pTransports } from '@optimystic/db-p2p';
3
+ import type { IRepo } from '@optimystic/db-core';
3
4
  import type { StrandDatabase } from './strand-database.js';
5
+ import type { SeedTrustPolicy } from './seed-trust-policy.js';
6
+ import type { KeyStore, KeyId } from './key-store.js';
7
+ import type { PushNotifier } from './push-notifier.js';
8
+ /**
9
+ * Extended Libp2p node with the coordinatedRepo attached by db-p2p's
10
+ * createLibp2pNode after node creation (not surfaced on the base Libp2p type).
11
+ */
12
+ export interface Libp2pNodeWithRepo extends Libp2p {
13
+ coordinatedRepo: IRepo;
14
+ }
4
15
  /**
5
16
  * Node profile determines storage participation
6
17
  */
@@ -31,11 +42,31 @@ export interface HibernationTimeouts {
31
42
  idleTimeout: number;
32
43
  /** Time before transitioning from idle to hibernating */
33
44
  hibernateTimeout: number;
34
- /** Interval for check-ins while hibernating */
45
+ /**
46
+ * Base interval for the FIRST check-in after hibernating. Successive
47
+ * no-activity check-ins escalate this delay by {@link checkInBackoffFactor}
48
+ * (see {@link HibernationManager}'s self-rescheduling check-in chain).
49
+ */
35
50
  checkInInterval: number;
51
+ /**
52
+ * Multiplier applied to the check-in delay after each check-in that finds no
53
+ * pending activity (must be >= 1; 1 disables escalation). Default 2 →
54
+ * doubling: base, base×2, base×4, … until {@link checkInMaxInterval}.
55
+ */
56
+ checkInBackoffFactor: number;
57
+ /**
58
+ * Upper bound on the escalating check-in delay — the concrete realization of
59
+ * the architecture's "minutes → hours → days" ceiling for this hint. Backoff
60
+ * never schedules a delay longer than this.
61
+ */
62
+ checkInMaxInterval: number;
36
63
  }
37
64
  /**
38
- * Default hibernation timeouts per latency hint
65
+ * Default hibernation timeouts per latency hint.
66
+ *
67
+ * `checkInInterval` is the BASE delay; `checkInMaxInterval` is the per-hint
68
+ * ceiling the exponential backoff escalates toward (interactive minutes→~1h,
69
+ * background minutes→~6h, archive ~1h→~3 days).
39
70
  */
40
71
  export declare const HIBERNATION_TIMEOUTS: Record<LatencyHint, HibernationTimeouts>;
41
72
  /**
@@ -103,11 +134,42 @@ export interface NetworkConfig {
103
134
  transports?: Libp2pTransports;
104
135
  /**
105
136
  * Optional async resolver returning the multiaddrs to embed in invites
106
- * (and other authority-address contexts). When unset, `libp2pNode.getMultiaddrs()`
137
+ * (and other owner-address contexts). When unset, `libp2pNode.getMultiaddrs()`
107
138
  * is used. Hosts behind NAT supply this to substitute their DDNS hostname
108
139
  * and externally-mapped port — see `@serfab/cadre-host`'s NatService.
109
140
  */
110
141
  inviteAddressResolver?: () => Promise<string[]>;
142
+ /**
143
+ * Optional libp2p connection gater, threaded to both the control node and every
144
+ * strand's cohort node. libp2p's browser default denies dialing insecure
145
+ * WebSockets and private/loopback addresses; callers that must dial local or
146
+ * unsecured peers (the web reference dialing a `127.0.0.1/.../ws` responder,
147
+ * Playwright e2e, RN simulators) supply a permissive gater here — e.g.
148
+ * `{ denyDialMultiaddr: () => false }`. See `@optimystic/db-p2p`'s
149
+ * `libp2p-node-base` for the underlying option.
150
+ */
151
+ connectionGater?: ConnectionGater;
152
+ /**
153
+ * Tuning for the proactive control-cohort dial routine
154
+ * ({@link CadreNode.reconcileControlCohort}), which keeps a party's control
155
+ * nodes connected so the `CadreControl` collections form a replicating cohort.
156
+ * Both fields are optional; omit for the defaults
157
+ * ({@link DEFAULT_CONTROL_COHORT_TARGET_DEGREE} /
158
+ * {@link DEFAULT_CONTROL_COHORT_RECONCILE_MS}).
159
+ */
160
+ controlCohort?: {
161
+ /**
162
+ * Cap on the number of NON-owner siblings dialed per reconcile pass
163
+ * (backbone/owner members are always dialed and do not count). Defaults
164
+ * to {@link DEFAULT_CONTROL_COHORT_TARGET_DEGREE}.
165
+ */
166
+ targetDegree?: number;
167
+ /**
168
+ * Recurring reconcile cadence in ms. Defaults to
169
+ * {@link DEFAULT_CONTROL_COHORT_RECONCILE_MS}.
170
+ */
171
+ reconcileMs?: number;
172
+ };
111
173
  }
112
174
  /**
113
175
  * Hibernation configuration
@@ -117,7 +179,19 @@ export interface HibernationConfig {
117
179
  defaultLatencyHint?: LatencyHint;
118
180
  /** Custom timeouts per latency hint (overrides defaults) */
119
181
  customTimeouts?: Partial<Record<LatencyHint, Partial<HibernationTimeouts>>>;
182
+ /**
183
+ * How long a hibernating strand stays resumed during a check-in to let its
184
+ * strand network connect to reachable cohort peers (and the app drive
185
+ * pull-on-read activity) before re-hibernating if still idle. Defaults to
186
+ * {@link DEFAULT_CHECKIN_WINDOW_MS}.
187
+ */
188
+ checkInWindowMs?: number;
120
189
  }
190
+ /**
191
+ * Default duration of the resume-and-probe window during a strand check-in.
192
+ * See {@link HibernationConfig.checkInWindowMs}.
193
+ */
194
+ export declare const DEFAULT_CHECKIN_WINDOW_MS: number;
121
195
  /**
122
196
  * Control network configuration
123
197
  */
@@ -131,8 +205,28 @@ export interface ControlNetworkConfig {
131
205
  * Main configuration for a CadreNode
132
206
  */
133
207
  export interface CadreNodeConfig {
134
- /** If provided, use this keypair for the node identity */
208
+ /**
209
+ * If provided, use this keypair for the node identity (direct injection).
210
+ * Mutually exclusive with {@link keyStore} — supplying both is a configuration
211
+ * error (fail closed) thrown by `start()` before any network bring-up.
212
+ */
135
213
  privateKey?: PrivateKey;
214
+ /**
215
+ * Pluggable secure store for node key material. When set, the node loads its
216
+ * identity from `keyStore` under {@link identityKeyId}, generating + persisting
217
+ * a fresh Ed25519 key on first run (protobuf bytes are the canonical stored
218
+ * form). Mutually exclusive with {@link privateKey}. Absent ⇒ legacy behavior
219
+ * (use `privateKey`, else libp2p generates an ephemeral key).
220
+ *
221
+ * In the single-key reference model the owner signing key is *derived from*
222
+ * the node identity (see {@link CadreNode.getIdentityOwnerKey}), so
223
+ * protecting the identity in a secure enclave protects the owner key too.
224
+ * A future separate-owner slot (`ownerKeyId`) is anticipated but not
225
+ * built here.
226
+ */
227
+ keyStore?: KeyStore;
228
+ /** Slot id for the node identity in {@link keyStore}. Default: `'cadre/identity'`. */
229
+ identityKeyId?: KeyId;
136
230
  /** Control network connection settings */
137
231
  controlNetwork: ControlNetworkConfig;
138
232
  /** Node profile: transaction-only or storage */
@@ -147,6 +241,52 @@ export interface CadreNodeConfig {
147
241
  hibernation?: HibernationConfig;
148
242
  /** Polling interval for strand watcher in ms (default: 5000) */
149
243
  strandWatchInterval?: number;
244
+ /**
245
+ * Require a valid author signature on every sApp schema before a strand is
246
+ * formed or joined. Defaults to true (fail closed): an unsigned schema is
247
+ * rejected before any libp2p node or schema DDL is brought up. Set false ONLY
248
+ * for dev/test where unsigned demo schemas are used.
249
+ */
250
+ requireSignedSchemas?: boolean;
251
+ /**
252
+ * Node-wide default trust anchor for INBOUND control-network seeds. Forwarded
253
+ * into every SeedBootstrapService this node constructs (owner, receive-only
254
+ * listener, and the temp service used by applySeed when no service exists), and
255
+ * used as the service-level default the libp2p seed-protocol handler relies on
256
+ * (that path has no per-call override seam). Defaults to dbAnchoredTrustPolicy()
257
+ * inside SeedBootstrapService when unset — a cold-start node then rejects every
258
+ * seed. A per-call `applySeed(seed, { trustPolicy })` override still wins over
259
+ * this default for callers that hold out-of-band material (e.g. a pinned key
260
+ * from a CadreInvite).
261
+ */
262
+ seedTrustPolicy?: SeedTrustPolicy;
263
+ /**
264
+ * Platform push-delivery for suspended mobile peers. When present, the node's
265
+ * server fan-out can deliver strand-wake data messages over the platform push
266
+ * channel (FCM/APNs). Absent ⇒ no platform push (control-network push-wake only).
267
+ *
268
+ * The `notifier` is **injected**, not constructed here: a Node host builds it
269
+ * from `@serfab/cadre-core/push-node` (`createPushNotifier(credentials)`) and
270
+ * passes the instance, so the FCM/APNs modules (`node:crypto`/`node:http2`)
271
+ * stay out of the cross-platform core graph. Ownership transfers to the node on
272
+ * injection — `CadreNode.stop` closes the notifier (via `PushFanoutService`),
273
+ * releasing its APNs HTTP/2 session; a host must NOT also close an injected
274
+ * notifier itself. `cooldownMs`/`debounceMs` carry the fan-out anti-spam policy.
275
+ */
276
+ push?: {
277
+ /** The Node-constructed push router (see `@serfab/cadre-core/push-node`). */
278
+ notifier: PushNotifier;
279
+ /**
280
+ * Per-`(peer, strand)` minimum gap between push-wakes the fan-out emits.
281
+ * Default {@link DEFAULT_PUSH_COOLDOWN_MS} (5 min).
282
+ */
283
+ cooldownMs?: number;
284
+ /**
285
+ * Per-strand burst-coalescing window: a second trigger within it is dropped.
286
+ * Default {@link DEFAULT_PUSH_DEBOUNCE_MS} (10 s).
287
+ */
288
+ debounceMs?: number;
289
+ };
150
290
  }
151
291
  /**
152
292
  * Status of a strand instance
@@ -184,6 +324,27 @@ export interface StrandInstance {
184
324
  /** Error message if status is 'error' */
185
325
  error?: string;
186
326
  }
327
+ /**
328
+ * Outcome of an on-demand {@link CadreNode.serviceWake} — the mobile push-wake
329
+ * cycle (resume → bounded window → re-hibernate-if-idle) run imperatively for a
330
+ * single strand.
331
+ */
332
+ export interface ServiceWakeResult {
333
+ /** The strand the wake was requested for. */
334
+ strandId: string;
335
+ /**
336
+ * Whether the wake was actually serviced. `false` when the node is not
337
+ * running, or the strand is unknown to this node (not a member-participated
338
+ * strand) — never a thrown error, so a background task can branch on it.
339
+ */
340
+ serviced: boolean;
341
+ /**
342
+ * Whether activity landed during the wake window. `true` leaves the strand
343
+ * active; `false` re-hibernates it (or the strand was already live, in which
344
+ * case it reflects current liveness). Always `false` when `serviced` is `false`.
345
+ */
346
+ hadActivity: boolean;
347
+ }
187
348
  /**
188
349
  * Strand row from control network - basic membership info
189
350
  */
@@ -203,7 +364,13 @@ export interface SAppConfig {
203
364
  version: string;
204
365
  /** The declarative schema DDL */
205
366
  schema: string;
206
- /** Author's signature over the schema for verification (omit to skip verification) */
367
+ /**
368
+ * Author's signature over the schema for verification. Required under the
369
+ * default `requireSignedSchemas` node policy — a config without it is rejected
370
+ * before strand bring-up. Optional in the type because authoring/serialization
371
+ * must represent an as-yet-unsigned config; omitting it is honored only when
372
+ * the node explicitly relaxes the policy (`requireSignedSchemas: false`, dev/test).
373
+ */
207
374
  signature?: string;
208
375
  /** Latency hint for hibernation behavior (optional, defaults to config) */
209
376
  latencyHint?: LatencyHint;
@@ -231,10 +398,22 @@ export interface StrandConfig {
231
398
  /** sApp configuration provided by the hosting application */
232
399
  sAppConfig: SAppConfig;
233
400
  /**
234
- * Lifecycle mode for this strand instance. Defaults to `'networked'` when omitted
235
- * to preserve the historical behavior for multi-peer callers.
401
+ * Lifecycle mode for this strand instance. When omitted, CadreNode infers the
402
+ * mode from cohort membership (`bootstrap` for a solo/first node with no other
403
+ * `CadrePeer` rows, `networked` once the cohort has other members). An explicit
404
+ * value always wins.
236
405
  */
237
406
  mode?: StrandMode;
407
+ /**
408
+ * Whether THIS node is the strand's founder — the party that provisioned and
409
+ * published the strand (the responder in formation, the creator in host/solo
410
+ * paths; the same party that calls {@link CadreNode.publishStrand}). The founder
411
+ * runs the one-time membership bootstrap at bring-up (writes `Strand.Header`, and
412
+ * for a closed strand the founding `Member`+`Manager`). A joiner leaves this
413
+ * `false`/unset and writes nothing — it receives those rows via Optimystic sync.
414
+ * Defaults to `false`.
415
+ */
416
+ founder?: boolean;
238
417
  }
239
418
  /**
240
419
  * Result of creating a new cadre peer
@@ -286,6 +465,13 @@ export interface FormStrandResult {
286
465
  invitePrivateKey: string;
287
466
  /** The strand that was created */
288
467
  strandId: string;
468
+ /**
469
+ * The strand's membership key (closed-strand read-gating secret), delivered through
470
+ * the formation protocol after consent (provision-then-record). Present only for a
471
+ * closed strand the responder returned with its key; undefined otherwise. Distinct
472
+ * from {@link invitePrivateKey} (the initiator's own generated signing key).
473
+ */
474
+ memberPrivateKey?: string;
289
475
  }
290
476
  /**
291
477
  * Result of validating a strand formation request
@@ -347,6 +533,17 @@ export interface CadreNodeEvents {
347
533
  'strand:waking': {
348
534
  strandId: string;
349
535
  };
536
+ /**
537
+ * Emitted when the control network advertises a strand this node has no
538
+ * registered `sAppConfig` for — i.e. a strand created by another member. The
539
+ * hosting app decides whether to join it (register a config + `addStrand`,
540
+ * e.g. via a chat `joinChatStrand` helper). Carries the full {@link StrandRow}
541
+ * so the app can join without re-querying the control DB.
542
+ */
543
+ 'strand:discovered': {
544
+ strandId: string;
545
+ strand: StrandRow;
546
+ };
350
547
  'control:connected': void;
351
548
  'control:disconnected': void;
352
549
  /** Emitted when a seed is received via the seed protocol */
@@ -365,6 +562,175 @@ export interface CadreNodeEvents {
365
562
  error: string;
366
563
  };
367
564
  }
565
+ /**
566
+ * A peer's self-published, freshness-stamped, self-signed address record.
567
+ *
568
+ * This is the logical shape of a `CadreControl.CadrePeer` row. A node publishes
569
+ * its own record (signing with the ed25519 key behind its PeerId); any node can
570
+ * resolve another member's current signaling/relay multiaddrs from only its
571
+ * PeerId by reading the record, re-verifying the self-signature against
572
+ * `publicKey`, checking freshness, and applying a trust gate.
573
+ *
574
+ * The signature covers `peerId`, the comma-joined `addrs`, and `updatedAt` — see
575
+ * `peerRecordSignedPayload`. `publicKey` is NOT inside the signed payload: it is
576
+ * the key the signature is verified *with*, and its libp2p-identity binding to
577
+ * `peerId` is checked separately at resolve time, so re-signing it would be
578
+ * redundant.
579
+ */
580
+ export interface PeerAddressRecord {
581
+ /** libp2p peer ID (base58btc) — the row key. */
582
+ peerId: string;
583
+ /** ed25519 public key (base64url) whose libp2p identity IS `peerId`. */
584
+ publicKey: string;
585
+ /** Current dialable multiaddrs, signaling (`/p2p-circuit`) first. */
586
+ addrs: string[];
587
+ /** epoch ms — freshness; strictly increasing per peer. */
588
+ updatedAt: number;
589
+ /** ed25519 self-signature over the signed payload, base64url. */
590
+ sig: string;
591
+ }
592
+ /**
593
+ * Outcome of {@link CadreNode.registerSelf}, surfaced so callers (e.g. the CLI
594
+ * `--owner` branch) can log what happened:
595
+ * - `inserted` — the first owner-signed INSERT of this node's `CadrePeer` row.
596
+ * - `refreshed` — a self-signed UPDATE of an existing row (heartbeat / addr change).
597
+ * - `skipped` — nothing written (no self-signing key, or not yet a member with no
598
+ * owner service to self-insert).
599
+ */
600
+ export type SelfRegistrationOutcome = 'inserted' | 'refreshed' | 'skipped';
601
+ /**
602
+ * Options for {@link CadreNode.resolvePeerAddrs}.
603
+ */
604
+ export interface ResolveOpts {
605
+ /**
606
+ * Freshness ceiling in ms. A record older than this is filtered out (a
607
+ * resolver must never hand back a dead relay reservation). Defaults to
608
+ * {@link DEFAULT_PEER_RECORD_MAX_AGE_MS}.
609
+ */
610
+ maxAgeMs?: number;
611
+ /** Return only `/p2p-circuit` signaling addrs (the WebRTC dial input). */
612
+ signalingOnly?: boolean;
613
+ /**
614
+ * Pluggable trust gate, evaluated after signature + freshness pass. Defaults
615
+ * to {@link currentMemberTrustPolicy} (any existing — therefore
616
+ * owner-vouched — CadrePeer member is trusted). Inject a stricter policy
617
+ * (e.g. from `seed-signerkey-trust-policy`) to reject otherwise-valid records
618
+ * before dialing.
619
+ */
620
+ trustPolicy?: PeerResolveTrustPolicy;
621
+ }
622
+ /**
623
+ * Context handed to a {@link PeerResolveTrustPolicy} when gating a resolution.
624
+ */
625
+ export interface PeerResolveContext {
626
+ /** The peer being resolved. */
627
+ peerId: string;
628
+ /** The record's ed25519 public key (already bound to `peerId` and verified). */
629
+ publicKey: string;
630
+ /** The party whose control network produced the record. */
631
+ partyId: string;
632
+ /** The full verified, fresh record. */
633
+ record: PeerAddressRecord;
634
+ }
635
+ /**
636
+ * Decides whether a signature-verified, fresh peer-address record should be
637
+ * trusted enough to dial. The seam through which a richer trust model
638
+ * (trust circle, pinned keys) composes with `resolvePeerAddrs`.
639
+ */
640
+ export interface PeerResolveTrustPolicy {
641
+ evaluate(ctx: PeerResolveContext): Promise<boolean> | boolean;
642
+ }
643
+ /** Platform push channel a {@link DeviceTokenRecord} targets. */
644
+ export type PushPlatform = 'fcm' | 'apns';
645
+ /**
646
+ * A mobile cadre peer's self-published, freshness-stamped, self-signed platform
647
+ * push token.
648
+ *
649
+ * This is the logical shape of a `CadreControl.DeviceToken` row. A phone publishes
650
+ * its own record (signing with the ed25519 key behind its PeerId); a server peer
651
+ * resolves it from the phone's PeerId to deliver a push-wake over FCM/APNs when a
652
+ * control-network dial cannot reach the OS-suspended process. The signature covers
653
+ * `peerId`, `platform`, `token`, and `updatedAt` — see `deviceTokenSignedPayload`.
654
+ *
655
+ * Unlike {@link PeerAddressRecord}, the record carries NO public key: it is verified
656
+ * against the `CadrePeer.PublicKey` bound to the same `peerId`, so a `DeviceToken`
657
+ * with no backing `CadrePeer` row can never be resolved.
658
+ */
659
+ export interface DeviceTokenRecord {
660
+ /** libp2p peer ID (base58btc) — the row key and the CadrePeer this token belongs to. */
661
+ peerId: string;
662
+ /** Push channel: `'fcm'` (Android/Firebase) or `'apns'` (Apple). */
663
+ platform: PushPlatform;
664
+ /** Opaque platform device/registration token. */
665
+ token: string;
666
+ /** epoch ms — freshness; strictly increasing per self-update (replay guard). */
667
+ updatedAt: number;
668
+ /** ed25519 self-signature over the signed payload, base64url. */
669
+ sig: string;
670
+ }
671
+ /**
672
+ * Options for {@link CadreNode.resolveDeviceToken}.
673
+ */
674
+ export interface ResolveDeviceTokenOpts {
675
+ /**
676
+ * Optional freshness ceiling in ms. Defaults to NO ceiling (a record is fresh
677
+ * as long as its `updatedAt` is positive): unlike a relay reservation, a device
678
+ * push token stays valid until it rotates, so a long-suspended phone must remain
679
+ * resolvable for push-wake. Set this to bound staleness explicitly.
680
+ */
681
+ maxAgeMs?: number;
682
+ }
683
+ /**
684
+ * Platform push-delivery credentials injected into a participating node's config
685
+ * (`CadreNodeConfig.push`). Provisioned per spawned node by `cadre-host` and read
686
+ * by `createPushNotifier` to construct the FCM and/or APNs senders. A platform
687
+ * whose block is absent is simply not deliverable (the router returns a
688
+ * best-effort "no credentials" failure rather than throwing).
689
+ *
690
+ * Secret hygiene: every `privateKey` field is a secret — never logged (treated
691
+ * like the node's startup/seed tokens).
692
+ */
693
+ export interface PushCredentials {
694
+ /** FCM (Android / Firebase) service-account credentials. */
695
+ fcm?: FcmCredentials;
696
+ /** APNs (Apple) auth-key credentials. */
697
+ apns?: ApnsCredentials;
698
+ /**
699
+ * Per-`(peer, strand)` minimum gap between push-wakes the server fan-out emits —
700
+ * the anti-spam cooldown a chatty strand needs. Default
701
+ * {@link DEFAULT_PUSH_COOLDOWN_MS} (5 min). In-memory, acceptably lossy across
702
+ * restarts (`serviceWake` is idempotent, so a duplicate wake is harmless).
703
+ */
704
+ cooldownMs?: number;
705
+ /**
706
+ * Per-strand burst-coalescing window for the fan-out: a second activity trigger
707
+ * within this window is dropped (one wake is enough). Default
708
+ * {@link DEFAULT_PUSH_DEBOUNCE_MS} (10 s).
709
+ */
710
+ debounceMs?: number;
711
+ }
712
+ /** Google service-account fields needed for FCM HTTP v1 OAuth2. */
713
+ export interface FcmCredentials {
714
+ /** GCP project id → `POST https://fcm.googleapis.com/v1/projects/{projectId}/messages:send`. */
715
+ projectId: string;
716
+ /** Service-account email (JWT `iss`/`sub`). */
717
+ clientEmail: string;
718
+ /** Service-account RSA private key, PEM. Secret — never logged. */
719
+ privateKey: string;
720
+ }
721
+ /** Apple APNs auth-key (.p8) fields. */
722
+ export interface ApnsCredentials {
723
+ /** APNs key id (JWT header `kid`). */
724
+ keyId: string;
725
+ /** Apple team id (JWT `iss`). */
726
+ teamId: string;
727
+ /** App bundle id → `apns-topic` header. */
728
+ bundleId: string;
729
+ /** `.p8` ES256 private key, PEM. Secret — never logged. */
730
+ privateKey: string;
731
+ /** `true` → `api.push.apple.com`; false/undefined → `api.sandbox.push.apple.com`. */
732
+ production?: boolean;
733
+ }
368
734
  /**
369
735
  * A peer entry in a control network seed.
370
736
  * Contains connection information for a cadre peer.
@@ -374,9 +740,9 @@ export interface SeedPeer {
374
740
  peerId: string;
375
741
  /** Multiaddrs for connecting to this peer */
376
742
  multiaddrs: string[];
377
- /** Whether this peer holds an authority key */
378
- isAuthority: boolean;
379
- /** ed25519 public key (base64url) — present on authority peers for signerKey verification */
743
+ /** Whether this peer holds an owner key */
744
+ isOwner: boolean;
745
+ /** ed25519 public key (base64url) — present on owner peers for signerKey verification */
380
746
  publicKey?: string;
381
747
  }
382
748
  /**
@@ -390,25 +756,11 @@ export interface ControlNetworkSeed {
390
756
  partyId: string;
391
757
  /** Known peers in the cadre */
392
758
  peers: SeedPeer[];
393
- /** Optional signed transactions to pre-populate the cache */
394
- transactions?: SignedTransaction[];
395
- /** Signature over the seed by an authority key */
759
+ /** Signature over the seed by an owner key */
396
760
  signature: string;
397
- /** The authority key that signed this seed */
761
+ /** The owner key that signed this seed */
398
762
  signerKey: string;
399
763
  }
400
- /**
401
- * A signed transaction for inclusion in seeds.
402
- * Allows pre-populating the control network cache.
403
- */
404
- export interface SignedTransaction {
405
- /** Transaction ID */
406
- id: string;
407
- /** Serialized transaction data */
408
- data: string;
409
- /** Signature over the transaction */
410
- signature: string;
411
- }
412
764
  /**
413
765
  * Message sent from instigator to new node during seed delivery.
414
766
  * Delivered via /sereus/seed/1.0.0 libp2p protocol.
@@ -418,11 +770,9 @@ export interface SeedMessage {
418
770
  partyId: string;
419
771
  /** Known peers in the cadre */
420
772
  peers: SeedPeer[];
421
- /** Optional transactions to pre-populate cache */
422
- transactions?: SignedTransaction[];
423
- /** Signature by an authority key */
773
+ /** Signature by an owner key */
424
774
  signature: string;
425
- /** The authority key that signed this message */
775
+ /** The owner key that signed this message */
426
776
  signerKey: string;
427
777
  }
428
778
  /**
@@ -434,6 +784,52 @@ export interface SeedAckMessage {
434
784
  /** Reason for rejection (if not accepted) */
435
785
  reason?: string;
436
786
  }
787
+ /**
788
+ * Request sent over the control network's `WAKE_PROTOCOL` from a same-cadre peer
789
+ * to a hibernating peer, asking it to bring a strand online and pull pending
790
+ * activity. The receiver gates this on cadre membership before honoring it.
791
+ */
792
+ export interface WakeRequest {
793
+ /** The strand the sender knows has pending activity. */
794
+ strandId: string;
795
+ /** Optional cause hint, e.g. `"activity"` (a server saw new traffic) or `"manual"`. */
796
+ reason?: string;
797
+ }
798
+ /**
799
+ * Acknowledgment of a {@link WakeRequest}, returned on the same stream.
800
+ */
801
+ export interface WakeAck {
802
+ /** Whether the receiver honored the wake (member + participated strand). */
803
+ accepted: boolean;
804
+ /** The strand's status after the wake (present when `accepted`). */
805
+ status?: StrandStatus;
806
+ /** Reason for rejection (non-member, unknown strand, malformed frame). */
807
+ reason?: string;
808
+ }
809
+ /**
810
+ * Control-network request from one of a party's own cadre nodes to a sibling,
811
+ * asking "what are your live multiaddrs for this strand's separate network?".
812
+ * Used to seed a strand mesh from co-cadre nodes that already run that strand.
813
+ * The receiver gates this on cadre membership before answering.
814
+ */
815
+ export interface StrandAddrRequest {
816
+ /** The strand whose strand-network address the requester wants to seed from. */
817
+ strandId: string;
818
+ }
819
+ /**
820
+ * Response to a {@link StrandAddrRequest}, carrying the responder's strand-node
821
+ * multiaddrs, returned on the same stream.
822
+ */
823
+ export interface StrandAddrResponse {
824
+ /** Echoes the requested strand id (empty on a reject/error reply). */
825
+ strandId: string;
826
+ /**
827
+ * Dialable strand-network multiaddr strings (signaling/`p2p-circuit` first);
828
+ * empty when the responder is a non-member, or does not currently run that
829
+ * strand, or the exchange failed.
830
+ */
831
+ multiaddrs: string[];
832
+ }
437
833
  /**
438
834
  * Options for authorizing a new peer in the cadre.
439
835
  */
@@ -465,8 +861,15 @@ export type NodeTopology = 'public' | 'nat';
465
861
  export interface CadreInvite {
466
862
  /** Party ID of the cadre */
467
863
  partyId: string;
468
- /** Multiaddrs to dial the authority */
469
- authorityAddrs: string[];
864
+ /** Multiaddrs to dial the owner */
865
+ ownerAddrs: string[];
866
+ /**
867
+ * Owner ed25519 public keys (base64url) of the cadre, carried out-of-band
868
+ * so a cold-start invitee can pin the trusted owner set before applying
869
+ * any seed (the seed itself cannot vouch for its own signer). Populated by
870
+ * `createInvite` from the issuer's `OwnerKey` table.
871
+ */
872
+ ownerKeys?: string[];
470
873
  /** Optional invite token for validation */
471
874
  token?: string;
472
875
  /** Timestamp when invite was created */
package/dist/types.js CHANGED
@@ -1,26 +1,43 @@
1
1
  /**
2
- * Default hibernation timeouts per latency hint
2
+ * Default hibernation timeouts per latency hint.
3
+ *
4
+ * `checkInInterval` is the BASE delay; `checkInMaxInterval` is the per-hint
5
+ * ceiling the exponential backoff escalates toward (interactive minutes→~1h,
6
+ * background minutes→~6h, archive ~1h→~3 days).
3
7
  */
4
8
  export const HIBERNATION_TIMEOUTS = {
5
9
  realtime: {
6
10
  idleTimeout: Infinity, // Never idle
7
11
  hibernateTimeout: Infinity, // Never hibernate
8
- checkInInterval: Infinity // N/A
12
+ checkInInterval: Infinity, // N/A
13
+ checkInBackoffFactor: 2,
14
+ checkInMaxInterval: Infinity // N/A
9
15
  },
10
16
  interactive: {
11
17
  idleTimeout: 5 * 60 * 1000, // 5 minutes
12
18
  hibernateTimeout: 15 * 60 * 1000, // 15 minutes after idle
13
- checkInInterval: 30 * 1000 // 30 seconds
19
+ checkInInterval: 30 * 1000, // base: 30 seconds
20
+ checkInBackoffFactor: 2,
21
+ checkInMaxInterval: 60 * 60 * 1000 // cap: ~1 hour
14
22
  },
15
23
  background: {
16
24
  idleTimeout: 1 * 60 * 1000, // 1 minute
17
25
  hibernateTimeout: 5 * 60 * 1000, // 5 minutes after idle
18
- checkInInterval: 5 * 60 * 1000 // 5 minutes
26
+ checkInInterval: 5 * 60 * 1000, // base: 5 minutes
27
+ checkInBackoffFactor: 2,
28
+ checkInMaxInterval: 6 * 60 * 60 * 1000 // cap: ~6 hours
19
29
  },
20
30
  archive: {
21
31
  idleTimeout: 10 * 1000, // 10 seconds
22
32
  hibernateTimeout: 30 * 1000, // 30 seconds after idle
23
- checkInInterval: 60 * 60 * 1000 // 1 hour
33
+ checkInInterval: 60 * 60 * 1000, // base: 1 hour
34
+ checkInBackoffFactor: 2,
35
+ checkInMaxInterval: 3 * 24 * 60 * 60 * 1000 // cap: ~3 days
24
36
  }
25
37
  };
38
+ /**
39
+ * Default duration of the resume-and-probe window during a strand check-in.
40
+ * See {@link HibernationConfig.checkInWindowMs}.
41
+ */
42
+ export const DEFAULT_CHECKIN_WINDOW_MS = 15 * 1000;
26
43
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAmCA;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA6C;IAC5E,QAAQ,EAAE;QACR,WAAW,EAAE,QAAQ,EAAS,aAAa;QAC3C,gBAAgB,EAAE,QAAQ,EAAI,kBAAkB;QAChD,eAAe,EAAE,QAAQ,CAAK,MAAM;KACrC;IACD,WAAW,EAAE;QACX,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAI,YAAY;QAC1C,gBAAgB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,wBAAwB;QAC1D,eAAe,EAAE,EAAE,GAAG,IAAI,CAAI,aAAa;KAC5C;IACD,UAAU,EAAE;QACV,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAI,WAAW;QACzC,gBAAgB,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAG,uBAAuB;QACzD,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAI,YAAY;KAC/C;IACD,OAAO,EAAE;QACP,WAAW,EAAE,EAAE,GAAG,IAAI,EAAQ,aAAa;QAC3C,gBAAgB,EAAE,EAAE,GAAG,IAAI,EAAG,wBAAwB;QACtD,eAAe,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAG,SAAS;KAC5C;CACF,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA+DA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAA6C;IAC5E,QAAQ,EAAE;QACR,WAAW,EAAE,QAAQ,EAAS,aAAa;QAC3C,gBAAgB,EAAE,QAAQ,EAAI,kBAAkB;QAChD,eAAe,EAAE,QAAQ,EAAK,MAAM;QACpC,oBAAoB,EAAE,CAAC;QACvB,kBAAkB,EAAE,QAAQ,CAAE,MAAM;KACrC;IACD,WAAW,EAAE;QACX,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAI,YAAY;QAC1C,gBAAgB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,wBAAwB;QAC1D,eAAe,EAAE,EAAE,GAAG,IAAI,EAAI,mBAAmB;QACjD,oBAAoB,EAAE,CAAC;QACvB,kBAAkB,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,eAAe;KACnD;IACD,UAAU,EAAE;QACV,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAI,WAAW;QACzC,gBAAgB,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAG,uBAAuB;QACzD,eAAe,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,EAAI,kBAAkB;QACpD,oBAAoB,EAAE,CAAC;QACvB,kBAAkB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,gBAAgB;KACxD;IACD,OAAO,EAAE;QACP,WAAW,EAAE,EAAE,GAAG,IAAI,EAAQ,aAAa;QAC3C,gBAAgB,EAAE,EAAE,GAAG,IAAI,EAAG,wBAAwB;QACtD,eAAe,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAG,eAAe;QACjD,oBAAoB,EAAE,CAAC;QACvB,kBAAkB,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,eAAe;KAC5D;CACF,CAAC;AA4HF;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,GAAG,IAAI,CAAC"}