@serfab/cadre-core 0.12.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 +23 -8
- 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 +883 -102
- package/dist/cadre-node.js +1800 -209
- 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 +318 -44
- package/dist/control-database.js +516 -74
- 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 +133 -21
- 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-file.d.ts +13 -0
- package/dist/enrolled-machine-store-file.js +41 -0
- package/dist/enrolled-machine-store-file.js.map +1 -0
- package/dist/enrolled-machine-store.d.ts +81 -0
- package/dist/enrolled-machine-store.js +225 -0
- package/dist/enrolled-machine-store.js.map +1 -0
- package/dist/file-durable-slot.js +5 -3
- package/dist/file-durable-slot.js.map +1 -1
- package/dist/index.d.ts +17 -8
- package/dist/index.js +54 -9
- package/dist/index.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +15 -1
- package/dist/membership-connection-gater.js +9 -3
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +21 -7
- package/dist/node-local-snapshot.js +30 -9
- 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/{strand-backfill.d.ts → peer-join-backfill.d.ts} +49 -20
- package/dist/{strand-backfill.js → peer-join-backfill.js} +133 -52
- package/dist/peer-join-backfill.js.map +1 -0
- package/dist/relay-addrs.d.ts +160 -49
- package/dist/relay-addrs.js +287 -60
- 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 +41 -11
- package/dist/strand-database.js +48 -18
- 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 +99 -5
- package/dist/strand-formation-manager.js +119 -8
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +97 -3
- package/dist/strand-formation-protocol.js +108 -2
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +389 -19
- package/dist/strand-instance-manager.js +678 -65
- 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 +143 -11
- package/dist/strand-membership-writer.js +419 -135
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +116 -0
- package/dist/strand-network-config.js +192 -0
- package/dist/strand-network-config.js.map +1 -0
- package/dist/strand-revocation-enforcer.d.ts +392 -0
- package/dist/strand-revocation-enforcer.js +533 -0
- package/dist/strand-revocation-enforcer.js.map +1 -0
- package/dist/strand-solicitation.d.ts +17 -1
- package/dist/strand-solicitation.js +13 -2
- 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 +12 -1
- 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 +472 -46
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -8
- package/src/bootstrap-peer-store-file.ts +2 -2
- package/src/bootstrap-peer-store.ts +69 -27
- package/src/cadre-node.ts +1926 -228
- package/src/control-authorization.ts +2 -1
- package/src/control-cohort.ts +122 -130
- package/src/control-database.ts +586 -74
- package/src/control-read-retry.ts +202 -167
- package/src/control-retry.ts +297 -190
- package/src/control-schema.ts +133 -21
- package/src/control-write-retry.ts +525 -383
- package/src/enrolled-machine-store-file.ts +43 -0
- package/src/enrolled-machine-store.ts +280 -0
- package/src/file-durable-slot.ts +5 -3
- package/src/index.ts +134 -15
- package/src/membership-connection-gater.ts +9 -3
- package/src/node-local-snapshot.ts +38 -9
- package/src/peer-addr-book.ts +251 -250
- package/src/peer-dial.ts +215 -0
- package/src/{strand-backfill.ts → peer-join-backfill.ts} +478 -389
- package/src/relay-addrs.ts +458 -218
- 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 +61 -21
- package/src/strand-first-sync-gate.ts +258 -0
- package/src/strand-formation-manager.ts +162 -8
- package/src/strand-formation-protocol.ts +850 -692
- package/src/strand-instance-manager.ts +877 -74
- 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 +550 -190
- package/src/strand-network-config.ts +250 -0
- package/src/strand-revocation-enforcer.ts +653 -0
- package/src/strand-solicitation.ts +499 -471
- package/src/strand-wake-protocol.ts +21 -66
- package/src/strand-watcher.ts +325 -314
- package/src/timed-step.ts +28 -0
- package/src/timeout-scheduler.ts +28 -0
- package/src/types.ts +487 -52
- package/dist/strand-backfill.js.map +0 -1
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import type { PrivateKey } from '@libp2p/interface';
|
|
2
|
-
import { type
|
|
2
|
+
import { type PeerJoinBackfillConfig } from './peer-join-backfill.js';
|
|
3
|
+
import { type StrandRevocationEnforcementConfig } from './strand-revocation-enforcer.js';
|
|
4
|
+
import { type PendingMembershipInviteSource, type StrandMembershipReconciliationConfig } from './strand-membership-reconciler.js';
|
|
5
|
+
import { type StrandFirstSyncConfig } from './strand-first-sync-gate.js';
|
|
3
6
|
import type { StrandInstance, StrandRow, StorageConfig, NetworkConfig, LatencyHint, NodeProfile, SAppConfig } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Whether a tracked instance is launched but waiting for its first sync: the runtime is
|
|
9
|
+
* up (`libp2pNode` set) while the database is still withheld (`database` unset). That
|
|
10
|
+
* is the first-sync gate's state regardless of what `status` says — the hibernation
|
|
11
|
+
* manager's idle timer can flip a long-gated joiner to `'idle'` without changing it.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: `status` is therefore not the source of truth for "gated": a joiner that reaches
|
|
14
|
+
* nobody for `idleTimeout` (5 min at the interactive hint) reads `'idle'`, then
|
|
15
|
+
* `'hibernating'`, while still holding no database. Harmless today — every decision the
|
|
16
|
+
* runtime makes goes through this predicate, {@link StrandInstanceManager.publishDatabase}
|
|
17
|
+
* announces on the idle-flipped instance exactly as on a `'syncing'` one, and an app that
|
|
18
|
+
* shows "waiting for the other member" on `'syncing'` merely sees "idle" instead after
|
|
19
|
+
* five minutes. If an app ever needs the gated LABEL to survive the idle flip, make the
|
|
20
|
+
* hibernation manager's idle transition preserve `'syncing'` rather than adding a flag.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isAwaitingFirstSync(instance: StrandInstance): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The status a LIVE instance (runtime rebuilt or woken) should report: `'active'` once its
|
|
25
|
+
* database is published, `'syncing'` while the first-sync gate still withholds it. Every
|
|
26
|
+
* site that would otherwise write `'active'` after a build or a wake goes through this,
|
|
27
|
+
* so a gated joiner never reads as writable.
|
|
28
|
+
*/
|
|
29
|
+
export declare function liveStrandStatus(instance: StrandInstance): 'active' | 'syncing';
|
|
4
30
|
/**
|
|
5
31
|
* Configuration for starting a strand instance
|
|
6
32
|
*/
|
|
@@ -24,10 +50,25 @@ export interface StartStrandConfig {
|
|
|
24
50
|
/**
|
|
25
51
|
* Whether this node founds the strand (vs. joins it). Forwarded to the
|
|
26
52
|
* StrandDatabase so the founder bootstrap (Header, founding Member/Manager)
|
|
27
|
-
* runs once at bring-up. Joiners leave this unset and write nothing.
|
|
28
|
-
*
|
|
53
|
+
* runs once at bring-up. Joiners leave this unset and write nothing. Callers
|
|
54
|
+
* resolve it BEFORE calling `startStrand` (`CadreNode.launchStrand` derives it
|
|
55
|
+
* from the row's `FounderOwnerKey` when no explicit flag is given — see
|
|
56
|
+
* {@link StrandConfig.founder}); a founder request that arrives while the
|
|
57
|
+
* instance is already tracked goes through {@link StrandInstanceManager.foundExistingStrand},
|
|
58
|
+
* never through a repeat `startStrand`.
|
|
29
59
|
*/
|
|
30
60
|
founder?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* THIS party's own strand membership private key (base64 protobuf) for a closed
|
|
63
|
+
* strand — the identity the founder bootstrap derives `Member.Key` /
|
|
64
|
+
* `Manager.MemberKey` from. Resolved by the caller (`CadreNode.launchStrand`: an
|
|
65
|
+
* explicit attach-time key, else the control-layer `StrandPartyKey` row, healed by
|
|
66
|
+
* mint-on-launch for the founding machine) and retained with the launch config, so a
|
|
67
|
+
* hibernation wake rebuilds under the same identity. Deliberately NOT the strand
|
|
68
|
+
* row's shared `MemberPrivateKey` (see `StrandDatabaseConfig.partyMemberPrivateKey`).
|
|
69
|
+
* Absent for open strands, and for joiners with no persisted party key.
|
|
70
|
+
*/
|
|
71
|
+
partyMemberPrivateKey?: string;
|
|
31
72
|
/**
|
|
32
73
|
* Number of nodes Optimystic is told this strand's replication cluster should
|
|
33
74
|
* have. Same rule as {@link CadreNodeConfig.strandClusterSize}, which CadreNode
|
|
@@ -39,23 +80,136 @@ export interface StartStrandConfig {
|
|
|
39
80
|
*/
|
|
40
81
|
clusterSize?: number;
|
|
41
82
|
/**
|
|
42
|
-
*
|
|
83
|
+
* Machines that SERVE THIS STRAND (this node included), for the strand node's
|
|
84
|
+
* block-repair corroboration yardstick — see `resolveRepairYardstick`. It must be
|
|
85
|
+
* an authenticated per-strand count: the machines that actually run a node for this
|
|
86
|
+
* strand, not the machines that exist.
|
|
87
|
+
*
|
|
88
|
+
* **The party's enrolled-machine count is NOT this number and must never be passed
|
|
89
|
+
* here.** A strand launches only on machines whose embedding app registered its sApp
|
|
90
|
+
* config (`CadreNode.addStrand`), so a closed strand shared by two machines of a
|
|
91
|
+
* three-machine party is served by two. Passing three over-declares, and
|
|
92
|
+
* over-declaring is the unsafe direction: at a declaration of 3 or more Optimystic
|
|
93
|
+
* pins the repair corroboration floor at two corroborating peers, which a cohort
|
|
94
|
+
* that can only ever field one peer can never reach — so the strand can never repair
|
|
95
|
+
* a block (`cluster-fetch:no-quorum`, surfacing as reads failing with `Missing
|
|
96
|
+
* block`). That regression is why this field was renamed off "enrolledMachines":
|
|
97
|
+
* `bug-strand-yardstick-counts-party-machines`.
|
|
98
|
+
*
|
|
99
|
+
* **Nothing feeds it in production today.** No authenticated per-strand serving count
|
|
100
|
+
* exists yet, so `CadreNode` passes nothing and the strand node runs the frozen
|
|
101
|
+
* `STRAND_CLUSTER_POLICY` — declaring no yardstick, which leaves the known,
|
|
102
|
+
* upstream-tracked single-voter exposure
|
|
103
|
+
* (`backlog/debt-read-repair-single-voter-corroboration`). Building the count is
|
|
104
|
+
* `backlog/feat-strand-yardstick-from-serving-machines`; this field and its
|
|
105
|
+
* threading are the seam it plugs into.
|
|
106
|
+
*
|
|
107
|
+
* Volatile when a source does exist: re-resolve it on every resume beside the cohort
|
|
108
|
+
* seed, since machines join and leave a strand while it hibernates. Omitting it means
|
|
109
|
+
* "this node does not know", which declares nothing.
|
|
110
|
+
*/
|
|
111
|
+
servingMachines?: number;
|
|
112
|
+
/**
|
|
113
|
+
* Tuning for the strand peer-join block catch-up ({@link PeerJoinBackfill}),
|
|
43
114
|
* forwarded from {@link CadreNodeConfig.strandBackfill}. When the strand's
|
|
44
115
|
* libp2p node connects to a peer this runtime has not yet caught up, every
|
|
45
116
|
* block in the strand's own raw store is pushed to it. Runs only on strands
|
|
46
117
|
* with per-strand storage; `{ enabled: false }` restores the pre-existing
|
|
47
118
|
* no-backfill behaviour.
|
|
48
119
|
*/
|
|
49
|
-
backfill?:
|
|
120
|
+
backfill?: PeerJoinBackfillConfig;
|
|
121
|
+
/**
|
|
122
|
+
* Tuning for the CLOSED-strand revoked-peer gate
|
|
123
|
+
* ({@link StrandRevocationEnforcer}), forwarded from
|
|
124
|
+
* {@link CadreNodeConfig.strandRevocationEnforcement}. Armed only for
|
|
125
|
+
* `strandRow.Type === 'c'` (an open strand has no membership rows to derive a
|
|
126
|
+
* deny set from); `{ enabled: false }` restores the pre-existing behaviour
|
|
127
|
+
* (a removed party's peers keep being served).
|
|
128
|
+
*/
|
|
129
|
+
revocationEnforcement?: StrandRevocationEnforcementConfig;
|
|
130
|
+
/**
|
|
131
|
+
* The staged formation membership invitation for THIS strand — the seam the
|
|
132
|
+
* bring-up membership reconciler (`strand-membership-reconciler.ts`) reads to
|
|
133
|
+
* redeem the party's `Strand.Member` seat, and clears once the invitation is
|
|
134
|
+
* spent, burned, or dead. `CadreNode.launchStrand` wires it over its in-memory
|
|
135
|
+
* `pendingMembershipInvites` map; read lazily per pass, so a re-formation that
|
|
136
|
+
* replaces the entry between passes is picked up. Only consulted for closed
|
|
137
|
+
* strands launched with a {@link partyMemberPrivateKey}; harmless otherwise.
|
|
138
|
+
*/
|
|
139
|
+
pendingMembershipInvite?: PendingMembershipInviteSource;
|
|
140
|
+
/**
|
|
141
|
+
* Tuning for the CLOSED-strand membership reconciler, forwarded from
|
|
142
|
+
* {@link CadreNodeConfig.strandMembershipReconciliation}. Armed only for
|
|
143
|
+
* closed strands launched with a {@link partyMemberPrivateKey};
|
|
144
|
+
* `{ enabled: false }` disarms the loop (test fixtures that hand-drive the
|
|
145
|
+
* membership writers). When it names no `pollIntervalMs` the reconciler
|
|
146
|
+
* mirrors {@link revocationEnforcement}'s cadence — as its IDLE cadence and
|
|
147
|
+
* the cap of its unfinished-join retry ladder, not as a flat retry interval.
|
|
148
|
+
*/
|
|
149
|
+
membershipReconciliation?: StrandMembershipReconciliationConfig;
|
|
150
|
+
/**
|
|
151
|
+
* Called when THIS node's own peer id turns up in the strand's revoked set —
|
|
152
|
+
* this node's party was removed from the closed strand, or left it. Fires once
|
|
153
|
+
* per removal (re-armed if the party is re-admitted; a resume also rebuilds the
|
|
154
|
+
* enforcer and may legitimately re-fire), and only for closed strands with the
|
|
155
|
+
* gate armed. `CadreNode`
|
|
156
|
+
* wires it to its `strand:revoked` event; nothing is stopped or torn down on
|
|
157
|
+
* this node's behalf — what to do about it is the app's call.
|
|
158
|
+
*/
|
|
159
|
+
onSelfRevoked?: (strandId: string) => void;
|
|
160
|
+
/**
|
|
161
|
+
* Tuning for the JOINING machine's first-sync write gate (`strand-first-sync-gate.ts`),
|
|
162
|
+
* forwarded from {@link CadreNodeConfig.strandFirstSync}: the Header probe cadence while
|
|
163
|
+
* a non-founder launch is `'syncing'`, and the default budget {@link whenWritable} waits
|
|
164
|
+
* before rejecting. Founders and machines that already hold the Header are never gated.
|
|
165
|
+
*/
|
|
166
|
+
firstSync?: StrandFirstSyncConfig;
|
|
167
|
+
/**
|
|
168
|
+
* Called when a launch that came up `'syncing'` (see {@link firstSync}) becomes
|
|
169
|
+
* writable: the strand's `Strand.Header` arrived from a peer — or a founder request
|
|
170
|
+
* ran the bootstrap against the gated database — and `instance.database` is now set.
|
|
171
|
+
* Never called for a launch that was writable when `startStrand`/`resumeStrand`
|
|
172
|
+
* resolved. `CadreNode` wires it to its `strand:writable` event. Retained with the
|
|
173
|
+
* launch config, so a hibernation resume that comes up gated announces too.
|
|
174
|
+
*/
|
|
175
|
+
onWritable?: (strandId: string) => void;
|
|
176
|
+
/**
|
|
177
|
+
* Re-announce this strand's delegate peer id (`delegatePeerId` — the strand
|
|
178
|
+
* node's own transport peer id) to ONE relay (`relayAddr`, a direct dial addr
|
|
179
|
+
* with a trailing `/p2p/<relayPeerId>`), unthrottled. Awaited before every
|
|
180
|
+
* relay-reservation RE-drive of that relay — never before the first attempt,
|
|
181
|
+
* which the launch/resume seed pass has already announced for.
|
|
182
|
+
*
|
|
183
|
+
* Why: a party control node running the relay server admits the strand node on
|
|
184
|
+
* an in-memory delegate grant that a relay restart drops, so a re-drive that did
|
|
185
|
+
* not re-announce first would be denied at the relay's gate. `CadreNode` wires
|
|
186
|
+
* its own control-mesh announce here; a throw or rejection is logged by the
|
|
187
|
+
* supervisor and the re-drive still runs. Retained with the launch config, so a
|
|
188
|
+
* hibernation wake's rebuilt supervisors carry it too.
|
|
189
|
+
*/
|
|
190
|
+
announceDelegateToRelay?: (strandId: string, relayAddr: string, delegatePeerId: string) => Promise<void>;
|
|
50
191
|
}
|
|
51
192
|
/**
|
|
52
|
-
* Volatile inputs re-resolved when resuming a quiesced strand
|
|
53
|
-
*
|
|
54
|
-
*
|
|
193
|
+
* Volatile inputs re-resolved when resuming a quiesced strand — the ones that can
|
|
194
|
+
* have moved since it last ran, and that the rebuilt libp2p node freezes again. Each
|
|
195
|
+
* one omitted keeps the value the retained launch config already holds, so a resume
|
|
196
|
+
* that passes nothing rebuilds the strand exactly as it last ran.
|
|
55
197
|
*/
|
|
56
198
|
export interface ResumeStrandOverrides {
|
|
57
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* Freshly-resolved cohort discovery seed (multiaddr strings). Grows as peers are
|
|
201
|
+
* learned since the strand first launched.
|
|
202
|
+
*/
|
|
58
203
|
bootstrapNodes?: string[];
|
|
204
|
+
/**
|
|
205
|
+
* Freshly-read count of the machines serving this strand, for the repair yardstick
|
|
206
|
+
* (see {@link StartStrandConfig.servingMachines} — including why the party's
|
|
207
|
+
* enrolled-machine count is not it). Moves whenever a machine starts or stops
|
|
208
|
+
* serving the strand, which a hibernating strand does not otherwise notice. Nothing
|
|
209
|
+
* passes it today. Omitting it here does NOT mean "unknown" the way omitting it from
|
|
210
|
+
* {@link StartStrandConfig} does — it retains the last value; see `resumeStrand`.
|
|
211
|
+
*/
|
|
212
|
+
servingMachines?: number;
|
|
59
213
|
}
|
|
60
214
|
/**
|
|
61
215
|
* Get the isolated storage path for a specific strand.
|
|
@@ -93,6 +247,52 @@ export declare class StrandInstanceManager {
|
|
|
93
247
|
* missed writes).
|
|
94
248
|
*/
|
|
95
249
|
private backfills;
|
|
250
|
+
/**
|
|
251
|
+
* The per-strand revoked-peer enforcer (closed strands only), keyed by strand
|
|
252
|
+
* id. Same lifecycle rationale as {@link backfills}: created in
|
|
253
|
+
* `buildStrandRuntime`, stopped and dropped in `releaseRuntime`, so quiesce →
|
|
254
|
+
* resume rebuilds it with a fresh (initially empty, fail-open) snapshot.
|
|
255
|
+
*/
|
|
256
|
+
private revocationEnforcers;
|
|
257
|
+
/**
|
|
258
|
+
* The per-strand membership reconciler (closed strands launched with a party
|
|
259
|
+
* key only), keyed by strand id. Same lifecycle rationale as {@link backfills}:
|
|
260
|
+
* created in `buildStrandRuntime`, stopped and dropped in `releaseRuntime`, so
|
|
261
|
+
* quiesce → resume rebuilds it and re-runs the idempotent join ladder (redeem a
|
|
262
|
+
* staged invitation, write this machine's own `MemberPeer` binding) from scratch.
|
|
263
|
+
*/
|
|
264
|
+
private membershipReconcilers;
|
|
265
|
+
/**
|
|
266
|
+
* The per-strand first-sync gate (`strand-first-sync-gate.ts`), keyed by strand id:
|
|
267
|
+
* holds a launch's `StrandDatabase` while it is NOT yet published on the instance.
|
|
268
|
+
* Present from the moment the database object exists in `buildStrandRuntime` until
|
|
269
|
+
* {@link publishDatabase} hands it to the app — so it doubles as the rollback handle
|
|
270
|
+
* for a failed `initialize()` — and, for a joiner that holds no `Strand.Header` yet,
|
|
271
|
+
* for as long as the launch stays `'syncing'`. Same lifecycle as {@link backfills}:
|
|
272
|
+
* stopped (its database closed) and dropped in `releaseRuntime`, so a quiesce →
|
|
273
|
+
* resume rebuild re-probes over the same store. An entry and `instance.database`
|
|
274
|
+
* are mutually exclusive.
|
|
275
|
+
*/
|
|
276
|
+
private firstSyncGates;
|
|
277
|
+
/**
|
|
278
|
+
* Callers blocked in {@link whenWritable}, keyed by strand id. Resolved by
|
|
279
|
+
* {@link publishDatabase}, rejected by {@link stopStrand}; each waiter also carries
|
|
280
|
+
* its own timeout. Deliberately NOT cleared by `releaseRuntime`: a waiter outlives a
|
|
281
|
+
* quiesce → resume cycle, since the rebuild is what may finally publish the database.
|
|
282
|
+
*/
|
|
283
|
+
private writableWaiters;
|
|
284
|
+
/**
|
|
285
|
+
* The per-strand relay-reservation supervisors — ONE PER CONFIGURED RELAY, each
|
|
286
|
+
* over the node's own bare `/p2p-circuit` listener for that relay
|
|
287
|
+
* (`strand-network-config.ts`) — keyed by strand id. Same lifecycle rationale as
|
|
288
|
+
* {@link backfills}: started in `buildStrandRuntime` right after the libp2p node
|
|
289
|
+
* exists, stopped FIRST and dropped in `releaseRuntime` (before the database
|
|
290
|
+
* closes and the node stops, so no re-drive dials a node being torn down), so
|
|
291
|
+
* quiesce → resume rebuilds them over the new node. What makes a strand node's
|
|
292
|
+
* lost relay slot come back on its own — the control node has the same loop in
|
|
293
|
+
* `CadreNode.reserveRelays`.
|
|
294
|
+
*/
|
|
295
|
+
private relaySupervisors;
|
|
96
296
|
/**
|
|
97
297
|
* The resolved (cache-wrapped) raw storage per strand id — the instance's OWN
|
|
98
298
|
* store, resolved once in `startStrand` and held until `stopStrand` disposes it.
|
|
@@ -148,23 +348,44 @@ export declare class StrandInstanceManager {
|
|
|
148
348
|
* Build (or rebuild) the libp2p node + StrandDatabase for an instance and
|
|
149
349
|
* attach them, transitioning it to `active`. Shared by `startStrand` (fresh
|
|
150
350
|
* launch) and `resumeStrand` (rehydrating a quiesced instance). Reads all
|
|
151
|
-
* volatile inputs (bootstrapNodes, network, profile,
|
|
152
|
-
* from `config`, so the caller controls the
|
|
351
|
+
* volatile inputs (bootstrapNodes, servingMachines, network, profile,
|
|
352
|
+
* privateKey, sApp config) from `config`, so the caller controls the
|
|
353
|
+
* cohort-derived values. Storage is the
|
|
153
354
|
* one input it does NOT re-read from `config`: that belongs to the instance and
|
|
154
355
|
* comes from `strandStorages`.
|
|
155
356
|
*/
|
|
156
357
|
private buildStrandRuntime;
|
|
157
358
|
/**
|
|
158
|
-
* Release an instance's strand-network runtime:
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
359
|
+
* Release an instance's strand-network runtime: stop the relay supervisors and
|
|
360
|
+
* the other background loops, close the StrandDatabase, then stop the libp2p
|
|
361
|
+
* node (construction order in reverse), clearing both fields and zeroing
|
|
362
|
+
* connectedPeers. Tolerant of partially-built state — either handle may be
|
|
363
|
+
* absent — so it doubles as rollback for a failed `buildStrandRuntime`. Shared
|
|
364
|
+
* by `quiesceStrand`, `stopStrand`, and that rollback path.
|
|
163
365
|
*
|
|
164
366
|
* Leaves `strandStorages` untouched by design — the store outlives the runtime it
|
|
165
367
|
* was built into, which is what makes a resume warm. Only `stopStrand` disposes it.
|
|
166
368
|
*/
|
|
167
369
|
private releaseRuntime;
|
|
370
|
+
/**
|
|
371
|
+
* One {@link superviseRelayReservation} per relay dial addr, each over exactly
|
|
372
|
+
* that relay so "held" is judged per relay (`circuitMultiaddrsVia`) and losing
|
|
373
|
+
* one relay re-drives only that one. Default timings — the control node's (2 s
|
|
374
|
+
* doubling to 60 s between failed attempts, a 5 s liveness check while held).
|
|
375
|
+
*
|
|
376
|
+
* Each supervisor's `beforeRedrive` is the caller's
|
|
377
|
+
* {@link StartStrandConfig.announceDelegateToRelay} for THIS relay and THIS
|
|
378
|
+
* node's peer id, so a re-drive after a relay restart is preceded by a fresh
|
|
379
|
+
* delegate grant on the relay about to be dialed. Returns `[]` when no relay is
|
|
380
|
+
* configured — the strand then simply has no circuit listener to fill.
|
|
381
|
+
*/
|
|
382
|
+
private startRelaySupervisors;
|
|
383
|
+
/**
|
|
384
|
+
* Wait for every relay supervisor's first attempt to settle, concurrently, and log
|
|
385
|
+
* the ones that landed nothing. Never throws — a strand whose relay is unreachable
|
|
386
|
+
* at launch still comes up, with its supervisors retrying in the background.
|
|
387
|
+
*/
|
|
388
|
+
private awaitFirstRelayAttempts;
|
|
168
389
|
/**
|
|
169
390
|
* Drop the strand's owned store and release this strand's claim on its cache. The
|
|
170
391
|
* wrapper counts holders, so the cache is emptied and unregistered from the shared
|
|
@@ -177,6 +398,49 @@ export declare class StrandInstanceManager {
|
|
|
177
398
|
* (e.g. `MemoryRawStorage`), so no instanceof test is needed.
|
|
178
399
|
*/
|
|
179
400
|
private disposeStrandStorage;
|
|
401
|
+
/**
|
|
402
|
+
* Refresh a strand's revoked-peer deny set NOW — and, with it, run the
|
|
403
|
+
* teardown sweep that hangs up every connected revoked peer.
|
|
404
|
+
*
|
|
405
|
+
* The gate is otherwise poll-driven (default
|
|
406
|
+
* `DEFAULT_REVOCATION_POLL_INTERVAL_MS`), because neither a replicated
|
|
407
|
+
* revocation nor a local `revokeMember`/`leaveStrand` raises anything this
|
|
408
|
+
* runtime can hook. A caller that just wrote a revocation against the
|
|
409
|
+
* strand's database should follow it with this call, which makes the cut
|
|
410
|
+
* immediate instead of waiting out the interval.
|
|
411
|
+
*
|
|
412
|
+
* Quiet no-op — matching `quiesceStrand`'s posture — when the strand is not
|
|
413
|
+
* tracked, is quiesced, is open, or has the gate disabled: in every one of
|
|
414
|
+
* those cases there is no enforcer to refresh. Never rejects (the enforcer's
|
|
415
|
+
* refresh contains its own failures), and resolves only once the sweep has
|
|
416
|
+
* finished.
|
|
417
|
+
*/
|
|
418
|
+
refreshRevocationEnforcement(strandId: string): Promise<void>;
|
|
419
|
+
/**
|
|
420
|
+
* Best-effort removal of THIS machine's own `Strand.MemberPeer` binding — the
|
|
421
|
+
* self arm of `removeMemberPeer`, signed with the retained launch config's party
|
|
422
|
+
* key. Called by `CadreNode.unpublishStrand` BEFORE the local stop, while the
|
|
423
|
+
* strand database and transport are still live: after the unpublish commits, the
|
|
424
|
+
* party's `StrandPartyKey` control row is gone, so no restart could ever sign
|
|
425
|
+
* this removal again — the in-memory retained key is the last chance.
|
|
426
|
+
*
|
|
427
|
+
* Never throws. Quiet no-op for a strand that is untracked, open, or launched
|
|
428
|
+
* without a party key; a quiesced (hibernating) strand, or a strand whose write
|
|
429
|
+
* quorum is already unreachable, leaves the stale binding behind with a log —
|
|
430
|
+
* it grants nothing today (admission is not allowlist-gated) and only
|
|
431
|
+
* mis-credits diversity. The strand's own membership reconciler is stopped —
|
|
432
|
+
* and its in-flight pass awaited — FIRST, so a pass that was already past its
|
|
433
|
+
* own stopped check cannot re-register the binding this is about to clear.
|
|
434
|
+
*
|
|
435
|
+
* NOTE: deliberately NOT handled here (or anywhere yet): clearing the bindings
|
|
436
|
+
* of a machine removed from the CADRE at the control layer, or of a party's
|
|
437
|
+
* OTHER machines when the party unpublishes — the party still holds the key on
|
|
438
|
+
* every surviving machine and can clear from any of them, and the stale rows
|
|
439
|
+
* grant nothing while admission is deny-list only. Revisit when
|
|
440
|
+
* `feat-strand-member-allowlist-admission` lands and a stale binding starts
|
|
441
|
+
* granting admission rather than merely mis-crediting diversity.
|
|
442
|
+
*/
|
|
443
|
+
clearOwnMemberPeerBinding(strandId: string): Promise<void>;
|
|
180
444
|
/**
|
|
181
445
|
* Quiesce a strand: release its strand-network resources (stop the libp2p node,
|
|
182
446
|
* close the StrandDatabase) while RETAINING the instance record — identity,
|
|
@@ -190,11 +454,117 @@ export declare class StrandInstanceManager {
|
|
|
190
454
|
* Resume a previously-quiesced strand: rebuild its libp2p node + StrandDatabase
|
|
191
455
|
* from the retained launch config and re-attach them, transitioning it back to
|
|
192
456
|
* `active`. `overrides` re-applies volatile inputs that may have changed since
|
|
193
|
-
* launch (the cohort `bootstrapNodes` seed
|
|
194
|
-
* a later resume reuses the latest
|
|
195
|
-
* if it is already running.
|
|
457
|
+
* launch (the cohort `bootstrapNodes` seed and the strand's `servingMachines`
|
|
458
|
+
* count) and updates the retained config so a later resume reuses the latest
|
|
459
|
+
* values. Returns the live instance unchanged if it is already running.
|
|
196
460
|
*/
|
|
197
461
|
resumeStrand(strandId: string, overrides?: ResumeStrandOverrides): Promise<StrandInstance>;
|
|
462
|
+
/**
|
|
463
|
+
* Honor a founder request against an ALREADY-TRACKED strand — the seam that
|
|
464
|
+
* closes the "whoever launches first decides whether the bootstrap runs" gap:
|
|
465
|
+
* an instance first launched as a joiner (an app's own attach, or a watcher
|
|
466
|
+
* poll winning the launch race) used to swallow a later founder request
|
|
467
|
+
* silently, leaving the strand active with no `Strand.Header`.
|
|
468
|
+
*
|
|
469
|
+
* Flips the RETAINED launch config's `founder` to true, so every later
|
|
470
|
+
* quiesce → resume rebuild founds as well (the bootstrap is insert-if-absent —
|
|
471
|
+
* {@link StrandDatabase.ensureFounderBootstrap} — so re-running it per rebuild
|
|
472
|
+
* writes nothing twice), and runs the bootstrap against the live database now.
|
|
473
|
+
*
|
|
474
|
+
* @returns how the request resolved:
|
|
475
|
+
* - `'already-founder'` — the retained config already founds; nothing to do.
|
|
476
|
+
* - `'bootstrapped'` — config flipped and the live database ran the bootstrap.
|
|
477
|
+
* - `'needs-resume'` — config flipped, but the instance is quiesced (no live
|
|
478
|
+
* database), so the bootstrap could not run here: the CALLER must wake the
|
|
479
|
+
* strand (`CadreNode.wakeStrand`, which owns the hibernation bookkeeping this
|
|
480
|
+
* manager does not) so the rebuild — which now founds — runs it.
|
|
481
|
+
* @param resolvePartyKey - Asked for the party's own membership key when (and only
|
|
482
|
+
* when) a CLOSED strand's retained config carries none — the instance was launched
|
|
483
|
+
* as a joiner before its `StrandPartyKey` row existed or replicated. The resolved
|
|
484
|
+
* key is retained alongside the founder flip so the bootstrap (now, or on the
|
|
485
|
+
* caller's wake for `'needs-resume'`) can seat the founding Member/Manager.
|
|
486
|
+
* @throws when the strand is not tracked — this seam exists only for the
|
|
487
|
+
* tracked-instance launch path; an untracked id is a caller bug.
|
|
488
|
+
* @throws whatever the live bootstrap throws (e.g. `PreSplitStrandIdentityError`),
|
|
489
|
+
* after {@link withdrawFounderRequest} — so a retry re-attempts the founding rather
|
|
490
|
+
* than resolving `'already-founder'` over an instance that never founded. A caller
|
|
491
|
+
* whose `'needs-resume'` wake fails owes the same withdrawal.
|
|
492
|
+
*/
|
|
493
|
+
foundExistingStrand(strandId: string, resolvePartyKey?: () => Promise<string | undefined>): Promise<'already-founder' | 'bootstrapped' | 'needs-resume'>;
|
|
494
|
+
/**
|
|
495
|
+
* Whether `strandId` is launched but still waiting for its first sync — a joiner whose
|
|
496
|
+
* runtime is up while its database is withheld (status `'syncing'`, or `'idle'` after
|
|
497
|
+
* the hibernation manager's idle timer fired on it). `false` for a writable, quiesced,
|
|
498
|
+
* or untracked strand. The predicate {@link whenWritable}'s callers gate on.
|
|
499
|
+
*/
|
|
500
|
+
isAwaitingFirstSync(strandId: string): boolean;
|
|
501
|
+
/**
|
|
502
|
+
* Resolve once the strand's database is published to the app — immediately for a
|
|
503
|
+
* writable strand — or reject with {@link StrandAwaitingFirstSyncError} after
|
|
504
|
+
* `timeoutMs` (default: the retained launch config's `firstSync.timeoutMs`, else
|
|
505
|
+
* {@link DEFAULT_STRAND_FIRST_SYNC_TIMEOUT_MS}). The rejection is RETRYABLE: nothing is
|
|
506
|
+
* torn down, the gate keeps probing, and a later call waits afresh. A quiesced
|
|
507
|
+
* (hibernating) strand is not woken here — the wait spans a resume, so a caller that
|
|
508
|
+
* wants one wakes it (`CadreNode.wakeStrand`) and waits. Rejects immediately for an
|
|
509
|
+
* untracked strand, and whenever the strand is stopped while a wait is pending.
|
|
510
|
+
*/
|
|
511
|
+
whenWritable(strandId: string, options?: {
|
|
512
|
+
timeoutMs?: number;
|
|
513
|
+
}): Promise<StrandInstance>;
|
|
514
|
+
/**
|
|
515
|
+
* Hand a launch's database to the app: set `instance.database`, retire the gate that
|
|
516
|
+
* held it, release every {@link whenWritable} waiter, and — when the launch had already
|
|
517
|
+
* been reported gated — flip it `'active'` and announce it through the retained config's
|
|
518
|
+
* `onWritable`. "Reported gated" is any status but `'starting'`: `'syncing'`, or the
|
|
519
|
+
* `'idle'` the hibernation manager's idle timer flips a long-gated joiner to (see
|
|
520
|
+
* {@link isAwaitingFirstSync}) — the Header arriving after that flip must announce too,
|
|
521
|
+
* or an app hanging on `strand:writable` never learns the strand opened. A publish that
|
|
522
|
+
* lands while the instance is still `'starting'` (the Header arrived during the rest of
|
|
523
|
+
* bring-up) announces nothing: `startStrand` / `resumeStrand` are about to report the
|
|
524
|
+
* strand `'active'`, and `strand:writable` is defined as the follow-up to a gated
|
|
525
|
+
* launch, never a duplicate of `strand:started`.
|
|
526
|
+
*
|
|
527
|
+
* It is also the one seam where a WITHHELD database becomes available, so it kicks the
|
|
528
|
+
* membership reconciler: on a gated launch that loop's own immediate pass ran while
|
|
529
|
+
* `instance.database` was still unset, found no database, and would otherwise not try
|
|
530
|
+
* again until its retry timer fired. A launch that was never gated publishes here BEFORE
|
|
531
|
+
* the reconciler is constructed, so there is nothing registered to kick and the loop's own
|
|
532
|
+
* immediate pass already sees the database — which is why the kick needs no `wasGated`
|
|
533
|
+
* test of its own.
|
|
534
|
+
*/
|
|
535
|
+
private publishDatabase;
|
|
536
|
+
/** Reject every {@link whenWritable} waiter for a strand that is going away. */
|
|
537
|
+
private rejectWritableWaiters;
|
|
538
|
+
/**
|
|
539
|
+
* Undo {@link foundExistingStrand}'s founder flip after the founding it promised
|
|
540
|
+
* failed, so the retained config again says what this instance actually runs as (a
|
|
541
|
+
* joiner) and the next founder request re-attempts the bootstrap. The resolved party
|
|
542
|
+
* key stays retained — it is this party's identity for the strand either way. No-op
|
|
543
|
+
* when the strand is untracked or its config does not found.
|
|
544
|
+
*/
|
|
545
|
+
withdrawFounderRequest(strandId: string): void;
|
|
546
|
+
/**
|
|
547
|
+
* Run the (idempotent) founder bootstrap against a tracked strand's LIVE
|
|
548
|
+
* database, independently of what the retained launch config says.
|
|
549
|
+
*
|
|
550
|
+
* Separate from {@link foundExistingStrand} because a caller that resolved
|
|
551
|
+
* `'needs-resume'` and woke the strand must not assume the wake's own rebuild
|
|
552
|
+
* founded it: `HibernationManager` COALESCES wakes, so a wake already in flight
|
|
553
|
+
* when the config flipped had already read the PRE-flip config and rebuilt as a
|
|
554
|
+
* joiner. Re-running the bootstrap costs one insert-if-absent probe per table
|
|
555
|
+
* and is the only thing that makes "founding resolves once the Header is
|
|
556
|
+
* written" true on that path.
|
|
557
|
+
*
|
|
558
|
+
* A strand still `'syncing'` (its database withheld behind the first-sync gate) is
|
|
559
|
+
* founded through the gate: the bootstrap writes the Header this machine was waiting
|
|
560
|
+
* to receive, so a successful run publishes the database and the strand goes
|
|
561
|
+
* `'active'` — the one legitimate way a joiner launch becomes writable without a peer.
|
|
562
|
+
*
|
|
563
|
+
* @throws when the strand is not tracked, or is still quiesced (no live
|
|
564
|
+
* database) — both mean the bootstrap did NOT run, which a founder request
|
|
565
|
+
* must never swallow.
|
|
566
|
+
*/
|
|
567
|
+
ensureFounderBootstrap(strandId: string): Promise<void>;
|
|
198
568
|
/**
|
|
199
569
|
* Stop a strand instance
|
|
200
570
|
*/
|