@serfab/cadre-core 0.9.0 → 0.11.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 (193) hide show
  1. package/README.md +255 -253
  2. package/dist/bootstrap-peer-store-file.d.ts +13 -0
  3. package/dist/bootstrap-peer-store-file.js +42 -0
  4. package/dist/bootstrap-peer-store-file.js.map +1 -0
  5. package/dist/bootstrap-peer-store.d.ts +73 -0
  6. package/dist/bootstrap-peer-store.js +128 -0
  7. package/dist/bootstrap-peer-store.js.map +1 -0
  8. package/dist/cadre-node.d.ts +1121 -81
  9. package/dist/cadre-node.js +2276 -262
  10. package/dist/cadre-node.js.map +1 -1
  11. package/dist/control-authorization.d.ts +93 -0
  12. package/dist/control-authorization.js +57 -0
  13. package/dist/control-authorization.js.map +1 -0
  14. package/dist/control-cohort.d.ts +26 -0
  15. package/dist/control-cohort.js +26 -0
  16. package/dist/control-cohort.js.map +1 -1
  17. package/dist/control-database.d.ts +959 -54
  18. package/dist/control-database.js +1421 -148
  19. package/dist/control-database.js.map +1 -1
  20. package/dist/control-formation-recorder.d.ts +90 -10
  21. package/dist/control-formation-recorder.js +130 -14
  22. package/dist/control-formation-recorder.js.map +1 -1
  23. package/dist/control-schema.d.ts +1 -1
  24. package/dist/control-schema.js +651 -72
  25. package/dist/control-schema.js.map +1 -1
  26. package/dist/control-stream.d.ts +38 -4
  27. package/dist/control-stream.js +70 -4
  28. package/dist/control-stream.js.map +1 -1
  29. package/dist/control-write-retry.d.ts +161 -0
  30. package/dist/control-write-retry.js +412 -0
  31. package/dist/control-write-retry.js.map +1 -0
  32. package/dist/delegate-admission.d.ts +134 -0
  33. package/dist/delegate-admission.js +244 -0
  34. package/dist/delegate-admission.js.map +1 -0
  35. package/dist/device-token.d.ts +11 -11
  36. package/dist/device-token.js +14 -13
  37. package/dist/device-token.js.map +1 -1
  38. package/dist/ed25519-key.d.ts +26 -0
  39. package/dist/ed25519-key.js +56 -1
  40. package/dist/ed25519-key.js.map +1 -1
  41. package/dist/file-durable-slot.d.ts +20 -0
  42. package/dist/file-durable-slot.js +53 -0
  43. package/dist/file-durable-slot.js.map +1 -0
  44. package/dist/formation-approval.d.ts +157 -0
  45. package/dist/formation-approval.js +375 -0
  46. package/dist/formation-approval.js.map +1 -0
  47. package/dist/fs-atomic.d.ts +38 -0
  48. package/dist/fs-atomic.js +97 -0
  49. package/dist/fs-atomic.js.map +1 -0
  50. package/dist/identity-key.d.ts +49 -0
  51. package/dist/identity-key.js +81 -0
  52. package/dist/identity-key.js.map +1 -0
  53. package/dist/index.d.ts +17 -7
  54. package/dist/index.js +40 -6
  55. package/dist/index.js.map +1 -1
  56. package/dist/key-store-file.d.ts +2 -24
  57. package/dist/key-store-file.js +10 -77
  58. package/dist/key-store-file.js.map +1 -1
  59. package/dist/membership-connection-gater.d.ts +146 -0
  60. package/dist/membership-connection-gater.js +180 -0
  61. package/dist/membership-connection-gater.js.map +1 -0
  62. package/dist/node-local-snapshot.d.ts +109 -0
  63. package/dist/node-local-snapshot.js +182 -0
  64. package/dist/node-local-snapshot.js.map +1 -0
  65. package/dist/peer-addr-book.d.ts +92 -0
  66. package/dist/peer-addr-book.js +220 -0
  67. package/dist/peer-addr-book.js.map +1 -0
  68. package/dist/peer-authorization.d.ts +135 -18
  69. package/dist/peer-authorization.js +152 -21
  70. package/dist/peer-authorization.js.map +1 -1
  71. package/dist/peer-record.d.ts +21 -8
  72. package/dist/peer-record.js +26 -10
  73. package/dist/peer-record.js.map +1 -1
  74. package/dist/relay-addrs.d.ts +45 -0
  75. package/dist/relay-addrs.js +103 -0
  76. package/dist/relay-addrs.js.map +1 -0
  77. package/dist/relay-reservation.d.ts +266 -0
  78. package/dist/relay-reservation.js +613 -0
  79. package/dist/relay-reservation.js.map +1 -0
  80. package/dist/seed-bootstrap.d.ts +228 -56
  81. package/dist/seed-bootstrap.js +340 -198
  82. package/dist/seed-bootstrap.js.map +1 -1
  83. package/dist/seed-trust-policy.d.ts +47 -21
  84. package/dist/seed-trust-policy.js +24 -15
  85. package/dist/seed-trust-policy.js.map +1 -1
  86. package/dist/strand-addr-protocol.d.ts +42 -13
  87. package/dist/strand-addr-protocol.js +58 -52
  88. package/dist/strand-addr-protocol.js.map +1 -1
  89. package/dist/strand-backfill.d.ts +109 -0
  90. package/dist/strand-backfill.js +279 -0
  91. package/dist/strand-backfill.js.map +1 -0
  92. package/dist/strand-cohort.d.ts +4 -37
  93. package/dist/strand-cohort.js +4 -15
  94. package/dist/strand-cohort.js.map +1 -1
  95. package/dist/strand-database.d.ts +11 -16
  96. package/dist/strand-database.js +19 -7
  97. package/dist/strand-database.js.map +1 -1
  98. package/dist/strand-formation-manager.d.ts +36 -7
  99. package/dist/strand-formation-manager.js +115 -15
  100. package/dist/strand-formation-manager.js.map +1 -1
  101. package/dist/strand-formation-protocol.d.ts +103 -4
  102. package/dist/strand-formation-protocol.js +232 -4
  103. package/dist/strand-formation-protocol.js.map +1 -1
  104. package/dist/strand-instance-manager.d.ts +88 -19
  105. package/dist/strand-instance-manager.js +178 -34
  106. package/dist/strand-instance-manager.js.map +1 -1
  107. package/dist/strand-member-registry.d.ts +5 -5
  108. package/dist/strand-member-registry.js +18 -10
  109. package/dist/strand-member-registry.js.map +1 -1
  110. package/dist/strand-membership-writer.d.ts +484 -88
  111. package/dist/strand-membership-writer.js +751 -128
  112. package/dist/strand-membership-writer.js.map +1 -1
  113. package/dist/strand-solicitation.d.ts +99 -33
  114. package/dist/strand-solicitation.js +100 -52
  115. package/dist/strand-solicitation.js.map +1 -1
  116. package/dist/strand-transport-key.d.ts +40 -0
  117. package/dist/strand-transport-key.js +56 -0
  118. package/dist/strand-transport-key.js.map +1 -0
  119. package/dist/strand-wake-protocol.d.ts +9 -7
  120. package/dist/strand-wake-protocol.js +22 -46
  121. package/dist/strand-wake-protocol.js.map +1 -1
  122. package/dist/strand-watcher.d.ts +17 -2
  123. package/dist/strand-watcher.js +47 -3
  124. package/dist/strand-watcher.js.map +1 -1
  125. package/dist/trusted-owner-store-file.d.ts +12 -0
  126. package/dist/trusted-owner-store-file.js +39 -0
  127. package/dist/trusted-owner-store-file.js.map +1 -0
  128. package/dist/trusted-owner-store.d.ts +64 -0
  129. package/dist/trusted-owner-store.js +126 -0
  130. package/dist/trusted-owner-store.js.map +1 -0
  131. package/dist/types.d.ts +307 -44
  132. package/dist/types.js +13 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +20 -8
  135. package/src/arachnode-stub.ts +159 -159
  136. package/src/bootstrap-peer-store-file.ts +44 -0
  137. package/src/bootstrap-peer-store.ts +172 -0
  138. package/src/cadre-node.ts +5158 -2981
  139. package/src/canonical-json.ts +30 -30
  140. package/src/control-authorization.ts +114 -0
  141. package/src/control-cohort.ts +130 -103
  142. package/src/control-database.ts +2406 -948
  143. package/src/control-formation-recorder.ts +305 -151
  144. package/src/control-schema.ts +651 -72
  145. package/src/control-stream.ts +205 -128
  146. package/src/control-write-retry.ts +483 -0
  147. package/src/delegate-admission.ts +302 -0
  148. package/src/device-token.ts +94 -89
  149. package/src/diagnostics/connection-path.ts +277 -277
  150. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  151. package/src/ed25519-key.ts +66 -1
  152. package/src/enrollment.ts +199 -199
  153. package/src/file-durable-slot.ts +61 -0
  154. package/src/formation-approval.ts +604 -0
  155. package/src/fs-atomic.ts +107 -0
  156. package/src/hibernation-manager.ts +437 -437
  157. package/src/identity-key.ts +109 -0
  158. package/src/index.ts +137 -5
  159. package/src/key-store-file.ts +132 -203
  160. package/src/key-store.ts +99 -99
  161. package/src/membership-connection-gater.ts +204 -0
  162. package/src/node-local-snapshot.ts +260 -0
  163. package/src/peer-addr-book.ts +250 -0
  164. package/src/peer-authorization.ts +259 -81
  165. package/src/peer-record.ts +164 -146
  166. package/src/push-credentials.ts +84 -84
  167. package/src/push-node.ts +64 -64
  168. package/src/push-notifier-apns.ts +247 -247
  169. package/src/push-notifier-fcm.ts +220 -220
  170. package/src/push-notifier-shared.ts +28 -28
  171. package/src/push-notifier.ts +52 -52
  172. package/src/relay-addrs.ts +111 -0
  173. package/src/relay-reservation.ts +823 -0
  174. package/src/seed-bootstrap.ts +409 -209
  175. package/src/seed-trust-policy.ts +140 -107
  176. package/src/strand-addr-protocol.ts +441 -410
  177. package/src/strand-backfill.ts +366 -0
  178. package/src/strand-cohort.ts +38 -77
  179. package/src/strand-database.ts +202 -204
  180. package/src/strand-formation-manager.ts +491 -368
  181. package/src/strand-formation-protocol.ts +692 -391
  182. package/src/strand-instance-manager.ts +201 -46
  183. package/src/strand-member-key.ts +39 -39
  184. package/src/strand-member-registry.ts +182 -177
  185. package/src/strand-membership-writer.ts +938 -146
  186. package/src/strand-solicitation.ts +472 -407
  187. package/src/strand-transport-key.ts +58 -0
  188. package/src/strand-wake-payload.ts +32 -32
  189. package/src/strand-wake-protocol.ts +314 -331
  190. package/src/strand-watcher.ts +314 -251
  191. package/src/trusted-owner-store-file.ts +41 -0
  192. package/src/trusted-owner-store.ts +171 -0
  193. package/src/types.ts +1309 -1032
