@serfab/cadre-core 1.17.0 → 1.19.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/cadre-invite-protocol.d.ts +35 -13
- package/dist/cadre-invite-protocol.js +90 -25
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-node.d.ts +586 -255
- package/dist/cadre-node.js +1182 -566
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +66 -9
- package/dist/control-cohort.js +67 -9
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +451 -193
- package/dist/control-database.js +701 -359
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +46 -43
- package/dist/control-formation-recorder.js +77 -78
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +6 -0
- package/dist/control-protocol-guard.js +14 -2
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +48 -31
- package/dist/control-schema-version.js +67 -45
- package/dist/control-schema-version.js.map +1 -1
- package/dist/control-schema.d.ts +8 -8
- package/dist/control-schema.js +16 -16
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +42 -7
- package/dist/control-write-retry.js +87 -19
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store.d.ts +3 -3
- package/dist/enrolled-machine-store.js +3 -3
- package/dist/formation-token.d.ts +48 -0
- package/dist/formation-token.js +26 -0
- package/dist/formation-token.js.map +1 -0
- package/dist/index.d.ts +15 -14
- package/dist/index.js +17 -14
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +26 -9
- package/dist/joined-strand-store.js +63 -24
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/link-budget.d.ts +9 -7
- package/dist/link-budget.js +9 -7
- package/dist/link-budget.js.map +1 -1
- package/dist/member-redemption.d.ts +15 -9
- package/dist/member-redemption.js +13 -8
- package/dist/member-redemption.js.map +1 -1
- package/dist/membership-view.d.ts +91 -0
- package/dist/membership-view.js +110 -0
- package/dist/membership-view.js.map +1 -0
- package/dist/owner-grant-protocol.d.ts +11 -7
- package/dist/owner-grant-protocol.js +31 -17
- package/dist/owner-grant-protocol.js.map +1 -1
- package/dist/peer-addr-book.d.ts +10 -1
- package/dist/peer-addr-book.js +4 -3
- package/dist/peer-addr-book.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +96 -30
- package/dist/peer-join-backfill.js +272 -128
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +48 -18
- package/dist/pending-join-runner.js +74 -56
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +47 -40
- package/dist/seed-bootstrap.js +66 -61
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/self-address-update.d.ts +13 -0
- package/dist/self-address-update.js +19 -0
- package/dist/self-address-update.js.map +1 -0
- package/dist/strand-database.d.ts +23 -2
- package/dist/strand-database.js +26 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -89
- package/dist/strand-first-sync-gate.js +288 -122
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +9 -9
- package/dist/strand-formation-deadlines.js +9 -9
- package/dist/strand-formation-manager.d.ts +23 -17
- package/dist/strand-formation-manager.js +46 -44
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +5 -8
- package/dist/strand-formation-protocol.js +101 -16
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +26 -17
- package/dist/strand-instance-manager.js +119 -50
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +16 -13
- package/dist/strand-membership-reconciler.js +28 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +40 -28
- package/dist/strand-membership-writer.js +212 -153
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.js +5 -2
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-revocation-enforcer.d.ts +134 -60
- package/dist/strand-revocation-enforcer.js +144 -71
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +50 -39
- package/dist/strand-solicitation.js +10 -3
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.d.ts +80 -13
- package/dist/strand-watcher.js +152 -48
- package/dist/strand-watcher.js.map +1 -1
- package/dist/table-change-watch.d.ts +63 -0
- package/dist/table-change-watch.js +209 -0
- package/dist/table-change-watch.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +4 -3
- package/dist/timeout-scheduler.js +4 -3
- package/dist/timeout-scheduler.js.map +1 -1
- package/dist/types.d.ts +90 -49
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/cadre-invite-protocol.ts +119 -31
- package/src/cadre-node.ts +1289 -588
- package/src/control-cohort.ts +86 -9
- package/src/control-database.ts +848 -403
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +16 -2
- package/src/control-schema-version.ts +101 -51
- package/src/control-schema.ts +16 -16
- package/src/control-write-retry.ts +91 -19
- package/src/delegate-admission.ts +351 -351
- package/src/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +30 -18
- package/src/joined-strand-store.ts +69 -24
- package/src/link-budget.ts +9 -7
- package/src/member-redemption.ts +20 -13
- package/src/membership-view.ts +144 -0
- package/src/owner-grant-protocol.ts +33 -18
- package/src/peer-addr-book.ts +4 -3
- package/src/peer-join-backfill.ts +961 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/seed-bootstrap.ts +72 -67
- package/src/self-address-update.ts +19 -0
- package/src/strand-database.ts +352 -322
- package/src/strand-first-sync-gate.ts +334 -134
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +731 -725
- package/src/strand-formation-protocol.ts +122 -24
- package/src/strand-instance-manager.ts +2092 -2026
- package/src/strand-membership-reconciler.ts +31 -25
- package/src/strand-membership-writer.ts +2093 -2019
- package/src/strand-move.ts +5 -2
- package/src/strand-revocation-enforcer.ts +740 -654
- package/src/strand-solicitation.ts +61 -34
- package/src/strand-watcher.ts +175 -56
- package/src/table-change-watch.ts +285 -0
- package/src/timeout-scheduler.ts +29 -28
- package/src/types.ts +89 -51
package/dist/cadre-node.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ import { type ConnectionPathSummary } from './diagnostics/connection-path.js';
|
|
|
28
28
|
*
|
|
29
29
|
* Ten minutes sits comfortably inside the peerStore's one-hour address expiry
|
|
30
30
|
* (`MAX_ADDRESS_AGE`, see `peer-addr-book.ts`) with headroom for several missed
|
|
31
|
-
* passes, and far above the
|
|
32
|
-
* strand-addr RPC fan-out stays cheap.
|
|
31
|
+
* passes, and far above the reconcile pass's 2-minute backstop the refresh rides
|
|
32
|
+
* on, so the strand-addr RPC fan-out stays cheap.
|
|
33
33
|
*/
|
|
34
34
|
export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
|
|
35
35
|
/**
|
|
@@ -38,8 +38,9 @@ export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
|
|
|
38
38
|
* phone asking before its `CadrePeer` row has replicated to the sibling is refused
|
|
39
39
|
* and its delegate grant goes unrecorded, and waiting the full
|
|
40
40
|
* {@link STRAND_PEER_ADDR_REFRESH_MS} for the next try is the delay this bounds.
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* A pass arms its own wake for the earliest due time it leaves behind
|
|
42
|
+
* (`nextReconcileWakeMs`), so a sibling that keeps failing costs one timed-out
|
|
43
|
+
* RPC a minute, with no pass in between.
|
|
43
44
|
*/
|
|
44
45
|
export declare const STRAND_PEER_ADDR_RETRY_MS: number;
|
|
45
46
|
/**
|
|
@@ -240,8 +241,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
240
241
|
*
|
|
241
242
|
* Cleared by {@link cleanup} only, deliberately NOT by {@link detachStrand}: the watcher's
|
|
242
243
|
* filter rejection stops a replica through that very method, and forgetting the id there
|
|
243
|
-
* would have the next
|
|
244
|
-
* relaunch the replica —
|
|
244
|
+
* would have the next watcher run find it unknown again, re-admit the strand provisionally
|
|
245
|
+
* and relaunch the replica — on every other run, forever. A strand id's sApp never changes,
|
|
245
246
|
* and the map is bounded by the strands this party has published, like
|
|
246
247
|
* {@link discoveredStrands}.
|
|
247
248
|
*/
|
|
@@ -269,19 +270,24 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
269
270
|
* wait that ends in mutual denial (the upstream gate is fail-closed on
|
|
270
271
|
* timeout, unlike the fail-open connection gater). Optimystic's predicate is
|
|
271
272
|
* synchronous by contract. So the set is refreshed OUT OF BAND instead — after
|
|
272
|
-
* start,
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
273
|
+
* start, at the head of every {@link reconcileControlCohort} pass, whenever a
|
|
274
|
+
* membership table changes (the gate watch {@link start} takes through
|
|
275
|
+
* `ControlDatabase.watchMembershipTables`, which hears a local commit and a change
|
|
276
|
+
* replicated into this node's storage alike, within a 50 ms window), and immediately
|
|
277
|
+
* after every LOCAL membership mutation so a just-vouched peer is admitted
|
|
278
|
+
* before the writer's call returns. That last refresh is AUTOMATIC: the
|
|
279
|
+
* control database notifies {@link refreshMembershipGate} after every
|
|
276
280
|
* committed `CadrePeer` write (see `ControlDatabase.mutateCadrePeer`), so no
|
|
277
|
-
* writer has to remember. A change
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
281
|
+
* writer has to remember. A change this node's storage never hears of — a
|
|
282
|
+
* machine outside the block's cohort on a party larger than the replication
|
|
283
|
+
* breadth — is picked up at the pass's 2-minute backstop: bounded staleness,
|
|
284
|
+
* acceptable because this gate is defense in depth: rows an unadmitted peer
|
|
285
|
+
* manages to write are still disbelieved at read time by the voucher-anchored
|
|
286
|
+
* predicate. For cohort choice the same window means a member whose row
|
|
287
|
+
* arrived by replication is left out of this node's control-block cohorts
|
|
288
|
+
* until that refresh. Optimystic reads the predicate live on every lookup, so
|
|
289
|
+
* replacing this set is the whole re-evaluation; there is no per-peer
|
|
290
|
+
* reclassify call to make.
|
|
285
291
|
*/
|
|
286
292
|
private authorizedControlPeers;
|
|
287
293
|
/**
|
|
@@ -339,9 +345,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
339
345
|
* When this node last ANNOUNCED a delegate, keyed `<targetPeerId>\n<strandId>`
|
|
340
346
|
* (the peer announced TO, not the delegate). Throttles
|
|
341
347
|
* {@link refreshDelegateGrants} to once per `DELEGATE_GRANT_TTL_MS / 2` per
|
|
342
|
-
* (relay, strand) so
|
|
343
|
-
* chatter; the launch/resume announce passes record here too.
|
|
344
|
-
* strand is no longer running are pruned on each reconcile pass.
|
|
348
|
+
* (relay, strand) so a run of change-driven reconcile passes never becomes
|
|
349
|
+
* per-pass RPC chatter; the launch/resume announce passes record here too.
|
|
350
|
+
* Keys whose strand is no longer running are pruned on each reconcile pass.
|
|
345
351
|
*/
|
|
346
352
|
private readonly delegateAnnounceAt;
|
|
347
353
|
/**
|
|
@@ -368,12 +374,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
368
374
|
* strand, keyed `peerStrandKey(siblingControlPeerId, strandId)` → epoch ms; a
|
|
369
375
|
* missing key is due. Bounds {@link refreshStrandPeerAddrs}'s fan-out per
|
|
370
376
|
* (sibling, strand) rather than per strand, so a sibling that connects after a
|
|
371
|
-
* pass is asked on the
|
|
372
|
-
* sibling's answer set. An answer (even an empty one) makes the sibling
|
|
373
|
-
* in {@link STRAND_PEER_ADDR_REFRESH_MS}; no answer, in
|
|
374
|
-
* {@link STRAND_PEER_ADDR_RETRY_MS}
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
+
* pass is asked on the pass its connection starts instead of waiting out a stamp
|
|
378
|
+
* another sibling's answer set. An answer (even an empty one) makes the sibling
|
|
379
|
+
* due again in {@link STRAND_PEER_ADDR_REFRESH_MS}; no answer, in
|
|
380
|
+
* {@link STRAND_PEER_ADDR_RETRY_MS}, and each pass arms its own wake for the
|
|
381
|
+
* earliest due time it leaves behind (`nextReconcileWakeMs`). Keys whose strand
|
|
382
|
+
* stopped running or whose sibling is no longer connected are pruned every pass,
|
|
383
|
+
* so a resumed strand and a reconnected sibling are both asked at once.
|
|
377
384
|
*/
|
|
378
385
|
private readonly strandAddrAskDueAt;
|
|
379
386
|
/**
|
|
@@ -455,6 +462,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
455
462
|
* `unpublishStrand` / `forgetJoinedStrand` as {@link formationStrandAddrs} is.
|
|
456
463
|
*/
|
|
457
464
|
private readonly pendingMembershipInvites;
|
|
465
|
+
/**
|
|
466
|
+
* The party key {@link adoptFormationMembershipInvite} just seated or read, by strand id,
|
|
467
|
+
* so the launch that follows a join does not read the `StrandPartyKey` row again. Taken
|
|
468
|
+
* (deleted) by the {@link resolveStrandPartyKey} that uses it, so any later launch reads
|
|
469
|
+
* the row; dropped by {@link detachStrand}. The joiner's counterpart of the founder's
|
|
470
|
+
* `StrandConfig.partyMemberPrivateKey`, without the app carrying the secret.
|
|
471
|
+
*
|
|
472
|
+
* NOTE: an entry is not dropped when the row is removed, because no `CadreNode` path removes
|
|
473
|
+
* it; an embedder calling `getControlDatabase().deleteStrandPartyKey` between a join and its
|
|
474
|
+
* launch would get the deleted key. If a node-level rotation is added, drop the entry there.
|
|
475
|
+
*/
|
|
476
|
+
private readonly formedPartyKeys;
|
|
458
477
|
/**
|
|
459
478
|
* Invite keys of the membership invitations this process has staged, by
|
|
460
479
|
* {@link adoptFormationMembershipInvite} or from a `JoinSuccess` row
|
|
@@ -548,7 +567,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
548
567
|
private joinedStrandStore;
|
|
549
568
|
/**
|
|
550
569
|
* This session's joined strands: {@link joinedStrandStore} and the party-wide
|
|
551
|
-
* `JoinedStrand` table, unioned with the control rows each watcher
|
|
570
|
+
* `JoinedStrand` table, unioned with the control rows each watcher run, published and
|
|
552
571
|
* removed by the reconcile pass. Per SESSION, unlike the store — rebuilt at every
|
|
553
572
|
* {@link start}, which is what ends a revoked join's "keep offering until this session
|
|
554
573
|
* ends" (see `JoinedStrandSession.forgetAfterThisSession`).
|
|
@@ -579,17 +598,41 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
579
598
|
/** Listener that re-publishes the self record when reachable addresses change. */
|
|
580
599
|
private selfPeerUpdateHandler;
|
|
581
600
|
/**
|
|
582
|
-
*
|
|
583
|
-
*
|
|
584
|
-
*
|
|
585
|
-
* {@link
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
*
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
601
|
+
* The reconcile pass's schedule (see {@link reconcileControlCohort}): single-flight
|
|
602
|
+
* runs on `table-change-watch.ts`'s rule, with no table subscription of its own — the
|
|
603
|
+
* gate watch ({@link membershipGateWatch}), the control connection listeners,
|
|
604
|
+
* `self:peer:update`, and the pass's own wake ({@link armReconcileWake}) `trigger` it,
|
|
605
|
+
* and its backstop (`network.controlCohort.reconcileMs`, default
|
|
606
|
+
* `DEFAULT_CONTROL_COHORT_RECONCILE_MS`) runs it when nothing has. Built alongside
|
|
607
|
+
* {@link recordRefreshTimer} in {@link startRecordRefresh}, about 1 s after start, and
|
|
608
|
+
* disposed in {@link stopRecordRefresh}; its timers never keep the loop alive.
|
|
609
|
+
*/
|
|
610
|
+
private controlReconcileRuns;
|
|
611
|
+
/**
|
|
612
|
+
* The subscription on the membership tables (`ControlDatabase.watchMembershipTables`) that
|
|
613
|
+
* re-materializes the membership gate and triggers {@link controlReconcileRuns} on every
|
|
614
|
+
* change ({@link onMembershipGateTablesChanged}).
|
|
615
|
+
* Taken in {@link start} right after the membership listener is wired, disposed in
|
|
616
|
+
* {@link cleanup} before the control database closes.
|
|
617
|
+
*/
|
|
618
|
+
private membershipGateWatch;
|
|
619
|
+
/** The second membership-view drop after a sibling connects ({@link armMembershipViewReload}); cleared in {@link stopRecordRefresh}. */
|
|
620
|
+
private membershipViewReloadTimer;
|
|
621
|
+
/**
|
|
622
|
+
* Consecutive passes whose dial of a selected sibling or a cold-start bootstrap peer
|
|
623
|
+
* failed (`ControlCohortPassOutcome.unconnected`), for the redial ladder
|
|
624
|
+
* ({@link armReconcileWake}); 0 after a pass none of whose dials did. Per session:
|
|
625
|
+
* reset in {@link stopRecordRefresh}.
|
|
626
|
+
*/
|
|
627
|
+
private reconcileRedialStreak;
|
|
628
|
+
/** Peers a {@link redialClaimedNode} loop is running for, so a repeated claim starts no second one. */
|
|
629
|
+
private readonly claimRedialPeers;
|
|
630
|
+
/**
|
|
631
|
+
* Single-flight guard for {@link reconcileControlCohort}. An explicit call, the
|
|
632
|
+
* `self:peer:update` trigger and a scheduled run can fire close together; collapsing
|
|
633
|
+
* concurrent passes into one in-flight run prevents two passes from double-dialing the
|
|
634
|
+
* same siblings (mirrors {@link registerSelfInFlight}). A scheduled run waits for this
|
|
635
|
+
* pass and then starts its own rather than joining it.
|
|
593
636
|
*/
|
|
594
637
|
private reconcileControlCohortInFlight;
|
|
595
638
|
/**
|
|
@@ -612,18 +655,26 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
612
655
|
* machine in the party is already discarding this node's address as stale.
|
|
613
656
|
*/
|
|
614
657
|
private lastSelfRecordPublishAt;
|
|
658
|
+
/**
|
|
659
|
+
* The newest record this process signed for its own `CadrePeer` row: written by either path
|
|
660
|
+
* of {@link publishSelfRecord}, or handed to a member to write by {@link redeemCadreInvitation}.
|
|
661
|
+
* What {@link publishSelfRecordUnlessCurrent} compares the row against. Per session, cleared
|
|
662
|
+
* with {@link lastSelfRecordPublishAt}: a row carried over from an earlier process is never
|
|
663
|
+
* taken as current.
|
|
664
|
+
*/
|
|
665
|
+
private lastSignedSelfRecord;
|
|
615
666
|
/** Say-once latch for {@link escalateIfSelfRecordStale}; re-armed by the next publish. */
|
|
616
667
|
private selfRecordStaleWarned;
|
|
617
668
|
/**
|
|
618
669
|
* Re-replication queue for owner `authorizePeer` writes that committed while
|
|
619
|
-
* this node was alone
|
|
620
|
-
*
|
|
621
|
-
*
|
|
670
|
+
* this node was alone ({@link committedAlone}) — the Optimystic commit was
|
|
671
|
+
* local-only (the block's cohort was this node) and never broadcast. Maps the
|
|
672
|
+
* affected subject peerId → 'authorize', to
|
|
622
673
|
* re-issue once the cohort grows. Removals are NOT tracked here — a delete
|
|
623
674
|
* leaves a `Revocation` tombstone, tracked by {@link pendingRevocations}; a
|
|
624
675
|
* removePeer also clears any queued authorize for the same subject (the row is
|
|
625
|
-
* gone, so re-issuing the insert would be wrong). Drained
|
|
626
|
-
*
|
|
676
|
+
* gone, so re-issuing the insert would be wrong). Drained when a sibling becomes
|
|
677
|
+
* reachable ({@link updateControlSiblingReachability}) by {@link drainPendingControlReplication}.
|
|
627
678
|
*/
|
|
628
679
|
private pendingPeerWrites;
|
|
629
680
|
/**
|
|
@@ -647,14 +698,6 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
647
698
|
* exactly a "removals from before this lifetime" case).
|
|
648
699
|
*/
|
|
649
700
|
private reissuedHeldRevocations;
|
|
650
|
-
/**
|
|
651
|
-
* Whether this process has seen the singleton `Revocation` ledger marker filed — its own
|
|
652
|
-
* `'opened'` or an `'already-open'` answer, in {@link openRevocationLedgerIfDue}. A
|
|
653
|
-
* record of work done, not a cached authorization answer: the marker can never be
|
|
654
|
-
* deleted (`NoDelete`), so this can only go stale if the database itself is replaced,
|
|
655
|
-
* which is why stop() clears it with the other per-process replication state.
|
|
656
|
-
*/
|
|
657
|
-
private revocationLedgerOpened;
|
|
658
701
|
/** This node's own `CadrePeer` self-write committed local-only (re-touched on growth). */
|
|
659
702
|
private pendingSelfPeerWrite;
|
|
660
703
|
/** This node's own `DeviceToken` self-write committed local-only (re-touched on growth). */
|
|
@@ -669,14 +712,24 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
669
712
|
/** Single-flight guard for the re-replication drain (mirrors {@link registerSelfInFlight}). */
|
|
670
713
|
private drainControlReplicationInFlight;
|
|
671
714
|
/**
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
715
|
+
* Connected control peers whose identify says they serve the control database
|
|
716
|
+
* ({@link onControlPeerIdentified}): the peers Optimystic can count into a control block's
|
|
717
|
+
* cohort, before its membership check. A peer joins on its first `peer:identify` of a
|
|
718
|
+
* connected period, not on `connection:open`: until it identifies, every control block's
|
|
719
|
+
* cohort is this node alone. It leaves when its last connection closes. Read through
|
|
720
|
+
* {@link hasReachableControlSibling}, which applies the membership check.
|
|
721
|
+
*/
|
|
722
|
+
private readonly identifiedControlPeers;
|
|
723
|
+
/**
|
|
724
|
+
* {@link hasReachableControlSibling} at its last evaluation, so the drain fires on the
|
|
725
|
+
* false→true edge only (the earliest point a re-issue reaches the cohort), not on every later
|
|
726
|
+
* identify. Re-armed to false when no reachable sibling remains
|
|
727
|
+
* ({@link updateControlSiblingReachability}).
|
|
675
728
|
*/
|
|
676
|
-
private
|
|
677
|
-
/** `
|
|
678
|
-
private
|
|
679
|
-
/** `connection:close` listener
|
|
729
|
+
private controlSiblingReachable;
|
|
730
|
+
/** `peer:identify` listener: {@link onControlPeerIdentified} (teardown in {@link stopRecordRefresh}). */
|
|
731
|
+
private controlPeerIdentifyHandler;
|
|
732
|
+
/** `connection:close` listener: {@link onControlConnectionClosed} (teardown in {@link stopRecordRefresh}). */
|
|
680
733
|
private controlConnectionCloseHandler;
|
|
681
734
|
constructor(config: CadreNodeConfig);
|
|
682
735
|
/**
|
|
@@ -1002,8 +1055,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1002
1055
|
private controlNetworkName;
|
|
1003
1056
|
/**
|
|
1004
1057
|
* Arm the CONTROL network's peer-join block catch-up: push every block in the
|
|
1005
|
-
* control database's own raw store to each
|
|
1006
|
-
*
|
|
1058
|
+
* control database's own raw store to each AUTHORIZED member on each
|
|
1059
|
+
* (re)connection, skipping what it is known to hold. This is what physically replicates
|
|
1007
1060
|
* control blocks committed while the writer was alone — the named
|
|
1008
1061
|
* collection-header blocks written once at genesis above all, whose revision
|
|
1009
1062
|
* never moves again, so no later commit ever carries them to a member that
|
|
@@ -1012,7 +1065,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1012
1065
|
* for peers it knew about before the restart).
|
|
1013
1066
|
*
|
|
1014
1067
|
* Unlike the per-strand instances (see `strand-instance-manager.ts`), pushes
|
|
1015
|
-
* are gated on {@link
|
|
1068
|
+
* are gated on the authorized-member snapshot ({@link authorizedControlPeers},
|
|
1069
|
+
* which {@link refreshAuthorizedControlPeers} materializes from the same rows and
|
|
1070
|
+
* rule {@link isAuthorizedMember} queries), judged at push time: the control
|
|
1016
1071
|
* network's inbound connection gate deliberately admits non-members in
|
|
1017
1072
|
* several states (seed delivery to an un-enrolled node, an open enrollment
|
|
1018
1073
|
* window, an outstanding invitation, configured bootstrap/relay peers), and
|
|
@@ -1028,9 +1083,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1028
1083
|
* The one non-member the gate admits is a device whose invitation
|
|
1029
1084
|
* redemption this member is admitting ({@link admitRedeemingDevice}).
|
|
1030
1085
|
*
|
|
1031
|
-
* No-ops (logged) when
|
|
1032
|
-
*
|
|
1033
|
-
* no way to dial.
|
|
1086
|
+
* No-ops (logged) when {@link controlBackfillStorage} answers nothing (disabled,
|
|
1087
|
+
* or no control storage configured) or the node exposes no key network — the
|
|
1088
|
+
* backfill would have nothing to read or no way to dial.
|
|
1034
1089
|
*/
|
|
1035
1090
|
private startControlBackfill;
|
|
1036
1091
|
/**
|
|
@@ -1070,6 +1125,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1070
1125
|
* operation count.
|
|
1071
1126
|
*/
|
|
1072
1127
|
private resolveControlStorage;
|
|
1128
|
+
/**
|
|
1129
|
+
* The store the control network's peer-join block catch-up reads, or `undefined` when this
|
|
1130
|
+
* runtime does not arm it: `controlBackfill.enabled: false`, or no control storage. Both of
|
|
1131
|
+
* its readers ask here so they cannot disagree: {@link startControlBackfill} arms on it, and
|
|
1132
|
+
* {@link buildControlNodeOptions} turns Optimystic's rebalance reaction off on it.
|
|
1133
|
+
*
|
|
1134
|
+
* Through the resolver, not the {@link controlStorage} memo field: the resolver is
|
|
1135
|
+
* idempotent, so the catch-up cannot be silently disarmed by a future reordering that moves
|
|
1136
|
+
* a read ahead of the one that populated the field.
|
|
1137
|
+
*/
|
|
1138
|
+
private controlBackfillStorage;
|
|
1073
1139
|
/**
|
|
1074
1140
|
* Warn the operator when `network.announceAddrs` will silently cost this node the
|
|
1075
1141
|
* relay reachability it also configured.
|
|
@@ -1169,8 +1235,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1169
1235
|
* (it redeems here by seating the row from its bundle, after which check 7
|
|
1170
1236
|
* admits) or from a sibling whose membership row is still replicating (check 5
|
|
1171
1237
|
* admits once it lands; if it has not landed by the deadline, the connection
|
|
1172
|
-
* is closed and
|
|
1173
|
-
* dials are never gated). The protocol guard refuses all of them every
|
|
1238
|
+
* is closed and the reconcile pass the close starts on either side re-forms it,
|
|
1239
|
+
* since outbound dials are never gated). The protocol guard refuses all of them every
|
|
1174
1240
|
* members-only protocol meanwhile. A circuit-relay reservation admitted by
|
|
1175
1241
|
* {@link admitControlRelayReservation} disarms the deadline (see
|
|
1176
1242
|
* `membership-connection-gater.ts` → "The relay-reservation seam").
|
|
@@ -1318,6 +1384,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1318
1384
|
* needs a key-based admission.
|
|
1319
1385
|
*/
|
|
1320
1386
|
private isControlNetworkMember;
|
|
1387
|
+
/**
|
|
1388
|
+
* Whether this node sends control writes to `remotePeerId`: Optimystic's `isNetworkMember`
|
|
1389
|
+
* for the control node (cohort and coordinator choice), and the membership half of
|
|
1390
|
+
* {@link hasReachableControlSibling}. The stream gate's rule ({@link isControlNetworkMember}),
|
|
1391
|
+
* unlogged, because Optimystic asks several times per peer per cohort assembly and counts
|
|
1392
|
+
* refusals on its own debug line; plus a device whose verified redemption is being written
|
|
1393
|
+
* ({@link admittingDevices}), which must hold the rows that admit it.
|
|
1394
|
+
*/
|
|
1395
|
+
private isControlCohortMember;
|
|
1321
1396
|
/**
|
|
1322
1397
|
* Per-stream authorization for the protocol guard's `members-snapshot` class
|
|
1323
1398
|
* (`control-protocol-guard.ts`): the four Optimystic control-DB protocols
|
|
@@ -1345,7 +1420,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1345
1420
|
* this listener with its write lock HELD (`notifyMembershipChanged`), so a retrying
|
|
1346
1421
|
* read here would sleep its backoff holding the lock and stall every other local
|
|
1347
1422
|
* writer. Nothing is lost — this refresh already keeps the previous snapshot on
|
|
1348
|
-
* failure and is re-driven by the next membership
|
|
1423
|
+
* failure and is re-driven by the next membership change (local or replicated, through
|
|
1424
|
+
* the gate watch) and by the reconcile pass's backstop.
|
|
1349
1425
|
*
|
|
1350
1426
|
* The trusted-owner anchor sync ({@link syncOwnerAnchor}) runs FIRST, so the snapshot
|
|
1351
1427
|
* built here judges `CadrePeer` rows against the anchor as the `OwnerKey` table now
|
|
@@ -1407,9 +1483,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1407
1483
|
*/
|
|
1408
1484
|
private getBootstrapPeerIds;
|
|
1409
1485
|
/**
|
|
1410
|
-
* What the strand watcher
|
|
1486
|
+
* What the strand watcher reads: this party's control rows plus a row per cross-party
|
|
1411
1487
|
* join (see {@link joinedStrands}), so a joined strand is offered and relaunched on
|
|
1412
|
-
* the same path as the party's own.
|
|
1488
|
+
* the same path as the party's own. It runs on a change to either table; the
|
|
1489
|
+
* machine-local joins wake it through {@link JoinedStrandSession}'s change callback.
|
|
1413
1490
|
*/
|
|
1414
1491
|
private createStrandQueryable;
|
|
1415
1492
|
/**
|
|
@@ -1448,6 +1525,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1448
1525
|
* after start (non-blocking). {@link registerSelf} is idempotent and safely
|
|
1449
1526
|
* no-ops when it cannot yet sign/insert (e.g. owner key not installed),
|
|
1450
1527
|
* so the timer is harmless even when registration only becomes possible later.
|
|
1528
|
+
* A record this process already put in place that is still current (a founding owner's
|
|
1529
|
+
* explicit publish, a redeemed invitation's admission, a restart's first-growth re-touch) is
|
|
1530
|
+
* not written again ({@link publishSelfRecordUnlessCurrent}). Whether the node is connected
|
|
1531
|
+
* yet does not matter: a device that redeems within a second of starting is.
|
|
1451
1532
|
*/
|
|
1452
1533
|
private scheduleSelfRegistration;
|
|
1453
1534
|
/**
|
|
@@ -1479,11 +1560,42 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1479
1560
|
/** The body of {@link registerSelf}; serialised by its single-flight guard. */
|
|
1480
1561
|
private publishSelfRecord;
|
|
1481
1562
|
/**
|
|
1482
|
-
*
|
|
1483
|
-
*
|
|
1484
|
-
*
|
|
1563
|
+
* Bookkeeping for a committed write of this node's own `CadrePeer` row by {@link publishSelfRecord}:
|
|
1564
|
+
* `record` is the one the row holds (for the update path, the one its body signed on the
|
|
1565
|
+
* attempt that committed, not the first it signed). Queued for the drain's re-touch
|
|
1566
|
+
* ({@link pendingSelfPeerWrite}) when no sibling was reachable when the write started or when
|
|
1567
|
+
* it ended: a sibling that identifies while the write is out may do so after the write's cohort
|
|
1568
|
+
* was chosen, and the first-growth drain skips a row holding {@link lastSignedSelfRecord} unless
|
|
1569
|
+
* this flag is set. Both fields are set here together so no write can leave the record without
|
|
1570
|
+
* the flag.
|
|
1571
|
+
*/
|
|
1572
|
+
private noteSelfPeerWritten;
|
|
1573
|
+
/**
|
|
1574
|
+
* Record that this node's own address record is in place as of `at`, and re-arm the
|
|
1575
|
+
* stale-record warning. Called for BOTH publishing paths of {@link publishSelfRecord}
|
|
1576
|
+
* ({@link noteSelfPeerWritten}), and by {@link publishSelfRecordUnlessCurrent} with the stamp
|
|
1577
|
+
* of a record already in place — a `skipped` publish wrote nothing and found nothing, so it
|
|
1578
|
+
* must not refresh the stamp.
|
|
1485
1579
|
*/
|
|
1486
1580
|
private noteSelfRecordPublished;
|
|
1581
|
+
/**
|
|
1582
|
+
* {@link registerSelf}, unless the own row already holds the record this process last signed
|
|
1583
|
+
* ({@link lastSignedSelfRecord}) and that record is current ({@link isCurrentSelfRecord});
|
|
1584
|
+
* then nothing is written and the record counts as published at its own stamp. For a publish
|
|
1585
|
+
* that puts a record in place (the boot timer, the reconcile pass's first publish, the
|
|
1586
|
+
* first-growth drain), never the heartbeat, which exists to re-stamp freshness. `'current'`
|
|
1587
|
+
* is this method's own answer; {@link registerSelf} always writes.
|
|
1588
|
+
*/
|
|
1589
|
+
private publishSelfRecordUnlessCurrent;
|
|
1590
|
+
/**
|
|
1591
|
+
* Is `record` still the one to publish: signed less than half a heartbeat ago, with this
|
|
1592
|
+
* node's profile, over the addresses it would publish now, and the signature the own row
|
|
1593
|
+
* holds? The age bound keeps a record that is not re-written fresh for resolvers: at most half
|
|
1594
|
+
* a heartbeat old here and re-stamped by the heartbeat within one more, so never older than
|
|
1595
|
+
* 1.5 heartbeats (11.25 min) against {@link DEFAULT_PEER_RECORD_MAX_AGE_MS}. Cheap checks
|
|
1596
|
+
* first: the row comes from the membership view, the addresses from the app's resolver.
|
|
1597
|
+
*/
|
|
1598
|
+
private isCurrentSelfRecord;
|
|
1487
1599
|
/**
|
|
1488
1600
|
* Report an abandoned control write to the embedding app.
|
|
1489
1601
|
*
|
|
@@ -1570,12 +1682,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1570
1682
|
* process does not learn it was removed ({@link noteOwnRemoval}), and pays that read on every
|
|
1571
1683
|
* pass.
|
|
1572
1684
|
*
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1575
|
-
*
|
|
1576
|
-
* then never see rows written while it
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1685
|
+
* Only while a sibling is reachable ({@link hasReachableControlSibling}): a row written
|
|
1686
|
+
* elsewhere arrives from a sibling, and an owner's publish committed alone is local-only and
|
|
1687
|
+
* forks its own history — a restarted owner whose embedder runs a pass before it reconnects
|
|
1688
|
+
* would write its row on the stale side of the fork, and then never see rows written while it
|
|
1689
|
+
* was down (`cadre-invite-any-member` runs that pass and fails every time without this
|
|
1690
|
+
* condition). A connection is not enough: until the sibling identifies, the write's cohort is
|
|
1691
|
+
* this node alone. The boot-time publish and the first-growth drain already try at the
|
|
1692
|
+
* node's lone and first-sibling moments; this retries on the passes after.
|
|
1693
|
+
*
|
|
1694
|
+
* A machine admitted by invitation finds its row already holding the record its redemption
|
|
1695
|
+
* carried, and writes nothing ({@link publishSelfRecordUnlessCurrent}).
|
|
1579
1696
|
*/
|
|
1580
1697
|
private publishFirstSelfRecord;
|
|
1581
1698
|
/**
|
|
@@ -1592,7 +1709,21 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1592
1709
|
* stack listeners/timers.
|
|
1593
1710
|
*/
|
|
1594
1711
|
private startRecordRefresh;
|
|
1595
|
-
/**
|
|
1712
|
+
/** The reconcile schedule's backstop: `network.controlCohort.reconcileMs`, default `DEFAULT_CONTROL_COHORT_RECONCILE_MS`. */
|
|
1713
|
+
private controlReconcileBackstopMs;
|
|
1714
|
+
/**
|
|
1715
|
+
* The gate watch's run ({@link membershipGateWatch}): re-materialize the gate, so a
|
|
1716
|
+
* replicated revocation hangs up the removed machine now (`hangUpRevokedControlPeers`
|
|
1717
|
+
* ends every refresh), then start a reconcile pass for whatever else the change means
|
|
1718
|
+
* (a new sibling to dial, a tombstoned row to reap). A local membership write refreshes
|
|
1719
|
+
* the gate twice — once from the awaited membership listener, so the writer sees its
|
|
1720
|
+
* change before returning, and once here, 50 ms later. The listener's refresh runs under
|
|
1721
|
+
* the write lock, so its reads take the committed-read path and the view keeps nothing
|
|
1722
|
+
* from them; this refresh is the read that refills the view after the write, which some
|
|
1723
|
+
* later caller would otherwise pay. Accepted: one reload per change, not two.
|
|
1724
|
+
*/
|
|
1725
|
+
private onMembershipGateTablesChanged;
|
|
1726
|
+
/** Tear down the self-record refresh timers, the reconcile schedule and the listeners (see {@link cleanup}). */
|
|
1596
1727
|
private stopRecordRefresh;
|
|
1597
1728
|
/**
|
|
1598
1729
|
* Resolve a peer's current, signed, trust-checkable multiaddrs from only its
|
|
@@ -1666,37 +1797,49 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1666
1797
|
* Run one proactive control-cohort dial pass to keep this node connected to its
|
|
1667
1798
|
* cadre siblings (so the `CadreControl` collections form a replicating cohort).
|
|
1668
1799
|
* Public so the cohort-growth-driven re-replication path
|
|
1669
|
-
* (`control-write-ensure-replicated`)
|
|
1670
|
-
*
|
|
1671
|
-
*
|
|
1800
|
+
* (`control-write-ensure-replicated`), `addDrone`'s callers, `claimNode`,
|
|
1801
|
+
* `redeemCadreInvitation` and tests can run a pass on demand. On its own a pass
|
|
1802
|
+
* runs when something it acts on changes, and otherwise at a backstop
|
|
1803
|
+
* ({@link controlReconcileRuns}): once after start; on `self:peer:update`; when a
|
|
1804
|
+
* membership table changes, local commit or replicated ({@link membershipGateWatch});
|
|
1805
|
+
* when a control connection to an authorized sibling opens or closes
|
|
1806
|
+
* ({@link onControlSiblingConnectionChange}); on the redial and strand-address wakes
|
|
1807
|
+
* a pass arms for itself ({@link armReconcileWake}); and at the backstop,
|
|
1808
|
+
* `network.controlCohort.reconcileMs` (default `DEFAULT_CONTROL_COHORT_RECONCILE_MS`,
|
|
1809
|
+
* 2 minutes) after the last pass settled. A connection to a relay, a bootstrap node or
|
|
1810
|
+
* another party's machine starts no pass: the pass acts on none of them, beyond
|
|
1811
|
+
* counting them as "connected" for its write steps, which therefore run at the next
|
|
1812
|
+
* membership change or backstop on a one-machine party whose only connection is a
|
|
1813
|
+
* relay.
|
|
1672
1814
|
*
|
|
1673
1815
|
* Concurrent triggers collapse into a single in-flight pass
|
|
1674
1816
|
* (see {@link reconcileControlCohortInFlight}) so two passes never double-dial;
|
|
1675
|
-
* a call that joins an in-flight pass resolves to THAT pass's result
|
|
1817
|
+
* a call that joins an in-flight pass resolves to THAT pass's result (a scheduled
|
|
1818
|
+
* run waits for it instead, so the change that woke the run is seen).
|
|
1676
1819
|
* Best-effort throughout: a failure to resolve/dial any one sibling is logged
|
|
1677
1820
|
* and the pass continues; the whole pass is a no-op when the node is alone.
|
|
1678
1821
|
*
|
|
1679
1822
|
* Resolves to the peers the pass dialled ({@link ControlCohortReconcileResult}),
|
|
1680
1823
|
* so a caller can tell a link this pass opened from one something else opened —
|
|
1681
|
-
* the pass skips a peer that is already connected. The
|
|
1682
|
-
* discard it.
|
|
1824
|
+
* the pass skips a peer that is already connected. The scheduled runs discard it.
|
|
1683
1825
|
*/
|
|
1684
1826
|
reconcileControlCohort(): Promise<ControlCohortReconcileResult>;
|
|
1827
|
+
/** One pass, then the wake it earns ({@link armReconcileWake}); the public result is the pass's `dialed`. */
|
|
1828
|
+
private runReconcileControlCohortAndArmWake;
|
|
1829
|
+
/**
|
|
1830
|
+
* After every pass, explicit or scheduled: advance or reset the redial streak and
|
|
1831
|
+
* `trigger` the schedule for the earliest of the pass's own reasons to run again
|
|
1832
|
+
* (`nextReconcileWakeMs`): a redial for a sibling or bootstrap peer it left
|
|
1833
|
+
* unconnected, or a strand-address ask falling due before the backstop. Nothing else
|
|
1834
|
+
* the pass does needs a wake sooner than the backstop: delegate grants re-announce
|
|
1835
|
+
* every 15 minutes inside a 30-minute TTL, the FRET and formation address re-merges
|
|
1836
|
+
* guard a one-hour expiry, and the self record has its own heartbeat. A pass that
|
|
1837
|
+
* runs before the schedule exists (an explicit call during the first second after
|
|
1838
|
+
* start) still counts for the streak.
|
|
1839
|
+
*/
|
|
1840
|
+
private armReconcileWake;
|
|
1685
1841
|
/** Body of {@link reconcileControlCohort}; serialised by its single-flight guard. */
|
|
1686
1842
|
private runReconcileControlCohort;
|
|
1687
|
-
/**
|
|
1688
|
-
* The owner-only half of the reconcile pass's ledger-marker step: file the singleton
|
|
1689
|
-
* `Revocation` marker ({@link SeedBootstrapService.openRevocationLedger}) unless this
|
|
1690
|
-
* process has already seen it filed. The connectivity gate is the caller's
|
|
1691
|
-
* ({@link runReconcileControlCohort}); why the marker exists is on
|
|
1692
|
-
* {@link ControlDatabase.openRevocationLedger}.
|
|
1693
|
-
*
|
|
1694
|
-
* Sets {@link revocationLedgerOpened} on `'opened'` or `'already-open'`. A node that
|
|
1695
|
-
* cannot sign as an owner does nothing: it picks the owner's marker up the first time it
|
|
1696
|
-
* reads the table, like any other replicated row. Best-effort like every step of the
|
|
1697
|
-
* pass — a failure is logged and leaves the flag clear, so the next connected pass retries.
|
|
1698
|
-
*/
|
|
1699
|
-
private openRevocationLedgerIfDue;
|
|
1700
1843
|
/**
|
|
1701
1844
|
* Resolve every sibling's signed address record once and merge what resolves
|
|
1702
1845
|
* into the control node's libp2p **address book** (peerStore). Returns the
|
|
@@ -1720,8 +1863,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1720
1863
|
* current ({@link refreshDialHint}).
|
|
1721
1864
|
*
|
|
1722
1865
|
* NOTE: this resolves EVERY sibling serially before the dial loop below runs,
|
|
1723
|
-
* so it costs one record query per sibling per reconcile pass (
|
|
1724
|
-
*
|
|
1866
|
+
* so it costs one record query per sibling per reconcile pass (served from the
|
|
1867
|
+
* membership rows view while it is current) and each one delays the pass's first
|
|
1868
|
+
* dial. A cadre is a handful of devices, so today
|
|
1725
1869
|
* that is a few extra local reads; if cadres ever grow large, batch the records
|
|
1726
1870
|
* into one query, merge only on change, or move the warm pass after the dials.
|
|
1727
1871
|
*/
|
|
@@ -1783,16 +1927,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1783
1927
|
*/
|
|
1784
1928
|
private collectSiblingStrandAddrs;
|
|
1785
1929
|
/**
|
|
1786
|
-
* Dial one sibling from its already-resolved addresses, best-effort.
|
|
1787
|
-
* whether the dial resolved (false when no address resolves or the dial fails).
|
|
1930
|
+
* Dial one sibling from its already-resolved addresses, best-effort.
|
|
1788
1931
|
*
|
|
1789
1932
|
* A per-peer failure (NAT, offline, relay down, connection-gater denial, or
|
|
1790
1933
|
* the {@link controlDialBudget} expiring) is logged and swallowed so one
|
|
1791
1934
|
* unreachable sibling never aborts the pass — exactly like
|
|
1792
1935
|
* {@link SeedBootstrapService.applySeed}'s owner-dial loop. A failed dial is
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1795
|
-
*
|
|
1936
|
+
* retried on the pass's redial ladder ({@link armReconcileWake}: 15 s, 30 s, 60 s,
|
|
1937
|
+
* then every backstop) and on any pass a change starts sooner. A sibling with no
|
|
1938
|
+
* dialable address, or whose every address relays through this node
|
|
1939
|
+
* ({@link SelfRelayOnlyError}), is not dialed at all and is `undialable`: it gets a
|
|
1940
|
+
* one-line log and no redial, since only a change — its record replicating in, or
|
|
1941
|
+
* the sibling connecting — can make it dialable, and each of those starts a pass.
|
|
1796
1942
|
*/
|
|
1797
1943
|
private dialControlSibling;
|
|
1798
1944
|
/**
|
|
@@ -1836,7 +1982,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1836
1982
|
* These addresses are never merged into the libp2p address book: they are
|
|
1837
1983
|
* unverified, and the address book takes only verified ones (see
|
|
1838
1984
|
* `mergePeerAddrs`). Layers that dial by bare peer id use the connection this
|
|
1839
|
-
* dial opens, and after it drops the
|
|
1985
|
+
* dial opens, and after it drops the reconcile pass the close starts dials again.
|
|
1840
1986
|
*
|
|
1841
1987
|
* NOTE: an entry is consulted here only for a current sibling, so a peer revoked
|
|
1842
1988
|
* by another owner is not dialed from it, even though the entry stays (only this
|
|
@@ -1870,8 +2016,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1870
2016
|
* Called for every seed this node accepts, on BOTH intake paths — the
|
|
1871
2017
|
* {@link applySeed} wrapper (which may run on a throwaway service, so the
|
|
1872
2018
|
* service itself is the wrong place to keep this) and the inbound
|
|
1873
|
-
* `/sereus/seed/1.0.0` handler via `onSeedApplied`.
|
|
1874
|
-
* skipped: there is nothing to dial.
|
|
2019
|
+
* `/sereus/seed/1.0.0` handler via `onSeedApplied`.
|
|
1875
2020
|
*
|
|
1876
2021
|
* `preferred` is the seed's own claim about its peers, exactly as
|
|
1877
2022
|
* `SeedBootstrapService.applySeed` uses it to choose its dial targets. That is
|
|
@@ -1879,7 +2024,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1879
2024
|
* signature-checked against a trust-anchored signer before this runs, and the
|
|
1880
2025
|
* flag only *selects a dial target*; a dial grants no authority.
|
|
1881
2026
|
*
|
|
1882
|
-
*
|
|
2027
|
+
* The peers retained are `seedDialTargets` (addressless peers and self dropped), the same
|
|
2028
|
+
* ones the service dials; they are returned for the `seed:applied` payload.
|
|
1883
2029
|
*/
|
|
1884
2030
|
private recordSeedBootstrapPeers;
|
|
1885
2031
|
/**
|
|
@@ -1894,9 +2040,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1894
2040
|
* failing a seed or an add that has already been accepted.
|
|
1895
2041
|
*
|
|
1896
2042
|
* Two peers are never retained. One with no address: there is nothing to dial.
|
|
1897
|
-
* And self: `
|
|
1898
|
-
*
|
|
1899
|
-
* could be handed this node's own id. Retaining self would make the cold-start
|
|
2043
|
+
* And self: `addDrone` could be handed this node's own id (seed intake arrives
|
|
2044
|
+
* already filtered by `seedDialTargets`). Retaining self would make the cold-start
|
|
1900
2045
|
* pass dial this node forever (the steady-state pass filters self out of its
|
|
1901
2046
|
* sibling list for the same reason).
|
|
1902
2047
|
*/
|
|
@@ -1914,15 +2059,16 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1914
2059
|
* help: it dials only siblings enumerated from that very table. Retrying here
|
|
1915
2060
|
* is the only way back in.
|
|
1916
2061
|
*
|
|
1917
|
-
* Unbounded
|
|
1918
|
-
*
|
|
1919
|
-
*
|
|
1920
|
-
* a
|
|
1921
|
-
*
|
|
1922
|
-
*
|
|
1923
|
-
*
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
2062
|
+
* Unbounded: the branch is already gated by "the control database has no
|
|
2063
|
+
* siblings", so it stops the moment the node is actually in the party, and a node
|
|
2064
|
+
* that is stranded MUST keep trying — a give-up rule would turn a transient outage
|
|
2065
|
+
* into a permanent one. A failed dial earns the pass a redial wake on the ladder
|
|
2066
|
+
* {@link armReconcileWake} runs (15 s, 30 s, 60 s, then every backstop, 2 minutes
|
|
2067
|
+
* by default), so the steady cost is one dial per bootstrap peer per backstop (a
|
|
2068
|
+
* seed prefers its minter and the party's always-on machines, a few at most), each
|
|
2069
|
+
* failing fast against an unreachable address.
|
|
2070
|
+
* NOTE: the ladder is per pass, not per peer; if seeds ever carry many preferred
|
|
2071
|
+
* peers, add per-peer backoff here.
|
|
1926
2072
|
*
|
|
1927
2073
|
* Best-effort per peer, exactly like {@link dialControlSibling}: one dead
|
|
1928
2074
|
* address never aborts the pass.
|
|
@@ -1941,20 +2087,26 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1941
2087
|
* encapsulated; an unencapsulatable one is dropped rather than dialed bare.
|
|
1942
2088
|
*/
|
|
1943
2089
|
private bootstrapDialAddrs;
|
|
1944
|
-
/**
|
|
2090
|
+
/**
|
|
2091
|
+
* Dial one peer from its retained dial target's addresses, best-effort: a cold-start bootstrap
|
|
2092
|
+
* peer, or a just-claimed node ({@link redialClaimedNode}), which `context` names in the logs.
|
|
2093
|
+
* Returns whether it connected.
|
|
2094
|
+
*/
|
|
1945
2095
|
private dialBootstrapPeer;
|
|
1946
2096
|
/**
|
|
1947
|
-
* Whether a control write happening now would commit local-only
|
|
1948
|
-
*
|
|
1949
|
-
*
|
|
1950
|
-
*
|
|
1951
|
-
*
|
|
1952
|
-
*
|
|
1953
|
-
*
|
|
1954
|
-
* is acceptable as the agreed first cut.
|
|
2097
|
+
* Whether a control write happening now would commit local-only: no sibling is reachable
|
|
2098
|
+
* ({@link hasReachableControlSibling}, the public form, so the two cannot drift). A relay, a
|
|
2099
|
+
* bootstrap node or a peer that has not identified yet holds a connection without being in the
|
|
2100
|
+
* write's cohort, so a write made with only those connected is queued too. One made alone at
|
|
2101
|
+
* the revision the cohort reached meanwhile is a fork no later consult sees, because a consult
|
|
2102
|
+
* compares revision numbers only; the re-issue on the growth edge is what conflicts with the
|
|
2103
|
+
* cohort's copy and pulls it in.
|
|
1955
2104
|
*
|
|
1956
|
-
*
|
|
1957
|
-
* the
|
|
2105
|
+
* Still a proxy for the precise signal (the block's own cohort): a reachable sibling outside
|
|
2106
|
+
* this block's cohort leaves the write local-only, caught by the cohort's pull-on-read. On the
|
|
2107
|
+
* control network every block's cohort is the whole party, so that gap is a party larger than
|
|
2108
|
+
* `CONTROL_REPLICATION_BREADTH`. Re-issuing a replicated row is harmless (an idempotent
|
|
2109
|
+
* monotonic bump).
|
|
1958
2110
|
*/
|
|
1959
2111
|
private committedAlone;
|
|
1960
2112
|
/**
|
|
@@ -1984,7 +2136,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1984
2136
|
/**
|
|
1985
2137
|
* Drain the write-while-alone re-replication queue: re-issue the writes that
|
|
1986
2138
|
* committed local-only now that the cohort can broadcast them. Public so the
|
|
1987
|
-
*
|
|
2139
|
+
* growth trigger ({@link updateControlSiblingReachability}) and tests can drive
|
|
1988
2140
|
* it; concurrent calls collapse into one in-flight drain
|
|
1989
2141
|
* ({@link drainControlReplicationInFlight}) so two growth signals never
|
|
1990
2142
|
* double-issue. Best-effort throughout — a per-row failure leaves that entry
|
|
@@ -2053,8 +2205,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2053
2205
|
private reconstructAuthoredMembership;
|
|
2054
2206
|
/**
|
|
2055
2207
|
* Body of {@link reconstructAuthoredMembership}'s sweep: re-issue each candidate
|
|
2056
|
-
* owner-authored row, returning how many were re-touched. A
|
|
2057
|
-
*
|
|
2208
|
+
* owner-authored row, returning how many were re-touched. A row the sweep already holds as
|
|
2209
|
+
* self-published is skipped without a call; the re-touch itself decides again over the row
|
|
2210
|
+
* it reads under the lock, so a row that vanished or was self-published since the sweep's
|
|
2211
|
+
* read is reported, not written. A per-row failure is logged and skipped; a teardown
|
|
2212
|
+
* mid-sweep stops it.
|
|
2058
2213
|
*/
|
|
2059
2214
|
private reissueAuthoredMembershipRows;
|
|
2060
2215
|
/**
|
|
@@ -2070,9 +2225,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2070
2225
|
*/
|
|
2071
2226
|
private drainPendingPeerWrites;
|
|
2072
2227
|
/**
|
|
2073
|
-
* Body of {@link drainPendingPeerWrites}' loop. An entry is cleared
|
|
2074
|
-
*
|
|
2075
|
-
* the
|
|
2228
|
+
* Body of {@link drainPendingPeerWrites}' loop. An entry is cleared on every outcome the
|
|
2229
|
+
* re-touch reports — re-touched, or nothing left to re-issue because the row was removed
|
|
2230
|
+
* since or the peer self-published; the re-touch decides which over the row it reads under
|
|
2231
|
+
* the lock. A failure is logged and the entry stays queued for the next cohort growth.
|
|
2076
2232
|
*/
|
|
2077
2233
|
private reissuePendingPeerWrites;
|
|
2078
2234
|
/**
|
|
@@ -2080,23 +2236,84 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2080
2236
|
* `UpdatedAt` strictly above the stored value (and the wall clock) and re-sign via
|
|
2081
2237
|
* the owner branch of `CadrePeer.AuthorizedUpdate`. The row already exists
|
|
2082
2238
|
* locally (it committed there, just local-only), so this is an UPDATE, not the
|
|
2083
|
-
* original INSERT.
|
|
2239
|
+
* original INSERT. The bump, and whether the row is still the owner's to re-issue, are
|
|
2240
|
+
* decided over the row as read inside the write ({@link SeedBootstrapService.reauthorizePeer}'s
|
|
2241
|
+
* re-broadcast mode).
|
|
2084
2242
|
*/
|
|
2085
2243
|
private reissuePeerAuthorize;
|
|
2086
2244
|
/**
|
|
2087
|
-
*
|
|
2088
|
-
* write-while-alone re-replication queue (single-flight;
|
|
2089
|
-
* not on
|
|
2090
|
-
*
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2245
|
+
* Re-evaluate {@link hasReachableControlSibling} and act on its edges: on false→true, drain
|
|
2246
|
+
* the write-while-alone re-replication queue (single-flight; best-effort, a drain failure is
|
|
2247
|
+
* logged, not thrown); on true→false, re-arm, so the next reachable sibling drains again.
|
|
2248
|
+
* Called when a peer identifies, when a connection closes, and after every membership refresh,
|
|
2249
|
+
* where a sibling that connected before this node authorized it (the production join order)
|
|
2250
|
+
* becomes reachable. A device counted in for its admission ({@link admittingDevices}) is not
|
|
2251
|
+
* a trigger: the admission's own writes are in flight, and its row landing refreshes
|
|
2252
|
+
* membership.
|
|
2253
|
+
*/
|
|
2254
|
+
private updateControlSiblingReachability;
|
|
2255
|
+
/**
|
|
2256
|
+
* A connected peer identified as serving the control database: count it
|
|
2257
|
+
* ({@link identifiedControlPeers}), re-evaluate the growth edge, and run the sibling-open
|
|
2258
|
+
* reactions, once per connected period. libp2p re-emits `peer:identify` on every
|
|
2259
|
+
* identify-push from a connected peer, and those repeats start nothing.
|
|
2260
|
+
*/
|
|
2261
|
+
private onControlPeerIdentified;
|
|
2262
|
+
/**
|
|
2263
|
+
* A control connection closed: `remotePeer` stops counting once it has none left, and the
|
|
2264
|
+
* growth edge re-arms if no reachable sibling remains. The sibling-close reaction runs on every
|
|
2265
|
+
* close. libp2p's connection manager drops the closed connection from `getConnections` before
|
|
2266
|
+
* this listener runs: libp2p dispatches each event on its internal bus, where the connection
|
|
2267
|
+
* manager listens, before re-dispatching it to the node's own listeners.
|
|
2268
|
+
*/
|
|
2269
|
+
private onControlConnectionClosed;
|
|
2270
|
+
/**
|
|
2271
|
+
* Count the peers that identified before {@link wireControlConnectionListeners} ran, whose
|
|
2272
|
+
* `peer:identify` has already fired, from their peer-store records. Updates the growth edge
|
|
2273
|
+
* but runs no sibling-open reaction, as a connection that predates the listeners never did. A
|
|
2274
|
+
* peer not yet in the peer store is still identifying, and its own event counts it.
|
|
2275
|
+
*/
|
|
2276
|
+
private noteIdentifiedControlPeers;
|
|
2277
|
+
/**
|
|
2278
|
+
* An AUTHORIZED sibling became reachable (its identify, once per connected period:
|
|
2279
|
+
* {@link onControlPeerIdentified}) or one of its connections closed: start a reconcile
|
|
2280
|
+
* pass. On open the pass asks the new sibling for strand addresses and re-arms the
|
|
2281
|
+
* delegate grants; on close it re-dials. First, on open, drop the membership rows
|
|
2282
|
+
* views: a machine that was offline while a sibling committed learns of the commit
|
|
2283
|
+
* only when it next reads the block from storage, and dropping the views makes the
|
|
2284
|
+
* pass's reads do that now rather than after the views' maximum age. Those reads can
|
|
2285
|
+
* fall inside the read-repair window, so the open also arms a second drop past it
|
|
2286
|
+
* ({@link armMembershipViewReload}). Any other
|
|
2287
|
+
* peer — a relay, a bootstrap node, another party's machine — starts nothing.
|
|
2288
|
+
*
|
|
2289
|
+
* On identify rather than `connection:open` because, until the sibling identifies,
|
|
2290
|
+
* Optimystic's cohort for every control block is this node alone: a pass started at the
|
|
2291
|
+
* open writes (the first self-record publish) and reads on a cohort of one.
|
|
2292
|
+
*/
|
|
2293
|
+
private onControlSiblingConnectionChange;
|
|
2294
|
+
/**
|
|
2295
|
+
* Drop the membership views again {@link MEMBERSHIP_VIEW_RECONNECT_RELOAD_MS} after a
|
|
2296
|
+
* sibling connects, and start a pass whose reads reload them. This node's reads before
|
|
2297
|
+
* the connection stamped Optimystic's read-repair window on the control blocks, so the
|
|
2298
|
+
* reload the open itself starts is served from the local copy with no cohort consult,
|
|
2299
|
+
* and the view would keep rows that predate the sibling's commits until its maximum age.
|
|
2300
|
+
* The reload past the window starts the consults; one that brings a newer revision
|
|
2301
|
+
* raises the table change that drops the view and refreshes the gate. Re-armed by every
|
|
2302
|
+
* open, so it runs one window after the latest.
|
|
2303
|
+
*
|
|
2304
|
+
* NOTE: a consult Optimystic defers (16 already in flight) starts nothing and leaves the block
|
|
2305
|
+
* unstamped, so the view keeps its rows until the first read past its maximum age, which on an
|
|
2306
|
+
* idle node is the reconcile backstop. If `cluster-tx:read-repair-deferred` lines show at a
|
|
2307
|
+
* reconnect, re-arm this reload once more instead of waiting for the backstop.
|
|
2308
|
+
*/
|
|
2309
|
+
private armMembershipViewReload;
|
|
2310
|
+
/**
|
|
2311
|
+
* Wire the control-connection listeners, which keep {@link identifiedControlPeers}: the
|
|
2312
|
+
* growth edge that drives write-while-alone re-replication, and the sibling reconcile
|
|
2313
|
+
* trigger ({@link onControlSiblingConnectionChange}). Wired in {@link start} (not the
|
|
2314
|
+
* delayed refresh path) so no early identify is missed, and reads back the peers that
|
|
2315
|
+
* identified before it ran ({@link noteIdentifiedControlPeers}); torn down in
|
|
2316
|
+
* {@link stopRecordRefresh}. Idempotent.
|
|
2100
2317
|
*/
|
|
2101
2318
|
private wireControlConnectionListeners;
|
|
2102
2319
|
/**
|
|
@@ -2353,8 +2570,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2353
2570
|
* registered, deliberately: both an explicit retry and the {@link StrandWatcher}'s
|
|
2354
2571
|
* automatic relaunch need it. A failed launch here hands the strand back to that
|
|
2355
2572
|
* relaunch — `StrandWatcher.forgetStrand` drops the id from the watcher's
|
|
2356
|
-
* `knownStrands
|
|
2357
|
-
*
|
|
2573
|
+
* `knownStrands`, records the backoff and wakes the watcher when it expires, so the
|
|
2574
|
+
* strand is re-attempted on the same ladder a watcher-driven failure gets
|
|
2575
|
+
* (`min(strandWatchInterval, 5 s) * 2^(failures-1)`, capped at 5 minutes). Because the
|
|
2358
2576
|
* config stays registered, the retry takes `handleStrandAdded`'s auto-launch branch:
|
|
2359
2577
|
* what the app sees is `strand:error` per failed retry and `strand:started` when one
|
|
2360
2578
|
* succeeds, never a second `strand:discovered`. {@link detachStrand} (reached via
|
|
@@ -2412,7 +2630,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2412
2630
|
* (the key exists nowhere else). No app-data wipe is needed for either.
|
|
2413
2631
|
*
|
|
2414
2632
|
* The insert is signed through this node's owner signer ({@link getOwnerSigner}), in one
|
|
2415
|
-
* approval with a closed strand's party
|
|
2633
|
+
* approval and one control transaction with a closed strand's party key
|
|
2634
|
+
* ({@link ControlDatabase.insertFoundingRows}). The signer's key is persisted on the row
|
|
2416
2635
|
* as `FounderOwnerKey` (the schema pins the column to the verified signer), and this node's
|
|
2417
2636
|
* peer id as `FounderPeerId` (bound into the signature), which is what later lets any
|
|
2418
2637
|
* launch derive "this machine is the founder" from the row alone.
|
|
@@ -2423,7 +2642,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2423
2642
|
*
|
|
2424
2643
|
* A CLOSED strand's publish also seats this party's own membership identity key —
|
|
2425
2644
|
* the `StrandPartyKey` row {@link ensureStrandPartyKey} documents — which is what the
|
|
2426
|
-
* founder bootstrap derives `Member.Key`/`Manager.MemberKey` from
|
|
2645
|
+
* founder bootstrap derives `Member.Key`/`Manager.MemberKey` from; a key the party already
|
|
2646
|
+
* holds for the id is kept. The
|
|
2427
2647
|
* `memberPrivateKey` ARGUMENT stays the strand-wide read secret formation hands to
|
|
2428
2648
|
* joiners; it derives nobody's identity.
|
|
2429
2649
|
*
|
|
@@ -2443,16 +2663,55 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2443
2663
|
*/
|
|
2444
2664
|
publishStrand(strandId: string, type?: 'o' | 'c', memberPrivateKey?: string, sign?: OwnerSign): Promise<StrandRow>;
|
|
2445
2665
|
/**
|
|
2446
|
-
* The
|
|
2447
|
-
*
|
|
2448
|
-
*
|
|
2666
|
+
* The `Strand` row this machine publishes for `strandId`, as {@link publishStrand} and
|
|
2667
|
+
* {@link foundStrand} write it: the id trimmed and checked, this node's owner key and peer id
|
|
2668
|
+
* as its founder.
|
|
2669
|
+
*/
|
|
2670
|
+
private founderStrandRow;
|
|
2671
|
+
/**
|
|
2672
|
+
* The write half of {@link publishStrand} and {@link foundStrand}: under one owner approval,
|
|
2673
|
+
* insert `desired`, a closed strand's party key and the requested invitation in one control
|
|
2674
|
+
* transaction ({@link ControlDatabase.insertFoundingRows}), adopting a matching row a
|
|
2675
|
+
* concurrent founder landed first. Reads nothing first: the caller has just read the
|
|
2676
|
+
* `Strand` row, and a stored key or row is resolved from the insert's own rejection.
|
|
2677
|
+
*/
|
|
2678
|
+
private writeFoundingRows;
|
|
2679
|
+
/** {@link writeFoundingRows} inside its approval. */
|
|
2680
|
+
private insertOrAdoptFounding;
|
|
2681
|
+
/**
|
|
2682
|
+
* Insert `desired`'s founding rows, minting a closed strand's party key — this party's own
|
|
2683
|
+
* membership identity, which the founder bootstrap derives `Member`/`Manager` from, distinct
|
|
2684
|
+
* from the row's shared `MemberPrivateKey` read secret. A key already stored for the id with
|
|
2685
|
+
* no strand row (one {@link ensureStrandPartyKey} seated, which it would adopt too) refuses
|
|
2686
|
+
* that transaction whole; the rows are then written again without a key.
|
|
2687
|
+
*
|
|
2688
|
+
* @returns The party key this seated; undefined when it seated none, so the launch reads the stored one.
|
|
2689
|
+
*/
|
|
2690
|
+
private seatFoundingRows;
|
|
2691
|
+
/**
|
|
2692
|
+
* Resolve a founding refused on `Strand.Id`: re-read the row that landed and adopt it when it
|
|
2693
|
+
* matches `desired` — a concurrent founder, such as two machines of one party on one id —
|
|
2694
|
+
* or throw naming the columns that differ.
|
|
2449
2695
|
*/
|
|
2450
|
-
private
|
|
2696
|
+
private adoptConcurrentFounding;
|
|
2697
|
+
/**
|
|
2698
|
+
* Publish a founding's invitation on its own, bound to `strandId`, for a founding that adopted
|
|
2699
|
+
* a stored row rather than writing its own — unless the invitation is already stored, bound to
|
|
2700
|
+
* this strand, as a repeat of a founding whose transaction committed leaves it.
|
|
2701
|
+
*
|
|
2702
|
+
* @param sign - The open approval's `sign`; omitted, the publish asks for its own.
|
|
2703
|
+
*/
|
|
2704
|
+
private publishFoundingInviteIfAbsent;
|
|
2451
2705
|
/**
|
|
2452
2706
|
* Found a strand — publish its row cadre-wide AND start the local instance as its
|
|
2453
2707
|
* founder — in one resumable call. The single entry point for creating a strand; a
|
|
2454
2708
|
* caller joining one someone else founded uses {@link addStrand} alone.
|
|
2455
2709
|
*
|
|
2710
|
+
* The publish is one control transaction under one owner approval: the `Strand` row, a
|
|
2711
|
+
* closed strand's `StrandPartyKey` row, and the invitation in
|
|
2712
|
+
* {@link FoundStrandConfig.formationInvite} when one is given. The start then uses the party
|
|
2713
|
+
* key the publish seated rather than reading it back.
|
|
2714
|
+
*
|
|
2456
2715
|
* Safe to re-run from any point of interruption, which hand-rolling
|
|
2457
2716
|
* {@link publishStrand} + {@link addStrand} is not. Either half can already have
|
|
2458
2717
|
* happened:
|
|
@@ -2511,10 +2770,20 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2511
2770
|
* so both forms of `foundStrand` reject on it before returning.
|
|
2512
2771
|
*/
|
|
2513
2772
|
private publishFoundingRow;
|
|
2773
|
+
/** {@link publishFoundingRow} when no row is stored: write the founding rows under this node's owner. */
|
|
2774
|
+
private publishFoundingStrand;
|
|
2775
|
+
/**
|
|
2776
|
+
* {@link publishFoundingRow} over a stored row: adopt it ({@link adoptPublishedStrand}), then
|
|
2777
|
+
* publish the requested invitation on its own, under its own approval.
|
|
2778
|
+
*/
|
|
2779
|
+
private resumeFounding;
|
|
2514
2780
|
/**
|
|
2515
2781
|
* The start half of {@link foundStrand}. Its timing lines (`addStrand: start`, then the
|
|
2516
2782
|
* duration or `failed after`) are logged when the start settles, also in the background
|
|
2517
2783
|
* form, so a start that hangs still shows as a start line with no end line.
|
|
2784
|
+
*
|
|
2785
|
+
* A party key the publish half just seated is handed to the launch, which would otherwise
|
|
2786
|
+
* read back the row the same founding wrote.
|
|
2518
2787
|
*/
|
|
2519
2788
|
private attachFoundingRow;
|
|
2520
2789
|
/**
|
|
@@ -2530,9 +2799,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2530
2799
|
* inverse of {@link publishStrand}.
|
|
2531
2800
|
*
|
|
2532
2801
|
* Party-wide, unlike {@link stopStrand}, which only stops the strand on THIS node: every
|
|
2533
|
-
* cadre node watching the table
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2802
|
+
* cadre node watching the table hears the row's removal and stops its own instance (a
|
|
2803
|
+
* machine outside the `Strand` block's cohort, on a cadre larger than 16, only at its
|
|
2804
|
+
* watcher's backstop read, default 5 minutes). This node converges immediately — the
|
|
2805
|
+
* method forces a watcher run and stops any still-running local instance before resolving. Removing the row
|
|
2536
2806
|
* removes OUR party's participation only: other parties in the strand keep their own
|
|
2537
2807
|
* rows and the strand network carries on, so no cross-party sign-off is involved — this
|
|
2538
2808
|
* is an owner-signed control-plane write like every other, not "destroy the network".
|
|
@@ -2597,9 +2867,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2597
2867
|
* as `MemberPrivateKey`; docs/strands.md → "Closed-Strand Member Key Handling") and is
|
|
2598
2868
|
* never put on the formation wire.
|
|
2599
2869
|
*
|
|
2600
|
-
*
|
|
2601
|
-
*
|
|
2602
|
-
*
|
|
2870
|
+
* A founding seats the key in the same transaction as the strand row, not through here.
|
|
2871
|
+
* Called internally by {@link publishStrand}'s repeat of a published closed strand and by a
|
|
2872
|
+
* founder launch that finds no row, which heal a closed strand row stored without its key
|
|
2873
|
+
* (one seated by `ControlDatabase.insertStrand` directly, or whose key was removed); public
|
|
2874
|
+
* so a test harness — or a joiner flow that persists a formation-issued identity — can seat
|
|
2603
2875
|
* a specific key deliberately.
|
|
2604
2876
|
*
|
|
2605
2877
|
* @param strandId - The strand the key is this party's identity for.
|
|
@@ -2616,8 +2888,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2616
2888
|
* Resolve the party membership key a closed strand's launch threads into the founder
|
|
2617
2889
|
* bootstrap: the explicit attach-time key when given, else the party's persisted
|
|
2618
2890
|
* `StrandPartyKey` row, else — on the one machine the row names as founder (`FounderPeerId`)
|
|
2619
|
-
* — a freshly minted-and-persisted key, which seats the identity for a
|
|
2620
|
-
*
|
|
2891
|
+
* — a freshly minted-and-persisted key, which seats the identity for a closed row stored
|
|
2892
|
+
* without one (see {@link ensureStrandPartyKey}). Everyone else resolves undefined: non-founding
|
|
2621
2893
|
* machines never mint (no mint race between a party's machines), and only a founder
|
|
2622
2894
|
* bootstrap needs the key at all.
|
|
2623
2895
|
*
|
|
@@ -2642,8 +2914,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2642
2914
|
* Counterpart to {@link createOpenInvitation}, which only mints the
|
|
2643
2915
|
* out-of-band {@link OpenInvitation} envelope: persisting the matching
|
|
2644
2916
|
* `FormationInvite` row is what makes the token *redeemable* — the host's
|
|
2645
|
-
* {@link ControlFormationUsageRecorder}
|
|
2646
|
-
*
|
|
2917
|
+
* {@link ControlFormationUsageRecorder} reads it (`readToken`) to check a
|
|
2918
|
+
* redemption. A host minting a closed-strand invite does both (mint +
|
|
2647
2919
|
* publish), exactly as the integration harness's `createInvitation` does.
|
|
2648
2920
|
*
|
|
2649
2921
|
* Signs through the same owner signer as {@link publishStrand}, whose key must be an
|
|
@@ -2663,6 +2935,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2663
2935
|
validationUrl?: string;
|
|
2664
2936
|
strandId?: string;
|
|
2665
2937
|
}, sign?: OwnerSign): Promise<void>;
|
|
2938
|
+
/**
|
|
2939
|
+
* Register an invitation this node just published with the strand solicitation service. A
|
|
2940
|
+
* host may publish one whose token was minted elsewhere; registering it locally opens the
|
|
2941
|
+
* connection gate's formation exemption immediately rather than waiting for the durable row
|
|
2942
|
+
* to become readable. A token {@link createOpenInvitation} minted here is already registered,
|
|
2943
|
+
* and this records the same expiry again.
|
|
2944
|
+
*/
|
|
2945
|
+
private registerPublishedInvitation;
|
|
2666
2946
|
/**
|
|
2667
2947
|
* Enroll an approver public key allowed to sign off on `ValidationUrl` redemptions.
|
|
2668
2948
|
*
|
|
@@ -2744,7 +3024,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2744
3024
|
* fan-out, so a rediscovery costs nothing beyond the map lookup plus the (pure)
|
|
2745
3025
|
* founder derivation. NOT a silent no-op any more when the launch resolves as a
|
|
2746
3026
|
* FOUNDER: the tracked instance may have been launched first as a joiner (an app
|
|
2747
|
-
* attach, or this node's own watcher
|
|
3027
|
+
* attach, or this node's own watcher run winning the `resolveCohortSeed` window
|
|
2748
3028
|
* below), which used to drop the founder request and leave the strand headerless —
|
|
2749
3029
|
* now the tracked instance is founded in place
|
|
2750
3030
|
* ({@link StrandInstanceManager.foundExistingStrand}), waking it first if quiesced
|
|
@@ -2756,9 +3036,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2756
3036
|
*
|
|
2757
3037
|
* An absent `sAppConfig` launches a storage replica ({@link CadreNodeConfig.hostUnclaimedStrands}),
|
|
2758
3038
|
* which is ALWAYS a joiner whatever the row says: the founder bootstrap writes the sApp
|
|
2759
|
-
* into `Strand.Header`, and a replica has none. So a self-founded row whose watcher
|
|
3039
|
+
* into `Strand.Header`, and a replica has none. So a self-founded row whose watcher run
|
|
2760
3040
|
* wins the race against its app's `addStrand` after a restart comes up as a replica — and
|
|
2761
|
-
* so does a `foundStrand` whose publish a watcher
|
|
3041
|
+
* so does a `foundStrand` whose publish a watcher run saw before its attach. A claim
|
|
2762
3042
|
* that finds a tracked replica upgrades it in place, over the same node and store
|
|
2763
3043
|
* ({@link StrandInstanceManager.attachSApp}), BEFORE any founding: the founder bootstrap
|
|
2764
3044
|
* needs the sApp the attach supplies.
|
|
@@ -2887,7 +3167,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2887
3167
|
*
|
|
2888
3168
|
* Timestamps are recorded OPTIMISTICALLY, whatever `collectStrandAddrs` reports
|
|
2889
3169
|
* per peer: recording only on success would re-announce to an unreachable relay
|
|
2890
|
-
* on every reconcile
|
|
3170
|
+
* on every reconcile pass, one dial timeout each. A failed INITIAL announce costs
|
|
2891
3171
|
* the strand supervisor its first attempt only (the relay denies the
|
|
2892
3172
|
* reservation; every re-drive re-announces first through
|
|
2893
3173
|
* {@link announceDelegateToRelay}); a failed REFRESH retries within
|
|
@@ -2961,9 +3241,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2961
3241
|
*
|
|
2962
3242
|
* Due times are per (sibling, strand) and set from each sibling's own outcome, so
|
|
2963
3243
|
* a sibling that connects late — a phone joining after the party's always-on
|
|
2964
|
-
* machines — is asked on the
|
|
2965
|
-
* (its view of the membership may not include us yet) is retried
|
|
2966
|
-
* {@link STRAND_PEER_ADDR_RETRY_MS} rather than
|
|
3244
|
+
* machines — is asked on the pass its connection starts, and one that could not
|
|
3245
|
+
* answer or refused (its view of the membership may not include us yet) is retried
|
|
3246
|
+
* within {@link STRAND_PEER_ADDR_RETRY_MS} rather than
|
|
3247
|
+
* {@link STRAND_PEER_ADDR_REFRESH_MS}; the pass that stamped the retry arms a wake
|
|
3248
|
+
* for it ({@link armReconcileWake}).
|
|
2967
3249
|
*
|
|
2968
3250
|
* Distinct from {@link refreshDelegateGrants}, deliberately: that pass covers
|
|
2969
3251
|
* RELAYS on a `DELEGATE_GRANT_TTL_MS / 2` throttle to keep circuit-relay
|
|
@@ -2983,15 +3265,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2983
3265
|
*
|
|
2984
3266
|
* `connectedSiblingTargets`' membership read is unbounded, and with zero
|
|
2985
3267
|
* connections its answer is empty whatever the table holds, so decide it from the
|
|
2986
|
-
* connection list instead of paying for the read once a
|
|
3268
|
+
* connection list instead of paying for the read once a pass.
|
|
2987
3269
|
*
|
|
2988
|
-
* NOTE: otherwise the read runs on EVERY
|
|
3270
|
+
* NOTE: otherwise the read runs on EVERY pass with a running strand, not only when
|
|
2989
3271
|
* some (sibling, strand) is due: pruning a departed sibling needs the current
|
|
2990
3272
|
* target set, and whether anyone is due cannot be told without it (a connected
|
|
2991
|
-
* non-member never gets a due time to compare). One more `CadrePeer` read per
|
|
2992
|
-
*
|
|
2993
|
-
*
|
|
2994
|
-
*
|
|
3273
|
+
* non-member never gets a due time to compare). One more `CadrePeer` read per
|
|
3274
|
+
* pass, on top of the two `runReconcileControlCohort` already makes, all served
|
|
3275
|
+
* from the membership rows view: an in-memory scan while the view is current,
|
|
3276
|
+
* storage only after the table changed or the view's maximum age passed. Sharing
|
|
3277
|
+
* one row set across the pass would save no storage read and was declined (see
|
|
3278
|
+
* the NOTE there).
|
|
2995
3279
|
*
|
|
2996
3280
|
* NOTE: one strand-addr RPC per (running strand × due sibling) — each a tiny
|
|
2997
3281
|
* request/response on an already-open control connection. If a node ever runs
|
|
@@ -3036,8 +3320,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3036
3320
|
*
|
|
3037
3321
|
* NOTE: every pass serialises the whole FRET table (`exportTable()`), verifies one
|
|
3038
3322
|
* signature per record and makes one `peerStore.merge` per peer, per running strand
|
|
3039
|
-
* per
|
|
3040
|
-
* nothing in libp2p's persistent peer store. If many strands or large tables make it
|
|
3323
|
+
* per pass — every change-driven pass and the 2-minute backstop. Fine at strand table
|
|
3324
|
+
* sizes, and an unchanged address set writes nothing in libp2p's persistent peer store. If many strands or large tables make it
|
|
3041
3325
|
* show up, read only the entries whose address stamp is near the one-hour mark.
|
|
3042
3326
|
*
|
|
3043
3327
|
* @returns the ids of the peers the table holds a usable record for.
|
|
@@ -3194,8 +3478,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3194
3478
|
* The stop + emit are skipped when the strand manager holds no instance for `strandId` —
|
|
3195
3479
|
* e.g. a party owner that published a strand's row but never ran it locally, or an
|
|
3196
3480
|
* explicit {@link stopStrand} for an id this node never started. `hibernationManager`
|
|
3197
|
-
* untrack and the `sAppConfigs` / {@link strandLaunchRefusals} / {@link discoveredStrands}
|
|
3198
|
-
* deletes stay unconditional (all no-ops when there is nothing to remove), so a launch
|
|
3481
|
+
* untrack and the `sAppConfigs` / {@link strandLaunchRefusals} / {@link discoveredStrands} /
|
|
3482
|
+
* {@link formedPartyKeys} deletes stay unconditional (all no-ops when there is nothing to remove), so a launch
|
|
3199
3483
|
* that failed before an instance was ever tracked still gets its stray entries cleared.
|
|
3200
3484
|
*
|
|
3201
3485
|
* Dropping the {@link discoveredStrands} entry here covers both callers, and both
|
|
@@ -3386,18 +3670,26 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3386
3670
|
*/
|
|
3387
3671
|
getControlDatabase(): ControlDatabase | null;
|
|
3388
3672
|
/**
|
|
3389
|
-
* Open control-network connections right now
|
|
3390
|
-
*
|
|
3391
|
-
*
|
|
3392
|
-
*
|
|
3393
|
-
* It approximates the precise signal (the block's cluster size), and a caller that
|
|
3394
|
-
* samples it AFTER a write samples a slightly wider window than `committedAlone`
|
|
3395
|
-
* does inside the write itself. It exists so an embedder can warn an owner before a
|
|
3396
|
-
* control write that this machine currently sees none of its siblings, and report
|
|
3397
|
-
* after one that the write may not have travelled — a warning shown unconditionally
|
|
3398
|
-
* is a warning people learn to ignore.
|
|
3673
|
+
* Open control-network connections right now, of every kind: siblings, relays, bootstrap
|
|
3674
|
+
* nodes and peers still identifying. A report of connectivity, not of replication reach: a
|
|
3675
|
+
* control write can commit local-only with connections open, and whether one would is
|
|
3676
|
+
* {@link hasReachableControlSibling}.
|
|
3399
3677
|
*/
|
|
3400
3678
|
getControlConnectionCount(): number;
|
|
3679
|
+
/**
|
|
3680
|
+
* Whether a control write made now reaches another machine: a connected peer has identified
|
|
3681
|
+
* as serving the control database and is one this node sends control writes to (an
|
|
3682
|
+
* authorized member, or a device whose admission it is writing). Optimystic counts a peer
|
|
3683
|
+
* into a control block's cohort on those same terms; with none, a write commits on this node
|
|
3684
|
+
* alone and is queued for re-issue once one is reachable (docs/architecture.md → "Writes made
|
|
3685
|
+
* while alone").
|
|
3686
|
+
*
|
|
3687
|
+
* It exists so an embedder can warn an owner before a control write that this machine
|
|
3688
|
+
* currently reaches none of its siblings, and report after one that the write may not have
|
|
3689
|
+
* travelled; a warning shown unconditionally is a warning people learn to ignore. A caller
|
|
3690
|
+
* that samples it AFTER a write samples a slightly wider window than the write did.
|
|
3691
|
+
*/
|
|
3692
|
+
hasReachableControlSibling(): boolean;
|
|
3401
3693
|
/**
|
|
3402
3694
|
* Force a poll of the strand watcher (for testing)
|
|
3403
3695
|
*/
|
|
@@ -3593,7 +3885,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3593
3885
|
* builds its snapshot against that anchor, which is also what its previous snapshot was
|
|
3594
3886
|
* judged against. Caught here rather than failing the refresh, because the membership read
|
|
3595
3887
|
* tolerates an unreachable `Revocation` block on an isolated node
|
|
3596
|
-
* (`ControlDatabase.
|
|
3888
|
+
* (`ControlDatabase.readRevocationRowsOrNone`) and `queryRevocations` does not: failing the
|
|
3597
3889
|
* whole refresh on that would freeze such a node's gate snapshot.
|
|
3598
3890
|
* NOTE: on such a node the sync is skipped at every refresh until the block is held; if an
|
|
3599
3891
|
* isolated node ever has to derive owner additions while cut off, give `queryRevocations`
|
|
@@ -3602,9 +3894,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3602
3894
|
* NOTE: cost per membership refresh is one `OwnerKey` read and one `Revocation` read (the
|
|
3603
3895
|
* tombstone list, whose `OwnerKey` stamps are handed to `queryOwnerKeyRows` so it does not
|
|
3604
3896
|
* read the retired set again, and which the hang-up of removed peers shares through
|
|
3605
|
-
* `loadTombstones`), plus the owner-grant chain when some owner row is
|
|
3606
|
-
*
|
|
3607
|
-
*
|
|
3897
|
+
* `loadTombstones`), plus the owner-grant chain's two reads when some owner row is
|
|
3898
|
+
* grant-seated. All are served from the control database's membership views, so they reach
|
|
3899
|
+
* storage only after their table changed or the view's maximum age passed
|
|
3900
|
+
* (`control-founding-consult-budget.spec.ts` pins the counts). On a party founded before
|
|
3901
|
+
* genesis created every control collection, whose `Revocation` table has never been
|
|
3902
|
+
* written, each `Revocation` load is a cohort consult until the first tombstone.
|
|
3608
3903
|
*
|
|
3609
3904
|
* @param retry - forwarded to every read; `false` on the refresh path, for the reason on
|
|
3610
3905
|
* {@link refreshAuthorizedControlPeers}.
|
|
@@ -3636,15 +3931,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3636
3931
|
/**
|
|
3637
3932
|
* The usage and invitation rows {@link hasAnchoredProof} resolves an invitation-admitted
|
|
3638
3933
|
* `CadrePeer` row through, keyed for lookup. Both reads honour `retry` for
|
|
3639
|
-
* {@link listAuthorizedMembers}' reason.
|
|
3640
|
-
*
|
|
3641
|
-
* NOTE: once invitations are the only admission route (ticket
|
|
3642
|
-
* `cadre-invitations-redeemable-by-any-member`), every cadre with a member not yet
|
|
3643
|
-
* re-vouched pays these two whole-table reads and three ed25519 verifies per row on every
|
|
3644
|
-
* membership refresh. Both tables are append-only and a verified chain never changes (expiry,
|
|
3645
|
-
* use count and withdrawal are not re-checked), so if the gate's admission deadline ever
|
|
3646
|
-
* shows this, memoise verified (row stamp → usage stamp) pairs per node and skip the reads
|
|
3647
|
-
* when every admitted row is already memoised.
|
|
3934
|
+
* {@link listAuthorizedMembers}' reason. The rows come from the membership views, so they
|
|
3935
|
+
* reach storage only after the table changed or the view's maximum age passed.
|
|
3648
3936
|
*/
|
|
3649
3937
|
private loadInvitationChain;
|
|
3650
3938
|
/**
|
|
@@ -3725,7 +4013,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3725
4013
|
* write-driven refresh never fires for it — yet applying it can ANCHOR a new
|
|
3726
4014
|
* owner key, which flips rows already present from unauthorized to authorized.
|
|
3727
4015
|
* Hence the explicit refresh here; without it a peer the freshly anchored owner
|
|
3728
|
-
* vouched for stays denied until the next
|
|
4016
|
+
* vouched for stays denied until the next membership change or the backstop pass.
|
|
3729
4017
|
*/
|
|
3730
4018
|
private seedEventCallbacks;
|
|
3731
4019
|
/**
|
|
@@ -3744,13 +4032,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3744
4032
|
* NO `CadrePeer` writer needs to call this: the control database notifies it
|
|
3745
4033
|
* after every committed member-row write (`ControlDatabase.mutateCadrePeer`,
|
|
3746
4034
|
* wired in {@link start}), which is exactly what makes the refresh automatic
|
|
3747
|
-
* rather than a caller obligation.
|
|
3748
|
-
*
|
|
3749
|
-
*
|
|
3750
|
-
*
|
|
3751
|
-
*
|
|
3752
|
-
*
|
|
3753
|
-
*
|
|
4035
|
+
* rather than a caller obligation. A membership row that arrived by REPLICATION
|
|
4036
|
+
* refreshes it too, through the gate watch {@link start} takes on the membership
|
|
4037
|
+
* tables (within its 50 ms window; a change this node's storage never hears of
|
|
4038
|
+
* waits for the reconcile pass's backstop). It stays public for the one change
|
|
4039
|
+
* that writes no row and raises no table event: a newly anchored trusted owner
|
|
4040
|
+
* key ({@link applySeed}), which flips rows ALREADY present from unauthorized to
|
|
4041
|
+
* authorized without touching them. A caller may also await it to observe a
|
|
4042
|
+
* refresh that began after its own change.
|
|
3754
4043
|
*
|
|
3755
4044
|
* Coalescing: marks the snapshot stale and resolves once a refresh that began
|
|
3756
4045
|
* after this call has completed, so a caller that awaits it always observes
|
|
@@ -3763,13 +4052,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3763
4052
|
* connected peer it newly covers — the strand-side counterpart to
|
|
3764
4053
|
* {@link refreshMembershipGate}.
|
|
3765
4054
|
*
|
|
3766
|
-
*
|
|
3767
|
-
*
|
|
3768
|
-
*
|
|
3769
|
-
*
|
|
3770
|
-
* The gate therefore polls (default 30 s). An app that has just called
|
|
3771
|
-
* `revokeMember` or `leaveStrand` should follow the write with this call so
|
|
3772
|
-
* the cut is immediate rather than up to one poll interval late.
|
|
4055
|
+
* The gate already refreshes on its own when the strand's `Member` or
|
|
4056
|
+
* `MemberPeer` table changes: at once for a `revokeMember` or `leaveStrand`
|
|
4057
|
+
* written on this machine, and when a replicated removal reaches this node's
|
|
4058
|
+
* storage. Call this to await the cut — it resolves after the sweep.
|
|
3773
4059
|
*
|
|
3774
4060
|
* Quiet no-op for a strand that is not running, is quiesced, is open, or has
|
|
3775
4061
|
* the gate disabled. Never rejects; resolves once the sweep has finished.
|
|
@@ -3799,8 +4085,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3799
4085
|
* CONCURRENT external write's refresh for the life of the scope: that writer's
|
|
3800
4086
|
* promise resolves before its peer is admitted, and admission lands at scope
|
|
3801
4087
|
* exit instead. Acceptable — scope exit is milliseconds away (bounded by the
|
|
3802
|
-
* drain), versus the
|
|
3803
|
-
*
|
|
4088
|
+
* drain), versus the reconcile pass that was the alternative before any of this
|
|
4089
|
+
* existed — but it is the reason these scopes stay short and rare.
|
|
3804
4090
|
*/
|
|
3805
4091
|
private deferMembershipGateRefresh;
|
|
3806
4092
|
/**
|
|
@@ -3892,7 +4178,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3892
4178
|
* `'self'` or `'alone'`, before any write.
|
|
3893
4179
|
*/
|
|
3894
4180
|
removeOwner(ownerPublicKeyB64: string, options?: RemoveOwnerOptions): Promise<OwnerKeyRemovalResult>;
|
|
3895
|
-
/** {@link planOwnerKeyRemoval}
|
|
4181
|
+
/** {@link planOwnerKeyRemoval} over `controlDatabase`, for a caller that already holds it. */
|
|
3896
4182
|
private planOwnerKeyRemovalWith;
|
|
3897
4183
|
/**
|
|
3898
4184
|
* The re-vouch step of {@link removeOwner}: every chosen dependent row is rebound to this
|
|
@@ -3901,12 +4187,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3901
4187
|
* the only rebinding `OwnerKey.NoUpdate` allows); peers through the owner re-touch
|
|
3902
4188
|
* (`reauthorizePeer`), which rewrites `VouchOwner`/`VouchSig` and clears `VouchUsage`.
|
|
3903
4189
|
*
|
|
3904
|
-
* The peer re-touch
|
|
3905
|
-
* `
|
|
3906
|
-
*
|
|
3907
|
-
*
|
|
3908
|
-
*
|
|
3909
|
-
*
|
|
4190
|
+
* The peer re-touch is the re-vouch mode of `reauthorizePeer`, which leaves the row's
|
|
4191
|
+
* `UpdatedAt` untouched and writes whether or not the machine has self-published. The owner
|
|
4192
|
+
* branch of `CadrePeer.AuthorizedUpdate` does not require a bump, and bumping a self-published
|
|
4193
|
+
* row would leave its `Sig` unverifiable, so unresolvable and unlisted as self-published, until
|
|
4194
|
+
* the machine's next heartbeat (the NOTE on that constraint); the write-while-alone drain's
|
|
4195
|
+
* re-broadcast mode bumps because it re-issues an otherwise unchanged row, whereas here the
|
|
4196
|
+
* voucher itself changes. A dependent whose row vanished since the plan is reported absent and
|
|
4197
|
+
* skipped.
|
|
3910
4198
|
*
|
|
3911
4199
|
* A failure rethrows and stops the call before the removal; re-vouches already made stay
|
|
3912
4200
|
* (the row is only re-signed by a current owner) and the caller retries. An owner re-vouch
|
|
@@ -4022,12 +4310,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4022
4310
|
* that connection from the addresses it was handed, on this launch or a later
|
|
4023
4311
|
* one.
|
|
4024
4312
|
*
|
|
4025
|
-
* Nothing is dialed here: the drone has not received the seed yet.
|
|
4026
|
-
*
|
|
4027
|
-
*
|
|
4028
|
-
*
|
|
4029
|
-
*
|
|
4030
|
-
*
|
|
4313
|
+
* Nothing is dialed here: the drone has not received the seed yet. Its `CadrePeer`
|
|
4314
|
+
* insert is a membership change, so a reconcile pass dials the retained addresses
|
|
4315
|
+
* within about 50 ms of this call returning, whether or not the caller runs one, and
|
|
4316
|
+
* a failed dial is retried on the redial ladder (15 s, 30 s, 60 s, then every
|
|
4317
|
+
* backstop). A caller that wants to await the connection calls
|
|
4318
|
+
* {@link reconcileControlCohort} itself, twice in a row: a call made while a pass is
|
|
4319
|
+
* in flight joins it, and that pass may have listed siblings before this add.
|
|
4031
4320
|
*/
|
|
4032
4321
|
addDrone(options: AddDroneOptions): Promise<DroneInitResult>;
|
|
4033
4322
|
/**
|
|
@@ -4060,12 +4349,19 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4060
4349
|
* by the node (its anchored branch), the row insert is a no-op on an existing row
|
|
4061
4350
|
* (`ControlDatabase.insertCadrePeer`), and the dial target is replaced, not duplicated.
|
|
4062
4351
|
*
|
|
4063
|
-
*
|
|
4064
|
-
*
|
|
4065
|
-
*
|
|
4066
|
-
*
|
|
4067
|
-
*
|
|
4068
|
-
*
|
|
4352
|
+
* Once it has acknowledged, the node restarts into this party (cadre-cli's
|
|
4353
|
+
* `restartIntoClaimedParty`), closing the connection the claim used. The dial target puts
|
|
4354
|
+
* the address that connection was dialed on first, so every later dial tries the address
|
|
4355
|
+
* that just worked before the others (a QR code lists public addresses before LAN ones, and
|
|
4356
|
+
* a home router often drops a connection to its own public address from inside). A
|
|
4357
|
+
* reconcile pass is kicked (not awaited) now, and the vouch's `CadrePeer` insert starts
|
|
4358
|
+
* another about 50 ms later; for the first {@link CONTROL_COHORT_REDIAL_MS} after the
|
|
4359
|
+
* acknowledgement this node also retries the node every second while it restarts
|
|
4360
|
+
* ({@link redialClaimedNode}). A caller that wants to wait for the connection awaits
|
|
4361
|
+
* {@link reconcileControlCohort} itself, with `addDrone`'s caveat that a pass already in
|
|
4362
|
+
* flight is joined rather than restarted. The claimed node dials this one back too, as the
|
|
4363
|
+
* seed's minter, when this node has a listen or relay address; a phone with neither is
|
|
4364
|
+
* reached by its own dials alone.
|
|
4069
4365
|
*
|
|
4070
4366
|
* @throws {ClaimRefusedError} when the node was reached and refused the claim.
|
|
4071
4367
|
* @throws {PeerUnreachableError} when the node was never reached.
|
|
@@ -4075,6 +4371,33 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4075
4371
|
multiaddrs: string[];
|
|
4076
4372
|
secret: string;
|
|
4077
4373
|
}): Promise<void>;
|
|
4374
|
+
/**
|
|
4375
|
+
* `addrs` with the address this node's open connection to `peerId` was dialed on moved to
|
|
4376
|
+
* the front, when it is one of them; `addrs` unchanged otherwise. Compared without a
|
|
4377
|
+
* trailing `/p2p/<peerId>`, which a QR code's address may or may not carry ({@link addrKey}).
|
|
4378
|
+
*/
|
|
4379
|
+
private connectedAddrFirst;
|
|
4380
|
+
/**
|
|
4381
|
+
* Retry a node this node has just claimed ({@link claimNode}) once every
|
|
4382
|
+
* {@link CLAIM_RESTART_REDIAL_STEP_MS} for {@link CONTROL_COHORT_REDIAL_MS} after it accepted.
|
|
4383
|
+
* The node restarts into this party right after it acknowledges, and while it does, a
|
|
4384
|
+
* reconcile pass's dial either fails or opens a connection the restarting node closes at
|
|
4385
|
+
* once. The first failure puts the pass's next redial 15 s out ({@link armReconcileWake}),
|
|
4386
|
+
* and an owner that cannot be dialed has no other way back to the node.
|
|
4387
|
+
*
|
|
4388
|
+
* Each step dials once from the retained dial target, unless a connection to the node is
|
|
4389
|
+
* open: the one the claim used until the restart closes it, or one to the restarted node
|
|
4390
|
+
* that has not identified yet. A dial that connects does not end the loop, because the
|
|
4391
|
+
* restarting node may still close it. The loop ends once the node has identified as serving
|
|
4392
|
+
* this party's control database ({@link identifiedControlPeers}, which the placeholder node
|
|
4393
|
+
* it ran before the claim never joins), at the window's end, when this node stops, or when
|
|
4394
|
+
* the target is forgotten ({@link removePeer}). Steps run one after another, so a dial
|
|
4395
|
+
* finishes on its own budget first. The redial streak is left alone: the ladder carries on
|
|
4396
|
+
* after the window. One loop per peer: a repeated claim while one runs starts none.
|
|
4397
|
+
*/
|
|
4398
|
+
private redialClaimedNode;
|
|
4399
|
+
/** One step of {@link redialClaimedNode}; false ends the loop. */
|
|
4400
|
+
private redialClaimedNodeStep;
|
|
4078
4401
|
/**
|
|
4079
4402
|
* Add a phone to the cadre with relay support.
|
|
4080
4403
|
* Use when both nodes are NAT'd (phone-to-phone).
|
|
@@ -4162,11 +4485,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4162
4485
|
* Steps: refuse an invitation for another party; pin its `ownerKeys` into the node-local
|
|
4163
4486
|
* anchor (`'invite'` provenance — the pin sticks even if the redemption then fails, as it
|
|
4164
4487
|
* does for a seed invite, so a later attempt or seed from the same owner is anchored);
|
|
4165
|
-
* sign the request
|
|
4166
|
-
* acceptance verify the reply against the pinned
|
|
4167
|
-
* store, retain the answering member as a
|
|
4168
|
-
*
|
|
4169
|
-
* the connection it already holds, the same unified behaviour as after a seed.
|
|
4488
|
+
* sign the request, which carries this node's own signed address record; dial the listed
|
|
4489
|
+
* members in order (`redeemAtMembers`); on acceptance verify the reply against the pinned
|
|
4490
|
+
* keys, merge its dial hints into the peer store, retain the answering member as a
|
|
4491
|
+
* cold-start dial target and start a cohort reconcile pass. This node then syncs the control
|
|
4492
|
+
* database over the connection it already holds, the same unified behaviour as after a seed.
|
|
4493
|
+
* The member writes the record on this node's `CadrePeer` row when its signature verifies, so
|
|
4494
|
+
* the row resolves to this node's addresses from its admission.
|
|
4170
4495
|
*
|
|
4171
4496
|
* A device that is already a member (a restart with the invitation still configured) is
|
|
4172
4497
|
* answered as accepted by the member's idempotent redemption, with no second usage row.
|
|
@@ -4304,9 +4629,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4304
4629
|
*/
|
|
4305
4630
|
redeemOwnerGrant(bundle: OwnerGrantBundle, signer: OwnerSigner, options?: RedeemOwnerGrantOptions): Promise<RedeemOwnerGrantResult>;
|
|
4306
4631
|
/**
|
|
4307
|
-
* {@link redeemOwnerGrant}'s local path: the alone refusal, the seat
|
|
4308
|
-
* then one approval around the consent signature and the
|
|
4309
|
-
* the approval, so a prompting signer never asks for a
|
|
4632
|
+
* {@link redeemOwnerGrant}'s local path: the alone refusal, the seat (only when this node holds
|
|
4633
|
+
* no grant row), the liveness refusal, then one approval around the consent signature and the
|
|
4634
|
+
* write. Every refusal comes before the approval, so a prompting signer never asks for a
|
|
4635
|
+
* redemption this node would refuse. The held row is read once and carried into the liveness
|
|
4636
|
+
* check and the write, as the member handler carries it (`MemberRedemptionHandler`).
|
|
4310
4637
|
*/
|
|
4311
4638
|
private redeemOwnerGrantLocally;
|
|
4312
4639
|
/**
|
|
@@ -4553,6 +4880,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4553
4880
|
* implements): mint a single-use `Strand.Invite` against the LIVE host strand so a
|
|
4554
4881
|
* validated joiner can seat its own `Strand.Member` row.
|
|
4555
4882
|
*
|
|
4883
|
+
* `host` is the strand as the formation session resolved it from its `Strand` row, read
|
|
4884
|
+
* once there; this method reads no `Strand` row of its own. A row removed in between is
|
|
4885
|
+
* refused when the consent row is written (the `StrandExists` CHECK).
|
|
4886
|
+
*
|
|
4556
4887
|
* - Open host strand → `null` (no members, nothing to invite into).
|
|
4557
4888
|
* - Closed host strand whose founder launch was refused as pre-split
|
|
4558
4889
|
* ({@link strandLaunchRefusals}) → rethrow the recorded `PreSplitStrandIdentityError`;
|