@serfab/cadre-core 0.13.0 → 1.0.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 +20 -6
- package/dist/bootstrap-peer-store-file.js +2 -2
- package/dist/bootstrap-peer-store.d.ts +32 -12
- package/dist/bootstrap-peer-store.js +41 -15
- package/dist/bootstrap-peer-store.js.map +1 -1
- package/dist/cadre-node.d.ts +558 -80
- package/dist/cadre-node.js +1060 -170
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +2 -2
- package/dist/control-authorization.js +1 -0
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +13 -26
- package/dist/control-cohort.js +0 -26
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +299 -44
- package/dist/control-database.js +469 -69
- package/dist/control-database.js.map +1 -1
- package/dist/control-read-retry.d.ts +40 -11
- package/dist/control-read-retry.js +48 -15
- package/dist/control-read-retry.js.map +1 -1
- package/dist/control-retry.d.ts +58 -3
- package/dist/control-retry.js +58 -3
- package/dist/control-retry.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +103 -19
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +80 -11
- package/dist/control-write-retry.js +168 -29
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store.js +2 -2
- package/dist/index.d.ts +14 -7
- package/dist/index.js +33 -6
- package/dist/index.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +15 -7
- package/dist/node-local-snapshot.js +23 -8
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/peer-dial.d.ts +143 -0
- package/dist/peer-dial.js +176 -0
- package/dist/peer-dial.js.map +1 -0
- package/dist/relay-addrs.d.ts +90 -52
- package/dist/relay-addrs.js +91 -59
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +71 -3
- package/dist/relay-reservation.js +116 -8
- package/dist/relay-reservation.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +30 -1
- package/dist/seed-bootstrap.js +62 -17
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/storage-scope.d.ts +62 -0
- package/dist/storage-scope.js +75 -0
- package/dist/storage-scope.js.map +1 -0
- package/dist/strand-database.d.ts +34 -16
- package/dist/strand-database.js +38 -24
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +152 -0
- package/dist/strand-first-sync-gate.js +208 -0
- package/dist/strand-first-sync-gate.js.map +1 -0
- package/dist/strand-formation-manager.d.ts +87 -5
- package/dist/strand-formation-manager.js +109 -5
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +21 -1
- package/dist/strand-formation-protocol.js +30 -0
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +243 -6
- package/dist/strand-instance-manager.js +470 -37
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-key.d.ts +20 -12
- package/dist/strand-member-key.js +20 -12
- package/dist/strand-member-key.js.map +1 -1
- package/dist/strand-member-registry.js +3 -0
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +361 -0
- package/dist/strand-membership-reconciler.js +548 -0
- package/dist/strand-membership-reconciler.js.map +1 -0
- package/dist/strand-membership-writer.d.ts +131 -11
- package/dist/strand-membership-writer.js +396 -134
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +46 -14
- package/dist/strand-network-config.js +79 -23
- package/dist/strand-network-config.js.map +1 -1
- package/dist/strand-solicitation.d.ts +10 -1
- package/dist/strand-solicitation.js +5 -0
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +11 -7
- package/dist/strand-wake-protocol.js +20 -52
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.js +6 -0
- package/dist/strand-watcher.js.map +1 -1
- package/dist/timed-step.d.ts +12 -0
- package/dist/timed-step.js +28 -0
- package/dist/timed-step.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +19 -0
- package/dist/timeout-scheduler.js +22 -0
- package/dist/timeout-scheduler.js.map +1 -0
- package/dist/types.d.ts +262 -30
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/bootstrap-peer-store-file.ts +2 -2
- package/src/bootstrap-peer-store.ts +69 -27
- package/src/cadre-node.ts +7301 -6353
- package/src/control-authorization.ts +2 -1
- package/src/control-cohort.ts +122 -130
- package/src/control-database.ts +537 -69
- package/src/control-read-retry.ts +202 -167
- package/src/control-retry.ts +297 -190
- package/src/control-schema.ts +103 -19
- package/src/control-write-retry.ts +525 -383
- package/src/enrolled-machine-store.ts +2 -2
- package/src/index.ts +83 -6
- package/src/node-local-snapshot.ts +25 -8
- package/src/peer-dial.ts +215 -0
- package/src/relay-addrs.ts +458 -436
- package/src/relay-reservation.ts +951 -828
- package/src/seed-bootstrap.ts +76 -18
- package/src/storage-scope.ts +78 -0
- package/src/strand-database.ts +50 -27
- package/src/strand-first-sync-gate.ts +258 -0
- package/src/strand-formation-manager.ts +140 -5
- package/src/strand-formation-protocol.ts +43 -1
- package/src/strand-instance-manager.ts +577 -37
- package/src/strand-member-key.ts +20 -12
- package/src/strand-member-registry.ts +3 -0
- package/src/strand-membership-reconciler.ts +680 -0
- package/src/strand-membership-writer.ts +527 -189
- package/src/strand-network-config.ts +102 -24
- package/src/strand-solicitation.ts +16 -1
- package/src/strand-wake-protocol.ts +21 -66
- package/src/strand-watcher.ts +6 -0
- package/src/timed-step.ts +28 -0
- package/src/timeout-scheduler.ts +28 -0
- package/src/types.ts +1789 -1554
|
@@ -38,6 +38,7 @@ export const CONTROL_TABLES = [
|
|
|
38
38
|
'OwnerKey',
|
|
39
39
|
'ValidationKey',
|
|
40
40
|
'Strand',
|
|
41
|
+
'StrandPartyKey',
|
|
41
42
|
'CadrePeer',
|
|
42
43
|
'DeviceToken',
|
|
43
44
|
'FormationInvite',
|
|
@@ -63,7 +64,7 @@ export type ControlTable = typeof CONTROL_TABLES[number];
|
|
|
63
64
|
* (`control-database.ts`) so the lightweight signers — `peer-authorization.ts`'s
|
|
64
65
|
* `revocationDigest` — can type against it without pulling in the runtime.
|
|
65
66
|
*/
|
|
66
|
-
export type RevocableTable = Extract<ControlTable, 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | 'DeviceToken'>;
|
|
67
|
+
export type RevocableTable = Extract<ControlTable, 'OwnerKey' | 'CadrePeer' | 'ValidationKey' | 'Strand' | 'StrandPartyKey' | 'DeviceToken'>;
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* What a signature authorizes, scoped to one table rule — or, for
|
package/src/control-cohort.ts
CHANGED
|
@@ -1,130 +1,122 @@
|
|
|
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/owner 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 (owner) member** — the owner set is
|
|
13
|
-
* small by design, and routing cohort formation through publicly-dialable
|
|
14
|
-
* owner nodes keeps the cohort ≥2 where owner-signed writes originate.
|
|
15
|
-
* - **Fill the remainder** up to `targetDegree` with non-owner 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-owner** members a single reconcile pass
|
|
36
|
-
* proactively dials. Backbone (owner) members are always dialed and do NOT
|
|
37
|
-
* count against this cap.
|
|
38
|
-
*/
|
|
39
|
-
export const DEFAULT_CONTROL_COHORT_TARGET_DEGREE = 6;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const cap = Math.max(0, targetDegree);
|
|
124
|
-
const fill = rest.slice(0, cap);
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
dials: [...backbone, ...fill],
|
|
128
|
-
cappedNonOwner: rest.length - fill.length,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
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/owner 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 (owner) member** — the owner set is
|
|
13
|
+
* small by design, and routing cohort formation through publicly-dialable
|
|
14
|
+
* owner nodes keeps the cohort ≥2 where owner-signed writes originate.
|
|
15
|
+
* - **Fill the remainder** up to `targetDegree` with non-owner 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-owner** members a single reconcile pass
|
|
36
|
+
* proactively dials. Backbone (owner) members are always dialed and do NOT
|
|
37
|
+
* count against this cap.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_CONTROL_COHORT_TARGET_DEGREE = 6;
|
|
40
|
+
|
|
41
|
+
// The per-sibling and per-address dial time limits live in `peer-dial.ts`
|
|
42
|
+
// (`DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS`,
|
|
43
|
+
// `DEFAULT_CONTROL_COHORT_PER_ADDRESS_DIAL_TIMEOUT_MS`), beside the dial they
|
|
44
|
+
// bound, which `seed-bootstrap.ts` also uses and cannot import from here.
|
|
45
|
+
|
|
46
|
+
/** Outcome of {@link selectControlCohortDials}: who to dial + what the cap dropped. */
|
|
47
|
+
export interface ControlCohortSelection {
|
|
48
|
+
/**
|
|
49
|
+
* Siblings to dial this pass, backbone first then the bounded non-owner
|
|
50
|
+
* fill. Already self-excluded (the caller must pass self-excluded `siblings`).
|
|
51
|
+
* Still includes peers that may turn out to be already-connected — the caller
|
|
52
|
+
* diffs against live connections separately.
|
|
53
|
+
*/
|
|
54
|
+
dials: CohortPeerRow[];
|
|
55
|
+
/** Non-owner members dropped by the `targetDegree` cap (0 when none). */
|
|
56
|
+
cappedNonOwner: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** What one {@link CadreNode.reconcileControlCohort} pass did. */
|
|
60
|
+
export interface ControlCohortReconcileResult {
|
|
61
|
+
/**
|
|
62
|
+
* Peer ids this pass dialled and whose dial resolved, in dial order: selected
|
|
63
|
+
* siblings on the steady-state pass, retained bootstrap peers on the cold-start
|
|
64
|
+
* pass (the pass takes one branch or the other, never both). A peer already
|
|
65
|
+
* connected when the pass began is never listed — the pass skips it. A resolved
|
|
66
|
+
* dial can still be refused moments later by the remote's membership gate, so
|
|
67
|
+
* an entry means "this pass opened a connection", not "that connection is still
|
|
68
|
+
* open".
|
|
69
|
+
*/
|
|
70
|
+
dialed: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Stable peerId ordering so the bounded fill is identical across passes/nodes. */
|
|
74
|
+
function byPeerId(a: CohortPeerRow, b: CohortPeerRow): number {
|
|
75
|
+
if (a.peerId < b.peerId) return -1;
|
|
76
|
+
if (a.peerId > b.peerId) return 1;
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Backbone-preferential, bounded-out-degree selection of control-cohort siblings
|
|
82
|
+
* to dial.
|
|
83
|
+
*
|
|
84
|
+
* A member is **backbone** iff the ed25519 key derived from its peerId
|
|
85
|
+
* (see {@link ed25519PublicKeyB64FromPeerId}) is in `ownerKeys`. All backbone
|
|
86
|
+
* members are selected; non-owner members fill up to `targetDegree` in
|
|
87
|
+
* deterministic peerId order.
|
|
88
|
+
*
|
|
89
|
+
* @param siblings - cadre members EXCLUDING self (the caller filters self out).
|
|
90
|
+
* @param ownerKeys - the converged `OwnerKey` set; may be empty/partial
|
|
91
|
+
* before owner convergence, in which case no member classifies as backbone
|
|
92
|
+
* yet and the bounded fill still makes progress (preference sharpens later).
|
|
93
|
+
* @param targetDegree - cap on non-owner dials (negative is treated as 0).
|
|
94
|
+
*/
|
|
95
|
+
export function selectControlCohortDials(
|
|
96
|
+
siblings: CohortPeerRow[],
|
|
97
|
+
ownerKeys: ReadonlySet<string>,
|
|
98
|
+
targetDegree: number,
|
|
99
|
+
): ControlCohortSelection {
|
|
100
|
+
const backbone: CohortPeerRow[] = [];
|
|
101
|
+
const rest: CohortPeerRow[] = [];
|
|
102
|
+
|
|
103
|
+
for (const peer of siblings) {
|
|
104
|
+
const pubKey = ed25519PublicKeyB64FromPeerId(peer.peerId);
|
|
105
|
+
if (pubKey !== null && ownerKeys.has(pubKey)) {
|
|
106
|
+
backbone.push(peer);
|
|
107
|
+
} else {
|
|
108
|
+
rest.push(peer);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
backbone.sort(byPeerId);
|
|
113
|
+
rest.sort(byPeerId);
|
|
114
|
+
|
|
115
|
+
const cap = Math.max(0, targetDegree);
|
|
116
|
+
const fill = rest.slice(0, cap);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
dials: [...backbone, ...fill],
|
|
120
|
+
cappedNonOwner: rest.length - fill.length,
|
|
121
|
+
};
|
|
122
|
+
}
|