@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
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `EdgeNode.redeemOwnerGrant`'s sequence: make the app an owner of the party it runs in with
3
+ * owner-grant text, seating the owner slot's key (docs/architecture.md → "Owner grants"). Runs
4
+ * over a narrow view of the runtime ({@link OwnerGrantRuntime}), inside one of its transitions,
5
+ * as the join does.
6
+ */
7
+ import type { CadreNode } from '../cadre-node.js';
8
+ import type { OwnerSigner } from '../owner-signer.js';
9
+ import type { OwnerGenesisResult } from './config.js';
10
+ /** The part of the edge-node runtime a redemption drives; the join's view has the same two calls. */
11
+ export interface OwnerGrantRuntime {
12
+ /** The owner slot's key, created when the slot is empty: the key an owner grant seats. */
13
+ ownerSlotSigner(): Promise<OwnerSigner>;
14
+ /** `granted`: an owner grant has just seated the slot's key, so wait for the row. */
15
+ wireJoinedOwner(node: CadreNode, granted: boolean): Promise<OwnerGenesisResult>;
16
+ }
17
+ /** What {@link redeemOwnerGrantText} settled. */
18
+ export interface RedeemedOwnerGrant {
19
+ /** The owner slot's key: the one the grant seated, or found already an owner. */
20
+ ownerKey: string;
21
+ /** The key was already a live owner of the party: nothing was signed or written, and the grant spent no use. */
22
+ alreadyOwner: boolean;
23
+ /**
24
+ * The key is wired as the node's owner signer. False when its row has not reached this node
25
+ * within the owner-step bound (a redemption at another member); a later start wires it.
26
+ */
27
+ wired: boolean;
28
+ }
29
+ /**
30
+ * Decode `text`, refuse a grant for another party, answer a slot key that is already an owner
31
+ * here, else redeem the grant for the slot's key (`CadreNode.redeemOwnerGrant`) and wire it.
32
+ *
33
+ * The already-owner answer comes first because the redemption's local path refuses `alone`
34
+ * before it asks whether the key is an owner, and a device that is one should hear so whether
35
+ * or not it is connected.
36
+ *
37
+ * @throws the decoder's error for text that is not an owner grant; `OwnerGrantRejectedError`
38
+ * `party-mismatch` for a grant of another party, the code a member serving another cadre answers;
39
+ * and what `CadreNode.redeemOwnerGrant` throws.
40
+ */
41
+ export declare function redeemOwnerGrantText(runtime: OwnerGrantRuntime, node: CadreNode, text: string): Promise<RedeemedOwnerGrant>;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `EdgeNode.redeemOwnerGrant`'s sequence: make the app an owner of the party it runs in with
3
+ * owner-grant text, seating the owner slot's key (docs/architecture.md → "Owner grants"). Runs
4
+ * over a narrow view of the runtime ({@link OwnerGrantRuntime}), inside one of its transitions,
5
+ * as the join does.
6
+ */
7
+ import { decodeOwnerGrant, OwnerGrantRejectedError } from '../owner-grant-protocol.js';
8
+ /**
9
+ * Decode `text`, refuse a grant for another party, answer a slot key that is already an owner
10
+ * here, else redeem the grant for the slot's key (`CadreNode.redeemOwnerGrant`) and wire it.
11
+ *
12
+ * The already-owner answer comes first because the redemption's local path refuses `alone`
13
+ * before it asks whether the key is an owner, and a device that is one should hear so whether
14
+ * or not it is connected.
15
+ *
16
+ * @throws the decoder's error for text that is not an owner grant; `OwnerGrantRejectedError`
17
+ * `party-mismatch` for a grant of another party, the code a member serving another cadre answers;
18
+ * and what `CadreNode.redeemOwnerGrant` throws.
19
+ */
20
+ export async function redeemOwnerGrantText(runtime, node, text) {
21
+ const bundle = decodeOwnerGrant(text);
22
+ if (bundle.partyId !== node.partyId) {
23
+ throw new OwnerGrantRejectedError('party-mismatch', `This grant is for cadre ${bundle.partyId}, not the one this app runs in`);
24
+ }
25
+ // NOTE: a key created here stays in the slot when the redemption then fails, as a failed grant
26
+ // join's does (`edge-join.ts`), so a later import of a different exported key is refused as
27
+ // slot-occupied until a grant seats this one. If users meet that, delete a key this call created
28
+ // when the refusal is one that wrote nothing (every refusal but an unreachable member or an
29
+ // unverifiable reply, after either of which the row may exist).
30
+ const signer = await runtime.ownerSlotSigner();
31
+ if (await node.wireOwnKeyIfOwner(signer)) {
32
+ return { ownerKey: signer.publicKey, alreadyOwner: true, wired: true };
33
+ }
34
+ const { alreadyOwner } = await node.redeemOwnerGrant(bundle, signer);
35
+ const wired = (await runtime.wireJoinedOwner(node, true)) === 'enrolled';
36
+ return { ownerKey: signer.publicKey, alreadyOwner, wired };
37
+ }
38
+ //# sourceMappingURL=owner-grant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-grant.js","sourceRoot":"","sources":["../../src/edge-node/owner-grant.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAyBvF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAA0B,EAAE,IAAe,EAAE,IAAY;IACnG,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,MAAM,IAAI,uBAAuB,CAAC,gBAAgB,EAAE,2BAA2B,MAAM,CAAC,OAAO,gCAAgC,CAAC,CAAC;IAChI,CAAC;IACD,+FAA+F;IAC/F,4FAA4F;IAC5F,iGAAiG;IACjG,4FAA4F;IAC5F,gEAAgE;IAChE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC;IAC/C,IAAI,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,CAAC,MAAM,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,UAAU,CAAC;IACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * What an app supplies to run the edge node (`createEdgeNode`): its key store, its storage, and
3
+ * the platform choices (transports, listen addresses, Noise crypto) that cadre-core cannot make
4
+ * for it. Fixed for the app's lifetime.
5
+ */
6
+ import type { IRawStorage, Libp2pTransports, NoiseCryptoInterface } from '@optimystic/db-p2p';
7
+ import type { KeyStore } from '../key-store.js';
8
+ import type { DurableSlot } from '../node-local-snapshot.js';
9
+ import type { CadreNodeConfig } from '../types.js';
10
+ import type { NodeLocalKv } from './node-local.js';
11
+ import type { NoiseCryptoMode } from './options.js';
12
+ export interface EdgeNodePlatform {
13
+ /**
14
+ * Everything secret: the identity (`DEFAULT_IDENTITY_KEY_ID`), the owner key in its own slot
15
+ * (`DEFAULT_OWNER_KEY_ID`), the joined-strand records (`KeyStoreJoinedStrandStore`) and the
16
+ * strand-move records. The phone's is the platform secure store; a browser's or a desktop
17
+ * app's may be a plaintext database, which is then where its identity already lives.
18
+ */
19
+ keyStore: KeyStore;
20
+ storage: EdgeNodeStorage;
21
+ /**
22
+ * libp2p transports, given the relays the node reserves on. Default: WebSockets and circuit
23
+ * relay. An app with WebRTC adds `webRTC({ rtcConfiguration: { iceServers } })`, `iceServers`
24
+ * from `resolveStunServers(relayAddrs)`.
25
+ */
26
+ transports?: (relayAddrs: readonly string[]) => Libp2pTransports;
27
+ /**
28
+ * Listen addresses besides the bare `/p2p-circuit` entry cadre-core adds for a named relay
29
+ * (`relay-addrs.ts` → `resolveListenAddrs`). Default `[]`: an edge device cannot accept a
30
+ * connection, so the only address it has is the one a relay reservation earns it. A browser
31
+ * tab returns `['/webrtc']` when it has relays, so a relayed connection can upgrade to a direct
32
+ * one.
33
+ */
34
+ listenAddrs?: (relayAddrs: readonly string[]) => string[];
35
+ /**
36
+ * Noise crypto built outside libp2p-noise: `build` returns the implementation for a mode,
37
+ * `defaultMode` the mode used when the start options name none. Absent: libp2p-noise's own
38
+ * crypto, pure JavaScript on a phone (the React Native kit's `/noise-crypto` is the native one).
39
+ */
40
+ noiseCrypto?: {
41
+ build: (mode: NoiseCryptoMode) => NoiseCryptoInterface | undefined;
42
+ defaultMode: NoiseCryptoMode;
43
+ };
44
+ /**
45
+ * The keys inside the node-local store. Each is a persistence contract: renaming one orphans
46
+ * every installed app's records. The defaults are the reference apps'; an app that names its
47
+ * own should pin them in a test.
48
+ */
49
+ names?: Partial<EdgeNodeNames>;
50
+ /**
51
+ * Dial loopback, private and plain `ws://` addresses. Default `true`: see the
52
+ * `connectionGater` comment in `buildEdgeNodeConfig`.
53
+ */
54
+ allowPrivateDial?: boolean;
55
+ /** Bound on owner genesis; default 60 s. See `runOwnerGenesis`. */
56
+ ownerGenesisTimeoutMs?: number;
57
+ /**
58
+ * A version naming the format of the data this build writes, for example the stack version it
59
+ * was built against. Saved with each successful start and reported by `loadSavedStart` as
60
+ * `writtenBy`, so a later build can tell data it cannot use from data it can.
61
+ */
62
+ dataVersion?: string;
63
+ /**
64
+ * The last word on the config the edge node built: strand filter, schema-signing policy,
65
+ * hibernation, `linkRoundTripMs`, or anything the parameters above do not cover.
66
+ */
67
+ configure?: (config: CadreNodeConfig) => CadreNodeConfig;
68
+ }
69
+ /** The keys inside the node-local store (`EdgeNodeStorage.nodeLocal`) that are the edge node's. */
70
+ export interface EdgeNodeNames {
71
+ /**
72
+ * Key of the saved-start record. One per app, not per party: it is what names the party every
73
+ * other record is filed under. Dot-free, so it cannot collide with the `<record>.<partyId>`
74
+ * keys of `node-local.ts`.
75
+ */
76
+ savedStartKey: string;
77
+ /**
78
+ * Key of the record naming a party whose dissolve has begun. Dot-free and one per app, as
79
+ * {@link EdgeNodeNames.savedStartKey} is.
80
+ */
81
+ dissolveMarkerKey: string;
82
+ /**
83
+ * Key of the join request this app opened as a link to another app (`LinkRequestStore` in
84
+ * `@serfab/cadre-rn/app-link`). Dot-free and one per app, as {@link EdgeNodeNames.savedStartKey} is.
85
+ */
86
+ linkRequestKey: string;
87
+ /**
88
+ * Key of the apps the user allowed to join before they asked (`AppRecommendationStore` in
89
+ * `@serfab/cadre-rn/app-link`). Dot-free and one per app, as {@link EdgeNodeNames.savedStartKey}
90
+ * is; each entry names its party.
91
+ */
92
+ recommendationsKey: string;
93
+ }
94
+ export declare const DEFAULT_EDGE_NODE_NAMES: EdgeNodeNames;
95
+ /**
96
+ * The app's storage, as the edge node opens, closes and deletes it: one raw block store per
97
+ * cadre-core storage scope, and one node-local key-value store beside them. The platform owns
98
+ * every handle; the edge node only says when it is done with them (`close`).
99
+ */
100
+ export interface EdgeNodeStorage {
101
+ /**
102
+ * Names the storage. `createEdgeNode` refuses a second starting, joining or running edge node
103
+ * over the same id in one process, and replaces a stopped one: two over the same storage
104
+ * would share databases and an identity.
105
+ */
106
+ readonly id: string;
107
+ /**
108
+ * One storage scope's raw block storage: `controlStorageScope(partyId)` for the control
109
+ * network, the strand id for each strand. The key is already safe as a database-name segment;
110
+ * use it verbatim, do not parse it. Synchronous, as `CadreNodeConfig.storage.provider` is; a
111
+ * platform whose opener is async returns `lazyRawStorage(...)`. The same handle for the same
112
+ * scope until {@link close}: cadre-core asks once per scope per runtime, and again after a
113
+ * strand stops.
114
+ */
115
+ rawStorage(scope: string): IRawStorage;
116
+ /** Delete a scope's storage. No handle on it may be open. Resolves when it is gone or was absent. */
117
+ destroyScope(scope: string): Promise<void>;
118
+ /**
119
+ * The node-local key-value store, holding the saved start, the dissolve marker, the app-link
120
+ * records and the per-party records of `node-local.ts`. Usable with no node running: reopens
121
+ * after {@link close}.
122
+ */
123
+ nodeLocal(): NodeLocalKv;
124
+ /**
125
+ * Flush and close the node-local store, then close every handle {@link rawStorage} opened. A
126
+ * failure closing one is logged and the rest still close; the call does not reject.
127
+ */
128
+ close(): Promise<void>;
129
+ /**
130
+ * Per-party slot for the trusted-owner anchor (`PersistentTrustedOwnerStore`). Absent: the
131
+ * node-local key `trustedOwnersKvKey(partyId)`. The anchor is not secret but it is
132
+ * trust-bearing, so the phone puts it in its secure store, beside the identity it qualifies.
133
+ */
134
+ trustedOwnerSlot?(partyId: string): RemovableSlot;
135
+ }
136
+ /** A {@link DurableSlot} a removal can delete: the trusted-owner anchor of a party being removed. */
137
+ export interface RemovableSlot extends DurableSlot {
138
+ /** Delete the slot's record, durably. Resolves when it is gone or was absent. */
139
+ remove(): Promise<void>;
140
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * What an app supplies to run the edge node (`createEdgeNode`): its key store, its storage, and
3
+ * the platform choices (transports, listen addresses, Noise crypto) that cadre-core cannot make
4
+ * for it. Fixed for the app's lifetime.
5
+ */
6
+ export const DEFAULT_EDGE_NODE_NAMES = {
7
+ savedStartKey: 'start-options',
8
+ dissolveMarkerKey: 'dissolving-party',
9
+ linkRequestKey: 'link-join-request',
10
+ recommendationsKey: 'recommended-apps',
11
+ };
12
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/edge-node/platform.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6FH,MAAM,CAAC,MAAM,uBAAuB,GAAkB;IACrD,aAAa,EAAE,eAAe;IAC9B,iBAAiB,EAAE,kBAAkB;IACrC,cAAc,EAAE,mBAAmB;IACnC,kBAAkB,EAAE,kBAAkB;CACtC,CAAC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Carrying the strands of the party this app founded into the party it joins
3
+ * (docs/architecture.md → "An app joins an existing cadre" → "Moving the app's strands").
4
+ *
5
+ * The app runs one node, so cadre-core's two ends of a move run on two nodes, one after the
6
+ * other: `exportStrandBundle` on the left party's node before the switch, `adoptStrandBundle` on
7
+ * the joined party's node once the app's key is wired as an owner there. Between them the bundle
8
+ * is kept here, one record per left party, so a join killed partway, or an owner key wired only
9
+ * on a later launch, finishes the move on a later start. The bundle carries read secrets, party
10
+ * keys and bearer invitations, so the record lives in the key store beside the identity.
11
+ */
12
+ import type { StrandBundle, StrandMoveReport } from '../strand-move.js';
13
+ import type { CadreNode } from '../cadre-node.js';
14
+ import type { KeyStore } from '../key-store.js';
15
+ import { type JoinCredential } from './join-credential.js';
16
+ /** How a join's strand move settled: `JoinCadreResult.strandMove`. */
17
+ export type StrandMoveOutcome =
18
+ /** Adopted. `partial`: `report.failed` lists what the next start, or `finishStrandMove`, retries. */
19
+ {
20
+ state: 'done' | 'partial';
21
+ report: StrandMoveReport;
22
+ }
23
+ /** Not adopted yet: the app's key is not wired as an owner of the joined party. A start after it is adopts. */
24
+ | {
25
+ state: 'deferred';
26
+ reason: 'owner-key-not-wired';
27
+ }
28
+ /** Not adopted yet: the adoption threw (logged). The next start, or `finishStrandMove`, retries. */
29
+ | {
30
+ state: 'deferred';
31
+ reason: 'adoption-failed';
32
+ error: Error;
33
+ };
34
+ /** A move still waiting: `EdgeNode.pendingStrandMove`. */
35
+ export interface PendingStrandMove {
36
+ fromPartyId: string;
37
+ /** The party the strands move into; the record is adopted there and nowhere else. */
38
+ toPartyId: string;
39
+ /** Strands of `fromPartyId` not seated in `toPartyId` yet. */
40
+ strandIds: string[];
41
+ /** Strands `toPartyId` refused permanently so far: it participates in them under its own identity. */
42
+ refused: StrandMoveReport['refused'];
43
+ }
44
+ /**
45
+ * `EdgeNode.joinCadre` refused before anything stopped: the party this app founded holds strands,
46
+ * pending joins or invitations, and a machine invitation does not make the app an owner of the
47
+ * party it joins. Every row a move seats there is owner-signed, so a member's join would leave
48
+ * them in a party the app no longer runs. Joining with an owner grant moves them.
49
+ */
50
+ export declare class StrandsNeedOwnerJoinError extends Error {
51
+ readonly fromPartyId: string;
52
+ readonly toPartyId: string;
53
+ readonly strandCount: number;
54
+ constructor(bundle: StrandBundle, toPartyId: string);
55
+ }
56
+ /** What the key store holds for one left party between the export and a complete adoption. */
57
+ interface StrandMoveRecord {
58
+ v: typeof RECORD_VERSION;
59
+ targetPartyId: string;
60
+ bundle: StrandBundle;
61
+ /** Strand ids an earlier adoption seated. Never left out of a retry: an invitation bound to one needs it under `moved`. */
62
+ moved: string[];
63
+ /** Permanent refusals from earlier adoptions, left out of a retry and reported each time. */
64
+ refused: StrandMoveReport['refused'];
65
+ }
66
+ declare const RECORD_VERSION = 1;
67
+ /** The move records, one key-store slot per left party. A read the key store refuses throws; it is never read as "no record". */
68
+ export declare class StrandMoveRecords {
69
+ private readonly keyStore;
70
+ constructor(keyStore: KeyStore);
71
+ /**
72
+ * Replaces the left party's record: a newer export is the fuller one.
73
+ *
74
+ * NOTE: on a phone, a bundle with a routing table per strand runs past expo-secure-store's
75
+ * 2048-byte value guideline; version 14 warns and stores it, and the iOS Keychain and Android's
76
+ * encrypted preferences hold more. If a later SDK throws instead, keep the routing tables
77
+ * (not secret) in the node-local store and only the secrets here.
78
+ */
79
+ save(record: StrandMoveRecord): Promise<void>;
80
+ load(fromPartyId: string): Promise<StrandMoveRecord | undefined>;
81
+ remove(fromPartyId: string): Promise<void>;
82
+ /** Every record naming `toPartyId`, in slot order. */
83
+ naming(toPartyId: string): Promise<StrandMoveRecord[]>;
84
+ /**
85
+ * The record in `slot`. One that does not parse is deleted with a warning: nothing can adopt
86
+ * it, and the strands are still in the left party's storage. Its absence then reads as
87
+ * "nothing waiting", so code that deletes that storage must not take a missing record alone
88
+ * as proof that every strand reached the joined party.
89
+ */
90
+ private read;
91
+ }
92
+ /**
93
+ * The record a join writes before the switch, or `undefined` when the bundle holds nothing to
94
+ * move. Throws {@link StrandsNeedOwnerJoinError} when it does and `credential` is a machine
95
+ * invitation, which does not make the app an owner of the party it joins.
96
+ */
97
+ export declare function joinMoveRecord(bundle: StrandBundle, credential: JoinCredential): StrandMoveRecord | undefined;
98
+ /**
99
+ * Adopt `record` on `node`, running in the record's target party, without the strands an earlier
100
+ * attempt had refused; then delete the record when nothing failed, or save this attempt's
101
+ * progress. The report lists every refusal so far. Throws what `adoptStrandBundle` throws. A
102
+ * failure updating the record is only logged: the record left behind is adopted again, without
103
+ * harm, by the next start.
104
+ */
105
+ export declare function adoptMoveRecord(node: CadreNode, records: StrandMoveRecords, record: StrandMoveRecord): Promise<StrandMoveReport>;
106
+ /**
107
+ * Adopt every record naming the node's party, by left party. A record the key store refuses to
108
+ * read throws; an adoption that throws is logged and reported as deferred, and its record kept.
109
+ */
110
+ export declare function adoptMovesInto(node: CadreNode, records: StrandMoveRecords): Promise<Map<string, StrandMoveOutcome>>;
111
+ export declare function deferredOutcome(err: unknown): StrandMoveOutcome;
112
+ /** A thrown value as an `Error`, for a status or outcome that carries one. */
113
+ export declare function toError(err: unknown): Error;
114
+ export declare function pendingMove(record: StrandMoveRecord): PendingStrandMove;
115
+ export {};
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Carrying the strands of the party this app founded into the party it joins
3
+ * (docs/architecture.md → "An app joins an existing cadre" → "Moving the app's strands").
4
+ *
5
+ * The app runs one node, so cadre-core's two ends of a move run on two nodes, one after the
6
+ * other: `exportStrandBundle` on the left party's node before the switch, `adoptStrandBundle` on
7
+ * the joined party's node once the app's key is wired as an owner there. Between them the bundle
8
+ * is kept here, one record per left party, so a join killed partway, or an owner key wired only
9
+ * on a later launch, finishes the move on a later start. The bundle carries read secrets, party
10
+ * keys and bearer invitations, so the record lives in the key store beside the identity.
11
+ */
12
+ import { adoptStrandBundle, StrandMoveNotOwnerError } from '../strand-move.js';
13
+ import { isOwnerGrant } from './join-credential.js';
14
+ import { toString as uint8ArrayToString, fromString as uint8ArrayFromString } from 'uint8arrays';
15
+ /**
16
+ * `EdgeNode.joinCadre` refused before anything stopped: the party this app founded holds strands,
17
+ * pending joins or invitations, and a machine invitation does not make the app an owner of the
18
+ * party it joins. Every row a move seats there is owner-signed, so a member's join would leave
19
+ * them in a party the app no longer runs. Joining with an owner grant moves them.
20
+ */
21
+ export class StrandsNeedOwnerJoinError extends Error {
22
+ constructor(bundle, toPartyId) {
23
+ super(`Cannot join party ${toPartyId} as a member: ${describeContent(bundle)} of party ${bundle.partyId} ` +
24
+ 'would be left behind, and only an owner can move them. Join with an owner grant instead, made under the owner app\'s Owner keys.');
25
+ this.name = 'StrandsNeedOwnerJoinError';
26
+ this.fromPartyId = bundle.partyId;
27
+ this.toPartyId = toPartyId;
28
+ this.strandCount = bundle.strands.length;
29
+ }
30
+ }
31
+ const RECORD_VERSION = 1;
32
+ /** Key-store id prefix of every move record; the left party's id follows, base64url. */
33
+ const SLOT_PREFIX = 'cadre/strand-move/';
34
+ /**
35
+ * A persistence contract: `cadre/strand-move/<base64url of the party id's UTF-8 bytes>`, the
36
+ * id the React Native kit wrote before the records moved here (its `secureStoreKeySegment`
37
+ * encodes the same way), so an installed phone's record is read back.
38
+ */
39
+ function slotId(fromPartyId) {
40
+ return SLOT_PREFIX + uint8ArrayToString(uint8ArrayFromString(fromPartyId, 'utf8'), 'base64url');
41
+ }
42
+ /** The move records, one key-store slot per left party. A read the key store refuses throws; it is never read as "no record". */
43
+ export class StrandMoveRecords {
44
+ constructor(keyStore) {
45
+ this.keyStore = keyStore;
46
+ }
47
+ /**
48
+ * Replaces the left party's record: a newer export is the fuller one.
49
+ *
50
+ * NOTE: on a phone, a bundle with a routing table per strand runs past expo-secure-store's
51
+ * 2048-byte value guideline; version 14 warns and stores it, and the iOS Keychain and Android's
52
+ * encrypted preferences hold more. If a later SDK throws instead, keep the routing tables
53
+ * (not secret) in the node-local store and only the secrets here.
54
+ */
55
+ save(record) {
56
+ return this.keyStore.set(slotId(record.bundle.partyId), uint8ArrayFromString(JSON.stringify(record), 'utf8'));
57
+ }
58
+ load(fromPartyId) {
59
+ return this.read(slotId(fromPartyId));
60
+ }
61
+ remove(fromPartyId) {
62
+ return this.keyStore.delete(slotId(fromPartyId));
63
+ }
64
+ /** Every record naming `toPartyId`, in slot order. */
65
+ async naming(toPartyId) {
66
+ const slots = (await this.keyStore.list()).filter((slot) => slot.startsWith(SLOT_PREFIX)).sort();
67
+ const records = [];
68
+ for (const slot of slots) {
69
+ const record = await this.read(slot);
70
+ if (record?.targetPartyId === toPartyId)
71
+ records.push(record);
72
+ }
73
+ return records;
74
+ }
75
+ /**
76
+ * The record in `slot`. One that does not parse is deleted with a warning: nothing can adopt
77
+ * it, and the strands are still in the left party's storage. Its absence then reads as
78
+ * "nothing waiting", so code that deletes that storage must not take a missing record alone
79
+ * as proof that every strand reached the joined party.
80
+ */
81
+ async read(slot) {
82
+ const bytes = await this.keyStore.get(slot);
83
+ if (bytes === undefined)
84
+ return undefined;
85
+ const record = parseRecord(uint8ArrayToString(bytes, 'utf8'));
86
+ if (record !== undefined && slotId(record.bundle.partyId) === slot)
87
+ return record;
88
+ console.warn(`[cadre-core/edge-node] deleting the strand move record in ${slot}: it is not one this kit wrote`);
89
+ try {
90
+ await this.keyStore.delete(slot);
91
+ }
92
+ catch (err) {
93
+ // Read as absent either way; failing every start over a record nothing can use would be worse.
94
+ console.warn(`[cadre-core/edge-node] could not delete the strand move record in ${slot}:`, err);
95
+ }
96
+ return undefined;
97
+ }
98
+ }
99
+ /**
100
+ * The record a join writes before the switch, or `undefined` when the bundle holds nothing to
101
+ * move. Throws {@link StrandsNeedOwnerJoinError} when it does and `credential` is a machine
102
+ * invitation, which does not make the app an owner of the party it joins.
103
+ */
104
+ export function joinMoveRecord(bundle, credential) {
105
+ if (bundle.strands.length + bundle.pendingJoins.length + bundle.formationInvites.length === 0)
106
+ return undefined;
107
+ if (!isOwnerGrant(credential))
108
+ throw new StrandsNeedOwnerJoinError(bundle, credential.partyId);
109
+ return { v: RECORD_VERSION, targetPartyId: credential.partyId, bundle, moved: [], refused: [] };
110
+ }
111
+ /**
112
+ * Adopt `record` on `node`, running in the record's target party, without the strands an earlier
113
+ * attempt had refused; then delete the record when nothing failed, or save this attempt's
114
+ * progress. The report lists every refusal so far. Throws what `adoptStrandBundle` throws. A
115
+ * failure updating the record is only logged: the record left behind is adopted again, without
116
+ * harm, by the next start.
117
+ */
118
+ export async function adoptMoveRecord(node, records, record) {
119
+ const refused = new Set(record.refused.map((entry) => entry.strandId));
120
+ const strands = record.bundle.strands.filter((entry) => !refused.has(entry.row.Id));
121
+ const adopted = await adoptStrandBundle(node, { ...record.bundle, strands });
122
+ const report = { ...adopted, refused: [...record.refused, ...adopted.refused] };
123
+ await keepWhatIsLeft(records, record, report);
124
+ return report;
125
+ }
126
+ async function keepWhatIsLeft(records, record, report) {
127
+ const fromPartyId = record.bundle.partyId;
128
+ try {
129
+ if (report.failed.length === 0) {
130
+ await records.remove(fromPartyId);
131
+ }
132
+ else {
133
+ const moved = [...new Set([...record.moved, ...report.moved])];
134
+ await records.save({ ...record, moved, refused: report.refused });
135
+ }
136
+ }
137
+ catch (err) {
138
+ console.warn(`[cadre-core/edge-node] could not update the strand move record of party ${fromPartyId}; the next start adopts it again:`, err);
139
+ }
140
+ }
141
+ /**
142
+ * Adopt every record naming the node's party, by left party. A record the key store refuses to
143
+ * read throws; an adoption that throws is logged and reported as deferred, and its record kept.
144
+ */
145
+ export async function adoptMovesInto(node, records) {
146
+ const outcomes = new Map();
147
+ for (const record of await records.naming(node.partyId)) {
148
+ outcomes.set(record.bundle.partyId, await adoptOrDefer(node, records, record));
149
+ }
150
+ return outcomes;
151
+ }
152
+ async function adoptOrDefer(node, records, record) {
153
+ try {
154
+ const report = await adoptMoveRecord(node, records, record);
155
+ return { state: report.failed.length === 0 ? 'done' : 'partial', report };
156
+ }
157
+ catch (err) {
158
+ console.warn(`[cadre-core/edge-node] moving the strands of party ${record.bundle.partyId} failed; the next start retries:`, err);
159
+ return deferredOutcome(err);
160
+ }
161
+ }
162
+ export function deferredOutcome(err) {
163
+ if (err instanceof StrandMoveNotOwnerError)
164
+ return { state: 'deferred', reason: 'owner-key-not-wired' };
165
+ return { state: 'deferred', reason: 'adoption-failed', error: toError(err) };
166
+ }
167
+ /** A thrown value as an `Error`, for a status or outcome that carries one. */
168
+ export function toError(err) {
169
+ return err instanceof Error ? err : new Error(String(err));
170
+ }
171
+ export function pendingMove(record) {
172
+ const settled = new Set([...record.moved, ...record.refused.map((entry) => entry.strandId)]);
173
+ return {
174
+ fromPartyId: record.bundle.partyId,
175
+ toPartyId: record.targetPartyId,
176
+ strandIds: record.bundle.strands.map((entry) => entry.row.Id).filter((strandId) => !settled.has(strandId)),
177
+ refused: record.refused,
178
+ };
179
+ }
180
+ /** The record in `text`, or `undefined` (logged) when it is not one. */
181
+ function parseRecord(text) {
182
+ let raw;
183
+ try {
184
+ raw = JSON.parse(text);
185
+ }
186
+ catch (err) {
187
+ console.warn('[cadre-core/edge-node] a strand move record is not JSON:', err);
188
+ return undefined;
189
+ }
190
+ return isRecord(raw) ? raw : undefined;
191
+ }
192
+ /** The record's shape down to the bundle's lists; their entries are cadre-core's export, written by this module. */
193
+ function isRecord(value) {
194
+ if (!isObject(value) || value.v !== RECORD_VERSION || typeof value.targetPartyId !== 'string')
195
+ return false;
196
+ if (!Array.isArray(value.moved) || !Array.isArray(value.refused))
197
+ return false;
198
+ const bundle = value.bundle;
199
+ return isObject(bundle)
200
+ && bundle.v === 1
201
+ && typeof bundle.partyId === 'string'
202
+ && Array.isArray(bundle.strands)
203
+ && Array.isArray(bundle.pendingJoins)
204
+ && Array.isArray(bundle.formationInvites);
205
+ }
206
+ function isObject(value) {
207
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
208
+ }
209
+ /** "2 strands and 1 invitation": the bundle's non-empty lists. */
210
+ function describeContent(bundle) {
211
+ const parts = [
212
+ count(bundle.strands.length, 'strand'),
213
+ count(bundle.pendingJoins.length, 'pending join'),
214
+ count(bundle.formationInvites.length, 'invitation'),
215
+ ].filter((part) => part !== null);
216
+ return parts.length > 1 ? `${parts.slice(0, -1).join(', ')} and ${parts[parts.length - 1]}` : parts.join('');
217
+ }
218
+ function count(n, noun) {
219
+ if (n === 0)
220
+ return null;
221
+ return `${n} ${noun}${n === 1 ? '' : 's'}`;
222
+ }
223
+ //# sourceMappingURL=strand-move.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strand-move.js","sourceRoot":"","sources":["../../src/edge-node/strand-move.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Strand helpers every edge app needs around a running node, from sereus-chat's bring-up.
3
+ */
4
+ import type { CadreNode } from '../cadre-node.js';
5
+ import type { StrandConfig, StrandInstance } from '../types.js';
6
+ /** Default patience for {@link attachStrandWhenWritable}: generous enough that running out means something is wrong. */
7
+ export declare const DEFAULT_STRAND_PATIENCE_MS = 240000;
8
+ /**
9
+ * `addStrand`, then keep waiting when the first sync outlasts its budget.
10
+ *
11
+ * `addStrand` rejects with `StrandAwaitingFirstSyncError` when the first sync has not finished,
12
+ * and leaves the strand launched and syncing: the timeout is a progress report, not an outcome.
13
+ * Measured on a link with a 1.8 s round trip, a first sync completed at about 150 s against a
14
+ * 120 s budget. So this waits for the strand to become writable, and rejects only when it has not
15
+ * within `patienceMs`.
16
+ */
17
+ export declare function attachStrandWhenWritable(node: CadreNode, config: StrandConfig, options?: {
18
+ patienceMs?: number;
19
+ }): Promise<StrandInstance>;
20
+ /**
21
+ * Run a write, retrying only on the transient super-majority failure the first writes after a lone
22
+ * restart hit (v1.9 release notes): `attempts` tries, `delayMs` apart. Any other error, and the
23
+ * last attempt's, propagates.
24
+ */
25
+ export declare function retryAfterRestart<T>(write: () => Promise<T>, options?: {
26
+ attempts?: number;
27
+ delayMs?: number;
28
+ }): Promise<T>;