@@ -14,17 +14,19 @@
14
14
  * stream (like seed delivery), so each side reads to EOF (under a read timeout)
15
15
  * and decodes one frame via the shared {@link decodeLengthPrefixedFrame} guard.
16
16
  *
17
- * **Authorization (v1):** the control network already restricts membership —
18
- * only this party's cadre peers connect (schema-gated `CadrePeer`). A wake is
19
- * low-risk: it only causes the receiver to spend resources coming online for a
20
- * strand it already participates in. So the receiver verifies the remote peer is
21
- * a `CadrePeer` member (via the injected `isMember` gate) and requires no
22
- * signature beyond control-network membership. A richer per-request signature is
23
- * deliberately out of scope for v1.
17
+ * **Authorization (v1):** a wake is low-risk — it only causes the receiver to
18
+ * spend resources coming online for a strand it already participates in — so the
19
+ * receiver carries no per-request signature and instead defers entirely to the
20
+ * injected `isMember` predicate. `CadreNode` injects its AUTHORIZED-membership
21
+ * predicate there (`isAuthorizedMember`: the sender's `CadrePeer` row must carry
22
+ * a voucher that verifies against an owner key in the receiver's node-local
23
+ * trusted-owner anchor), so a peer that merely published rows into the replicated
24
+ * control DB is refused. This module stays agnostic: it enforces whatever
25
+ * predicate it is given.
24
26
  */
