@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/README.md
CHANGED
|
@@ -149,8 +149,9 @@ const { seed, encodedSeed } = await node.addDrone({ dronePeerId, droneMultiaddrs
|
|
|
149
149
|
|
|
150
150
|
// The new machine starts with it: `cadre start --seed <encodedSeed> --pin-owner-key <owner key>`
|
|
151
151
|
|
|
152
|
-
//
|
|
153
|
-
// phone);
|
|
152
|
+
// Await the dial of the drone from the addresses addDrone kept (a drone cannot
|
|
153
|
+
// dial a phone); the row insert starts a pass that dials it anyway, about 50 ms
|
|
154
|
+
// later, and retries a failed dial after 15 s, 30 s, 60 s, then every 2 minutes
|
|
154
155
|
await node.reconcileControlCohort();
|
|
155
156
|
```
|
|
156
157
|
|
|
@@ -194,7 +195,7 @@ The main entry point for cadre participation.
|
|
|
194
195
|
| `publishStrand(id, type?, memberKey?)` | Owner-signed `Strand` row insert — makes the strand visible cadre-wide. Idempotent: a live row with the same `(Type, MemberPrivateKey)` is returned unwritten; different content on the same id throws. Prefer `foundStrand` when creating a strand |
|
|
195
196
|
| `unpublishStrand(id)` | Owner-signed party-wide removal — deletes this party's `Strand` row; every node stops the strand. Destroys a closed strand's `MemberPrivateKey` |
|
|
196
197
|
| `getEnrollmentService()` | Access peer enrollment API |
|
|
197
|
-
| `isOwner()` | Whether this node can act as an owner of its cadre:
|
|
198
|
+
| `isOwner()` | Whether this node can act as an owner of its cadre: an owner key is wired for signing (`useOwnerSigner`) and is a live `OwnerKey` row; never the node's identity key. The gate the owner-only flows (`createCadreInvitation`, `createOwnerGrant`, `claimNode`, `rentNode`) share with the apps |
|
|
198
199
|
| `invitationReach()` | Where a device could redeem an invitation minted now from, judged on the other members' addresses (`anywhere`, `local-network`, `issuer-only`), without minting one; `rentNode` chooses its join path by it |
|
|
199
200
|
|
|
200
201
|
### Events
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Node-only file-backed {@link BootstrapPeerStore}: one JSON file per party under
|
|
3
3
|
* a configured directory (the node's state directory — `cadre-cli` passes
|
|
4
4
|
* `ResolvedConfig.nodeStateDir`), holding the node-local, non-replicated dial
|
|
5
|
-
* targets learned out of band (a seed's
|
|
5
|
+
* targets learned out of band (a seed's preferred peers, and nodes this node added).
|
|
6
6
|
*
|
|
7
7
|
* Nothing but the file itself lives here: the envelope, load policy, per-entry
|
|
8
8
|
* validation (shape only — never signatures; see the `bootstrap-peer-store.ts`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type DurableSlot } from './node-local-snapshot.js';
|
|
2
2
|
/**
|
|
3
|
-
* One retained dial target learned out of band:
|
|
3
|
+
* One retained dial target learned out of band: a preferred peer a seed nominated,
|
|
4
4
|
* or a node this node added.
|
|
5
5
|
*/
|
|
6
6
|
export interface BootstrapPeerEntry {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* out of band, kept so it can dial those peers when nothing better is known.
|
|
4
4
|
* Two sources feed it:
|
|
5
5
|
*
|
|
6
|
-
* - **A seed's
|
|
7
|
-
*
|
|
8
|
-
* `SeedBootstrapService.applySeed` dials those
|
|
9
|
-
* best-effort; when that dial fails (
|
|
6
|
+
* - **A seed's preferred peers.** A newcomer applies a signed seed nominating
|
|
7
|
+
* the machine that minted it and the party's always-on machines, with the
|
|
8
|
+
* addresses they answer on. `SeedBootstrapService.applySeed` dials those peers
|
|
9
|
+
* exactly once, best-effort; when that dial fails (peer briefly down, relay reservation not
|
|
10
10
|
* up, not yet vouched) the node has an empty `CadrePeer` table and no
|
|
11
11
|
* connection, and `CadreNode.reconcileControlCohort`'s cold-start branch is
|
|
12
12
|
* the only way back — it re-dials these retained addresses every pass.
|
|
@@ -8,27 +8,29 @@
|
|
|
8
8
|
* ownership) through `ControlDatabase.redeemCadreInvite`, and answers with the party id,
|
|
9
9
|
* the row and dial hints. The owner that minted the invitation need not be online.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* The exchange shape — one frame each way, the member's hardening and admission of the
|
|
12
|
+
* device, the device's address-by-address dialing in passes — is `member-redemption.ts`'s,
|
|
13
|
+
* shared with the owner-grant protocol; this module supplies the invitation's bundle, request,
|
|
14
|
+
* checks, writes and refusal codes. Stranger-facing by design — the device is not a member
|
|
15
|
+
* when it dials — so the connection gate admits a stranger while a live invitation exists
|
|
16
|
+
* (`membership-connection-gater.ts`), and the handler's own trust decision is the proof of
|
|
17
|
+
* possession: nothing touches the database for a caller that cannot prove it holds the
|
|
18
|
+
* invitation and is the identity it names.
|
|
17
19
|
*
|
|
18
20
|
* The seed protocol is left untouched so `SeedMessage` can grow independently.
|
|
19
21
|
*/
|
|
20
22
|
import type { Libp2p } from '@libp2p/interface';
|
|
21
|
-
import { type ControlStream } from './control-stream.js';
|
|
22
23
|
import { type ControlDatabase } from './control-database.js';
|
|
23
|
-
import type
|
|
24
|
+
import { MemberRedemptionHandler, type MemberRedemption, type MemberRedemptionHandlerOptions, type RedemptionAddressOutcome, type RedemptionRejection } from './member-redemption.js';
|
|
25
|
+
import type { CadreInviteRow, CadreJoinProgress, PeerAddressRecord, SeedPeer } from './types.js';
|
|
24
26
|
/** Protocol id for cadre invitation redemption (parallel to `/sereus/seed/1.0.0`). */
|
|
25
27
|
export declare const CADRE_INVITE_PROTOCOL = "/sereus/cadre-invite/1.0.0";
|
|
26
28
|
/**
|
|
27
29
|
* What an owner hands out (QR, paste, link) and a device redeems. An invitation is an
|
|
28
30
|
* ed25519 keypair: the row holds the public half, `invitePrivateKey` is the proof of
|
|
29
31
|
* possession, and whoever holds this bundle can redeem it — treat it as a bearer
|
|
30
|
-
* credential for the row's lifetime
|
|
31
|
-
*
|
|
32
|
+
* credential for the row's lifetime. It admits a machine and grants nothing else; an owner
|
|
33
|
+
* grant (`owner-grant-protocol.ts`) is the credential that makes a key an owner.
|
|
32
34
|
*
|
|
33
35
|
* Carrying the signed row is what makes redemption work at a member that has not received
|
|
34
36
|
* the row by replication: the member seats it from the bundle under its stored issuer
|
|
@@ -60,7 +62,8 @@ export interface CadreInvitation {
|
|
|
60
62
|
/**
|
|
61
63
|
* Encode a bundle for out-of-band delivery: {@link CADRE_INVITATION_PREFIX}, then base64url
|
|
62
64
|
* over JSON. The prefix lets an app tell an invitation from a join request
|
|
63
|
-
* (`cadre-join-request.ts`)
|
|
65
|
+
* (`cadre-join-request.ts`) or an owner grant (`owner-grant-protocol.ts`) and name the mistake
|
|
66
|
+
* when one is pasted for the other.
|
|
64
67
|
*/
|
|
65
68
|
export declare function encodeCadreInvitation(invitation: CadreInvitation): string;
|
|
66
69
|
/**
|
|
@@ -69,21 +72,31 @@ export declare function encodeCadreInvitation(invitation: CadreInvitation): stri
|
|
|
69
72
|
* without a node (a settings screen that wants to show which cadre an invitation joins) can
|
|
70
73
|
* decode one.
|
|
71
74
|
*
|
|
72
|
-
* @throws on a join request, a string without the prefix or
|
|
73
|
-
* it, or one that decodes to another shape or version.
|
|
75
|
+
* @throws on an owner key, an owner grant or a join request, a string without the prefix or
|
|
76
|
+
* that is not base64url JSON after it, or one that decodes to another shape or version.
|
|
74
77
|
*/
|
|
75
78
|
export declare function decodeCadreInvitation(encoded: string): CadreInvitation;
|
|
79
|
+
/**
|
|
80
|
+
* The device's own address record as a request carries it: a {@link PeerAddressRecord} without
|
|
81
|
+
* the peer id, which the connection gives, and the key, which is the request's `peerKey`.
|
|
82
|
+
*/
|
|
83
|
+
export type RedeemSelfRecord = Pick<PeerAddressRecord, 'addrs' | 'updatedAt' | 'profile' | 'sig'>;
|
|
76
84
|
/** Device → member: one frame, the whole redemption. */
|
|
77
85
|
export interface CadreInviteRedeemRequest {
|
|
78
|
-
v:
|
|
86
|
+
v: 2;
|
|
79
87
|
/** The cadre the device believes it is joining; a machine serving another party refuses by name. */
|
|
80
88
|
partyId: string;
|
|
81
89
|
/** The owner-signed row from the bundle. */
|
|
82
90
|
invite: CadreInviteRow;
|
|
83
91
|
/** The device's ed25519 public key, base64url; must be the key behind the connecting peer id. */
|
|
84
92
|
peerKey: string;
|
|
85
|
-
/**
|
|
86
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The device's own address record for its `CadrePeer` row, self-signed for the invitation's
|
|
95
|
+
* party as `CadreNode.registerSelf` signs it (`signPeerRecord`); `addrs` is empty for a phone.
|
|
96
|
+
* The member stores `addrs` and, when the signature verifies over them, the rest of the
|
|
97
|
+
* record too ({@link admittedRecord}), so the device's first publish is its admission.
|
|
98
|
+
*/
|
|
99
|
+
record: RedeemSelfRecord;
|
|
87
100
|
/** Single-use nonce the device minted (`generateStampId`); both signatures cover it. */
|
|
88
101
|
usageStampId: string;
|
|
89
102
|
/** By `invitePrivateKey` over the `'redeem'` digest. */
|
|
@@ -112,7 +125,7 @@ export type CadreInviteRejectionCode =
|
|
|
112
125
|
| 'party-mismatch'
|
|
113
126
|
/** The member is at its cap on concurrent redemptions. */
|
|
114
127
|
| 'busy'
|
|
115
|
-
/** The write failed transiently (the write retry gave up); nothing was recorded. */
|
|
128
|
+
/** The write failed transiently (the write retry gave up), or the member has not synced the invitation row the cadre holds; nothing was recorded. */
|
|
116
129
|
| 'conflict'
|
|
117
130
|
/**
|
|
118
131
|
* An unexpected member-side failure, or a request frame the member could not read (timed
|
|
@@ -137,8 +150,9 @@ export interface CadreInviteRedeemReply {
|
|
|
137
150
|
invite?: CadreInviteRow;
|
|
138
151
|
/**
|
|
139
152
|
* Dial hints for the rest of the cadre, unsigned: the member's own projection of its
|
|
140
|
-
* `CadrePeer` rows (`ControlDatabase.querySeedPeers`)
|
|
141
|
-
*
|
|
153
|
+
* `CadrePeer` rows (`ControlDatabase.querySeedPeers`), the answering member and every
|
|
154
|
+
* always-on machine marked `preferred`. The device derives trust from its anchor, never
|
|
155
|
+
* from this.
|
|
142
156
|
*/
|
|
143
157
|
peers?: SeedPeer[];
|
|
144
158
|
}
|
|
@@ -149,6 +163,8 @@ export interface CadreInviteAccepted extends CadreInviteRedeemReply {
|
|
|
149
163
|
invite: CadreInviteRow;
|
|
150
164
|
peers: SeedPeer[];
|
|
151
165
|
}
|
|
166
|
+
/** A refusal as the member sends it: a code, a reason, nothing else. */
|
|
167
|
+
type CadreInviteRejection = RedemptionRejection<CadreInviteRejectionCode>;
|
|
152
168
|
/**
|
|
153
169
|
* A member answered and refused. `code` is `'unrecognized'` when the frame carried none this
|
|
154
170
|
* build knows (the two machines may run different versions), and such a refusal counts as
|
|
@@ -161,12 +177,8 @@ export declare class CadreInviteRejectedError extends Error {
|
|
|
161
177
|
/** Both arguments come straight off the wire, so neither is trusted to have its declared type. */
|
|
162
178
|
constructor(code: unknown, reason: unknown);
|
|
163
179
|
}
|
|
164
|
-
/** What one address the bundle named came to. */
|
|
165
|
-
export
|
|
166
|
-
addr: string;
|
|
167
|
-
/** A dial or exchange failure, or a retryable {@link CadreInviteRejectedError}. */
|
|
168
|
-
error: Error;
|
|
169
|
-
}
|
|
180
|
+
/** What one address the bundle named came to; the owner-grant redemption reports the same shape. */
|
|
181
|
+
export type CadreInviteAddressOutcome = RedemptionAddressOutcome;
|
|
170
182
|
/**
|
|
171
183
|
* Every address the bundle names was tried and none accepted: each failed to dial, answered
|
|
172
184
|
* malformed, or refused retryably. Always retryable; `outcomes` says what each address did.
|
|
@@ -207,8 +219,12 @@ export interface RedeemSigner {
|
|
|
207
219
|
* (invitation key, `usageStampId`, device key) for the invitation's party. The same bytes
|
|
208
220
|
* the schema verifies at the member (`cadreInviteRedeemMessage` / `cadreInviteConsentMessage`),
|
|
209
221
|
* whose own `party_id()` must therefore be the party the bundle names.
|
|
222
|
+
*
|
|
223
|
+
* @param record - the device's address record, already signed for the invitation's party over
|
|
224
|
+
* sanitized addresses ({@link sanitizeAddrs}); copied in unchanged, since the signature
|
|
225
|
+
* covers the exact list.
|
|
210
226
|
*/
|
|
211
|
-
export declare function signRedeemRequest(invitation: CadreInvitation, signer: RedeemSigner, usageStampId: string,
|
|
227
|
+
export declare function signRedeemRequest(invitation: CadreInvitation, signer: RedeemSigner, usageStampId: string, record: RedeemSelfRecord): CadreInviteRedeemRequest;
|
|
212
228
|
/**
|
|
213
229
|
* The device's check on an acceptance: the same party, the same invitation key, and a row
|
|
214
230
|
* signed by an issuer the device pinned from the bundle. Throws
|
|
@@ -222,100 +238,40 @@ export declare function signRedeemRequest(invitation: CadreInvitation, signer: R
|
|
|
222
238
|
*/
|
|
223
239
|
export declare function verifyRedeemReply(reply: CadreInviteAccepted, invitation: CadreInvitation, isTrustedIssuer: (ownerKey: string) => boolean): void;
|
|
224
240
|
/** The control-database surface a redemption needs; a `ControlDatabase` satisfies it. */
|
|
225
|
-
export type CadreInviteStore = Pick<ControlDatabase, 'seatCadreInvite' | 'isCadreInviteLive' | 'redeemCadreInvite' | 'querySeedPeers'>;
|
|
226
|
-
export interface CadreInviteHandlerOptions {
|
|
227
|
-
/** This member's
|
|
228
|
-
|
|
241
|
+
export type CadreInviteStore = Pick<ControlDatabase, 'queryCadreInvite' | 'seatCadreInvite' | 'isCadreInviteLive' | 'redeemCadreInvite' | 'querySeedPeers'>;
|
|
242
|
+
export interface CadreInviteHandlerOptions extends MemberRedemptionHandlerOptions {
|
|
243
|
+
/** This member's peer id: the reply's peer list marks it preferred, since the device just reached it. */
|
|
244
|
+
peerId: string;
|
|
229
245
|
store: CadreInviteStore;
|
|
230
|
-
/**
|
|
231
|
-
* Push this member's control store to the device, resolving once the push has finished, and
|
|
232
|
-
* keep the device counted in this member's control write cohort until the returned release
|
|
233
|
-
* is called. Called after the request verified and before the row is seated and the
|
|
234
|
-
* admission is written; the handler releases once both have settled. Absent, the writes go
|
|
235
|
-
* ahead with the device neither caught up nor in the cohort, so it does not hold them; see
|
|
236
|
-
* `admitDeviceIfLive`.
|
|
237
|
-
*/
|
|
238
|
-
admitDevice?: (peerId: string) => Promise<() => void>;
|
|
239
|
-
/** Time to wait for the request frame; default {@link DEFAULT_REDEEM_READ_TIMEOUT_MS}. */
|
|
240
|
-
redeemReadTimeoutMs?: number;
|
|
241
|
-
/** Cap on concurrent inbound redemptions; default {@link DEFAULT_MAX_CONCURRENT_REDEMPTIONS}. */
|
|
242
|
-
maxConcurrentRedemptions?: number;
|
|
243
246
|
}
|
|
244
247
|
/**
|
|
245
|
-
* Member side
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* Registered only by a started `CadreNode`, after its control database is up, like the
|
|
252
|
-
* formation responder; nothing here is reachable on a node without one.
|
|
248
|
+
* Member side of the invitation protocol (`MemberRedemptionHandler` has the shape): after the
|
|
249
|
+
* request's shape, it checks the party, the connecting identity (an ed25519 peer id is an
|
|
250
|
+
* identity multihash of the very key, and this is the only layer that can check the pair),
|
|
251
|
+
* both signatures and a targeted invitation's device, then seats the row from the bundle when
|
|
252
|
+
* this member holds none and redeems. An issuer this member does not know as an owner is the
|
|
253
|
+
* one retryable refusal left: the device tries the next address, which may hold the issuer's row.
|
|
253
254
|
*/
|
|
254
|
-
export declare class CadreInviteHandler {
|
|
255
|
-
private readonly
|
|
256
|
-
private readonly
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
255
|
+
export declare class CadreInviteHandler extends MemberRedemptionHandler<CadreInviteRedeemRequest, CadreInviteRedeemReply, CadreInviteRow> {
|
|
256
|
+
private readonly peerId;
|
|
257
|
+
private readonly store;
|
|
258
|
+
protected readonly busyRefusal: RedemptionRejection<"busy">;
|
|
259
|
+
protected readonly internalRefusal: RedemptionRejection<"internal">;
|
|
260
|
+
protected readonly malformedRefusal: RedemptionRejection<"invite-invalid">;
|
|
260
261
|
constructor(options: CadreInviteHandlerOptions);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
* because a phone redeems through a relay, whose connection libp2p marks limited; the
|
|
266
|
-
* exchange is two small frames, well inside any relay's cap.
|
|
267
|
-
*/
|
|
268
|
-
register(node: Libp2p, protocolId?: string): Promise<void>;
|
|
269
|
-
unregister(node: Libp2p, protocolId?: string): Promise<void>;
|
|
262
|
+
protected isWellFormedRequest(value: unknown): value is CadreInviteRedeemRequest;
|
|
263
|
+
protected checkRequest(request: CadreInviteRedeemRequest, remotePeerId: string): CadreInviteRejection | null;
|
|
264
|
+
protected readHeld(request: CadreInviteRedeemRequest): Promise<CadreInviteRow | null>;
|
|
265
|
+
protected isLive(request: CadreInviteRedeemRequest, held: CadreInviteRow | null): Promise<boolean>;
|
|
270
266
|
/**
|
|
271
|
-
*
|
|
272
|
-
*
|
|
267
|
+
* The two writes — the seat (its own transaction), only when this member holds no row, and
|
|
268
|
+
* the redemption — each answered by name when it fails. The held row is carried into the
|
|
269
|
+
* redemption so it is not read again in the write lock; after a fresh seat nothing is carried
|
|
270
|
+
* and the redemption reads the row it seated, since the bundle's copy is the requester's.
|
|
273
271
|
*/
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
|
|
277
|
-
* Throws on a frame that cannot be read, which {@link handleStream} answers `internal`:
|
|
278
|
-
* a read timeout or a cut frame proves nothing about the invitation, so the device must
|
|
279
|
-
* stay free to try the next address.
|
|
280
|
-
*/
|
|
281
|
-
private readRequest;
|
|
282
|
-
private decide;
|
|
283
|
-
/**
|
|
284
|
-
* The checks that cost no database work, in order: shape, party, connecting identity (an
|
|
285
|
-
* ed25519 peer id is an identity multihash of the very key, and this is the only layer that
|
|
286
|
-
* can check the pair), both signatures, and a targeted invitation's device.
|
|
287
|
-
*/
|
|
288
|
-
private checkRequest;
|
|
289
|
-
/**
|
|
290
|
-
* Admit the device to this member's control replication (see {@link admitDeviceIfLive}),
|
|
291
|
-
* seat the row from the bundle (its own transaction; a no-op when held), then redeem. The
|
|
292
|
-
* admission comes first because the seat is already a write the device takes part in. An
|
|
293
|
-
* issuer this member does not know as an owner is the one retryable refusal left: the device
|
|
294
|
-
* tries the next address, which may hold the issuer's row.
|
|
295
|
-
*/
|
|
296
|
-
private seatAndRedeem;
|
|
297
|
-
/** {@link seatAndRedeem}'s two writes, each answered by name when it fails. */
|
|
298
|
-
private seatAndRedeemAdmitted;
|
|
299
|
-
/**
|
|
300
|
-
* Admit the device to this member's control replication before the first write of the
|
|
301
|
-
* exchange (the seat, when this member does not hold the row; otherwise the admission), and
|
|
302
|
-
* resolve to the release that ends the admission. The device is not a member until the
|
|
303
|
-
* admission is written, so only this puts it in this member's control write cohort, and it
|
|
304
|
-
* must be there to hold the rows that admit it. It is pushed the control store first,
|
|
305
|
-
* because a cohort node with no base revision of a block refuses a commit to it; a member
|
|
306
|
-
* alone with the device would then have half its cohort refusing, and the write would tear
|
|
307
|
-
* instead of committing.
|
|
308
|
-
*
|
|
309
|
-
* Only for an invitation still live here, judged on the row this member holds or, before
|
|
310
|
-
* the seat, on the bundle's copy once its issuer signature verifies
|
|
311
|
-
* (`ControlDatabase.isCadreInviteLive`): the holder of a withdrawn, expired or spent one,
|
|
312
|
-
* or of a forged copy naming a real owner, is refused without being sent anything. The
|
|
313
|
-
* retry of an admission already written can read as spent too and is sent nothing; it
|
|
314
|
-
* needs nothing, since the device is a member and the ordinary catch-up reaches it.
|
|
315
|
-
* Best-effort: a failed check or push is logged and the writes go ahead without the device
|
|
316
|
-
* admitted, then commit or are answered retryably.
|
|
317
|
-
*/
|
|
318
|
-
private admitDeviceIfLive;
|
|
272
|
+
protected redeemAdmitted(request: CadreInviteRedeemRequest, remotePeerId: string, held: CadreInviteRow | null): Promise<CadreInviteRedeemReply>;
|
|
273
|
+
/** Seat the bundle's copy of the row; a refusal answered by name, or null once it is held. */
|
|
274
|
+
private seat;
|
|
319
275
|
/** Dial hints are an optimization; a failed read costs the device its hints, not its membership. */
|
|
320
276
|
private peersBestEffort;
|
|
321
277
|
}
|
|
@@ -326,11 +282,7 @@ export interface RedeemAtMembersOptions {
|
|
|
326
282
|
request: CadreInviteRedeemRequest;
|
|
327
283
|
/** Is this owner key one the device pinned from the bundle? The reply check's input. */
|
|
328
284
|
isTrustedIssuer: (ownerKey: string) => boolean;
|
|
329
|
-
/**
|
|
330
|
-
* This machine's declared link round trip (`NetworkConfig.linkRoundTripMs`), from which
|
|
331
|
-
* the per-address deadline derives: a dial that may need a relay plus one request
|
|
332
|
-
* (`relayedRequestBudgetMs`, 28.5 s at the default declaration), as seed delivery's is.
|
|
333
|
-
*/
|
|
285
|
+
/** See `MemberRedemptionOptions.linkRoundTripMs`. */
|
|
334
286
|
linkRoundTripMs?: number;
|
|
335
287
|
/** Per-address deadline override; default derived from `linkRoundTripMs`. */
|
|
336
288
|
addressBudgetMs?: number;
|
|
@@ -339,21 +291,14 @@ export interface RedeemAtMembersOptions {
|
|
|
339
291
|
signal?: AbortSignal;
|
|
340
292
|
}
|
|
341
293
|
/** What a successful redemption yields the device. */
|
|
342
|
-
export
|
|
343
|
-
reply: CadreInviteAccepted;
|
|
344
|
-
/** The answering member's peer id, from the address's trailing `/p2p/`; null when it named none. */
|
|
345
|
-
memberPeerId: string | null;
|
|
346
|
-
/** The address that answered. */
|
|
347
|
-
memberAddr: string;
|
|
348
|
-
}
|
|
294
|
+
export type CadreInviteRedemption = MemberRedemption<CadreInviteAccepted>;
|
|
349
295
|
/**
|
|
350
296
|
* Device side: try the bundle's addresses one at a time, each under its own deadline, and
|
|
351
|
-
* stop at the first acceptance or the first final refusal
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* and a retry at the next member after a dropped reply is answered as already a member.
|
|
297
|
+
* stop at the first acceptance or the first final refusal (`dialRedemption` in
|
|
298
|
+
* `member-redemption.ts`). Every address gets the SAME request, with one `usageStampId`:
|
|
299
|
+
* `CadreInviteUsage` is keyed by it, so a member the device gave up on that was still
|
|
300
|
+
* committing cannot admit it twice, and a retry at the next member after a dropped reply is
|
|
301
|
+
* answered as already a member.
|
|
357
302
|
*
|
|
358
303
|
* @throws {CadreInviteRejectedError} on a final refusal (its `retryable` is false).
|
|
359
304
|
* @throws {CadreInviteReplyInvalidError} on an acceptance that fails {@link verifyRedeemReply}.
|
|
@@ -368,16 +313,10 @@ export interface RedeemInPassesOptions extends RedeemAtMembersOptions {
|
|
|
368
313
|
onProgress?: (progress: CadreJoinProgress) => void;
|
|
369
314
|
}
|
|
370
315
|
/**
|
|
371
|
-
* Device side, in passes
|
|
372
|
-
* with `waitForMember`, as many as it takes, pausing between
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* pass as already a member, with no second usage row.
|
|
376
|
-
*
|
|
377
|
-
* A wait ends at the first acceptance, at a final refusal or an invalid reply (thrown as
|
|
378
|
-
* {@link redeemAtMembers} throws them), when the invitation expires by this device's clock
|
|
379
|
-
* (thrown as the `invite-spent` refusal a member would give), when `signal` aborts (its reason
|
|
380
|
-
* thrown), and when `node` stops. A bundle that names no address has nothing to wait for and
|
|
381
|
-
* throws at once.
|
|
316
|
+
* Device side, in passes (`dialRedemptionInPasses` in `member-redemption.ts`): one
|
|
317
|
+
* {@link redeemAtMembers} pass, or, with `waitForMember`, as many as it takes, pausing between
|
|
318
|
+
* them and never past the invitation's expiry, which by this device's clock is thrown as the
|
|
319
|
+
* `invite-spent` refusal a member would give.
|
|
382
320
|
*/
|
|
383
321
|
export declare function redeemInPasses(node: Libp2p, options: RedeemInPassesOptions): Promise<CadreInviteRedemption>;
|
|
322
|
+
export {};
|