@serfab/cadre-core 1.13.0 → 1.15.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 (230) hide show
  1. package/README.md +293 -274
  2. package/dist/cadre-invite-protocol.d.ts +383 -0
  3. package/dist/cadre-invite-protocol.js +774 -0
  4. package/dist/cadre-invite-protocol.js.map +1 -0
  5. package/dist/cadre-join-request.d.ts +42 -0
  6. package/dist/cadre-join-request.js +144 -0
  7. package/dist/cadre-join-request.js.map +1 -0
  8. package/dist/cadre-node.d.ts +849 -210
  9. package/dist/cadre-node.js +1574 -374
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/claim-proof.d.ts +45 -0
  12. package/dist/claim-proof.js +132 -0
  13. package/dist/claim-proof.js.map +1 -0
  14. package/dist/control-authorization.d.ts +80 -19
  15. package/dist/control-authorization.js +55 -13
  16. package/dist/control-authorization.js.map +1 -1
  17. package/dist/control-cohort.js +1 -1
  18. package/dist/control-cohort.js.map +1 -1
  19. package/dist/control-database.d.ts +605 -108
  20. package/dist/control-database.js +1299 -280
  21. package/dist/control-database.js.map +1 -1
  22. package/dist/control-protocol-guard.d.ts +142 -0
  23. package/dist/control-protocol-guard.js +293 -0
  24. package/dist/control-protocol-guard.js.map +1 -0
  25. package/dist/control-schema.d.ts +1 -1
  26. package/dist/control-schema.js +817 -1067
  27. package/dist/control-schema.js.map +1 -1
  28. package/dist/control-stream.d.ts +4 -0
  29. package/dist/control-stream.js +22 -7
  30. package/dist/control-stream.js.map +1 -1
  31. package/dist/delegate-admission.d.ts +1 -1
  32. package/dist/delegate-admission.js +1 -1
  33. package/dist/device-token.d.ts +11 -9
  34. package/dist/device-token.js +14 -12
  35. package/dist/device-token.js.map +1 -1
  36. package/dist/ed25519-key.d.ts +22 -9
  37. package/dist/ed25519-key.js +47 -10
  38. package/dist/ed25519-key.js.map +1 -1
  39. package/dist/fetch-impl.d.ts +12 -0
  40. package/dist/fetch-impl.js +23 -0
  41. package/dist/fetch-impl.js.map +1 -0
  42. package/dist/file-durable-slot.js +2 -1
  43. package/dist/file-durable-slot.js.map +1 -1
  44. package/dist/file-safe-name.d.ts +25 -0
  45. package/dist/file-safe-name.js +29 -0
  46. package/dist/file-safe-name.js.map +1 -0
  47. package/dist/formation-approval.js +5 -11
  48. package/dist/formation-approval.js.map +1 -1
  49. package/dist/fs-atomic.d.ts +0 -19
  50. package/dist/fs-atomic.js +3 -24
  51. package/dist/fs-atomic.js.map +1 -1
  52. package/dist/index.d.ts +22 -10
  53. package/dist/index.js +54 -11
  54. package/dist/index.js.map +1 -1
  55. package/dist/invitation-bootstrap.d.ts +50 -12
  56. package/dist/invitation-bootstrap.js +76 -13
  57. package/dist/invitation-bootstrap.js.map +1 -1
  58. package/dist/joined-strand-store.d.ts +7 -0
  59. package/dist/joined-strand-store.js +9 -0
  60. package/dist/joined-strand-store.js.map +1 -1
  61. package/dist/key-store-file.js +2 -1
  62. package/dist/key-store-file.js.map +1 -1
  63. package/dist/link-budget.d.ts +25 -33
  64. package/dist/link-budget.js +25 -35
  65. package/dist/link-budget.js.map +1 -1
  66. package/dist/machine-removal.d.ts +78 -0
  67. package/dist/machine-removal.js +166 -0
  68. package/dist/machine-removal.js.map +1 -0
  69. package/dist/membership-connection-gater.d.ts +191 -151
  70. package/dist/membership-connection-gater.js +236 -173
  71. package/dist/membership-connection-gater.js.map +1 -1
  72. package/dist/node-claim-payload.d.ts +74 -0
  73. package/dist/node-claim-payload.js +224 -0
  74. package/dist/node-claim-payload.js.map +1 -0
  75. package/dist/node-default-transports.d.ts +27 -0
  76. package/dist/node-default-transports.js +35 -0
  77. package/dist/node-default-transports.js.map +1 -0
  78. package/dist/node-transports.d.ts +30 -0
  79. package/dist/node-transports.js +40 -0
  80. package/dist/node-transports.js.map +1 -0
  81. package/dist/owner-anchor-sync.d.ts +22 -0
  82. package/dist/owner-anchor-sync.js +143 -0
  83. package/dist/owner-anchor-sync.js.map +1 -0
  84. package/dist/peer-authorization.d.ts +113 -18
  85. package/dist/peer-authorization.js +225 -29
  86. package/dist/peer-authorization.js.map +1 -1
  87. package/dist/peer-dial.d.ts +143 -27
  88. package/dist/peer-dial.js +276 -31
  89. package/dist/peer-dial.js.map +1 -1
  90. package/dist/peer-join-backfill.d.ts +26 -2
  91. package/dist/peer-join-backfill.js +60 -12
  92. package/dist/peer-join-backfill.js.map +1 -1
  93. package/dist/peer-record.d.ts +17 -52
  94. package/dist/peer-record.js +20 -58
  95. package/dist/peer-record.js.map +1 -1
  96. package/dist/pending-join-runner.d.ts +66 -58
  97. package/dist/pending-join-runner.js +136 -145
  98. package/dist/pending-join-runner.js.map +1 -1
  99. package/dist/primary-lan-address.d.ts +8 -0
  100. package/dist/primary-lan-address.js +46 -0
  101. package/dist/primary-lan-address.js.map +1 -0
  102. package/dist/provider/client.d.ts +131 -0
  103. package/dist/provider/client.js +226 -0
  104. package/dist/provider/client.js.map +1 -0
  105. package/dist/provider/descriptor.d.ts +41 -0
  106. package/dist/provider/descriptor.js +103 -0
  107. package/dist/provider/descriptor.js.map +1 -0
  108. package/dist/provider/index.d.ts +9 -0
  109. package/dist/provider/index.js +10 -0
  110. package/dist/provider/index.js.map +1 -0
  111. package/dist/provider/oauth.d.ts +125 -0
  112. package/dist/provider/oauth.js +199 -0
  113. package/dist/provider/oauth.js.map +1 -0
  114. package/dist/provider/rent-node.d.ts +101 -0
  115. package/dist/provider/rent-node.js +220 -0
  116. package/dist/provider/rent-node.js.map +1 -0
  117. package/dist/push-fanout.d.ts +8 -2
  118. package/dist/push-fanout.js +8 -2
  119. package/dist/push-fanout.js.map +1 -1
  120. package/dist/relay-addrs.d.ts +9 -4
  121. package/dist/relay-addrs.js +9 -4
  122. package/dist/relay-addrs.js.map +1 -1
  123. package/dist/relay-reservation.d.ts +51 -16
  124. package/dist/relay-reservation.js +107 -24
  125. package/dist/relay-reservation.js.map +1 -1
  126. package/dist/seed-bootstrap.d.ts +165 -123
  127. package/dist/seed-bootstrap.js +313 -275
  128. package/dist/seed-bootstrap.js.map +1 -1
  129. package/dist/seed-trust-policy.d.ts +98 -37
  130. package/dist/seed-trust-policy.js +188 -2
  131. package/dist/seed-trust-policy.js.map +1 -1
  132. package/dist/strand-addr-protocol.d.ts +24 -26
  133. package/dist/strand-addr-protocol.js +19 -26
  134. package/dist/strand-addr-protocol.js.map +1 -1
  135. package/dist/strand-commit-durability.d.ts +54 -0
  136. package/dist/strand-commit-durability.js +74 -0
  137. package/dist/strand-commit-durability.js.map +1 -0
  138. package/dist/strand-database.d.ts +23 -8
  139. package/dist/strand-database.js +41 -13
  140. package/dist/strand-database.js.map +1 -1
  141. package/dist/strand-first-sync-gate.d.ts +7 -1
  142. package/dist/strand-first-sync-gate.js +7 -1
  143. package/dist/strand-first-sync-gate.js.map +1 -1
  144. package/dist/strand-formation-protocol.js +5 -2
  145. package/dist/strand-formation-protocol.js.map +1 -1
  146. package/dist/strand-fret-addrs.d.ts +19 -1
  147. package/dist/strand-fret-addrs.js +27 -9
  148. package/dist/strand-fret-addrs.js.map +1 -1
  149. package/dist/strand-instance-manager.d.ts +81 -19
  150. package/dist/strand-instance-manager.js +216 -105
  151. package/dist/strand-instance-manager.js.map +1 -1
  152. package/dist/strand-move.d.ts +124 -0
  153. package/dist/strand-move.js +438 -0
  154. package/dist/strand-move.js.map +1 -0
  155. package/dist/strand-network-config.js +7 -6
  156. package/dist/strand-network-config.js.map +1 -1
  157. package/dist/strand-peer-redial.d.ts +113 -0
  158. package/dist/strand-peer-redial.js +236 -0
  159. package/dist/strand-peer-redial.js.map +1 -0
  160. package/dist/strand-revocation-enforcer.d.ts +11 -11
  161. package/dist/strand-revocation-enforcer.js +11 -11
  162. package/dist/strand-wake-protocol.d.ts +13 -16
  163. package/dist/strand-wake-protocol.js +23 -27
  164. package/dist/strand-wake-protocol.js.map +1 -1
  165. package/dist/trusted-owner-store.d.ts +35 -5
  166. package/dist/trusted-owner-store.js +50 -8
  167. package/dist/trusted-owner-store.js.map +1 -1
  168. package/dist/types.d.ts +512 -89
  169. package/dist/types.js +10 -0
  170. package/dist/types.js.map +1 -1
  171. package/package.json +19 -10
  172. package/src/cadre-invite-protocol.ts +1052 -0
  173. package/src/cadre-join-request.ts +167 -0
  174. package/src/cadre-node.ts +1703 -386
  175. package/src/claim-proof.ts +147 -0
  176. package/src/control-authorization.ts +203 -117
  177. package/src/control-cohort.ts +1 -1
  178. package/src/control-database.ts +4663 -3453
  179. package/src/control-protocol-guard.ts +348 -0
  180. package/src/control-schema.ts +828 -1078
  181. package/src/control-stream.ts +26 -7
  182. package/src/delegate-admission.ts +1 -1
  183. package/src/device-token.ts +14 -10
  184. package/src/ed25519-key.ts +47 -10
  185. package/src/fetch-impl.ts +23 -0
  186. package/src/file-durable-slot.ts +64 -63
  187. package/src/file-safe-name.ts +32 -0
  188. package/src/formation-approval.ts +604 -610
  189. package/src/fs-atomic.ts +92 -117
  190. package/src/index.ts +789 -648
  191. package/src/invitation-bootstrap.ts +91 -13
  192. package/src/joined-strand-store.ts +508 -498
  193. package/src/key-store-file.ts +142 -141
  194. package/src/link-budget.ts +25 -36
  195. package/src/machine-removal.ts +216 -0
  196. package/src/membership-connection-gater.ts +259 -191
  197. package/src/node-claim-payload.ts +259 -0
  198. package/src/node-default-transports.ts +38 -0
  199. package/src/node-transports.ts +49 -0
  200. package/src/owner-anchor-sync.ts +171 -0
  201. package/src/peer-authorization.ts +258 -29
  202. package/src/peer-dial.ts +604 -315
  203. package/src/peer-join-backfill.ts +62 -11
  204. package/src/peer-record.ts +159 -197
  205. package/src/pending-join-runner.ts +729 -735
  206. package/src/primary-lan-address.ts +47 -0
  207. package/src/provider/client.ts +326 -0
  208. package/src/provider/descriptor.ts +129 -0
  209. package/src/provider/index.ts +59 -0
  210. package/src/provider/oauth.ts +285 -0
  211. package/src/provider/rent-node.ts +275 -0
  212. package/src/push-fanout.ts +8 -2
  213. package/src/relay-addrs.ts +9 -4
  214. package/src/relay-reservation.ts +1211 -1121
  215. package/src/seed-bootstrap.ts +1502 -1448
  216. package/src/seed-trust-policy.ts +353 -140
  217. package/src/strand-addr-protocol.ts +26 -35
  218. package/src/strand-commit-durability.ts +116 -0
  219. package/src/strand-database.ts +46 -14
  220. package/src/strand-first-sync-gate.ts +7 -1
  221. package/src/strand-formation-protocol.ts +1039 -1036
  222. package/src/strand-fret-addrs.ts +179 -145
  223. package/src/strand-instance-manager.ts +2026 -1883
  224. package/src/strand-move.ts +585 -0
  225. package/src/strand-network-config.ts +7 -6
  226. package/src/strand-peer-redial.ts +307 -0
  227. package/src/strand-revocation-enforcer.ts +11 -11
  228. package/src/strand-wake-protocol.ts +23 -30
  229. package/src/trusted-owner-store.ts +242 -171
  230. package/src/types.ts +2687 -2242
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Moving a party's strands into another party of the same owner.
3
+ *
4
+ * A party's participation in a strand is control-database state: its `Strand` row (a strand
5
+ * it founded) or `JoinedStrand` row (one it joined), carrying the strand's shared read secret,
6
+ * and its `StrandPartyKey` row, the membership identity whose public half seats the party's
7
+ * `Member` / `Manager` rows in the strand database. Nothing in the strand database names a
8
+ * party, so a strand follows those rows: re-seated in another party under the same member key,
9
+ * on the same peer, the strand's other members see the same member on the same peer and
10
+ * nothing on the strand side changes.
11
+ *
12
+ * The case this serves is a phone app that founded a one-node cadre of its own (party B) and
13
+ * then joins the user's main cadre (party A) by switching its one node across
14
+ * (docs/architecture.md → "An app joins an existing cadre" → "Moving the app's strands"). Two
15
+ * nodes never run at once, so the move has two ends: {@link exportStrandBundle} reads everything
16
+ * B's node knows about its strands while it still runs, the embedder carries the bundle across
17
+ * the switch as data, and {@link adoptStrandBundle} seats it on A's node once the app's key is
18
+ * an owner there. Nothing is deleted in B; dissolving it is the embedder's later step, once
19
+ * {@link unheldBundleContent} finds nothing of B's export that A's {@link readStrandHoldings} lacks.
20
+ *
21
+ * Built over `CadreNode`'s public surface, so `cadre-node.ts` does not grow by the feature.
22
+ */
23
+ import debug from 'debug';
24
+ import { sign } from '@optimystic/quereus-plugin-crypto';
25
+ import { isPendingJoinConflict, isStrandIdConflict, pendingJoinId } from './control-database.js';
26
+ import { joinedStrandRow } from './joined-strand-store.js';
27
+ const log = debug('sereus:cadre:strand-move');
28
+ /** {@link adoptStrandBundle} refused before any write: the running node's key is not an owner of its party. */
29
+ export class StrandMoveNotOwnerError extends Error {
30
+ constructor(partyId) {
31
+ super(`Cannot adopt strands into party ${partyId}: this node's key is not one of its owners, and every re-seated row is owner-signed`);
32
+ this.partyId = partyId;
33
+ this.name = 'StrandMoveNotOwnerError';
34
+ }
35
+ }
36
+ /** {@link adoptStrandBundle} refused before any write: the bundle was exported from the party the node runs. */
37
+ export class StrandMoveSamePartyError extends Error {
38
+ constructor(partyId) {
39
+ super(`Cannot adopt strands into party ${partyId}: the bundle was exported from that same party`);
40
+ this.partyId = partyId;
41
+ this.name = 'StrandMoveSamePartyError';
42
+ }
43
+ }
44
+ /**
45
+ * Read, from a running node, everything its party knows about its strands: the party's own
46
+ * strands, the strands it joined (party-wide rows and this machine's unpublished records), each
47
+ * with its party key, saved routing table and carried addresses; every pending join with its
48
+ * outcome; and every invitation still redeemable.
49
+ *
50
+ * @throws when the node is not started.
51
+ */
52
+ export async function exportStrandBundle(node) {
53
+ const database = requireControlDatabase(node, 'export a strand bundle');
54
+ const now = Date.now();
55
+ const bundle = {
56
+ v: 1,
57
+ partyId: node.partyId,
58
+ exportedAt: now,
59
+ strands: await exportStrands(node, database),
60
+ pendingJoins: await database.queryPendingJoins(),
61
+ formationInvites: await exportFormationInvites(database, now)
62
+ };
63
+ log('export(%s): %d strand(s), %d pending join(s), %d invitation(s)', node.partyId, bundle.strands.length, bundle.pendingJoins.length, bundle.formationInvites.length);
64
+ return bundle;
65
+ }
66
+ /** One entry per strand id, the party's own first: an id in both `Strand` and `JoinedStrand` exports once, as `own`. */
67
+ async function exportStrands(node, database) {
68
+ const entries = new Map();
69
+ for (const row of await database.queryStrands()) {
70
+ entries.set(row.Id, await exportStrand(node, database, 'own', row));
71
+ }
72
+ // Publication needs a control connection, so a one-node party's joins are machine-local
73
+ // records until it holds one (a relay's or a foreign party's node counts) and `JoinedStrand`
74
+ // rows after. Both are read.
75
+ const unpublished = (await node.listUnpublishedJoinedStrands()).map(joinedStrandRow);
76
+ for (const row of [...await database.queryJoinedStrands(), ...unpublished]) {
77
+ if (!entries.has(row.Id)) {
78
+ entries.set(row.Id, await exportStrand(node, database, 'joined', row));
79
+ }
80
+ }
81
+ return [...entries.values()];
82
+ }
83
+ async function exportStrand(node, database, kind, row) {
84
+ const networkState = node.getStrandNetworkStateStore()?.load(row.Id);
85
+ return {
86
+ kind,
87
+ row: { Id: row.Id, Type: row.Type, MemberPrivateKey: row.MemberPrivateKey },
88
+ partyKey: await database.queryStrandPartyKey(row.Id),
89
+ ...(networkState !== undefined && { networkState }),
90
+ carriedAddrs: node.getCarriedStrandAddrs(row.Id)
91
+ };
92
+ }
93
+ /**
94
+ * The invitations still redeemable at export: unexpired, with uses left after the usage
95
+ * recorded so far. `FormationUsage` rows are append-only history and stay behind, so the
96
+ * adopting party's count restarts at zero against the reduced cap. A spent or expired
97
+ * invitation is left out: there is nothing to keep.
98
+ */
99
+ async function exportFormationInvites(database, now) {
100
+ const entries = [];
101
+ for (const row of await database.queryFormationInvites()) {
102
+ if (isExpired(row.expiresAtMs, now)) {
103
+ continue;
104
+ }
105
+ const usesLeft = row.totalUses === null ? null : row.totalUses - await database.countFormationUsage(row.token);
106
+ if (usesLeft !== null && usesLeft <= 0) {
107
+ continue;
108
+ }
109
+ entries.push({
110
+ token: row.token,
111
+ sAppId: row.sAppId,
112
+ expiresAtMs: row.expiresAtMs,
113
+ usesLeft,
114
+ validationUrl: row.validationUrl,
115
+ strandId: row.strandId
116
+ });
117
+ }
118
+ return entries;
119
+ }
120
+ /**
121
+ * Seat `bundle` in the running node's party, every row owner-signed by the node's own identity
122
+ * key. Refuses up front, with a typed error and no write, when that key is not an owner of the
123
+ * party ({@link StrandMoveNotOwnerError}) or the bundle came from this very party
124
+ * ({@link StrandMoveSamePartyError}).
125
+ *
126
+ * Strands first, then the invitations, then the pending joins. Every write is idempotent (a row
127
+ * already present with the same content is skipped and counted as moved), so the same bundle
128
+ * can be adopted again after a crash or a transient write failure and finishes what is left.
129
+ * One strand's failure does not stop the others: it is reported under `failed` for the retry.
130
+ * One transaction over the whole bundle was weighed and declined: the per-row writers already
131
+ * carry the stamp, signature and retry policy, and the resumable bundle covers a crash between
132
+ * rows.
133
+ *
134
+ * @throws when the node is not started, beside the two typed refusals.
135
+ */
136
+ export async function adoptStrandBundle(node, bundle) {
137
+ const database = requireControlDatabase(node, 'adopt a strand bundle');
138
+ if (bundle.partyId === node.partyId) {
139
+ throw new StrandMoveSamePartyError(node.partyId);
140
+ }
141
+ if (!(await node.wireOwnKeyIfOwner())) {
142
+ throw new StrandMoveNotOwnerError(node.partyId);
143
+ }
144
+ const owner = node.getIdentityOwnerKey();
145
+ const signer = { ownerKey: owner.publicKeyB64, signMessage: signMessageWith(owner.privateKeyB64) };
146
+ const report = {
147
+ fromPartyId: bundle.partyId,
148
+ moved: [],
149
+ refused: [],
150
+ failed: [],
151
+ invitationsMoved: 0,
152
+ invitationsDropped: 0,
153
+ pendingJoinsMoved: 0
154
+ };
155
+ for (const entry of bundle.strands) {
156
+ await adoptStrand(node, database, signer, entry, report);
157
+ }
158
+ const settled = settledStrands(report);
159
+ const now = Date.now();
160
+ for (const invite of bundle.formationInvites) {
161
+ await adoptFormationInvite(node, database, invite, settled, report, now);
162
+ }
163
+ for (const join of bundle.pendingJoins) {
164
+ await adoptPendingJoin(database, signer, join, report);
165
+ }
166
+ log('adopt(%s ← %s): %d moved, %d refused, %d failed; %d invitation(s) moved, %d dropped; %d pending join(s)', node.partyId, bundle.partyId, report.moved.length, report.refused.length, report.failed.length, report.invitationsMoved, report.invitationsDropped, report.pendingJoinsMoved);
167
+ return report;
168
+ }
169
+ function settledStrands(report) {
170
+ return {
171
+ moved: new Set(report.moved),
172
+ failed: new Set(report.failed.filter((entry) => entry.kind === 'strand').map((entry) => entry.id))
173
+ };
174
+ }
175
+ /**
176
+ * Seat one strand, in the order the adopting node's strand watcher demands. The watcher polls
177
+ * `Strand` and `JoinedStrand` every five seconds and launches, or offers, a new row at once; a
178
+ * closed strand's founder launch resolves the party key and, finding none, mints a fresh one,
179
+ * which would found the strand a second time under a stranger's identity. So the party key is
180
+ * seated before the row, and the strand's peers (its saved routing table and carried addresses)
181
+ * before both, so the launch's cohort seed has somewhere to dial. Every step is
182
+ * insert-if-absent.
183
+ */
184
+ async function adoptStrand(node, database, signer, entry, report) {
185
+ const strandId = entry.row.Id;
186
+ try {
187
+ const live = await readLiveStrand(database, strandId);
188
+ const refusal = strandRefusal(entry, live);
189
+ if (refusal !== null) {
190
+ log('adopt: strand %s refused: %s', strandId, refusal);
191
+ report.refused.push({ strandId, reason: refusal });
192
+ return;
193
+ }
194
+ await seatStrandPeers(node, entry);
195
+ if (entry.partyKey !== null) {
196
+ await node.ensureStrandPartyKey(strandId, entry.partyKey);
197
+ }
198
+ if (entry.kind === 'own') {
199
+ await node.publishStrand(strandId, entry.row.Type, entry.row.MemberPrivateKey ?? undefined);
200
+ }
201
+ else {
202
+ await seatJoinedStrand(database, signer, entry.row, live.joined);
203
+ }
204
+ report.moved.push(strandId);
205
+ }
206
+ catch (error) {
207
+ log('adopt: strand %s (%s) not seated this time; adopt the same bundle again: %o', strandId, entry.kind, error);
208
+ report.failed.push({ kind: 'strand', id: strandId, reason: errorText(error) });
209
+ }
210
+ }
211
+ async function readLiveStrand(database, strandId) {
212
+ return {
213
+ partyKey: await database.queryStrandPartyKey(strandId),
214
+ own: await database.queryStrand(strandId),
215
+ joined: await database.queryJoinedStrand(strandId)
216
+ };
217
+ }
218
+ /**
219
+ * Why `entry` can never be seated here, or null. A strand this party already participates in
220
+ * under its own identity keeps that membership: its `StrandPartyKey` is the key its `Member`
221
+ * row was seated under, and the bundle's key is another party's seat. A row for the id of the
222
+ * other kind (the party founded what the bundle joined, or joined what the bundle founded), or
223
+ * of the same kind with different content, is the same refusal. Decided from a read before any
224
+ * write, so a retry reports the refusal again instead of failing on a write; the writes
225
+ * re-check (`ensureStrandPartyKey` and `publishStrand` throw on a mismatch), so a row landing
226
+ * between the read and the write fails the strand this time and is refused on the retry.
227
+ */
228
+ function strandRefusal(entry, live) {
229
+ const strandId = entry.row.Id;
230
+ if (live.partyKey !== null && live.partyKey !== entry.partyKey) {
231
+ return `this party already holds its own membership identity for strand ${strandId} and keeps that membership`;
232
+ }
233
+ if (entry.kind === 'own' && live.joined !== null) {
234
+ return `this party already participates in strand ${strandId} as a strand it joined and keeps that membership`;
235
+ }
236
+ if (entry.kind === 'joined' && live.own !== null) {
237
+ return `this party already holds strand ${strandId} as its own and keeps that membership`;
238
+ }
239
+ const same = entry.kind === 'own' ? live.own : live.joined;
240
+ if (same !== null && !sameStrandContent(same, entry.row)) {
241
+ return `this party already holds strand ${strandId} as ${entry.kind === 'own' ? 'its own' : 'a join'} with different content`;
242
+ }
243
+ return null;
244
+ }
245
+ /**
246
+ * The strand's saved routing table and carried addresses, when this node holds none for it: a
247
+ * launch here since an earlier attempt may have saved fresher state than the bundle carries.
248
+ */
249
+ async function seatStrandPeers(node, entry) {
250
+ const strandId = entry.row.Id;
251
+ const store = node.getStrandNetworkStateStore();
252
+ if (entry.networkState !== undefined && store !== null && store.load(strandId) === undefined) {
253
+ await store.save(strandId, entry.networkState);
254
+ }
255
+ if (entry.carriedAddrs.length > 0 && node.getCarriedStrandAddrs(strandId).length === 0) {
256
+ await node.recordCarriedStrandAddrs(strandId, entry.carriedAddrs);
257
+ }
258
+ }
259
+ /**
260
+ * `insertJoinedStrand`, read as done when the identical row is already there: `existing` from
261
+ * the refusal read (a retry), or one landing between that read and the insert (a sibling's
262
+ * publish; `strandRefusal` has already compared the content of the first, and the second is
263
+ * compared here).
264
+ */
265
+ async function seatJoinedStrand(database, signer, row, existing) {
266
+ if (existing !== null) {
267
+ return;
268
+ }
269
+ try {
270
+ await database.insertJoinedStrand(row, signer.ownerKey, signer.signMessage);
271
+ }
272
+ catch (error) {
273
+ if (!isStrandIdConflict(error, 'JoinedStrand')) {
274
+ throw error;
275
+ }
276
+ const landed = await database.queryJoinedStrand(row.Id);
277
+ if (landed === null || !sameStrandContent(landed, row)) {
278
+ throw error;
279
+ }
280
+ log('adopt: joined strand %s landed concurrently with identical content; adopted', row.Id);
281
+ }
282
+ }
283
+ /**
284
+ * Copy one invitation the exporting party handed out. Its envelope names the app's machine,
285
+ * whose peer id and relay address do not change with the party, so the row here is what keeps
286
+ * an envelope already handed out redeemable. Dropped and counted: an invitation with a
287
+ * `ValidationUrl` (its sign-offs verify against a `ValidationKey` row, and which keys may
288
+ * validate here is this party's owners' decision) and one bound to a strand not seated here,
289
+ * whether refused above or no longer held by the exporting party (`unpublishStrand` leaves a
290
+ * bound row behind): the formation recorder would refuse every redemption as a missing host.
291
+ * Dropped silently: one that expired since the export. One bound to a strand that failed this
292
+ * time is left for the retry that seats the strand.
293
+ */
294
+ async function adoptFormationInvite(node, database, invite, settled, report, now) {
295
+ if (invite.strandId !== null && settled.failed.has(invite.strandId)) {
296
+ return;
297
+ }
298
+ const hostMissing = invite.strandId !== null && !settled.moved.has(invite.strandId);
299
+ if (invite.validationUrl !== null || hostMissing) {
300
+ report.invitationsDropped += 1;
301
+ return;
302
+ }
303
+ if (isExpired(invite.expiresAtMs, now)) {
304
+ return;
305
+ }
306
+ try {
307
+ if (await database.queryFormationInvite(invite.token) === null) {
308
+ await node.publishFormationInvite(invite.token, invite.sAppId, {
309
+ expiresAtMs: invite.expiresAtMs ?? undefined,
310
+ totalUses: invite.usesLeft ?? undefined,
311
+ strandId: invite.strandId ?? undefined
312
+ });
313
+ }
314
+ report.invitationsMoved += 1;
315
+ }
316
+ catch (error) {
317
+ log('adopt: invitation for sApp %s not seated this time; adopt the same bundle again: %o', invite.sAppId, error);
318
+ report.failed.push({ kind: 'invitation', id: pendingJoinId(invite.token), reason: errorText(error) });
319
+ }
320
+ }
321
+ /**
322
+ * Re-seat one join the exporting party asked for, with its outcome, under a fresh request
323
+ * stamp. A `joined` outcome's membership invitation matters: for a closed strand it is the seat
324
+ * the party's membership reconciler has not redeemed yet, and this node's retry loop re-stages
325
+ * it from the moved row at its next read. A `failed` outcome is history for the user to
326
+ * dismiss; the seven-day removal cleans it up.
327
+ *
328
+ * A request this party already holds for the invitation is read as done, whether an earlier
329
+ * adoption seated it or the user asked for the same invitation from here. Its outcome is written
330
+ * only when the live request is still pending and is this bundle's own (every request column
331
+ * equal), which closes the crash window between the request insert and its outcome insert
332
+ * without touching a request this party made on its own. A `joined` outcome whose strand was
333
+ * refused above moves too: its invitation is staged like any other, and the reconciler, finding
334
+ * the party's member row already seated, burns it unspent or drops it dead.
335
+ *
336
+ * NOTE: if party-private app tables land in the control database
337
+ * (tickets/blocked/decide-party-private-labels-and-app-state.md), they must move with the
338
+ * strands, and this is the site: one more per-row idempotent write beside the join.
339
+ */
340
+ async function adoptPendingJoin(database, signer, join, report) {
341
+ const { StampId: _stampId, outcome, ...request } = join;
342
+ try {
343
+ const live = await seatJoinRequest(database, signer, request);
344
+ if (live !== null && outcome !== null && live.outcome === null) {
345
+ await database.recordJoinOutcome(live, outcome, signer.ownerKey, signer.signMessage);
346
+ }
347
+ report.pendingJoinsMoved += 1;
348
+ }
349
+ catch (error) {
350
+ log('adopt: pending join %s not seated this time; adopt the same bundle again: %o', join.Id, error);
351
+ report.failed.push({ kind: 'pending-join', id: join.Id, reason: errorText(error) });
352
+ }
353
+ }
354
+ /**
355
+ * The live request for `request` after its insert: the row just written, the identical row
356
+ * already there, or null when the party holds a different request for the invitation (its
357
+ * own ask, left alone) or a dismissed one whose retired stamp is still held until the reap
358
+ * pass (the party chose not to have it).
359
+ */
360
+ async function seatJoinRequest(database, signer, request) {
361
+ try {
362
+ return await database.insertJoinRequest(request, signer.ownerKey, signer.signMessage);
363
+ }
364
+ catch (error) {
365
+ if (!isPendingJoinConflict(error)) {
366
+ throw error;
367
+ }
368
+ const existing = await database.queryPendingJoin(request.Id);
369
+ return existing !== null && sameRequest(existing, request) ? existing : null;
370
+ }
371
+ }
372
+ function sameRequest(live, request) {
373
+ return live.Invitation === request.Invitation
374
+ && live.Disclosure === request.Disclosure
375
+ && live.RequestedAt === request.RequestedAt
376
+ && live.ExpiresAt === request.ExpiresAt;
377
+ }
378
+ /**
379
+ * Every strand id the node's party holds, of either kind (`Strand`, `JoinedStrand` and this
380
+ * machine's unpublished joins), and every pending join id it holds, whatever the outcome.
381
+ *
382
+ * @throws when the node is not started.
383
+ */
384
+ export async function readStrandHoldings(node) {
385
+ const database = requireControlDatabase(node, 'read its strand holdings');
386
+ const strands = [
387
+ ...await database.queryStrands(),
388
+ ...await database.queryJoinedStrands(),
389
+ ...await node.listUnpublishedJoinedStrands()
390
+ ];
391
+ return {
392
+ strandIds: new Set(strands.map((row) => row.Id)),
393
+ pendingJoinIds: new Set((await database.queryPendingJoins()).map((join) => join.Id))
394
+ };
395
+ }
396
+ /**
397
+ * What of `bundle` a party with `holdings` lacks, so the exporting party can be dissolved only
398
+ * when this is empty. A strand counts as held under any kind: one the adoption refused is in
399
+ * the holdings (that is why it was refused), so its data stays reachable and only the
400
+ * exporting party's seat in it is lost. A pending join counts only while open at `nowMs`: a
401
+ * `joined` one's strand is covered by the strand check, a `failed` one is history, and an
402
+ * expired one can never be attempted. Formation invitations are not compared: the exporting
403
+ * node never runs again once dissolved, so one the adoption dropped was unredeemable from then.
404
+ */
405
+ export function unheldBundleContent(bundle, holdings, nowMs) {
406
+ return {
407
+ strandIds: bundle.strands.map((entry) => entry.row.Id).filter((strandId) => !holdings.strandIds.has(strandId)),
408
+ pendingJoinIds: bundle.pendingJoins
409
+ .filter((join) => isOpenJoin(join, nowMs) && !holdings.pendingJoinIds.has(join.Id))
410
+ .map((join) => join.Id)
411
+ };
412
+ }
413
+ /** No outcome yet, and before `ExpiresAt`: the pending-join runner attempts nothing at or after it. */
414
+ function isOpenJoin(join, nowMs) {
415
+ return join.outcome === null && nowMs < join.ExpiresAt;
416
+ }
417
+ /** Content only: `Type` and the read secret. `FounderOwnerKey` is provenance, and a moved strand's changes. */
418
+ function sameStrandContent(live, desired) {
419
+ return live.Type === desired.Type && (live.MemberPrivateKey ?? null) === (desired.MemberPrivateKey ?? null);
420
+ }
421
+ /** The comparison `hasOutstandingFormationInvite` makes, so an invitation the responder would refuse is never copied. */
422
+ function isExpired(expiresAtMs, now) {
423
+ return expiresAtMs !== null && expiresAtMs <= now;
424
+ }
425
+ function requireControlDatabase(node, action) {
426
+ const database = node.getControlDatabase();
427
+ if (database === null) {
428
+ throw new Error(`CadreNode must be started before it can ${action}`);
429
+ }
430
+ return database;
431
+ }
432
+ function signMessageWith(privateKeyB64) {
433
+ return (message) => sign(message, privateKeyB64, 'ed25519', 'bytes', 'base64url', 'base64url');
434
+ }
435
+ function errorText(error) {
436
+ return error instanceof Error ? error.message : String(error);
437
+ }
438
+ //# sourceMappingURL=strand-move.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strand-move.js","sourceRoot":"","sources":["../src/strand-move.ts"],"names":[],"mappings":"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"}
@@ -89,13 +89,14 @@ export function strandNodeAddrs(network) {
89
89
  const kept = relayAddrs.length > 0 ? direct.filter((addr) => !isCircuitListenAddr(addr)) : direct;
90
90
  // The search entries are appended AFTER the dedupe on purpose: they are identical
91
91
  // strings, and each one must survive as its own listener (see the module doc).
92
+ // A node given these entries is built with libp2p's relay discovery off whenever it
93
+ // has a transport list to wrap (`node-transports.ts`), so an empty slot starts no
94
+ // peer-store scan or random walk; only its supervisor fills it.
92
95
  //
93
- // NOTE: every bare entry also makes libp2p start its own relay discovery whenever
94
- // that listener's slot is empty (launch, and every loss). Measured on a node with
95
- // no peer router: one peer-store scan plus a random walk that fails at once
96
- // (`NoPeerRoutersError`) per loss event — no dial churn. If strand nodes ever get
97
- // a peer router (a DHT), that walk turns into real dials on every relay loss;
98
- // cap or disable `circuitRelayTransport`'s discovery then.
96
+ // NOTE: where discovery stays on (a Node host that passed no `defaultTransports`),
97
+ // each empty slot still runs one peer-store scan plus a random walk that fails at once
98
+ // (`NoPeerRoutersError`), measured on a node with no peer router. If strand nodes ever
99
+ // get a peer router (a DHT), that walk turns into real dials on every relay loss.
99
100
  const listenAddrs = [
100
101
  ...dedupe(kept.map(ephemeralPortListenAddr)),
101
102
  ...relayAddrs.map(() => RELAY_SEARCH_LISTEN_ADDR)
@@ -1 +1 @@
1
- {"version":3,"file":"strand-network-config.js","sourceRoot":"","sources":["../src/strand-network-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strand-network-config.js","sourceRoot":"","sources":["../src/strand-network-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * A running strand node redialing the strand's other members itself, when it holds a
3
+ * connection to none of them, with the dial budget the host's declared link gives a
4
+ * relayed connection.
5
+ *
6
+ * Why. Below cadre-core the only thing that redials a strand peer is FRET's maintenance:
7
+ * its pings and neighbour announcements open the connection themselves (`dial: true`) under
8
+ * a fixed 2 000 ms limit on the whole call (`MAINTENANCE_RPC_TIMEOUT_MS` in `p2p-fret`'s
9
+ * `fret-service.ts`). Opening a relayed connection takes longer than that on a slow link:
10
+ * about 9 s on a 20 ms per-frame cap with a 70%-busy event loop
11
+ * (`joiner-first-commit-over-slow-relay`, whose slow-restart arm reproduces this), where
12
+ * {@link relayedDialBudgetMs} allows 21.5 s at the default declaration. Each such redial is
13
+ * cut off part-way through the handshake, from both sides, for as long as the link stays that
14
+ * slow. And after a restart nothing else dials a strand peer: a cross-party strand's launch
15
+ * seed is empty, and cadre-core's address passes only fill the address book. Two phones that
16
+ * restarted then never reconnect, and a write either made alone never reaches the other.
17
+ *
18
+ * What it does. One loop per strand runtime, started in `StrandInstanceManager`'s runtime
19
+ * build and stopped with the runtime (stop, quiesce, failed build), so a hibernating strand
20
+ * never dials. Each look at the node:
21
+ * - a member connected: nothing to do; look again in {@link STRAND_REDIAL_CHECK_MS}, or as
22
+ * soon as a peer disconnects.
23
+ * - no member connected and none known: likewise nothing (a founder alone, a storage replica
24
+ * before anyone arrives).
25
+ * - no member connected and some known: dial every known member at once
26
+ * ({@link dialPeerAddrs}, direct addresses before relayed ones), each address with the
27
+ * relayed-dial budget. A round that connects to one goes back to watching; one that does
28
+ * not waits a backoff starting at that budget and doubling to
29
+ * {@link STRAND_REDIAL_MAX_BACKOFF_MS}.
30
+ *
31
+ * Who is known: every peer the node's FRET table holds a verified signed address record for
32
+ * (`strand-fret-addrs.ts`), plus the launch seed (sibling strand addresses and a formation's
33
+ * carried addresses). Who counts as connected: a peer FRET labels `'member'` (it has seen it
34
+ * speak this strand's protocols), or a seed peer. Neither ever includes a configured relay, a
35
+ * peer FRET labels `'foreign'`, or a peer the revocation gate refuses. The relay matters most:
36
+ * the strand node is connected to it throughout and FRET files it with a record like any
37
+ * peer, so counting it would leave this loop idle exactly when it is needed.
38
+ *
39
+ * NOTE: this compensates for FRET's 2 000 ms maintenance dial limit, which cannot open a
40
+ * relayed connection on a slow link (upstream half:
41
+ * `blocked/fret-maintenance-ping-dial-budget-below-a-relayed-dial`). If FRET's maintenance
42
+ * dial ever takes a limit derived from the declared link, this loop becomes a backstop and
43
+ * may be deleted, as `peer-join-backfill.ts` says of itself.
44
+ */
45
+ import { type AddrDialer, type PeerDialBudget } from './peer-dial.js';
46
+ import { type FretAddrHost } from './strand-fret-addrs.js';
47
+ import { type TimeoutScheduler } from './timeout-scheduler.js';
48
+ /**
49
+ * Gap between looks while a member is connected or none is known. While a member is
50
+ * connected a look reads FRET's labels and the connection list and checks no signature
51
+ * ({@link strandFretMembership}); a disconnect also triggers a look at once.
52
+ */
53
+ export declare const STRAND_REDIAL_CHECK_MS = 5000;
54
+ /**
55
+ * Ceiling on the wait between failed rounds. A member that is gone for good is dialed this
56
+ * often for as long as FRET keeps its record (up to 14 days after it was last confirmed).
57
+ */
58
+ export declare const STRAND_REDIAL_MAX_BACKOFF_MS: number;
59
+ /** The loop's dial budget and cadence. */
60
+ export interface StrandRedialTimings {
61
+ /** Limit on one address's dial, and on one member's addresses together. */
62
+ budget: PeerDialBudget;
63
+ /** Wait after the first failed round; doubled after each further one. */
64
+ minBackoffMs: number;
65
+ /** Ceiling the backoff doubles up to. */
66
+ maxBackoffMs: number;
67
+ /** Gap between looks while nothing needs dialing. */
68
+ checkMs: number;
69
+ }
70
+ /**
71
+ * The timings at a declared link round trip: {@link relayedDialBudgetMs} per address, the
72
+ * same whole-budget count per member as the control cohort's dials (dead direct addresses
73
+ * a record lists ahead of the relayed one that works), and a first backoff of one per-address
74
+ * budget.
75
+ */
76
+ export declare function strandRedialTimings(linkRoundTripMs?: number): StrandRedialTimings;
77
+ /** The slice of a strand's libp2p node the loop uses. Structurally satisfied by db-p2p's node. */
78
+ export interface RedialNode extends AddrDialer {
79
+ services?: FretAddrHost['services'];
80
+ getConnections(): ReadonlyArray<{
81
+ remotePeer: {
82
+ toString(): string;
83
+ };
84
+ }>;
85
+ addEventListener(type: 'peer:disconnect', listener: () => void): void;
86
+ removeEventListener(type: 'peer:disconnect', listener: () => void): void;
87
+ }
88
+ export interface StrandRedialDeps {
89
+ /** Names the strand in logs. */
90
+ label: string;
91
+ node: RedialNode;
92
+ /**
93
+ * The launch seed: multiaddr strings, each naming its peer in a trailing `/p2p/`.
94
+ *
95
+ * NOTE: read once, at runtime build. Addresses a re-formation carries while the strand runs
96
+ * reach the peerStore (`recordFormationStrandAddrs`) but not this loop until the runtime is
97
+ * rebuilt; if a re-formed strand is seen not reconnecting until relaunch, read the seed per round.
98
+ */
99
+ seed: readonly string[];
100
+ /** Peer ids never dialed nor counted as members: the relays the node reserves on. */
101
+ excluded: ReadonlySet<string>;
102
+ /** The revocation gate's current verdict, asked of peers and of this node itself; absent for an open strand. */
103
+ isRevoked?: (peerId: string) => boolean;
104
+ /** Omit for real (unref'd) timers. */
105
+ scheduler?: TimeoutScheduler;
106
+ }
107
+ /** A running redial loop; its only imperative is {@link stop}. */
108
+ export interface StrandPeerRedial {
109
+ /** Idempotent. No look or round starts after it; dials in flight run out on their own deadlines, and their result is discarded. */
110
+ stop(): void;
111
+ }
112
+ /** Start the loop; its first look runs at once. */
113
+ export declare function superviseStrandPeerRedial(deps: StrandRedialDeps, timings: StrandRedialTimings): StrandPeerRedial;