@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.
Files changed (268) hide show
  1. package/README.md +4 -3
  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 +81 -142
  6. package/dist/cadre-invite-protocol.js +168 -458
  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 +1089 -475
  12. package/dist/cadre-node.js +2170 -1037
  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 +88 -32
  18. package/dist/control-cohort.js +91 -33
  19. package/dist/control-cohort.js.map +1 -1
  20. package/dist/control-database.d.ts +856 -318
  21. package/dist/control-database.js +1422 -701
  22. package/dist/control-database.js.map +1 -1
  23. package/dist/control-formation-recorder.d.ts +46 -43
  24. package/dist/control-formation-recorder.js +77 -78
  25. package/dist/control-formation-recorder.js.map +1 -1
  26. package/dist/control-protocol-guard.d.ts +11 -4
  27. package/dist/control-protocol-guard.js +20 -5
  28. package/dist/control-protocol-guard.js.map +1 -1
  29. package/dist/control-schema-version.d.ts +97 -0
  30. package/dist/control-schema-version.js +225 -0
  31. package/dist/control-schema-version.js.map +1 -0
  32. package/dist/control-schema.d.ts +45 -5
  33. package/dist/control-schema.js +262 -78
  34. package/dist/control-schema.js.map +1 -1
  35. package/dist/control-write-retry.d.ts +45 -10
  36. package/dist/control-write-retry.js +92 -22
  37. package/dist/control-write-retry.js.map +1 -1
  38. package/dist/delegate-admission.d.ts +13 -0
  39. package/dist/delegate-admission.js +27 -0
  40. package/dist/delegate-admission.js.map +1 -1
  41. package/dist/ed25519-key.d.ts +10 -7
  42. package/dist/ed25519-key.js +23 -20
  43. package/dist/ed25519-key.js.map +1 -1
  44. package/dist/edge-node/config.d.ts +122 -0
  45. package/dist/edge-node/config.js +199 -0
  46. package/dist/edge-node/config.js.map +1 -0
  47. package/dist/edge-node/dissolve.d.ts +95 -0
  48. package/dist/edge-node/dissolve.js +184 -0
  49. package/dist/edge-node/dissolve.js.map +1 -0
  50. package/dist/edge-node/edge-join.d.ts +44 -0
  51. package/dist/edge-node/edge-join.js +227 -0
  52. package/dist/edge-node/edge-join.js.map +1 -0
  53. package/dist/edge-node/edge-node.d.ts +295 -0
  54. package/dist/edge-node/edge-node.js +704 -0
  55. package/dist/edge-node/edge-node.js.map +1 -0
  56. package/dist/edge-node/index.d.ts +18 -0
  57. package/dist/edge-node/index.js +18 -0
  58. package/dist/edge-node/index.js.map +1 -0
  59. package/dist/edge-node/join-credential.d.ts +11 -0
  60. package/dist/edge-node/join-credential.js +11 -0
  61. package/dist/edge-node/join-credential.js.map +1 -0
  62. package/dist/edge-node/lazy-raw-storage.d.ts +40 -0
  63. package/dist/edge-node/lazy-raw-storage.js +134 -0
  64. package/dist/edge-node/lazy-raw-storage.js.map +1 -0
  65. package/dist/edge-node/node-local.d.ts +60 -0
  66. package/dist/edge-node/node-local.js +58 -0
  67. package/dist/edge-node/node-local.js.map +1 -0
  68. package/dist/edge-node/options.d.ts +83 -0
  69. package/dist/edge-node/options.js +110 -0
  70. package/dist/edge-node/options.js.map +1 -0
  71. package/dist/edge-node/owner-grant.d.ts +41 -0
  72. package/dist/edge-node/owner-grant.js +38 -0
  73. package/dist/edge-node/owner-grant.js.map +1 -0
  74. package/dist/edge-node/platform.d.ts +140 -0
  75. package/dist/edge-node/platform.js +12 -0
  76. package/dist/edge-node/platform.js.map +1 -0
  77. package/dist/edge-node/strand-move.d.ts +115 -0
  78. package/dist/edge-node/strand-move.js +223 -0
  79. package/dist/edge-node/strand-move.js.map +1 -0
  80. package/dist/edge-node/strands.d.ts +28 -0
  81. package/dist/edge-node/strands.js +60 -0
  82. package/dist/edge-node/strands.js.map +1 -0
  83. package/dist/enrolled-machine-store.d.ts +3 -3
  84. package/dist/enrolled-machine-store.js +3 -3
  85. package/dist/formation-token.d.ts +48 -0
  86. package/dist/formation-token.js +26 -0
  87. package/dist/formation-token.js.map +1 -0
  88. package/dist/index.d.ts +27 -17
  89. package/dist/index.js +39 -17
  90. package/dist/index.js.map +1 -1
  91. package/dist/joined-strand-store.d.ts +29 -12
  92. package/dist/joined-strand-store.js +66 -27
  93. package/dist/joined-strand-store.js.map +1 -1
  94. package/dist/key-store.d.ts +9 -4
  95. package/dist/key-store.js +9 -4
  96. package/dist/key-store.js.map +1 -1
  97. package/dist/link-budget.d.ts +9 -7
  98. package/dist/link-budget.js +9 -7
  99. package/dist/link-budget.js.map +1 -1
  100. package/dist/machine-removal.d.ts +19 -39
  101. package/dist/machine-removal.js +24 -98
  102. package/dist/machine-removal.js.map +1 -1
  103. package/dist/member-redemption.d.ts +256 -0
  104. package/dist/member-redemption.js +490 -0
  105. package/dist/member-redemption.js.map +1 -0
  106. package/dist/membership-view.d.ts +91 -0
  107. package/dist/membership-view.js +110 -0
  108. package/dist/membership-view.js.map +1 -0
  109. package/dist/owner-anchor-sync.d.ts +4 -4
  110. package/dist/owner-anchor-sync.js +16 -15
  111. package/dist/owner-anchor-sync.js.map +1 -1
  112. package/dist/owner-grant-protocol.d.ts +251 -0
  113. package/dist/owner-grant-protocol.js +344 -0
  114. package/dist/owner-grant-protocol.js.map +1 -0
  115. package/dist/owner-key-export.d.ts +70 -0
  116. package/dist/owner-key-export.js +279 -0
  117. package/dist/owner-key-export.js.map +1 -0
  118. package/dist/owner-key-flows.d.ts +55 -0
  119. package/dist/owner-key-flows.js +60 -0
  120. package/dist/owner-key-flows.js.map +1 -0
  121. package/dist/owner-key-store.d.ts +31 -0
  122. package/dist/owner-key-store.js +81 -0
  123. package/dist/owner-key-store.js.map +1 -0
  124. package/dist/owner-keys.d.ts +78 -0
  125. package/dist/owner-keys.js +133 -0
  126. package/dist/owner-keys.js.map +1 -0
  127. package/dist/owner-signer.d.ts +54 -0
  128. package/dist/owner-signer.js +51 -0
  129. package/dist/owner-signer.js.map +1 -0
  130. package/dist/peer-addr-book.d.ts +10 -1
  131. package/dist/peer-addr-book.js +4 -3
  132. package/dist/peer-addr-book.js.map +1 -1
  133. package/dist/peer-authorization.d.ts +83 -18
  134. package/dist/peer-authorization.js +123 -53
  135. package/dist/peer-authorization.js.map +1 -1
  136. package/dist/peer-join-backfill.d.ts +89 -20
  137. package/dist/peer-join-backfill.js +265 -108
  138. package/dist/peer-join-backfill.js.map +1 -1
  139. package/dist/pending-join-runner.d.ts +48 -18
  140. package/dist/pending-join-runner.js +74 -56
  141. package/dist/pending-join-runner.js.map +1 -1
  142. package/dist/provider/rent-node.js +1 -1
  143. package/dist/provider/rent-node.js.map +1 -1
  144. package/dist/seed-bootstrap.d.ts +162 -133
  145. package/dist/seed-bootstrap.js +300 -283
  146. package/dist/seed-bootstrap.js.map +1 -1
  147. package/dist/seed-trust-policy.js +4 -4
  148. package/dist/strand-addr-protocol.d.ts +4 -1
  149. package/dist/strand-addr-protocol.js +8 -2
  150. package/dist/strand-addr-protocol.js.map +1 -1
  151. package/dist/strand-cohort.d.ts +3 -0
  152. package/dist/strand-cohort.js.map +1 -1
  153. package/dist/strand-database.d.ts +24 -2
  154. package/dist/strand-database.js +29 -6
  155. package/dist/strand-database.js.map +1 -1
  156. package/dist/strand-first-sync-gate.d.ts +176 -88
  157. package/dist/strand-first-sync-gate.js +288 -121
  158. package/dist/strand-first-sync-gate.js.map +1 -1
  159. package/dist/strand-formation-deadlines.d.ts +9 -9
  160. package/dist/strand-formation-deadlines.js +9 -9
  161. package/dist/strand-formation-manager.d.ts +23 -17
  162. package/dist/strand-formation-manager.js +46 -44
  163. package/dist/strand-formation-manager.js.map +1 -1
  164. package/dist/strand-formation-protocol.d.ts +5 -8
  165. package/dist/strand-formation-protocol.js +101 -16
  166. package/dist/strand-formation-protocol.js.map +1 -1
  167. package/dist/strand-instance-manager.d.ts +27 -18
  168. package/dist/strand-instance-manager.js +119 -50
  169. package/dist/strand-instance-manager.js.map +1 -1
  170. package/dist/strand-membership-reconciler.d.ts +16 -13
  171. package/dist/strand-membership-reconciler.js +30 -22
  172. package/dist/strand-membership-reconciler.js.map +1 -1
  173. package/dist/strand-membership-writer.d.ts +45 -22
  174. package/dist/strand-membership-writer.js +226 -131
  175. package/dist/strand-membership-writer.js.map +1 -1
  176. package/dist/strand-move.d.ts +6 -5
  177. package/dist/strand-move.js +36 -32
  178. package/dist/strand-move.js.map +1 -1
  179. package/dist/strand-peer-redial.js +2 -3
  180. package/dist/strand-peer-redial.js.map +1 -1
  181. package/dist/strand-revocation-enforcer.d.ts +134 -60
  182. package/dist/strand-revocation-enforcer.js +144 -71
  183. package/dist/strand-revocation-enforcer.js.map +1 -1
  184. package/dist/strand-solicitation.d.ts +50 -39
  185. package/dist/strand-solicitation.js +10 -3
  186. package/dist/strand-solicitation.js.map +1 -1
  187. package/dist/strand-watcher.d.ts +80 -13
  188. package/dist/strand-watcher.js +152 -48
  189. package/dist/strand-watcher.js.map +1 -1
  190. package/dist/table-change-watch.d.ts +63 -0
  191. package/dist/table-change-watch.js +209 -0
  192. package/dist/table-change-watch.js.map +1 -0
  193. package/dist/timeout-scheduler.d.ts +4 -3
  194. package/dist/timeout-scheduler.js +4 -3
  195. package/dist/timeout-scheduler.js.map +1 -1
  196. package/dist/types.d.ts +374 -150
  197. package/dist/types.js +4 -4
  198. package/dist/types.js.map +1 -1
  199. package/package.json +17 -8
  200. package/src/bootstrap-peer-store-file.ts +44 -44
  201. package/src/bootstrap-peer-store.ts +214 -214
  202. package/src/cadre-invite-protocol.ts +740 -1052
  203. package/src/cadre-join-request.ts +24 -3
  204. package/src/cadre-node.ts +2402 -1079
  205. package/src/control-authorization.ts +47 -14
  206. package/src/control-cohort.ts +204 -128
  207. package/src/control-database.ts +1749 -820
  208. package/src/control-formation-recorder.ts +359 -353
  209. package/src/control-protocol-guard.ts +23 -6
  210. package/src/control-schema-version.ts +301 -0
  211. package/src/control-schema.ts +273 -78
  212. package/src/control-write-retry.ts +641 -567
  213. package/src/delegate-admission.ts +351 -314
  214. package/src/ed25519-key.ts +174 -171
  215. package/src/edge-node/config.ts +277 -0
  216. package/src/edge-node/dissolve.ts +251 -0
  217. package/src/edge-node/edge-join.ts +319 -0
  218. package/src/edge-node/edge-node.ts +1045 -0
  219. package/src/edge-node/index.ts +59 -0
  220. package/src/edge-node/join-credential.ts +16 -0
  221. package/src/edge-node/lazy-raw-storage.ts +178 -0
  222. package/src/edge-node/node-local.ts +84 -0
  223. package/src/edge-node/options.ts +179 -0
  224. package/src/edge-node/owner-grant.ts +63 -0
  225. package/src/edge-node/platform.ts +151 -0
  226. package/src/edge-node/strand-move.ts +270 -0
  227. package/src/edge-node/strands.ts +72 -0
  228. package/src/enrolled-machine-store.ts +3 -3
  229. package/src/formation-token.ts +63 -0
  230. package/src/index.ts +899 -789
  231. package/src/joined-strand-store.ts +73 -28
  232. package/src/key-store.ts +10 -4
  233. package/src/link-budget.ts +9 -7
  234. package/src/machine-removal.ts +26 -111
  235. package/src/member-redemption.ts +649 -0
  236. package/src/membership-view.ts +144 -0
  237. package/src/owner-anchor-sync.ts +19 -18
  238. package/src/owner-grant-protocol.ts +547 -0
  239. package/src/owner-key-export.ts +348 -0
  240. package/src/owner-key-flows.ts +109 -0
  241. package/src/owner-key-store.ts +85 -0
  242. package/src/owner-keys.ts +178 -0
  243. package/src/owner-signer.ts +94 -0
  244. package/src/peer-addr-book.ts +4 -3
  245. package/src/peer-authorization.ts +558 -488
  246. package/src/peer-join-backfill.ts +971 -761
  247. package/src/pending-join-runner.ts +76 -53
  248. package/src/provider/rent-node.ts +1 -1
  249. package/src/seed-bootstrap.ts +343 -349
  250. package/src/seed-trust-policy.ts +4 -4
  251. package/src/strand-addr-protocol.ts +547 -538
  252. package/src/strand-cohort.ts +4 -0
  253. package/src/strand-database.ts +352 -319
  254. package/src/strand-first-sync-gate.ts +334 -133
  255. package/src/strand-formation-deadlines.ts +227 -227
  256. package/src/strand-formation-manager.ts +731 -725
  257. package/src/strand-formation-protocol.ts +122 -24
  258. package/src/strand-instance-manager.ts +2092 -2026
  259. package/src/strand-membership-reconciler.ts +33 -25
  260. package/src/strand-membership-writer.ts +2093 -1974
  261. package/src/strand-move.ts +50 -38
  262. package/src/strand-peer-redial.ts +2 -3
  263. package/src/strand-revocation-enforcer.ts +740 -654
  264. package/src/strand-solicitation.ts +61 -34
  265. package/src/strand-watcher.ts +175 -56
  266. package/src/table-change-watch.ts +285 -0
  267. package/src/timeout-scheduler.ts +29 -28
  268. package/src/types.ts +391 -152
