@serfab/cadre-core 1.16.0 → 1.18.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 +4 -3
- package/dist/bootstrap-peer-store-file.js +1 -1
- package/dist/bootstrap-peer-store.d.ts +1 -1
- package/dist/bootstrap-peer-store.js +4 -4
- package/dist/cadre-invite-protocol.d.ts +81 -142
- package/dist/cadre-invite-protocol.js +168 -458
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-join-request.d.ts +15 -2
- package/dist/cadre-join-request.js +22 -3
- package/dist/cadre-join-request.js.map +1 -1
- package/dist/cadre-node.d.ts +1089 -475
- package/dist/cadre-node.js +2170 -1037
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +36 -14
- package/dist/control-authorization.js +20 -3
- package/dist/control-authorization.js.map +1 -1
- package/dist/control-cohort.d.ts +88 -32
- package/dist/control-cohort.js +91 -33
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +856 -318
- package/dist/control-database.js +1422 -701
- 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 +11 -4
- package/dist/control-protocol-guard.js +20 -5
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +97 -0
- package/dist/control-schema-version.js +225 -0
- package/dist/control-schema-version.js.map +1 -0
- package/dist/control-schema.d.ts +45 -5
- package/dist/control-schema.js +262 -78
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +45 -10
- package/dist/control-write-retry.js +92 -22
- package/dist/control-write-retry.js.map +1 -1
- package/dist/delegate-admission.d.ts +13 -0
- package/dist/delegate-admission.js +27 -0
- package/dist/delegate-admission.js.map +1 -1
- package/dist/ed25519-key.d.ts +10 -7
- package/dist/ed25519-key.js +23 -20
- package/dist/ed25519-key.js.map +1 -1
- package/dist/edge-node/config.d.ts +122 -0
- package/dist/edge-node/config.js +199 -0
- package/dist/edge-node/config.js.map +1 -0
- package/dist/edge-node/dissolve.d.ts +95 -0
- package/dist/edge-node/dissolve.js +184 -0
- package/dist/edge-node/dissolve.js.map +1 -0
- package/dist/edge-node/edge-join.d.ts +44 -0
- package/dist/edge-node/edge-join.js +227 -0
- package/dist/edge-node/edge-join.js.map +1 -0
- package/dist/edge-node/edge-node.d.ts +295 -0
- package/dist/edge-node/edge-node.js +704 -0
- package/dist/edge-node/edge-node.js.map +1 -0
- package/dist/edge-node/index.d.ts +18 -0
- package/dist/edge-node/index.js +18 -0
- package/dist/edge-node/index.js.map +1 -0
- package/dist/edge-node/join-credential.d.ts +11 -0
- package/dist/edge-node/join-credential.js +11 -0
- package/dist/edge-node/join-credential.js.map +1 -0
- package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
- package/dist/edge-node/lazy-raw-storage.js +134 -0
- package/dist/edge-node/lazy-raw-storage.js.map +1 -0
- package/dist/edge-node/node-local.d.ts +60 -0
- package/dist/edge-node/node-local.js +58 -0
- package/dist/edge-node/node-local.js.map +1 -0
- package/dist/edge-node/options.d.ts +83 -0
- package/dist/edge-node/options.js +110 -0
- package/dist/edge-node/options.js.map +1 -0
- package/dist/edge-node/owner-grant.d.ts +41 -0
- package/dist/edge-node/owner-grant.js +38 -0
- package/dist/edge-node/owner-grant.js.map +1 -0
- package/dist/edge-node/platform.d.ts +140 -0
- package/dist/edge-node/platform.js +12 -0
- package/dist/edge-node/platform.js.map +1 -0
- package/dist/edge-node/strand-move.d.ts +115 -0
- package/dist/edge-node/strand-move.js +223 -0
- package/dist/edge-node/strand-move.js.map +1 -0
- package/dist/edge-node/strands.d.ts +28 -0
- package/dist/edge-node/strands.js +60 -0
- package/dist/edge-node/strands.js.map +1 -0
- 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 +27 -17
- package/dist/index.js +39 -17
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +29 -12
- package/dist/joined-strand-store.js +66 -27
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/key-store.d.ts +9 -4
- package/dist/key-store.js +9 -4
- package/dist/key-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/machine-removal.d.ts +19 -39
- package/dist/machine-removal.js +24 -98
- package/dist/machine-removal.js.map +1 -1
- package/dist/member-redemption.d.ts +256 -0
- package/dist/member-redemption.js +490 -0
- package/dist/member-redemption.js.map +1 -0
- 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-anchor-sync.d.ts +4 -4
- package/dist/owner-anchor-sync.js +16 -15
- package/dist/owner-anchor-sync.js.map +1 -1
- package/dist/owner-grant-protocol.d.ts +251 -0
- package/dist/owner-grant-protocol.js +344 -0
- package/dist/owner-grant-protocol.js.map +1 -0
- package/dist/owner-key-export.d.ts +70 -0
- package/dist/owner-key-export.js +279 -0
- package/dist/owner-key-export.js.map +1 -0
- package/dist/owner-key-flows.d.ts +55 -0
- package/dist/owner-key-flows.js +60 -0
- package/dist/owner-key-flows.js.map +1 -0
- package/dist/owner-key-store.d.ts +31 -0
- package/dist/owner-key-store.js +81 -0
- package/dist/owner-key-store.js.map +1 -0
- package/dist/owner-keys.d.ts +78 -0
- package/dist/owner-keys.js +133 -0
- package/dist/owner-keys.js.map +1 -0
- package/dist/owner-signer.d.ts +54 -0
- package/dist/owner-signer.js +51 -0
- package/dist/owner-signer.js.map +1 -0
- 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-authorization.d.ts +83 -18
- package/dist/peer-authorization.js +123 -53
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +89 -20
- package/dist/peer-join-backfill.js +265 -108
- 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/provider/rent-node.js +1 -1
- package/dist/provider/rent-node.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +162 -133
- package/dist/seed-bootstrap.js +300 -283
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.js +4 -4
- package/dist/strand-addr-protocol.d.ts +4 -1
- package/dist/strand-addr-protocol.js +8 -2
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-cohort.d.ts +3 -0
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +24 -2
- package/dist/strand-database.js +29 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -88
- package/dist/strand-first-sync-gate.js +288 -121
- 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 +27 -18
- 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 +30 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +45 -22
- package/dist/strand-membership-writer.js +226 -131
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.d.ts +6 -5
- package/dist/strand-move.js +36 -32
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-peer-redial.js +2 -3
- package/dist/strand-peer-redial.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 +374 -150
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +17 -8
- package/src/bootstrap-peer-store-file.ts +44 -44
- package/src/bootstrap-peer-store.ts +214 -214
- package/src/cadre-invite-protocol.ts +740 -1052
- package/src/cadre-join-request.ts +24 -3
- package/src/cadre-node.ts +2402 -1079
- package/src/control-authorization.ts +47 -14
- package/src/control-cohort.ts +204 -128
- package/src/control-database.ts +1749 -820
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +23 -6
- package/src/control-schema-version.ts +301 -0
- package/src/control-schema.ts +273 -78
- package/src/control-write-retry.ts +641 -567
- package/src/delegate-admission.ts +351 -314
- package/src/ed25519-key.ts +174 -171
- package/src/edge-node/config.ts +277 -0
- package/src/edge-node/dissolve.ts +251 -0
- package/src/edge-node/edge-join.ts +319 -0
- package/src/edge-node/edge-node.ts +1045 -0
- package/src/edge-node/index.ts +59 -0
- package/src/edge-node/join-credential.ts +16 -0
- package/src/edge-node/lazy-raw-storage.ts +178 -0
- package/src/edge-node/node-local.ts +84 -0
- package/src/edge-node/options.ts +179 -0
- package/src/edge-node/owner-grant.ts +63 -0
- package/src/edge-node/platform.ts +151 -0
- package/src/edge-node/strand-move.ts +270 -0
- package/src/edge-node/strands.ts +72 -0
- package/src/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +899 -789
- package/src/joined-strand-store.ts +73 -28
- package/src/key-store.ts +10 -4
- package/src/link-budget.ts +9 -7
- package/src/machine-removal.ts +26 -111
- package/src/member-redemption.ts +649 -0
- package/src/membership-view.ts +144 -0
- package/src/owner-anchor-sync.ts +19 -18
- package/src/owner-grant-protocol.ts +547 -0
- package/src/owner-key-export.ts +348 -0
- package/src/owner-key-flows.ts +109 -0
- package/src/owner-key-store.ts +85 -0
- package/src/owner-keys.ts +178 -0
- package/src/owner-signer.ts +94 -0
- package/src/peer-addr-book.ts +4 -3
- package/src/peer-authorization.ts +558 -488
- package/src/peer-join-backfill.ts +971 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/provider/rent-node.ts +1 -1
- package/src/seed-bootstrap.ts +343 -349
- package/src/seed-trust-policy.ts +4 -4
- package/src/strand-addr-protocol.ts +547 -538
- package/src/strand-cohort.ts +4 -0
- package/src/strand-database.ts +352 -319
- package/src/strand-first-sync-gate.ts +334 -133
- 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 +33 -25
- package/src/strand-membership-writer.ts +2093 -1974
- package/src/strand-move.ts +50 -38
- package/src/strand-peer-redial.ts +2 -3
- 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 +391 -152
package/dist/cadre-node.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { Libp2p, PeerId } from '@libp2p/interface';
|
|
2
2
|
import type { Multiaddr } from '@multiformats/multiaddr';
|
|
3
|
-
import type { CadreNodeConfig, StrandInstance, StrandRow, StrandConfig, FoundStrandConfig, FoundStrandResult, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, SeedAckMessage, SeedDeliveryTarget, AddDroneOptions, DroneInitResult, OpenInvitation, FormStrandResult, StrandFormationDisclosure, StrandMembershipInvite, ResolveOpts, SelfRegistrationOutcome, ServiceWakeResult, PushPlatform, DeviceTokenRecord, CadreInviteStatus, CadreMachine, MachineRemovalPlan, MachineRemovalResult,
|
|
4
|
-
import { type Ed25519KeyPair } from './ed25519-key.js';
|
|
3
|
+
import type { CadreNodeConfig, StrandInstance, StrandRow, StrandConfig, FoundStrandConfig, FoundStrandResult, FoundStrandOptions, FoundStrandBackgroundResult, SAppConfig, CadreNodeEvents, ControlNetworkSeed, ApplySeedResult, SeedAckMessage, SeedDeliveryTarget, AddDroneOptions, DroneInitResult, OpenInvitation, FormStrandResult, StrandFormationDisclosure, StrandMembershipInvite, ResolveOpts, SelfRegistrationOutcome, ServiceWakeResult, PushPlatform, DeviceTokenRecord, CadreInviteStatus, CadreMachine, CadreOwnerKey, MachineRemovalPlan, MachineRemovalResult, OwnerKeyRemovalPlan, OwnerKeyRemovalResult, RemoveOwnerOptions, CadreRemoval, CadreInvitationReach, CreateCadreInvitationOptions, CreateCadreInvitationResult, RedeemCadreInvitationOptions, RedeemCadreInvitationResult, CreateOwnerGrantOptions, CreateOwnerGrantResult, OwnerGrantStatus, RedeemOwnerGrantOptions, RedeemOwnerGrantResult, JoinWithOwnerGrantResult, ResolveDeviceTokenOpts, PendingJoinStatus } from './types.js';
|
|
5
4
|
import { type TrustedOwnerStore, type TrustSource } from './trusted-owner-store.js';
|
|
5
|
+
import { type OwnerSign, type OwnerSigner } from './owner-signer.js';
|
|
6
6
|
import { type BootstrapPeerStore } from './bootstrap-peer-store.js';
|
|
7
7
|
import { type StrandNetworkStateStore } from './strand-network-state.js';
|
|
8
8
|
import { type EnrolledMachineStore } from './enrolled-machine-store.js';
|
|
9
9
|
import { type JoinedStrandRecord } from './joined-strand-store.js';
|
|
10
10
|
import { type SAppIdLookup } from './strand-watcher.js';
|
|
11
|
+
import type { CohortPeerRow } from './strand-cohort.js';
|
|
11
12
|
import { type ControlCohortReconcileResult } from './control-cohort.js';
|
|
12
13
|
import { EnrollmentService } from './enrollment.js';
|
|
13
14
|
import { ControlDatabase } from './control-database.js';
|
|
14
15
|
import { type CadreInvitation } from './cadre-invite-protocol.js';
|
|
16
|
+
import { type OwnerGrantBundle } from './owner-grant-protocol.js';
|
|
15
17
|
import { SeedBootstrapService } from './seed-bootstrap.js';
|
|
16
18
|
import { type SeedTrustPolicy } from './seed-trust-policy.js';
|
|
17
19
|
import { StrandSolicitationService, type StrandSolicitationServiceOptions } from './strand-solicitation.js';
|
|
@@ -26,8 +28,8 @@ import { type ConnectionPathSummary } from './diagnostics/connection-path.js';
|
|
|
26
28
|
*
|
|
27
29
|
* Ten minutes sits comfortably inside the peerStore's one-hour address expiry
|
|
28
30
|
* (`MAX_ADDRESS_AGE`, see `peer-addr-book.ts`) with headroom for several missed
|
|
29
|
-
* passes, and far above the
|
|
30
|
-
* 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.
|
|
31
33
|
*/
|
|
32
34
|
export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
|
|
33
35
|
/**
|
|
@@ -36,18 +38,19 @@ export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
|
|
|
36
38
|
* phone asking before its `CadrePeer` row has replicated to the sibling is refused
|
|
37
39
|
* and its delegate grant goes unrecorded, and waiting the full
|
|
38
40
|
* {@link STRAND_PEER_ADDR_REFRESH_MS} for the next try is the delay this bounds.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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.
|
|
41
44
|
*/
|
|
42
45
|
export declare const STRAND_PEER_ADDR_RETRY_MS: number;
|
|
43
46
|
/**
|
|
44
|
-
* Default lifetime of an
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
47
|
+
* Default lifetime of an owner grant ({@link CadreNode.createOwnerGrant}): whoever holds the
|
|
48
|
+
* bundle can become an owner, so it is a bearer credential for admin rights and lives only
|
|
49
|
+
* long enough to be pasted or scanned by the device it was minted for. A backup grant is minted
|
|
50
|
+
* with no expiry, by explicit choice.
|
|
48
51
|
*/
|
|
49
|
-
export declare const
|
|
50
|
-
/** Default lifetime of
|
|
52
|
+
export declare const OWNER_GRANT_DEFAULT_TTL_MS: number;
|
|
53
|
+
/** Default lifetime of a cadre invitation ({@link CadreNode.createCadreInvitation}): it admits a machine and grants nothing else, so it need not be short. */
|
|
51
54
|
export declare const CADRE_INVITATION_DEFAULT_TTL_MS: number;
|
|
52
55
|
type EventHandler<T> = (data: T) => void;
|
|
53
56
|
/**
|
|
@@ -134,6 +137,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
134
137
|
* once that database is up, on every node.
|
|
135
138
|
*/
|
|
136
139
|
private cadreInviteHandler;
|
|
140
|
+
/**
|
|
141
|
+
* Owner grant redemption handler (`/sereus/owner-grant/1.0.0`): a holder of a grant dials
|
|
142
|
+
* this machine and proves possession of the grant and of the key it seats; the handler seats
|
|
143
|
+
* the row and writes the `OwnerKey` row against this node's control database. Registered by
|
|
144
|
+
* {@link start} beside the invitation handler, on every node.
|
|
145
|
+
*/
|
|
146
|
+
private ownerGrantHandler;
|
|
137
147
|
/**
|
|
138
148
|
* Server-side push-wake fan-out. Constructed by {@link start} only when
|
|
139
149
|
* `config.push` (an injected `PushNotifier` + policy) is present — without it
|
|
@@ -231,8 +241,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
231
241
|
*
|
|
232
242
|
* Cleared by {@link cleanup} only, deliberately NOT by {@link detachStrand}: the watcher's
|
|
233
243
|
* filter rejection stops a replica through that very method, and forgetting the id there
|
|
234
|
-
* would have the next
|
|
235
|
-
* 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,
|
|
236
246
|
* and the map is bounded by the strands this party has published, like
|
|
237
247
|
* {@link discoveredStrands}.
|
|
238
248
|
*/
|
|
@@ -260,19 +270,24 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
260
270
|
* wait that ends in mutual denial (the upstream gate is fail-closed on
|
|
261
271
|
* timeout, unlike the fail-open connection gater). Optimystic's predicate is
|
|
262
272
|
* synchronous by contract. So the set is refreshed OUT OF BAND instead — after
|
|
263
|
-
* start,
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
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
|
|
267
280
|
* committed `CadrePeer` write (see `ControlDatabase.mutateCadrePeer`), so no
|
|
268
|
-
* writer has to remember. A change
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
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.
|
|
276
291
|
*/
|
|
277
292
|
private authorizedControlPeers;
|
|
278
293
|
/**
|
|
@@ -330,22 +345,42 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
330
345
|
* When this node last ANNOUNCED a delegate, keyed `<targetPeerId>\n<strandId>`
|
|
331
346
|
* (the peer announced TO, not the delegate). Throttles
|
|
332
347
|
* {@link refreshDelegateGrants} to once per `DELEGATE_GRANT_TTL_MS / 2` per
|
|
333
|
-
* (relay, strand) so
|
|
334
|
-
* chatter; the launch/resume announce passes record here too.
|
|
335
|
-
* 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.
|
|
336
351
|
*/
|
|
337
352
|
private readonly delegateAnnounceAt;
|
|
353
|
+
/**
|
|
354
|
+
* Relay peerIds whose protocol negotiation refused the delegate announcement
|
|
355
|
+
* (strand-addr outcome `unsupported`), recorded and cleared by
|
|
356
|
+
* {@link recordDelegateAnnounces} from every announce pass's outcomes. Only a
|
|
357
|
+
* party-run relay has a membership gate that needs a grant, and it serves
|
|
358
|
+
* strand-addr, so a refusal marks a dedicated relay: {@link circuitRelayTargets}
|
|
359
|
+
* leaves it out of the founding/resume and periodic announcements, which would
|
|
360
|
+
* otherwise spend a protocol negotiation on it every time (gotchoices/sereus#49). Not
|
|
361
|
+
* pruned: one entry per relay this node ever named.
|
|
362
|
+
*
|
|
363
|
+
* {@link announceDelegateToRelay} still asks every relay, remembered or not, and
|
|
364
|
+
* its reply is what clears an entry. A party-run relay registers its strand-addr
|
|
365
|
+
* handler late in its own `start()`, after its libp2p node and relay server are
|
|
366
|
+
* up, so an announcement landing in that window is refused by a relay that serves
|
|
367
|
+
* the protocol a moment later. If the re-drive hook skipped remembered relays too,
|
|
368
|
+
* that relay would stay skipped until this node restarts and its gate would deny
|
|
369
|
+
* every reservation through it.
|
|
370
|
+
*/
|
|
371
|
+
private readonly relaysRefusingStrandAddr;
|
|
338
372
|
/**
|
|
339
373
|
* When each connected sibling is next due a strand-addr RPC for each running
|
|
340
374
|
* strand, keyed `peerStrandKey(siblingControlPeerId, strandId)` → epoch ms; a
|
|
341
375
|
* missing key is due. Bounds {@link refreshStrandPeerAddrs}'s fan-out per
|
|
342
376
|
* (sibling, strand) rather than per strand, so a sibling that connects after a
|
|
343
|
-
* pass is asked on the
|
|
344
|
-
* sibling's answer set. An answer (even an empty one) makes the sibling
|
|
345
|
-
* in {@link STRAND_PEER_ADDR_REFRESH_MS}; no answer, in
|
|
346
|
-
* {@link STRAND_PEER_ADDR_RETRY_MS}
|
|
347
|
-
*
|
|
348
|
-
*
|
|
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.
|
|
349
384
|
*/
|
|
350
385
|
private readonly strandAddrAskDueAt;
|
|
351
386
|
/**
|
|
@@ -427,6 +462,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
427
462
|
* `unpublishStrand` / `forgetJoinedStrand` as {@link formationStrandAddrs} is.
|
|
428
463
|
*/
|
|
429
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;
|
|
430
477
|
/**
|
|
431
478
|
* Invite keys of the membership invitations this process has staged, by
|
|
432
479
|
* {@link adoptFormationMembershipInvite} or from a `JoinSuccess` row
|
|
@@ -460,7 +507,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
460
507
|
/**
|
|
461
508
|
* Dial targets learned out of band, keyed by peer id, valued by the multiaddr
|
|
462
509
|
* strings this node was handed (parsed lazily, at dial time). Two writers:
|
|
463
|
-
* {@link recordSeedBootstrapPeers} retains the
|
|
510
|
+
* {@link recordSeedBootstrapPeers} retains the preferred peers of every seed
|
|
464
511
|
* this node applies, and {@link addDrone} retains the addresses of every node
|
|
465
512
|
* this node adds. Two readers: {@link dialColdStartBootstrap} dials every entry
|
|
466
513
|
* while the control database still has no siblings, and
|
|
@@ -520,7 +567,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
520
567
|
private joinedStrandStore;
|
|
521
568
|
/**
|
|
522
569
|
* This session's joined strands: {@link joinedStrandStore} and the party-wide
|
|
523
|
-
* `JoinedStrand` table, unioned with the control rows each watcher
|
|
570
|
+
* `JoinedStrand` table, unioned with the control rows each watcher run, published and
|
|
524
571
|
* removed by the reconcile pass. Per SESSION, unlike the store — rebuilt at every
|
|
525
572
|
* {@link start}, which is what ends a revoked join's "keep offering until this session
|
|
526
573
|
* ends" (see `JoinedStrandSession.forgetAfterThisSession`).
|
|
@@ -551,17 +598,41 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
551
598
|
/** Listener that re-publishes the self record when reachable addresses change. */
|
|
552
599
|
private selfPeerUpdateHandler;
|
|
553
600
|
/**
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
* {@link
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
*
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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.
|
|
565
636
|
*/
|
|
566
637
|
private reconcileControlCohortInFlight;
|
|
567
638
|
/**
|
|
@@ -584,18 +655,26 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
584
655
|
* machine in the party is already discarding this node's address as stale.
|
|
585
656
|
*/
|
|
586
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;
|
|
587
666
|
/** Say-once latch for {@link escalateIfSelfRecordStale}; re-armed by the next publish. */
|
|
588
667
|
private selfRecordStaleWarned;
|
|
589
668
|
/**
|
|
590
669
|
* Re-replication queue for owner `authorizePeer` writes that committed while
|
|
591
|
-
* this node was alone
|
|
592
|
-
*
|
|
593
|
-
*
|
|
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
|
|
594
673
|
* re-issue once the cohort grows. Removals are NOT tracked here — a delete
|
|
595
674
|
* leaves a `Revocation` tombstone, tracked by {@link pendingRevocations}; a
|
|
596
675
|
* removePeer also clears any queued authorize for the same subject (the row is
|
|
597
|
-
* gone, so re-issuing the insert would be wrong). Drained
|
|
598
|
-
*
|
|
676
|
+
* gone, so re-issuing the insert would be wrong). Drained when a sibling becomes
|
|
677
|
+
* reachable ({@link updateControlSiblingReachability}) by {@link drainPendingControlReplication}.
|
|
599
678
|
*/
|
|
600
679
|
private pendingPeerWrites;
|
|
601
680
|
/**
|
|
@@ -619,14 +698,6 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
619
698
|
* exactly a "removals from before this lifetime" case).
|
|
620
699
|
*/
|
|
621
700
|
private reissuedHeldRevocations;
|
|
622
|
-
/**
|
|
623
|
-
* Whether this process has seen the singleton `Revocation` ledger marker filed — its own
|
|
624
|
-
* `'opened'` or an `'already-open'` answer, in {@link openRevocationLedgerIfDue}. A
|
|
625
|
-
* record of work done, not a cached authorization answer: the marker can never be
|
|
626
|
-
* deleted (`NoDelete`), so this can only go stale if the database itself is replaced,
|
|
627
|
-
* which is why stop() clears it with the other per-process replication state.
|
|
628
|
-
*/
|
|
629
|
-
private revocationLedgerOpened;
|
|
630
701
|
/** This node's own `CadrePeer` self-write committed local-only (re-touched on growth). */
|
|
631
702
|
private pendingSelfPeerWrite;
|
|
632
703
|
/** This node's own `DeviceToken` self-write committed local-only (re-touched on growth). */
|
|
@@ -641,14 +712,24 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
641
712
|
/** Single-flight guard for the re-replication drain (mirrors {@link registerSelfInFlight}). */
|
|
642
713
|
private drainControlReplicationInFlight;
|
|
643
714
|
/**
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
*
|
|
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.
|
|
647
721
|
*/
|
|
648
|
-
private
|
|
649
|
-
/**
|
|
650
|
-
|
|
651
|
-
|
|
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}).
|
|
728
|
+
*/
|
|
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}). */
|
|
652
733
|
private controlConnectionCloseHandler;
|
|
653
734
|
constructor(config: CadreNodeConfig);
|
|
654
735
|
/**
|
|
@@ -777,7 +858,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
777
858
|
/**
|
|
778
859
|
* Construct (or adopt) the node-local trusted-owner anchor and seed the
|
|
779
860
|
* out-of-band pinned keys from `config.trustedOwners`. The anchor's BASE comes
|
|
780
|
-
* only from out of band — genesis self-trust ({@link
|
|
861
|
+
* only from out of band — genesis self-trust ({@link useOwnerSigner}),
|
|
781
862
|
* config pins (here), runtime enrollment pins ({@link trustOwnerKeys}) and a
|
|
782
863
|
* claim; everything else in it is derived from that base along verifiable
|
|
783
864
|
* chains through the replicated `OwnerKey` table ({@link syncOwnerAnchor}),
|
|
@@ -943,12 +1024,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
943
1024
|
getTrustedOwnerStore(): TrustedOwnerStore | null;
|
|
944
1025
|
/**
|
|
945
1026
|
* Persist out-of-band-established owner keys into the node-local anchor —
|
|
946
|
-
* the runtime enrollment seam: {@link redeemCadreInvitation}
|
|
947
|
-
*
|
|
948
|
-
* holds the pins when the reply and the rows
|
|
1027
|
+
* the runtime enrollment seam: {@link redeemCadreInvitation} and
|
|
1028
|
+
* {@link redeemOwnerGrant} call it with their bundle's `ownerKeys` BEFORE dialing
|
|
1029
|
+
* a member (so the anchor already holds the pins when the reply and the rows
|
|
1030
|
+
* that follow are judged), and an
|
|
949
1031
|
* embedder calls it with an operator-supplied pin. Idempotent. ('genesis'
|
|
950
1032
|
* provenance is reserved for the node's own founding key, seeded internally by
|
|
951
|
-
* {@link
|
|
1033
|
+
* {@link useOwnerSigner}; 'chain' is written only by {@link syncOwnerAnchor}.)
|
|
952
1034
|
*
|
|
953
1035
|
* Validates every key's shape before trusting any of them (all-or-nothing):
|
|
954
1036
|
* a malformed entry anywhere in `keys` rejects the whole call before a
|
|
@@ -973,8 +1055,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
973
1055
|
private controlNetworkName;
|
|
974
1056
|
/**
|
|
975
1057
|
* Arm the CONTROL network's peer-join block catch-up: push every block in the
|
|
976
|
-
* control database's own raw store to each
|
|
977
|
-
*
|
|
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
|
|
978
1060
|
* control blocks committed while the writer was alone — the named
|
|
979
1061
|
* collection-header blocks written once at genesis above all, whose revision
|
|
980
1062
|
* never moves again, so no later commit ever carries them to a member that
|
|
@@ -983,7 +1065,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
983
1065
|
* for peers it knew about before the restart).
|
|
984
1066
|
*
|
|
985
1067
|
* Unlike the per-strand instances (see `strand-instance-manager.ts`), pushes
|
|
986
|
-
* 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
|
|
987
1071
|
* network's inbound connection gate deliberately admits non-members in
|
|
988
1072
|
* several states (seed delivery to an un-enrolled node, an open enrollment
|
|
989
1073
|
* window, an outstanding invitation, configured bootstrap/relay peers), and
|
|
@@ -999,9 +1083,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
999
1083
|
* The one non-member the gate admits is a device whose invitation
|
|
1000
1084
|
* redemption this member is admitting ({@link admitRedeemingDevice}).
|
|
1001
1085
|
*
|
|
1002
|
-
* No-ops (logged) when
|
|
1003
|
-
*
|
|
1004
|
-
* 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.
|
|
1005
1089
|
*/
|
|
1006
1090
|
private startControlBackfill;
|
|
1007
1091
|
/**
|
|
@@ -1041,6 +1125,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1041
1125
|
* operation count.
|
|
1042
1126
|
*/
|
|
1043
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;
|
|
1044
1139
|
/**
|
|
1045
1140
|
* Warn the operator when `network.announceAddrs` will silently cost this node the
|
|
1046
1141
|
* relay reachability it also configured.
|
|
@@ -1095,10 +1190,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1095
1190
|
* own strand node, so a NAT'd member's strand node can hold a
|
|
1096
1191
|
* circuit-relay reservation here (see `delegate-admission.ts`).
|
|
1097
1192
|
* Connection only: the per-stream gate below never honors a grant;
|
|
1098
|
-
* 3. the
|
|
1193
|
+
* 3. the peer is a device whose invitation or owner-grant redemption this
|
|
1194
|
+
* node has verified and is writing ({@link admittingDevices}): its
|
|
1195
|
+
* connection carries the exchange, which must not be cut at the
|
|
1196
|
+
* provisional deadline. A LIVE OWNER GRANT does not count as expecting a
|
|
1197
|
+
* stranger the way check 7's invitation does: a backup grant has no
|
|
1198
|
+
* expiry, and counting it would admit every stranger outright for the
|
|
1199
|
+
* party's life, so the holder rides the provisional admission until its
|
|
1200
|
+
* redemption verifies, then this;
|
|
1201
|
+
* 4. the authorized-member set is empty — cold start: the rows that would
|
|
1099
1202
|
* authorize anyone arrive by replication over these very connections;
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1203
|
+
* 5. the peer IS an authorized member; or
|
|
1204
|
+
* 6. an open invitation is OUTSTANDING — at least one unexpired,
|
|
1102
1205
|
* not-fully-consumed invitation this node minted or persisted (see
|
|
1103
1206
|
* `StrandSolicitationService.hasOutstandingInvitation`). A formation
|
|
1104
1207
|
* initiator is another party's peer by design and its token is only
|
|
@@ -1106,19 +1209,19 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1106
1209
|
* "does this node expect a stranger at all?". REGISTERING the responder
|
|
1107
1210
|
* does not count: every node registers one at {@link start}, and
|
|
1108
1211
|
* registering mints no invitation; or
|
|
1109
|
-
*
|
|
1212
|
+
* 7. a LIVE cadre invitation exists — a `CadreInvite` row this node holds
|
|
1110
1213
|
* that is not withdrawn, unexpired, has uses left and whose issuer is
|
|
1111
1214
|
* still an owner (`ControlDatabase.hasLiveCadreInvite`). The device that
|
|
1112
1215
|
* redeems it is a stranger until the redemption writes its row, and its
|
|
1113
1216
|
* proof of possession is only checkable inside `/sereus/cadre-invite/1.0.0`;
|
|
1114
|
-
* same reasoning as check
|
|
1217
|
+
* same reasoning as check 6, and the same expectation-of-a-stranger key.
|
|
1115
1218
|
*
|
|
1116
1219
|
* Ordering is semantically free (the checks are OR'd) but decides who pays:
|
|
1117
|
-
* checks 1-
|
|
1118
|
-
* that is otherwise headed for a provisional admission reaches check
|
|
1119
|
-
*
|
|
1220
|
+
* checks 1-3 are in-memory, 4/5 share one control-DB read, and only a peer
|
|
1221
|
+
* that is otherwise headed for a provisional admission reaches check 6's and
|
|
1222
|
+
* 7's invitation lookups.
|
|
1120
1223
|
*
|
|
1121
|
-
* Caveats of check
|
|
1224
|
+
* Caveats of check 6:
|
|
1122
1225
|
* - the in-memory mint registry dies with the process, so after a restart
|
|
1123
1226
|
* only invitations persisted as `FormationInvite` rows still hold the
|
|
1124
1227
|
* window open (re-mint otherwise);
|
|
@@ -1129,21 +1232,21 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1129
1232
|
* When every check falls through: `'admit-provisionally'`, on every node,
|
|
1130
1233
|
* relay server or not. The gate sees only a peer id, so it cannot tell an
|
|
1131
1234
|
* outsider from a device whose invitation row this node has not received yet
|
|
1132
|
-
* (it redeems here by seating the row from its bundle, after which check
|
|
1133
|
-
* admits) or from a sibling whose membership row is still replicating (check
|
|
1235
|
+
* (it redeems here by seating the row from its bundle, after which check 7
|
|
1236
|
+
* admits) or from a sibling whose membership row is still replicating (check 5
|
|
1134
1237
|
* admits once it lands; if it has not landed by the deadline, the connection
|
|
1135
|
-
* is closed and
|
|
1136
|
-
* 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
|
|
1137
1240
|
* members-only protocol meanwhile. A circuit-relay reservation admitted by
|
|
1138
1241
|
* {@link admitControlRelayReservation} disarms the deadline (see
|
|
1139
1242
|
* `membership-connection-gater.ts` → "The relay-reservation seam").
|
|
1140
1243
|
*
|
|
1141
|
-
* NOTE: check
|
|
1244
|
+
* NOTE: check 4/5 runs a control-DB read per inbound connection
|
|
1142
1245
|
* (`listAuthorizedMembers`); connections are rare and cadres small, and this
|
|
1143
1246
|
* layer is fail-open behind `ADMISSION_DECISION_TIMEOUT_MS`, so the live
|
|
1144
1247
|
* read is safe here — unlike the per-stream gate, which must consult the
|
|
1145
1248
|
* materialized {@link authorizedControlPeers} snapshot instead.
|
|
1146
|
-
* NOTE: checks
|
|
1249
|
+
* NOTE: checks 6 and 7 add two more control reads (`hasOutstandingFormationInvite`,
|
|
1147
1250
|
* `hasLiveCadreInvite`) for a stranger with no locally minted invitation in play, on
|
|
1148
1251
|
* every node, and a provisional connection's deadline runs the whole decision once
|
|
1149
1252
|
* more. If stranger connections ever arrive fast enough for those reads — on arrival
|
|
@@ -1281,6 +1384,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1281
1384
|
* needs a key-based admission.
|
|
1282
1385
|
*/
|
|
1283
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;
|
|
1284
1396
|
/**
|
|
1285
1397
|
* Per-stream authorization for the protocol guard's `members-snapshot` class
|
|
1286
1398
|
* (`control-protocol-guard.ts`): the four Optimystic control-DB protocols
|
|
@@ -1308,13 +1420,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1308
1420
|
* this listener with its write lock HELD (`notifyMembershipChanged`), so a retrying
|
|
1309
1421
|
* read here would sleep its backoff holding the lock and stall every other local
|
|
1310
1422
|
* writer. Nothing is lost — this refresh already keeps the previous snapshot on
|
|
1311
|
-
* 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.
|
|
1312
1425
|
*
|
|
1313
1426
|
* The trusted-owner anchor sync ({@link syncOwnerAnchor}) runs FIRST, so the snapshot
|
|
1314
1427
|
* built here judges `CadrePeer` rows against the anchor as the `OwnerKey` table now
|
|
1315
|
-
* has it. The
|
|
1316
|
-
*
|
|
1317
|
-
*
|
|
1428
|
+
* has it. The tombstone list the sync and the two end-of-refresh checks
|
|
1429
|
+
* ({@link hangUpRevokedControlPeers}, then {@link noteOwnRemoval}) read is loaded at most
|
|
1430
|
+
* once per refresh, as is the invitation chain the membership read may need.
|
|
1318
1431
|
*/
|
|
1319
1432
|
private refreshAuthorizedControlPeers;
|
|
1320
1433
|
/**
|
|
@@ -1370,9 +1483,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1370
1483
|
*/
|
|
1371
1484
|
private getBootstrapPeerIds;
|
|
1372
1485
|
/**
|
|
1373
|
-
* What the strand watcher
|
|
1486
|
+
* What the strand watcher reads: this party's control rows plus a row per cross-party
|
|
1374
1487
|
* join (see {@link joinedStrands}), so a joined strand is offered and relaunched on
|
|
1375
|
-
* 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.
|
|
1376
1490
|
*/
|
|
1377
1491
|
private createStrandQueryable;
|
|
1378
1492
|
/**
|
|
@@ -1381,30 +1495,40 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1381
1495
|
*/
|
|
1382
1496
|
private createPartyJoinedStrandLedger;
|
|
1383
1497
|
/**
|
|
1384
|
-
* This machine's owner
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1498
|
+
* This machine's owner signer when it can make owner-gated control writes: one is wired
|
|
1499
|
+
* ({@link useOwnerSigner}) and the party's `OwnerKey` table enrolls its key. A phone that
|
|
1500
|
+
* ran self-genesis after another machine founded the party passes the first test and not
|
|
1501
|
+
* the second (`runOwnerGenesis` in the reference apps), and a write it signed would only
|
|
1502
|
+
* be refused.
|
|
1389
1503
|
*/
|
|
1390
|
-
private
|
|
1504
|
+
private enrolledOwnerSigner;
|
|
1391
1505
|
/**
|
|
1392
|
-
* Whether this node can act as an owner of its cadre:
|
|
1393
|
-
*
|
|
1394
|
-
*
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1506
|
+
* Whether this node can act as an owner of its cadre: an owner signer is wired
|
|
1507
|
+
* ({@link useOwnerSigner}) and its key is in the live `OwnerKey` set. What the owner-only
|
|
1508
|
+
* flows (`createCadreInvitation`, `claimNode`, `rentNode`) need; false before `start()`,
|
|
1509
|
+
* and on a machine that joined as a member. The reference apps' "is this device an owner"
|
|
1510
|
+
* check.
|
|
1397
1511
|
*/
|
|
1398
1512
|
isOwner(): Promise<boolean>;
|
|
1399
|
-
/** {@link
|
|
1400
|
-
private
|
|
1401
|
-
/**
|
|
1402
|
-
|
|
1513
|
+
/** {@link enrolledOwnerSigner} judged against an owner set the caller has already read. */
|
|
1514
|
+
private ownerSignerEnrolledIn;
|
|
1515
|
+
/**
|
|
1516
|
+
* The owner signer {@link useOwnerSigner} wired, or null: every owner signature this node
|
|
1517
|
+
* makes goes through it. Enrollment is not checked ({@link isOwner} does). A caller that
|
|
1518
|
+
* makes several owner writes as one action opens one `approve` on it and hands the
|
|
1519
|
+
* resulting `sign` to the methods that take one (`publishStrand`, `ensureStrandPartyKey`,
|
|
1520
|
+
* `publishFormationInvite`), as `adoptStrandBundle` does.
|
|
1521
|
+
*/
|
|
1522
|
+
getOwnerSigner(): OwnerSigner | null;
|
|
1403
1523
|
/**
|
|
1404
1524
|
* Schedule the node's initial self-record publish + ongoing refresh shortly
|
|
1405
1525
|
* after start (non-blocking). {@link registerSelf} is idempotent and safely
|
|
1406
1526
|
* no-ops when it cannot yet sign/insert (e.g. owner key not installed),
|
|
1407
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.
|
|
1408
1532
|
*/
|
|
1409
1533
|
private scheduleSelfRegistration;
|
|
1410
1534
|
/**
|
|
@@ -1436,11 +1560,42 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1436
1560
|
/** The body of {@link registerSelf}; serialised by its single-flight guard. */
|
|
1437
1561
|
private publishSelfRecord;
|
|
1438
1562
|
/**
|
|
1439
|
-
*
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
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.
|
|
1442
1579
|
*/
|
|
1443
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;
|
|
1444
1599
|
/**
|
|
1445
1600
|
* Report an abandoned control write to the embedding app.
|
|
1446
1601
|
*
|
|
@@ -1501,41 +1656,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1501
1656
|
*/
|
|
1502
1657
|
private getSelfSigningKey;
|
|
1503
1658
|
/**
|
|
1504
|
-
* Does the `Strand` row name THIS machine as its founder —
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1507
|
-
*
|
|
1508
|
-
*
|
|
1509
|
-
* enough for {@link launchStrand}'s tracked-instance
|
|
1510
|
-
*
|
|
1511
|
-
* NOTE: reads "one owner key per machine" — the reference model, where the owner
|
|
1512
|
-
* key IS the key behind the PeerId. Two machines running the SAME identity key
|
|
1513
|
-
* would both derive `true` and each bootstrap on its own replica, which is the
|
|
1514
|
-
* double-`Header` hazard this derivation exists to avoid. Unreachable today: that
|
|
1515
|
-
* configuration also gives both machines one PeerId, which already breaks control
|
|
1516
|
-
* networking well before any strand launches. Revisit if machines ever share an
|
|
1517
|
-
* owner key while holding distinct transport identities — the derivation would
|
|
1518
|
-
* then need a per-machine discriminator (the `CadrePeer` PeerId) on the row.
|
|
1659
|
+
* Does the `Strand` row name THIS machine as its founder — is its `FounderPeerId` this
|
|
1660
|
+
* node's peer id? A null column (a consent-seated strand, or a hand-built row) derives
|
|
1661
|
+
* `false`: without a positive match this machine must attach, never bootstrap. A peer id
|
|
1662
|
+
* rather than an owner key, so two machines holding one owner key never both bootstrap
|
|
1663
|
+
* (the double-`Header` hazard), and a node derives it before any owner signer is wired.
|
|
1664
|
+
* Pure comparison, no I/O — cheap enough for {@link launchStrand}'s tracked-instance
|
|
1665
|
+
* early return.
|
|
1519
1666
|
*/
|
|
1520
1667
|
private isSelfFoundedRow;
|
|
1521
|
-
/**
|
|
1522
|
-
* The owner keypair (base64url Ed25519) derived from this node's resolved
|
|
1523
|
-
* identity key. In the single-key reference model the owner signing key is
|
|
1524
|
-
* *derived from* the node identity (see {@link ed25519KeyPairFromLibp2p}), so the
|
|
1525
|
-
* same key material protected in a secure enclave backs both.
|
|
1526
|
-
*
|
|
1527
|
-
* Exposed so the hosting app retains control of owner genesis: cadre-core
|
|
1528
|
-
* resolves + protects the identity, then the app sources this pair to drive
|
|
1529
|
-
* `ensureOwnerKey(pub)` + `initializeSeedBootstrap(priv)` itself — cadre-core
|
|
1530
|
-
* never silently runs genesis. A future separate-owner slot would return a
|
|
1531
|
-
* distinct key here instead of the identity-derived one.
|
|
1532
|
-
*
|
|
1533
|
-
* @returns The base64url seed/public-key owner pair.
|
|
1534
|
-
* @throws If called before {@link start} has resolved the identity, or when the
|
|
1535
|
-
* node runs on an ephemeral libp2p key (no `keyStore`/`privateKey` configured),
|
|
1536
|
-
* since that key is internal to libp2p and not exposed.
|
|
1537
|
-
*/
|
|
1538
|
-
getIdentityOwnerKey(): Ed25519KeyPair;
|
|
1539
1668
|
/**
|
|
1540
1669
|
* Publish this node's own address record from the reconcile pass until its first publish of
|
|
1541
1670
|
* this session lands. A node with no owner key — seeded (`addDrone`, `cadre enroll add`),
|
|
@@ -1553,12 +1682,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1553
1682
|
* process does not learn it was removed ({@link noteOwnRemoval}), and pays that read on every
|
|
1554
1683
|
* pass.
|
|
1555
1684
|
*
|
|
1556
|
-
*
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1559
|
-
* then never see rows written while it
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
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}).
|
|
1562
1696
|
*/
|
|
1563
1697
|
private publishFirstSelfRecord;
|
|
1564
1698
|
/**
|
|
@@ -1575,7 +1709,21 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1575
1709
|
* stack listeners/timers.
|
|
1576
1710
|
*/
|
|
1577
1711
|
private startRecordRefresh;
|
|
1578
|
-
/**
|
|
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}). */
|
|
1579
1727
|
private stopRecordRefresh;
|
|
1580
1728
|
/**
|
|
1581
1729
|
* Resolve a peer's current, signed, trust-checkable multiaddrs from only its
|
|
@@ -1649,37 +1797,49 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1649
1797
|
* Run one proactive control-cohort dial pass to keep this node connected to its
|
|
1650
1798
|
* cadre siblings (so the `CadreControl` collections form a replicating cohort).
|
|
1651
1799
|
* Public so the cohort-growth-driven re-replication path
|
|
1652
|
-
* (`control-write-ensure-replicated`)
|
|
1653
|
-
*
|
|
1654
|
-
*
|
|
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.
|
|
1655
1814
|
*
|
|
1656
1815
|
* Concurrent triggers collapse into a single in-flight pass
|
|
1657
1816
|
* (see {@link reconcileControlCohortInFlight}) so two passes never double-dial;
|
|
1658
|
-
* 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).
|
|
1659
1819
|
* Best-effort throughout: a failure to resolve/dial any one sibling is logged
|
|
1660
1820
|
* and the pass continues; the whole pass is a no-op when the node is alone.
|
|
1661
1821
|
*
|
|
1662
1822
|
* Resolves to the peers the pass dialled ({@link ControlCohortReconcileResult}),
|
|
1663
1823
|
* so a caller can tell a link this pass opened from one something else opened —
|
|
1664
|
-
* the pass skips a peer that is already connected. The
|
|
1665
|
-
* discard it.
|
|
1824
|
+
* the pass skips a peer that is already connected. The scheduled runs discard it.
|
|
1666
1825
|
*/
|
|
1667
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;
|
|
1668
1841
|
/** Body of {@link reconcileControlCohort}; serialised by its single-flight guard. */
|
|
1669
1842
|
private runReconcileControlCohort;
|
|
1670
|
-
/**
|
|
1671
|
-
* The owner-only half of the reconcile pass's ledger-marker step: file the singleton
|
|
1672
|
-
* `Revocation` marker ({@link SeedBootstrapService.openRevocationLedger}) unless this
|
|
1673
|
-
* process has already seen it filed. The connectivity gate is the caller's
|
|
1674
|
-
* ({@link runReconcileControlCohort}); why the marker exists is on
|
|
1675
|
-
* {@link ControlDatabase.openRevocationLedger}.
|
|
1676
|
-
*
|
|
1677
|
-
* Sets {@link revocationLedgerOpened} on `'opened'` or `'already-open'`. A node that
|
|
1678
|
-
* cannot sign as an owner does nothing: it picks the owner's marker up the first time it
|
|
1679
|
-
* reads the table, like any other replicated row. Best-effort like every step of the
|
|
1680
|
-
* pass — a failure is logged and leaves the flag clear, so the next connected pass retries.
|
|
1681
|
-
*/
|
|
1682
|
-
private openRevocationLedgerIfDue;
|
|
1683
1843
|
/**
|
|
1684
1844
|
* Resolve every sibling's signed address record once and merge what resolves
|
|
1685
1845
|
* into the control node's libp2p **address book** (peerStore). Returns the
|
|
@@ -1703,8 +1863,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1703
1863
|
* current ({@link refreshDialHint}).
|
|
1704
1864
|
*
|
|
1705
1865
|
* NOTE: this resolves EVERY sibling serially before the dial loop below runs,
|
|
1706
|
-
* so it costs one record query per sibling per reconcile pass (
|
|
1707
|
-
*
|
|
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
|
|
1708
1869
|
* that is a few extra local reads; if cadres ever grow large, batch the records
|
|
1709
1870
|
* into one query, merge only on change, or move the warm pass after the dials.
|
|
1710
1871
|
*/
|
|
@@ -1766,16 +1927,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1766
1927
|
*/
|
|
1767
1928
|
private collectSiblingStrandAddrs;
|
|
1768
1929
|
/**
|
|
1769
|
-
* Dial one sibling from its already-resolved addresses, best-effort.
|
|
1770
|
-
* whether the dial resolved (false when no address resolves or the dial fails).
|
|
1930
|
+
* Dial one sibling from its already-resolved addresses, best-effort.
|
|
1771
1931
|
*
|
|
1772
1932
|
* A per-peer failure (NAT, offline, relay down, connection-gater denial, or
|
|
1773
1933
|
* the {@link controlDialBudget} expiring) is logged and swallowed so one
|
|
1774
1934
|
* unreachable sibling never aborts the pass — exactly like
|
|
1775
1935
|
* {@link SeedBootstrapService.applySeed}'s owner-dial loop. A failed dial is
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
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.
|
|
1779
1942
|
*/
|
|
1780
1943
|
private dialControlSibling;
|
|
1781
1944
|
/**
|
|
@@ -1819,7 +1982,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1819
1982
|
* These addresses are never merged into the libp2p address book: they are
|
|
1820
1983
|
* unverified, and the address book takes only verified ones (see
|
|
1821
1984
|
* `mergePeerAddrs`). Layers that dial by bare peer id use the connection this
|
|
1822
|
-
* dial opens, and after it drops the
|
|
1985
|
+
* dial opens, and after it drops the reconcile pass the close starts dials again.
|
|
1823
1986
|
*
|
|
1824
1987
|
* NOTE: an entry is consulted here only for a current sibling, so a peer revoked
|
|
1825
1988
|
* by another owner is not dialed from it, even though the entry stays (only this
|
|
@@ -1847,22 +2010,22 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1847
2010
|
*/
|
|
1848
2011
|
private peerStoreAddrs;
|
|
1849
2012
|
/**
|
|
1850
|
-
* Retain a just-applied seed's
|
|
1851
|
-
* dial targets (see {@link bootstrapPeerStore}).
|
|
2013
|
+
* Retain a just-applied seed's preferred peers (`SeedPeer.preferred`) as cold-start
|
|
2014
|
+
* bootstrap dial targets (see {@link bootstrapPeerStore}).
|
|
1852
2015
|
*
|
|
1853
2016
|
* Called for every seed this node accepts, on BOTH intake paths — the
|
|
1854
2017
|
* {@link applySeed} wrapper (which may run on a throwaway service, so the
|
|
1855
2018
|
* service itself is the wrong place to keep this) and the inbound
|
|
1856
|
-
* `/sereus/seed/1.0.0` handler via `onSeedApplied`.
|
|
1857
|
-
* skipped: there is nothing to dial.
|
|
2019
|
+
* `/sereus/seed/1.0.0` handler via `onSeedApplied`.
|
|
1858
2020
|
*
|
|
1859
|
-
* `
|
|
2021
|
+
* `preferred` is the seed's own claim about its peers, exactly as
|
|
1860
2022
|
* `SeedBootstrapService.applySeed` uses it to choose its dial targets. That is
|
|
1861
2023
|
* sound here for the same reason it is sound there — the whole seed is
|
|
1862
2024
|
* signature-checked against a trust-anchored signer before this runs, and the
|
|
1863
2025
|
* flag only *selects a dial target*; a dial grants no authority.
|
|
1864
2026
|
*
|
|
1865
|
-
*
|
|
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.
|
|
1866
2029
|
*/
|
|
1867
2030
|
private recordSeedBootstrapPeers;
|
|
1868
2031
|
/**
|
|
@@ -1877,34 +2040,35 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1877
2040
|
* failing a seed or an add that has already been accepted.
|
|
1878
2041
|
*
|
|
1879
2042
|
* Two peers are never retained. One with no address: there is nothing to dial.
|
|
1880
|
-
* And self: `
|
|
1881
|
-
*
|
|
1882
|
-
* 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
|
|
1883
2045
|
* pass dial this node forever (the steady-state pass filters self out of its
|
|
1884
2046
|
* sibling list for the same reason).
|
|
1885
2047
|
*/
|
|
1886
2048
|
private retainDialTarget;
|
|
1887
2049
|
/**
|
|
1888
2050
|
* Cold-start branch of the reconcile pass: re-dial every retained out-of-band
|
|
1889
|
-
* dial target (see {@link bootstrapPeerStore}) — in practice the
|
|
1890
|
-
* the seeds this node applied — while the control database still has no
|
|
2051
|
+
* dial target (see {@link bootstrapPeerStore}) — in practice the preferred peers
|
|
2052
|
+
* of the seeds this node applied — while the control database still has no
|
|
1891
2053
|
* siblings to dial.
|
|
1892
2054
|
*
|
|
1893
|
-
* `SeedBootstrapService.applySeed` dials those
|
|
1894
|
-
* When that single dial fails —
|
|
2055
|
+
* `SeedBootstrapService.applySeed` dials those peers exactly ONCE, best-effort.
|
|
2056
|
+
* When that single dial fails — peer momentarily down, relay reservation not
|
|
1895
2057
|
* up yet, NAT traversal lost the race — the joining node has an empty
|
|
1896
2058
|
* `CadrePeer` table and no connection, and the steady-state pass above cannot
|
|
1897
2059
|
* help: it dials only siblings enumerated from that very table. Retrying here
|
|
1898
2060
|
* is the only way back in.
|
|
1899
2061
|
*
|
|
1900
|
-
* Unbounded
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
1903
|
-
* a
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
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.
|
|
1908
2072
|
*
|
|
1909
2073
|
* Best-effort per peer, exactly like {@link dialControlSibling}: one dead
|
|
1910
2074
|
* address never aborts the pass.
|
|
@@ -1923,20 +2087,26 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1923
2087
|
* encapsulated; an unencapsulatable one is dropped rather than dialed bare.
|
|
1924
2088
|
*/
|
|
1925
2089
|
private bootstrapDialAddrs;
|
|
1926
|
-
/**
|
|
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
|
+
*/
|
|
1927
2095
|
private dialBootstrapPeer;
|
|
1928
2096
|
/**
|
|
1929
|
-
* Whether a control write happening now would commit local-only
|
|
1930
|
-
*
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
1934
|
-
*
|
|
1935
|
-
*
|
|
1936
|
-
* 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.
|
|
1937
2104
|
*
|
|
1938
|
-
*
|
|
1939
|
-
* 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).
|
|
1940
2110
|
*/
|
|
1941
2111
|
private committedAlone;
|
|
1942
2112
|
/**
|
|
@@ -1966,7 +2136,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
1966
2136
|
/**
|
|
1967
2137
|
* Drain the write-while-alone re-replication queue: re-issue the writes that
|
|
1968
2138
|
* committed local-only now that the cohort can broadcast them. Public so the
|
|
1969
|
-
*
|
|
2139
|
+
* growth trigger ({@link updateControlSiblingReachability}) and tests can drive
|
|
1970
2140
|
* it; concurrent calls collapse into one in-flight drain
|
|
1971
2141
|
* ({@link drainControlReplicationInFlight}) so two growth signals never
|
|
1972
2142
|
* double-issue. Best-effort throughout — a per-row failure leaves that entry
|
|
@@ -2035,8 +2205,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2035
2205
|
private reconstructAuthoredMembership;
|
|
2036
2206
|
/**
|
|
2037
2207
|
* Body of {@link reconstructAuthoredMembership}'s sweep: re-issue each candidate
|
|
2038
|
-
* owner-authored row, returning how many were re-touched. A
|
|
2039
|
-
*
|
|
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.
|
|
2040
2213
|
*/
|
|
2041
2214
|
private reissueAuthoredMembershipRows;
|
|
2042
2215
|
/**
|
|
@@ -2052,9 +2225,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2052
2225
|
*/
|
|
2053
2226
|
private drainPendingPeerWrites;
|
|
2054
2227
|
/**
|
|
2055
|
-
* Body of {@link drainPendingPeerWrites}' loop. An entry is cleared
|
|
2056
|
-
*
|
|
2057
|
-
* 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.
|
|
2058
2232
|
*/
|
|
2059
2233
|
private reissuePendingPeerWrites;
|
|
2060
2234
|
/**
|
|
@@ -2062,23 +2236,84 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2062
2236
|
* `UpdatedAt` strictly above the stored value (and the wall clock) and re-sign via
|
|
2063
2237
|
* the owner branch of `CadrePeer.AuthorizedUpdate`. The row already exists
|
|
2064
2238
|
* locally (it committed there, just local-only), so this is an UPDATE, not the
|
|
2065
|
-
* 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).
|
|
2066
2242
|
*/
|
|
2067
2243
|
private reissuePeerAuthorize;
|
|
2068
2244
|
/**
|
|
2069
|
-
*
|
|
2070
|
-
* write-while-alone re-replication queue (single-flight;
|
|
2071
|
-
* not on
|
|
2072
|
-
*
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
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.
|
|
2082
2317
|
*/
|
|
2083
2318
|
private wireControlConnectionListeners;
|
|
2084
2319
|
/**
|
|
@@ -2322,7 +2557,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2322
2557
|
* `Strand` row. A joiner (the row arrived over the control network) wants exactly this; a
|
|
2323
2558
|
* FOUNDER wants {@link foundStrand}, which publishes and attaches in one resumable call.
|
|
2324
2559
|
* With no explicit `founder` flag, founder-ness is derived from the row's
|
|
2325
|
-
* `
|
|
2560
|
+
* `FounderPeerId` (see {@link StrandConfig.founder}) — so attaching a row THIS machine
|
|
2326
2561
|
* published (e.g. re-attaching its own orphan after a restart) founds it, and attaching
|
|
2327
2562
|
* anyone else's row joins, without the caller needing to know which it is.
|
|
2328
2563
|
*
|
|
@@ -2335,8 +2570,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2335
2570
|
* registered, deliberately: both an explicit retry and the {@link StrandWatcher}'s
|
|
2336
2571
|
* automatic relaunch need it. A failed launch here hands the strand back to that
|
|
2337
2572
|
* relaunch — `StrandWatcher.forgetStrand` drops the id from the watcher's
|
|
2338
|
-
* `knownStrands
|
|
2339
|
-
*
|
|
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
|
|
2340
2576
|
* config stays registered, the retry takes `handleStrandAdded`'s auto-launch branch:
|
|
2341
2577
|
* what the app sees is `strand:error` per failed retry and `strand:started` when one
|
|
2342
2578
|
* succeeds, never a second `strand:discovered`. {@link detachStrand} (reached via
|
|
@@ -2393,11 +2629,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2393
2629
|
* fresh publish also clears it for an OPEN strand, but is destructive for a closed one
|
|
2394
2630
|
* (the key exists nowhere else). No app-data wipe is needed for either.
|
|
2395
2631
|
*
|
|
2396
|
-
* The insert is signed
|
|
2397
|
-
*
|
|
2398
|
-
*
|
|
2399
|
-
*
|
|
2400
|
-
*
|
|
2632
|
+
* The insert is signed through this node's owner signer ({@link getOwnerSigner}), in one
|
|
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
|
|
2635
|
+
* as `FounderOwnerKey` (the schema pins the column to the verified signer), and this node's
|
|
2636
|
+
* peer id as `FounderPeerId` (bound into the signature), which is what later lets any
|
|
2637
|
+
* launch derive "this machine is the founder" from the row alone.
|
|
2401
2638
|
* The key must be enrolled in `OwnerKey` (e.g. via {@link ControlDatabase.ensureOwnerKey}
|
|
2402
2639
|
* at genesis) or the schema's `Strand.AuthorizedInsert` constraint rejects the write.
|
|
2403
2640
|
* Failing loudly here is intentional: a silently-unpublished strand would run
|
|
@@ -2405,7 +2642,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2405
2642
|
*
|
|
2406
2643
|
* A CLOSED strand's publish also seats this party's own membership identity key —
|
|
2407
2644
|
* the `StrandPartyKey` row {@link ensureStrandPartyKey} documents — which is what the
|
|
2408
|
-
* 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
|
|
2409
2647
|
* `memberPrivateKey` ARGUMENT stays the strand-wide read secret formation hands to
|
|
2410
2648
|
* joiners; it derives nobody's identity.
|
|
2411
2649
|
*
|
|
@@ -2413,20 +2651,67 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2413
2651
|
* {@link addStrand}).
|
|
2414
2652
|
* @param type - `'o'` for open (default) or `'c'` for closed.
|
|
2415
2653
|
* @param memberPrivateKey - Optional shared membership (read) key for a closed strand.
|
|
2654
|
+
* @param sign - The `sign` of an owner approval the caller already holds
|
|
2655
|
+
* ({@link getOwnerSigner}), so a larger action asks once; omitted, this asks for its own.
|
|
2416
2656
|
* @returns The live `Strand` row — the one just inserted, or the matching one already
|
|
2417
2657
|
* there. A closed strand's caller should carry THIS row's `MemberPrivateKey` forward:
|
|
2418
2658
|
* on a repeat it is the stored key, not the argument.
|
|
2419
|
-
* @throws if the node is not started,
|
|
2659
|
+
* @throws if the node is not started, has no owner signer wired, the id is blank or
|
|
2420
2660
|
* unusable as a storage scope key (`InvalidStrandIdError` — see `storage-scope.ts`), a
|
|
2421
2661
|
* row with the same id holds different content, or the control DB rejects the
|
|
2422
2662
|
* (unauthorized) insert.
|
|
2423
2663
|
*/
|
|
2424
|
-
publishStrand(strandId: string, type?: 'o' | 'c', memberPrivateKey?: string): Promise<StrandRow>;
|
|
2664
|
+
publishStrand(strandId: string, type?: 'o' | 'c', memberPrivateKey?: string, sign?: OwnerSign): Promise<StrandRow>;
|
|
2665
|
+
/**
|
|
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.
|
|
2695
|
+
*/
|
|
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;
|
|
2425
2705
|
/**
|
|
2426
2706
|
* Found a strand — publish its row cadre-wide AND start the local instance as its
|
|
2427
2707
|
* founder — in one resumable call. The single entry point for creating a strand; a
|
|
2428
2708
|
* caller joining one someone else founded uses {@link addStrand} alone.
|
|
2429
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
|
+
*
|
|
2430
2715
|
* Safe to re-run from any point of interruption, which hand-rolling
|
|
2431
2716
|
* {@link publishStrand} + {@link addStrand} is not. Either half can already have
|
|
2432
2717
|
* happened:
|
|
@@ -2438,7 +2723,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2438
2723
|
* attempt (as the reference apps do) would otherwise present a key that does not match
|
|
2439
2724
|
* the membership already seated in the strand.
|
|
2440
2725
|
* - **instance already running** → the tracked instance is returned, and — because the
|
|
2441
|
-
* `Strand` row records its
|
|
2726
|
+
* `Strand` row records its founding machine (`FounderPeerId`) — a founder request
|
|
2442
2727
|
* against an instance that was first launched as a joiner now runs the (idempotent,
|
|
2443
2728
|
* insert-if-absent) founder bootstrap on it rather than silently skipping it
|
|
2444
2729
|
* ({@link StrandInstanceManager.foundExistingStrand}). So a strand that something else
|
|
@@ -2447,7 +2732,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2447
2732
|
* `resolveCohortSeed` window — still ends up with its `Strand.Header` written.
|
|
2448
2733
|
*
|
|
2449
2734
|
* Founder-ness is resolved FROM THE ROW, not assumed: this machine founds iff the
|
|
2450
|
-
* resolved row's `
|
|
2735
|
+
* resolved row's `FounderPeerId` is its own peer id. A fresh publish records this
|
|
2451
2736
|
* machine, so the common path founds; a machine that lost a concurrent founding race to
|
|
2452
2737
|
* a sibling adopts the sibling's row and ATTACHES instead — deliberately, since two
|
|
2453
2738
|
* machines bootstrapping the same strand on separate replicas is the double-`Header`
|
|
@@ -2456,12 +2741,51 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2456
2741
|
* `Type` is compared, not adopted: a stored row of the other type means the caller and
|
|
2457
2742
|
* the control plane disagree about what this strand IS, so it throws.
|
|
2458
2743
|
*
|
|
2744
|
+
* **Returning after the publish.** `foundStrand(config, { background: true })` resolves
|
|
2745
|
+
* once the row is published and continues the local start in
|
|
2746
|
+
* {@link FoundStrandBackgroundResult.ready}, for a caller that needs only the row (an
|
|
2747
|
+
* invitation reads just its id and membership key). Publish errors still reject the call
|
|
2748
|
+
* itself. A joiner that redeems an invitation to a closed strand before its start finishes
|
|
2749
|
+
* is refused with the retryable `host-strand-unavailable` and the token stays unspent:
|
|
2750
|
+
* {@link requestJoin} retries that on its backoff, while {@link formStrand} throws it to its
|
|
2751
|
+
* caller once every machine the invitation names has refused.
|
|
2752
|
+
*
|
|
2459
2753
|
* @returns The instance AND the row the strand actually runs under — read the membership
|
|
2460
2754
|
* key from the returned row, not from a freshly minted one ({@link FoundStrandResult}).
|
|
2461
2755
|
* @throws if the node is not started, the id is blank, a published row of the same id has
|
|
2462
2756
|
* a different `Type`, or either half rejects.
|
|
2463
2757
|
*/
|
|
2464
2758
|
foundStrand(config: FoundStrandConfig): Promise<FoundStrandResult>;
|
|
2759
|
+
/**
|
|
2760
|
+
* Publish the strand's row as {@link foundStrand} does, and resolve once it is published;
|
|
2761
|
+
* the local start continues in {@link FoundStrandBackgroundResult.ready}.
|
|
2762
|
+
*
|
|
2763
|
+
* @throws as the default form does for the publish half; the start's own failure rejects
|
|
2764
|
+
* `ready` instead.
|
|
2765
|
+
*/
|
|
2766
|
+
foundStrand(config: FoundStrandConfig, options: FoundStrandOptions): Promise<FoundStrandBackgroundResult>;
|
|
2767
|
+
/**
|
|
2768
|
+
* The publish half of {@link foundStrand}: adopt the stored row or publish a new one, and
|
|
2769
|
+
* derive whether this machine founds it. Every check that can refuse the founding is here,
|
|
2770
|
+
* so both forms of `foundStrand` reject on it before returning.
|
|
2771
|
+
*/
|
|
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;
|
|
2780
|
+
/**
|
|
2781
|
+
* The start half of {@link foundStrand}. Its timing lines (`addStrand: start`, then the
|
|
2782
|
+
* duration or `failed after`) are logged when the start settles, also in the background
|
|
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.
|
|
2787
|
+
*/
|
|
2788
|
+
private attachFoundingRow;
|
|
2465
2789
|
/**
|
|
2466
2790
|
* Resume onto an already-published `Strand` row: keep its stored content, reject a type
|
|
2467
2791
|
* disagreement. The stored `MemberPrivateKey` is authoritative — see {@link foundStrand}
|
|
@@ -2475,9 +2799,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2475
2799
|
* inverse of {@link publishStrand}.
|
|
2476
2800
|
*
|
|
2477
2801
|
* Party-wide, unlike {@link stopStrand}, which only stops the strand on THIS node: every
|
|
2478
|
-
* cadre node watching the table
|
|
2479
|
-
*
|
|
2480
|
-
*
|
|
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
|
|
2481
2806
|
* removes OUR party's participation only: other parties in the strand keep their own
|
|
2482
2807
|
* rows and the strand network carries on, so no cross-party sign-off is involved — this
|
|
2483
2808
|
* is an owner-signed control-plane write like every other, not "destroy the network".
|
|
@@ -2521,7 +2846,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2521
2846
|
* step, not a widening of this method.
|
|
2522
2847
|
*
|
|
2523
2848
|
* @param strandId - The `Strand` row id to remove (as passed to {@link publishStrand}).
|
|
2524
|
-
* @throws if the node is not started,
|
|
2849
|
+
* @throws if the node is not started, has no owner signer wired, the id is blank, or
|
|
2525
2850
|
* the signer is not an enrolled owner (the schema's `Strand.AuthorizedDelete` rejects
|
|
2526
2851
|
* the write and the row survives). A rejection does NOT imply the row survived: the
|
|
2527
2852
|
* local stop runs after the control-plane delete has already committed, so a failure
|
|
@@ -2542,9 +2867,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2542
2867
|
* as `MemberPrivateKey`; docs/strands.md → "Closed-Strand Member Key Handling") and is
|
|
2543
2868
|
* never put on the formation wire.
|
|
2544
2869
|
*
|
|
2545
|
-
*
|
|
2546
|
-
*
|
|
2547
|
-
*
|
|
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
|
|
2548
2875
|
* a specific key deliberately.
|
|
2549
2876
|
*
|
|
2550
2877
|
* @param strandId - The strand the key is this party's identity for.
|
|
@@ -2552,15 +2879,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2552
2879
|
* `generateStrandMemberKey` mints). When a row already holds a DIFFERENT key this
|
|
2553
2880
|
* throws — a party has one identity per strand; rotation is a deliberate
|
|
2554
2881
|
* remove-then-insert, not a silent swap.
|
|
2882
|
+
* @param sign - The `sign` of an owner approval the caller already holds; omitted, a
|
|
2883
|
+
* mint asks for its own (a stored key asks for none).
|
|
2555
2884
|
* @returns The live party key: the one just seated, or the stored one.
|
|
2556
2885
|
*/
|
|
2557
|
-
ensureStrandPartyKey(strandId: string, partyMemberPrivateKey?: string): Promise<string>;
|
|
2886
|
+
ensureStrandPartyKey(strandId: string, partyMemberPrivateKey?: string, sign?: OwnerSign): Promise<string>;
|
|
2558
2887
|
/**
|
|
2559
2888
|
* Resolve the party membership key a closed strand's launch threads into the founder
|
|
2560
2889
|
* bootstrap: the explicit attach-time key when given, else the party's persisted
|
|
2561
|
-
* `StrandPartyKey` row, else — on the one machine
|
|
2562
|
-
*
|
|
2563
|
-
*
|
|
2890
|
+
* `StrandPartyKey` row, else — on the one machine the row names as founder (`FounderPeerId`)
|
|
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
|
|
2564
2893
|
* machines never mint (no mint race between a party's machines), and only a founder
|
|
2565
2894
|
* bootstrap needs the key at all.
|
|
2566
2895
|
*
|
|
@@ -2585,26 +2914,35 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2585
2914
|
* Counterpart to {@link createOpenInvitation}, which only mints the
|
|
2586
2915
|
* out-of-band {@link OpenInvitation} envelope: persisting the matching
|
|
2587
2916
|
* `FormationInvite` row is what makes the token *redeemable* — the host's
|
|
2588
|
-
* {@link ControlFormationUsageRecorder}
|
|
2589
|
-
*
|
|
2917
|
+
* {@link ControlFormationUsageRecorder} reads it (`readToken`) to check a
|
|
2918
|
+
* redemption. A host minting a closed-strand invite does both (mint +
|
|
2590
2919
|
* publish), exactly as the integration harness's `createInvitation` does.
|
|
2591
2920
|
*
|
|
2592
|
-
* Signs
|
|
2593
|
-
*
|
|
2594
|
-
* Throws loudly if the node isn't started or exposes no signing key.
|
|
2921
|
+
* Signs through the same owner signer as {@link publishStrand}, whose key must be an
|
|
2922
|
+
* enrolled `OwnerKey`. Throws loudly if the node isn't started or has no owner signer wired.
|
|
2595
2923
|
*
|
|
2596
2924
|
* @param token - Invitation token (the `FormationInvite` primary key); use the
|
|
2597
2925
|
* `token` of the {@link OpenInvitation} from {@link createOpenInvitation}.
|
|
2598
2926
|
* @param sAppId - The sApp a redeemed strand will use.
|
|
2599
2927
|
* @param options - Optional `expiresAtMs` (epoch ms), `totalUses`, `validationUrl`,
|
|
2600
2928
|
* `strandId` (bind a closed/pre-existing host strand for provision-then-record).
|
|
2929
|
+
* @param sign - The `sign` of an owner approval the caller already holds; omitted, this
|
|
2930
|
+
* asks for its own.
|
|
2601
2931
|
*/
|
|
2602
2932
|
publishFormationInvite(token: string, sAppId: string, options?: {
|
|
2603
2933
|
expiresAtMs?: number;
|
|
2604
2934
|
totalUses?: number;
|
|
2605
2935
|
validationUrl?: string;
|
|
2606
2936
|
strandId?: string;
|
|
2607
|
-
}): Promise<void>;
|
|
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;
|
|
2608
2946
|
/**
|
|
2609
2947
|
* Enroll an approver public key allowed to sign off on `ValidationUrl` redemptions.
|
|
2610
2948
|
*
|
|
@@ -2613,10 +2951,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2613
2951
|
* — this is how a party says which outside approver it trusts. Without an enrollment,
|
|
2614
2952
|
* every such invitation is unredeemable.
|
|
2615
2953
|
*
|
|
2616
|
-
* Signs
|
|
2617
|
-
*
|
|
2618
|
-
*
|
|
2619
|
-
*
|
|
2954
|
+
* Signs through the same owner signer as {@link publishStrand}, whose key must be an
|
|
2955
|
+
* enrolled `OwnerKey`. Throws loudly if the node isn't started, has no owner signer wired,
|
|
2956
|
+
* or the key is blank — a blank key would reach the database and fail the signature CHECK
|
|
2957
|
+
* as an opaque constraint error.
|
|
2620
2958
|
*
|
|
2621
2959
|
* Enrolling this node's OWN owner key is permitted and harmless: the domain/action tags
|
|
2622
2960
|
* baked into each authorization digest mean an owner-key signature can never satisfy the
|
|
@@ -2650,22 +2988,22 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2650
2988
|
removeValidationKey(key: string): Promise<void>;
|
|
2651
2989
|
/**
|
|
2652
2990
|
* The approver keys currently enrolled, sorted. Read-only, so unlike
|
|
2653
|
-
* {@link enrollValidationKey} / {@link removeValidationKey} it needs no owner
|
|
2654
|
-
*
|
|
2991
|
+
* {@link enrollValidationKey} / {@link removeValidationKey} it needs no owner signer —
|
|
2992
|
+
* only a started node.
|
|
2655
2993
|
*/
|
|
2656
2994
|
listValidationKeys(): Promise<string[]>;
|
|
2657
2995
|
/**
|
|
2658
|
-
*
|
|
2659
|
-
* {@link unpublishStrand}, {@link
|
|
2660
|
-
* {@link removeValidationKey}
|
|
2661
|
-
*
|
|
2662
|
-
*
|
|
2996
|
+
* The owner signer every owner-signed strand write needs — {@link publishStrand},
|
|
2997
|
+
* {@link unpublishStrand}, {@link ensureStrandPartyKey}, {@link publishFormationInvite},
|
|
2998
|
+
* {@link enrollValidationKey}, {@link removeValidationKey} — failing loudly in one two-part
|
|
2999
|
+
* shape (not started / no signer wired, naming the wiring call as the fix). Enrollment is the
|
|
3000
|
+
* schema's to judge. Narrows `controlDatabase` for the caller: a return means
|
|
2663
3001
|
* `this.controlDatabase` is non-null too.
|
|
2664
3002
|
*
|
|
2665
3003
|
* @param action - Infinitive phrase naming the attempted write, e.g. `'enroll a
|
|
2666
3004
|
* validation key'`; interpolated into both messages.
|
|
2667
3005
|
*/
|
|
2668
|
-
private
|
|
3006
|
+
private requireOwnerSigner;
|
|
2669
3007
|
/**
|
|
2670
3008
|
* Shared strand launch path for both the explicit (`addStrand`) and the
|
|
2671
3009
|
* control-discovered (`handleStrandAdded`) entry points. Resolves the cohort
|
|
@@ -2673,10 +3011,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2673
3011
|
* before emitting `strand:started`.
|
|
2674
3012
|
*
|
|
2675
3013
|
* Founder-ness: an explicit `founder` argument wins (the formation/responder flows pass
|
|
2676
|
-
* one deliberately — their consent-seated rows carry a null `
|
|
3014
|
+
* one deliberately — their consent-seated rows carry a null `FounderPeerId`); when
|
|
2677
3015
|
* unset it is DERIVED from the row, so the control-discovered path
|
|
2678
3016
|
* (`handleStrandAdded`) and a restart's re-attach found this machine's own strands
|
|
2679
|
-
* without the caller having to know. The derivation is pure
|
|
3017
|
+
* without the caller having to know. The derivation is a pure peer-id comparison — no I/O.
|
|
2680
3018
|
*
|
|
2681
3019
|
* Idempotent when the strand manager already tracks `strand.Id` — the watcher
|
|
2682
3020
|
* rediscovers a row this node already started and calls this again via
|
|
@@ -2686,7 +3024,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2686
3024
|
* fan-out, so a rediscovery costs nothing beyond the map lookup plus the (pure)
|
|
2687
3025
|
* founder derivation. NOT a silent no-op any more when the launch resolves as a
|
|
2688
3026
|
* FOUNDER: the tracked instance may have been launched first as a joiner (an app
|
|
2689
|
-
* attach, or this node's own watcher
|
|
3027
|
+
* attach, or this node's own watcher run winning the `resolveCohortSeed` window
|
|
2690
3028
|
* below), which used to drop the founder request and leave the strand headerless —
|
|
2691
3029
|
* now the tracked instance is founded in place
|
|
2692
3030
|
* ({@link StrandInstanceManager.foundExistingStrand}), waking it first if quiesced
|
|
@@ -2698,9 +3036,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2698
3036
|
*
|
|
2699
3037
|
* An absent `sAppConfig` launches a storage replica ({@link CadreNodeConfig.hostUnclaimedStrands}),
|
|
2700
3038
|
* which is ALWAYS a joiner whatever the row says: the founder bootstrap writes the sApp
|
|
2701
|
-
* 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
|
|
2702
3040
|
* wins the race against its app's `addStrand` after a restart comes up as a replica — and
|
|
2703
|
-
* so does a `foundStrand` whose publish a watcher
|
|
3041
|
+
* so does a `foundStrand` whose publish a watcher run saw before its attach. A claim
|
|
2704
3042
|
* that finds a tracked replica upgrades it in place, over the same node and store
|
|
2705
3043
|
* ({@link StrandInstanceManager.attachSApp}), BEFORE any founding: the founder bootstrap
|
|
2706
3044
|
* needs the sApp the attach supplies.
|
|
@@ -2745,10 +3083,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2745
3083
|
* receiver records an admission grant BEFORE `libp2p.start()` (re-)dials the
|
|
2746
3084
|
* relay reservation — the gate denial there is fatal, not degraded. A
|
|
2747
3085
|
* party-member relay answers the RPC (its control node admits us as a
|
|
2748
|
-
* member); a dedicated ops/ relay does not speak the protocol
|
|
2749
|
-
*
|
|
2750
|
-
*
|
|
2751
|
-
*
|
|
3086
|
+
* member); a dedicated ops/ relay does not speak the protocol, comes back
|
|
3087
|
+
* `unsupported`, and is left out of later passes
|
|
3088
|
+
* ({@link relaysRefusingStrandAddr}) — it has no membership gate and needs no
|
|
3089
|
+
* grant. The relay's direct addr rides along as the dial fallback for a relay
|
|
3090
|
+
* we are not yet connected to.
|
|
2752
3091
|
*/
|
|
2753
3092
|
private resolveCohortSeed;
|
|
2754
3093
|
/**
|
|
@@ -2811,20 +3150,25 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2811
3150
|
* `libp2p.start()`. Kept because it costs one spread and this is the one place
|
|
2812
3151
|
* that would silently stop announcing if that rejection is ever relaxed.
|
|
2813
3152
|
*
|
|
3153
|
+
* Less the relays remembered as refusing the strand-addr protocol
|
|
3154
|
+
* ({@link relaysRefusingStrandAddr}): both callers are announcement passes, and
|
|
3155
|
+
* those relays hold no grant.
|
|
3156
|
+
*
|
|
2814
3157
|
* NOTE: a relay the STRAND node discovers on its own (autorelay — in neither
|
|
2815
3158
|
* source) gets no announcement, and a membership-gated one will deny it; fine
|
|
2816
3159
|
* now, every realistic topology feeds one of the two sources.
|
|
2817
3160
|
*/
|
|
2818
3161
|
private circuitRelayTargets;
|
|
2819
3162
|
/**
|
|
2820
|
-
* Record
|
|
2821
|
-
*
|
|
3163
|
+
* Record what a delegate-carrying announce pass learned about the relays it
|
|
3164
|
+
* dialed: the announce timestamps {@link refreshDelegateGrants} throttles on,
|
|
3165
|
+
* and from `outcomes` which relays refuse the strand-addr protocol
|
|
3166
|
+
* ({@link relaysRefusingStrandAddr}). Every announce site records through here.
|
|
2822
3167
|
*
|
|
2823
|
-
*
|
|
2824
|
-
*
|
|
2825
|
-
*
|
|
2826
|
-
*
|
|
2827
|
-
* costs the strand supervisor its first attempt only (the relay denies the
|
|
3168
|
+
* Timestamps are recorded OPTIMISTICALLY, whatever `collectStrandAddrs` reports
|
|
3169
|
+
* per peer: recording only on success would re-announce to an unreachable relay
|
|
3170
|
+
* on every reconcile pass, one dial timeout each. A failed INITIAL announce costs
|
|
3171
|
+
* the strand supervisor its first attempt only (the relay denies the
|
|
2828
3172
|
* reservation; every re-drive re-announces first through
|
|
2829
3173
|
* {@link announceDelegateToRelay}); a failed REFRESH retries within
|
|
2830
3174
|
* `DELEGATE_GRANT_TTL_MS / 2` (15 min), still inside the 30 min TTL.
|
|
@@ -2860,11 +3204,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2860
3204
|
* The throttle map is updated afterwards, so the periodic pass does not announce
|
|
2861
3205
|
* again right away.
|
|
2862
3206
|
*
|
|
2863
|
-
*
|
|
2864
|
-
*
|
|
2865
|
-
*
|
|
2866
|
-
*
|
|
2867
|
-
*
|
|
3207
|
+
* Unlike the other announce passes, this asks a relay remembered in
|
|
3208
|
+
* {@link relaysRefusingStrandAddr} too, and its reply clears the entry — see
|
|
3209
|
+
* there for why. Against a dedicated ops relay (no strand-addr RPC) that costs
|
|
3210
|
+
* one refused protocol negotiation (`unsupported`) per re-drive attempt, bounded
|
|
3211
|
+
* by the supervisor's backoff. Never throws on that path; a relay addr that names
|
|
3212
|
+
* no peer id is logged and skipped (the hook's caller runs the drive regardless).
|
|
2868
3213
|
*
|
|
2869
3214
|
* NOTE: against a relay that is DOWN this hook costs up to two strand-addr
|
|
2870
3215
|
* timeouts (dial by peer id, then by addr; 28.5 s each at the default declared link round trip)
|
|
@@ -2896,9 +3241,11 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2896
3241
|
*
|
|
2897
3242
|
* Due times are per (sibling, strand) and set from each sibling's own outcome, so
|
|
2898
3243
|
* a sibling that connects late — a phone joining after the party's always-on
|
|
2899
|
-
* machines — is asked on the
|
|
2900
|
-
* (its view of the membership may not include us yet) is retried
|
|
2901
|
-
* {@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}).
|
|
2902
3249
|
*
|
|
2903
3250
|
* Distinct from {@link refreshDelegateGrants}, deliberately: that pass covers
|
|
2904
3251
|
* RELAYS on a `DELEGATE_GRANT_TTL_MS / 2` throttle to keep circuit-relay
|
|
@@ -2918,15 +3265,17 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2918
3265
|
*
|
|
2919
3266
|
* `connectedSiblingTargets`' membership read is unbounded, and with zero
|
|
2920
3267
|
* connections its answer is empty whatever the table holds, so decide it from the
|
|
2921
|
-
* connection list instead of paying for the read once a
|
|
3268
|
+
* connection list instead of paying for the read once a pass.
|
|
2922
3269
|
*
|
|
2923
|
-
* NOTE: otherwise the read runs on EVERY
|
|
3270
|
+
* NOTE: otherwise the read runs on EVERY pass with a running strand, not only when
|
|
2924
3271
|
* some (sibling, strand) is due: pruning a departed sibling needs the current
|
|
2925
3272
|
* target set, and whether anyone is due cannot be told without it (a connected
|
|
2926
|
-
* non-member never gets a due time to compare). One more `CadrePeer` read per
|
|
2927
|
-
*
|
|
2928
|
-
*
|
|
2929
|
-
*
|
|
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).
|
|
2930
3279
|
*
|
|
2931
3280
|
* NOTE: one strand-addr RPC per (running strand × due sibling) — each a tiny
|
|
2932
3281
|
* request/response on an already-open control connection. If a node ever runs
|
|
@@ -2971,8 +3320,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
2971
3320
|
*
|
|
2972
3321
|
* NOTE: every pass serialises the whole FRET table (`exportTable()`), verifies one
|
|
2973
3322
|
* signature per record and makes one `peerStore.merge` per peer, per running strand
|
|
2974
|
-
* per
|
|
2975
|
-
* 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
|
|
2976
3325
|
* show up, read only the entries whose address stamp is near the one-hour mark.
|
|
2977
3326
|
*
|
|
2978
3327
|
* @returns the ids of the peers the table holds a usable record for.
|
|
@@ -3129,8 +3478,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3129
3478
|
* The stop + emit are skipped when the strand manager holds no instance for `strandId` —
|
|
3130
3479
|
* e.g. a party owner that published a strand's row but never ran it locally, or an
|
|
3131
3480
|
* explicit {@link stopStrand} for an id this node never started. `hibernationManager`
|
|
3132
|
-
* untrack and the `sAppConfigs` / {@link strandLaunchRefusals} / {@link discoveredStrands}
|
|
3133
|
-
* 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
|
|
3134
3483
|
* that failed before an instance was ever tracked still gets its stray entries cleared.
|
|
3135
3484
|
*
|
|
3136
3485
|
* Dropping the {@link discoveredStrands} entry here covers both callers, and both
|
|
@@ -3321,18 +3670,26 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3321
3670
|
*/
|
|
3322
3671
|
getControlDatabase(): ControlDatabase | null;
|
|
3323
3672
|
/**
|
|
3324
|
-
* Open control-network connections right now
|
|
3325
|
-
*
|
|
3326
|
-
*
|
|
3327
|
-
*
|
|
3328
|
-
* It approximates the precise signal (the block's cluster size), and a caller that
|
|
3329
|
-
* samples it AFTER a write samples a slightly wider window than `committedAlone`
|
|
3330
|
-
* does inside the write itself. It exists so an embedder can warn an owner before a
|
|
3331
|
-
* control write that this machine currently sees none of its siblings, and report
|
|
3332
|
-
* after one that the write may not have travelled — a warning shown unconditionally
|
|
3333
|
-
* 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}.
|
|
3334
3677
|
*/
|
|
3335
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;
|
|
3336
3693
|
/**
|
|
3337
3694
|
* Force a poll of the strand watcher (for testing)
|
|
3338
3695
|
*/
|
|
@@ -3342,10 +3699,26 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3342
3699
|
*/
|
|
3343
3700
|
getSAppConfig(strandId: string): SAppConfig | undefined;
|
|
3344
3701
|
/**
|
|
3345
|
-
*
|
|
3346
|
-
*
|
|
3702
|
+
* Make `signer` this node's owner signer (`owner-signer.ts`): every owner signature the node
|
|
3703
|
+
* produces from now on goes through it — seeds, vouches, removals, invitations, strand and
|
|
3704
|
+
* join rows, and the background re-issues. Builds the owner-capable seed bootstrap service
|
|
3705
|
+
* around it, so this must come before any of those. One signer per node; a node started
|
|
3706
|
+
* with `claim` is refused (its claimant owns it).
|
|
3707
|
+
*
|
|
3708
|
+
* Anchors the signer's key in the node-local trusted-owner store as `genesis`: wiring a key
|
|
3709
|
+
* declares it an authority of this party.
|
|
3347
3710
|
*
|
|
3348
|
-
* @
|
|
3711
|
+
* @throws when the node is not started, was started with `claim`, or already holds the
|
|
3712
|
+
* seed protocol handler (a second wiring, or {@link enableSeedListener} first).
|
|
3713
|
+
*/
|
|
3714
|
+
useOwnerSigner(signer: OwnerSigner): Promise<void>;
|
|
3715
|
+
/**
|
|
3716
|
+
* {@link useOwnerSigner} with a software signer over `ownerPrivateKey` (`softwareOwnerSigner`):
|
|
3717
|
+
* the owner key held in this process's memory. Kept as a convenience because most tests and
|
|
3718
|
+
* the embedders that hold the key themselves wire it this way; one that keeps the key
|
|
3719
|
+
* elsewhere wires its own signer.
|
|
3720
|
+
*
|
|
3721
|
+
* @param ownerPrivateKey - The owner key's 32-byte Ed25519 seed, base64url.
|
|
3349
3722
|
*/
|
|
3350
3723
|
initializeSeedBootstrap(ownerPrivateKey: string): Promise<void>;
|
|
3351
3724
|
/**
|
|
@@ -3392,15 +3765,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3392
3765
|
* never reach here: {@link ControlDatabase.queryCadrePeers} excludes them, so a
|
|
3393
3766
|
* revoked peer is not addressable either.
|
|
3394
3767
|
*
|
|
3395
|
-
* NOTE: answers the whole `CadrePeerRow`, past the
|
|
3768
|
+
* NOTE: answers the whole `CadrePeerRow`, past the fields declared; the integration
|
|
3396
3769
|
* harness (`owner-cli-node.ts`, `OwnerCliMemberRow.sig`) reads `sig` off it through the admin
|
|
3397
3770
|
* channel to tell a row another member seated from one the subject published itself. Narrow
|
|
3398
3771
|
* this to the declared fields only together with that reader.
|
|
3399
3772
|
*/
|
|
3400
|
-
listMembers(): Promise<
|
|
3401
|
-
peerId: string;
|
|
3402
|
-
multiaddr: string | null;
|
|
3403
|
-
}>>;
|
|
3773
|
+
listMembers(): Promise<CohortPeerRow[]>;
|
|
3404
3774
|
/**
|
|
3405
3775
|
* Probe whether a given peer is a `CadrePeer` member.
|
|
3406
3776
|
*
|
|
@@ -3515,7 +3885,7 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3515
3885
|
* builds its snapshot against that anchor, which is also what its previous snapshot was
|
|
3516
3886
|
* judged against. Caught here rather than failing the refresh, because the membership read
|
|
3517
3887
|
* tolerates an unreachable `Revocation` block on an isolated node
|
|
3518
|
-
* (`ControlDatabase.
|
|
3888
|
+
* (`ControlDatabase.readRevocationRowsOrNone`) and `queryRevocations` does not: failing the
|
|
3519
3889
|
* whole refresh on that would freeze such a node's gate snapshot.
|
|
3520
3890
|
* NOTE: on such a node the sync is skipped at every refresh until the block is held; if an
|
|
3521
3891
|
* isolated node ever has to derive owner additions while cut off, give `queryRevocations`
|
|
@@ -3524,11 +3894,12 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3524
3894
|
* NOTE: cost per membership refresh is one `OwnerKey` read and one `Revocation` read (the
|
|
3525
3895
|
* tombstone list, whose `OwnerKey` stamps are handed to `queryOwnerKeyRows` so it does not
|
|
3526
3896
|
* read the retired set again, and which the hang-up of removed peers shares through
|
|
3527
|
-
* `loadTombstones`), plus the
|
|
3528
|
-
*
|
|
3529
|
-
*
|
|
3530
|
-
*
|
|
3531
|
-
*
|
|
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.
|
|
3532
3903
|
*
|
|
3533
3904
|
* @param retry - forwarded to every read; `false` on the refresh path, for the reason on
|
|
3534
3905
|
* {@link refreshAuthorizedControlPeers}.
|
|
@@ -3558,19 +3929,18 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3558
3929
|
/** A per-refresh {@link RevocationSource}: one `queryRevocations` read, shared by every caller in the pass. */
|
|
3559
3930
|
private revocationSource;
|
|
3560
3931
|
/**
|
|
3561
|
-
* The usage and invitation rows {@link hasAnchoredProof}
|
|
3562
|
-
*
|
|
3563
|
-
*
|
|
3564
|
-
*
|
|
3565
|
-
* NOTE: once invitations are the only admission route (ticket
|
|
3566
|
-
* `cadre-invitations-redeemable-by-any-member`), every cadre with a member not yet
|
|
3567
|
-
* re-vouched pays these two whole-table reads and three ed25519 verifies per row on every
|
|
3568
|
-
* membership refresh. Both tables are append-only and a verified chain never changes (expiry,
|
|
3569
|
-
* use count and withdrawal are not re-checked), so if the gate's admission deadline ever
|
|
3570
|
-
* shows this, memoise verified (row stamp → usage stamp) pairs per node and skip the reads
|
|
3571
|
-
* when every admitted row is already memoised.
|
|
3932
|
+
* The usage and invitation rows {@link hasAnchoredProof} resolves an invitation-admitted
|
|
3933
|
+
* `CadrePeer` row through, keyed for lookup. Both reads honour `retry` for
|
|
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.
|
|
3572
3936
|
*/
|
|
3573
3937
|
private loadInvitationChain;
|
|
3938
|
+
/**
|
|
3939
|
+
* The usage and grant rows {@link syncOwnerAnchor} resolves a grant-seated `OwnerKey` row
|
|
3940
|
+
* through, keyed for lookup: the owner-grant twin of {@link loadInvitationChain}, read only by
|
|
3941
|
+
* the anchor sync, since a grant admits no machine.
|
|
3942
|
+
*/
|
|
3943
|
+
private loadOwnerGrantChain;
|
|
3574
3944
|
/**
|
|
3575
3945
|
* The owner-voucher half of {@link hasAnchoredProof}: complete voucher, `VouchOwner` in
|
|
3576
3946
|
* the node-local anchor, signature valid over the row's (PeerId, StampId) voucher digest.
|
|
@@ -3635,15 +4005,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3635
4005
|
private installSeedBootstrapService;
|
|
3636
4006
|
/**
|
|
3637
4007
|
* The event callbacks every {@link SeedBootstrapService} this node owns is
|
|
3638
|
-
* wired with — shared by the owner-capable service ({@link
|
|
4008
|
+
* wired with — shared by the owner-capable service ({@link useOwnerSigner})
|
|
3639
4009
|
* and the listener-only one ({@link enableSeedListener}) so the two cannot drift.
|
|
3640
4010
|
*
|
|
3641
4011
|
* A seed applied by the INBOUND protocol handler writes no `CadrePeer` row of
|
|
3642
|
-
* its own (it merges the libp2p peer store and dials
|
|
4012
|
+
* its own (it merges the libp2p peer store and dials the seed's preferred peers), so the automatic
|
|
3643
4013
|
* write-driven refresh never fires for it — yet applying it can ANCHOR a new
|
|
3644
4014
|
* owner key, which flips rows already present from unauthorized to authorized.
|
|
3645
4015
|
* Hence the explicit refresh here; without it a peer the freshly anchored owner
|
|
3646
|
-
* vouched for stays denied until the next
|
|
4016
|
+
* vouched for stays denied until the next membership change or the backstop pass.
|
|
3647
4017
|
*/
|
|
3648
4018
|
private seedEventCallbacks;
|
|
3649
4019
|
/**
|
|
@@ -3662,13 +4032,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3662
4032
|
* NO `CadrePeer` writer needs to call this: the control database notifies it
|
|
3663
4033
|
* after every committed member-row write (`ControlDatabase.mutateCadrePeer`,
|
|
3664
4034
|
* wired in {@link start}), which is exactly what makes the refresh automatic
|
|
3665
|
-
* rather than a caller obligation.
|
|
3666
|
-
*
|
|
3667
|
-
*
|
|
3668
|
-
*
|
|
3669
|
-
*
|
|
3670
|
-
*
|
|
3671
|
-
*
|
|
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.
|
|
3672
4043
|
*
|
|
3673
4044
|
* Coalescing: marks the snapshot stale and resolves once a refresh that began
|
|
3674
4045
|
* after this call has completed, so a caller that awaits it always observes
|
|
@@ -3681,13 +4052,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3681
4052
|
* connected peer it newly covers — the strand-side counterpart to
|
|
3682
4053
|
* {@link refreshMembershipGate}.
|
|
3683
4054
|
*
|
|
3684
|
-
*
|
|
3685
|
-
*
|
|
3686
|
-
*
|
|
3687
|
-
*
|
|
3688
|
-
* The gate therefore polls (default 30 s). An app that has just called
|
|
3689
|
-
* `revokeMember` or `leaveStrand` should follow the write with this call so
|
|
3690
|
-
* 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.
|
|
3691
4059
|
*
|
|
3692
4060
|
* Quiet no-op for a strand that is not running, is quiesced, is open, or has
|
|
3693
4061
|
* the gate disabled. Never rejects; resolves once the sweep has finished.
|
|
@@ -3717,8 +4085,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3717
4085
|
* CONCURRENT external write's refresh for the life of the scope: that writer's
|
|
3718
4086
|
* promise resolves before its peer is admitted, and admission lands at scope
|
|
3719
4087
|
* exit instead. Acceptable — scope exit is milliseconds away (bounded by the
|
|
3720
|
-
* drain), versus the
|
|
3721
|
-
*
|
|
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.
|
|
3722
4090
|
*/
|
|
3723
4091
|
private deferMembershipGateRefresh;
|
|
3724
4092
|
/**
|
|
@@ -3729,13 +4097,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3729
4097
|
* @param multiaddrs - Optional multiaddrs for the peer
|
|
3730
4098
|
*/
|
|
3731
4099
|
authorizePeer(peerId: string, multiaddrs?: string[]): Promise<void>;
|
|
4100
|
+
/** {@link authorizePeer}'s body, signing inside the caller's owner approval when it passes `sign` ({@link claimNode}). */
|
|
4101
|
+
private vouchPeer;
|
|
3732
4102
|
/**
|
|
3733
4103
|
* Remove a previously-authorized peer from the cadre.
|
|
3734
4104
|
* Signs the peer ID with the owner key and deletes the CadrePeer row.
|
|
3735
4105
|
*
|
|
3736
|
-
* Removes the `CadrePeer` row only. The call that removes a machine completely (its
|
|
3737
|
-
*
|
|
3738
|
-
*
|
|
4106
|
+
* Removes the `CadrePeer` row only. The call that removes a machine completely (its push
|
|
4107
|
+
* token and the invitations it could come back through too, in one transaction) is
|
|
4108
|
+
* {@link removeMachine}.
|
|
3739
4109
|
*
|
|
3740
4110
|
* @param peerId - The peer ID to remove
|
|
3741
4111
|
*/
|
|
@@ -3745,55 +4115,123 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3745
4115
|
* `OwnerKey.Authorized`; the row stores the vouching pair). The write notifies the
|
|
3746
4116
|
* membership hub, so the refresh it triggers has run by the time this resolves.
|
|
3747
4117
|
*
|
|
4118
|
+
* Refused while alone ({@link ownerKeyWriteAlone}): an `OwnerKey` add has no re-issue path,
|
|
4119
|
+
* so one committed with no control connection would stay on this node.
|
|
4120
|
+
*
|
|
3748
4121
|
* @param ownerPublicKeyB64 - the new owner's ed25519 public key (base64url).
|
|
4122
|
+
* @param options.label - the user's name for the key ("phone", "paper backup"), bound into
|
|
4123
|
+
* the signature; trimmed, at most 64 characters, blank meaning none.
|
|
3749
4124
|
* @returns `true` when the row was seated, `false` when the key is already a live owner.
|
|
4125
|
+
* @throws OwnerKeyAddRefusedError `alone`, before any write.
|
|
4126
|
+
*/
|
|
4127
|
+
addOwner(ownerPublicKeyB64: string, options?: {
|
|
4128
|
+
label?: string;
|
|
4129
|
+
}): Promise<boolean>;
|
|
4130
|
+
/**
|
|
4131
|
+
* Whether an `OwnerKey` add made now ({@link addOwner}, or the re-add of
|
|
4132
|
+
* {@link removeOwner}'s re-vouch) is refused `alone`: this node holds no control connection
|
|
4133
|
+
* while the party has other members. An `OwnerKey` row has no update to re-touch, so nothing
|
|
4134
|
+
* re-issues it on cohort growth, and a row written now would fork this node's history. A
|
|
4135
|
+
* party with no other members cannot fork, so a solo founder adding an owner key is not
|
|
4136
|
+
* refused.
|
|
4137
|
+
*
|
|
4138
|
+
* Waits out an in-flight membership refresh first: until `start`'s first one lands the
|
|
4139
|
+
* member snapshot is empty, which would read as a party with no other members.
|
|
4140
|
+
*/
|
|
4141
|
+
private ownerKeyWriteAlone;
|
|
4142
|
+
/**
|
|
4143
|
+
* Every live owner key with its label, who vouched it, whether this node's owner signer
|
|
4144
|
+
* holds it and whether this node's trusted-owner anchor trusts it ({@link CadreOwnerKey}),
|
|
4145
|
+
* for an app's owner-key screen.
|
|
4146
|
+
*/
|
|
4147
|
+
listOwnerKeys(): Promise<CadreOwnerKey[]>;
|
|
4148
|
+
/**
|
|
4149
|
+
* What {@link removeOwner} would do for `ownerPublicKeyB64`, read without writing, for an
|
|
4150
|
+
* app's confirmation step ({@link OwnerKeyRemovalPlan}): whether this node may remove it,
|
|
4151
|
+
* which rows the key vouched (so the app can offer to keep them by re-vouching), and how this
|
|
4152
|
+
* node's own rows depend on it (`planOwnerKeyRemoval` in `owner-keys.ts`).
|
|
4153
|
+
*/
|
|
4154
|
+
planOwnerKeyRemoval(ownerPublicKeyB64: string): Promise<OwnerKeyRemovalPlan>;
|
|
4155
|
+
/**
|
|
4156
|
+
* Remove an owner key, signed by this node's owner key (which must be a different owner): the
|
|
4157
|
+
* `OwnerKey` row and its `Revocation` tombstone commit together, and the tombstone rides the
|
|
4158
|
+
* committed-delete seam ({@link noteGuardedDelete}), so one committed while alone is re-issued
|
|
4159
|
+
* on cohort growth like every other removal. The machines the key was used on stay members;
|
|
4160
|
+
* removing one is {@link removeMachine}. The owner grants the key issued stop being redeemable
|
|
4161
|
+
* (the plan's `grants` names the live ones) and are not withdrawn: a tombstone over a grant
|
|
4162
|
+
* nobody can redeem would add nothing.
|
|
4163
|
+
*
|
|
4164
|
+
* Steps: re-plan ({@link planOwnerKeyRemoval}) and throw {@link OwnerKeyRemovalRefusedError}
|
|
4165
|
+
* on a refusal; check `options.revouch` against the plan's dependents and throw before any
|
|
4166
|
+
* write when it names anything else; refuse `alone` ({@link ownerKeyWriteAlone}) when an
|
|
4167
|
+
* `OwnerKey` row is to be re-vouched, since that re-add has no re-issue path; **re-vouch
|
|
4168
|
+
* first**, under this node's key, the dependents the caller chose to keep and this node's own
|
|
4169
|
+
* row when the key vouched it (`ControlDatabase.revouchOwnerKey`, `reauthorizePeer`), so they
|
|
4170
|
+
* are never untrusted in between, and stop before the removal if one fails (re-vouches
|
|
4171
|
+
* already made stay, harmlessly); then the removal. The re-vouches and the removal are one owner approval. The
|
|
4172
|
+
* write notifies the membership hub, so the refresh it triggers has run by the time this
|
|
4173
|
+
* resolves.
|
|
4174
|
+
*
|
|
4175
|
+
* @param ownerPublicKeyB64 - the owner's ed25519 public key (base64url).
|
|
4176
|
+
* @returns what was written ({@link OwnerKeyRemovalResult}).
|
|
4177
|
+
* @throws {OwnerKeyRemovalRefusedError} `'not-an-owner'`, `'unknown'`, `'last-owner'`,
|
|
4178
|
+
* `'self'` or `'alone'`, before any write.
|
|
3750
4179
|
*/
|
|
3751
|
-
|
|
4180
|
+
removeOwner(ownerPublicKeyB64: string, options?: RemoveOwnerOptions): Promise<OwnerKeyRemovalResult>;
|
|
4181
|
+
/** {@link planOwnerKeyRemoval} over `controlDatabase`, for a caller that already holds it. */
|
|
4182
|
+
private planOwnerKeyRemovalWith;
|
|
3752
4183
|
/**
|
|
3753
|
-
*
|
|
3754
|
-
* the
|
|
3755
|
-
*
|
|
3756
|
-
*
|
|
3757
|
-
*
|
|
4184
|
+
* The re-vouch step of {@link removeOwner}: every chosen dependent row is rebound to this
|
|
4185
|
+
* node's key BEFORE the removal, so no reader ever judges it against an owner that is gone.
|
|
4186
|
+
* Owners through `ControlDatabase.revouchOwnerKey` (a delete-and-re-add under a fresh stamp,
|
|
4187
|
+
* the only rebinding `OwnerKey.NoUpdate` allows); peers through the owner re-touch
|
|
4188
|
+
* (`reauthorizePeer`), which rewrites `VouchOwner`/`VouchSig` and clears `VouchUsage`.
|
|
3758
4189
|
*
|
|
3759
|
-
*
|
|
3760
|
-
*
|
|
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.
|
|
3761
4198
|
*
|
|
3762
|
-
*
|
|
3763
|
-
*
|
|
4199
|
+
* A failure rethrows and stops the call before the removal; re-vouches already made stay
|
|
4200
|
+
* (the row is only re-signed by a current owner) and the caller retries. An owner re-vouch
|
|
4201
|
+
* is refused alone before this runs; one whose last connection drops while it runs is logged,
|
|
4202
|
+
* as {@link addOwner}'s add is.
|
|
4203
|
+
*
|
|
4204
|
+
* NOTE: a peer re-touch committed alone is not queued, and the drain skips self-published
|
|
4205
|
+
* rows anyway, so the other machines may get the removal's tombstone without the new voucher
|
|
4206
|
+
* (`tickets/backlog/bug-owner-key-removal-revouch-made-alone-is-not-reissued`).
|
|
3764
4207
|
*/
|
|
3765
|
-
|
|
4208
|
+
private revouchDependents;
|
|
3766
4209
|
/**
|
|
3767
4210
|
* The cadre's machines as an app lists them ({@link CadreMachine}): one entry per live
|
|
3768
|
-
* `CadrePeer` row, judged by this node's membership predicate
|
|
3769
|
-
*
|
|
4211
|
+
* `CadrePeer` row, judged by this node's membership predicate (`describeMachines` in
|
|
4212
|
+
* `machine-removal.ts`). Owner keys are not machines; {@link listOwnerKeys} lists them.
|
|
3770
4213
|
* Unlocked reads with the default retry.
|
|
3771
4214
|
*/
|
|
3772
4215
|
listMachines(): Promise<CadreMachine[]>;
|
|
3773
4216
|
/**
|
|
3774
4217
|
* What {@link removeMachine} would do for `peerId`, read without writing, for an app's
|
|
3775
|
-
* confirmation step ({@link MachineRemovalPlan}): whether this node may remove it
|
|
3776
|
-
*
|
|
3777
|
-
* re-vouching), which invitations it could come back through, and whether this node's own
|
|
3778
|
-
* rows depend on it (`planMachineRemoval` in `machine-removal.ts`).
|
|
4218
|
+
* confirmation step ({@link MachineRemovalPlan}): whether this node may remove it and which
|
|
4219
|
+
* invitations it could come back through (`planMachineRemoval` in `machine-removal.ts`).
|
|
3779
4220
|
*/
|
|
3780
4221
|
planMachineRemoval(peerId: string): Promise<MachineRemovalPlan>;
|
|
3781
4222
|
/**
|
|
3782
4223
|
* Remove a machine from the cadre completely, signed by this node's owner key: its
|
|
3783
|
-
* `CadrePeer` row, its `
|
|
3784
|
-
*
|
|
3785
|
-
*
|
|
3786
|
-
*
|
|
3787
|
-
*
|
|
3788
|
-
*
|
|
3789
|
-
*
|
|
3790
|
-
*
|
|
3791
|
-
*
|
|
3792
|
-
*
|
|
3793
|
-
*
|
|
3794
|
-
* made stay, harmlessly); then the removal transaction. Afterwards this node forgets the
|
|
3795
|
-
* machine as a dial target, and the refresh the write notifies hangs up its control
|
|
3796
|
-
* connection ({@link hangUpRevokedControlPeers}) before this resolves.
|
|
4224
|
+
* `CadrePeer` row, its `DeviceToken` row (a removed phone must not be push-woken) and a
|
|
4225
|
+
* withdrawal of every live invitation it could redeem to come back, in ONE transaction
|
|
4226
|
+
* ({@link ControlDatabase.removeMachine}), with a `Revocation` tombstone per row so the
|
|
4227
|
+
* removal holds on every machine ("Deletes made while alone" in docs/architecture.md), under
|
|
4228
|
+
* one owner approval. Afterwards this node forgets the machine as a dial target, and the
|
|
4229
|
+
* refresh the write notifies hangs up its control connection ({@link hangUpRevokedControlPeers})
|
|
4230
|
+
* before this resolves.
|
|
4231
|
+
*
|
|
4232
|
+
* Owner keys stay: they are the person's, not the machine's. A lost phone that held one can
|
|
4233
|
+
* still sign, a fresh machine invitation it could redeem included, so removing that key
|
|
4234
|
+
* ({@link removeOwner}) is the step that locks it out.
|
|
3797
4235
|
*
|
|
3798
4236
|
* Not undone by this call: the machine keeps every row and strand block it already synced
|
|
3799
4237
|
* and the party's strand member keys (`tickets/blocked/decide-removed-machine-strand-access`).
|
|
@@ -3801,37 +4239,14 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3801
4239
|
*
|
|
3802
4240
|
* @returns what was written ({@link MachineRemovalResult}); all false when no live row names
|
|
3803
4241
|
* the machine here (already removed elsewhere).
|
|
3804
|
-
* @throws {MachineRemovalRefusedError} `'not-an-owner'
|
|
4242
|
+
* @throws {MachineRemovalRefusedError} `'not-an-owner'` or `'self'`, before any write.
|
|
3805
4243
|
*/
|
|
3806
|
-
removeMachine(peerId: string
|
|
4244
|
+
removeMachine(peerId: string): Promise<MachineRemovalResult>;
|
|
3807
4245
|
/**
|
|
3808
|
-
* The
|
|
3809
|
-
*
|
|
4246
|
+
* The read behind {@link listMachines}: the live `CadrePeer` rows projected as machines, with
|
|
4247
|
+
* this node's membership predicate as the `trusted` test.
|
|
3810
4248
|
*/
|
|
3811
4249
|
private readMachines;
|
|
3812
|
-
/** {@link planMachineRemoval} with the rows it was judged from, which {@link removeMachine} re-vouches dependents by. */
|
|
3813
|
-
private planMachineRemovalWith;
|
|
3814
|
-
/**
|
|
3815
|
-
* The re-vouch step of {@link removeMachine}: every chosen dependent row is rebound to this
|
|
3816
|
-
* node's key BEFORE the removal, so no reader ever judges it against an owner that is gone.
|
|
3817
|
-
* Owners through `ControlDatabase.revouchOwnerKey` (a delete-and-re-add under a fresh stamp,
|
|
3818
|
-
* the only rebinding `OwnerKey.NoUpdate` allows); peers through the owner re-touch
|
|
3819
|
-
* (`reauthorizePeer`), which rewrites `VouchOwner`/`VouchSig` and clears `VouchUsage`.
|
|
3820
|
-
*
|
|
3821
|
-
* The peer re-touch keeps the row's `UpdatedAt` where it is. The owner branch of
|
|
3822
|
-
* `CadrePeer.AuthorizedUpdate` does not require a bump, and bumping a self-published row
|
|
3823
|
-
* would leave its `Sig` unverifiable, so unresolvable and unlisted as self-published, until
|
|
3824
|
-
* the machine's next heartbeat (the NOTE on that constraint); the write-while-alone drain
|
|
3825
|
-
* bumps because it re-issues an otherwise unchanged row, whereas here the voucher itself
|
|
3826
|
-
* changes. A row never self-published carries no stamp to keep and gets the clock.
|
|
3827
|
-
*
|
|
3828
|
-
* A failure rethrows and stops the call before the removal; re-vouches already made stay
|
|
3829
|
-
* (the row is only re-signed by a current owner) and the caller retries. An owner re-vouch
|
|
3830
|
-
* committed alone is not re-issued on growth (the add half has no re-issue path,
|
|
3831
|
-
* `tickets/backlog/debt-owner-key-add-while-alone-has-no-reissue`), so it is logged as
|
|
3832
|
-
* {@link addOwner} logs it; the peer re-touches are re-issued by the existing drain.
|
|
3833
|
-
*/
|
|
3834
|
-
private revouchDependents;
|
|
3835
4250
|
/**
|
|
3836
4251
|
* Create a seed from the current control network state.
|
|
3837
4252
|
* The seed contains peer information and is signed by an owner.
|
|
@@ -3852,9 +4267,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3852
4267
|
trustPolicy?: SeedTrustPolicy;
|
|
3853
4268
|
}): Promise<ApplySeedResult>;
|
|
3854
4269
|
/**
|
|
3855
|
-
* Post-process an {@link applySeed} outcome: retain the seed's
|
|
4270
|
+
* Post-process an {@link applySeed} outcome: retain the seed's preferred peers as
|
|
3856
4271
|
* cold-start bootstrap targets, and surface a seed that was accepted but whose
|
|
3857
|
-
* every
|
|
4272
|
+
* every dial failed — the node is now seeded yet unconnected, and the
|
|
3858
4273
|
* cold-start reconcile branch is what gets it out of that state.
|
|
3859
4274
|
*/
|
|
3860
4275
|
private noteAppliedSeed;
|
|
@@ -3895,12 +4310,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3895
4310
|
* that connection from the addresses it was handed, on this launch or a later
|
|
3896
4311
|
* one.
|
|
3897
4312
|
*
|
|
3898
|
-
* Nothing is dialed here: the drone has not received the seed yet.
|
|
3899
|
-
*
|
|
3900
|
-
*
|
|
3901
|
-
*
|
|
3902
|
-
*
|
|
3903
|
-
*
|
|
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.
|
|
3904
4320
|
*/
|
|
3905
4321
|
addDrone(options: AddDroneOptions): Promise<DroneInitResult>;
|
|
3906
4322
|
/**
|
|
@@ -3910,9 +4326,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3910
4326
|
* vouch the node into this cadre and keep its addresses as a dial target. The owner
|
|
3911
4327
|
* side of the claim-secret entry in the Seed Delivery Protocol (`docs/architecture.md`).
|
|
3912
4328
|
*
|
|
3913
|
-
* Requires the seed service with an owner key ({@link
|
|
3914
|
-
* {@link addDrone} does
|
|
3915
|
-
*
|
|
4329
|
+
* Requires the seed service with an owner key ({@link useOwnerSigner}), as
|
|
4330
|
+
* {@link addDrone} does, and like it is one owner approval (the seed and the vouch). The
|
|
4331
|
+
* order is the REVERSE of `addDrone`'s: seed first, row second. A refused claim or a
|
|
4332
|
+
* failed delivery therefore leaves no `CadrePeer` row and
|
|
3916
4333
|
* no retained dial target behind on this node — a phone that typed the wrong secret,
|
|
3917
4334
|
* or scanned a node someone else already owns, has nothing to clean up.
|
|
3918
4335
|
*
|
|
@@ -3932,12 +4349,19 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3932
4349
|
* by the node (its anchored branch), the row insert is a no-op on an existing row
|
|
3933
4350
|
* (`ControlDatabase.insertCadrePeer`), and the dial target is replaced, not duplicated.
|
|
3934
4351
|
*
|
|
3935
|
-
*
|
|
3936
|
-
*
|
|
3937
|
-
*
|
|
3938
|
-
*
|
|
3939
|
-
*
|
|
3940
|
-
*
|
|
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.
|
|
3941
4365
|
*
|
|
3942
4366
|
* @throws {ClaimRefusedError} when the node was reached and refused the claim.
|
|
3943
4367
|
* @throws {PeerUnreachableError} when the node was never reached.
|
|
@@ -3947,6 +4371,33 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3947
4371
|
multiaddrs: string[];
|
|
3948
4372
|
secret: string;
|
|
3949
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;
|
|
3950
4401
|
/**
|
|
3951
4402
|
* Add a phone to the cadre with relay support.
|
|
3952
4403
|
* Use when both nodes are NAT'd (phone-to-phone).
|
|
@@ -3969,13 +4420,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
3969
4420
|
/**
|
|
3970
4421
|
* Mint an owner-signed cadre invitation that a device can redeem at ANY member machine of
|
|
3971
4422
|
* this party, the owner offline included ({@link redeemCadreInvitation}). Requires the
|
|
3972
|
-
* owner key ({@link
|
|
4423
|
+
* owner key ({@link useOwnerSigner}).
|
|
3973
4424
|
*
|
|
3974
4425
|
* The invitation is an ed25519 keypair: the public half is the `CadreInvite` row's key,
|
|
3975
|
-
* the private half rides in the returned bundle as the proof of possession.
|
|
3976
|
-
*
|
|
3977
|
-
*
|
|
3978
|
-
*
|
|
4426
|
+
* the private half rides in the returned bundle as the proof of possession. It admits a
|
|
4427
|
+
* machine and grants nothing else (ownership is {@link createOwnerGrant}'s), so it defaults
|
|
4428
|
+
* to a 24-hour lifetime ({@link CADRE_INVITATION_DEFAULT_TTL_MS}) and one use unless `uses`
|
|
4429
|
+
* says otherwise. The bundle carries the signed row (so a
|
|
3979
4430
|
* member that has not received it by replication seats it from the bundle), this node's
|
|
3980
4431
|
* anchored owner keys — every current owner this node has verified, the out-of-band ones
|
|
3981
4432
|
* and those derived along verifiable chains ({@link syncOwnerAnchor}) — (the device pins
|
|
@@ -4000,7 +4451,15 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4000
4451
|
* @throws when no owner key is wired, the anchor is empty, `uses` is not a positive
|
|
4001
4452
|
* integer, or neither this machine nor any other member has an address.
|
|
4002
4453
|
*/
|
|
4003
|
-
createCadreInvitation(options
|
|
4454
|
+
createCadreInvitation(options?: CreateCadreInvitationOptions): Promise<CreateCadreInvitationResult>;
|
|
4455
|
+
/**
|
|
4456
|
+
* What the two credentials an owner mints share ({@link createCadreInvitation},
|
|
4457
|
+
* {@link createOwnerGrant}), checked in this order: the owner-capable service; the anchor's
|
|
4458
|
+
* keys the holder pins (an empty anchor is refused rather than falling back to the table, since
|
|
4459
|
+
* a reply could not be checked against it); the use count; and the member addresses with their
|
|
4460
|
+
* reach, refused when neither this machine nor any other member has one.
|
|
4461
|
+
*/
|
|
4462
|
+
private issuedBundleParts;
|
|
4004
4463
|
/**
|
|
4005
4464
|
* Every cadre invitation this node holds — minted here or replicated in — with its
|
|
4006
4465
|
* standing ({@link CadreInviteStatus}: live, withdrawn, redemptions recorded).
|
|
@@ -4026,11 +4485,13 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4026
4485
|
* Steps: refuse an invitation for another party; pin its `ownerKeys` into the node-local
|
|
4027
4486
|
* anchor (`'invite'` provenance — the pin sticks even if the redemption then fails, as it
|
|
4028
4487
|
* does for a seed invite, so a later attempt or seed from the same owner is anchored);
|
|
4029
|
-
* sign the request
|
|
4030
|
-
* acceptance verify the reply against the pinned
|
|
4031
|
-
* store, retain the answering member as a
|
|
4032
|
-
*
|
|
4033
|
-
* 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.
|
|
4034
4495
|
*
|
|
4035
4496
|
* A device that is already a member (a restart with the invitation still configured) is
|
|
4036
4497
|
* answered as accepted by the member's idempotent redemption, with no second usage row.
|
|
@@ -4044,11 +4505,9 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4044
4505
|
* pass whose reply was lost answers the next as already a member. `options.onProgress`
|
|
4045
4506
|
* reports each pass and pause.
|
|
4046
4507
|
*
|
|
4047
|
-
*
|
|
4048
|
-
*
|
|
4049
|
-
*
|
|
4050
|
-
* machines derive this node's key into their anchors from the row's stored proof on their
|
|
4051
|
-
* next membership refresh ({@link syncOwnerAnchor}).
|
|
4508
|
+
* The invitation admits this node as a machine and nothing else: ownership is an owner
|
|
4509
|
+
* grant ({@link redeemOwnerGrant}, {@link joinWithOwnerGrant}). A key of this node's that is
|
|
4510
|
+
* already an owner here is wired afterwards with {@link wireOwnKeyIfOwner}(signer).
|
|
4052
4511
|
*
|
|
4053
4512
|
* Limit: the reply check ({@link verifyRedeemReply}) does not authenticate the member. A
|
|
4054
4513
|
* forged bundle, or a legitimate one whose addresses were swapped, dials a machine that
|
|
@@ -4064,24 +4523,175 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4064
4523
|
*/
|
|
4065
4524
|
redeemCadreInvitation(invitation: CadreInvitation, options?: RedeemCadreInvitationOptions): Promise<RedeemCadreInvitationResult>;
|
|
4066
4525
|
/**
|
|
4067
|
-
* If
|
|
4068
|
-
*
|
|
4069
|
-
*
|
|
4070
|
-
*
|
|
4071
|
-
* `
|
|
4526
|
+
* If `signer`'s key is a live `OwnerKey` here, wire it ({@link useOwnerSigner}) and return
|
|
4527
|
+
* true; otherwise change nothing and return false. For a machine that joined someone else's
|
|
4528
|
+
* cadre: after an owner grant seated its key, and on every later start.
|
|
4529
|
+
*
|
|
4530
|
+
* Without `signer` the candidate is the signer already wired, so the call answers whether
|
|
4531
|
+
* that key is still a live owner; with nothing wired it is false. The node's identity key is
|
|
4532
|
+
* never a candidate: a machine's network identity is never an owner key.
|
|
4072
4533
|
*
|
|
4073
4534
|
* Unlike `ensureOwnerKey` it never inserts an `OwnerKey` row, so a machine that joined a
|
|
4074
4535
|
* cadre can never genesis-insert itself into it, and a non-owner never anchors its own key.
|
|
4075
4536
|
* An owner whose key is already wired (an earlier call) is answered true with nothing redone.
|
|
4076
4537
|
*
|
|
4077
4538
|
* NOTE: a node running the listener-only seed service ({@link enableSeedListener}) cannot be
|
|
4078
|
-
* wired this way: libp2p takes one seed handler, and {@link
|
|
4079
|
-
*
|
|
4080
|
-
*
|
|
4081
|
-
*
|
|
4082
|
-
* @throws when the node is not started
|
|
4083
|
-
|
|
4084
|
-
|
|
4539
|
+
* wired this way: libp2p takes one seed handler, and {@link useOwnerSigner} would register a
|
|
4540
|
+
* second. Nothing that joins by invitation enables the listener today; if one does, replace
|
|
4541
|
+
* the listener here rather than beside it.
|
|
4542
|
+
*
|
|
4543
|
+
* @throws when the node is not started, or when `signer` names a different key from the
|
|
4544
|
+
* signer already wired (one signer per node).
|
|
4545
|
+
*/
|
|
4546
|
+
wireOwnKeyIfOwner(signer?: OwnerSigner): Promise<boolean>;
|
|
4547
|
+
/**
|
|
4548
|
+
* Mint an owner-signed owner grant: whoever holds the returned bundle can seat an owner key of
|
|
4549
|
+
* their own through ANY member machine of this party ({@link redeemOwnerGrant}), or join the
|
|
4550
|
+
* party as a machine and an owner at once ({@link joinWithOwnerGrant}), the owner that minted
|
|
4551
|
+
* it offline or not. Requires the owner key ({@link useOwnerSigner}); one owner approval.
|
|
4552
|
+
*
|
|
4553
|
+
* The grant is an ed25519 keypair: the public half is the `OwnerGrant` row's key, the private
|
|
4554
|
+
* half rides in the bundle as the proof of possession. It admits no machine and names no peer
|
|
4555
|
+
* id. Whoever holds the bundle can become an owner, so it defaults to a 15-minute lifetime
|
|
4556
|
+
* ({@link OWNER_GRANT_DEFAULT_TTL_MS}) and one use; `expiresInMs: null` mints a BACKUP grant
|
|
4557
|
+
* with no expiry, which must be asked for explicitly and kept as carefully as an owner key.
|
|
4558
|
+
* The label is the issuer's choice and goes on every key the grant seats ({@link addOwner}'s
|
|
4559
|
+
* rule: trimmed, at most 64 characters, blank meaning none). The bundle carries the signed
|
|
4560
|
+
* row, this node's anchored owner keys and at most six member addresses, chosen as
|
|
4561
|
+
* {@link createCadreInvitation} chooses them and for the same reasons; `reach` says where a
|
|
4562
|
+
* holder could redeem it once this machine is gone. A backup's addresses are those at mint
|
|
4563
|
+
* time, so it can go stale if every member's address changes.
|
|
4564
|
+
*
|
|
4565
|
+
* @throws when no owner key is wired, the anchor is empty, `uses` is not a positive integer,
|
|
4566
|
+
* the label is over 64 characters, or neither this machine nor any other member has an address.
|
|
4567
|
+
*/
|
|
4568
|
+
createOwnerGrant(options?: CreateOwnerGrantOptions): Promise<CreateOwnerGrantResult>;
|
|
4569
|
+
/**
|
|
4570
|
+
* Every owner grant this node holds — minted here or replicated in — with its standing
|
|
4571
|
+
* ({@link OwnerGrantStatus}: live, withdrawn, redemptions recorded).
|
|
4572
|
+
*/
|
|
4573
|
+
listOwnerGrants(): Promise<OwnerGrantStatus[]>;
|
|
4574
|
+
/**
|
|
4575
|
+
* Withdraw an owner grant: an owner-signed `Revocation` tombstone over its row, which stays as
|
|
4576
|
+
* the proof behind every key it seated; those keys stay owners (removing one is
|
|
4577
|
+
* {@link removeOwner}). No further redemption succeeds anywhere the tombstone reaches, and a
|
|
4578
|
+
* member holding it refuses the bundle's copy of the row. A withdrawal committed while alone is
|
|
4579
|
+
* re-issued on cohort growth like any other tombstone.
|
|
4580
|
+
*
|
|
4581
|
+
* @returns `true` when this call filed the tombstone, `false` when the row is not held here or
|
|
4582
|
+
* was already withdrawn.
|
|
4583
|
+
*/
|
|
4584
|
+
withdrawOwnerGrant(key: string): Promise<boolean>;
|
|
4585
|
+
/**
|
|
4586
|
+
* Become an owner of this node's party with an owner grant: seat `signer`'s key as an
|
|
4587
|
+
* `OwnerKey` row by consent, with no owner online. `signer` holds the key to seat; its one
|
|
4588
|
+
* `approve` names the party and says the key becomes an owner of it, and signs the `'consent'`
|
|
4589
|
+
* digest. The grant's `'redeem'` signature is made with the bundle's private key in memory.
|
|
4590
|
+
* A bundle for another party is refused before anything is pinned, dialed or written.
|
|
4591
|
+
*
|
|
4592
|
+
* Two paths, chosen by whether this node is a live member of the party ({@link isLiveMemberSelf})
|
|
4593
|
+
* or an enrolled owner of it ({@link isOwner}):
|
|
4594
|
+
*
|
|
4595
|
+
* - **Local**, on a member machine: the grant row is seated from the bundle when absent (its
|
|
4596
|
+
* own transaction) and the redemption written in this node's own control database
|
|
4597
|
+
* (`ControlDatabase.redeemOwnerGrant`), replicating like any other write. Refused `alone`
|
|
4598
|
+
* under {@link addOwner}'s rule, before the approval: an `OwnerKey` row has no re-issue
|
|
4599
|
+
* path, so one committed with no control connection while the party has other members would
|
|
4600
|
+
* stay here; connect, then try again. A grant that is not live here (withdrawn, expired,
|
|
4601
|
+
* spent, its issuer no longer an owner) is refused before the approval too, unless the key
|
|
4602
|
+
* is already an owner. The bundle's `ownerKeys` are NOT pinned: this node judges the issuer
|
|
4603
|
+
* by its own `OwnerKey` table, and its anchor must not be widened by pasted text.
|
|
4604
|
+
* - **Protocol**, otherwise (a device with no machine in the cadre; a node started in the
|
|
4605
|
+
* party on an empty store): the bundle's `ownerKeys` are pinned into the node-local anchor
|
|
4606
|
+
* (`'invite'` provenance, the pin a credential the owner handed out carries, as
|
|
4607
|
+
* {@link redeemCadreInvitation} pins), then the members the bundle names are dialed over
|
|
4608
|
+
* `/sereus/owner-grant/1.0.0` (`redeemOwnerGrantInPasses`), one signed request for every
|
|
4609
|
+
* address and every pass as {@link redeemCadreInvitation} dials, so a retry after a lost
|
|
4610
|
+
* reply is answered `alreadyOwner`. The member that accepts pushes its control store here
|
|
4611
|
+
* and writes the row with this node counted in its write cohort, so a member alone with
|
|
4612
|
+
* this node still commits. `options` are the invitation's (`waitForMember`, `signal`,
|
|
4613
|
+
* `onProgress`).
|
|
4614
|
+
*
|
|
4615
|
+
* Neither path wires the signer: call {@link wireOwnKeyIfOwner}(signer) afterwards — at once
|
|
4616
|
+
* on the local path, and on the protocol path once the row has synced here. A key that is
|
|
4617
|
+
* already a live owner is answered `alreadyOwner` with no use spent, on both paths. To join
|
|
4618
|
+
* the party as a machine too, use {@link joinWithOwnerGrant}.
|
|
4619
|
+
*
|
|
4620
|
+
* @throws a plain `Error` for a node not started or a bundle naming another party;
|
|
4621
|
+
* {@link OwnerKeyAddRefusedError} `alone` on the local path, `OwnerGrantIssuerUnknownError`
|
|
4622
|
+
* when the issuer is not an owner here, `OwnerGrantRejectedError` `grant-spent` for a
|
|
4623
|
+
* grant no longer live here, and `OwnerGrantRejectedError` `conflict` (retryable) when the
|
|
4624
|
+
* cadre holds the grant row and this node has not synced it yet; on the protocol path
|
|
4625
|
+
* `OwnerGrantRejectedError` on a final refusal, `OwnerGrantReplyInvalidError` on an
|
|
4626
|
+
* acceptance that fails the reply check,
|
|
4627
|
+
* `OwnerGrantUnreachableError` when every address was tried (one pass only), and the
|
|
4628
|
+
* signal's reason on an abort.
|
|
4629
|
+
*/
|
|
4630
|
+
redeemOwnerGrant(bundle: OwnerGrantBundle, signer: OwnerSigner, options?: RedeemOwnerGrantOptions): Promise<RedeemOwnerGrantResult>;
|
|
4631
|
+
/**
|
|
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`).
|
|
4637
|
+
*/
|
|
4638
|
+
private redeemOwnerGrantLocally;
|
|
4639
|
+
/**
|
|
4640
|
+
* {@link redeemOwnerGrant}'s protocol path, given the signed request: dial the bundle's members
|
|
4641
|
+
* in passes, then refresh the membership gate, since the rows the member pushed and wrote are
|
|
4642
|
+
* judged against the pins and the anchor derives this node's key from them once its row is here.
|
|
4643
|
+
*/
|
|
4644
|
+
private redeemOwnerGrantAtMembers;
|
|
4645
|
+
/** The signed request for `bundle` seating `signer`'s key, under an open approval's `sign`; the nonce is minted here, by the holder. */
|
|
4646
|
+
private signOwnerGrantRequest;
|
|
4647
|
+
/**
|
|
4648
|
+
* Join this node's party as a machine AND an owner with one owner grant, for a node started in
|
|
4649
|
+
* the grant's party that is not a member: a fresh install recovering with a backup grant, an
|
|
4650
|
+
* app joining as an owner. Needs a started node with a stable identity, as
|
|
4651
|
+
* {@link redeemCadreInvitation} does, and a bundle for this party.
|
|
4652
|
+
*
|
|
4653
|
+
* Steps, the two signatures made under ONE approval of `signer` before any dial, so a
|
|
4654
|
+
* prompting signer asks once and its prompt does not span the network:
|
|
4655
|
+
*
|
|
4656
|
+
* 1. {@link redeemOwnerGrant}'s protocol path: the member that accepts seats `signer`'s key.
|
|
4657
|
+
* 2. A **self-invitation**: a `CadreInvite` row targeted at this node's peer id, one use, a
|
|
4658
|
+
* short expiry ({@link SELF_INVITATION_TTL_MS}), signed by `signer`
|
|
4659
|
+
* (`ControlDatabase.signCadreInvite`; never inserted here, since this store holds no
|
|
4660
|
+
* `OwnerKey` row for the issuer yet). Its bundle carries the grant's owner keys plus the
|
|
4661
|
+
* new key (the device's reply check wants the row's issuer among the pins) and the grant's
|
|
4662
|
+
* members with the member that accepted the grant first, since that member holds the new
|
|
4663
|
+
* key as a live owner and seats the row from the bundle; one the row has not reached yet
|
|
4664
|
+
* answers `issuer-unknown`, which is retryable.
|
|
4665
|
+
* 3. {@link redeemCadreInvitation} with it: the member admits this node.
|
|
4666
|
+
* 4. {@link wireOwnKeyIfOwner}(signer) once the `OwnerKey` row has synced here, asked every
|
|
4667
|
+
* {@link OWNER_ROW_SYNC_RECHECK_MS} up to {@link OWNER_ROW_SYNC_TIMEOUT_MS}; the result's
|
|
4668
|
+
* `ownerWired` says whether it did in time, and a later call wires it otherwise.
|
|
4669
|
+
*
|
|
4670
|
+
* @throws as {@link redeemOwnerGrant} (protocol path) and {@link redeemCadreInvitation} throw,
|
|
4671
|
+
* and a plain `Error` for an ephemeral identity.
|
|
4672
|
+
*/
|
|
4673
|
+
joinWithOwnerGrant(bundle: OwnerGrantBundle, signer: OwnerSigner, options?: RedeemOwnerGrantOptions): Promise<JoinWithOwnerGrantResult>;
|
|
4674
|
+
/**
|
|
4675
|
+
* {@link wireOwnKeyIfOwner}(signer) once `signer`'s `OwnerKey` row has reached this node, asked
|
|
4676
|
+
* again every {@link OWNER_ROW_SYNC_RECHECK_MS} until it has, the node stops, or
|
|
4677
|
+
* {@link OWNER_ROW_SYNC_TIMEOUT_MS} passes: false then, and a later call wires it. Usually
|
|
4678
|
+
* immediate: the member pushed its whole store, the row included, before the admission write.
|
|
4679
|
+
*/
|
|
4680
|
+
private wireOwnKeyOnceSynced;
|
|
4681
|
+
/**
|
|
4682
|
+
* The started node's handles for a grant call, the bundle's party checked first: a grant for
|
|
4683
|
+
* another party is refused before any pin, dial or write.
|
|
4684
|
+
*/
|
|
4685
|
+
private requireStartedForGrant;
|
|
4686
|
+
/**
|
|
4687
|
+
* Is this node a live member of its party: its own `CadrePeer` row is held here and passes the
|
|
4688
|
+
* membership predicate ({@link hasAnchoredProof}, judged as {@link listAuthorizedMembers}
|
|
4689
|
+
* judges every other row; that listing leaves this node out)? What decides
|
|
4690
|
+
* {@link redeemOwnerGrant}'s path. A machine's own row is written by its enrollment (or its
|
|
4691
|
+
* first self-record, for an owner's machine), so a node on an empty store, or one holding only
|
|
4692
|
+
* a store a member pushed it, is not one.
|
|
4693
|
+
*/
|
|
4694
|
+
private isLiveMemberSelf;
|
|
4085
4695
|
/**
|
|
4086
4696
|
* Replace this node's strand solicitation service — the formation responder every node
|
|
4087
4697
|
* installs at {@link start}, and the initiator side of {@link formStrand}. Optional: call it
|
|
@@ -4191,8 +4801,8 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4191
4801
|
*/
|
|
4192
4802
|
dismissPendingJoin(id: string): Promise<boolean>;
|
|
4193
4803
|
private requirePendingJoins;
|
|
4194
|
-
/** This machine's owner
|
|
4195
|
-
private
|
|
4804
|
+
/** This machine's enrolled owner signer ({@link enrolledOwnerSigner}), or a throw carrying `refusal`. */
|
|
4805
|
+
private requireEnrolledOwnerSigner;
|
|
4196
4806
|
/**
|
|
4197
4807
|
* Write a join request, or adopt the party's existing request for the same invitation: a
|
|
4198
4808
|
* pending one as it is, a finished one rewritten as `requested` (the user asked again).
|
|
@@ -4270,6 +4880,10 @@ export declare class CadreNode implements SAppIdLookup {
|
|
|
4270
4880
|
* implements): mint a single-use `Strand.Invite` against the LIVE host strand so a
|
|
4271
4881
|
* validated joiner can seat its own `Strand.Member` row.
|
|
4272
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
|
+
*
|
|
4273
4887
|
* - Open host strand → `null` (no members, nothing to invite into).
|
|
4274
4888
|
* - Closed host strand whose founder launch was refused as pre-split
|
|
4275
4889
|
* ({@link strandLaunchRefusals}) → rethrow the recorded `PreSplitStrandIdentityError`;
|