@serfab/cadre-core 1.9.0 → 1.11.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.
Files changed (119) hide show
  1. package/dist/cadre-node.d.ts +148 -44
  2. package/dist/cadre-node.js +363 -139
  3. package/dist/cadre-node.js.map +1 -1
  4. package/dist/control-authorization.d.ts +2 -2
  5. package/dist/control-authorization.js +1 -0
  6. package/dist/control-authorization.js.map +1 -1
  7. package/dist/control-cohort.d.ts +1 -1
  8. package/dist/control-cohort.js +1 -1
  9. package/dist/control-database.d.ts +105 -14
  10. package/dist/control-database.js +242 -73
  11. package/dist/control-database.js.map +1 -1
  12. package/dist/control-formation-recorder.d.ts +6 -6
  13. package/dist/control-formation-recorder.js +7 -8
  14. package/dist/control-formation-recorder.js.map +1 -1
  15. package/dist/control-schema.d.ts +1 -1
  16. package/dist/control-schema.js +1066 -979
  17. package/dist/control-schema.js.map +1 -1
  18. package/dist/control-write-retry.d.ts +21 -15
  19. package/dist/control-write-retry.js +21 -15
  20. package/dist/control-write-retry.js.map +1 -1
  21. package/dist/diagnostics/connection-path.d.ts +1 -18
  22. package/dist/diagnostics/connection-path.js +1 -20
  23. package/dist/diagnostics/connection-path.js.map +1 -1
  24. package/dist/identity-key.d.ts +0 -30
  25. package/dist/identity-key.js +7 -40
  26. package/dist/identity-key.js.map +1 -1
  27. package/dist/index.d.ts +8 -5
  28. package/dist/index.js +12 -6
  29. package/dist/index.js.map +1 -1
  30. package/dist/invitation-bootstrap.d.ts +32 -0
  31. package/dist/invitation-bootstrap.js +30 -0
  32. package/dist/invitation-bootstrap.js.map +1 -0
  33. package/dist/link-budget.d.ts +204 -119
  34. package/dist/link-budget.js +208 -120
  35. package/dist/link-budget.js.map +1 -1
  36. package/dist/membership-connection-gater.d.ts +3 -3
  37. package/dist/membership-connection-gater.js +3 -3
  38. package/dist/peer-dial.d.ts +18 -14
  39. package/dist/peer-dial.js +18 -14
  40. package/dist/peer-dial.js.map +1 -1
  41. package/dist/peer-join-backfill.d.ts +3 -3
  42. package/dist/pending-join-runner.d.ts +186 -0
  43. package/dist/pending-join-runner.js +649 -0
  44. package/dist/pending-join-runner.js.map +1 -0
  45. package/dist/push-fanout.d.ts +1 -1
  46. package/dist/push-fanout.js +1 -1
  47. package/dist/relay-stun.d.ts +24 -0
  48. package/dist/relay-stun.js +51 -0
  49. package/dist/relay-stun.js.map +1 -0
  50. package/dist/seed-bootstrap.d.ts +2 -2
  51. package/dist/seed-bootstrap.js +5 -5
  52. package/dist/strand-addr-protocol.js +2 -2
  53. package/dist/strand-formation-deadlines.d.ts +5 -5
  54. package/dist/strand-formation-deadlines.js +5 -5
  55. package/dist/strand-formation-manager.d.ts +18 -4
  56. package/dist/strand-formation-manager.js +48 -33
  57. package/dist/strand-formation-manager.js.map +1 -1
  58. package/dist/strand-formation-protocol.d.ts +71 -14
  59. package/dist/strand-formation-protocol.js +222 -47
  60. package/dist/strand-formation-protocol.js.map +1 -1
  61. package/dist/strand-formation-rejection.d.ts +94 -0
  62. package/dist/strand-formation-rejection.js +77 -0
  63. package/dist/strand-formation-rejection.js.map +1 -0
  64. package/dist/strand-instance-manager.js +4 -1
  65. package/dist/strand-instance-manager.js.map +1 -1
  66. package/dist/strand-membership-writer.js +32 -32
  67. package/dist/strand-solicitation.d.ts +11 -3
  68. package/dist/strand-solicitation.js +9 -0
  69. package/dist/strand-solicitation.js.map +1 -1
  70. package/dist/types.d.ts +103 -17
  71. package/dist/types.js +5 -9
  72. package/dist/types.js.map +1 -1
  73. package/package.json +22 -22
  74. package/src/cadre-node.ts +8654 -8400
  75. package/src/control-authorization.ts +2 -1
  76. package/src/control-cohort.ts +128 -128
  77. package/src/control-database.ts +303 -82
  78. package/src/control-formation-recorder.ts +353 -354
  79. package/src/control-read-retry.ts +216 -216
  80. package/src/control-schema.ts +1078 -991
  81. package/src/control-write-retry.ts +567 -561
  82. package/src/diagnostics/connection-path.ts +1 -29
  83. package/src/formation-approval.ts +610 -610
  84. package/src/hibernation-manager.ts +638 -638
  85. package/src/identity-key.ts +7 -62
  86. package/src/index.ts +644 -627
  87. package/src/invitation-bootstrap.ts +40 -0
  88. package/src/joined-strand-store.ts +498 -498
  89. package/src/link-budget.ts +684 -583
  90. package/src/membership-connection-gater.ts +570 -570
  91. package/src/node-local-snapshot.ts +298 -298
  92. package/src/peer-dial.ts +315 -311
  93. package/src/peer-join-backfill.ts +710 -710
  94. package/src/pending-join-runner.ts +735 -0
  95. package/src/push-fanout.ts +1 -1
  96. package/src/relay-reservation.ts +1121 -1121
  97. package/src/relay-server.ts +98 -98
  98. package/src/relay-stun.ts +58 -0
  99. package/src/seed-bootstrap.ts +1448 -1448
  100. package/src/strand-addr-protocol.ts +547 -547
  101. package/src/strand-database.ts +287 -287
  102. package/src/strand-first-sync-gate.ts +391 -391
  103. package/src/strand-formation-deadlines.ts +227 -227
  104. package/src/strand-formation-manager.ts +725 -700
  105. package/src/strand-formation-protocol.ts +1036 -802
  106. package/src/strand-formation-rejection.ts +129 -0
  107. package/src/strand-fret-addrs.ts +145 -145
  108. package/src/strand-instance-manager.ts +1789 -1786
  109. package/src/strand-membership-writer.ts +1974 -1974
  110. package/src/strand-network-state-file.ts +40 -40
  111. package/src/strand-network-state.ts +243 -243
  112. package/src/strand-revocation-enforcer.ts +654 -654
  113. package/src/strand-solicitation.ts +532 -519
  114. package/src/strand-wake-protocol.ts +401 -401
  115. package/src/types.ts +2222 -2140
  116. package/dist/diagnostics/webrtc-turn-tracker.d.ts +0 -50
  117. package/dist/diagnostics/webrtc-turn-tracker.js +0 -196
  118. package/dist/diagnostics/webrtc-turn-tracker.js.map +0 -1
  119. package/src/diagnostics/webrtc-turn-tracker.ts +0 -222