25
27
  import debug from 'debug';
26
28
  import { decodeLengthPrefixedFrame } from './seed-bootstrap.js';
27
- import { writeFrame, withTimeout, readStreamToEnd } from './control-stream.js';
29
+ import { writeFrame, withDeadline, exchangeFrame, readStreamToEnd } from './control-stream.js';
28
30
  const log = debug('sereus:cadre:strand-wake');
29
31
  /** Protocol id for control-network push-wake (parallel to `/sereus/seed/1.0.0`). */
30
32
  export const WAKE_PROTOCOL = '/sereus/strand-wake/1.0.0';
@@ -159,8 +161,8 @@ export class StrandWakeService {
159
161
  * - Already-live strand → no-op, `accepted` with current status.
160
162
  */
161
163
  async processWakeRequest(request, remotePeerId) {
162
- // Control-network membership is the v1 authorization: only this party's
163
- // cadre peers may ask us to wake.
164
+ // The injected membership predicate is the whole v1 authorization (CadreNode
165
+ // supplies the voucher-anchored one); only a peer it admits may ask us to wake.
164
166
  if (!(await this.options.isMember(remotePeerId))) {
165
167
  log('Rejecting wake from non-member %s', remotePeerId);
166
168
  return { accepted: false, reason: 'Sender is not a cadre member' };
@@ -197,12 +199,10 @@ export async function dialWake(node, addrs, request, options = {}) {
197
199
  const timeoutMs = options.timeoutMs ?? DEFAULT_WAKE_TIMEOUT_MS;
198
200
  let lastError = null;
199
201
  for (const addr of addrs) {
200
- // One controller per attempt: on timeout, `withTimeout`'s `onTimeout` aborts
201
- // it, which aborts the in-flight dialProtocol (via the dial `signal`) and the
202
- // live stream — so neither the connect nor the unbounded ack-read leaks.
203
- const controller = new AbortController();
204
202
  try {
205
- return await withTimeout(timeoutMs, `Wake dial ${addr.toString()}`, () => sendWake(node, addr, protocolId, request, timeoutMs, controller.signal), () => controller.abort());
203
+ // One deadline per attempt: its signal aborts the in-flight dialProtocol and
204
+ // resets the live stream, so neither the connect nor the ack-read leaks.
205
+ return await withDeadline(timeoutMs, `Wake dial ${addr.toString()}`, (signal) => sendWake(node, addr, protocolId, request, timeoutMs, signal));
206
206
  }
207
207
  catch (err) {
208
208
  lastError = err instanceof Error ? err : new Error(String(err));
@@ -214,12 +214,10 @@ export async function dialWake(node, addrs, request, options = {}) {
214
214
  /**
215
215
  * Open one stream, send the request, half-close, and read the ack.
216
216
  *
217
- * `signal` is the per-attempt abort signal from {@link dialWake}: it is passed to
218
- * `dialProtocol` so a timeout during connect aborts the dial, and once the stream
219
- * is open an abort listener resets the live stream — releasing the otherwise
220
- * unbounded ack-read. `readFrame` still applies `timeoutMs` as a backstop for the
221
- * case where an abort does not propagate (e.g. a stream double whose `abort()` is
222
- * a no-op).
217
+ * `signal` is the per-attempt deadline from {@link dialWake}: it goes to
218
+ * `dialProtocol` so a timeout during connect aborts the dial, and into
219
+ * {@link exchangeFrame} so a timeout after the stream is open resets it —
220
+ * releasing the otherwise unbounded ack-read.
223
221
  */
224
222
  async function sendWake(node, addr, protocolId, request, timeoutMs, signal) {
225
223
  // `runOnLimitedConnection: true`: the target may be reachable only over a
@@ -228,30 +226,8 @@ async function sendWake(node, addr, protocolId, request, timeoutMs, signal) {
228
226
  // relay's data/duration cap, so opening it on the limited connection is safe
229
227
  // and is the whole point of dialing the relay address.
230
228
  const rawStream = await node.dialProtocol(addr, protocolId, { runOnLimitedConnection: true, signal });
231
- const stream = rawStream;
232
- const abortErr = new Error('Wake dial aborted by timeout');
233
- const onAbort = () => stream.abort(abortErr);
234
- // If the timeout already fired during connect, release the freshly-opened stream now.
235
- if (signal.aborted) {
236
- onAbort();
237
- throw abortErr;
238
- }
239
- signal.addEventListener('abort', onAbort, { once: true });
240
- try {
241
- writeFrame(stream, request);
242
- // close() half-closes the write end (EOF) while the read end stays open for
243
- // the ack — the same libp2p 3.x pattern as seed delivery.
244
- await stream.close();
245
- const ack = await readFrame(stream, timeoutMs);
246
- log('Wake ack from %s: accepted=%s status=%s', addr.toString(), ack.accepted, ack.status ?? '-');
247
- return ack;
248
- }
249
- catch (err) {
250
- stream.abort(err instanceof Error ? err : new Error(String(err)));
251
- throw err;
252
- }
253
- finally {
254
- signal.removeEventListener('abort', onAbort);
255
- }
229
+ const ack = await exchangeFrame(rawStream, signal, request, (stream) => readFrame(stream, timeoutMs), 'Wake dial aborted by timeout');
230
+ log('Wake ack from %s: accepted=%s status=%s', addr.toString(), ack.accepted, ack.status ?? '-');
231
+ return ack;
256
232
  }
257
233
  //# sourceMappingURL=strand-wake-protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"strand-wake-protocol.js","sourceRoot":"","sources":["../src/strand-wake-protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strand-wake-protocol.js","sourceRoot":"","sources":["../src/strand-wake-protocol.ts"],"names":[],"mappings":"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"}
@@ -39,13 +39,23 @@ export declare class StrandWatcher {
39
39
  private readonly callbacks;
40
40
  private readonly queryable;
41
41
  private readonly sAppIdLookup?;
42
+ /**
43
+ * Wall-clock source for the retry backoff. Read fresh at each use rather than
44
+ * snapshotted per poll: a poll awaits `onStrandAdded`, and a launch that fails
45
+ * slowly (e.g. a dial timeout) would otherwise be scheduled off the clock as it
46
+ * read *before* the attempt, putting `nextAttemptAt` in the past. Injectable so
47
+ * tests can advance time deterministically without fake timers.
48
+ */
49
+ private readonly now;
42
50
  private knownStrands;
43
51
  /** Ids admitted under a `defer` decision; re-evaluated each poll until they resolve. */
44
52
  private provisional;
53
+ /** Ids whose last launch attempt threw, with the backoff gating their retry. */
54
+ private failureStates;
45
55
  private pollTimer;
46
56
  private initialPollTimer;
47
57
  private running;
48
- constructor(queryable: StrandQueryable, callbacks: StrandWatcherCallbacks, filter?: StrandFilter, pollInterval?: number, sAppIdLookup?: SAppIdLookup);
58
+ constructor(queryable: StrandQueryable, callbacks: StrandWatcherCallbacks, filter?: StrandFilter, pollInterval?: number, sAppIdLookup?: SAppIdLookup, now?: () => number);
49
59
  /**
50
60
  * Evaluate a strand against the current filter, distinguishing a not-yet-known
51
61
  * sAppId (`defer`) from a known non-match (`reject`). A `defer` admission is
@@ -53,7 +63,12 @@ export declare class StrandWatcher {
53
63
  */
54
64
  private evaluateFilter;
55
65
  /**
56
- * Poll for strand changes
66
+ * Record a failed launch attempt and schedule when the strand may be retried:
67
+ * `pollInterval * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
68
+ */
69
+ private recordFailure;
70
+ /**
71
+ * Poll for strand changes.
57
72
  */
58
73
  private poll;
59
74
  /**
@@ -1,5 +1,12 @@
1
1
  import debug from 'debug';
2
2
  const log = debug('sereus:cadre:strand-watcher');
3
+ /**
4
+ * Ceiling on the retry delay for a strand whose launch keeps failing. Attempts
5
+ * are never abandoned — the failure this backoff exists for (a transient network
6
+ * or storage fault) can last a long time — but a permanently-unlaunchable strand
7
+ * must not re-attempt on every poll and storm the host app with `strand:error`.
8
+ */
9
+ const MAX_RETRY_BACKOFF_MS = 5 * 60 * 1000;
3
10
  /**
4
11
  * Watches the control network's Strand table for changes and triggers
5
12
  * strand instance start/stop via callbacks.
@@ -7,10 +14,12 @@ const log = debug('sereus:cadre:strand-watcher');
7
14
  * Uses polling until Optimystic supports reactive subscriptions.
8
15
  */
9
16
  export class StrandWatcher {
10
- constructor(queryable, callbacks, filter = { mode: 'all' }, pollInterval = 5000, sAppIdLookup) {
17
+ constructor(queryable, callbacks, filter = { mode: 'all' }, pollInterval = 5000, sAppIdLookup, now = Date.now) {
11
18
  this.knownStrands = new Map();
12
19
  /** Ids admitted under a `defer` decision; re-evaluated each poll until they resolve. */
13
20
  this.provisional = new Set();
21
+ /** Ids whose last launch attempt threw, with the backoff gating their retry. */
22
+ this.failureStates = new Map();
14
23
  this.pollTimer = null;
15
24
  this.initialPollTimer = null;
16
25
  this.running = false;
@@ -19,6 +28,7 @@ export class StrandWatcher {
19
28
  this.filter = filter;
20
29
  this.pollInterval = pollInterval;
21
30
  this.sAppIdLookup = sAppIdLookup;
31
+ this.now = now;
22
32
  log('StrandWatcher created with filter: %o, interval: %dms', filter, pollInterval);
23
33
  }
24
34
  /**
@@ -54,7 +64,17 @@ export class StrandWatcher {
54
64
  }
55
65
  }
56
66
  /**
57
- * Poll for strand changes
67
+ * Record a failed launch attempt and schedule when the strand may be retried:
68
+ * `pollInterval * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
69
+ */
70
+ recordFailure(strandId) {
71
+ const failures = (this.failureStates.get(strandId)?.failures ?? 0) + 1;
72
+ const delay = Math.min(this.pollInterval * 2 ** (failures - 1), MAX_RETRY_BACKOFF_MS);
73
+ this.failureStates.set(strandId, { failures, nextAttemptAt: this.now() + delay });
74
+ log('Strand %s launch failed (%d consecutive) - next attempt in %dms', strandId, failures, delay);
75
+ }
76
+ /**
77
+ * Poll for strand changes.
58
78
  */
59
79
  async poll() {
60
80
  if (!this.running)
@@ -66,10 +86,17 @@ export class StrandWatcher {
66
86
  for (const strand of currentStrands) {
67
87
  if (this.knownStrands.has(strand.Id))
68
88
  continue;
89
+ const failure = this.failureStates.get(strand.Id);
90
+ // Clock read per candidate, not once per poll: an earlier strand's launch
91
+ // may have taken a long time, and this one's gate must reflect that.
92
+ if (failure && this.now() < failure.nextAttemptAt)
93
+ continue; // backing off after a failed launch
69
94
  const decision = this.evaluateFilter(strand);
70
95
  if (decision === 'reject')
71
96
  continue;
72
97
  log('Strand added: %s', strand.Id);
98
+ // Set BEFORE the await: this doubles as the in-flight guard that stops the
99
+ // interval timer from starting a second concurrent launch for the same strand.
73
100
  this.knownStrands.set(strand.Id, strand);
74
101
  if (decision === 'defer') {
75
102
  // Provisional admission: sAppId unknown, re-check on later polls.
@@ -77,9 +104,16 @@ export class StrandWatcher {
77
104
  }
78
105
  try {
79
106
  await this.callbacks.onStrandAdded(strand);
107
+ this.failureStates.delete(strand.Id);
80
108
  }
81
109
  catch (error) {
82
110
  log('Error handling strand add for %s: %o', strand.Id, error);
111
+ // A failed launch leaves nothing running (StrandInstanceManager drops the
112
+ // record), so forget the strand and let a later poll retry it — gated by
113
+ // the backoff recorded here.
114
+ this.knownStrands.delete(strand.Id);
115
+ this.provisional.delete(strand.Id);
116
+ this.recordFailure(strand.Id);
83
117
  }
84
118
  }
85
119
  // Re-evaluate provisional admissions whose sAppId may now be resolvable.
@@ -107,7 +141,9 @@ export class StrandWatcher {
107
141
  }
108
142
  // decision === 'defer': still unknown, leave provisional for next poll.
109
143
  }
110
- // Find removed strands
144
+ // Find removed strands. A strand whose launch failed is no longer in
145
+ // knownStrands, so it correctly never fires onStrandRemoved — nothing was
146
+ // ever started for it.
111
147
  for (const [strandId] of this.knownStrands) {
112
148
  if (!currentMap.has(strandId)) {
113
149
  log('Strand removed: %s', strandId);
@@ -121,6 +157,13 @@ export class StrandWatcher {
121
157
  }
122
158
  }
123
159
  }
160
+ // Drop backoff state for strands whose control-network row is gone; a row
161
+ // that reappears is a fresh strand and gets a fresh first attempt.
162
+ for (const strandId of [...this.failureStates.keys()]) {
163
+ if (!currentMap.has(strandId)) {
164
+ this.failureStates.delete(strandId);
165
+ }
166
+ }
124
167
  }
125
168
  catch (error) {
126
169
  log('Error polling strands: %o', error);
@@ -167,6 +210,7 @@ export class StrandWatcher {
167
210
  }
168
211
  this.knownStrands.clear();
169
212
  this.provisional.clear();
213
+ this.failureStates.clear();
170
214
  log('StrandWatcher stopped');
171
215
  }
172
216
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { TrustedOwnerStore } from './trusted-owner-store.js';
2
+ /**
3
+ * File-backed {@link TrustedOwnerStore}. Open via {@link open}, which loads the
4
+ * existing file and returns the cross-platform store over it; an absent, corrupt, or wrong-party file is a cold
5
+ * start (empty anchor), while a present-but-unreadable file throws. Those rules
6
+ * and their reasoning live on `NodeLocalSnapshot.open` — they are properties of
7
+ * every persistent backend, not of this one.
8
+ */
9
+ export declare const FileTrustedOwnerStore: {
10
+ /** Load (or cold-start) the party's anchor from a file in `dir`. */
11
+ open(dir: string, partyId: string): Promise<TrustedOwnerStore>;
12
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Node-only file-backed {@link TrustedOwnerStore}: one JSON file per party under
3
+ * a configured directory (the node's state directory — `cadre-cli` passes
4
+ * `ResolvedConfig.nodeStateDir`), holding the node-local, non-replicated
5
+ * trusted-owner anchor.
6
+ *
7
+ * Nothing but the file itself lives here: the anchor's envelope, load policy,
8
+ * per-entry validation and serialised snapshot writes are cross-platform in
9
+ * `PersistentTrustedOwnerStore` / `node-local-snapshot.ts`, and the file is a
10
+ * `FileDurableSlot`. This module exists only so the `node:fs` edge stays out of
11
+ * the package's cross-platform default entry (`./index.js`) — import it from
12
+ * the dedicated subpath instead:
13
+ *
14
+ * ```ts
15
+ * import { FileTrustedOwnerStore } from '@serfab/cadre-core/trusted-owner-store-file';
16
+ * ```
17
+ *
18
+ * A React Native / browser entry graph never resolves this path, so the
19
+ * `node:fs` edge never reaches a bundler that cannot satisfy it (same
20
+ * isolation pattern as `key-store-file.ts`).
21
+ */
22
+ import { FileDurableSlot } from './file-durable-slot.js';
23
+ import { PersistentTrustedOwnerStore } from './trusted-owner-store.js';
24
+ /** Base name of the anchor file: `<dir>/trusted-owners.<encoded partyId>.json`. */
25
+ const SLOT_NAME = 'trusted-owners';
26
+ /**
27
+ * File-backed {@link TrustedOwnerStore}. Open via {@link open}, which loads the
28
+ * existing file and returns the cross-platform store over it; an absent, corrupt, or wrong-party file is a cold
29
+ * start (empty anchor), while a present-but-unreadable file throws. Those rules
30
+ * and their reasoning live on `NodeLocalSnapshot.open` — they are properties of
31
+ * every persistent backend, not of this one.
32
+ */
33
+ export const FileTrustedOwnerStore = {
34
+ /** Load (or cold-start) the party's anchor from a file in `dir`. */
35
+ async open(dir, partyId) {
36
+ return PersistentTrustedOwnerStore.open(new FileDurableSlot(dir, SLOT_NAME, partyId), partyId);
37
+ }
38
+ };
39
+ //# sourceMappingURL=trusted-owner-store-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted-owner-store-file.js","sourceRoot":"","sources":["../src/trusted-owner-store-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAGvE,mFAAmF;AACnF,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACpC,oEAAoE;IACpE,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,OAAe;QACtC,OAAO,2BAA2B,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAChG,CAAC;CACD,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { type DurableSlot } from './node-local-snapshot.js';
2
+ /** How an owner key entered the anchor (out-of-band provenance). */
3
+ export type TrustSource = 'genesis' | 'invite' | 'operator';
4
+ export interface TrustedOwnerStore {
5
+ /** Party this anchor is scoped to. */
6
+ readonly partyId: string;
7
+ /** Is this ed25519 (base64url) key one of my party's out-of-band-trusted owners? */
8
+ has(ownerKey: string): boolean;
9
+ /**
10
+ * All anchored owner keys (e.g. for seed-trust `knownOwnerKeys`).
11
+ *
12
+ * NOTE: every backend copies into a fresh Set per call (so the result is a
13
+ * snapshot decoupled from later `trust()` calls). Anchors hold a handful of
14
+ * keys and callers are per-seed, so the copy is free today; if a hot path
15
+ * ever calls this per message, prefer `has()` or cache the snapshot.
16
+ */
17
+ all(): ReadonlySet<string>;
18
+ /**
19
+ * Add a key established out of band (genesis self-trust / invite pin /
20
+ * operator pin). Idempotent: re-trusting a known key is a no-op that keeps
21
+ * the original source. Implementations MUST reflect the key in {@link has} /
22
+ * {@link all} synchronously; the returned promise tracks durability only
23
+ * (a persistent backend's write), so a synchronous caller may safely consult
24
+ * the store right after invoking this.
25
+ */
26
+ trust(ownerKey: string, source: TrustSource): Promise<void>;
27
+ }
28
+ /**
29
+ * Ephemeral in-memory anchor for nodes without durable storage (tests, browser
30
+ * demos, not-yet-persisted mobile). Same contract, no disk: trust established
31
+ * here must be re-supplied (invite / operator pin) on the next process.
32
+ */
33
+ export declare class MemoryTrustedOwnerStore implements TrustedOwnerStore {
34
+ readonly partyId: string;
35
+ private readonly keys;
36
+ constructor(partyId: string);
37
+ has(ownerKey: string): boolean;
38
+ all(): ReadonlySet<string>;
39
+ trust(ownerKey: string, source: TrustSource): Promise<void>;
40
+ }
41
+ /**
42
+ * Durable {@link TrustedOwnerStore} over an app-supplied {@link DurableSlot} —
43
+ * the cross-platform half of every persistent backend (the Node
44
+ * `FileTrustedOwnerStore` is this class over a file slot).
45
+ *
46
+ * Construct via {@link open}. Load and persist policy — what an absent,
47
+ * corrupt, foreign-party or unreadable slot does, and what a failed persist
48
+ * does — is documented once on `NodeLocalSnapshot`; this class only supplies
49
+ * the payload shape and the anchor's whole-record-on-bad-entry policy.
50
+ */
51
+ export declare class PersistentTrustedOwnerStore implements TrustedOwnerStore {
52
+ private readonly snapshot;
53
+ private constructor();
54
+ /** Load (or cold-start) the party's anchor from `slot`. */
55
+ static open(slot: DurableSlot, partyId: string): Promise<PersistentTrustedOwnerStore>;
56
+ get partyId(): string;
57
+ has(ownerKey: string): boolean;
58
+ all(): ReadonlySet<string>;
59
+ /**
60
+ * Anchor a key: visible via {@link has} / {@link all} synchronously, then the
61
+ * full snapshot is persisted (see `NodeLocalSnapshot.put`).
62
+ */
63
+ trust(ownerKey: string, source: TrustSource): Promise<void>;
64
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Node-local, NON-replicated trusted-owner anchor.
3
+ *
4
+ * The replicated `CadreControl.OwnerKey` table cannot anchor trust: any
5
+ * connecting node can genesis-insert its own key into its local table and that
6
+ * row replicates into every peer's copy, so "is this key in `OwnerKey`?" can be
7
+ * made true by a stranger. This store is the anchor that CAN be trusted — a
8
+ * per-party, on-device record of owner keys established OUT OF BAND: founding
9
+ * the party (genesis self-trust), the pinned keys carried by the `CadreInvite`
10
+ * that enrolled this node, or an explicit operator pin. It is never written
11
+ * from replicated control state; that is the whole point.
12
+ *
13
+ * Three implementations:
14
+ * - {@link MemoryTrustedOwnerStore} (this module, cross-platform) — ephemeral;
15
+ * the default when no store is injected via `CadreNodeConfig.trustedOwners`.
16
+ * - {@link PersistentTrustedOwnerStore} (this module, cross-platform) — durable
17
+ * over any `DurableSlot` the embedding app supplies (IndexedDB, SecureStore,
18
+ * SQLite, …). The load/persist policy lives in `node-local-snapshot.ts`; read
19
+ * {@link NodeLocalSnapshot.open} for what an absent, corrupt, foreign-party
20
+ * or unreadable slot does.
21
+ * - `FileTrustedOwnerStore` — the above over a Node file in the node's state
22
+ * directory; Node-only, behind the subpath
23
+ * `@serfab/cadre-core/trusted-owner-store-file` (same isolation pattern as
24
+ * `key-store-file`) so `node:fs` never lands in the RN/browser entry graph.
25
+ *
26
+ * Keys are additive: nothing in this interface removes a key (owner revocation
27
+ * is out of scope here and tracked with the broader control-sync design).
28
+ */
29
+ import debug from 'debug';
30
+ import { NodeLocalSnapshot } from './node-local-snapshot.js';
31
+ const log = debug('sereus:cadre:trusted-owner-store');
32
+ /**
33
+ * Ephemeral in-memory anchor for nodes without durable storage (tests, browser
34
+ * demos, not-yet-persisted mobile). Same contract, no disk: trust established
35
+ * here must be re-supplied (invite / operator pin) on the next process.
36
+ */
37
+ export class MemoryTrustedOwnerStore {
38
+ constructor(partyId) {
39
+ this.partyId = partyId;
40
+ this.keys = new Map();
41
+ }
42
+ has(ownerKey) {
43
+ return this.keys.has(ownerKey);
44
+ }
45
+ all() {
46
+ return new Set(this.keys.keys());
47
+ }
48
+ async trust(ownerKey, source) {
49
+ if (this.keys.has(ownerKey)) {
50
+ return;
51
+ }
52
+ this.keys.set(ownerKey, source);
53
+ log('trusted owner key anchored (party=%s, source=%s)', this.partyId, source);
54
+ }
55
+ }
56
+ const KNOWN_SOURCES = new Set(['genesis', 'invite', 'operator']);
57
+ /**
58
+ * What the anchor persists: `owners` maps ownerKey (base64url) -> provenance.
59
+ *
60
+ * One unusable entry discards the WHOLE record: trusting a subset of the keys a
61
+ * record claims is a silent, security-relevant downgrade, so a record that
62
+ * cannot be read in full is treated as no anchor at all (see
63
+ * `UnusableEntryPolicy` in `node-local-snapshot.ts`).
64
+ */
65
+ const TRUSTED_OWNER_SNAPSHOT = {
66
+ label: 'trusted-owner anchor',
67
+ payloadKey: 'owners',
68
+ unusableEntry: 'discard-all',
69
+ acceptEntry: (_ownerKey, entry) => {
70
+ if (typeof entry !== 'object' || entry === null)
71
+ return undefined;
72
+ const { source, trustedAt } = entry;
73
+ if (typeof source !== 'string' || !KNOWN_SOURCES.has(source))
74
+ return undefined;
75
+ if (typeof trustedAt !== 'number')
76
+ return undefined;
77
+ return { source: source, trustedAt };
78
+ }
79
+ };
80
+ /**
81
+ * Durable {@link TrustedOwnerStore} over an app-supplied {@link DurableSlot} —
82
+ * the cross-platform half of every persistent backend (the Node
83
+ * `FileTrustedOwnerStore` is this class over a file slot).
84
+ *
85
+ * Construct via {@link open}. Load and persist policy — what an absent,
86
+ * corrupt, foreign-party or unreadable slot does, and what a failed persist
87
+ * does — is documented once on `NodeLocalSnapshot`; this class only supplies
88
+ * the payload shape and the anchor's whole-record-on-bad-entry policy.
89
+ */
90
+ export class PersistentTrustedOwnerStore {
91
+ constructor(snapshot) {
92
+ this.snapshot = snapshot;
93
+ }
94
+ /** Load (or cold-start) the party's anchor from `slot`. */
95
+ static async open(slot, partyId) {
96
+ return new PersistentTrustedOwnerStore(await NodeLocalSnapshot.open(slot, partyId, TRUSTED_OWNER_SNAPSHOT));
97
+ }
98
+ get partyId() {
99
+ return this.snapshot.partyId;
100
+ }
101
+ has(ownerKey) {
102
+ return this.snapshot.has(ownerKey);
103
+ }
104
+ all() {
105
+ return this.snapshot.keySnapshot();
106
+ }
107
+ /**
108
+ * Anchor a key: visible via {@link has} / {@link all} synchronously, then the
109
+ * full snapshot is persisted (see `NodeLocalSnapshot.put`).
110
+ */
111
+ trust(ownerKey, source) {
112
+ if (this.snapshot.has(ownerKey)) {
113
+ // Idempotent: re-enrollment / restart re-seeding keeps the original
114
+ // provenance and skips the write entirely.
115
+ // NOTE: this resolves immediately rather than joining the write chain, so
116
+ // re-trusting a key whose first persist is still in flight does not await
117
+ // that persist. Harmless for the seeding callers (they re-trust only after
118
+ // the original settled, or do not await at all); chain it if a caller ever
119
+ // needs "durable by the time trust() resolves" for a repeat key.
120
+ return Promise.resolve();
121
+ }
122
+ log('trusted owner key anchored (party=%s, source=%s); persisting', this.partyId, source);
123
+ return this.snapshot.put(ownerKey, { source, trustedAt: Date.now() });
124
+ }
125
+ }
126
+ //# sourceMappingURL=trusted-owner-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trusted-owner-store.js","sourceRoot":"","sources":["../src/trusted-owner-store.ts"],"names":[],"mappings":"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"}