@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.
Files changed (166) hide show
  1. package/README.md +9 -8
  2. package/dist/authority-key.d.ts +41 -0
  3. package/dist/authority-key.js +52 -0
  4. package/dist/authority-key.js.map +1 -0
  5. package/dist/cadre-node.d.ts +777 -11
  6. package/dist/cadre-node.js +1912 -82
  7. package/dist/cadre-node.js.map +1 -1
  8. package/dist/canonical-datetime.d.ts +33 -0
  9. package/dist/canonical-datetime.js +38 -0
  10. package/dist/canonical-datetime.js.map +1 -0
  11. package/dist/canonical-json.d.ts +12 -0
  12. package/dist/canonical-json.js +33 -0
  13. package/dist/canonical-json.js.map +1 -0
  14. package/dist/control-cohort.d.ts +63 -0
  15. package/dist/control-cohort.js +80 -0
  16. package/dist/control-cohort.js.map +1 -0
  17. package/dist/control-database.d.ts +259 -5
  18. package/dist/control-database.js +541 -128
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +89 -0
  21. package/dist/control-formation-recorder.js +139 -0
  22. package/dist/control-formation-recorder.js.map +1 -0
  23. package/dist/control-schema.d.ts +12 -0
  24. package/dist/control-schema.js +199 -0
  25. package/dist/control-schema.js.map +1 -0
  26. package/dist/control-stream.d.ts +54 -0
  27. package/dist/control-stream.js +104 -0
  28. package/dist/control-stream.js.map +1 -0
  29. package/dist/device-token.d.ts +50 -0
  30. package/dist/device-token.js +66 -0
  31. package/dist/device-token.js.map +1 -0
  32. package/dist/diagnostics/connection-path.d.ts +118 -0
  33. package/dist/diagnostics/connection-path.js +204 -0
  34. package/dist/diagnostics/connection-path.js.map +1 -0
  35. package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
  36. package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
  37. package/dist/diagnostics/webrtc-turn-tracker.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 +26 -0
  51. package/dist/peer-authorization.js +40 -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-notifier-apns.d.ts +53 -0
  63. package/dist/push-notifier-apns.js +195 -0
  64. package/dist/push-notifier-apns.js.map +1 -0
  65. package/dist/push-notifier-fcm.d.ts +38 -0
  66. package/dist/push-notifier-fcm.js +183 -0
  67. package/dist/push-notifier-fcm.js.map +1 -0
  68. package/dist/push-notifier-shared.d.ts +18 -0
  69. package/dist/push-notifier-shared.js +25 -0
  70. package/dist/push-notifier-shared.js.map +1 -0
  71. package/dist/push-notifier.d.ts +71 -0
  72. package/dist/push-notifier.js +50 -0
  73. package/dist/push-notifier.js.map +1 -0
  74. package/dist/schema-verification.d.ts +11 -2
  75. package/dist/schema-verification.js +15 -4
  76. package/dist/schema-verification.js.map +1 -1
  77. package/dist/seed-bootstrap.d.ts +210 -7
  78. package/dist/seed-bootstrap.js +429 -131
  79. package/dist/seed-bootstrap.js.map +1 -1
  80. package/dist/seed-trust-policy.d.ts +63 -0
  81. package/dist/seed-trust-policy.js +56 -0
  82. package/dist/seed-trust-policy.js.map +1 -0
  83. package/dist/strand-addr-protocol.d.ts +132 -0
  84. package/dist/strand-addr-protocol.js +313 -0
  85. package/dist/strand-addr-protocol.js.map +1 -0
  86. package/dist/strand-cohort.d.ts +54 -0
  87. package/dist/strand-cohort.js +38 -0
  88. package/dist/strand-cohort.js.map +1 -0
  89. package/dist/strand-database.d.ts +54 -6
  90. package/dist/strand-database.js +88 -89
  91. package/dist/strand-database.js.map +1 -1
  92. package/dist/strand-formation-manager.d.ts +79 -19
  93. package/dist/strand-formation-manager.js +196 -104
  94. package/dist/strand-formation-manager.js.map +1 -1
  95. package/dist/strand-formation-protocol.d.ts +162 -0
  96. package/dist/strand-formation-protocol.js +279 -0
  97. package/dist/strand-formation-protocol.js.map +1 -0
  98. package/dist/strand-instance-manager.d.ts +73 -4
  99. package/dist/strand-instance-manager.js +167 -41
  100. package/dist/strand-instance-manager.js.map +1 -1
  101. package/dist/strand-member-key.d.ts +29 -0
  102. package/dist/strand-member-key.js +38 -0
  103. package/dist/strand-member-key.js.map +1 -0
  104. package/dist/strand-member-registry.d.ts +98 -0
  105. package/dist/strand-member-registry.js +129 -0
  106. package/dist/strand-member-registry.js.map +1 -0
  107. package/dist/strand-membership-writer.d.ts +361 -0
  108. package/dist/strand-membership-writer.js +449 -0
  109. package/dist/strand-membership-writer.js.map +1 -0
  110. package/dist/strand-solicitation.d.ts +77 -2
  111. package/dist/strand-solicitation.js +25 -4
  112. package/dist/strand-solicitation.js.map +1 -1
  113. package/dist/strand-wake-payload.d.ts +30 -0
  114. package/dist/strand-wake-payload.js +20 -0
  115. package/dist/strand-wake-payload.js.map +1 -0
  116. package/dist/strand-wake-protocol.d.ts +114 -0
  117. package/dist/strand-wake-protocol.js +257 -0
  118. package/dist/strand-wake-protocol.js.map +1 -0
  119. package/dist/strand-watcher.d.ts +6 -2
  120. package/dist/strand-watcher.js +57 -17
  121. package/dist/strand-watcher.js.map +1 -1
  122. package/dist/types.d.ts +413 -23
  123. package/dist/types.js +22 -5
  124. package/dist/types.js.map +1 -1
  125. package/package.json +14 -8
  126. package/src/authority-key.ts +68 -0
  127. package/src/cadre-node.ts +2937 -929
  128. package/src/canonical-datetime.ts +39 -0
  129. package/src/canonical-json.ts +30 -0
  130. package/src/control-cohort.ts +103 -0
  131. package/src/control-database.ts +931 -383
  132. package/src/control-formation-recorder.ts +151 -0
  133. package/src/control-schema.ts +198 -0
  134. package/src/control-stream.ts +128 -0
  135. package/src/device-token.ts +89 -0
  136. package/src/diagnostics/connection-path.ts +277 -0
  137. package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
  138. package/src/hibernation-manager.ts +197 -28
  139. package/src/index.ts +273 -79
  140. package/src/key-store-file.ts +203 -0
  141. package/src/key-store.ts +99 -0
  142. package/src/peer-authorization.ts +53 -0
  143. package/src/peer-record.ts +146 -0
  144. package/src/push-credentials.ts +84 -0
  145. package/src/push-fanout.ts +272 -0
  146. package/src/push-notifier-apns.ts +243 -0
  147. package/src/push-notifier-fcm.ts +221 -0
  148. package/src/push-notifier-shared.ts +28 -0
  149. package/src/push-notifier.ts +93 -0
  150. package/src/schema-verification.ts +18 -4
  151. package/src/seed-bootstrap.ts +504 -171
  152. package/src/seed-trust-policy.ts +107 -0
  153. package/src/strand-addr-protocol.ts +410 -0
  154. package/src/strand-cohort.ts +77 -0
  155. package/src/strand-database.ts +113 -126
  156. package/src/strand-formation-manager.ts +233 -142
  157. package/src/strand-formation-protocol.ts +391 -0
  158. package/src/strand-instance-manager.ts +207 -56
  159. package/src/strand-member-key.ts +39 -0
  160. package/src/strand-member-registry.ts +177 -0
  161. package/src/strand-membership-writer.ts +640 -0
  162. package/src/strand-solicitation.ts +93 -4
  163. package/src/strand-wake-payload.ts +32 -0
  164. package/src/strand-wake-protocol.ts +331 -0
  165. package/src/strand-watcher.ts +61 -17
  166. package/src/types.ts +1012 -575