@@ -21,9 +21,9 @@
21
21
  * Built over `CadreNode`'s public surface, so `cadre-node.ts` does not grow by the feature.
22
22
  */
23
23
  import debug from 'debug';
24
- import { sign } from '@optimystic/quereus-plugin-crypto';
25
24
  import type { PersistedNetworkState } from '@optimystic/db-p2p';
26
25
  import type { CadreNode } from './cadre-node.js';
26
+ import type { OwnerSign } from './owner-signer.js';
27
27
  import {
28
28
  isPendingJoinConflict,
29
29
  isStrandIdConflict,
@@ -98,10 +98,10 @@ export interface StrandMoveReport {
98
98
  pendingJoinsMoved: number;
99
99
  }
100
100
 
101
- /** {@link adoptStrandBundle} refused before any write: the running node's key is not an owner of its party. */
101
+ /** {@link adoptStrandBundle} refused before any write: the running node's owner key is not an owner of its party. */
102
102
  export class StrandMoveNotOwnerError extends Error {
103
103
  constructor(readonly partyId: string) {
104
- super(`Cannot adopt strands into party ${partyId}: this node's key is not one of its owners, and every re-seated row is owner-signed`);
104
+ super(`Cannot adopt strands into party ${partyId}: this node's owner key is not one of its owners, and every re-seated row is owner-signed`);
105
105
  this.name = 'StrandMoveNotOwnerError';
106
106
  }
107
107
  }
@@ -201,10 +201,11 @@ async function exportFormationInvites(database: ControlDatabase, now: number): P
201
201
  }
