@serfab/cadre-core 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +1 -1
  2. package/dist/bootstrap-peer-store-file.js +1 -1
  3. package/dist/bootstrap-peer-store.d.ts +1 -1
  4. package/dist/bootstrap-peer-store.js +4 -4
  5. package/dist/cadre-invite-protocol.d.ts +55 -138
  6. package/dist/cadre-invite-protocol.js +94 -449
  7. package/dist/cadre-invite-protocol.js.map +1 -1
  8. package/dist/cadre-join-request.d.ts +15 -2
  9. package/dist/cadre-join-request.js +22 -3
  10. package/dist/cadre-join-request.js.map +1 -1
  11. package/dist/cadre-node.d.ts +530 -247
  12. package/dist/cadre-node.js +1046 -525
  13. package/dist/cadre-node.js.map +1 -1
  14. package/dist/control-authorization.d.ts +36 -14
  15. package/dist/control-authorization.js +20 -3
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.d.ts +22 -23
  18. package/dist/control-cohort.js +24 -24
  19. package/dist/control-cohort.js.map +1 -1
  20. package/dist/control-database.d.ts +416 -136
  21. package/dist/control-database.js +757 -378
  22. package/dist/control-database.js.map +1 -1
  23. package/dist/control-protocol-guard.d.ts +5 -4
  24. package/dist/control-protocol-guard.js +6 -3
  25. package/dist/control-protocol-guard.js.map +1 -1
  26. package/dist/control-schema-version.d.ts +80 -0
  27. package/dist/control-schema-version.js +203 -0
  28. package/dist/control-schema-version.js.map +1 -0
  29. package/dist/control-schema.d.ts +45 -5
  30. package/dist/control-schema.js +254 -70
  31. package/dist/control-schema.js.map +1 -1
  32. package/dist/control-write-retry.d.ts +5 -5
  33. package/dist/control-write-retry.js +10 -8
  34. package/dist/control-write-retry.js.map +1 -1
  35. package/dist/delegate-admission.d.ts +13 -0
  36. package/dist/delegate-admission.js +27 -0
  37. package/dist/delegate-admission.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +10 -7
  39. package/dist/ed25519-key.js +23 -20
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/edge-node/config.d.ts +122 -0
  42. package/dist/edge-node/config.js +199 -0
  43. package/dist/edge-node/config.js.map +1 -0
  44. package/dist/edge-node/dissolve.d.ts +95 -0
  45. package/dist/edge-node/dissolve.js +184 -0
  46. package/dist/edge-node/dissolve.js.map +1 -0
  47. package/dist/edge-node/edge-join.d.ts +44 -0
  48. package/dist/edge-node/edge-join.js +227 -0
  49. package/dist/edge-node/edge-join.js.map +1 -0
  50. package/dist/edge-node/edge-node.d.ts +295 -0
  51. package/dist/edge-node/edge-node.js +704 -0
  52. package/dist/edge-node/edge-node.js.map +1 -0
  53. package/dist/edge-node/index.d.ts +18 -0
  54. package/dist/edge-node/index.js +18 -0
  55. package/dist/edge-node/index.js.map +1 -0
  56. package/dist/edge-node/join-credential.d.ts +11 -0
  57. package/dist/edge-node/join-credential.js +11 -0
  58. package/dist/edge-node/join-credential.js.map +1 -0
  59. package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
  60. package/dist/edge-node/lazy-raw-storage.js +134 -0
  61. package/dist/edge-node/lazy-raw-storage.js.map +1 -0
  62. package/dist/edge-node/node-local.d.ts +60 -0
  63. package/dist/edge-node/node-local.js +58 -0
  64. package/dist/edge-node/node-local.js.map +1 -0
  65. package/dist/edge-node/options.d.ts +83 -0
  66. package/dist/edge-node/options.js +110 -0
  67. package/dist/edge-node/options.js.map +1 -0
  68. package/dist/edge-node/owner-grant.d.ts +41 -0
  69. package/dist/edge-node/owner-grant.js +38 -0
  70. package/dist/edge-node/owner-grant.js.map +1 -0
  71. package/dist/edge-node/platform.d.ts +140 -0
  72. package/dist/edge-node/platform.js +12 -0
  73. package/dist/edge-node/platform.js.map +1 -0
  74. package/dist/edge-node/strand-move.d.ts +115 -0
  75. package/dist/edge-node/strand-move.js +223 -0
  76. package/dist/edge-node/strand-move.js.map +1 -0
  77. package/dist/edge-node/strands.d.ts +28 -0
  78. package/dist/edge-node/strands.js +60 -0
  79. package/dist/edge-node/strands.js.map +1 -0
  80. package/dist/index.d.ts +16 -7
  81. package/dist/index.js +26 -7
  82. package/dist/index.js.map +1 -1
  83. package/dist/joined-strand-store.d.ts +3 -3
  84. package/dist/joined-strand-store.js +3 -3
  85. package/dist/joined-strand-store.js.map +1 -1
  86. package/dist/key-store.d.ts +9 -4
  87. package/dist/key-store.js +9 -4
  88. package/dist/key-store.js.map +1 -1
  89. package/dist/machine-removal.d.ts +19 -39
  90. package/dist/machine-removal.js +24 -98
  91. package/dist/machine-removal.js.map +1 -1
  92. package/dist/member-redemption.d.ts +250 -0
  93. package/dist/member-redemption.js +485 -0
  94. package/dist/member-redemption.js.map +1 -0
  95. package/dist/owner-anchor-sync.d.ts +4 -4
  96. package/dist/owner-anchor-sync.js +16 -15
  97. package/dist/owner-anchor-sync.js.map +1 -1
  98. package/dist/owner-grant-protocol.d.ts +247 -0
  99. package/dist/owner-grant-protocol.js +330 -0
  100. package/dist/owner-grant-protocol.js.map +1 -0
  101. package/dist/owner-key-export.d.ts +70 -0
  102. package/dist/owner-key-export.js +279 -0
  103. package/dist/owner-key-export.js.map +1 -0
  104. package/dist/owner-key-flows.d.ts +55 -0
  105. package/dist/owner-key-flows.js +60 -0
  106. package/dist/owner-key-flows.js.map +1 -0
  107. package/dist/owner-key-store.d.ts +31 -0
  108. package/dist/owner-key-store.js +81 -0
  109. package/dist/owner-key-store.js.map +1 -0
  110. package/dist/owner-keys.d.ts +78 -0
  111. package/dist/owner-keys.js +133 -0
  112. package/dist/owner-keys.js.map +1 -0
  113. package/dist/owner-signer.d.ts +54 -0
  114. package/dist/owner-signer.js +51 -0
  115. package/dist/owner-signer.js.map +1 -0
  116. package/dist/peer-authorization.d.ts +83 -18
  117. package/dist/peer-authorization.js +123 -53
  118. package/dist/peer-authorization.js.map +1 -1
  119. package/dist/provider/rent-node.js +1 -1
  120. package/dist/provider/rent-node.js.map +1 -1
  121. package/dist/seed-bootstrap.d.ts +131 -109
  122. package/dist/seed-bootstrap.js +260 -248
  123. package/dist/seed-bootstrap.js.map +1 -1
  124. package/dist/seed-trust-policy.js +4 -4
  125. package/dist/strand-addr-protocol.d.ts +4 -1
  126. package/dist/strand-addr-protocol.js +8 -2
  127. package/dist/strand-addr-protocol.js.map +1 -1
  128. package/dist/strand-cohort.d.ts +3 -0
  129. package/dist/strand-cohort.js.map +1 -1
  130. package/dist/strand-database.d.ts +1 -0
  131. package/dist/strand-database.js +3 -0
  132. package/dist/strand-database.js.map +1 -1
  133. package/dist/strand-first-sync-gate.d.ts +4 -3
  134. package/dist/strand-first-sync-gate.js +4 -3
  135. package/dist/strand-first-sync-gate.js.map +1 -1
  136. package/dist/strand-instance-manager.d.ts +1 -1
  137. package/dist/strand-membership-writer.d.ts +13 -2
  138. package/dist/strand-membership-writer.js +52 -16
  139. package/dist/strand-membership-writer.js.map +1 -1
  140. package/dist/strand-move.d.ts +6 -5
  141. package/dist/strand-move.js +31 -30
  142. package/dist/strand-move.js.map +1 -1
  143. package/dist/strand-peer-redial.js +2 -3
  144. package/dist/strand-peer-redial.js.map +1 -1
  145. package/dist/types.d.ts +286 -103
  146. package/dist/types.js.map +1 -1
  147. package/package.json +11 -2
  148. package/src/bootstrap-peer-store-file.ts +44 -44
  149. package/src/bootstrap-peer-store.ts +214 -214
  150. package/src/cadre-invite-protocol.ts +652 -1052
  151. package/src/cadre-join-request.ts +24 -3
  152. package/src/cadre-node.ts +1166 -541
  153. package/src/control-authorization.ts +47 -14
  154. package/src/control-cohort.ts +127 -128
  155. package/src/control-database.ts +940 -456
  156. package/src/control-protocol-guard.ts +7 -4
  157. package/src/control-schema-version.ts +251 -0
  158. package/src/control-schema.ts +265 -70
  159. package/src/control-write-retry.ts +569 -567
  160. package/src/delegate-admission.ts +37 -0
  161. package/src/ed25519-key.ts +174 -171
  162. package/src/edge-node/config.ts +277 -0
  163. package/src/edge-node/dissolve.ts +251 -0
  164. package/src/edge-node/edge-join.ts +319 -0
  165. package/src/edge-node/edge-node.ts +1045 -0
  166. package/src/edge-node/index.ts +59 -0
  167. package/src/edge-node/join-credential.ts +16 -0
  168. package/src/edge-node/lazy-raw-storage.ts +178 -0
  169. package/src/edge-node/node-local.ts +84 -0
  170. package/src/edge-node/options.ts +179 -0
  171. package/src/edge-node/owner-grant.ts +63 -0
  172. package/src/edge-node/platform.ts +151 -0
  173. package/src/edge-node/strand-move.ts +270 -0
  174. package/src/edge-node/strands.ts +72 -0
  175. package/src/index.ts +886 -789
  176. package/src/joined-strand-store.ts +4 -4
  177. package/src/key-store.ts +10 -4
  178. package/src/machine-removal.ts +26 -111
  179. package/src/member-redemption.ts +642 -0
  180. package/src/owner-anchor-sync.ts +19 -18
  181. package/src/owner-grant-protocol.ts +532 -0
  182. package/src/owner-key-export.ts +348 -0
  183. package/src/owner-key-flows.ts +109 -0
  184. package/src/owner-key-store.ts +85 -0
  185. package/src/owner-keys.ts +178 -0
  186. package/src/owner-signer.ts +94 -0
  187. package/src/peer-authorization.ts +558 -488
  188. package/src/provider/rent-node.ts +1 -1
  189. package/src/seed-bootstrap.ts +301 -312
  190. package/src/seed-trust-policy.ts +4 -4
  191. package/src/strand-addr-protocol.ts +547 -538
  192. package/src/strand-cohort.ts +4 -0
  193. package/src/strand-database.ts +3 -0
  194. package/src/strand-first-sync-gate.ts +4 -3
  195. package/src/strand-instance-manager.ts +1 -1
  196. package/src/strand-membership-writer.ts +61 -16
  197. package/src/strand-move.ts +45 -36
  198. package/src/strand-peer-redial.ts +2 -3
  199. package/src/types.ts +304 -103
