@serfab/cadre-core 1.14.0 → 1.16.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 (171) hide show
  1. package/README.md +293 -274
  2. package/dist/cadre-invite-protocol.d.ts +64 -29
  3. package/dist/cadre-invite-protocol.js +207 -38
  4. package/dist/cadre-invite-protocol.js.map +1 -1
  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 +375 -79
  9. package/dist/cadre-node.js +758 -186
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/control-database.d.ts +92 -12
  12. package/dist/control-database.js +323 -142
  13. package/dist/control-database.js.map +1 -1
  14. package/dist/control-schema.d.ts +1 -1
  15. package/dist/control-schema.js +19 -3
  16. package/dist/control-schema.js.map +1 -1
  17. package/dist/ed25519-key.d.ts +7 -9
  18. package/dist/ed25519-key.js +20 -10
  19. package/dist/ed25519-key.js.map +1 -1
  20. package/dist/fetch-impl.d.ts +12 -0
  21. package/dist/fetch-impl.js +23 -0
  22. package/dist/fetch-impl.js.map +1 -0
  23. package/dist/file-durable-slot.js +2 -1
  24. package/dist/file-durable-slot.js.map +1 -1
  25. package/dist/file-safe-name.d.ts +25 -0
  26. package/dist/file-safe-name.js +29 -0
  27. package/dist/file-safe-name.js.map +1 -0
  28. package/dist/formation-approval.js +5 -11
  29. package/dist/formation-approval.js.map +1 -1
  30. package/dist/fs-atomic.d.ts +0 -19
  31. package/dist/fs-atomic.js +3 -24
  32. package/dist/fs-atomic.js.map +1 -1
  33. package/dist/index.d.ts +12 -5
  34. package/dist/index.js +31 -6
  35. package/dist/index.js.map +1 -1
  36. package/dist/invitation-bootstrap.d.ts +50 -12
  37. package/dist/invitation-bootstrap.js +76 -13
  38. package/dist/invitation-bootstrap.js.map +1 -1
  39. package/dist/joined-strand-store.d.ts +7 -0
  40. package/dist/joined-strand-store.js +9 -0
  41. package/dist/joined-strand-store.js.map +1 -1
  42. package/dist/key-store-file.js +2 -1
  43. package/dist/key-store-file.js.map +1 -1
  44. package/dist/link-budget.d.ts +9 -4
  45. package/dist/link-budget.js +9 -4
  46. package/dist/link-budget.js.map +1 -1
  47. package/dist/machine-removal.d.ts +78 -0
  48. package/dist/machine-removal.js +166 -0
  49. package/dist/machine-removal.js.map +1 -0
  50. package/dist/membership-connection-gater.d.ts +6 -5
  51. package/dist/membership-connection-gater.js +6 -5
  52. package/dist/membership-connection-gater.js.map +1 -1
  53. package/dist/node-claim-payload.d.ts +55 -0
  54. package/dist/node-claim-payload.js +108 -4
  55. package/dist/node-claim-payload.js.map +1 -1
  56. package/dist/node-default-transports.d.ts +27 -0
  57. package/dist/node-default-transports.js +35 -0
  58. package/dist/node-default-transports.js.map +1 -0
  59. package/dist/node-transports.d.ts +30 -0
  60. package/dist/node-transports.js +40 -0
  61. package/dist/node-transports.js.map +1 -0
  62. package/dist/peer-dial.d.ts +124 -27
  63. package/dist/peer-dial.js +254 -30
  64. package/dist/peer-dial.js.map +1 -1
  65. package/dist/peer-join-backfill.js +6 -0
  66. package/dist/peer-join-backfill.js.map +1 -1
  67. package/dist/peer-record.d.ts +6 -45
  68. package/dist/peer-record.js +8 -50
  69. package/dist/peer-record.js.map +1 -1
  70. package/dist/primary-lan-address.d.ts +8 -0
  71. package/dist/primary-lan-address.js +46 -0
  72. package/dist/primary-lan-address.js.map +1 -0
  73. package/dist/provider/client.d.ts +131 -0
  74. package/dist/provider/client.js +226 -0
  75. package/dist/provider/client.js.map +1 -0
  76. package/dist/provider/descriptor.d.ts +41 -0
  77. package/dist/provider/descriptor.js +103 -0
  78. package/dist/provider/descriptor.js.map +1 -0
  79. package/dist/provider/index.d.ts +9 -0
  80. package/dist/provider/index.js +10 -0
  81. package/dist/provider/index.js.map +1 -0
  82. package/dist/provider/oauth.d.ts +125 -0
  83. package/dist/provider/oauth.js +199 -0
  84. package/dist/provider/oauth.js.map +1 -0
  85. package/dist/provider/rent-node.d.ts +101 -0
  86. package/dist/provider/rent-node.js +220 -0
  87. package/dist/provider/rent-node.js.map +1 -0
  88. package/dist/relay-addrs.d.ts +9 -4
  89. package/dist/relay-addrs.js +9 -4
  90. package/dist/relay-addrs.js.map +1 -1
  91. package/dist/relay-reservation.d.ts +51 -16
  92. package/dist/relay-reservation.js +107 -24
  93. package/dist/relay-reservation.js.map +1 -1
  94. package/dist/seed-bootstrap.d.ts +22 -5
  95. package/dist/seed-bootstrap.js +48 -25
  96. package/dist/seed-bootstrap.js.map +1 -1
  97. package/dist/strand-commit-durability.d.ts +54 -0
  98. package/dist/strand-commit-durability.js +74 -0
  99. package/dist/strand-commit-durability.js.map +1 -0
  100. package/dist/strand-database.d.ts +23 -8
  101. package/dist/strand-database.js +41 -13
  102. package/dist/strand-database.js.map +1 -1
  103. package/dist/strand-first-sync-gate.d.ts +7 -1
  104. package/dist/strand-first-sync-gate.js +7 -1
  105. package/dist/strand-first-sync-gate.js.map +1 -1
  106. package/dist/strand-formation-protocol.js +4 -1
  107. package/dist/strand-formation-protocol.js.map +1 -1
  108. package/dist/strand-fret-addrs.d.ts +19 -1
  109. package/dist/strand-fret-addrs.js +27 -9
  110. package/dist/strand-fret-addrs.js.map +1 -1
  111. package/dist/strand-instance-manager.d.ts +81 -19
  112. package/dist/strand-instance-manager.js +216 -105
  113. package/dist/strand-instance-manager.js.map +1 -1
  114. package/dist/strand-move.d.ts +124 -0
  115. package/dist/strand-move.js +438 -0
  116. package/dist/strand-move.js.map +1 -0
  117. package/dist/strand-network-config.js +7 -6
  118. package/dist/strand-network-config.js.map +1 -1
  119. package/dist/strand-peer-redial.d.ts +113 -0
  120. package/dist/strand-peer-redial.js +236 -0
  121. package/dist/strand-peer-redial.js.map +1 -0
  122. package/dist/strand-wake-protocol.js +10 -7
  123. package/dist/strand-wake-protocol.js.map +1 -1
  124. package/dist/types.d.ts +212 -4
  125. package/dist/types.js +10 -0
  126. package/dist/types.js.map +1 -1
  127. package/package.json +18 -10
  128. package/src/cadre-invite-protocol.ts +1052 -864
  129. package/src/cadre-join-request.ts +167 -0
  130. package/src/cadre-node.ts +10003 -9381
  131. package/src/control-database.ts +4663 -4451
  132. package/src/control-schema.ts +19 -3
  133. package/src/ed25519-key.ts +20 -10
  134. package/src/fetch-impl.ts +23 -0
  135. package/src/file-durable-slot.ts +64 -63
  136. package/src/file-safe-name.ts +32 -0
  137. package/src/formation-approval.ts +604 -610
  138. package/src/fs-atomic.ts +92 -117
  139. package/src/index.ts +71 -4
  140. package/src/invitation-bootstrap.ts +91 -13
  141. package/src/joined-strand-store.ts +508 -498
  142. package/src/key-store-file.ts +142 -141
  143. package/src/link-budget.ts +9 -4
  144. package/src/machine-removal.ts +216 -0
  145. package/src/membership-connection-gater.ts +638 -637
  146. package/src/node-claim-payload.ts +138 -4
  147. package/src/node-default-transports.ts +38 -0
  148. package/src/node-transports.ts +49 -0
  149. package/src/peer-dial.ts +604 -336
  150. package/src/peer-join-backfill.ts +6 -0
  151. package/src/peer-record.ts +159 -202
  152. package/src/primary-lan-address.ts +47 -0
  153. package/src/provider/client.ts +326 -0
  154. package/src/provider/descriptor.ts +129 -0
  155. package/src/provider/index.ts +59 -0
  156. package/src/provider/oauth.ts +285 -0
  157. package/src/provider/rent-node.ts +275 -0
  158. package/src/relay-addrs.ts +9 -4
  159. package/src/relay-reservation.ts +1211 -1121
  160. package/src/seed-bootstrap.ts +1502 -1471
  161. package/src/strand-commit-durability.ts +116 -0
  162. package/src/strand-database.ts +46 -14
  163. package/src/strand-first-sync-gate.ts +7 -1
  164. package/src/strand-formation-protocol.ts +1039 -1036
  165. package/src/strand-fret-addrs.ts +179 -145
  166. package/src/strand-instance-manager.ts +2026 -1883
  167. package/src/strand-move.ts +585 -0
  168. package/src/strand-network-config.ts +7 -6
  169. package/src/strand-peer-redial.ts +307 -0
  170. package/src/strand-wake-protocol.ts +10 -7
  171. package/src/types.ts +220 -4
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Rent a node from a hosting provider and add it to this node's cadre, by whichever of the two
3
+ * join paths fits (`docs/architecture.md` → "Renting a node from an app"):
4
+ *
5
+ * - **invitation**, when the cadre has a member the container can reach from the provider's
6
+ * network (`CadreNode.invitationReach()` reads `anywhere`): mint a single-use, non-owner
7
+ * invitation and create the container with it; the container joins by itself and the app
8
+ * watches `getContainer(id).join`.
9
+ * - **claim**, otherwise (a phone that is the cadre's only member, or members reachable only on
10
+ * their LAN, which the provider's container is never on): create the container, poll for its
11
+ * node code, and claim it from this node.
12
+ *
13
+ * Nothing here persists anything: which container belongs to which cadre is the app's record,
14
+ * and {@link finishRental} is how an app resumes a rental it was killed in the middle of. The
15
+ * node code carries the claim secret, so neither the code nor the decoded payload is ever
16
+ * logged, only the container id and the peer id.
17
+ */
18
+ import debug from 'debug';
19
+ import { decodeNodeClaimPayload } from '../node-claim-payload.js';
20
+ import { ProviderApiError } from './client.js';
21
+ const log = debug('sereus:cadre:provider-rent');
22
+ export class RentNodeError extends Error {
23
+ constructor(code, containerId) {
24
+ super(`rentNode: ${code}${containerId ? ` (container ${containerId})` : ''}`);
25
+ this.code = code;
26
+ this.containerId = containerId;
27
+ this.name = 'RentNodeError';
28
+ }
29
+ }
30
+ /**
31
+ * The provider's enrolment window is 60 s and an image pull can precede it. Bounds the
32
+ * container's start, not an exchange over the link, so it is not derived from the link budget.
33
+ */
34
+ // eslint-disable-next-line no-restricted-syntax -- link-independent: bounds the container's start (an image pull plus the provider's 60 s enrolment window), not an exchange over the link
35
+ export const DEFAULT_NODE_CODE_DEADLINE_MS = 120000;
36
+ export const NODE_CODE_POLL_MS = 2000;
37
+ /** Lifetime of the invitation a rental mints: long enough for the container to start and redeem, short enough to limit a leaked bundle. */
38
+ export const RENTAL_INVITATION_TTL_MS = 15 * 60000;
39
+ /**
40
+ * Rent a node and add it to `node`'s cadre; see the module doc for the path choice.
41
+ *
42
+ * The claim path's `claimNode` can take up to the per-peer dial limit plus the seed request
43
+ * when no address answers (its doc: 114.5 s at the default link); no timeout is added on top
44
+ * of it, and `onProgress('claiming')` is the app's cue to show a slow hint. Two calls at once
45
+ * on one node both mint or both create; serializing them is the app's.
46
+ *
47
+ * @throws {RentNodeError} `not-an-owner`, or `node-not-ready` once the container exists.
48
+ * @throws {ProviderApiError} when the provider refused a request (a lapsed subscription is `403 QUOTA_EXCEEDED`).
49
+ * @throws {ClaimRefusedError} or {PeerUnreachableError} from `claimNode`, as it throws them.
50
+ */
51
+ export async function rentNode(node, client, options = {}) {
52
+ await requireOwner(node);
53
+ const reach = await node.invitationReach();
54
+ log('renting a node for party %s; invitation reach %s → %s path', node.partyId, reach, reach === 'anywhere' ? 'invitation' : 'claim');
55
+ return reach === 'anywhere'
56
+ ? await rentByInvitation(node, client, options)
57
+ : await rentByClaim(node, client, options);
58
+ }
59
+ /**
60
+ * Resume a rental: for an app that was killed mid-flow or hit the node-code deadline.
61
+ *
62
+ * - a `stopped` or `error` container → `RentNodeError('container-gone')`;
63
+ * - a claim container not yet claimed → the code, then `claimNode`, as {@link rentNode} would;
64
+ * - a claim container claimed into this cadre → done. The provider withholds the code once a
65
+ * claim is folded, so the claim is not re-run; one claimed by another cadre's owner →
66
+ * `RentNodeError('claimed-by-another')`;
67
+ * - an invitation container → done while `pending` or `accepted` (the app watches `join`), or
68
+ * the failed outcome, after which the app deletes the container and rents again (the claim
69
+ * path is chosen then if the member that answered is gone).
70
+ */
71
+ export async function finishRental(node, client, containerId, options = {}) {
72
+ await requireOwner(node);
73
+ const report = await client.getContainer(containerId);
74
+ const kind = joinKindOf(report, containerId);
75
+ if (kind === 'claim') {
76
+ return await finishClaim(node, client, report, options);
77
+ }
78
+ const join = report.join;
79
+ if (join?.kind === 'invitation' && join.state === 'failed') {
80
+ return { containerId, outcome: 'failed', error: join.error, retryable: join.retryable };
81
+ }
82
+ return invitationResult(report.container);
83
+ }
84
+ async function requireOwner(node) {
85
+ if (!await node.isOwner())
86
+ throw new RentNodeError('not-an-owner');
87
+ }
88
+ async function rentByInvitation(node, client, options) {
89
+ report(options, 'inviting');
90
+ const { invitation, encoded } = await node.createCadreInvitation({ grantsOwner: false, uses: 1, expiresInMs: RENTAL_INVITATION_TTL_MS });
91
+ report(options, 'creating');
92
+ let container;
93
+ try {
94
+ container = await client.createContainer({ join: { kind: 'invitation', invitation: encoded }, profile: options.profile ?? 'storage' });
95
+ }
96
+ catch (error) {
97
+ await withdrawQuietly(node, invitation.invite.key);
98
+ throw error;
99
+ }
100
+ log('container %s created with an invitation; it joins by itself', container.id);
101
+ report(options, 'done');
102
+ return invitationResult(container);
103
+ }
104
+ /** A create that failed leaves no live invitation behind; a withdrawal that fails too is logged, and the create's error is what the caller sees. */
105
+ async function withdrawQuietly(node, key) {
106
+ try {
107
+ await node.withdrawCadreInvitation(key);
108
+ }
109
+ catch (error) {
110
+ log('withdrawing invitation %s after a failed create failed too: %o', key, error);
111
+ }
112
+ }
113
+ async function rentByClaim(node, client, options) {
114
+ report(options, 'creating');
115
+ const container = await client.createContainer({ join: { kind: 'claim' }, profile: options.profile ?? 'storage' });
116
+ log('container %s created for a claim', container.id);
117
+ return await claimContainer(node, client, container.id, options);
118
+ }
119
+ /** Poll for the node code, then claim; the second half of the claim path, shared with {@link finishRental}. */
120
+ async function claimContainer(node, client, containerId, options) {
121
+ report(options, 'waiting-for-code');
122
+ const { code, peerId } = await awaitNodeCode(client, containerId, options);
123
+ report(options, 'claiming');
124
+ // The claim is the owner's signature; the app asked the user before calling.
125
+ await node.claimNode(decodeNodeClaimPayload(code));
126
+ log('container %s claimed; its node is %s', containerId, peerId);
127
+ report(options, 'done');
128
+ return { containerId, join: 'claim', peerId };
129
+ }
130
+ /**
131
+ * `getNodeCode` every poll interval until it answers or the deadline passes. A container that
132
+ * dies meanwhile (`409 CONTAINER_GONE`) is `container-gone`, the same code `finishRental` gives
133
+ * one found dead, so an app handles both at one site.
134
+ */
135
+ async function awaitNodeCode(client, containerId, options) {
136
+ const deadline = Date.now() + (options.nodeCodeDeadlineMs ?? DEFAULT_NODE_CODE_DEADLINE_MS);
137
+ const poll = options.nodeCodePollMs ?? NODE_CODE_POLL_MS;
138
+ for (;;) {
139
+ const answer = await requestNodeCode(client, containerId);
140
+ if (!('notReady' in answer))
141
+ return answer;
142
+ if (Date.now() >= deadline) {
143
+ log('container %s has no node code by the deadline', containerId);
144
+ throw new RentNodeError('node-not-ready', containerId);
145
+ }
146
+ await sleep(poll);
147
+ }
148
+ }
149
+ async function requestNodeCode(client, containerId) {
150
+ try {
151
+ return await client.getNodeCode(containerId);
152
+ }
153
+ catch (error) {
154
+ if (error instanceof ProviderApiError && error.code === 'CONTAINER_GONE')
155
+ throw new RentNodeError('container-gone', containerId);
156
+ throw error;
157
+ }
158
+ }
159
+ async function finishClaim(node, client, report, options) {
160
+ const { container, join } = report;
161
+ if (join?.kind === 'claim' && join.state === 'claimed')
162
+ return judgeClaimed(node, report);
163
+ try {
164
+ return await claimContainer(node, client, container.id, options);
165
+ }
166
+ catch (error) {
167
+ // The node was claimed between the read and the code request; judge the fresh record.
168
+ if (error instanceof ProviderApiError && error.code === 'ALREADY_CLAIMED') {
169
+ return judgeClaimed(node, await client.getContainer(container.id));
170
+ }
171
+ throw error;
172
+ }
173
+ }
174
+ /**
175
+ * A claimed container is this cadre's when the claim named this party.
176
+ *
177
+ * NOTE: the row `claimNode` writes after the node accepts is not re-run here, because the
178
+ * provider withholds the code once the claim is folded. An app killed between the node
179
+ * accepting and that one local insert has a node that belongs to this owner with no row for it
180
+ * on this machine, the window the React Native app's accepted tradeoff for cadre-host claims
181
+ * names; the remedy is to delete the container and rent again. Revisit if a device run sees it.
182
+ */
183
+ function judgeClaimed(node, report) {
184
+ const { container, join } = report;
185
+ if (join?.kind !== 'claim' || join.state !== 'claimed')
186
+ throw new RentNodeError('container-gone', container.id);
187
+ if (join.partyId !== node.partyId) {
188
+ log('container %s was claimed into another party', container.id);
189
+ throw new RentNodeError('claimed-by-another', container.id);
190
+ }
191
+ return { containerId: container.id, join: 'claim', ...(container.peerId ? { peerId: container.peerId } : {}) };
192
+ }
193
+ /** Which path a container is on; refuses one that is gone or was never a rental. */
194
+ function joinKindOf(report, containerId) {
195
+ const { container, join } = report;
196
+ if (container.status === 'stopped' || container.status === 'error')
197
+ throw new RentNodeError('container-gone', containerId);
198
+ // The join state is folded from the node's first `/status` read, so a container still
199
+ // starting carries only the kind it was created with.
200
+ const kind = join?.kind ?? container.join?.kind;
201
+ if (kind === undefined)
202
+ throw new RentNodeError('not-a-rental', containerId);
203
+ return kind;
204
+ }
205
+ function invitationResult(container) {
206
+ return { containerId: container.id, join: 'invitation', ...(container.peerId ? { peerId: container.peerId } : {}) };
207
+ }
208
+ function report(options, step) {
209
+ try {
210
+ options.onProgress?.(step);
211
+ }
212
+ catch (error) {
213
+ log('onProgress(%s) threw; ignored: %o', step, error);
214
+ }
215
+ }
216
+ function sleep(ms) {
217
+ // A plain timer, not an unref'd one: a Node process renting from a script must stay up for it.
218
+ return new Promise((resolve) => setTimeout(resolve, ms));
219
+ }
220
+ //# sourceMappingURL=rent-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rent-node.js","sourceRoot":"","sources":["../../src/provider/rent-node.ts"],"names":[],"mappings":"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"}
@@ -50,7 +50,10 @@
50
50
  * only once the relay-hop protocol id is in that peer's peer-store protocol list,
51
51
  * and that list is written exclusively by IDENTIFY — which `@optimystic/db-p2p`
52
52
  * namespaces per network, so a cadre node and a stock relay never identify each
53
- * other. See `relay-reservation.ts`'s module doc and `docs/architecture.md`.
53
+ * other. A node with configured relays is built with that discovery switched off
54
+ * altogether when it has a transport list to wrap (`node-transports.ts`), because it
55
+ * CAN reach a relay-capable peer in its own namespace and take the configured relay's
56
+ * slot. See `relay-reservation.ts`'s module doc and `docs/architecture.md`.
54
57
  */
55
58
  import type { NetworkConfig } from './types.js';
56
59
  import type { RelayReservationState } from './relay-reservation.js';
@@ -136,9 +139,11 @@ export interface ListenTransportOptions {
136
139
  *
137
140
  * - **WebSocket is DERIVED.** It is the one non-TCP transport the shipped configs need
138
141
  * (the React Native reference app's companion drone; `cadre start --ws-port`), and
139
- * `@optimystic/db-p2p` already knows how to add it. `cadre-core` grows no dependency
140
- * and decides no transport policy — it flips db-p2p's own switch
141
- * ({@link WS_TRANSPORT_SWITCH_PORT}).
142
+ * `@optimystic/db-p2p` already knows how to add it, so `cadre-core` flips db-p2p's own
143
+ * switch ({@link WS_TRANSPORT_SWITCH_PORT}). A node with configured relays is the one
144
+ * case where cadre passes the transport list itself (`node-transports.ts`, to switch
145
+ * relay discovery off); when the embedder gave none, that list comes from
146
+ * `network.defaultTransports`, which reads the same switch.
142
147
  * - **Everything else is REFUSED.** Deriving `/quic-v1`, `/webrtc` or `/webtransport`
143
148
  * would mean importing transport packages into every `cadre-core` consumer including
144
149
  * the React Native and browser bundles (against the cross-platform rule in
@@ -50,7 +50,10 @@
50
50
  * only once the relay-hop protocol id is in that peer's peer-store protocol list,
51
51
  * and that list is written exclusively by IDENTIFY — which `@optimystic/db-p2p`
52
52
  * namespaces per network, so a cadre node and a stock relay never identify each
53
- * other. See `relay-reservation.ts`'s module doc and `docs/architecture.md`.
53
+ * other. A node with configured relays is built with that discovery switched off
54
+ * altogether when it has a transport list to wrap (`node-transports.ts`), because it
55
+ * CAN reach a relay-capable peer in its own namespace and take the configured relay's
56
+ * slot. See `relay-reservation.ts`'s module doc and `docs/architecture.md`.
54
57
  */
55
58
  import { multiaddr } from '@multiformats/multiaddr';
56
59
  import { circuitRelayTargetOrThrow } from './delegate-admission.js';
@@ -169,9 +172,11 @@ const WS_TRANSPORT_SWITCH_PORT = 0;
169
172
  *
170
173
  * - **WebSocket is DERIVED.** It is the one non-TCP transport the shipped configs need
171
174
  * (the React Native reference app's companion drone; `cadre start --ws-port`), and
172
- * `@optimystic/db-p2p` already knows how to add it. `cadre-core` grows no dependency
173
- * and decides no transport policy — it flips db-p2p's own switch
174
- * ({@link WS_TRANSPORT_SWITCH_PORT}).
175
+ * `@optimystic/db-p2p` already knows how to add it, so `cadre-core` flips db-p2p's own
176
+ * switch ({@link WS_TRANSPORT_SWITCH_PORT}). A node with configured relays is the one
177
+ * case where cadre passes the transport list itself (`node-transports.ts`, to switch
178
+ * relay discovery off); when the embedder gave none, that list comes from
179
+ * `network.defaultTransports`, which reads the same switch.
175
180
  * - **Everything else is REFUSED.** Deriving `/quic-v1`, `/webrtc` or `/webtransport`
176
181
  * would mean importing transport packages into every `cadre-core` consumer including
177
182
  * the React Native and browser bundles (against the cross-platform rule in
@@ -1 +1 @@
1
- {"version":3,"file":"relay-addrs.js","sourceRoot":"","sources":["../src/relay-addrs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay-addrs.js","sourceRoot":"","sources":["../src/relay-addrs.ts"],"names":[],"mappings":"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"}
@@ -73,12 +73,33 @@
73
73
  * relay's address. So it asks the relay for a slot directly, via the
74
74
  * circuit-relay transport's own reservation store.
75
75
  *
76
+ * ⚠️ WHY LIBP2P'S RELAY DISCOVERY IS SWITCHED OFF. Discovery does reach SOME
77
+ * peers: every connected peer whose identify lists the relay-hop protocol, and a
78
+ * storage-profile member's strand node runs the relay server in the same
79
+ * `/optimystic/strand-<id>/…` namespace as this node's strand node (control nodes
80
+ * share `/optimystic/control-<partyId>/…` the same way). Discovery reserves on such
81
+ * a peer as soon as it connects, and that takes one of the slots the configured
82
+ * relays' supervisors were going to fill. Worse, the reservation store runs one
83
+ * request at a time and, when a request takes the last free slot, clears its queue
84
+ * with `@libp2p/utils`' `Queue.clear()`, which drops a queued job WITHOUT ever
85
+ * settling its promise — so a supervisor whose `addRelay` was queued behind it
86
+ * waits out its whole deadline (gotchoices/sereus#34). A reservation discovery picks is
87
+ * also unmanaged: no supervisor re-drives it, the status does not count it, and a
88
+ * member's strand node goes away whenever that member hibernates. So every node
89
+ * whose bare search entries cadre generated for configured relays is built through
90
+ * {@link withoutRelayDiscovery} (`node-transports.ts`), and its slots are filled by
91
+ * the supervisors alone — given a transport list to wrap, which React Native and
92
+ * browser embedders always pass and a Node host passes as
93
+ * `network.defaultTransports` (cadre-cli does). A hand-written bare `/p2p-circuit`
94
+ * with no configured relay keeps discovery, since nothing else would fill that slot.
95
+ *
76
96
  * Status is derived from the node's LIVE multiaddrs on every read rather than
77
97
  * cached at drive time: when a relay restarts or the connection drops, libp2p's
78
98
  * listener clears its listening addrs and the circuit multiaddr disappears, so a
79
99
  * cached snapshot would keep claiming `reserved` for a node nothing can dial.
80
100
  *
81
- * And because discovery is out of reach, nothing would ever ASK again either —
101
+ * And because discovery is either off or blind to the relay, nothing would ever
102
+ * ASK again either —
82
103
  * so {@link driveRelayReservation} stays a single-shot primitive and
83
104
  * {@link superviseRelayReservation} owns the retry cadence on top of it. That
84
105
  * loop is what makes a lost reservation recover without a page reload. Single-shot
@@ -87,20 +108,20 @@
87
108
  * logs failures that read as real and — through the timers it still holds — keeps
88
109
  * the process alive for the rest of its `timeoutMs`.
89
110
  *
90
- * One measured refinement to "out of reach": libp2p 3.1.3's `connection.newStream`
91
- * records every protocol OUR OWN outbound stream negotiated into the peer store, so
92
- * the explicit reservation request writes the relay-hop protocol id against the
93
- * relay itself. From then on, discovery CAN refill a freed slot on its own — but
94
- * only when the relay answers at that moment (a hangup while the relay stays up;
95
- * measured at ~25 ms). A relay that is down when the slot frees fails that one
96
- * discovery attempt, which poisons the store's `relayFilter` against it, and
97
- * nothing in libp2p tries again: a relay RESTART still needs the supervisor, which
98
- * un-poisons the filter before every attempt ({@link clearRelayFilterEntry}). Two
99
- * mechanisms in production, one guarantee; the specs disable discovery's half
111
+ * What switching discovery off costs: libp2p 3.1.3's `connection.newStream` records
112
+ * every protocol OUR OWN outbound stream negotiated into the peer store, so the
113
+ * explicit reservation request writes the relay-hop protocol id against the relay
114
+ * itself, and with discovery on it refilled a slot freed by a hangup (while the
115
+ * relay stays up) within ~25 ms, measured. With it off that refill waits for the
116
+ * supervisor's next liveness check ({@link DEFAULT_RELAY_CHECK_MS}, 5 s while held).
117
+ * A relay RESTART always needed the supervisor, which un-poisons the store's
118
+ * `relayFilter` before every attempt ({@link clearRelayFilterEntry}). The specs
119
+ * that build their own clients keep discovery on and disable its refill
100
120
  * (`forgetRelayProtocol` in `relay-reservation.spec.ts`) so they prove the
101
121
  * supervisor's.
102
122
  */
103
123
  import type { Libp2p, PeerId } from '@libp2p/interface';
124
+ import type { Libp2pTransports } from '@optimystic/db-p2p';
104
125
  /**
105
126
  * Relay-reservation posture for a node that reserves through the SEARCH listen
106
127
  * addr (bare `/p2p-circuit`) — every cadre control node, whether its relays came
@@ -232,6 +253,19 @@ export declare function circuitMultiaddrsVia(node: Libp2p, addrs: readonly strin
232
253
  * module to the discovery wait that never fires.
233
254
  */
234
255
  export declare function findCircuitRelayTransport(node: Libp2p): CircuitRelayTransportLike | null;
256
+ /**
257
+ * `transports` with libp2p's relay discovery switched off in the circuit-relay transport any of
258
+ * them builds, for a node whose bare `/p2p-circuit` slots cadre's own supervisors fill (see the
259
+ * module header, "WHY LIBP2P'S RELAY DISCOVERY IS SWITCHED OFF"). Every other transport is built
260
+ * untouched.
261
+ *
262
+ * Each factory is wrapped rather than the built node patched afterwards: libp2p calls the
263
+ * factories in its constructor, and `@optimystic/db-p2p` builds with `start: false`, so the
264
+ * switch lands before anything starts. Patching the running node instead races — db-p2p starts
265
+ * the node before `createLibp2pNode` returns, and a cohort-seed connection in that window has
266
+ * already queued a discovered reservation.
267
+ */
268
+ export declare function withoutRelayDiscovery(transports: Libp2pTransports): Libp2pTransports;
235
269
  /**
236
270
  * Dial every relay in `addrs`, ask the first one that answers for a reservation
237
271
  * slot, then wait until a `/p2p-circuit` address appears.
@@ -317,8 +351,9 @@ export interface RelayReservationSupervisor {
317
351
  * it still is — the thing that makes a lost reservation recover on its own.
318
352
  *
319
353
  * Needed because nothing else re-drives: libp2p re-fills a search listener's
320
- * freed slot from relay DISCOVERY, and a cadre node's namespaced identify puts
321
- * discovery permanently out of reach (see the module header). Without this loop a
354
+ * freed slot from relay DISCOVERY, which cannot see a stock-identify relay from a
355
+ * cadre node's namespaced identify, and which is switched off outright on every
356
+ * node whose slots cadre supervises (see the module header). Without this loop a
322
357
  * relay restart left a browser tab undialable until the user reloaded the page.
323
358
  *
324
359
  * Per tick:
@@ -378,9 +413,9 @@ export declare function clearRelayFilterEntry(store: RelayReservationStoreLike,
378
413
  * NOBODY IS GOING TO TRY AGAIN. A lost reservation does come back on its own now,
379
414
  * as long as a {@link RelayReservationSupervisor} is running — that loop is the
380
415
  * only thing that re-drives, since libp2p re-fills a search listener's freed slot
381
- * from relay DISCOVERY, which a cadre node's namespaced identify puts permanently
382
- * out of reach (see the module header). So `error` means the supervisor was
383
- * stopped, was never started, or there is no node at all.
416
+ * from relay DISCOVERY, which cannot see a stock-identify relay and is switched off
417
+ * on every node cadre supervises (see the module header). So `error` means the
418
+ * supervisor was stopped, was never started, or there is no node at all.
384
419
  *
385
420
  * `retryAtMs` is the supervisor's next scheduled tick (`null` while a drive runs
386
421
  * or when there is no supervisor); it both selects `retrying` and is passed
@@ -73,12 +73,33 @@
73
73
  * relay's address. So it asks the relay for a slot directly, via the
74
74
  * circuit-relay transport's own reservation store.
75
75
  *
76
+ * ⚠️ WHY LIBP2P'S RELAY DISCOVERY IS SWITCHED OFF. Discovery does reach SOME
77
+ * peers: every connected peer whose identify lists the relay-hop protocol, and a
78
+ * storage-profile member's strand node runs the relay server in the same
79
+ * `/optimystic/strand-<id>/…` namespace as this node's strand node (control nodes
80
+ * share `/optimystic/control-<partyId>/…` the same way). Discovery reserves on such
81
+ * a peer as soon as it connects, and that takes one of the slots the configured
82
+ * relays' supervisors were going to fill. Worse, the reservation store runs one
83
+ * request at a time and, when a request takes the last free slot, clears its queue
84
+ * with `@libp2p/utils`' `Queue.clear()`, which drops a queued job WITHOUT ever
85
+ * settling its promise — so a supervisor whose `addRelay` was queued behind it
86
+ * waits out its whole deadline (gotchoices/sereus#34). A reservation discovery picks is
87
+ * also unmanaged: no supervisor re-drives it, the status does not count it, and a
88
+ * member's strand node goes away whenever that member hibernates. So every node
89
+ * whose bare search entries cadre generated for configured relays is built through
90
+ * {@link withoutRelayDiscovery} (`node-transports.ts`), and its slots are filled by
91
+ * the supervisors alone — given a transport list to wrap, which React Native and
92
+ * browser embedders always pass and a Node host passes as
93
+ * `network.defaultTransports` (cadre-cli does). A hand-written bare `/p2p-circuit`
94
+ * with no configured relay keeps discovery, since nothing else would fill that slot.
95
+ *
76
96
  * Status is derived from the node's LIVE multiaddrs on every read rather than
77
97
  * cached at drive time: when a relay restarts or the connection drops, libp2p's
78
98
  * listener clears its listening addrs and the circuit multiaddr disappears, so a
79
99
  * cached snapshot would keep claiming `reserved` for a node nothing can dial.
80
100
  *
81
- * And because discovery is out of reach, nothing would ever ASK again either —
101
+ * And because discovery is either off or blind to the relay, nothing would ever
102
+ * ASK again either —
82
103
  * so {@link driveRelayReservation} stays a single-shot primitive and
83
104
  * {@link superviseRelayReservation} owns the retry cadence on top of it. That
84
105
  * loop is what makes a lost reservation recover without a page reload. Single-shot
@@ -87,23 +108,22 @@
87
108
  * logs failures that read as real and — through the timers it still holds — keeps
88
109
  * the process alive for the rest of its `timeoutMs`.
89
110
  *
90
- * One measured refinement to "out of reach": libp2p 3.1.3's `connection.newStream`
91
- * records every protocol OUR OWN outbound stream negotiated into the peer store, so
92
- * the explicit reservation request writes the relay-hop protocol id against the
93
- * relay itself. From then on, discovery CAN refill a freed slot on its own — but
94
- * only when the relay answers at that moment (a hangup while the relay stays up;
95
- * measured at ~25 ms). A relay that is down when the slot frees fails that one
96
- * discovery attempt, which poisons the store's `relayFilter` against it, and
97
- * nothing in libp2p tries again: a relay RESTART still needs the supervisor, which
98
- * un-poisons the filter before every attempt ({@link clearRelayFilterEntry}). Two
99
- * mechanisms in production, one guarantee; the specs disable discovery's half
111
+ * What switching discovery off costs: libp2p 3.1.3's `connection.newStream` records
112
+ * every protocol OUR OWN outbound stream negotiated into the peer store, so the
113
+ * explicit reservation request writes the relay-hop protocol id against the relay
114
+ * itself, and with discovery on it refilled a slot freed by a hangup (while the
115
+ * relay stays up) within ~25 ms, measured. With it off that refill waits for the
116
+ * supervisor's next liveness check ({@link DEFAULT_RELAY_CHECK_MS}, 5 s while held).
117
+ * A relay RESTART always needed the supervisor, which un-poisons the store's
118
+ * `relayFilter` before every attempt ({@link clearRelayFilterEntry}). The specs
119
+ * that build their own clients keep discovery on and disable its refill
100
120
  * (`forgetRelayProtocol` in `relay-reservation.spec.ts`) so they prove the
101
121
  * supervisor's.
102
122
  */
103
123
  import debug from 'debug';
104
124
  import { multiaddr } from '@multiformats/multiaddr';
105
125
  import { peerIdFromString } from '@libp2p/peer-id';
106
- import { trailingPeerId } from './peer-record.js';
126
+ import { trailingPeerId } from './peer-dial.js';
107
127
  import { relayReservationBudgetMs } from './link-budget.js';
108
128
  const log = debug('sereus:cadre:relay-reservation');
109
129
  /**
@@ -218,6 +238,54 @@ function isReservationStore(value) {
218
238
  const store = value;
219
239
  return typeof store.addRelay === 'function' && typeof store.hasReservation === 'function';
220
240
  }
241
+ /**
242
+ * `transports` with libp2p's relay discovery switched off in the circuit-relay transport any of
243
+ * them builds, for a node whose bare `/p2p-circuit` slots cadre's own supervisors fill (see the
244
+ * module header, "WHY LIBP2P'S RELAY DISCOVERY IS SWITCHED OFF"). Every other transport is built
245
+ * untouched.
246
+ *
247
+ * Each factory is wrapped rather than the built node patched afterwards: libp2p calls the
248
+ * factories in its constructor, and `@optimystic/db-p2p` builds with `start: false`, so the
249
+ * switch lands before anything starts. Patching the running node instead races — db-p2p starts
250
+ * the node before `createLibp2pNode` returns, and a cohort-seed connection in that window has
251
+ * already queued a discovered reservation.
252
+ */
253
+ export function withoutRelayDiscovery(transports) {
254
+ return transports.map((factory) => (components) => switchOffRelayDiscovery(factory(components)));
255
+ }
256
+ /** Logged once per process, not per node: every node in it runs the same `@libp2p/circuit-relay-v2`. */
257
+ let reportedDiscoveryFieldMissing = false;
258
+ /**
259
+ * Clear `transport`'s `discovery` when it is the circuit-relay transport — duck-typed by its
260
+ * reservation store, as {@link findCircuitRelayTransport} finds it.
261
+ *
262
+ * `@libp2p/circuit-relay-v2` 4.2.13 has no init option for this, but declares the field optional
263
+ * and guards every use (`this.discovery?.startDiscovery()`, `start`/`stop` skip a non-startable
264
+ * value), so `undefined` before `start()` registers no relay-hop topology and never runs a
265
+ * peer-store scan.
266
+ */
267
+ function switchOffRelayDiscovery(transport) {
268
+ if (typeof transport !== 'object' || transport === null) {
269
+ return transport;
270
+ }
271
+ const candidate = transport;
272
+ if (!isReservationStore(candidate.reservationStore)) {
273
+ return transport;
274
+ }
275
+ if (!Object.prototype.hasOwnProperty.call(candidate, 'discovery')) {
276
+ // Not thrown: a node with discovery left on still works, it is only exposed to the slot theft
277
+ // again — which the `withoutRelayDiscovery` spec in `relay-reservation.spec.ts` reproduces.
278
+ if (!reportedDiscoveryFieldMissing) {
279
+ reportedDiscoveryFieldMissing = true;
280
+ console.warn('cadre: the circuit-relay transport has no `discovery` field to switch off — the ' +
281
+ '@libp2p/circuit-relay-v2 shape changed. libp2p relay discovery stays on, and it can take a ' +
282
+ 'configured relay\'s reservation slot.');
283
+ }
284
+ return transport;
285
+ }
286
+ candidate.discovery = undefined;
287
+ return transport;
288
+ }
221
289
  /** Sentinel resolved by the wait arm of {@link requestOneReservation}'s race. */
222
290
  const WAIT_OVER = Symbol('relay-reservation-wait-over');
223
291
  /**
@@ -314,10 +382,12 @@ export async function driveRelayReservation(node, addrs, opts) {
314
382
  const { connected, error: dialError } = await dialRelays(node, addrs, deadline, signal);
315
383
  const attempt = await requestReservation(node, connected, deadline, signal);
316
384
  // NOTE: a REJECTED reservation still spends the rest of the deadline polling,
317
- // because discovery may independently land one (only the no-transport case is
318
- // `fatal`). So a misconfigured node reports its now-legible reason a full
319
- // `timeoutMs` late. If startup latency on that path ever matters, shorten the
320
- // wait once every connected relay has rejected — do not skip it outright.
385
+ // because on a node that kept libp2p's relay discovery (a hand-written bare
386
+ // `/p2p-circuit` driven through `reserveRelays`) discovery may independently land
387
+ // one (only the no-transport case is `fatal`). So a misconfigured node reports its
388
+ // now-legible reason a full `timeoutMs` late. If startup latency on that path ever
389
+ // matters, shorten the wait once every connected relay has rejected — do not skip
390
+ // it outright.
321
391
  if (!attempt.fatal && (await waitForCircuitReservation(node, addrs, deadline, pollMs, signal))) {
322
392
  return { error: null, cancelled: false };
323
393
  }
@@ -410,9 +480,10 @@ function firstDialError(addrs, results) {
410
480
  *
411
481
  * Sequential, not concurrent: a bare `/p2p-circuit` listener registers exactly
412
482
  * ONE pending reservation, so a second concurrent `addRelay` would be rejected
413
- * with `HadEnoughRelaysError` — noise, not a failure. Running discovery is not a
414
- * conflict either; `addRelay` dedupes through its own peer queue, so a relay that
415
- * genuinely is discoverable still reserves exactly once.
483
+ * with `HadEnoughRelaysError` — noise, not a failure. Discovery naming the SAME
484
+ * relay is not a conflict (`addRelay` dedupes through its own peer queue); discovery
485
+ * naming a DIFFERENT peer is, which is why it is off on every node cadre supervises
486
+ * (module header).
416
487
  */
417
488
  async function requestReservation(node, connected, deadline, signal) {
418
489
  if (connected.length === 0) {
@@ -459,6 +530,17 @@ async function requestReservation(node, connected, deadline, signal) {
459
530
  * race settling some other way, aborted in the `finally`. Out-racing the deadline
460
531
  * timer is not enough: the loser stays pending, and a pending timer is what holds
461
532
  * a Node process open past the stop that asked for it.
533
+ *
534
+ * NOTE: the reservation store's `Queue.clear()` drops a queued job unsettled whenever
535
+ * a reservation takes the last pending slot. With relay discovery off, the slots never
536
+ * outnumber the supervised relays, so a first request cannot be dropped that way. A
537
+ * REFRESH of a held reservation that is queued at the moment another relay's first
538
+ * reservation takes the last slot still is, and that reservation then lapses at the
539
+ * relay unrefreshed. If that is ever observed, or anything besides the supervisors
540
+ * gains a way to fill these slots, also end this wait on the store's
541
+ * `relay:found-enough-relays` event while this relay holds no reservation. The real
542
+ * fix — `#checkReservationCount` aborting queued jobs instead of clearing them —
543
+ * belongs to js-libp2p.
462
544
  */
463
545
  async function requestOneReservation(store, relay, deadline, signal) {
464
546
  const waitOver = new AbortController();
@@ -554,8 +636,9 @@ export const DEFAULT_RELAY_MAX_BACKOFF_MS = 60000;
554
636
  * it still is — the thing that makes a lost reservation recover on its own.
555
637
  *
556
638
  * Needed because nothing else re-drives: libp2p re-fills a search listener's
557
- * freed slot from relay DISCOVERY, and a cadre node's namespaced identify puts
558
- * discovery permanently out of reach (see the module header). Without this loop a
639
+ * freed slot from relay DISCOVERY, which cannot see a stock-identify relay from a
640
+ * cadre node's namespaced identify, and which is switched off outright on every
641
+ * node whose slots cadre supervises (see the module header). Without this loop a
559
642
  * relay restart left a browser tab undialable until the user reloaded the page.
560
643
  *
561
644
  * Per tick:
@@ -815,9 +898,9 @@ class RelayReservationLoop {
815
898
  * NOBODY IS GOING TO TRY AGAIN. A lost reservation does come back on its own now,
816
899
  * as long as a {@link RelayReservationSupervisor} is running — that loop is the
817
900
  * only thing that re-drives, since libp2p re-fills a search listener's freed slot
818
- * from relay DISCOVERY, which a cadre node's namespaced identify puts permanently
819
- * out of reach (see the module header). So `error` means the supervisor was
820
- * stopped, was never started, or there is no node at all.
901
+ * from relay DISCOVERY, which cannot see a stock-identify relay and is switched off
902
+ * on every node cadre supervises (see the module header). So `error` means the
903
+ * supervisor was stopped, was never started, or there is no node at all.
821
904
  *
822
905
  * `retryAtMs` is the supervisor's next scheduled tick (`null` while a drive runs
823
906
  * or when there is no supervisor); it both selects `retrying` and is passed