@@ -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
@@ -589,23 +612,6 @@ export declare class CadreNode implements SAppIdLookup {
589
612
  private controlConnectionOpenHandler;
590
613
  /** `connection:close` listener re-arming the growth edge (teardown in {@link stopRecordRefresh}). */
591
614
  private controlConnectionCloseHandler;
592
- /**
593
- * Hooks `globalThis.RTCPeerConnection` to observe whether ICE selected a TURN
594
- * relay candidate for each WebRTC session. Installed in {@link start}, disposed
595
- * in {@link cleanup}; inert on Node.js (no `RTCPeerConnection`).
596
- */
597
- private readonly turnTracker;
598
- /**
599
- * Peer IDs whose current WebRTC connection was observed to be TURN-relayed.
600
- * Populated by {@link handleTurnConnectionOpen} (drains the tracker queue on a
601
- * `/webrtc` `connection:open`), cleared per-peer on `connection:close`. Read by
602
- * {@link getConnectionPaths} to promote `webrtc` → `webrtc-turn` (relayed).
603
- */
604
- private turnRelayedPeers;
605
- /** `connection:open` listener feeding TURN detection (teardown in {@link stopRecordRefresh}). */
606
- private turnConnectionOpenHandler;
607
- /** `connection:close` listener clearing a peer's TURN flag (teardown in {@link stopRecordRefresh}). */
608
- private turnConnectionCloseHandler;
609
615
  constructor(config: CadreNodeConfig);
610
616
  /**
611
617
  * SAppIdLookup implementation - get sAppId for a strand: the claiming config's, else
@@ -966,11 +972,9 @@ export declare class CadreNode implements SAppIdLookup {
966
972
  * `StrandSolicitationService.hasOutstandingInvitation`). A formation
967
973
  * initiator is another party's peer by design and its token is only
968
974
  * checkable inside the protocol, so the gate asks the coarser question
969
- * "does this node expect a stranger at all?". Merely REGISTERING the
970
- * responder ({@link initializeStrandSolicitation}) no longer suspends
971
- * stranger denial — eager registration (as `reference-app-rn` does at
972
- * bring-up) and `formStrand`'s lazy initialization both leave the gate
973
- * armed, because neither mints an invitation.
975
+ * "does this node expect a stranger at all?". REGISTERING the responder
976
+ * does not suspend stranger denial: every node registers one at
977
+ * {@link start}, and registering mints no invitation.
974
978
  *
975
979
  * Ordering is semantically free (the checks are OR'd) but decides who pays:
976
980
  * checks 1-3 are in-memory, 4/5 share one control-DB read, and only a peer
@@ -1002,6 +1006,11 @@ export declare class CadreNode implements SAppIdLookup {
1002
1006
  * layer is fail-open behind `ADMISSION_DECISION_TIMEOUT_MS`, so the live
1003
1007
  * read is safe here — unlike the per-stream gate, which must consult the
1004
1008
  * materialized {@link authorizedControlPeers} snapshot instead.
1009
+ * NOTE: check 6 adds a second control read (`hasOutstandingFormationInvite`) for a
1010
+ * stranger with no locally minted invitation in play, on every node now that every
1011
+ * node runs the responder — a relay-enabled storage node included. If stranger
1012
+ * connections to such a node ever arrive fast enough for that read to show, cache
1013
+ * the answer for a few seconds.
1005
1014
  * NOTE: on a relay-DISABLED node, a sibling whose membership row has not yet
1006
1015
  * replicated here is denied until the row converges (typically via the
1007
1016
  * owner); either side's next outbound reconcile dial (outbound is never
@@ -1344,6 +1353,12 @@ export declare class CadreNode implements SAppIdLookup {
1344
1353
  * handling, is what keeps a mixed list from taking a whole peer offline.
1345
1354
  */
1346
1355
  resolvePeerAddrs(peerId: string, opts?: ResolveOpts): Promise<Multiaddr[]>;
1356
+ /**
1357
+ * {@link resolvePeerAddrs} with the record's `UpdatedAt` kept beside the addresses, for a
1358
+ * caller that ranks peers by how recently they published. `null` wherever that returns `[]`
1359
+ * for a failed gate.
1360
+ */
1361
+ private resolvePeerRecord;
1347
1362
  /**
1348
1363
  * Guarantee every address handed to a control-network dial terminates in
1349
1364
  * `/p2p/<peerId>`, with no duplicates — the invariant every dial path
@@ -1537,15 +1552,18 @@ export declare class CadreNode implements SAppIdLookup {
1537
1552
  *
1538
1553
  * The list may name transports this node cannot dial (a lent node reports TCP
1539
1554
  * and `/ws` addresses to a phone that dials WebSockets only). That needs no
1540
- * filtering here: libp2p's dial queue (`calculateMultiaddrs`, libp2p 3.1.3)
1555
+ * filtering here: libp2p's dial queue (`calculateMultiaddrs`, libp2p 3.3.11)
1541
1556
  * rejects an address no transport can dial before touching the network, so
1542
1557
  * such an address costs a log line.
1543
1558
  *
1544
1559
  * The list is NOT handed to one `dial()`. libp2p tries a multi-address dial's
1545
- * addresses one after another under a single deadline, with no limit per
1546
- * address, and sorts loopback addresses last — so one or two addresses that
1547
- * never answer used up the whole deadline before the address that worked was
1548
- * tried. {@link dialPeerAddrs} dials each address on its own time limit instead.
1560
+ * addresses one after another under a single deadline, and sorts loopback
1561
+ * addresses last — so one or two addresses that never answer use up most of
1562
+ * the deadline before the address that works is tried. libp2p 3.3's
1563
+ * per-address `addressDialTimeout` does not prevent that: cadre sizes it for a
1564
+ * cold relayed open through two admission decisions (`optimysticDialLimits`,
1565
+ * 39 s at the default declared link).
1566
+ * {@link dialPeerAddrs} dials each address on its own time limit instead.
1549
1567
  */
1550
1568
  private resolveControlDialAddrs;
1551
1569
  /**
@@ -1817,16 +1835,6 @@ export declare class CadreNode implements SAppIdLookup {
1817
1835
  * Idempotent.
1818
1836
  */
1819
1837
  private wireControlConnectionListeners;
1820
- /**
1821
- * On a control connection opening: if it is a WebRTC connection, drain the TURN
1822
- * tracker for the just-settled ICE verdict and, when it relayed, mark the peer
1823
- * so {@link getConnectionPaths} classifies it `webrtc-turn` (relayed). The
1824
- * settlement↔open correlation is timing-based and best-effort; an unknown
1825
- * verdict degrades to not-relayed. Never throws to the event loop.
1826
- */
1827
- private handleTurnConnectionOpen;
1828
- /** On a control connection closing, drop any TURN-relayed flag for its peer. */
1829
- private handleTurnConnectionClose;
1830
1838
  /**
1831
1839
  * Publish (or refresh) this node's own self-signed `DeviceToken` row so a server
1832
1840
  * peer can resolve its FCM/APNs push token from its PeerId alone. Mirrors
@@ -2613,9 +2621,9 @@ export declare class CadreNode implements SAppIdLookup {
2613
2621
  * caller runs the drive regardless).
2614
2622
  *
2615
2623
  * NOTE: against a relay that is DOWN this hook costs up to two strand-addr
2616
- * timeouts (dial by peer id, then by addr; 23 s each at the default declared link round trip)
2624
+ * timeouts (dial by peer id, then by addr; 28.5 s each at the default declared link round trip)
2617
2625
  * before the reservation drive even starts, so one failed re-drive holds the supervisor
2618
- * `driving` for those 46 s plus the drive's own deadline — 64 s at the default, and longer on
2626
+ * `driving` for those 57 s plus the drive's own deadline — 75 s at the default, and longer on
2619
2627
  * a host that declared a slower one (`link-budget.ts`).
2620
2628
  * Bounded and harmless while the relay is unreachable anyway; if recovery
2621
2629
  * latency after a relay comes back ever matters, skip the announce when the
@@ -3435,32 +3443,128 @@ export declare class CadreNode implements SAppIdLookup {
3435
3443
  */
3436
3444
  dialInvite(invite: CadreInvite): Promise<void>;
3437
3445
  /**
3438
- * Initialize the strand solicitation service.
3439
- * This enables forming strands with other parties via open invitations.
3446
+ * Replace this node's strand solicitation service — the formation responder every node
3447
+ * installs at {@link start}, and the initiator side of {@link formStrand}. Optional: call it
3448
+ * only to customize the responder (an approver, a provisioner, formation deadlines). The
3449
+ * new service's handler replaces the previous one's on the control node, and invitations
3450
+ * the previous service minted stay outstanding.
3451
+ *
3452
+ * Without `options.formationUsageRecorder`, tokens are checked against this party's
3453
+ * replicated `FormationInvite`/`FormationUsage` rows ({@link ControlFormationUsageRecorder}).
3454
+ * A responder that accepts every token is reachable only by constructing
3455
+ * {@link StrandSolicitationService} directly.
3440
3456
  *
3441
3457
  * @param options - Configuration for the solicitation service
3442
3458
  */
3443
3459
  initializeStrandSolicitation(options?: StrandSolicitationServiceOptions): Promise<void>;
3444
3460
  /**
3445
- * Get the strand solicitation service (for advanced use)
3461
+ * Answer formation from start. Log-and-continue: a node that cannot is still useful, and
3462
+ * {@link createOpenInvitation} / {@link formStrand} install the responder when it is missing.
3463
+ */
3464
+ private installDefaultFormationResponder;
3465
+ /**
3466
+ * Move the control node's formation handler from the registered service to `service`.
3467
+ * Queued by {@link initializeStrandSolicitation} so swaps run one at a time: libp2p allows
3468
+ * one handler per protocol id, and two overlapping swaps would each find the other's
3469
+ * handler in the way. A failed swap registers the previous service again, points
3470
+ * {@link strandSolicitationService} back at it unless a later call has replaced it, and
3471
+ * rethrows.
3472
+ *
3473
+ * NOTE: same stray-handler caveat as {@link installSeedBootstrapService} — a failed
3474
+ * peer-store merge leaves the formation handler registered, so the restore below (and a
3475
+ * retry) rejects as a duplicate.
3476
+ */
3477
+ private swapFormationResponder;
3478
+ /**
3479
+ * Get the strand solicitation service (for advanced use). Non-null on a started node unless
3480
+ * the install at {@link start} failed.
3446
3481
  */
3447
3482
  getStrandSolicitationService(): StrandSolicitationService | null;
3448
3483
  /**
3449
3484
  * Create an open invitation for others to form strands with this party.
3450
3485
  *
3486
+ * The invitation's bootstrap list names this machine first (it is the one most likely to
3487
+ * run the host strand), then a few of the party's other machines
3488
+ * ({@link siblingInvitationAddrs}), so a joiner can still form while this one is offline.
3489
+ *
3451
3490
  * @param sAppId - The sApp to use for formed strands
3452
3491
  * @param expirationMs - How long the invitation is valid (ms from now)
3453
3492
  * @returns The open invitation to share out-of-band
3493
+ * @throws when neither this machine nor any other machine of the party has an address
3454
3494
  */
3455
3495
  createOpenInvitation(sAppId: string, expirationMs?: number): Promise<OpenInvitation>;
3456
3496
  /**
3457
- * Form a strand with a responder via an open invitation.
3497
+ * Addresses of the party's other machines for an invitation's bootstrap list
3498
+ * ({@link selectInvitationSiblingAddrs} picks which). The source is each authorized
3499
+ * member's signed `CadrePeer` record ({@link resolvePeerRecord}), so a machine whose record
3500
+ * is missing, stale or untrusted is left out; live connections only order them.
3501
+ *
3502
+ * Best-effort: a failed read leaves the invitation naming this machine alone, which is what
3503
+ * it named before siblings were added.
3504
+ */
3505
+ private siblingInvitationAddrs;
3506
+ /**
3507
+ * Form a strand with a responder via an open invitation: one attempt, from this machine, with
3508
+ * nothing recorded party-wide. {@link requestJoin} is the path that keeps trying.
3458
3509
  *
3459
3510
  * @param invitation - The open invitation received out-of-band
3460
3511
  * @param disclosure - Identity/context information to share with the responder
3461
3512
  * @returns The member key and strand info if successful
3513
+ * @throws `FormationRejectedError` or `FormationUnreachableError` when the formation fails, and
3514
+ * `FormationPostApprovalError` when it was approved and a step on this machine then failed
3462
3515
  */
3463
3516
  formStrand(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<FormStrandResult>;
3517
+ /**
3518
+ * Ask to join through `invitation` and have the party keep trying until the join works, the
3519
+ * invitation is used up or expires, or the request is dismissed: across restarts, and from
3520
+ * any owner machine of the party (`docs/strands.md` → "Joining while the inviter is
3521
+ * offline"). Records a party-wide `PendingJoin` row, runs one attempt on this machine at
3522
+ * once, and returns the status after it: `'joined'` when the inviter answered yes, `'waiting'`
3523
+ * when it was unreachable or not ready, `'failed'` when it refused for good.
3524
+ *
3525
+ * Asking again for an invitation whose request is still pending adopts that request as it
3526
+ * is; asking again after it finished starts a fresh one. Later changes arrive as
3527
+ * `pendingJoin:changed`. A joined strand is offered through `strand:discovered` like any
3528
+ * {@link formStrand} join. Do not also call {@link formStrand} with the same invitation:
3529
+ * nothing coordinates the two, and one of them gets `token-spent`.
3530
+ *
3531
+ * @throws on a machine that is not an enrolled owner (the row is owner-signed), and on an
3532
+ * invitation already past its expiration
3533
+ */
3534
+ requestJoin(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<PendingJoinStatus>;
3535
+ /**
3536
+ * Every join asked for with {@link requestJoin} and not dismissed, pending or finished, as this
3537
+ * machine sees it (see `PendingJoinStatus` for which states are machine-local).
3538
+ */
3539
+ listPendingJoins(): Promise<PendingJoinStatus[]>;
3540
+ /**
3541
+ * Remove a join request party-wide. On a pending request this cancels it: every owner machine
3542
+ * stops at its next read, and an attempt already running does not record its outcome (a join
3543
+ * it made stays on that machine, as any {@link formStrand} join does). Owner machines only.
3544
+ * Resolves `false` when there was no such request.
3545
+ */
3546
+ dismissPendingJoin(id: string): Promise<boolean>;
3547
+ private requirePendingJoins;
3548
+ /** This machine's owner key and signer for an owner-signed control write, or a throw carrying `refusal`. */
3549
+ private requireOwnerSigner;
3550
+ /**
3551
+ * Write a join request's row, or adopt the party's existing row for the same invitation: a
3552
+ * pending one as it is, a finished one replaced by `requested` (the user asked again).
3553
+ */
3554
+ private recordJoinRequest;
3555
+ /** Build and start the pending-join retry loop over this node (see {@link pendingJoinRunner}). */
3556
+ private startPendingJoinRunner;
3557
+ /** The invitation a `PendingJoin` row stores. A failure names the row, never the invitation, which is a bearer credential. */
3558
+ private pendingJoinInvitation;
3559
+ private pendingJoinSAppId;
3560
+ /**
3561
+ * Stage the membership invitation of each `joined` row this process has not staged yet, so a
3562
+ * closed strand joined on another owner machine is seated by whichever machine launches it
3563
+ * first; the party key it admits is the replicated `StrandPartyKey` row the finishing machine
3564
+ * seated. A strand that already has an invitation staged keeps it, for the reconciler to
3565
+ * settle first.
3566
+ */
3567
+ private stageMembershipInvitesFromPendingJoins;
3464
3568
  /**
3465
3569
  * Remember the strand a formation just joined (see {@link joinedStrandStore}), so it
3466
3570
  * is re-offered after a restart even when the app is killed before its `addStrand`, and