package/README.md CHANGED
@@ -194,7 +194,7 @@ The main entry point for cadre participation.
194
194
  | `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
195
  | `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
196
  | `getEnrollmentService()` | Access peer enrollment API |
197
- | `isOwner()` | Whether this node can act as an owner of its cadre: its identity key is wired for signing and enrolled in `OwnerKey`. The gate the owner-only flows (`createCadreInvitation`, `claimNode`, `rentNode`) share with the apps |
197
+ | `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
198
  | `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
199
 
200
200
  ### 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 owner peers, and nodes this node added).
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: an owner peer a seed nominated,
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 owner peers.** A newcomer applies a signed seed nominating the
7
- * party's owner machines and the addresses they answer on.
8
- * `SeedBootstrapService.applySeed` dials those owners exactly once,
9
- * best-effort; when that dial fails (owner briefly down, relay reservation not
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,18 +8,20 @@
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
- * Same shape as the seed and wake protocols: one length-prefixed JSON frame each way over
12
- * the shared primitives in `control-stream.ts`. Stranger-facing by design — the device is
13
- * not a member when it dials — so the connection gate admits a stranger while a live
14
- * invitation exists (`membership-connection-gater.ts`), and the handler's own trust
15
- * decision is the proof of possession: nothing touches the database for a caller that
16
- * cannot prove it holds the invitation and is the identity it names.
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';
24
+ import { MemberRedemptionHandler, type MemberRedemption, type MemberRedemptionHandlerOptions, type RedemptionAddressOutcome, type RedemptionRejection } from './member-redemption.js';
23
25
  import type { CadreInviteRow, CadreJoinProgress, 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";
@@ -27,8 +29,8 @@ export declare const CADRE_INVITE_PROTOCOL = "/sereus/cadre-invite/1.0.0";
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 (an untargeted owner-granting one defaults to a
31
- * 15-minute expiry for that reason).
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`) and name the mistake when one is pasted for the other.
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,8 +72,8 @@ 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 that is not base64url JSON after
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;
76
79
  /** Device → member: one frame, the whole redemption. */
@@ -112,7 +115,7 @@ export type CadreInviteRejectionCode =
112
115
  | 'party-mismatch'
113
116
  /** The member is at its cap on concurrent redemptions. */
114
117
  | 'busy'
115
- /** The write failed transiently (the write retry gave up); nothing was recorded. */
118
+ /** 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
119
  | 'conflict'
117
120
  /**
118
121
  * An unexpected member-side failure, or a request frame the member could not read (timed
@@ -137,8 +140,9 @@ export interface CadreInviteRedeemReply {
137
140
  invite?: CadreInviteRow;
138
141
  /**
139
142
  * Dial hints for the rest of the cadre, unsigned: the member's own projection of its
140
- * `CadrePeer` rows (`ControlDatabase.querySeedPeers`). `isOwner` is a dial hint only, as
141
- * the seed's NOTE says; the device derives trust from its anchor, never from this.
143
+ * `CadrePeer` rows (`ControlDatabase.querySeedPeers`), the answering member and every
144
+ * always-on machine marked `preferred`. The device derives trust from its anchor, never
145
+ * from this.
142
146
  */
143
147
  peers?: SeedPeer[];
144
148
  }
@@ -149,6 +153,8 @@ export interface CadreInviteAccepted extends CadreInviteRedeemReply {
149
153
  invite: CadreInviteRow;
150
154
  peers: SeedPeer[];
151
155
  }
156
+ /** A refusal as the member sends it: a code, a reason, nothing else. */
157
+ type CadreInviteRejection = RedemptionRejection<CadreInviteRejectionCode>;
152
158
  /**
153
159
  * A member answered and refused. `code` is `'unrecognized'` when the frame carried none this
154
160
  * build knows (the two machines may run different versions), and such a refusal counts as
@@ -161,12 +167,8 @@ export declare class CadreInviteRejectedError extends Error {
161
167
  /** Both arguments come straight off the wire, so neither is trusted to have its declared type. */
162
168
  constructor(code: unknown, reason: unknown);
163
169
  }
164
- /** What one address the bundle named came to. */
165
- export interface CadreInviteAddressOutcome {
166
- addr: string;
167
- /** A dial or exchange failure, or a retryable {@link CadreInviteRejectedError}. */
168
- error: Error;
169
- }
170
+ /** What one address the bundle named came to; the owner-grant redemption reports the same shape. */
171
+ export type CadreInviteAddressOutcome = RedemptionAddressOutcome;
170
172
  /**
171
173
  * Every address the bundle names was tried and none accepted: each failed to dial, answered
172
174
  * malformed, or refused retryably. Always retryable; `outcomes` says what each address did.
@@ -223,99 +225,31 @@ export declare function signRedeemRequest(invitation: CadreInvitation, signer: R
223
225
  export declare function verifyRedeemReply(reply: CadreInviteAccepted, invitation: CadreInvitation, isTrustedIssuer: (ownerKey: string) => boolean): void;
224
226
  /** The control-database surface a redemption needs; a `ControlDatabase` satisfies it. */
225
227
  export type CadreInviteStore = Pick<ControlDatabase, 'seatCadreInvite' | 'isCadreInviteLive' | 'redeemCadreInvite' | 'querySeedPeers'>;
226
- export interface CadreInviteHandlerOptions {
227
- /** This member's party id; a request for another party is refused by name. */
228
- partyId: string;
228
+ export interface CadreInviteHandlerOptions extends MemberRedemptionHandlerOptions {
229
+ /** This member's peer id: the reply's peer list marks it preferred, since the device just reached it. */
230
+ peerId: string;
229
231
  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
232
  }
244
233
  /**
245
- * Member side: registers the handler and drives each inbound stream through the checks in
246
- * cost order — shape, connecting identity, both signatures, target device — so no database
247
- * work happens for a stranger that cannot prove anything, then admits the device to this
248
- * member's control replication (`admitDevice`), seats the row and redeems.
249
- * Hardened as the seed handler is: a concurrency cap (over it, `busy`) and a read timeout.
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.
234
+ * Member side of the invitation protocol (`MemberRedemptionHandler` has the shape): after the
235
+ * request's shape, it checks the party, the connecting identity (an ed25519 peer id is an
236
+ * identity multihash of the very key, and this is the only layer that can check the pair),
237
+ * both signatures and a targeted invitation's device, then seats the row from the bundle and
238
+ * redeems. An issuer this member does not know as an owner is the one retryable refusal left:
239
+ * the device tries the next address, which may hold the issuer's row.
253
240
  */
254
- export declare class CadreInviteHandler {
255
- private readonly options;
256
- private readonly readTimeoutMs;
257
- private readonly maxConcurrent;
258
- private readonly registered;
259
- private active;
241
+ export declare class CadreInviteHandler extends MemberRedemptionHandler<CadreInviteRedeemRequest, CadreInviteRedeemReply> {
242
+ private readonly peerId;
243
+ private readonly store;
244
+ protected readonly busyRefusal: RedemptionRejection<"busy">;
245
+ protected readonly internalRefusal: RedemptionRejection<"internal">;
246
+ protected readonly malformedRefusal: RedemptionRejection<"invite-invalid">;
260
247
  constructor(options: CadreInviteHandlerOptions);
261
- /** In-flight inbound redemptions. */
262
- get activeCount(): number;
263
- /**
264
- * Register on `node`; rejects when libp2p refuses the registration. `runOnLimitedConnection`
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>;
270
- /**
271
- * One inbound redemption: read the frame, decide, reply, close. Public as the seam the
272
- * protocol spec drives directly; `remotePeerId` is the connection's authenticated peer.
273
- */
274
- handleStream(stream: ControlStream, remotePeerId: string): Promise<void>;
275
- /**
276
- * Read the request frame to EOF, bounded and size-capped; JSON only, shape is `decide`'s.
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;
248
+ protected isWellFormedRequest(value: unknown): value is CadreInviteRedeemRequest;
249
+ protected checkRequest(request: CadreInviteRedeemRequest, remotePeerId: string): CadreInviteRejection | null;
250
+ protected isLive(request: CadreInviteRedeemRequest): Promise<boolean>;
251
+ /** The two writes — the seat (its own transaction; a no-op when held) and the redemption — each answered by name when it fails. */
252
+ protected redeemAdmitted(request: CadreInviteRedeemRequest, remotePeerId: string): Promise<CadreInviteRedeemReply>;
319
253
  /** Dial hints are an optimization; a failed read costs the device its hints, not its membership. */
320
254
  private peersBestEffort;
321
255
  }
@@ -326,11 +260,7 @@ export interface RedeemAtMembersOptions {
326
260
  request: CadreInviteRedeemRequest;
327
261
  /** Is this owner key one the device pinned from the bundle? The reply check's input. */
328
262
  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
- */
263
+ /** See `MemberRedemptionOptions.linkRoundTripMs`. */
334
264
  linkRoundTripMs?: number;
335
265
  /** Per-address deadline override; default derived from `linkRoundTripMs`. */
336
266
  addressBudgetMs?: number;
@@ -339,21 +269,14 @@ export interface RedeemAtMembersOptions {
339
269
  signal?: AbortSignal;
340
270
  }
341
271
  /** What a successful redemption yields the device. */
342
- export interface CadreInviteRedemption {
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
- }
272
+ export type CadreInviteRedemption = MemberRedemption<CadreInviteAccepted>;
349
273
  /**
350
274
  * 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. A dial failure, a malformed
352
- * reply or a retryable refusal moves to the next address.
353
- *
354
- * Every address gets the SAME request, with one `usageStampId`: `CadreInviteUsage` is keyed
355
- * by it, so a member the device gave up on that was still committing cannot admit it twice,
356
- * and a retry at the next member after a dropped reply is answered as already a member.
275
+ * stop at the first acceptance or the first final refusal (`dialRedemption` in
276
+ * `member-redemption.ts`). Every address gets the SAME request, with one `usageStampId`:
277
+ * `CadreInviteUsage` is keyed by it, so a member the device gave up on that was still
278
+ * committing cannot admit it twice, and a retry at the next member after a dropped reply is
279
+ * answered as already a member.
357
280
  *
358
281
  * @throws {CadreInviteRejectedError} on a final refusal (its `retryable` is false).
359
282
  * @throws {CadreInviteReplyInvalidError} on an acceptance that fails {@link verifyRedeemReply}.
@@ -368,16 +291,10 @@ export interface RedeemInPassesOptions extends RedeemAtMembersOptions {
368
291
  onProgress?: (progress: CadreJoinProgress) => void;
369
292
  }
370
293
  /**
371
- * Device side, in passes: one {@link redeemAtMembers} pass over the bundle's addresses, or,
372
- * with `waitForMember`, as many as it takes, pausing between them (5 s after the first,
373
- * doubling to 60 s, never past the invitation's expiry). Every pass sends the same signed
374
- * request, so a member that admitted the device on a pass whose reply was lost answers a later
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.
294
+ * Device side, in passes (`dialRedemptionInPasses` in `member-redemption.ts`): one
295
+ * {@link redeemAtMembers} pass, or, with `waitForMember`, as many as it takes, pausing between
296
+ * them and never past the invitation's expiry, which by this device's clock is thrown as the
297
+ * `invite-spent` refusal a member would give.
382
298
  */
383
299
  export declare function redeemInPasses(node: Libp2p, options: RedeemInPassesOptions): Promise<CadreInviteRedemption>;
300
+ export {};