@serfab/cadre-core 1.9.0 → 1.10.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/dist/cadre-node.d.ts +148 -17
- package/dist/cadre-node.js +361 -45
- 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 +1 -1
- package/dist/control-cohort.js +1 -1
- package/dist/control-database.d.ts +105 -14
- package/dist/control-database.js +242 -73
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +6 -6
- package/dist/control-formation-recorder.js +7 -8
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +90 -3
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +21 -15
- package/dist/control-write-retry.js +21 -15
- package/dist/control-write-retry.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/invitation-bootstrap.d.ts +32 -0
- package/dist/invitation-bootstrap.js +30 -0
- package/dist/invitation-bootstrap.js.map +1 -0
- package/dist/link-budget.d.ts +204 -119
- package/dist/link-budget.js +208 -120
- package/dist/link-budget.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +3 -3
- package/dist/membership-connection-gater.js +3 -3
- package/dist/peer-dial.d.ts +18 -14
- package/dist/peer-dial.js +18 -14
- package/dist/peer-dial.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +3 -3
- package/dist/pending-join-runner.d.ts +186 -0
- package/dist/pending-join-runner.js +649 -0
- package/dist/pending-join-runner.js.map +1 -0
- package/dist/push-fanout.d.ts +1 -1
- package/dist/push-fanout.js +1 -1
- package/dist/seed-bootstrap.d.ts +2 -2
- package/dist/seed-bootstrap.js +1 -1
- package/dist/strand-addr-protocol.js +2 -2
- package/dist/strand-formation-deadlines.d.ts +5 -5
- package/dist/strand-formation-deadlines.js +5 -5
- package/dist/strand-formation-manager.d.ts +18 -4
- package/dist/strand-formation-manager.js +48 -33
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +71 -14
- package/dist/strand-formation-protocol.js +222 -47
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-formation-rejection.d.ts +94 -0
- package/dist/strand-formation-rejection.js +77 -0
- package/dist/strand-formation-rejection.js.map +1 -0
- package/dist/strand-instance-manager.js +4 -1
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-solicitation.d.ts +11 -3
- package/dist/strand-solicitation.js +9 -0
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/types.d.ts +103 -17
- package/dist/types.js +5 -9
- package/dist/types.js.map +1 -1
- package/package.json +22 -22
- package/src/cadre-node.ts +399 -46
- package/src/control-authorization.ts +117 -116
- package/src/control-cohort.ts +1 -1
- package/src/control-database.ts +303 -82
- package/src/control-formation-recorder.ts +8 -9
- package/src/control-schema.ts +90 -3
- package/src/control-write-retry.ts +21 -15
- package/src/index.ts +21 -1
- package/src/invitation-bootstrap.ts +40 -0
- package/src/link-budget.ts +221 -120
- package/src/membership-connection-gater.ts +3 -3
- package/src/peer-dial.ts +18 -14
- package/src/peer-join-backfill.ts +3 -3
- package/src/pending-join-runner.ts +735 -0
- package/src/push-fanout.ts +1 -1
- package/src/seed-bootstrap.ts +2 -2
- package/src/strand-addr-protocol.ts +2 -2
- package/src/strand-formation-deadlines.ts +5 -5
- package/src/strand-formation-manager.ts +62 -37
- package/src/strand-formation-protocol.ts +1036 -802
- package/src/strand-formation-rejection.ts +129 -0
- package/src/strand-instance-manager.ts +4 -1
- package/src/strand-solicitation.ts +17 -4
- package/src/types.ts +99 -17
package/dist/cadre-node.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Libp2p, PeerId } from '@libp2p/interface';
|
|
2
2
|
import type { Multiaddr } from '@multiformats/multiaddr';
|
|
3
|
-
import type { CadreNodeConfig, StrandInstance, StrandRow, StrandConfig, FoundStrandConfig, FoundStrandResult, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, AddDroneOptions, AddPhoneOptions, DroneInitResult, InviteResult, CadreInvite, OpenInvitation, FormStrandResult, StrandFormationDisclosure, StrandMembershipInvite, 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, StrandMembershipInvite, ResolveOpts, SelfRegistrationOutcome, ServiceWakeResult, PushPlatform, DeviceTokenRecord, ResolveDeviceTokenOpts, PendingJoinStatus } 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';
|
|
@@ -84,6 +84,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
84
84
|
private enrollmentService;
|
|
85
85
|
private seedBootstrapService;
|
|
86
86
|
private strandSolicitationService;
|
|
87
|
+
/**
|
|
88
|
+
* The service whose formation handler is registered on the control node. Trails
|
|
89
|
+
* {@link strandSolicitationService} while {@link initializeStrandSolicitation} swaps a new
|
|
90
|
+
* one in: the field is set at once so concurrent callers reuse it, the handler only when
|
|
91
|
+
* its queued swap runs.
|
|
92
|
+
*/
|
|
93
|
+
private registeredSolicitation;
|
|
94
|
+
/** Tail of the queued formation-handler swaps ({@link swapFormationResponder}). */
|
|
95
|
+
private solicitationSwaps;
|
|
87
96
|
private strandWakeService;
|
|
88
97
|
/**
|
|
89
98
|
* Control-network strand-address responder. Answers a co-cadre sibling's
|
|
@@ -382,6 +391,20 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
382
391
|
* `unpublishStrand` / `forgetJoinedStrand` as {@link formationStrandAddrs} is.
|
|
383
392
|
*/
|
|
384
393
|
private readonly pendingMembershipInvites;
|
|
394
|
+
/**
|
|
395
|
+
* Invite keys of the membership invitations this process has staged, by
|
|
396
|
+
* {@link adoptFormationMembershipInvite} or from a `joined` `PendingJoin` row
|
|
397
|
+
* ({@link stageMembershipInvitesFromPendingJoins}), so one the reconciler has settled is not
|
|
398
|
+
* staged again by the next pending-join pass. Same lifetime as
|
|
399
|
+
* {@link pendingMembershipInvites}: a restarted process stages each live one once more, and the
|
|
400
|
+
* reconciler settles it again (spent, or the party already seated).
|
|
401
|
+
*/
|
|
402
|
+
private readonly stagedMembershipInviteKeys;
|
|
403
|
+
/**
|
|
404
|
+
* The pending-join retry loop (`pending-join-runner.ts`): built by {@link start}, stopped by
|
|
405
|
+
* {@link cleanup}. It acts only while this machine is an enrolled owner.
|
|
406
|
+
*/
|
|
407
|
+
private pendingJoinRunner;
|
|
385
408
|
/**
|
|
386
409
|
* Founder launches refused because the strand was founded before the per-party
|
|
387
410
|
* identity split (`PreSplitStrandIdentityError`), keyed by strandId. Read by
|
|
@@ -966,11 +989,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
966
989
|
* `StrandSolicitationService.hasOutstandingInvitation`). A formation
|
|
967
990
|
* initiator is another party's peer by design and its token is only
|
|
968
991
|
* checkable inside the protocol, so the gate asks the coarser question
|
|
969
|
-
* "does this node expect a stranger at all?".
|
|
970
|
-
*
|
|
971
|
-
*
|
|
972
|
-
* bring-up) and `formStrand`'s lazy initialization both leave the gate
|
|
973
|
-
* armed, because neither mints an invitation.
|
|
992
|
+
* "does this node expect a stranger at all?". REGISTERING the responder
|
|
993
|
+
* does not suspend stranger denial: every node registers one at
|
|
994
|
+
* {@link start}, and registering mints no invitation.
|
|
974
995
|
*
|
|
975
996
|
* Ordering is semantically free (the checks are OR'd) but decides who pays:
|
|
976
997
|
* checks 1-3 are in-memory, 4/5 share one control-DB read, and only a peer
|
|
@@ -1002,6 +1023,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1002
1023
|
* layer is fail-open behind `ADMISSION_DECISION_TIMEOUT_MS`, so the live
|
|
1003
1024
|
* read is safe here — unlike the per-stream gate, which must consult the
|
|
1004
1025
|
* materialized {@link authorizedControlPeers} snapshot instead.
|
|
1026
|
+
* NOTE: check 6 adds a second control read (`hasOutstandingFormationInvite`) for a
|
|
1027
|
+
* stranger with no locally minted invitation in play, on every node now that every
|
|
1028
|
+
* node runs the responder — a relay-enabled storage node included. If stranger
|
|
1029
|
+
* connections to such a node ever arrive fast enough for that read to show, cache
|
|
1030
|
+
* the answer for a few seconds.
|
|
1005
1031
|
* NOTE: on a relay-DISABLED node, a sibling whose membership row has not yet
|
|
1006
1032
|
* replicated here is denied until the row converges (typically via the
|
|
1007
1033
|
* owner); either side's next outbound reconcile dial (outbound is never
|
|
@@ -1344,6 +1370,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1344
1370
|
* handling, is what keeps a mixed list from taking a whole peer offline.
|
|
1345
1371
|
*/
|
|
1346
1372
|
resolvePeerAddrs(peerId: string, opts?: ResolveOpts): Promise<Multiaddr[]>;
|
|
1373
|
+
/**
|
|
1374
|
+
* {@link resolvePeerAddrs} with the record's `UpdatedAt` kept beside the addresses, for a
|
|
1375
|
+
* caller that ranks peers by how recently they published. `null` wherever that returns `[]`
|
|
1376
|
+
* for a failed gate.
|
|
1377
|
+
*/
|
|
1378
|
+
private resolvePeerRecord;
|
|
1347
1379
|
/**
|
|
1348
1380
|
* Guarantee every address handed to a control-network dial terminates in
|
|
1349
1381
|
* `/p2p/<peerId>`, with no duplicates — the invariant every dial path
|
|
@@ -1537,15 +1569,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1537
1569
|
*
|
|
1538
1570
|
* The list may name transports this node cannot dial (a lent node reports TCP
|
|
1539
1571
|
* and `/ws` addresses to a phone that dials WebSockets only). That needs no
|
|
1540
|
-
* filtering here: libp2p's dial queue (`calculateMultiaddrs`, libp2p 3.
|
|
1572
|
+
* filtering here: libp2p's dial queue (`calculateMultiaddrs`, libp2p 3.3.11)
|
|
1541
1573
|
* rejects an address no transport can dial before touching the network, so
|
|
1542
1574
|
* such an address costs a log line.
|
|
1543
1575
|
*
|
|
1544
1576
|
* The list is NOT handed to one `dial()`. libp2p tries a multi-address dial's
|
|
1545
|
-
* addresses one after another under a single deadline,
|
|
1546
|
-
*
|
|
1547
|
-
*
|
|
1548
|
-
*
|
|
1577
|
+
* addresses one after another under a single deadline, and sorts loopback
|
|
1578
|
+
* addresses last — so one or two addresses that never answer use up most of
|
|
1579
|
+
* the deadline before the address that works is tried. libp2p 3.3's
|
|
1580
|
+
* per-address `addressDialTimeout` does not prevent that: cadre sizes it for a
|
|
1581
|
+
* cold relayed open through two admission decisions (`optimysticDialLimits`,
|
|
1582
|
+
* 39 s at the default declared link).
|
|
1583
|
+
* {@link dialPeerAddrs} dials each address on its own time limit instead.
|
|
1549
1584
|
*/
|
|
1550
1585
|
private resolveControlDialAddrs;
|
|
1551
1586
|
/**
|
|
@@ -2613,9 +2648,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2613
2648
|
* caller runs the drive regardless).
|
|
2614
2649
|
*
|
|
2615
2650
|
* NOTE: against a relay that is DOWN this hook costs up to two strand-addr
|
|
2616
|
-
* timeouts (dial by peer id, then by addr;
|
|
2651
|
+
* timeouts (dial by peer id, then by addr; 28.5 s each at the default declared link round trip)
|
|
2617
2652
|
* before the reservation drive even starts, so one failed re-drive holds the supervisor
|
|
2618
|
-
* `driving` for those
|
|
2653
|
+
* `driving` for those 57 s plus the drive's own deadline — 75 s at the default, and longer on
|
|
2619
2654
|
* a host that declared a slower one (`link-budget.ts`).
|
|
2620
2655
|
* Bounded and harmless while the relay is unreachable anyway; if recovery
|
|
2621
2656
|
* latency after a relay comes back ever matters, skip the announce when the
|
|
@@ -3435,32 +3470,128 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3435
3470
|
*/
|
|
3436
3471
|
dialInvite(invite: CadreInvite): Promise<void>;
|
|
3437
3472
|
/**
|
|
3438
|
-
*
|
|
3439
|
-
*
|
|
3473
|
+
* Replace this node's strand solicitation service — the formation responder every node
|
|
3474
|
+
* installs at {@link start}, and the initiator side of {@link formStrand}. Optional: call it
|
|
3475
|
+
* only to customize the responder (an approver, a provisioner, formation deadlines). The
|
|
3476
|
+
* new service's handler replaces the previous one's on the control node, and invitations
|
|
3477
|
+
* the previous service minted stay outstanding.
|
|
3478
|
+
*
|
|
3479
|
+
* Without `options.formationUsageRecorder`, tokens are checked against this party's
|
|
3480
|
+
* replicated `FormationInvite`/`FormationUsage` rows ({@link ControlFormationUsageRecorder}).
|
|
3481
|
+
* A responder that accepts every token is reachable only by constructing
|
|
3482
|
+
* {@link StrandSolicitationService} directly.
|
|
3440
3483
|
*
|
|
3441
3484
|
* @param options - Configuration for the solicitation service
|
|
3442
3485
|
*/
|
|
3443
3486
|
initializeStrandSolicitation(options?: StrandSolicitationServiceOptions): Promise<void>;
|
|
3444
3487
|
/**
|
|
3445
|
-
*
|
|
3488
|
+
* Answer formation from start. Log-and-continue: a node that cannot is still useful, and
|
|
3489
|
+
* {@link createOpenInvitation} / {@link formStrand} install the responder when it is missing.
|
|
3490
|
+
*/
|
|
3491
|
+
private installDefaultFormationResponder;
|
|
3492
|
+
/**
|
|
3493
|
+
* Move the control node's formation handler from the registered service to `service`.
|
|
3494
|
+
* Queued by {@link initializeStrandSolicitation} so swaps run one at a time: libp2p allows
|
|
3495
|
+
* one handler per protocol id, and two overlapping swaps would each find the other's
|
|
3496
|
+
* handler in the way. A failed swap registers the previous service again, points
|
|
3497
|
+
* {@link strandSolicitationService} back at it unless a later call has replaced it, and
|
|
3498
|
+
* rethrows.
|
|
3499
|
+
*
|
|
3500
|
+
* NOTE: same stray-handler caveat as {@link installSeedBootstrapService} — a failed
|
|
3501
|
+
* peer-store merge leaves the formation handler registered, so the restore below (and a
|
|
3502
|
+
* retry) rejects as a duplicate.
|
|
3503
|
+
*/
|
|
3504
|
+
private swapFormationResponder;
|
|
3505
|
+
/**
|
|
3506
|
+
* Get the strand solicitation service (for advanced use). Non-null on a started node unless
|
|
3507
|
+
* the install at {@link start} failed.
|
|
3446
3508
|
*/
|
|
3447
3509
|
getStrandSolicitationService(): StrandSolicitationService | null;
|
|
3448
3510
|
/**
|
|
3449
3511
|
* Create an open invitation for others to form strands with this party.
|
|
3450
3512
|
*
|
|
3513
|
+
* The invitation's bootstrap list names this machine first (it is the one most likely to
|
|
3514
|
+
* run the host strand), then a few of the party's other machines
|
|
3515
|
+
* ({@link siblingInvitationAddrs}), so a joiner can still form while this one is offline.
|
|
3516
|
+
*
|
|
3451
3517
|
* @param sAppId - The sApp to use for formed strands
|
|
3452
3518
|
* @param expirationMs - How long the invitation is valid (ms from now)
|
|
3453
3519
|
* @returns The open invitation to share out-of-band
|
|
3520
|
+
* @throws when neither this machine nor any other machine of the party has an address
|
|
3454
3521
|
*/
|
|
3455
3522
|
createOpenInvitation(sAppId: string, expirationMs?: number): Promise<OpenInvitation>;
|
|
3456
3523
|
/**
|
|
3457
|
-
*
|
|
3524
|
+
* Addresses of the party's other machines for an invitation's bootstrap list
|
|
3525
|
+
* ({@link selectInvitationSiblingAddrs} picks which). The source is each authorized
|
|
3526
|
+
* member's signed `CadrePeer` record ({@link resolvePeerRecord}), so a machine whose record
|
|
3527
|
+
* is missing, stale or untrusted is left out; live connections only order them.
|
|
3528
|
+
*
|
|
3529
|
+
* Best-effort: a failed read leaves the invitation naming this machine alone, which is what
|
|
3530
|
+
* it named before siblings were added.
|
|
3531
|
+
*/
|
|
3532
|
+
private siblingInvitationAddrs;
|
|
3533
|
+
/**
|
|
3534
|
+
* Form a strand with a responder via an open invitation: one attempt, from this machine, with
|
|
3535
|
+
* nothing recorded party-wide. {@link requestJoin} is the path that keeps trying.
|
|
3458
3536
|
*
|
|
3459
3537
|
* @param invitation - The open invitation received out-of-band
|
|
3460
3538
|
* @param disclosure - Identity/context information to share with the responder
|
|
3461
3539
|
* @returns The member key and strand info if successful
|
|
3540
|
+
* @throws `FormationRejectedError` or `FormationUnreachableError` when the formation fails, and
|
|
3541
|
+
* `FormationPostApprovalError` when it was approved and a step on this machine then failed
|
|
3462
3542
|
*/
|
|
3463
3543
|
formStrand(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<FormStrandResult>;
|
|
3544
|
+
/**
|
|
3545
|
+
* Ask to join through `invitation` and have the party keep trying until the join works, the
|
|
3546
|
+
* invitation is used up or expires, or the request is dismissed: across restarts, and from
|
|
3547
|
+
* any owner machine of the party (`docs/strands.md` → "Joining while the inviter is
|
|
3548
|
+
* offline"). Records a party-wide `PendingJoin` row, runs one attempt on this machine at
|
|
3549
|
+
* once, and returns the status after it: `'joined'` when the inviter answered yes, `'waiting'`
|
|
3550
|
+
* when it was unreachable or not ready, `'failed'` when it refused for good.
|
|
3551
|
+
*
|
|
3552
|
+
* Asking again for an invitation whose request is still pending adopts that request as it
|
|
3553
|
+
* is; asking again after it finished starts a fresh one. Later changes arrive as
|
|
3554
|
+
* `pendingJoin:changed`. A joined strand is offered through `strand:discovered` like any
|
|
3555
|
+
* {@link formStrand} join. Do not also call {@link formStrand} with the same invitation:
|
|
3556
|
+
* nothing coordinates the two, and one of them gets `token-spent`.
|
|
3557
|
+
*
|
|
3558
|
+
* @throws on a machine that is not an enrolled owner (the row is owner-signed), and on an
|
|
3559
|
+
* invitation already past its expiration
|
|
3560
|
+
*/
|
|
3561
|
+
requestJoin(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<PendingJoinStatus>;
|
|
3562
|
+
/**
|
|
3563
|
+
* Every join asked for with {@link requestJoin} and not dismissed, pending or finished, as this
|
|
3564
|
+
* machine sees it (see `PendingJoinStatus` for which states are machine-local).
|
|
3565
|
+
*/
|
|
3566
|
+
listPendingJoins(): Promise<PendingJoinStatus[]>;
|
|
3567
|
+
/**
|
|
3568
|
+
* Remove a join request party-wide. On a pending request this cancels it: every owner machine
|
|
3569
|
+
* stops at its next read, and an attempt already running does not record its outcome (a join
|
|
3570
|
+
* it made stays on that machine, as any {@link formStrand} join does). Owner machines only.
|
|
3571
|
+
* Resolves `false` when there was no such request.
|
|
3572
|
+
*/
|
|
3573
|
+
dismissPendingJoin(id: string): Promise<boolean>;
|
|
3574
|
+
private requirePendingJoins;
|
|
3575
|
+
/** This machine's owner key and signer for an owner-signed control write, or a throw carrying `refusal`. */
|
|
3576
|
+
private requireOwnerSigner;
|
|
3577
|
+
/**
|
|
3578
|
+
* Write a join request's row, or adopt the party's existing row for the same invitation: a
|
|
3579
|
+
* pending one as it is, a finished one replaced by `requested` (the user asked again).
|
|
3580
|
+
*/
|
|
3581
|
+
private recordJoinRequest;
|
|
3582
|
+
/** Build and start the pending-join retry loop over this node (see {@link pendingJoinRunner}). */
|
|
3583
|
+
private startPendingJoinRunner;
|
|
3584
|
+
/** The invitation a `PendingJoin` row stores. A failure names the row, never the invitation, which is a bearer credential. */
|
|
3585
|
+
private pendingJoinInvitation;
|
|
3586
|
+
private pendingJoinSAppId;
|
|
3587
|
+
/**
|
|
3588
|
+
* Stage the membership invitation of each `joined` row this process has not staged yet, so a
|
|
3589
|
+
* closed strand joined on another owner machine is seated by whichever machine launches it
|
|
3590
|
+
* first; the party key it admits is the replicated `StrandPartyKey` row the finishing machine
|
|
3591
|
+
* seated. A strand that already has an invitation staged keeps it, for the reconciler to
|
|
3592
|
+
* settle first.
|
|
3593
|
+
*/
|
|
3594
|
+
private stageMembershipInvitesFromPendingJoins;
|
|
3464
3595
|
/**
|
|
3465
3596
|
* Remember the strand a formation just joined (see {@link joinedStrandStore}), so it
|
|
3466
3597
|
* is re-offered after a restart even when the app is killed before its `addStrand`, and
|