@@ -0,0 +1,39 @@
1
+ import type { Database } from '@quereus/quereus';
2
+
3
+ /**
4
+ * Canonicalise an epoch-ms timestamp to the exact `datetime` string Quereus stores,
5
+ * by round-tripping through the engine's own `datetime(?)` scalar — the same parse
6
+ * the `datetime` column coercion uses.
7
+ *
8
+ * **Stored form:** a `datetime` column always stores and reads back values as
9
+ * `YYYY-MM-DDTHH:MM:SS[.frac]` (T-separated, no trailing Z). Any valid input —
10
+ * space-form (`YYYY-MM-DD HH:MM:SS`), T-form, or ISO-Z (`...T...000Z`) — is
11
+ * coerced to this form on read; ordering over a `datetime` column is therefore
12
+ * chronologically correct regardless of which input form was inserted.
13
+ *
14
+ * **Raw bound-parameter comparisons:** Quereus only coerces operands that carry
15
+ * `datetime` semantics (typed columns or explicit `cast(... as datetime)`). A raw,
16
+ * un-coerced bound parameter is compared lexically. To avoid mis-ordering, anything
17
+ * compared against a `datetime` column value via a raw `> ?` should be produced by
18
+ * this helper (or `datetime()` / an explicit cast), so both sides are T-form.
19
+ *
20
+ * This avoids a hand-rolled ISO formatter whose fractional-second handling could
21
+ * diverge from Temporal. Any signed payload segment that must byte-match a
22
+ * `datetime`-typed column value AFTER coercion (e.g. the control-layer
23
+ * `FormationInvite.ExpiresAt` or the strand-layer `Invite.Expiration`) must be
24
+ * produced this way, so the signer and the deferred CHECK verifier agree exactly.
25
+ *
26
+ * Pure scalar eval (no network) — safe to call on any composed Quereus database.
27
+ *
28
+ * @param db - The Quereus database to evaluate against.
29
+ * @param epochMs - The timestamp in epoch milliseconds.
30
+ * @returns The engine-canonical `datetime` string for that instant (T-separated ISO,
31
+ * no trailing Z).
32
+ * @throws If the `datetime()` eval returns no row.
33
+ */
34
+ export async function canonicalDatetime(db: Database, epochMs: number): Promise<string> {
35
+ for await (const row of db.eval('select datetime(?) as Canonical', [epochMs])) {
36
+ return row.Canonical as string;
37
+ }
38
+ throw new Error('datetime() canonicalisation returned no row');
39
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Canonical JSON serialization for signed payloads.
3
+ *
4
+ * Recursively sorts object keys, drops `undefined` values, emits no whitespace
5
+ * and no trailing newline. Arrays preserve their order. This is a tiny RFC 8785
6
+ * subset, sufficient for the fixed-shape payloads we sign (update manifests in
7
+ * cadre-host, control-network seeds here).
8
+ *
9
+ * Lives in cadre-core because cadre-host depends on cadre-core (not the
10
+ * reverse); cadre-host re-imports this rather than keeping its own copy.
11
+ */
12
+ export function canonicalJson(value: unknown): string {
13
+ if (value === null) return 'null';
14
+ if (typeof value === 'number') {
15
+ if (!Number.isFinite(value)) throw new Error('cannot serialize non-finite number');
16
+ return JSON.stringify(value);
17
+ }
18
+ if (typeof value === 'string' || typeof value === 'boolean') {
19
+ return JSON.stringify(value);
20
+ }
21
+ if (Array.isArray(value)) {
22
+ return '[' + value.map((v) => canonicalJson(v)).join(',') + ']';
23
+ }
24
+ if (typeof value === 'object') {
25
+ const obj = value as Record<string, unknown>;
26
+ const keys = Object.keys(obj).filter((k) => obj[k] !== undefined).sort();
27
+ return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalJson(obj[k])).join(',') + '}';
28
+ }
29
+ throw new Error(`cannot serialize value of type ${typeof value}`);
30
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Control-cohort dial selection: the pure policy behind
3
+ * {@link CadreNode.reconcileControlCohort}. Decides WHICH known cadre siblings a
4
+ * node proactively dials each pass to keep the `CadreControl` collections' cohort
5
+ * connected (and thus replicating), without devolving into an N² mesh.
6
+ *
7
+ * FRET only needs a *connected* graph with the block clusters reachable, not a
8
+ * full mesh. Storage/authority nodes are the stable, publicly-dialable backbone
9
+ * that hold the control blocks, so the policy is **backbone-preferential with a
10
+ * bounded out-degree**:
11
+ *
12
+ * - **Always dial every backbone (authority) member** — the authority set is
13
+ * small by design, and routing cohort formation through publicly-dialable
14
+ * authority nodes keeps the cohort ≥2 where authority-signed writes originate.
15
+ * - **Fill the remainder** up to `targetDegree` with non-authority members,
16
+ * ordered deterministically (peerId sort) so every node makes the same stable
17
+ * choice across passes and load spreads predictably.
18
+ *
19
+ * For a small party (members ≤ degree) this degenerates to a full mesh, which is
20
+ * correct, cheap, and matches what the convergence tests do by hand; for a large
21
+ * party it bounds out-degree to `backbone + targetDegree`.
22
+ */
23
+
24
+ import { ed25519PublicKeyB64FromPeerId } from './seed-bootstrap.js';
25
+ import type { CohortPeerRow } from './strand-cohort.js';
26
+
27
+ /**
28
+ * Default recurring cadence for {@link CadreNode.reconcileControlCohort} (ms).
29
+ * In the same spirit as the 5s strand-watch poll but lighter, since a control
30
+ * cohort changes far less often than strand activity.
31
+ */
32
+ export const DEFAULT_CONTROL_COHORT_RECONCILE_MS = 15_000;
33
+
34
+ /**
35
+ * Default cap on the number of **non-authority** members a single reconcile pass
36
+ * proactively dials. Backbone (authority) members are always dialed and do NOT
37
+ * count against this cap.
38
+ */
39
+ export const DEFAULT_CONTROL_COHORT_TARGET_DEGREE = 6;
40
+
41
+ /** Outcome of {@link selectControlCohortDials}: who to dial + what the cap dropped. */
42
+ export interface ControlCohortSelection {
43
+ /**
44
+ * Siblings to dial this pass, backbone first then the bounded non-authority
45
+ * fill. Already self-excluded (the caller must pass self-excluded `siblings`).
46
+ * Still includes peers that may turn out to be already-connected — the caller
47
+ * diffs against live connections separately.
48
+ */
49
+ dials: CohortPeerRow[];
50
+ /** Non-authority members dropped by the `targetDegree` cap (0 when none). */
51
+ cappedNonAuthority: number;
52
+ }
53
+
54
+ /** Stable peerId ordering so the bounded fill is identical across passes/nodes. */
55
+ function byPeerId(a: CohortPeerRow, b: CohortPeerRow): number {
56
+ if (a.peerId < b.peerId) return -1;
57
+ if (a.peerId > b.peerId) return 1;
58
+ return 0;
59
+ }
60
+
61
+ /**
62
+ * Backbone-preferential, bounded-out-degree selection of control-cohort siblings
63
+ * to dial.
64
+ *
65
+ * A member is **backbone** iff the ed25519 key derived from its peerId
66
+ * (see {@link ed25519PublicKeyB64FromPeerId}) is in `authorityKeys`. All backbone
67
+ * members are selected; non-authority members fill up to `targetDegree` in
68
+ * deterministic peerId order.
69
+ *
70
+ * @param siblings - cadre members EXCLUDING self (the caller filters self out).
71
+ * @param authorityKeys - the converged `AuthorityKey` set; may be empty/partial
72
+ * before authority convergence, in which case no member classifies as backbone
73
+ * yet and the bounded fill still makes progress (preference sharpens later).
74
+ * @param targetDegree - cap on non-authority dials (negative is treated as 0).
75
+ */
76
+ export function selectControlCohortDials(
77
+ siblings: CohortPeerRow[],
78
+ authorityKeys: ReadonlySet<string>,
79
+ targetDegree: number,
80
+ ): ControlCohortSelection {
81
+ const backbone: CohortPeerRow[] = [];
82
+ const rest: CohortPeerRow[] = [];
83
+
84
+ for (const peer of siblings) {
85
+ const pubKey = ed25519PublicKeyB64FromPeerId(peer.peerId);
86
+ if (pubKey !== null && authorityKeys.has(pubKey)) {
87
+ backbone.push(peer);
88
+ } else {
89
+ rest.push(peer);
90
+ }
91
+ }
92
+
93
+ backbone.sort(byPeerId);
94
+ rest.sort(byPeerId);
95
+
96
+ const cap = Math.max(0, targetDegree);
97
+ const fill = rest.slice(0, cap);
98
+
99
+ return {
100
+ dials: [...backbone, ...fill],
101
+ cappedNonAuthority: rest.length - fill.length,
102
+ };
103
+ }