@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,199 @@
1
+ /**
2
+ * The edge node's `CadreNodeConfig` and how a start settles its owner key. Nothing here needs a
3
+ * node started, so a test can build the node shape an edge device runs.
4
+ */
5
+ import { DEFAULT_IDENTITY_KEY_ID } from '../key-store.js';
6
+ import { createOwnerKey, loadOwnerSigner } from '../owner-key-store.js';
7
+ import { OWNER_KEY_MAX_LABEL } from '../owner-key-export.js';
8
+ /**
9
+ * The edge node's config: transaction profile (Ring Zulu only, intermittent connectivity), no
10
+ * listen address unless the platform names one, reachability through the configured relays (if
11
+ * any) but never a hard requirement for one, every strand the control network lists, and no
12
+ * automatic hibernation (the app's lifecycle runner hibernates on background). Schema signing is
13
+ * left at cadre-core's fail-closed default; an app with unsigned schemas relaxes it in
14
+ * `EdgeNodePlatform.configure`.
15
+ */
16
+ export function buildEdgeNodeConfig(inputs) {
17
+ return {
18
+ // Identity comes from the key store (loaded when present, generated and persisted on first
19
+ // run). Mutually exclusive with `privateKey`, which would take it out of the key store.
20
+ keyStore: inputs.keyStore,
21
+ identityKeyId: DEFAULT_IDENTITY_KEY_ID,
22
+ controlNetwork: {
23
+ partyId: inputs.partyId,
24
+ bootstrapNodes: inputs.bootstrapAddrs,
25
+ },
26
+ profile: 'transaction',
27
+ storage: {
28
+ provider: inputs.storageProvider,
29
+ },
30
+ network: {
31
+ transports: inputs.transports,
32
+ // An edge device accepts no inbound connection, so by default it listens on nothing:
33
+ // the only address it ever has is the `/p2p-circuit` address a relay reservation earns
34
+ // it. An explicitly empty `listenAddrs` stays empty through cadre-core's derivation, and
35
+ // naming a relay adds the bare `/p2p-circuit` search entry to it
36
+ // (`cadre-core/src/relay-addrs.ts` → `resolveListenAddrs`). A browser adds `/webrtc`.
37
+ listenAddrs: inputs.listenAddrs ?? [],
38
+ // Through this config field rather than `CadreNode.reserveRelays()`, which reaches the
39
+ // control node only: formation has the invitee dial the control node and then the strand
40
+ // nodes, so both need a circuit address.
41
+ //
42
+ // NOTE: naming a relay starts a reservation supervisor that runs for as long as the
43
+ // control node does, including while backgrounded (hibernation stops strands, not the
44
+ // control node): a 5 s local check while the reservation holds, a dial every backoff
45
+ // interval (2 s → 60 s) while it does not. If background battery use becomes a complaint,
46
+ // raise `checkMs`/`maxBackoffMs` in cadre-core rather than stopping the supervisor here,
47
+ // which would leave a device that silently stopped being invitable.
48
+ relayAddrs: inputs.relayAddrs,
49
+ // An edge device must come up with its relay down — on a plane, on dead Wi-Fi, or with
50
+ // none configured. A first reservation attempt that lands nothing is logged and retried in
51
+ // the background instead of failing `start()`. A malformed entry still throws.
52
+ requireRelay: false,
53
+ // The platform's Noise primitives (native SHA-256 / ChaCha20-Poly1305, and X25519 in
54
+ // `full` mode, on a phone); only local primitives change, not the wire protocol.
55
+ noiseCrypto: inputs.noiseCrypto,
56
+ // libp2p's `connection-gater` points its `react-native` and `browser` fields at a build
57
+ // that refuses to dial plain `ws://`, loopback and private addresses. A cadre-host node
58
+ // claimed by its code on the same Wi-Fi, an emulator reaching `10.0.2.2` and a self-run
59
+ // relay without TLS are all of those. Only the dial is permitted: the connection is still
60
+ // Noise, the pinned `/p2p/<id>` decides which machine answers, and membership is still
61
+ // gated by `denyDialPeer` and the inbound hooks. cadre-core hands this gater to strand nodes too.
62
+ ...(inputs.allowPrivateDial ? { connectionGater: { denyDialMultiaddr: () => false } } : {}),
63
+ },
64
+ strandFilter: { mode: 'all' },
65
+ hibernation: { enabled: false },
66
+ trustedOwners: { store: inputs.trustedOwnerStore },
67
+ bootstrapPeers: { store: inputs.bootstrapPeerStore },
68
+ enrolledMachines: { store: inputs.enrolledMachineStore },
69
+ strandNetworkState: { store: inputs.strandNetworkStateStore },
70
+ };
71
+ }
72
+ /**
73
+ * Default bound on {@link runOwnerGenesis} and {@link wireJoinedOwnerKey}: what a start can wait
74
+ * for its owner step, which runs control reads and one control write, before it goes on without
75
+ * it. Not derived from the link, since a start that waited out a stalled write (about 84 s, see
76
+ * `docs/cadre-consistency.md`) would be a start the user gave up on.
77
+ */
78
+ // eslint-disable-next-line no-restricted-syntax -- cuts off by design: a start goes on without its owner step rather than wait out a stalled control write; see docs/cadre-consistency.md → "Deadlines Over Optimystic's Reads and Commits"
79
+ export const DEFAULT_OWNER_GENESIS_TIMEOUT_MS = 60000;
80
+ /** Default `EdgeNodeOptions.ownerKeyLabel`: the founding owner key's name on its `OwnerKey` row. */
81
+ export const DEFAULT_OWNER_KEY_LABEL = 'phone';
82
+ /**
83
+ * Refuses an `ownerKeyLabel` the founding `OwnerKey` row would refuse, so a start naming one
84
+ * fails outright: fail-soft genesis would only log it, on that start and every later one.
85
+ */
86
+ export function requireOwnerKeyLabel(label) {
87
+ const length = label?.trim().length ?? 0;
88
+ if (length > OWNER_KEY_MAX_LABEL) {
89
+ throw new Error(`ownerKeyLabel must be at most ${OWNER_KEY_MAX_LABEL} characters (this one has ${length})`);
90
+ }
91
+ }
92
+ /** How often {@link wireJoinedOwnerKey} asks again for an owner key an owner grant just seated. */
93
+ const GRANTED_OWNER_RECHECK_MS = 2000;
94
+ /**
95
+ * The owner step of a party this app founded. On a party with no `OwnerKey` row yet, the owner
96
+ * slot's key (created there now unless the slot already holds one) becomes the founding owner
97
+ * under `label` and is wired for signing, which also lets the node author its own `CadrePeer` row.
98
+ * On a party that has owners, the slot's key is wired only when it is a live owner, and no key is
99
+ * created: a new one could not be seated without another owner's signature. A node must be an
100
+ * enrolled owner to publish strands, and `CadreNode.requestJoin` works only on one. A joined
101
+ * party's start runs {@link wireJoinedOwnerKey} instead.
102
+ *
103
+ * Fail-soft and bounded: a failure or a genesis that has not settled within `timeoutMs` is logged
104
+ * and returned, and the start stands — the app can still join discovered strands and sync. A
105
+ * genesis that times out keeps running; its later failure is logged.
106
+ *
107
+ * NOTE: the bound is here, in cadre-core, so every edge node gets it; a Node host runs genesis
108
+ * through the CLI's own flow instead. Since v1.10 a control write against an unresponsive member
109
+ * can hold the write lock for about 84 s, so an unbounded wait here would be a real stall. A bound
110
+ * on that write itself, in `ControlDatabase`, would make this one redundant; drop `timeoutMs` then.
111
+ */
112
+ export function runOwnerGenesis(node, options) {
113
+ const label = options.label ?? DEFAULT_OWNER_KEY_LABEL;
114
+ return withinBound('owner self-genesis', enrollFoundedOwner(node, options.keyStore, label), options.timeoutMs ?? DEFAULT_OWNER_GENESIS_TIMEOUT_MS);
115
+ }
116
+ /**
117
+ * The owner step of a party this app joined: wire a key only when the party lists it as a live
118
+ * owner, never insert an `OwnerKey` row, so a joined app can neither seat itself as a founder nor
119
+ * anchor a key that is no owner's. Only the owner slot's key is tried: the key an owner grant
120
+ * seated, one imported here, or one an owner of the party added. The identity key is never
121
+ * tried, since a machine's network identity is never an owner key. Fail-soft and bounded as
122
+ * {@link runOwnerGenesis} is.
123
+ *
124
+ * `granted`: an owner grant has just made this app an owner, so a key not listed yet means the
125
+ * row has not reached this node, not that it is no owner. It is asked again every 2 s until it
126
+ * is wired or the bound passes; a start that timed out wires it on the next start.
127
+ */
128
+ export function wireJoinedOwnerKey(node, options) {
129
+ let expired = false;
130
+ const wiring = async () => {
131
+ const held = await loadOwnerSigner(options.keyStore);
132
+ while (!(await wireJoinedOwner(node, held))) {
133
+ if (!options.granted)
134
+ return 'member';
135
+ await new Promise((resolve) => setTimeout(resolve, GRANTED_OWNER_RECHECK_MS));
136
+ // Past the bound the start has gone on as `'timed-out'`, and may have stopped the node.
137
+ if (expired)
138
+ return 'member';
139
+ }
140
+ return 'enrolled';
141
+ };
142
+ return withinBound('wiring the joined owner key', wiring(), options.timeoutMs ?? DEFAULT_OWNER_GENESIS_TIMEOUT_MS)
143
+ .finally(() => {
144
+ expired = true;
145
+ });
146
+ }
147
+ /** `settling`, or `'failed'` when it rejects (logged), or `'timed-out'` when it has not settled within `timeoutMs` (logged). */
148
+ async function withinBound(label, settling, timeoutMs) {
149
+ const settled = settling.catch((err) => {
150
+ console.warn(`[cadre-core/edge-node] ${label} failed:`, err);
151
+ return 'failed';
152
+ });
153
+ let timer;
154
+ const deadline = new Promise((resolve) => {
155
+ timer = setTimeout(() => resolve('timed-out'), timeoutMs);
156
+ });
157
+ try {
158
+ const result = await Promise.race([settled, deadline]);
159
+ if (result === 'timed-out') {
160
+ console.warn(`[cadre-core/edge-node] ${label} has not settled after ${timeoutMs} ms; starting without it`);
161
+ }
162
+ return result;
163
+ }
164
+ finally {
165
+ clearTimeout(timer);
166
+ }
167
+ }
168
+ async function enrollFoundedOwner(node, keyStore, label) {
169
+ const controlDb = node.getControlDatabase();
170
+ if (!controlDb) {
171
+ throw new Error('control database unavailable after start; cannot run owner genesis');
172
+ }
173
+ const held = await loadOwnerSigner(keyStore);
174
+ if (await controlDb.hasOwnerKey()) {
175
+ return held ? wiredIfOwner(node, held) : 'member';
176
+ }
177
+ // Loaded rather than created when the slot already holds a key: one imported before founding,
178
+ // or one an iOS reinstall kept in the Keychain while the app's data went.
179
+ const founding = held ?? (await createOwnerKey(keyStore));
180
+ if (!(await controlDb.ensureOwnerKey(founding.publicKey, label))) {
181
+ // A row arrived between the two reads: the party has an owner now, so judge the key as above.
182
+ return wiredIfOwner(node, founding);
183
+ }
184
+ await node.useOwnerSigner(founding);
185
+ return 'enrolled';
186
+ }
187
+ async function wiredIfOwner(node, signer) {
188
+ return (await node.wireOwnKeyIfOwner(signer)) ? 'enrolled' : 'member';
189
+ }
190
+ /**
191
+ * Wire `held`, the owner slot's key, when the party lists it. A node takes one owner signer, so
192
+ * a signer already wired is asked about instead (is it still a live owner?) and never replaced.
193
+ */
194
+ async function wireJoinedOwner(node, held) {
195
+ if (node.getOwnerSigner() !== null)
196
+ return await node.wireOwnKeyIfOwner();
197
+ return held !== undefined && (await node.wireOwnKeyIfOwner(held));
198
+ }
199
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/edge-node/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Dissolving the party this app founded and left (docs/architecture.md → "An app joins an
3
+ * existing cadre" → "Dissolving the party it left"): the check that the running party holds
4
+ * everything the left one holds, and the removal of the left party's local storage. The same
5
+ * removal deletes a party whose owner removed this app ("Removed from A"), with no check.
6
+ *
7
+ * The removal is local: it writes nothing to either party's control database. A marker in the
8
+ * node-local database names the party from before the first deletion until after the saved
9
+ * start stops naming it, so a removal that a crash or a failed step interrupts is finished by
10
+ * the next start or the next dissolve without checking again: the decision to remove was made
11
+ * before the marker was written. Every step deletes what may already be gone.
12
+ */
13
+ import type { StrandBundle, StrandHoldings } from '../strand-move.js';
14
+ import type { CadreNode } from '../cadre-node.js';
15
+ import type { KeyStore } from '../key-store.js';
16
+ import { type NodeLocalKv } from './node-local.js';
17
+ import type { RemovableSlot } from './platform.js';
18
+ import { type EdgeNodeOptions } from './options.js';
19
+ import { type PendingStrandMove, type StrandMoveRecords } from './strand-move.js';
20
+ /** How `EdgeNode.dissolveFormerParty` runs. */
21
+ export interface DissolveFormerPartyOptions {
22
+ /** Strand and pending-join ids of the former party to give up although the running party does not hold them; from a `FormerPartyNotEmptyError`. */
23
+ discard?: readonly string[];
24
+ }
25
+ /**
26
+ * `EdgeNode.dissolveFormerParty` refused, nothing stopped: moving the former party's strands has
27
+ * not finished. A move into the running party finishes with `finishStrandMove()`; one into
28
+ * another party is adopted only there.
29
+ */
30
+ export declare class StrandMovePendingError extends Error {
31
+ readonly pending: PendingStrandMove;
32
+ constructor(pending: PendingStrandMove);
33
+ }
34
+ /** What keeps a former party from being dissolved: `FormerPartyNotEmptyError`'s lists. */
35
+ interface FormerPartyRemains {
36
+ otherMachines: string[];
37
+ strandIds: string[];
38
+ pendingJoinIds: string[];
39
+ }
40
+ /** `EdgeNode.dissolveFormerParty` refused, nothing deleted: the former party holds what the running party lacks, or has other machines. */
41
+ export declare class FormerPartyNotEmptyError extends Error {
42
+ readonly partyId: string;
43
+ /** Peer ids of `CadrePeer` rows other than this node's. Not discardable: removing them is the user's choice, made in that party. */
44
+ readonly otherMachines: string[];
45
+ /** Not held by the running party and not in `discard`. */
46
+ readonly strandIds: string[];
47
+ /** Open pending joins the running party does not hold, and not in `discard`. */
48
+ readonly pendingJoinIds: string[];
49
+ constructor(partyId: string, remains: FormerPartyRemains);
50
+ }
51
+ /** What the former party holds, read from a node started for it alone. */
52
+ export interface FormerPartyContent {
53
+ bundle: StrandBundle;
54
+ /** Peer ids of its `CadrePeer` rows other than the reading node's. */
55
+ otherMachines: string[];
56
+ }
57
+ export declare function readFormerParty(node: CadreNode): Promise<FormerPartyContent>;
58
+ /** The refusal `content` earns against what the running party holds, or null when it may be dissolved. */
59
+ export declare function formerPartyRefusal(content: FormerPartyContent, holdings: StrandHoldings, discard: readonly string[], nowMs: number): FormerPartyNotEmptyError | null;
60
+ /** The former party's start for {@link readFormerParty}: nothing to dial or reserve on, the running Noise mode. */
61
+ export declare function formerPartyRead(partyId: string, running: EdgeNodeOptions): EdgeNodeOptions;
62
+ /** `options` without `formerPartyId` when it names `partyId`. */
63
+ export declare function withoutFormerParty(options: EdgeNodeOptions, partyId: string | undefined): EdgeNodeOptions;
64
+ /** The edge node's storage a removal reaches. No handle on the party removed may be open. */
65
+ export interface PartyStorage {
66
+ /** The node-local key-value store (`EdgeNodeStorage.nodeLocal`). */
67
+ nodeLocal: NodeLocalKv;
68
+ /** `EdgeNodeNames.dissolveMarkerKey`. */
69
+ markerKey: string;
70
+ /** `EdgeNodeNames.savedStartKey`. */
71
+ savedStartKey: string;
72
+ /** `EdgeNodeStorage.destroyScope`. */
73
+ destroyScope: (scope: string) => Promise<void>;
74
+ /** The party's trusted-owner anchor slot, the platform's own or the node-local default, for its removal. */
75
+ trustedOwnerSlot: (partyId: string) => RemovableSlot;
76
+ keyStore: KeyStore;
77
+ moves: StrandMoveRecords;
78
+ }
79
+ /** Mark `partyId` as being removed, then remove its local storage. Only once removing it is decided: a dissolve's check passed, or an owner removed this app. */
80
+ export declare function removeParty(storage: PartyStorage, partyId: string): Promise<void>;
81
+ /** Finish the removal a marker names, if any. */
82
+ export declare function finishMarkedRemoval(storage: PartyStorage): Promise<void>;
83
+ /** What {@link finishRemovalAtStart} found: the party the marker named, if any, and why finishing its removal failed, if it did. */
84
+ export interface RemovalAtStart {
85
+ /** Absent when there was no marker, or when it could not be read (`error` says so). */
86
+ partyId?: string;
87
+ error?: Error;
88
+ }
89
+ /**
90
+ * {@link finishMarkedRemoval} before a start, which must then not keep the party it names as
91
+ * `formerPartyId`, finished or not. A failure is logged and reported, and the start goes on; the
92
+ * marker stays for the next.
93
+ */
94
+ export declare function finishRemovalAtStart(storage: PartyStorage): Promise<RemovalAtStart>;
95
+ export {};
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Dissolving the party this app founded and left (docs/architecture.md → "An app joins an
3
+ * existing cadre" → "Dissolving the party it left"): the check that the running party holds
4
+ * everything the left one holds, and the removal of the left party's local storage. The same
5
+ * removal deletes a party whose owner removed this app ("Removed from A"), with no check.
6
+ *
7
+ * The removal is local: it writes nothing to either party's control database. A marker in the
8
+ * node-local database names the party from before the first deletion until after the saved
9
+ * start stops naming it, so a removal that a crash or a failed step interrupts is finished by
10
+ * the next start or the next dissolve without checking again: the decision to remove was made
11
+ * before the marker was written. Every step deletes what may already be gone.
12
+ */
13
+ import { controlStorageScope } from '../storage-scope.js';
14
+ import { exportStrandBundle, unheldBundleContent } from '../strand-move.js';
15
+ import { KeyStoreJoinedStrandStore } from '../joined-strand-store.js';
16
+ import { bootstrapPeersKvKey, enrolledMachinesKvKey, strandNetworkKvKey } from './node-local.js';
17
+ import { parseSavedStart, serializeSavedStart } from './options.js';
18
+ import { toError } from './strand-move.js';
19
+ /**
20
+ * `EdgeNode.dissolveFormerParty` refused, nothing stopped: moving the former party's strands has
21
+ * not finished. A move into the running party finishes with `finishStrandMove()`; one into
22
+ * another party is adopted only there.
23
+ */
24
+ export class StrandMovePendingError extends Error {
25
+ constructor(pending) {
26
+ super(`Cannot dissolve party ${pending.fromPartyId}: moving its strands into party ${pending.toPartyId} has not finished`);
27
+ this.pending = pending;
28
+ this.name = 'StrandMovePendingError';
29
+ }
30
+ }
31
+ /** `EdgeNode.dissolveFormerParty` refused, nothing deleted: the former party holds what the running party lacks, or has other machines. */
32
+ export class FormerPartyNotEmptyError extends Error {
33
+ constructor(partyId, remains) {
34
+ super(`Cannot dissolve party ${partyId}: ${describeRemains(remains)}`);
35
+ this.name = 'FormerPartyNotEmptyError';
36
+ this.partyId = partyId;
37
+ this.otherMachines = remains.otherMachines;
38
+ this.strandIds = remains.strandIds;
39
+ this.pendingJoinIds = remains.pendingJoinIds;
40
+ }
41
+ }
42
+ export async function readFormerParty(node) {
43
+ const self = node.peerId?.toString();
44
+ const machines = new Set((await node.listMembers()).map((member) => member.peerId));
45
+ return { bundle: await exportStrandBundle(node), otherMachines: [...machines].filter((peerId) => peerId !== self) };
46
+ }
47
+ /** The refusal `content` earns against what the running party holds, or null when it may be dissolved. */
48
+ export function formerPartyRefusal(content, holdings, discard, nowMs) {
49
+ const discarded = new Set(discard);
50
+ const unheld = unheldBundleContent(content.bundle, holdings, nowMs);
51
+ const remains = {
52
+ otherMachines: content.otherMachines,
53
+ strandIds: unheld.strandIds.filter((strandId) => !discarded.has(strandId)),
54
+ pendingJoinIds: unheld.pendingJoinIds.filter((joinId) => !discarded.has(joinId)),
55
+ };
56
+ const empty = remains.otherMachines.length + remains.strandIds.length + remains.pendingJoinIds.length === 0;
57
+ return empty ? null : new FormerPartyNotEmptyError(content.bundle.partyId, remains);
58
+ }
59
+ /** The former party's start for {@link readFormerParty}: nothing to dial or reserve on, the running Noise mode. */
60
+ export function formerPartyRead(partyId, running) {
61
+ return {
62
+ partyId,
63
+ bootstrapAddrs: [],
64
+ relayAddrs: [],
65
+ ...(running.noiseCryptoMode !== undefined && { noiseCryptoMode: running.noiseCryptoMode }),
66
+ };
67
+ }
68
+ /** `options` without `formerPartyId` when it names `partyId`. */
69
+ export function withoutFormerParty(options, partyId) {
70
+ if (partyId === undefined || options.formerPartyId !== partyId)
71
+ return options;
72
+ const { formerPartyId: _dissolved, ...rest } = options;
73
+ return rest;
74
+ }
75
+ const MARKER_VERSION = 1;
76
+ /** Mark `partyId` as being removed, then remove its local storage. Only once removing it is decided: a dissolve's check passed, or an owner removed this app. */
77
+ export async function removeParty(storage, partyId) {
78
+ await storage.nodeLocal.set(storage.markerKey, JSON.stringify({ v: MARKER_VERSION, partyId }));
79
+ // On disk before the first deletion, so a crash during the removal leaves the marker that finishes it.
80
+ await storage.nodeLocal.flush();
81
+ await finishRemoving(storage, partyId);
82
+ }
83
+ /**
84
+ * The party a begun dissolve names, or `undefined`. A marker that does not parse is deleted with
85
+ * a warning: nothing can finish it, and the party it named keeps what is left of its storage.
86
+ */
87
+ async function markedParty(storage) {
88
+ const text = await storage.nodeLocal.get(storage.markerKey);
89
+ if (text === undefined)
90
+ return undefined;
91
+ const partyId = parseMarker(text);
92
+ if (partyId === undefined) {
93
+ console.warn(`[cadre-core/edge-node] deleting the dissolve marker ${storage.markerKey}: it is not one this kit wrote`);
94
+ await storage.nodeLocal.delete(storage.markerKey);
95
+ }
96
+ return partyId;
97
+ }
98
+ /** Finish the removal a marker names, if any. */
99
+ export async function finishMarkedRemoval(storage) {
100
+ const partyId = await markedParty(storage);
101
+ if (partyId !== undefined)
102
+ await finishRemoving(storage, partyId);
103
+ }
104
+ /**
105
+ * {@link finishMarkedRemoval} before a start, which must then not keep the party it names as
106
+ * `formerPartyId`, finished or not. A failure is logged and reported, and the start goes on; the
107
+ * marker stays for the next.
108
+ */
109
+ export async function finishRemovalAtStart(storage) {
110
+ let partyId;
111
+ try {
112
+ partyId = await markedParty(storage);
113
+ if (partyId !== undefined)
114
+ await finishRemoving(storage, partyId);
115
+ return { partyId };
116
+ }
117
+ catch (err) {
118
+ console.warn('[cadre-core/edge-node] finishing the removal of a dissolved party failed; the next start retries:', err);
119
+ return { partyId, error: toError(err) };
120
+ }
121
+ }
122
+ /**
123
+ * Delete what is left of `partyId`'s local storage, then the marker: its control database, its
124
+ * node-local records, its trusted-owner anchor, its unpublished joined-strand records and its move
125
+ * record; then the saved start stops naming it as `formerPartyId`.
126
+ *
127
+ * NOTE: strand databases stay. A strand's storage scope is its strand id, not the party, so a
128
+ * moved strand's database is the running party's copy; a discarded strand's is left too, for the
129
+ * running party to reuse if it joins that strand again. If discarded strands' storage ever needs
130
+ * reclaiming, delete it here only for a strand no party on this device holds.
131
+ */
132
+ async function finishRemoving(storage, partyId) {
133
+ await storage.destroyScope(controlStorageScope(partyId));
134
+ for (const key of [bootstrapPeersKvKey(partyId), enrolledMachinesKvKey(partyId), strandNetworkKvKey(partyId)]) {
135
+ await storage.nodeLocal.delete(key);
136
+ }
137
+ await storage.trustedOwnerSlot(partyId).remove();
138
+ await forgetJoinedStrands(storage.keyStore, partyId);
139
+ await storage.moves.remove(partyId);
140
+ await dropFromSavedStart(storage, partyId);
141
+ await storage.nodeLocal.delete(storage.markerKey);
142
+ // One batch: the saved start stops naming the party in the same write that deletes the marker.
143
+ await storage.nodeLocal.flush();
144
+ }
145
+ async function forgetJoinedStrands(keyStore, partyId) {
146
+ const joins = new KeyStoreJoinedStrandStore(keyStore, partyId);
147
+ for (const record of await joins.list())
148
+ await joins.forget(record.Id);
149
+ }
150
+ /** A write that throws, unlike the runtime's best-effort save, so a failure keeps the marker for the next start. */
151
+ async function dropFromSavedStart(storage, partyId) {
152
+ const saved = parseSavedStart(await storage.nodeLocal.get(storage.savedStartKey));
153
+ if (saved?.options.formerPartyId !== partyId)
154
+ return;
155
+ await storage.nodeLocal.set(storage.savedStartKey, serializeSavedStart({ ...saved, options: withoutFormerParty(saved.options, partyId) }));
156
+ }
157
+ /** The party id in a marker's `text`, or `undefined` (logged) when it is not a marker. */
158
+ function parseMarker(text) {
159
+ let raw;
160
+ try {
161
+ raw = JSON.parse(text);
162
+ }
163
+ catch (err) {
164
+ console.warn('[cadre-core/edge-node] the dissolve marker is not JSON:', err);
165
+ return undefined;
166
+ }
167
+ if (typeof raw !== 'object' || raw === null)
168
+ return undefined;
169
+ const { v, partyId } = raw;
170
+ return v === MARKER_VERSION && typeof partyId === 'string' && partyId.length > 0 ? partyId : undefined;
171
+ }
172
+ /** "other machines are in it (…); the running party does not hold strands …": the non-empty lists. */
173
+ function describeRemains(remains) {
174
+ const parts = [
175
+ listed('other machines are in it', remains.otherMachines),
176
+ listed('the running party does not hold its strands', remains.strandIds),
177
+ listed('the running party does not hold its open pending joins', remains.pendingJoinIds),
178
+ ].filter((part) => part !== null);
179
+ return parts.join('; ');
180
+ }
181
+ function listed(what, ids) {
182
+ return ids.length === 0 ? null : `${what} (${ids.join(', ')})`;
183
+ }
184
+ //# sourceMappingURL=dissolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dissolve.js","sourceRoot":"","sources":["../../src/edge-node/dissolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE5E,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAGtE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,kBAAkB,EAAoB,MAAM,iBAAiB,CAAC;AAEnH,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAwB,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAE,OAAO,EAAkD,MAAM,kBAAkB,CAAC;AAQ3F;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAChD,YAAqB,OAA0B;QAC9C,KAAK,CAAC,yBAAyB,OAAO,CAAC,WAAW,mCAAmC,OAAO,CAAC,SAAS,mBAAmB,CAAC,CAAC;QADvG,YAAO,GAAP,OAAO,CAAmB;QAE9C,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACtC,CAAC;CACD;AASD,2IAA2I;AAC3I,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IASlD,YAAY,OAAe,EAAE,OAA2B;QACvD,KAAK,CAAC,yBAAyB,OAAO,KAAK,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvE,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC3C,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;IAC9C,CAAC;CACD;AASD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAe;IACpD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACpF,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;AACrH,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,kBAAkB,CACjC,OAA2B,EAC3B,QAAwB,EACxB,OAA0B,EAC1B,KAAa;IAEb,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpE,MAAM,OAAO,GAAuB;QACnC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1E,cAAc,EAAE,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;KAChF,CAAC;IACF,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,CAAC;IAC5G,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,wBAAwB,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACrF,CAAC;AAED,mHAAmH;AACnH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,OAAwB;IACxE,OAAO;QACN,OAAO;QACP,cAAc,EAAE,EAAE;QAClB,UAAU,EAAE,EAAE;QACd,GAAG,CAAC,OAAO,CAAC,eAAe,KAAK,SAAS,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC;KAC1F,CAAC;AACH,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,kBAAkB,CAAC,OAAwB,EAAE,OAA2B;IACvF,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,aAAa,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC;IAC/E,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACvD,OAAO,IAAI,CAAC;AACb,CAAC;AAkBD,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,iKAAiK;AACjK,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAqB,EAAE,OAAe;IACvE,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAC/F,uGAAuG;IACvG,MAAM,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IAChC,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,WAAW,CAAC,OAAqB;IAC/C,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,uDAAuD,OAAO,CAAC,SAAS,gCAAgC,CAAC,CAAC;QACvH,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,iDAAiD;AACjD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAqB;IAC9D,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACnE,CAAC;AASD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAqB;IAC/D,IAAI,OAA2B,CAAC;IAChC,IAAI,CAAC;QACJ,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClE,OAAO,EAAE,OAAO,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,mGAAmG,EAAE,GAAG,CAAC,CAAC;QACvH,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;IACzC,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,cAAc,CAAC,OAAqB,EAAE,OAAe;IACnE,MAAM,OAAO,CAAC,YAAY,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,KAAK,MAAM,GAAG,IAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC/G,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACrC,CAAC;IACD,MAAM,OAAO,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;IACjD,MAAM,mBAAmB,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClD,+FAA+F;IAC/F,MAAM,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;AACjC,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,QAAkB,EAAE,OAAe;IACrE,MAAM,KAAK,GAAG,IAAI,yBAAyB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/D,KAAK,MAAM,MAAM,IAAI,MAAM,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,oHAAoH;AACpH,KAAK,UAAU,kBAAkB,CAAC,OAAqB,EAAE,OAAe;IACvE,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IAClF,IAAI,KAAK,EAAE,OAAO,CAAC,aAAa,KAAK,OAAO;QAAE,OAAO;IACrD,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,mBAAmB,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,kBAAkB,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5I,CAAC;AAED,0FAA0F;AAC1F,SAAS,WAAW,CAAC,IAAY;IAChC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACJ,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,yDAAyD,EAAE,GAAG,CAAC,CAAC;QAC7E,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAA8B,CAAC;IACtD,OAAO,CAAC,KAAK,cAAc,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACxG,CAAC;AAED,sGAAsG;AACtG,SAAS,eAAe,CAAC,OAA2B;IACnD,MAAM,KAAK,GAAG;QACb,MAAM,CAAC,0BAA0B,EAAE,OAAO,CAAC,aAAa,CAAC;QACzD,MAAM,CAAC,6CAA6C,EAAE,OAAO,CAAC,SAAS,CAAC;QACxE,MAAM,CAAC,wDAAwD,EAAE,OAAO,CAAC,cAAc,CAAC;KACxF,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAClD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,MAAM,CAAC,IAAY,EAAE,GAAsB;IACnD,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAChE,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * `EdgeNode.joinCadre`'s sequence: redeem in place on a node already running in the credential's
3
+ * party, or stop the running node, start one in that party, redeem there and go back on any
4
+ * failure (docs/architecture.md → "An app joins an existing cadre"). The credential is a machine
5
+ * invitation, which admits the app as a machine, or an owner grant, which makes it a machine and
6
+ * an owner at once ({@link JoinCredential}). Runs over a narrow view of the runtime
7
+ * ({@link JoinRuntime}), inside one of its transitions.
8
+ */
9
+ import type { CadreNode } from '../cadre-node.js';
10
+ import type { OwnerSigner } from '../owner-signer.js';
11
+ import type { OwnerGenesisResult } from './config.js';
12
+ import type { EdgeNodeStatus, JoinCadreOptions, JoinCadreResult } from './edge-node.js';
13
+ import { type JoinCredential } from './join-credential.js';
14
+ import type { EdgeNodeOptions, SavedStart } from './options.js';
15
+ import { type StrandMoveRecords } from './strand-move.js';
16
+ /** The part of the edge-node runtime a join drives. Every call runs inside the join's own transition. */
17
+ export interface JoinRuntime {
18
+ /** The running node, or null. */
19
+ readonly node: CadreNode | null;
20
+ /** The options the running node was started with; null unless running. */
21
+ readonly runningOptions: EdgeNodeOptions | null;
22
+ readonly moves: StrandMoveRecords;
23
+ readonly status: EdgeNodeStatus;
24
+ setStatus(status: EdgeNodeStatus): void;
25
+ readSavedStart(): Promise<SavedStart | undefined>;
26
+ /** Stop the node and close every store; nothing runs afterwards. */
27
+ shutDown(): Promise<void>;
28
+ /** Build and start the node for `options`, subscriptions attached; nothing saved, nothing reported. */
29
+ bringUp(options: EdgeNodeOptions): Promise<{
30
+ node: CadreNode;
31
+ resolved: EdgeNodeOptions;
32
+ }>;
33
+ /** Start a node for `options`' party only to run `read` on it, then stop it. */
34
+ readAlone<T>(options: EdgeNodeOptions, read: (node: CadreNode) => Promise<T>, purpose: string): Promise<T>;
35
+ /** A full start: bring up, settle the owner key, adopt waiting moves, save and report `running`. */
36
+ buildAndStart(options: EdgeNodeOptions): Promise<CadreNode>;
37
+ /** The owner slot's key, created when the slot is empty: the key an owner grant seats. */
38
+ ownerSlotSigner(): Promise<OwnerSigner>;
39
+ /** `granted`: an owner grant has just seated the slot's key, so wait for the row. */
40
+ wireJoinedOwner(node: CadreNode, granted: boolean): Promise<OwnerGenesisResult>;
41
+ saveRunningStart(resolved: EdgeNodeOptions): Promise<void>;
42
+ reportRunning(node: CadreNode, resolved: EdgeNodeOptions, owner: OwnerGenesisResult): void;
43
+ }
44
+ export declare function joinCadre(runtime: JoinRuntime, credential: JoinCredential, options: JoinCadreOptions, signal: AbortSignal): Promise<JoinCadreResult>;