@serfab/cadre-core 0.6.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/dist/authority-key.d.ts +41 -0
- package/dist/authority-key.js +52 -0
- package/dist/authority-key.js.map +1 -0
- package/dist/cadre-node.d.ts +777 -11
- package/dist/cadre-node.js +1912 -82
- package/dist/cadre-node.js.map +1 -1
- package/dist/canonical-datetime.d.ts +33 -0
- package/dist/canonical-datetime.js +38 -0
- package/dist/canonical-datetime.js.map +1 -0
- package/dist/canonical-json.d.ts +12 -0
- package/dist/canonical-json.js +33 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/control-cohort.d.ts +63 -0
- package/dist/control-cohort.js +80 -0
- package/dist/control-cohort.js.map +1 -0
- package/dist/control-database.d.ts +259 -5
- package/dist/control-database.js +541 -128
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +89 -0
- package/dist/control-formation-recorder.js +139 -0
- package/dist/control-formation-recorder.js.map +1 -0
- package/dist/control-schema.d.ts +12 -0
- package/dist/control-schema.js +199 -0
- package/dist/control-schema.js.map +1 -0
- package/dist/control-stream.d.ts +54 -0
- package/dist/control-stream.js +104 -0
- package/dist/control-stream.js.map +1 -0
- package/dist/device-token.d.ts +50 -0
- package/dist/device-token.js +66 -0
- package/dist/device-token.js.map +1 -0
- package/dist/diagnostics/connection-path.d.ts +118 -0
- package/dist/diagnostics/connection-path.js +204 -0
- package/dist/diagnostics/connection-path.js.map +1 -0
- package/dist/diagnostics/webrtc-turn-tracker.d.ts +50 -0
- package/dist/diagnostics/webrtc-turn-tracker.js +196 -0
- package/dist/diagnostics/webrtc-turn-tracker.js.map +1 -0
- package/dist/hibernation-manager.d.ts +79 -0
- package/dist/hibernation-manager.js +173 -23
- package/dist/hibernation-manager.js.map +1 -1
- package/dist/index.d.ts +25 -4
- package/dist/index.js +59 -4
- package/dist/index.js.map +1 -1
- package/dist/key-store-file.d.ts +52 -0
- package/dist/key-store-file.js +194 -0
- package/dist/key-store-file.js.map +1 -0
- package/dist/key-store.d.ts +75 -0
- package/dist/key-store.js +66 -0
- package/dist/key-store.js.map +1 -0
- package/dist/peer-authorization.d.ts +26 -0
- package/dist/peer-authorization.js +40 -0
- package/dist/peer-authorization.js.map +1 -0
- package/dist/peer-record.d.ts +89 -0
- package/dist/peer-record.js +117 -0
- package/dist/peer-record.js.map +1 -0
- package/dist/push-credentials.d.ts +34 -0
- package/dist/push-credentials.js +81 -0
- package/dist/push-credentials.js.map +1 -0
- package/dist/push-fanout.d.ts +137 -0
- package/dist/push-fanout.js +197 -0
- package/dist/push-fanout.js.map +1 -0
- package/dist/push-notifier-apns.d.ts +53 -0
- package/dist/push-notifier-apns.js +195 -0
- package/dist/push-notifier-apns.js.map +1 -0
- package/dist/push-notifier-fcm.d.ts +38 -0
- package/dist/push-notifier-fcm.js +183 -0
- package/dist/push-notifier-fcm.js.map +1 -0
- package/dist/push-notifier-shared.d.ts +18 -0
- package/dist/push-notifier-shared.js +25 -0
- package/dist/push-notifier-shared.js.map +1 -0
- package/dist/push-notifier.d.ts +71 -0
- package/dist/push-notifier.js +50 -0
- package/dist/push-notifier.js.map +1 -0
- package/dist/schema-verification.d.ts +11 -2
- package/dist/schema-verification.js +15 -4
- package/dist/schema-verification.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +210 -7
- package/dist/seed-bootstrap.js +429 -131
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +63 -0
- package/dist/seed-trust-policy.js +56 -0
- package/dist/seed-trust-policy.js.map +1 -0
- package/dist/strand-addr-protocol.d.ts +132 -0
- package/dist/strand-addr-protocol.js +313 -0
- package/dist/strand-addr-protocol.js.map +1 -0
- package/dist/strand-cohort.d.ts +54 -0
- package/dist/strand-cohort.js +38 -0
- package/dist/strand-cohort.js.map +1 -0
- package/dist/strand-database.d.ts +54 -6
- package/dist/strand-database.js +88 -89
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +79 -19
- package/dist/strand-formation-manager.js +196 -104
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +162 -0
- package/dist/strand-formation-protocol.js +279 -0
- package/dist/strand-formation-protocol.js.map +1 -0
- package/dist/strand-instance-manager.d.ts +73 -4
- package/dist/strand-instance-manager.js +167 -41
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +29 -0
- package/dist/strand-member-key.js +38 -0
- package/dist/strand-member-key.js.map +1 -0
- package/dist/strand-member-registry.d.ts +98 -0
- package/dist/strand-member-registry.js +129 -0
- package/dist/strand-member-registry.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +361 -0
- package/dist/strand-membership-writer.js +449 -0
- package/dist/strand-membership-writer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +77 -2
- package/dist/strand-solicitation.js +25 -4
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-payload.d.ts +30 -0
- package/dist/strand-wake-payload.js +20 -0
- package/dist/strand-wake-payload.js.map +1 -0
- package/dist/strand-wake-protocol.d.ts +114 -0
- package/dist/strand-wake-protocol.js +257 -0
- package/dist/strand-wake-protocol.js.map +1 -0
- package/dist/strand-watcher.d.ts +6 -2
- package/dist/strand-watcher.js +57 -17
- package/dist/strand-watcher.js.map +1 -1
- package/dist/types.d.ts +413 -23
- package/dist/types.js +22 -5
- package/dist/types.js.map +1 -1
- package/package.json +14 -8
- package/src/authority-key.ts +68 -0
- package/src/cadre-node.ts +2937 -929
- package/src/canonical-datetime.ts +39 -0
- package/src/canonical-json.ts +30 -0
- package/src/control-cohort.ts +103 -0
- package/src/control-database.ts +931 -383
- package/src/control-formation-recorder.ts +151 -0
- package/src/control-schema.ts +198 -0
- package/src/control-stream.ts +128 -0
- package/src/device-token.ts +89 -0
- package/src/diagnostics/connection-path.ts +277 -0
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -0
- package/src/hibernation-manager.ts +197 -28
- package/src/index.ts +273 -79
- package/src/key-store-file.ts +203 -0
- package/src/key-store.ts +99 -0
- package/src/peer-authorization.ts +53 -0
- package/src/peer-record.ts +146 -0
- package/src/push-credentials.ts +84 -0
- package/src/push-fanout.ts +272 -0
- package/src/push-notifier-apns.ts +243 -0
- package/src/push-notifier-fcm.ts +221 -0
- package/src/push-notifier-shared.ts +28 -0
- package/src/push-notifier.ts +93 -0
- package/src/schema-verification.ts +18 -4
- package/src/seed-bootstrap.ts +504 -171
- package/src/seed-trust-policy.ts +107 -0
- package/src/strand-addr-protocol.ts +410 -0
- package/src/strand-cohort.ts +77 -0
- package/src/strand-database.ts +113 -126
- package/src/strand-formation-manager.ts +233 -142
- package/src/strand-formation-protocol.ts +391 -0
- package/src/strand-instance-manager.ts +207 -56
- package/src/strand-member-key.ts +39 -0
- package/src/strand-member-registry.ts +177 -0
- package/src/strand-membership-writer.ts +640 -0
- package/src/strand-solicitation.ts +93 -4
- package/src/strand-wake-payload.ts +32 -0
- package/src/strand-wake-protocol.ts +331 -0
- package/src/strand-watcher.ts +61 -17
- package/src/types.ts +1012 -575
|
@@ -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
|
+
}
|