202
202
 
203
203
  /**
204
- * Seat `bundle` in the running node's party, every row owner-signed by the node's own identity
205
- * key. Refuses up front, with a typed error and no write, when that key is not an owner of the
206
- * party ({@link StrandMoveNotOwnerError}) or the bundle came from this very party
207
- * ({@link StrandMoveSamePartyError}).
204
+ * Seat `bundle` in the running node's party, every row owner-signed through the node's owner
205
+ * signer (`CadreNode.getOwnerSigner`, wired here by `wireOwnKeyIfOwner` when it is not yet),
206
+ * in one approval for the whole bundle. Refuses up front, with a typed error and no write, when
207
+ * that signer's key is not an owner of the party ({@link StrandMoveNotOwnerError}) or the
208
+ * bundle came from this very party ({@link StrandMoveSamePartyError}).
208
209
  *
209
210
  * Strands first, then the invitations, then the pending joins. Every write is idempotent (a row
210
211
  * already present with the same content is skipped and counted as moved), so the same bundle
@@ -221,11 +222,28 @@ export async function adoptStrandBundle(node: CadreNode, bundle: StrandBundle):
221
222
  if (bundle.partyId === node.partyId) {
222
223
  throw new StrandMoveSamePartyError(node.partyId);
223
224
  }
224
- if (!(await node.wireOwnKeyIfOwner())) {
225
+ const owner = (await node.wireOwnKeyIfOwner()) ? node.getOwnerSigner() : null;
226
+ if (owner === null) {
225
227
  throw new StrandMoveNotOwnerError(node.partyId);
226
228
  }
227
- const owner = node.getIdentityOwnerKey();
228
- const signer: OwnerSigner = { ownerKey: owner.publicKeyB64, signMessage: signMessageWith(owner.privateKeyB64) };
229
+ const action = `Move ${bundle.strands.length} strand(s) from the cadre ${bundle.partyId} into this one`;
230
+ return await owner.approve({ partyId: node.partyId, action }, (sign) =>
231
+ adoptUnderApproval(node, database, bundle, { ownerKey: owner.publicKey, sign }));
232
+ }
233
+
234
+ /** An open owner approval: the key every row is written under, and the approval's `sign`. */
235
+ interface OwnerApproval {
236
+ ownerKey: string;
237
+ sign: OwnerSign;
238
+ }
239
+
240
+ /** {@link adoptStrandBundle}'s writes, every one signed inside the one approval `approval`. */
241
+ async function adoptUnderApproval(
242
+ node: CadreNode,
243
+ database: ControlDatabase,
244
+ bundle: StrandBundle,
245
+ approval: OwnerApproval
246
+ ): Promise<StrandMoveReport> {
229
247
  const report: StrandMoveReport = {
230
248
  fromPartyId: bundle.partyId,
231
249
  moved: [],
@@ -236,15 +254,15 @@ export async function adoptStrandBundle(node: CadreNode, bundle: StrandBundle):
236
254
  pendingJoinsMoved: 0
237
255
  };
238
256
  for (const entry of bundle.strands) {
239
- await adoptStrand(node, database, signer, entry, report);
257
+ await adoptStrand(node, database, approval, entry, report);
240
258
  }
241
259
  const settled = settledStrands(report);
242
260
  const now = Date.now();
243
261
  for (const invite of bundle.formationInvites) {
244
- await adoptFormationInvite(node, database, invite, settled, report, now);
262
+ await adoptFormationInvite(node, database, approval.sign, invite, settled, report, now);
245
263
  }
246
264
  for (const join of bundle.pendingJoins) {
247
- await adoptPendingJoin(database, signer, join, report);
265
+ await adoptPendingJoin(database, approval, join, report);
248
266
  }
249
267
  log('adopt(%s ← %s): %d moved, %d refused, %d failed; %d invitation(s) moved, %d dropped; %d pending join(s)',
250
268
  node.partyId, bundle.partyId, report.moved.length, report.refused.length, report.failed.length,
@@ -252,11 +270,6 @@ export async function adoptStrandBundle(node: CadreNode, bundle: StrandBundle):
252
270
  return report;
253
271
  }
254
272
 
255
- interface OwnerSigner {
256
- ownerKey: string;
257
- signMessage: (message: Uint8Array) => string;
258
- }
259
-
260
273
  /** The strands an adoption has decided on, by outcome, for the invitations bound to them. */
261
274
  interface SettledStrands {
262
275
  moved: Set<string>;
@@ -271,8 +284,8 @@ function settledStrands(report: StrandMoveReport): SettledStrands {
271
284
  }
272
285
 
273
286
  /**
274
- * Seat one strand, in the order the adopting node's strand watcher demands. The watcher polls
275
- * `Strand` and `JoinedStrand` every five seconds and launches, or offers, a new row at once; a
287
+ * Seat one strand, in the order the adopting node's strand watcher demands. The watcher runs
288
+ * on every change to `Strand` and `JoinedStrand` and launches, or offers, a new row at once; a
276
289
  * closed strand's founder launch resolves the party key and, finding none, mints a fresh one,
277
290
  * which would found the strand a second time under a stranger's identity. So the party key is
278
291
  * seated before the row, and the strand's peers (its saved routing table and carried addresses)
@@ -282,7 +295,7 @@ function settledStrands(report: StrandMoveReport): SettledStrands {
282
295
  async function adoptStrand(
283
296
  node: CadreNode,
284
297
  database: ControlDatabase,
285
- signer: OwnerSigner,
298
+ approval: OwnerApproval,
286
299
  entry: StrandBundleEntry,
287
300
  report: StrandMoveReport
288
301
  ): Promise<void> {
@@ -297,12 +310,15 @@ async function adoptStrand(
297
310
  }
298
311
  await seatStrandPeers(node, entry);
299
312
  if (entry.partyKey !== null) {
300
- await node.ensureStrandPartyKey(strandId, entry.partyKey);
313
+ await node.ensureStrandPartyKey(strandId, entry.partyKey, approval.sign);
301
314
  }
302
315
  if (entry.kind === 'own') {
303
- await node.publishStrand(strandId, entry.row.Type, entry.row.MemberPrivateKey ?? undefined);
316
+ // NOTE: with the party key seated first, a closed strand's founding transaction is refused
317
+ // on `StrandPartyKey.Id` and written again without a key: a local refusal, no extra commit.
318
+ // If moves of many closed strands get slow, let publishStrand take a known party key.
319
+ await node.publishStrand(strandId, entry.row.Type, entry.row.MemberPrivateKey ?? undefined, approval.sign);
304
320
  } else {
305
- await seatJoinedStrand(database, signer, entry.row, live.joined);
321
+ await seatJoinedStrand(database, approval, entry.row, live.joined);
306
322
  }
307
323
  report.moved.push(strandId);
308
324
  } catch (error) {
@@ -377,7 +393,7 @@ async function seatStrandPeers(node: CadreNode, entry: StrandBundleEntry): Promi
377
393
  */
378
394
  async function seatJoinedStrand(
379
395
  database: ControlDatabase,
380
- signer: OwnerSigner,
396
+ approval: OwnerApproval,
381
397
  row: StrandBundleEntry['row'],
382
398
  existing: StrandRow | null
383
399
  ): Promise<void> {
@@ -385,7 +401,7 @@ async function seatJoinedStrand(
385
401
  return;
386
402
  }
387
403
  try {
388
- await database.insertJoinedStrand(row, signer.ownerKey, signer.signMessage);
404
+ await database.insertJoinedStrand(row, approval.ownerKey, approval.sign);
389
405
  } catch (error) {
390
406
  if (!isStrandIdConflict(error, 'JoinedStrand')) {
391
407
  throw error;
@@ -412,6 +428,7 @@ async function seatJoinedStrand(
412
428
  async function adoptFormationInvite(
413
429
  node: CadreNode,
414
430
  database: ControlDatabase,
431
+ sign: OwnerSign,
415
432
  invite: FormationInviteEntry,
416
433
  settled: SettledStrands,
417
434
  report: StrandMoveReport,
@@ -434,7 +451,7 @@ async function adoptFormationInvite(
434
451
  expiresAtMs: invite.expiresAtMs ?? undefined,
435
452
  totalUses: invite.usesLeft ?? undefined,
436
453
  strandId: invite.strandId ?? undefined
437
- });
454
+ }, sign);
438
455
  }
439
456
  report.invitationsMoved += 1;
440
457
  } catch (error) {
@@ -464,15 +481,15 @@ async function adoptFormationInvite(
464
481
  */
465
482
  async function adoptPendingJoin(
466
483
  database: ControlDatabase,
467
- signer: OwnerSigner,
484
+ approval: OwnerApproval,
468
485
  join: PendingJoin,
469
486
  report: StrandMoveReport
470
487
  ): Promise<void> {
471
488
  const { StampId: _stampId, outcome, ...request } = join;
472
489
  try {
473
- const live = await seatJoinRequest(database, signer, request);
490
+ const live = await seatJoinRequest(database, approval, request);
474
491
  if (live !== null && outcome !== null && live.outcome === null) {
475
- await database.recordJoinOutcome(live, outcome, signer.ownerKey, signer.signMessage);
492
+ await database.recordJoinOutcome(live, outcome, approval.ownerKey, approval.sign);
476
493
  }
477
494
  report.pendingJoinsMoved += 1;
478
495
  } catch (error) {
@@ -487,9 +504,9 @@ async function adoptPendingJoin(
487
504
  * own ask, left alone) or a dismissed one whose retired stamp is still held until the reap
488
505
  * pass (the party chose not to have it).
489
506
  */
490
- async function seatJoinRequest(database: ControlDatabase, signer: OwnerSigner, request: JoinRequestFields): Promise<PendingJoin | null> {
507
+ async function seatJoinRequest(database: ControlDatabase, approval: OwnerApproval, request: JoinRequestFields): Promise<PendingJoin | null> {
491
508
  try {
492
- return await database.insertJoinRequest(request, signer.ownerKey, signer.signMessage);
509
+ return await database.insertJoinRequest(request, approval.ownerKey, approval.sign);
493
510
  } catch (error) {
494
511
  if (!isPendingJoinConflict(error)) {
495
512
  throw error;
@@ -557,7 +574,7 @@ function isOpenJoin(join: PendingJoin, nowMs: number): boolean {
557
574
  return join.outcome === null && nowMs < join.ExpiresAt;
558
575
  }
559
576
 
560
- /** Content only: `Type` and the read secret. `FounderOwnerKey` is provenance, and a moved strand's changes. */
577
+ /** Content only: `Type` and the read secret. The founder columns are provenance, and a moved strand's change. */
561
578
  function sameStrandContent(live: StrandRow, desired: StrandBundleEntry['row']): boolean {
562
579
  return live.Type === desired.Type && (live.MemberPrivateKey ?? null) === (desired.MemberPrivateKey ?? null);
563
580
  }
@@ -575,11 +592,6 @@ function requireControlDatabase(node: CadreNode, action: string): ControlDatabas
575
592
  return database;
576
593
  }
577
594
 
578
- function signMessageWith(privateKeyB64: string): (message: Uint8Array) => string {
579
- return (message: Uint8Array): string =>
580
- sign(message, privateKeyB64, 'ed25519', 'bytes', 'base64url', 'base64url') as string;
581
- }
582
-
583
595
  function errorText(error: unknown): string {
584
596
  return error instanceof Error ? error.message : String(error);
585
597
  }
@@ -234,9 +234,8 @@ class StrandPeerRedialLoop implements StrandPeerRedial {
234
234
  *
235
235
  * NOTE: a round whose dials failed within milliseconds (a relay connection that is still
236
236
  * closing, the member's relay reservation not back yet) waits as long as one whose dials ran
237
- * out their budget. In `joiner-first-commit-over-slow-relay`'s third arm that first wait is what
238
- * leaves the joiner writable with no member connected. If reconnecting within seconds of a cut
239
- * ever matters, retry a round that failed that fast after a short delay instead.
237
+ * out their budget. If reconnecting within seconds of a cut ever matters, retry a round that
238
+ * failed that fast after a short delay instead.
240
239
  *
241
240
  * NOTE: one such fast failure is `ConnectionClosedError: The connection muxer is "closed"`. A
242
241
  * relay that aborts its connection to this node cannot tell it so (`@libp2p/websockets`'