@serfab/cadre-core 0.11.0 → 0.13.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 +3 -2
- package/dist/announce-addrs.d.ts +49 -0
- package/dist/announce-addrs.js +78 -0
- package/dist/announce-addrs.js.map +1 -0
- package/dist/cadre-node.d.ts +607 -53
- package/dist/cadre-node.js +1268 -157
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-database.d.ts +174 -23
- package/dist/control-database.js +317 -107
- package/dist/control-database.js.map +1 -1
- package/dist/control-read-retry.d.ts +103 -0
- package/dist/control-read-retry.js +145 -0
- package/dist/control-read-retry.js.map +1 -0
- package/dist/control-retry.d.ts +101 -0
- package/dist/control-retry.js +108 -0
- package/dist/control-retry.js.map +1 -0
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +50 -6
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +59 -35
- package/dist/control-write-retry.js +21 -92
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +17 -15
- package/dist/delegate-admission.js +17 -15
- package/dist/delegate-admission.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 +10 -6
- package/dist/index.js +39 -12
- package/dist/index.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +226 -26
- package/dist/membership-connection-gater.js +316 -30
- package/dist/membership-connection-gater.js.map +1 -1
- package/dist/node-local-snapshot.d.ts +6 -0
- package/dist/node-local-snapshot.js +7 -1
- package/dist/node-local-snapshot.js.map +1 -1
- package/dist/{strand-backfill.d.ts → peer-join-backfill.d.ts} +49 -20
- package/dist/{strand-backfill.js → peer-join-backfill.js} +146 -56
- package/dist/peer-join-backfill.js.map +1 -0
- package/dist/peer-record.d.ts +26 -0
- package/dist/peer-record.js +32 -0
- package/dist/peer-record.js.map +1 -1
- package/dist/relay-addrs.d.ts +145 -22
- package/dist/relay-addrs.js +314 -31
- package/dist/relay-addrs.js.map +1 -1
- package/dist/relay-reservation.d.ts +20 -15
- package/dist/relay-reservation.js +18 -14
- package/dist/relay-reservation.js.map +1 -1
- package/dist/strand-addr-protocol.js +16 -0
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-database.d.ts +12 -0
- package/dist/strand-database.js +16 -0
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +22 -9
- package/dist/strand-formation-manager.js +20 -12
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +83 -9
- package/dist/strand-formation-protocol.js +86 -10
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +151 -17
- package/dist/strand-instance-manager.js +237 -32
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +118 -14
- package/dist/strand-membership-writer.js +203 -15
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-network-config.d.ts +84 -0
- package/dist/strand-network-config.js +136 -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 +10 -3
- package/dist/strand-solicitation.js +12 -7
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-wake-protocol.d.ts +47 -2
- package/dist/strand-wake-protocol.js +76 -6
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.js +6 -1
- package/dist/strand-watcher.js.map +1 -1
- package/dist/types.d.ts +282 -43
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/package.json +16 -8
- package/src/announce-addrs.ts +93 -0
- package/src/cadre-node.ts +1366 -171
- package/src/control-database.ts +2628 -2406
- package/src/control-read-retry.ts +167 -0
- package/src/control-retry.ts +190 -0
- package/src/control-schema.ts +50 -6
- package/src/control-write-retry.ts +30 -130
- package/src/delegate-admission.ts +17 -15
- 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 +477 -407
- package/src/membership-connection-gater.ts +539 -204
- package/src/node-local-snapshot.ts +13 -1
- package/src/peer-addr-book.ts +251 -250
- package/src/{strand-backfill.ts → peer-join-backfill.ts} +187 -75
- package/src/peer-record.ts +33 -0
- package/src/relay-addrs.ts +436 -111
- package/src/relay-reservation.ts +828 -823
- package/src/strand-addr-protocol.ts +17 -0
- package/src/strand-database.ts +17 -0
- package/src/strand-formation-manager.ts +511 -491
- package/src/strand-formation-protocol.ts +808 -692
- package/src/strand-instance-manager.ts +899 -611
- package/src/strand-membership-writer.ts +232 -15
- package/src/strand-network-config.ts +172 -0
- package/src/strand-revocation-enforcer.ts +653 -0
- package/src/strand-solicitation.ts +484 -472
- package/src/strand-wake-protocol.ts +94 -8
- package/src/strand-watcher.ts +319 -314
- package/src/types.ts +1554 -1309
- package/dist/strand-backfill.js.map +0 -1
package/dist/cadre-node.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Libp2p, PeerId } from '@libp2p/interface';
|
|
2
2
|
import type { Multiaddr } from '@multiformats/multiaddr';
|
|
3
|
-
import type { CadreNodeConfig, StrandInstance, StrandConfig, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, AddDroneOptions, AddPhoneOptions, DroneInitResult, InviteResult, CadreInvite, OpenInvitation, FormStrandResult, StrandFormationDisclosure, ResolveOpts, SelfRegistrationOutcome, ServiceWakeResult, PushPlatform, DeviceTokenRecord, ResolveDeviceTokenOpts } from './types.js';
|
|
3
|
+
import type { CadreNodeConfig, StrandInstance, StrandRow, StrandConfig, FoundStrandConfig, FoundStrandResult, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, AddDroneOptions, AddPhoneOptions, DroneInitResult, InviteResult, CadreInvite, OpenInvitation, FormStrandResult, StrandFormationDisclosure, ResolveOpts, SelfRegistrationOutcome, ServiceWakeResult, PushPlatform, DeviceTokenRecord, ResolveDeviceTokenOpts } from './types.js';
|
|
4
4
|
import { type Ed25519KeyPair } from './ed25519-key.js';
|
|
5
5
|
import { type TrustedOwnerStore, type TrustSource } from './trusted-owner-store.js';
|
|
6
6
|
import { type BootstrapPeerStore } from './bootstrap-peer-store.js';
|
|
7
|
+
import { type EnrolledMachineStore } from './enrolled-machine-store.js';
|
|
7
8
|
import { type SAppIdLookup } from './strand-watcher.js';
|
|
8
9
|
import { EnrollmentService } from './enrollment.js';
|
|
9
10
|
import { ControlDatabase } from './control-database.js';
|
|
@@ -57,6 +58,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
57
58
|
private trustedOwnerStore;
|
|
58
59
|
private controlNode;
|
|
59
60
|
private controlDatabase;
|
|
61
|
+
/**
|
|
62
|
+
* The CONTROL network's peer-join block catch-up — same module as the
|
|
63
|
+
* per-strand ones, but membership-gated (see {@link startControlBackfill}).
|
|
64
|
+
* Created in {@link start} after the control node and database are up, stopped
|
|
65
|
+
* and dropped in {@link cleanup} before either is torn down, so a stop()→
|
|
66
|
+
* start() cycle rebuilds it with a fresh caught-up-peer memo.
|
|
67
|
+
*/
|
|
68
|
+
private controlBackfill;
|
|
60
69
|
private strandWatcher;
|
|
61
70
|
private strandManager;
|
|
62
71
|
private hibernationManager;
|
|
@@ -103,10 +112,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
103
112
|
private windowWaiters;
|
|
104
113
|
private eventHandlers;
|
|
105
114
|
/**
|
|
106
|
-
* Relay multiaddrs the caller asked {@link reserveRelays} to reserve through
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
115
|
+
* Relay multiaddrs the caller asked {@link reserveRelays} to reserve through —
|
|
116
|
+
* or, on a node that names `network.relayAddrs`, the ones
|
|
117
|
+
* {@link driveControlRelayReservation} asked for at the end of {@link start}.
|
|
118
|
+
* Empty (the default) means neither happened, so {@link getRelayReservationState}
|
|
119
|
+
* reports `none`.
|
|
110
120
|
*/
|
|
111
121
|
private relayReserveAddrs;
|
|
112
122
|
/**
|
|
@@ -121,6 +131,23 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
121
131
|
* supervisor to hold one — i.e. the pre-start `control node unavailable` case.
|
|
122
132
|
*/
|
|
123
133
|
private relayReserveError;
|
|
134
|
+
/**
|
|
135
|
+
* True from just before the control libp2p node is created until
|
|
136
|
+
* {@link ControlDatabase.initialize} has settled — the connection gate's
|
|
137
|
+
* BRING-UP QUIET PERIOD (`membership-connection-gater.ts`), during which this
|
|
138
|
+
* node refuses every control connection in both directions.
|
|
139
|
+
*
|
|
140
|
+
* The invariant: the control database is built while this node holds zero
|
|
141
|
+
* control connections. Every same-party sibling connected in that window joins
|
|
142
|
+
* the Optimystic cohort the bring-up's block probes consult, and a sibling that
|
|
143
|
+
* has not yet replicated this node's `CadrePeer` row refuses them all — which
|
|
144
|
+
* fails `start()` outright and cannot be retried into convergence (writing the
|
|
145
|
+
* row that would clear the refusal needs the database being built).
|
|
146
|
+
*
|
|
147
|
+
* Cleared on FAILURE as well as success ({@link cleanup} clears it), so
|
|
148
|
+
* teardown is never gated.
|
|
149
|
+
*/
|
|
150
|
+
private controlBringUpInFlight;
|
|
124
151
|
/** Map of strandId -> sAppConfig for sAppId filtering and management */
|
|
125
152
|
private sAppConfigs;
|
|
126
153
|
/**
|
|
@@ -181,12 +208,25 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
181
208
|
/**
|
|
182
209
|
* In-memory delegate admission grants (see `delegate-admission.ts`): the
|
|
183
210
|
* strand-node transport peerIds this party's members have announced over the
|
|
184
|
-
* strand-addr RPC, admitted at the CONNECTION
|
|
185
|
-
* strand node can hold a circuit-relay reservation on this
|
|
186
|
-
*
|
|
187
|
-
*
|
|
211
|
+
* strand-addr RPC, admitted at the CONNECTION and RESERVATION levels so a
|
|
212
|
+
* member's NAT'd strand node can hold a circuit-relay reservation on this
|
|
213
|
+
* node (without spending the unauthorized-reservation budget). Consulted
|
|
214
|
+
* ONLY by {@link admitInboundControlConnection} and
|
|
215
|
+
* {@link admitControlRelayReservation}; the fail-closed per-stream gate
|
|
216
|
+
* ({@link authorizeInboundControlStream}) never honors it.
|
|
188
217
|
*/
|
|
189
218
|
private readonly delegateAdmission;
|
|
219
|
+
/**
|
|
220
|
+
* Bounded budget of concurrent circuit-relay reservations granted to peers
|
|
221
|
+
* this node cannot (yet) place as members — the boot-ordering window where a
|
|
222
|
+
* genuine sibling reserves before its `CadrePeer` row has replicated here.
|
|
223
|
+
* Consulted only by {@link admitControlRelayReservation} (the gater's
|
|
224
|
+
* `denyInboundRelayReservation` policy); authorized members and announced
|
|
225
|
+
* delegates are admitted before it and never counted. Cap from
|
|
226
|
+
* `network.unauthorizedRelayReservationCap` (default
|
|
227
|
+
* `MAX_UNAUTHORIZED_RELAY_RESERVATIONS`).
|
|
228
|
+
*/
|
|
229
|
+
private readonly unauthorizedRelayReservations;
|
|
190
230
|
/**
|
|
191
231
|
* When this node last ANNOUNCED a delegate, keyed `<targetPeerId>\n<strandId>`
|
|
192
232
|
* (the peer announced TO, not the delegate). Throttles
|
|
@@ -206,6 +246,36 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
206
246
|
* stale stamp.
|
|
207
247
|
*/
|
|
208
248
|
private readonly strandPeerAddrRefreshAt;
|
|
249
|
+
/**
|
|
250
|
+
* CROSS-PARTY strand-network addresses learned at formation, keyed by strandId — the
|
|
251
|
+
* responder's live strand addrs, carried back on the formation result
|
|
252
|
+
* (`FormationResultMessage.strandAddrs`) and recorded by {@link formStrand}.
|
|
253
|
+
*
|
|
254
|
+
* This is the ONLY cross-party discovery input there is. The strand-addr RPC that
|
|
255
|
+
* resolves a strand's addresses is membership-gated and answers own-party siblings
|
|
256
|
+
* only, so without this map a joiner's cohort seed for a two-party strand is empty and
|
|
257
|
+
* the mesh never forms.
|
|
258
|
+
*
|
|
259
|
+
* Read by {@link resolveCohortSeed} (launch + hibernation resume) and re-merged by
|
|
260
|
+
* every {@link refreshStrandPeerAddrs} pass — the re-merge is what keeps the entries
|
|
261
|
+
* alive past the peerStore's one-hour address expiry, since nothing can re-resolve
|
|
262
|
+
* them.
|
|
263
|
+
*
|
|
264
|
+
* IN-MEMORY and ONE-SHOT, deliberately (see `docs/strands.md`): the addresses die with
|
|
265
|
+
* the process, and a restarted initiator with no sibling running the strand is back to
|
|
266
|
+
* an empty seed until the durability work lands
|
|
267
|
+
* (`backlog/feat-cross-party-strand-addr-durability`). They are also never refreshed,
|
|
268
|
+
* so a responder whose relay reservation rotates before the initiator dials leaves a
|
|
269
|
+
* dead entry behind; recovery today is a fresh invitation.
|
|
270
|
+
*
|
|
271
|
+
* NOTE: entries are kept for the node's lifetime, including across a
|
|
272
|
+
* {@link stopStrand} — a stopped strand may be rediscovered by the watcher and must
|
|
273
|
+
* still get its seed. Each strand's list is capped at `MAX_STRAND_ADDRS`, but the
|
|
274
|
+
* number of KEYS is bounded only by the strands this node has ever formed as an
|
|
275
|
+
* initiator (not by time); if a node ever forms strands at scale, evict on
|
|
276
|
+
* `unpublishStrand` or cap the map.
|
|
277
|
+
*/
|
|
278
|
+
private readonly crossPartyStrandAddrs;
|
|
209
279
|
/**
|
|
210
280
|
* Cold-start bootstrap dial targets: the owner-flagged peers of every seed
|
|
211
281
|
* this node has applied, keyed by peer id, valued by the seed's multiaddr
|
|
@@ -240,6 +310,34 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
240
310
|
* the party if it is ever stranded again.
|
|
241
311
|
*/
|
|
242
312
|
private bootstrapPeerStore;
|
|
313
|
+
/**
|
|
314
|
+
* Node-local record of the party's enrolled-machine count (see
|
|
315
|
+
* `enrolled-machine-store.ts`), kept so the CONTROL node can declare a
|
|
316
|
+
* block-repair yardstick at bring-up. Constructed (or adopted from
|
|
317
|
+
* `config.enrolledMachines.store`) by {@link initializeEnrolledMachineStore};
|
|
318
|
+
* written by {@link refreshAuthorizedControlPeers}. Like
|
|
319
|
+
* {@link bootstrapPeerStore} it is deliberately NOT cleared by {@link cleanup},
|
|
320
|
+
* so it survives a stop()→start() cycle of the same node instance.
|
|
321
|
+
*/
|
|
322
|
+
private enrolledMachineStore;
|
|
323
|
+
/**
|
|
324
|
+
* The enrolled-machine count this node's CONTROL libp2p node was (or will be)
|
|
325
|
+
* built with — read out of {@link enrolledMachineStore} during {@link start},
|
|
326
|
+
* before {@link createControlNode}, and consumed by
|
|
327
|
+
* {@link buildControlNodeOptions}.
|
|
328
|
+
*
|
|
329
|
+
* A captured FIELD rather than a live `store.count()` read, deliberately: the
|
|
330
|
+
* store moves as membership changes, but Optimystic froze the policy when the
|
|
331
|
+
* node was built, so this field is the honest answer to "what did this node
|
|
332
|
+
* actually declare?" and does not drift away from the running node. It is
|
|
333
|
+
* re-read on each {@link start}, which is what makes a stop()→start() cycle pick
|
|
334
|
+
* up a count recorded during the previous run.
|
|
335
|
+
*
|
|
336
|
+
* `undefined` — a brand-new node, an unreadable slot, or the ephemeral default
|
|
337
|
+
* store — means "this node does not know", which `controlClusterPolicy` answers
|
|
338
|
+
* with the frozen base policy itself.
|
|
339
|
+
*/
|
|
340
|
+
private declaredEnrolledMachines;
|
|
243
341
|
/** Initial self-registration timer (see {@link scheduleSelfRegistration}). */
|
|
244
342
|
private selfRegistrationTimer;
|
|
245
343
|
/** TTL heartbeat that re-publishes the self record before it goes stale. */
|
|
@@ -471,6 +569,32 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
471
569
|
* must never enter this node's dial loop.
|
|
472
570
|
*/
|
|
473
571
|
private initializeBootstrapPeerStore;
|
|
572
|
+
/**
|
|
573
|
+
* Construct (or adopt) the node-local enrolled-machine store and capture the
|
|
574
|
+
* count this run will declare into {@link declaredEnrolledMachines}.
|
|
575
|
+
*
|
|
576
|
+
* Synchronous for the same reason {@link initializeBootstrapPeerStore} is: an
|
|
577
|
+
* injected store has already loaded its persisted count by the time it is handed
|
|
578
|
+
* in (its `open` is the async part), and the in-memory default has nothing to
|
|
579
|
+
* load. Must run BEFORE {@link createControlNode} — that is the whole point of
|
|
580
|
+
* the record; see `enrolled-machine-store.ts`.
|
|
581
|
+
*
|
|
582
|
+
* The store instance is kept across stop()→start() (so a count recorded during
|
|
583
|
+
* the previous run is not lost with the ephemeral default), but the DECLARED
|
|
584
|
+
* count is re-read every time — which is how a restart applies a number the
|
|
585
|
+
* previous run learned.
|
|
586
|
+
*
|
|
587
|
+
* A store scoped to a different party is a configuration error, fail closed. It
|
|
588
|
+
* is only a repair hint, but a hint sized by a foreign party's membership is a
|
|
589
|
+
* number nobody chose, and the mismatch always means a miswired embedder.
|
|
590
|
+
*/
|
|
591
|
+
private initializeEnrolledMachineStore;
|
|
592
|
+
/**
|
|
593
|
+
* The node-local enrolled-machine store (null before {@link start}) — what this
|
|
594
|
+
* node last knew about its party's size. Exposed for diagnostics and for a host
|
|
595
|
+
* that wants to show which repair yardstick the next launch will declare.
|
|
596
|
+
*/
|
|
597
|
+
getEnrolledMachineStore(): EnrolledMachineStore | null;
|
|
474
598
|
/**
|
|
475
599
|
* The node-local cold-start bootstrap-peer store (null before {@link start}) —
|
|
476
600
|
* the retained owner addresses {@link reconcileControlCohort}'s cold-start
|
|
@@ -503,6 +627,44 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
503
627
|
*/
|
|
504
628
|
trustOwnerKeys(keys: Iterable<string>, source: Exclude<TrustSource, 'genesis'>): Promise<void>;
|
|
505
629
|
private createControlNode;
|
|
630
|
+
/**
|
|
631
|
+
* The control libp2p node's Optimystic network name — ONE binding for every
|
|
632
|
+
* derivation from it (`db-p2p` namespaces all of the node's protocol ids as
|
|
633
|
+
* `/optimystic/<networkName>/...`), so the node options and the block-transfer
|
|
634
|
+
* protocol prefix the control backfill dials can never drift apart.
|
|
635
|
+
*/
|
|
636
|
+
private controlNetworkName;
|
|
637
|
+
/**
|
|
638
|
+
* Arm the CONTROL network's peer-join block catch-up: push every block in the
|
|
639
|
+
* control database's own raw store to each newly connected AUTHORIZED member
|
|
640
|
+
* this runtime has not yet caught up. This is what physically replicates
|
|
641
|
+
* control blocks committed while the writer was alone — the named
|
|
642
|
+
* collection-header blocks written once at genesis above all, whose revision
|
|
643
|
+
* never moves again, so no later commit ever carries them to a member that
|
|
644
|
+
* joined after them. Without it, such a member that restarts offline reads
|
|
645
|
+
* the affected control tables as EMPTY, silently (`isMember()` answers false
|
|
646
|
+
* for peers it knew about before the restart).
|
|
647
|
+
*
|
|
648
|
+
* Unlike the per-strand instances (see `strand-instance-manager.ts`), pushes
|
|
649
|
+
* are gated on {@link isAuthorizedMember}, judged at push time: the control
|
|
650
|
+
* network's inbound connection gate deliberately admits non-members in
|
|
651
|
+
* several states (seed delivery to an un-enrolled node, an open enrollment
|
|
652
|
+
* window, an outstanding invitation, configured bootstrap/relay peers), and
|
|
653
|
+
* pushing the whole control store to such a peer would hand a stranger the
|
|
654
|
+
* party's entire membership, addresses and strand list. The receiving side
|
|
655
|
+
* needs no work of its own: `createLibp2pNode` registers the block-transfer
|
|
656
|
+
* handler on every node it builds, control node included, and this node's
|
|
657
|
+
* per-stream gate (`authorizeInboundControlStream`) covers the inbound
|
|
658
|
+
* direction. A denied peer is retried on its next `connection:open`, and —
|
|
659
|
+
* because the production join order is connect-then-authorize — on every
|
|
660
|
+
* committed membership change via {@link refreshAuthorizedControlPeers}'s
|
|
661
|
+
* `scheduleConnectedPeers()` call.
|
|
662
|
+
*
|
|
663
|
+
* No-ops (logged) when the embedder configured no control storage or the
|
|
664
|
+
* node exposes no key network — the backfill would have nothing to read or
|
|
665
|
+
* no way to dial.
|
|
666
|
+
*/
|
|
667
|
+
private startControlBackfill;
|
|
506
668
|
/**
|
|
507
669
|
* The control database's cache-wrapped raw storage, resolved from
|
|
508
670
|
* `config.storage.provider` on first use and then held in {@link controlStorage}
|
|
@@ -522,6 +684,27 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
522
684
|
* operation count.
|
|
523
685
|
*/
|
|
524
686
|
private resolveControlStorage;
|
|
687
|
+
/**
|
|
688
|
+
* Warn the operator when `network.announceAddrs` will silently cost this node the
|
|
689
|
+
* relay reachability it also configured.
|
|
690
|
+
*
|
|
691
|
+
* A non-empty announce set REPLACES everything libp2p advertises, so the
|
|
692
|
+
* `/p2p-circuit` address earned by a relay reservation is dropped from the node's
|
|
693
|
+
* advertised addresses even though the reservation itself is still held — peers
|
|
694
|
+
* behind NAT stop being able to reach it through that relay. Not an error: an
|
|
695
|
+
* operator whose relay slot is decorative may genuinely want only the announced
|
|
696
|
+
* address, so this reports and proceeds.
|
|
697
|
+
*
|
|
698
|
+
* Keyed off the RESOLVED listen addrs rather than `relayAddrs` alone, so a
|
|
699
|
+
* hand-written `/p2p-circuit` entry in `listenAddrs` — the same reservation by the
|
|
700
|
+
* longer route — is caught too.
|
|
701
|
+
*
|
|
702
|
+
* NOTE: the only direct `console.*` in this library — a boot-time operator warning, not a
|
|
703
|
+
* diagnostic trace (those use `debug`, which an operator never sees without `DEBUG=`). If a
|
|
704
|
+
* second such warning ever appears here, route both through a `CadreNodeEvents` entry the
|
|
705
|
+
* embedder surfaces instead of growing a console surface inside a library.
|
|
706
|
+
*/
|
|
707
|
+
private warnIfAnnounceAddrsDiscardRelay;
|
|
525
708
|
/**
|
|
526
709
|
* Map this node's config onto the control network's libp2p node options.
|
|
527
710
|
*
|
|
@@ -531,6 +714,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
531
714
|
* only caller in production is `createControlNode`.
|
|
532
715
|
*/
|
|
533
716
|
private buildControlNodeOptions;
|
|
717
|
+
/**
|
|
718
|
+
* Does this node's CONTROL libp2p run the circuit-relay server? An explicit
|
|
719
|
+
* `network.enableRelay` wins; otherwise storage-profile nodes default to on
|
|
720
|
+
* (better connectivity/uptime). Shared by {@link buildControlNodeOptions}
|
|
721
|
+
* (which configures the server from it) and
|
|
722
|
+
* {@link admitInboundControlConnection} (whose deny/admit-for-relay branch
|
|
723
|
+
* must agree with whether a reservation is even servable here).
|
|
724
|
+
*/
|
|
725
|
+
private relayServerEnabled;
|
|
534
726
|
/**
|
|
535
727
|
* Decide whether an inbound CONTROL-network connection from `remotePeerId`
|
|
536
728
|
* should be admitted — the policy behind the connection gater wired in
|
|
@@ -540,7 +732,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
540
732
|
* `membership-connection-gater.ts` for the layer's rationale and the
|
|
541
733
|
* stranger-open protocol allowlist).
|
|
542
734
|
*
|
|
543
|
-
*
|
|
735
|
+
* Returns `'admit'` when ANY of:
|
|
544
736
|
* 1. a shared-baseline check admits ({@link admitControlPeerUnconditionally}
|
|
545
737
|
* — not running / DB torn down, absent-or-empty trusted-owner anchor, or
|
|
546
738
|
* configured bootstrap/relay infrastructure);
|
|
@@ -577,17 +769,69 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
577
769
|
* this node yet is denied even though the formation handler would have
|
|
578
770
|
* accepted it, exactly like the unreplicated-membership-row case below.
|
|
579
771
|
*
|
|
772
|
+
* When every check falls through, the verdict depends on whether this node
|
|
773
|
+
* runs the circuit-relay server ({@link relayServerEnabled}): without one,
|
|
774
|
+
* `'deny'`; with one, `'admit-for-relay'` — a circuit-relay reservation is
|
|
775
|
+
* established by the reserving peer DIALING the relay, so a connection deny
|
|
776
|
+
* here kills the reservation, and that deny is NOT self-healing: an outbound
|
|
777
|
+
* reconcile re-dial re-establishes a data link, but no outbound dial can
|
|
778
|
+
* grant the remote peer a reservation, and a relay-only peer has no address
|
|
779
|
+
* of its own to dial back — the reservation IS its address. The gater admits
|
|
780
|
+
* such a connection, decides the reservation via
|
|
781
|
+
* {@link admitControlRelayReservation}, and drops the connection if no
|
|
782
|
+
* reservation is admitted in time (see `membership-connection-gater.ts` →
|
|
783
|
+
* "The relay-reservation seam").
|
|
784
|
+
*
|
|
580
785
|
* NOTE: check 4/5 runs a control-DB read per inbound connection
|
|
581
786
|
* (`listAuthorizedMembers`); connections are rare and cadres small, and this
|
|
582
787
|
* layer is fail-open behind `ADMISSION_DECISION_TIMEOUT_MS`, so the live
|
|
583
788
|
* read is safe here — unlike the per-stream gate, which must consult the
|
|
584
789
|
* materialized {@link authorizedControlPeers} snapshot instead.
|
|
585
|
-
* NOTE: a sibling whose membership row has not yet
|
|
586
|
-
*
|
|
587
|
-
* either side's next outbound reconcile dial (outbound is never
|
|
588
|
-
* re-establishes the link — self-healing,
|
|
790
|
+
* NOTE: on a relay-DISABLED node, a sibling whose membership row has not yet
|
|
791
|
+
* replicated here is denied until the row converges (typically via the
|
|
792
|
+
* owner); either side's next outbound reconcile dial (outbound is never
|
|
793
|
+
* gated) re-establishes the DATA link — self-healing for data, visible as a
|
|
794
|
+
* transient deny. That self-healing story never covered a reservation, which
|
|
795
|
+
* is exactly why the relay-enabled path above exists.
|
|
589
796
|
*/
|
|
590
797
|
private admitInboundControlConnection;
|
|
798
|
+
/**
|
|
799
|
+
* Should `remotePeerId` be granted a circuit-relay reservation slot on this
|
|
800
|
+
* node's relay server? The policy behind the gater's
|
|
801
|
+
* `denyInboundRelayReservation` hook (see `membership-connection-gater.ts` →
|
|
802
|
+
* "The relay-reservation seam"); the circuit-relay server consults it per
|
|
803
|
+
* RESERVE request, so it is never called on a node whose relay server is off.
|
|
804
|
+
*
|
|
805
|
+
* Admits when ANY of:
|
|
806
|
+
* 1. a shared-baseline check admits ({@link admitControlPeerUnconditionally}
|
|
807
|
+
* — not running / DB torn down, absent-or-empty trusted-owner anchor, or
|
|
808
|
+
* configured bootstrap/relay infrastructure);
|
|
809
|
+
* 2. the peer holds a live delegate admission grant — a member's strand
|
|
810
|
+
* node reserving here (see `delegate-admission.ts`); never counted
|
|
811
|
+
* against the unauthorized budget;
|
|
812
|
+
* 3. the authorized-member set is empty (cold start) or the peer IS an
|
|
813
|
+
* authorized member — members are never counted against the budget; or
|
|
814
|
+
* 4. the peer takes (or already holds) a slot in the bounded
|
|
815
|
+
* unauthorized-reservation budget — the boot-ordering window where a
|
|
816
|
+
* genuine member reserves before its `CadrePeer` row replicates here.
|
|
817
|
+
* Cap via `network.unauthorizedRelayReservationCap` (0 = refuse every
|
|
818
|
+
* unauthorized reservation).
|
|
819
|
+
*
|
|
820
|
+
* Every uncounted admission also RELEASES the budget slot the peer may hold
|
|
821
|
+
* from an earlier reservation taken while it was still unplaceable — a member
|
|
822
|
+
* that boots, reserves, and only then has its row replicate here would
|
|
823
|
+
* otherwise keep that slot spent for the rest of the entry's TTL.
|
|
824
|
+
*
|
|
825
|
+
* The connection gate's stranger carve-outs (an open enrollment window, an
|
|
826
|
+
* outstanding formation invitation) deliberately do NOT extend here: they
|
|
827
|
+
* exist so a stranger's SEED or FORMATION stream can ride a connection, and
|
|
828
|
+
* neither needs relay capacity. A genuine invitee that does need a relay slot
|
|
829
|
+
* takes one from the budget like any other unplaced peer, so an open window
|
|
830
|
+
* never becomes an unbounded grant of this node's forwarding capacity.
|
|
831
|
+
*/
|
|
832
|
+
private admitControlRelayReservation;
|
|
833
|
+
/** Admit a reservation on the peer's own merits, giving back any budget slot it still holds. */
|
|
834
|
+
private admitReservationUncounted;
|
|
591
835
|
/**
|
|
592
836
|
* Hold the control-network inbound gate open for not-yet-authorized peers
|
|
593
837
|
* until `untilEpochMs` (extends, never shrinks, an already-open window).
|
|
@@ -599,7 +843,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
599
843
|
/**
|
|
600
844
|
* Record (or refresh) a delegate admission grant: `delegatePeerId` is the
|
|
601
845
|
* transport peerId of `announcerPeerId`'s strand-`strandId` node, admitted
|
|
602
|
-
* at the CONNECTION
|
|
846
|
+
* at the CONNECTION and RESERVATION levels (all a circuit-relay reservation
|
|
847
|
+
* needs, without spending the unauthorized-reservation budget) for
|
|
603
848
|
* `DELEGATE_GRANT_TTL_MS`. Called by the strand-addr responder after its
|
|
604
849
|
* authorized-membership gate has passed; public so tests can drive the
|
|
605
850
|
* admission policy without a full strand launch. A re-announce for the same
|
|
@@ -677,6 +922,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
677
922
|
*
|
|
678
923
|
* The SOLE caller is {@link drainMembershipGate}, which serializes refreshes —
|
|
679
924
|
* so two reads can no longer settle out of order.
|
|
925
|
+
*
|
|
926
|
+
* Reads WITHOUT the transient-failure retry, deliberately: `ControlDatabase` drives
|
|
927
|
+
* this listener with its write lock HELD (`notifyMembershipChanged`), so a retrying
|
|
928
|
+
* read here would sleep its backoff holding the lock and stall every other local
|
|
929
|
+
* writer. Nothing is lost — this refresh already keeps the previous snapshot on
|
|
930
|
+
* failure and is re-driven by the next membership write and by the timed reconcile.
|
|
680
931
|
*/
|
|
681
932
|
private refreshAuthorizedControlPeers;
|
|
682
933
|
/**
|
|
@@ -737,6 +988,24 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
737
988
|
* producing an unresolvable row.
|
|
738
989
|
*/
|
|
739
990
|
private getSelfSigningKey;
|
|
991
|
+
/**
|
|
992
|
+
* Does the `Strand` row name THIS machine as its founder — i.e. is its
|
|
993
|
+
* `FounderOwnerKey` this node's own owner key (the key behind its PeerId)?
|
|
994
|
+
* A null/absent column (a consent-seated strand, or a hand-built row) and a
|
|
995
|
+
* node with no owner key both derive `false`: without a positive match this
|
|
996
|
+
* machine must attach, never bootstrap. Pure key derivation, no I/O — cheap
|
|
997
|
+
* enough for {@link launchStrand}'s tracked-instance early return.
|
|
998
|
+
*
|
|
999
|
+
* NOTE: reads "one owner key per machine" — the reference model, where the owner
|
|
1000
|
+
* key IS the key behind the PeerId. Two machines running the SAME identity key
|
|
1001
|
+
* would both derive `true` and each bootstrap on its own replica, which is the
|
|
1002
|
+
* double-`Header` hazard this derivation exists to avoid. Unreachable today: that
|
|
1003
|
+
* configuration also gives both machines one PeerId, which already breaks control
|
|
1004
|
+
* networking well before any strand launches. Revisit if machines ever share an
|
|
1005
|
+
* owner key while holding distinct transport identities — the derivation would
|
|
1006
|
+
* then need a per-machine discriminator (the `CadrePeer` PeerId) on the row.
|
|
1007
|
+
*/
|
|
1008
|
+
private isSelfFoundedRow;
|
|
740
1009
|
/**
|
|
741
1010
|
* The owner keypair (base64url Ed25519) derived from this node's resolved
|
|
742
1011
|
* identity key. In the single-key reference model the owner signing key is
|
|
@@ -787,8 +1056,49 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
787
1056
|
* Survivors are returned signaling (`/p2p-circuit`) first, filtered to
|
|
788
1057
|
* signaling-only when requested, as parsed `Multiaddr`s (unparsable addrs
|
|
789
1058
|
* dropped). Any gate failure yields an empty array rather than throwing.
|
|
1059
|
+
*
|
|
1060
|
+
* Every returned address is normalized to terminate in `/p2p/<peerId>` — see
|
|
1061
|
+
* {@link normalizeDialAddrs} for why that invariant, not each dial site's own
|
|
1062
|
+
* handling, is what keeps a mixed list from taking a whole peer offline.
|
|
790
1063
|
*/
|
|
791
1064
|
resolvePeerAddrs(peerId: string, opts?: ResolveOpts): Promise<Multiaddr[]>;
|
|
1065
|
+
/**
|
|
1066
|
+
* Guarantee every address handed to a control-network dial terminates in
|
|
1067
|
+
* `/p2p/<peerId>`, with no duplicates — the invariant every dial path
|
|
1068
|
+
* downstream relies on.
|
|
1069
|
+
*
|
|
1070
|
+
* `libp2p.dial(addrs)` requires that the addresses in ONE dial either all name
|
|
1071
|
+
* a peer id or none do; a list mixing a circuit address (which ends in
|
|
1072
|
+
* `/p2p/<target>`) with a bare direct one (which ends in `/tcp/…`) makes that
|
|
1073
|
+
* call throw, and the whole peer is then silently skipped rather than one bad
|
|
1074
|
+
* address.
|
|
1075
|
+
*
|
|
1076
|
+
* Applied by ALL THREE sources of control-dial candidates, so no call site
|
|
1077
|
+
* invents its own rule and none can produce a mixed list: the signed record
|
|
1078
|
+
* ({@link resolvePeerAddrs}), the libp2p address book
|
|
1079
|
+
* ({@link peerStoreAddrs}), and a seed's retained owner addresses
|
|
1080
|
+
* ({@link bootstrapDialAddrs}).
|
|
1081
|
+
*
|
|
1082
|
+
* An address naming a DIFFERENT trailing peer id is dropped: it does not reach
|
|
1083
|
+
* `peerId`, so it does not belong in this peer's candidate list (libp2p's own
|
|
1084
|
+
* dial queue filters the same shape out one layer lower).
|
|
1085
|
+
*/
|
|
1086
|
+
private normalizeDialAddrs;
|
|
1087
|
+
/**
|
|
1088
|
+
* One address bound to `peerId`, or `null` when it cannot be — logged either
|
|
1089
|
+
* way, never thrown, so every caller's list-shaping stays total.
|
|
1090
|
+
*
|
|
1091
|
+
* Used both to normalize addresses this node DIALS (a `CadrePeer` row, a
|
|
1092
|
+
* seed-supplied bootstrap addr) and to normalize the ones it ANNOUNCES
|
|
1093
|
+
* ({@link getStrandMultiaddrs}); the rule is the same either way — an address
|
|
1094
|
+
* that does not reach `peerId` is not an address for `peerId`.
|
|
1095
|
+
*
|
|
1096
|
+
* `withTrailingPeerId` encapsulates `/p2p/<peerId>`, which throws on a peer id
|
|
1097
|
+
* that does not parse. Unreachable for a `CadrePeer` row (the binding gate
|
|
1098
|
+
* above parsed it already) and for our own node's id, reachable for a
|
|
1099
|
+
* seed-supplied one ({@link bootstrapDialAddrs}).
|
|
1100
|
+
*/
|
|
1101
|
+
private bindAddrToPeer;
|
|
792
1102
|
/** Parse multiaddr strings, dropping (and logging) any that fail to parse. */
|
|
793
1103
|
private parseMultiaddrs;
|
|
794
1104
|
/**
|
|
@@ -875,6 +1185,16 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
875
1185
|
* Cold-start fallback: the libp2p peerStore multiaddrs for `peerId` (seeded by
|
|
876
1186
|
* {@link SeedBootstrapService.applySeed}). Returns `[]` on a missing entry or any
|
|
877
1187
|
* parse/lookup failure — never throws.
|
|
1188
|
+
*
|
|
1189
|
+
* Normalized through {@link normalizeDialAddrs} for the same reason
|
|
1190
|
+
* {@link resolvePeerAddrs} is, and with more cause: the address book hands back
|
|
1191
|
+
* a list that is inherently MIXED. `@libp2p/peer-store`'s
|
|
1192
|
+
* `dedupeFilterAndSortAddresses` strips a trailing `/p2p/<peerId>` only when
|
|
1193
|
+
* that id is the address's FIRST `/p2p/` component, so a direct address
|
|
1194
|
+
* round-trips bare while a relayed one — whose first `/p2p/` names the relay —
|
|
1195
|
+
* keeps its suffix (the same asymmetry `peer-addr-book.ts`'s `addrKey` exists
|
|
1196
|
+
* to absorb). Feeding both to one `dial()` is exactly the
|
|
1197
|
+
* `InvalidParametersError` that skipped the whole peer.
|
|
878
1198
|
*/
|
|
879
1199
|
private peerStoreAddrs;
|
|
880
1200
|
/**
|
|
@@ -927,11 +1247,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
927
1247
|
* Bind a bootstrap peer's seed addresses to its peer id, so the dial
|
|
928
1248
|
* authenticates the peer it is aiming at rather than trusting whoever answers.
|
|
929
1249
|
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
934
|
-
* bare
|
|
1250
|
+
* The same {@link normalizeDialAddrs} rule the resolved and address-book paths
|
|
1251
|
+
* use, on the third and last source of control-dial candidates: an address that
|
|
1252
|
+
* already terminates in `/p2p/<id>` must carry THIS peer's id or it is dropped
|
|
1253
|
+
* (a seed that disagrees with itself is not a dial target); one that does not —
|
|
1254
|
+
* a bare listen addr, or a relay hop with the destination missing — gets the id
|
|
1255
|
+
* encapsulated; an unencapsulatable one is dropped rather than dialed bare.
|
|
935
1256
|
*/
|
|
936
1257
|
private bootstrapDialAddrs;
|
|
937
1258
|
/** Dial one cold-start bootstrap peer, best-effort. Returns whether it connected. */
|
|
@@ -1180,6 +1501,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1180
1501
|
expireDeviceToken(peerId: string): Promise<void>;
|
|
1181
1502
|
private handleStrandAdded;
|
|
1182
1503
|
private handleStrandRemoved;
|
|
1504
|
+
/**
|
|
1505
|
+
* Return this node to its pre-{@link start} state. The ONE teardown — `stop()` and
|
|
1506
|
+
* a failed `start()` both come here, so anything a stopped node must not still be
|
|
1507
|
+
* doing belongs in this method and not in `stop()`. `stop()` adds only the
|
|
1508
|
+
* `control:disconnected` emit, which a never-connected start has no edge for.
|
|
1509
|
+
*/
|
|
1183
1510
|
private cleanup;
|
|
1184
1511
|
private handleStrandIdle;
|
|
1185
1512
|
/**
|
|
@@ -1202,8 +1529,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1202
1529
|
/**
|
|
1203
1530
|
* Rebuild a quiesced strand's runtime, re-resolving the volatile cohort input
|
|
1204
1531
|
* first: the discovery seed may have grown since the strand last ran. Shared by
|
|
1205
|
-
* the wake (`handleStrandWake`) and check-in (`handleStrandCheckIn`) paths so
|
|
1206
|
-
*
|
|
1532
|
+
* the wake (`handleStrandWake`) and check-in (`handleStrandCheckIn`) paths so both
|
|
1533
|
+
* apply the same fresh resolution. `resumeStrand` is idempotent
|
|
1207
1534
|
* (returns the live instance unchanged) as a backstop against double-resume.
|
|
1208
1535
|
*/
|
|
1209
1536
|
private resumeStrandRuntime;
|
|
@@ -1264,6 +1591,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1264
1591
|
* Add a strand with its sApp configuration.
|
|
1265
1592
|
* The hosting application must provide the sApp schema when creating a strand.
|
|
1266
1593
|
*
|
|
1594
|
+
* This is the ATTACH half only — it starts the local instance and never publishes the
|
|
1595
|
+
* `Strand` row. A joiner (the row arrived over the control network) wants exactly this; a
|
|
1596
|
+
* FOUNDER wants {@link foundStrand}, which publishes and attaches in one resumable call.
|
|
1597
|
+
* With no explicit `founder` flag, founder-ness is derived from the row's
|
|
1598
|
+
* `FounderOwnerKey` (see {@link StrandConfig.founder}) — so attaching a row THIS machine
|
|
1599
|
+
* published (e.g. re-attaching its own orphan after a restart) founds it, and attaching
|
|
1600
|
+
* anyone else's row joins, without the caller needing to know which it is.
|
|
1601
|
+
*
|
|
1267
1602
|
* A rejected call leaves nothing running but DOES leave the sApp config
|
|
1268
1603
|
* registered, deliberately: both an explicit retry and the {@link StrandWatcher}'s
|
|
1269
1604
|
* automatic relaunch need it. So once the strand's row is visible on the control
|
|
@@ -1278,16 +1613,41 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1278
1613
|
* owner identity, so other cadre members discover it via control-network
|
|
1279
1614
|
* sync (their {@link StrandWatcher} fires `strand:discovered`).
|
|
1280
1615
|
*
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1284
|
-
*
|
|
1285
|
-
* `addStrand` (the row already exists).
|
|
1616
|
+
* **Founding a strand is TWO steps** — this one (publish the row cadre-wide) and
|
|
1617
|
+
* {@link addStrand} (start the local instance, `founder: true` to write the strand's
|
|
1618
|
+
* `Header`/founding membership). `addStrand` deliberately omits the insert: it only
|
|
1619
|
+
* starts the LOCAL instance, whereas publishing makes the strand visible cadre-wide. A
|
|
1620
|
+
* discovering peer only does `addStrand` (the row already exists). Callers founding a
|
|
1621
|
+
* strand should use {@link foundStrand}, which performs both steps and is safe to
|
|
1622
|
+
* re-run — hand-rolling the pair is what left strands half-founded when an app was
|
|
1623
|
+
* killed between them.
|
|
1624
|
+
*
|
|
1625
|
+
* **Idempotent for identical content.** A live row whose `(Type, MemberPrivateKey)` match
|
|
1626
|
+
* the arguments is the state this call would have produced, so the call logs and returns
|
|
1627
|
+
* that row instead of writing — a publish interrupted after it committed can be repeated.
|
|
1628
|
+
* DIFFERENT content on the same id throws, naming the columns that differ, rather than
|
|
1629
|
+
* surfacing the raw `UNIQUE constraint failed: Strand.Id`: silently accepting it would
|
|
1630
|
+
* let a retry reopen a closed strand or swap the key gating its reads. The read and the
|
|
1631
|
+
* insert are not atomic, so a concurrent founder (two machines of one party, same id) is
|
|
1632
|
+
* caught on the insert's uniqueness rejection and resolved the same way — re-read, no-op
|
|
1633
|
+
* on a match, rethrow otherwise. Never an overwrite either way.
|
|
1634
|
+
*
|
|
1635
|
+
* A tombstoned strand is NOT resurrected here: {@link unpublishStrand} deletes the row, so
|
|
1636
|
+
* the idempotent branch is unreachable and the ordinary publish path re-seats it, exactly
|
|
1637
|
+
* as that method documents.
|
|
1638
|
+
*
|
|
1639
|
+
* **Already stuck on `UNIQUE constraint failed: Strand.Id`?** The row is already published
|
|
1640
|
+
* — attach, do NOT republish: `addStrand` (or {@link foundStrand}) with the id and, for a
|
|
1641
|
+
* closed strand, the `MemberPrivateKey` read back from the row. `unpublishStrand` + a
|
|
1642
|
+
* fresh publish also clears it for an OPEN strand, but is destructive for a closed one
|
|
1643
|
+
* (the key exists nowhere else). No app-data wipe is needed for either.
|
|
1286
1644
|
*
|
|
1287
1645
|
* The insert is signed with the ed25519 key behind this node's PeerId — which
|
|
1288
1646
|
* {@link ed25519KeyPairFromLibp2p} also exposes as the node's owner keypair,
|
|
1289
|
-
* so peer identity and owner key are one and the same. That key
|
|
1290
|
-
*
|
|
1647
|
+
* so peer identity and owner key are one and the same. That key is also persisted on the
|
|
1648
|
+
* row as `FounderOwnerKey` (the schema pins the column to the verified signer), which is
|
|
1649
|
+
* what later lets any launch derive "this machine is the founder" from the row alone.
|
|
1650
|
+
* The key must be enrolled in `OwnerKey` (e.g. via {@link ControlDatabase.ensureOwnerKey}
|
|
1291
1651
|
* at genesis) or the schema's `Strand.AuthorizedInsert` constraint rejects the write.
|
|
1292
1652
|
* Failing loudly here is intentional: a silently-unpublished strand would run
|
|
1293
1653
|
* as a local-only island that no peer could ever discover or join.
|
|
@@ -1296,10 +1656,62 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1296
1656
|
* {@link addStrand}).
|
|
1297
1657
|
* @param type - `'o'` for open (default) or `'c'` for closed.
|
|
1298
1658
|
* @param memberPrivateKey - Optional membership key for a closed strand.
|
|
1299
|
-
* @
|
|
1300
|
-
*
|
|
1301
|
-
|
|
1302
|
-
|
|
1659
|
+
* @returns The live `Strand` row — the one just inserted, or the matching one already
|
|
1660
|
+
* there. A closed strand's caller should carry THIS row's `MemberPrivateKey` forward:
|
|
1661
|
+
* on a repeat it is the stored key, not the argument.
|
|
1662
|
+
* @throws if the node is not started, exposes no owner signing key, the id is blank, a
|
|
1663
|
+
* row with the same id holds different content, or the control DB rejects the
|
|
1664
|
+
* (unauthorized) insert.
|
|
1665
|
+
*/
|
|
1666
|
+
publishStrand(strandId: string, type?: 'o' | 'c', memberPrivateKey?: string): Promise<StrandRow>;
|
|
1667
|
+
/**
|
|
1668
|
+
* Found a strand — publish its row cadre-wide AND start the local instance as its
|
|
1669
|
+
* founder — in one resumable call. The single entry point for creating a strand; a
|
|
1670
|
+
* caller joining one someone else founded uses {@link addStrand} alone.
|
|
1671
|
+
*
|
|
1672
|
+
* Safe to re-run from any point of interruption, which hand-rolling
|
|
1673
|
+
* {@link publishStrand} + {@link addStrand} is not. Either half can already have
|
|
1674
|
+
* happened:
|
|
1675
|
+
*
|
|
1676
|
+
* - **row already published** → the stored row is adopted rather than re-published, so a
|
|
1677
|
+
* run killed after the insert committed no longer dies on
|
|
1678
|
+
* `UNIQUE constraint failed: Strand.Id`. For a closed strand the row's STORED
|
|
1679
|
+
* `MemberPrivateKey` wins over `config.memberPrivateKey`: a caller that mints a key per
|
|
1680
|
+
* attempt (as the reference apps do) would otherwise present a key that does not match
|
|
1681
|
+
* the membership already seated in the strand.
|
|
1682
|
+
* - **instance already running** → the tracked instance is returned, and — because the
|
|
1683
|
+
* `Strand` row records its publishing machine (`FounderOwnerKey`) — a founder request
|
|
1684
|
+
* against an instance that was first launched as a joiner now runs the (idempotent,
|
|
1685
|
+
* insert-if-absent) founder bootstrap on it rather than silently skipping it
|
|
1686
|
+
* ({@link StrandInstanceManager.foundExistingStrand}). So a strand that something else
|
|
1687
|
+
* attached first — the reference RN app's `strand:discovered` handler after a restart,
|
|
1688
|
+
* or this node's own {@link StrandWatcher} poll winning `launchStrand`'s
|
|
1689
|
+
* `resolveCohortSeed` window — still ends up with its `Strand.Header` written.
|
|
1690
|
+
*
|
|
1691
|
+
* Founder-ness is resolved FROM THE ROW, not assumed: this machine founds iff the
|
|
1692
|
+
* resolved row's `FounderOwnerKey` is its own owner key. A fresh publish records this
|
|
1693
|
+
* machine, so the common path founds; a machine that lost a concurrent founding race to
|
|
1694
|
+
* a sibling adopts the sibling's row and ATTACHES instead — deliberately, since two
|
|
1695
|
+
* machines bootstrapping the same strand on separate replicas is the double-`Header`
|
|
1696
|
+
* hazard. The returned {@link FoundStrandResult.founded} says which happened.
|
|
1697
|
+
*
|
|
1698
|
+
* `Type` is compared, not adopted: a stored row of the other type means the caller and
|
|
1699
|
+
* the control plane disagree about what this strand IS, so it throws.
|
|
1700
|
+
*
|
|
1701
|
+
* @returns The instance AND the row the strand actually runs under — read the membership
|
|
1702
|
+
* key from the returned row, not from a freshly minted one ({@link FoundStrandResult}).
|
|
1703
|
+
* @throws if the node is not started, the id is blank, a published row of the same id has
|
|
1704
|
+
* a different `Type`, or either half rejects.
|
|
1705
|
+
*/
|
|
1706
|
+
foundStrand(config: FoundStrandConfig): Promise<FoundStrandResult>;
|
|
1707
|
+
/**
|
|
1708
|
+
* Resume onto an already-published `Strand` row: keep its stored content, reject a type
|
|
1709
|
+
* disagreement. The stored `MemberPrivateKey` is authoritative — see {@link foundStrand}
|
|
1710
|
+
* for why a caller-minted key must lose here (and why that is NOT the same rule as
|
|
1711
|
+
* {@link publishStrand}'s, which throws on a key mismatch because it is being asked to
|
|
1712
|
+
* WRITE that key, not to resume onto what already exists).
|
|
1713
|
+
*/
|
|
1714
|
+
private adoptPublishedStrand;
|
|
1303
1715
|
/**
|
|
1304
1716
|
* Remove this party's `Strand` row from the shared control database — the owner-signed
|
|
1305
1717
|
* inverse of {@link publishStrand}.
|
|
@@ -1451,14 +1863,25 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1451
1863
|
* seed, starts the strand, and registers it with the hibernation manager
|
|
1452
1864
|
* before emitting `strand:started`.
|
|
1453
1865
|
*
|
|
1454
|
-
*
|
|
1455
|
-
*
|
|
1456
|
-
*
|
|
1457
|
-
*
|
|
1458
|
-
*
|
|
1866
|
+
* Founder-ness: an explicit `founder` argument wins (the formation/responder flows pass
|
|
1867
|
+
* one deliberately — their consent-seated rows carry a null `FounderOwnerKey`); when
|
|
1868
|
+
* unset it is DERIVED from the row, so the control-discovered path
|
|
1869
|
+
* (`handleStrandAdded`) and a restart's re-attach found this machine's own strands
|
|
1870
|
+
* without the caller having to know. The derivation is pure key comparison — no I/O.
|
|
1871
|
+
*
|
|
1872
|
+
* Idempotent when the strand manager already tracks `strand.Id` — the watcher
|
|
1873
|
+
* rediscovers a row this node already started and calls this again via
|
|
1874
|
+
* `handleStrandAdded`; without the guard that re-entry would resolve a fresh
|
|
1459
1875
|
* (already-connected) cohort seed and re-emit `strand:started` for an
|
|
1460
1876
|
* instance that never stopped. The guard runs before the cohort-seed RPC
|
|
1461
|
-
* fan-out, so a rediscovery costs nothing beyond the map lookup
|
|
1877
|
+
* fan-out, so a rediscovery costs nothing beyond the map lookup plus the (pure)
|
|
1878
|
+
* founder derivation. NOT a silent no-op any more when the launch resolves as a
|
|
1879
|
+
* FOUNDER: the tracked instance may have been launched first as a joiner (an app
|
|
1880
|
+
* attach, or this node's own watcher poll winning the `resolveCohortSeed` window
|
|
1881
|
+
* below), which used to drop the founder request and leave the strand headerless —
|
|
1882
|
+
* now the tracked instance is founded in place
|
|
1883
|
+
* ({@link StrandInstanceManager.foundExistingStrand}), waking it first if quiesced
|
|
1884
|
+
* so the bootstrap actually runs before this resolves.
|
|
1462
1885
|
*/
|
|
1463
1886
|
private launchStrand;
|
|
1464
1887
|
/**
|
|
@@ -1488,6 +1911,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1488
1911
|
* for a relay we are not yet connected to.
|
|
1489
1912
|
*/
|
|
1490
1913
|
private resolveCohortSeed;
|
|
1914
|
+
/**
|
|
1915
|
+
* The own-party half of {@link resolveCohortSeed}: strand-network addresses resolved
|
|
1916
|
+
* from CONNECTED cohort siblings over the control-mesh strand-addr RPC, doubling as
|
|
1917
|
+
* the delegate announcement when `delegatePeerId` is given. Empty when the control DB
|
|
1918
|
+
* or node is absent (not yet started / torn down).
|
|
1919
|
+
*/
|
|
1920
|
+
private resolveSiblingSeed;
|
|
1491
1921
|
/**
|
|
1492
1922
|
* The co-cadre siblings worth sending a strand-addr RPC to right now: cohort
|
|
1493
1923
|
* members (self excluded) we already hold an open control connection to. They
|
|
@@ -1521,11 +1951,25 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1521
1951
|
private connectedSiblingTargets;
|
|
1522
1952
|
/**
|
|
1523
1953
|
* The circuit relays this node's strand nodes would reserve through: the
|
|
1524
|
-
* union of
|
|
1525
|
-
* `network.listenAddrs` circuit entry
|
|
1526
|
-
*
|
|
1527
|
-
*
|
|
1528
|
-
*
|
|
1954
|
+
* union of CONFIGURED relays — `network.relayAddrs`, plus any hand-written
|
|
1955
|
+
* `network.listenAddrs` circuit entry, both of which a strand node inherits
|
|
1956
|
+
* verbatim — and the control node's own live `/p2p-circuit` multiaddrs (a
|
|
1957
|
+
* reservation this node discovered rather than configured).
|
|
1958
|
+
*
|
|
1959
|
+
* The CONFIGURED half reads `network.relayAddrs` and `network.listenAddrs`
|
|
1960
|
+
* directly rather than going through {@link resolveListenAddrs}: the control
|
|
1961
|
+
* node's resolution takes the `'search'` route, whose bare `/p2p-circuit` entry
|
|
1962
|
+
* names no relay, so a configured relay would be unannounceable here until this
|
|
1963
|
+
* node's own reservation landed — and the announce must be in place BEFORE a
|
|
1964
|
+
* strand node dials its reservation, since the relay's membership gate does not
|
|
1965
|
+
* know the strand's derived peerId (see delegate-admission.ts). Configuration is
|
|
1966
|
+
* authoritative; the live multiaddrs only ADD relays nobody configured.
|
|
1967
|
+
*
|
|
1968
|
+
* The `listenAddrs` half is now unreachable on a control node — `relay-addrs.ts`
|
|
1969
|
+
* rejects a hand-written `<relay>/p2p-circuit` listen entry outright, because the
|
|
1970
|
+
* bring-up quiet period denies the dial that listener makes from inside
|
|
1971
|
+
* `libp2p.start()`. Kept because it costs one spread and this is the one place
|
|
1972
|
+
* that would silently stop announcing if that rejection is ever relaxed.
|
|
1529
1973
|
*
|
|
1530
1974
|
* NOTE: a relay the STRAND node discovers on its own (autorelay — in neither
|
|
1531
1975
|
* source) gets no announcement, and a membership-gated one will deny it; fine
|
|
@@ -1587,9 +2031,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1587
2031
|
*/
|
|
1588
2032
|
private refreshStrandPeerAddrs;
|
|
1589
2033
|
/**
|
|
1590
|
-
* One strand's share of {@link refreshStrandPeerAddrs}: RPC the siblings,
|
|
1591
|
-
*
|
|
1592
|
-
* swallowed so one strand's failure never
|
|
2034
|
+
* One strand's share of {@link refreshStrandPeerAddrs}: RPC the siblings, union their
|
|
2035
|
+
* answers with this strand's cross-party contact addresses, and merge the lot into the
|
|
2036
|
+
* strand's address book. Errors are logged and swallowed so one strand's failure never
|
|
2037
|
+
* costs the others their refresh.
|
|
2038
|
+
*
|
|
2039
|
+
* `targets` may be EMPTY — a solo node, or one whose only control peers are strangers.
|
|
2040
|
+
* The cross-party addresses still have to be re-merged in that case: they were learned
|
|
2041
|
+
* at formation and can never be re-resolved, so this periodic re-merge is the ONLY
|
|
2042
|
+
* thing standing between them and the peerStore's one-hour expiry. A strand with
|
|
2043
|
+
* neither a sibling to ask nor a contact entry does nothing and leaves its throttle
|
|
2044
|
+
* unstamped, so the next reconcile tick retries rather than sitting out the whole
|
|
2045
|
+
* refresh interval having done nothing.
|
|
1593
2046
|
*/
|
|
1594
2047
|
private refreshOneStrandPeerAddrs;
|
|
1595
2048
|
/**
|
|
@@ -1610,6 +2063,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1610
2063
|
* handshake, and a bad entry costs one failed dial that ages out at the
|
|
1611
2064
|
* peerStore's one-hour expiry. No new gating here — cross-party strand trust is
|
|
1612
2065
|
* `backlog/strand-network-nat-relay-reachability`.
|
|
2066
|
+
*
|
|
2067
|
+
* NOTE: one input does NOT age out — the cross-party addresses a formation
|
|
2068
|
+
* carried back ({@link crossPartyStrandAddrs}). Nothing can re-resolve them, so
|
|
2069
|
+
* {@link refreshStrandPeerAddrs} re-merges the same stored list every pass, which
|
|
2070
|
+
* also means a junk entry from a responder survives for the node's lifetime rather
|
|
2071
|
+
* than an hour. Bounded to {@link MAX_STRAND_ADDRS} per strand, from a party this
|
|
2072
|
+
* node deliberately formed a strand with, and still authority-free — so the
|
|
2073
|
+
* exposure is a handful of failed dials, not a trust hole. Evict on
|
|
2074
|
+
* `unpublishStrand` if that ever stops being an acceptable price.
|
|
1613
2075
|
*/
|
|
1614
2076
|
private mergeStrandPeerAddrs;
|
|
1615
2077
|
/**
|
|
@@ -1719,16 +2181,48 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1719
2181
|
* Get the control network node (for advanced use)
|
|
1720
2182
|
*/
|
|
1721
2183
|
getControlNode(): Libp2p | null;
|
|
2184
|
+
/**
|
|
2185
|
+
* The boot-path half of {@link reserveRelays}: reserve through every
|
|
2186
|
+
* `network.relayAddrs` entry, or fail `start()`.
|
|
2187
|
+
*
|
|
2188
|
+
* WHY IT RUNS HERE, AT THE END. `network.relayAddrs` used to resolve to a
|
|
2189
|
+
* CONFIGURED circuit listener, which libp2p dials from inside `libp2p.start()`
|
|
2190
|
+
* — so the relay was already a connected peer, and therefore already in this
|
|
2191
|
+
* node's Optimystic cohort, before `ControlDatabase.initialize()` ran. Building
|
|
2192
|
+
* that database is a long chain of cohort-consulting block probes (catalog
|
|
2193
|
+
* hydration, then one per table, then the indexes), and a sibling that has not
|
|
2194
|
+
* yet replicated this node's `CadrePeer` row correctly refuses every one of
|
|
2195
|
+
* them, so bring-up died on `BlockUnavailableError` — deterministically, on
|
|
2196
|
+
* every boot of a relay-only node. Retrying could not converge either: the
|
|
2197
|
+
* condition that clears the refusal is this node's own row reaching the
|
|
2198
|
+
* sibling, and writing that row needs the database the retry is building.
|
|
2199
|
+
*
|
|
2200
|
+
* So the invariant is ordering, not retrying: the control database is built
|
|
2201
|
+
* while this node holds ZERO control connections (a cohort of one, entirely
|
|
2202
|
+
* local), and only then does it reach out. Everything above in {@link start}
|
|
2203
|
+
* has completed by the time this runs.
|
|
2204
|
+
*
|
|
2205
|
+
* BUDGET: the supervisor's first attempt — `relay-reservation.ts`'s
|
|
2206
|
+
* `DEFAULT_RELAY_RESERVE_TIMEOUT_MS`, 10 s — is what `start()` waits on — deliberately the module default rather
|
|
2207
|
+
* than a boot-specific one. A healthy dial-plus-reserve is sub-second even over
|
|
2208
|
+
* a WAN, so 10 s is slack for a slow link; going much longer would make a dead
|
|
2209
|
+
* relay indistinguishable from a hung start, and much shorter would fail nodes
|
|
2210
|
+
* on links that were merely slow. The retries carry on in the background after
|
|
2211
|
+
* this resolves, exactly as they do for a {@link reserveRelays} caller.
|
|
2212
|
+
*/
|
|
2213
|
+
private driveControlRelayReservation;
|
|
1722
2214
|
/**
|
|
1723
2215
|
* Start keeping a relay reservation: dial the given relay(s) from the control
|
|
1724
2216
|
* node, ask the first one that answers for a reservation slot, wait until the
|
|
1725
2217
|
* resulting `/p2p-circuit` address makes this node dialable — and keep a
|
|
1726
2218
|
* supervisor running that re-drives whenever the reservation is later lost.
|
|
1727
2219
|
*
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1730
|
-
*
|
|
1731
|
-
*
|
|
2220
|
+
* The FAIL-SOFT entry point, for a caller that discovers its relay at runtime
|
|
2221
|
+
* (a browser tab, a host UI). A node that names `network.relayAddrs` gets the
|
|
2222
|
+
* same drive from {@link driveControlRelayReservation} at the end of
|
|
2223
|
+
* {@link start}, which is fail-fast instead. Both fill the pending reservation
|
|
2224
|
+
* the bare `/p2p-circuit` search listener registered; calling this afterwards
|
|
2225
|
+
* simply replaces the supervisor with one over the new list.
|
|
1732
2226
|
*
|
|
1733
2227
|
* Resolves once the FIRST attempt has settled, exactly as it did when it drove
|
|
1734
2228
|
* once; the retries continue in the background until {@link stop} or the next
|
|
@@ -1790,13 +2284,40 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1790
2284
|
* Push the multiaddrs that future invites should advertise. Pass `null` to
|
|
1791
2285
|
* revert to the libp2p-reported addresses (the default). The host calls this
|
|
1792
2286
|
* at spawn and on every NAT change.
|
|
2287
|
+
*
|
|
2288
|
+
* Entries need NOT carry a `/p2p/<peerId>` suffix — {@link resolveInviteAddresses}
|
|
2289
|
+
* appends this node's own before anything publishes or dials them (see
|
|
2290
|
+
* {@link normalizeSelfAddrs}). Passing them suffixed is equally fine.
|
|
1793
2291
|
*/
|
|
1794
2292
|
setInviteAddresses(addresses: string[] | null): void;
|
|
1795
2293
|
/**
|
|
1796
2294
|
* Resolve the addresses to embed in invites. Prefers pushed addresses, then
|
|
1797
|
-
* any config-supplied resolver, then the libp2p-observed multiaddrs.
|
|
2295
|
+
* any config-supplied resolver, then the libp2p-observed multiaddrs. The two
|
|
2296
|
+
* app-supplied sources are normalized onto `/p2p/<self>` (see
|
|
2297
|
+
* {@link normalizeSelfAddrs}) so neither hook has to remember the suffix;
|
|
2298
|
+
* libp2p's own addresses already carry it.
|
|
1798
2299
|
*/
|
|
1799
2300
|
private resolveInviteAddresses;
|
|
2301
|
+
/**
|
|
2302
|
+
* Normalize addresses an app handed us for THIS node onto `/p2p/<self>`, the
|
|
2303
|
+
* same invariant {@link normalizeDialAddrs} enforces on the way out.
|
|
2304
|
+
*
|
|
2305
|
+
* `setInviteAddresses` (the admin API `PUT /admin/invite-addresses`) and
|
|
2306
|
+
* `network.inviteAddressResolver` both take arbitrary strings, and whatever
|
|
2307
|
+
* they return ends up in this node's published `CadrePeer` record — so ONE
|
|
2308
|
+
* unsuffixed entry from either hook is enough to give every sibling in the
|
|
2309
|
+
* party a mixed candidate list for this peer. Establishing the suffix here,
|
|
2310
|
+
* where those strings enter the system, means neither hook has to know the
|
|
2311
|
+
* rule; `cadre-host`'s `buildInviteAddresses` already appends it, and
|
|
2312
|
+
* re-normalizing an already-suffixed address is a no-op.
|
|
2313
|
+
*
|
|
2314
|
+
* Unparsable and other-peer-addressed entries are passed through untouched
|
|
2315
|
+
* rather than dropped: publication is not the place to police an address's
|
|
2316
|
+
* validity, and {@link resolvePeerAddrs} already drops both on the read side.
|
|
2317
|
+
* Before `start()` resolves an identity there is no peer id to append, so the
|
|
2318
|
+
* list is returned as given.
|
|
2319
|
+
*/
|
|
2320
|
+
private normalizeSelfAddrs;
|
|
1800
2321
|
/**
|
|
1801
2322
|
* Enumerate the cadre's `CadrePeer` membership — the ADDRESSABLE surface (see
|
|
1802
2323
|
* {@link isMember}). Rows whose stamp is retired in `CadreControl.Revocation`
|
|
@@ -1853,8 +2374,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1853
2374
|
* legit member goes un-authorized on readers that only pin the new key. Full
|
|
1854
2375
|
* rotation handling (re-vouch on rotate) is the
|
|
1855
2376
|
* `flip-strand-membership-rotation-known-gap` work, not this predicate's.
|
|
2377
|
+
*
|
|
2378
|
+
* @param retry - Whether the underlying membership read may retry a transient cluster
|
|
2379
|
+
* failure. Only {@link refreshAuthorizedControlPeers} passes `false`, because it runs
|
|
2380
|
+
* as the control database's membership listener with that database's write lock held;
|
|
2381
|
+
* see its comment.
|
|
1856
2382
|
*/
|
|
1857
|
-
listAuthorizedMembers(): Promise<Array<{
|
|
2383
|
+
listAuthorizedMembers(retry?: boolean): Promise<Array<{
|
|
1858
2384
|
peerId: string;
|
|
1859
2385
|
multiaddr: string | null;
|
|
1860
2386
|
}>>;
|
|
@@ -1940,6 +2466,23 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1940
2466
|
* best-effort (a failed read keeps the previous snapshot); never rejects.
|
|
1941
2467
|
*/
|
|
1942
2468
|
refreshMembershipGate(reason?: string): Promise<void>;
|
|
2469
|
+
/**
|
|
2470
|
+
* Re-materialize a CLOSED strand's revoked-peer deny set now, and hang up any
|
|
2471
|
+
* connected peer it newly covers — the strand-side counterpart to
|
|
2472
|
+
* {@link refreshMembershipGate}.
|
|
2473
|
+
*
|
|
2474
|
+
* Unlike that one, NOTHING calls this automatically: strand membership is
|
|
2475
|
+
* written through the strand's own `Database` handle
|
|
2476
|
+
* (`strand-membership-writer.ts`), which raises no notification this runtime
|
|
2477
|
+
* can hook, and a revocation that arrives by REPLICATION raises none either.
|
|
2478
|
+
* The gate therefore polls (default 30 s). An app that has just called
|
|
2479
|
+
* `revokeMember` or `leaveStrand` should follow the write with this call so
|
|
2480
|
+
* the cut is immediate rather than up to one poll interval late.
|
|
2481
|
+
*
|
|
2482
|
+
* Quiet no-op for a strand that is not running, is quiesced, is open, or has
|
|
2483
|
+
* the gate disabled. Never rejects; resolves once the sweep has finished.
|
|
2484
|
+
*/
|
|
2485
|
+
refreshRevocationEnforcement(strandId: string): Promise<void>;
|
|
1943
2486
|
/**
|
|
1944
2487
|
* The single in-flight refresh loop behind {@link refreshMembershipGate}: read
|
|
1945
2488
|
* until the stale flag stays clear, then release the slot.
|
|
@@ -2086,6 +2629,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2086
2629
|
* @returns The member key and strand info if successful
|
|
2087
2630
|
*/
|
|
2088
2631
|
formStrand(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<FormStrandResult>;
|
|
2632
|
+
/**
|
|
2633
|
+
* Remember the responder's strand-network addresses for a strand this node just
|
|
2634
|
+
* formed, so the strand's discovery seed has something to work with when the app
|
|
2635
|
+
* launches it (see {@link crossPartyStrandAddrs}).
|
|
2636
|
+
*
|
|
2637
|
+
* Scoped strictly to `strandId`: these addresses reach ONE strand node of ONE other
|
|
2638
|
+
* party and must never seed another strand's mesh or the control peerStore. An empty
|
|
2639
|
+
* list is recorded as no entry at all rather than an empty one, so a later formation
|
|
2640
|
+
* against the same strand that DOES disclose addresses is not shadowed.
|
|
2641
|
+
*/
|
|
2642
|
+
private recordCrossPartyStrandAddrs;
|
|
2089
2643
|
/**
|
|
2090
2644
|
* Encode an open invitation for out-of-band delivery (QR, link, etc.).
|
|
2091
2645
|
*/
|