@serfab/cadre-core 1.7.0 → 1.8.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 (162) hide show
  1. package/README.md +8 -6
  2. package/dist/cadre-node.d.ts +386 -114
  3. package/dist/cadre-node.js +887 -359
  4. package/dist/cadre-node.js.map +1 -1
  5. package/dist/control-authorization.d.ts +2 -2
  6. package/dist/control-authorization.js +1 -0
  7. package/dist/control-authorization.js.map +1 -1
  8. package/dist/control-cohort.d.ts +6 -0
  9. package/dist/control-cohort.js +6 -0
  10. package/dist/control-cohort.js.map +1 -1
  11. package/dist/control-database.d.ts +46 -8
  12. package/dist/control-database.js +97 -18
  13. package/dist/control-database.js.map +1 -1
  14. package/dist/control-formation-recorder.d.ts +37 -14
  15. package/dist/control-formation-recorder.js +60 -10
  16. package/dist/control-formation-recorder.js.map +1 -1
  17. package/dist/control-read-retry.d.ts +13 -12
  18. package/dist/control-read-retry.js +14 -12
  19. package/dist/control-read-retry.js.map +1 -1
  20. package/dist/control-schema.d.ts +1 -1
  21. package/dist/control-schema.js +67 -5
  22. package/dist/control-schema.js.map +1 -1
  23. package/dist/control-stream.d.ts +9 -1
  24. package/dist/control-stream.js +24 -1
  25. package/dist/control-stream.js.map +1 -1
  26. package/dist/control-write-retry.d.ts +12 -2
  27. package/dist/control-write-retry.js +13 -2
  28. package/dist/control-write-retry.js.map +1 -1
  29. package/dist/delegate-admission.d.ts +11 -8
  30. package/dist/delegate-admission.js +19 -11
  31. package/dist/delegate-admission.js.map +1 -1
  32. package/dist/diagnostics/connection-path.d.ts +0 -7
  33. package/dist/diagnostics/connection-path.js +0 -7
  34. package/dist/diagnostics/connection-path.js.map +1 -1
  35. package/dist/formation-approval.d.ts +7 -0
  36. package/dist/formation-approval.js +9 -3
  37. package/dist/formation-approval.js.map +1 -1
  38. package/dist/fs-atomic.d.ts +11 -2
  39. package/dist/fs-atomic.js +13 -3
  40. package/dist/fs-atomic.js.map +1 -1
  41. package/dist/hibernation-manager.d.ts +111 -8
  42. package/dist/hibernation-manager.js +234 -49
  43. package/dist/hibernation-manager.js.map +1 -1
  44. package/dist/index.d.ts +8 -6
  45. package/dist/index.js +15 -9
  46. package/dist/index.js.map +1 -1
  47. package/dist/joined-strand-store.d.ts +90 -25
  48. package/dist/joined-strand-store.js +223 -55
  49. package/dist/joined-strand-store.js.map +1 -1
  50. package/dist/key-store-file.js +14 -5
  51. package/dist/key-store-file.js.map +1 -1
  52. package/dist/link-budget.d.ts +367 -69
  53. package/dist/link-budget.js +375 -72
  54. package/dist/link-budget.js.map +1 -1
  55. package/dist/membership-connection-gater.d.ts +30 -43
  56. package/dist/membership-connection-gater.js +31 -42
  57. package/dist/membership-connection-gater.js.map +1 -1
  58. package/dist/peer-dial.d.ts +24 -4
  59. package/dist/peer-dial.js +74 -5
  60. package/dist/peer-dial.js.map +1 -1
  61. package/dist/peer-join-backfill.d.ts +2 -1
  62. package/dist/peer-join-backfill.js +6 -5
  63. package/dist/peer-join-backfill.js.map +1 -1
  64. package/dist/push-fanout.d.ts +6 -0
  65. package/dist/push-fanout.js +6 -0
  66. package/dist/push-fanout.js.map +1 -1
  67. package/dist/relay-reservation.d.ts +3 -1
  68. package/dist/relay-reservation.js +3 -1
  69. package/dist/relay-reservation.js.map +1 -1
  70. package/dist/relay-server.d.ts +79 -0
  71. package/dist/relay-server.js +74 -0
  72. package/dist/relay-server.js.map +1 -0
  73. package/dist/seed-bootstrap.d.ts +57 -4
  74. package/dist/seed-bootstrap.js +107 -83
  75. package/dist/seed-bootstrap.js.map +1 -1
  76. package/dist/storage-scope.d.ts +33 -17
  77. package/dist/storage-scope.js +44 -23
  78. package/dist/storage-scope.js.map +1 -1
  79. package/dist/strand-addr-protocol.d.ts +58 -21
  80. package/dist/strand-addr-protocol.js +133 -85
  81. package/dist/strand-addr-protocol.js.map +1 -1
  82. package/dist/strand-database.d.ts +20 -2
  83. package/dist/strand-database.js +42 -5
  84. package/dist/strand-database.js.map +1 -1
  85. package/dist/strand-first-sync-gate.d.ts +59 -21
  86. package/dist/strand-first-sync-gate.js +58 -19
  87. package/dist/strand-first-sync-gate.js.map +1 -1
  88. package/dist/strand-formation-deadlines.d.ts +151 -0
  89. package/dist/strand-formation-deadlines.js +171 -0
  90. package/dist/strand-formation-deadlines.js.map +1 -0
  91. package/dist/strand-formation-manager.d.ts +63 -30
  92. package/dist/strand-formation-manager.js +67 -41
  93. package/dist/strand-formation-manager.js.map +1 -1
  94. package/dist/strand-formation-protocol.d.ts +45 -35
  95. package/dist/strand-formation-protocol.js +59 -122
  96. package/dist/strand-formation-protocol.js.map +1 -1
  97. package/dist/strand-instance-manager.d.ts +113 -6
  98. package/dist/strand-instance-manager.js +257 -41
  99. package/dist/strand-instance-manager.js.map +1 -1
  100. package/dist/strand-membership-writer.d.ts +6 -0
  101. package/dist/strand-membership-writer.js +11 -0
  102. package/dist/strand-membership-writer.js.map +1 -1
  103. package/dist/strand-peer-book-protocol.d.ts +13 -4
  104. package/dist/strand-peer-book-protocol.js +3 -7
  105. package/dist/strand-peer-book-protocol.js.map +1 -1
  106. package/dist/strand-peer-book.d.ts +1 -1
  107. package/dist/strand-peer-book.js +8 -1
  108. package/dist/strand-peer-book.js.map +1 -1
  109. package/dist/strand-peer-observer.js +1 -1
  110. package/dist/strand-revocation-enforcer.js +2 -1
  111. package/dist/strand-revocation-enforcer.js.map +1 -1
  112. package/dist/strand-solicitation.d.ts +36 -20
  113. package/dist/strand-solicitation.js +4 -4
  114. package/dist/strand-solicitation.js.map +1 -1
  115. package/dist/strand-wake-protocol.d.ts +80 -23
  116. package/dist/strand-wake-protocol.js +89 -64
  117. package/dist/strand-wake-protocol.js.map +1 -1
  118. package/dist/types.d.ts +128 -38
  119. package/dist/types.js +4 -3
  120. package/dist/types.js.map +1 -1
  121. package/package.json +7 -7
  122. package/src/cadre-node.ts +8367 -7776
  123. package/src/control-authorization.ts +2 -1
  124. package/src/control-cohort.ts +6 -0
  125. package/src/control-database.ts +122 -18
  126. package/src/control-formation-recorder.ts +354 -305
  127. package/src/control-read-retry.ts +14 -12
  128. package/src/control-schema.ts +67 -5
  129. package/src/control-stream.ts +25 -1
  130. package/src/control-write-retry.ts +13 -2
  131. package/src/delegate-admission.ts +23 -13
  132. package/src/diagnostics/connection-path.ts +0 -7
  133. package/src/formation-approval.ts +610 -604
  134. package/src/fs-atomic.ts +14 -4
  135. package/src/hibernation-manager.ts +638 -437
  136. package/src/index.ts +28 -10
  137. package/src/joined-strand-store.ts +247 -57
  138. package/src/key-store-file.ts +14 -5
  139. package/src/link-budget.ts +393 -74
  140. package/src/membership-connection-gater.ts +31 -45
  141. package/src/peer-dial.ts +81 -7
  142. package/src/peer-join-backfill.ts +8 -6
  143. package/src/push-fanout.ts +6 -0
  144. package/src/relay-reservation.ts +3 -1
  145. package/src/relay-server.ts +98 -0
  146. package/src/seed-bootstrap.ts +133 -91
  147. package/src/storage-scope.ts +44 -23
  148. package/src/strand-addr-protocol.ts +547 -458
  149. package/src/strand-database.ts +287 -242
  150. package/src/strand-first-sync-gate.ts +58 -19
  151. package/src/strand-formation-deadlines.ts +227 -0
  152. package/src/strand-formation-manager.ts +700 -647
  153. package/src/strand-formation-protocol.ts +103 -150
  154. package/src/strand-instance-manager.ts +287 -44
  155. package/src/strand-membership-writer.ts +12 -0
  156. package/src/strand-peer-book-protocol.ts +16 -13
  157. package/src/strand-peer-book.ts +8 -1
  158. package/src/strand-peer-observer.ts +1 -1
  159. package/src/strand-revocation-enforcer.ts +2 -1
  160. package/src/strand-solicitation.ts +519 -500
  161. package/src/strand-wake-protocol.ts +401 -355
  162. package/src/types.ts +130 -38
@@ -17,10 +17,10 @@ import { type RelayReservationState, type RelayReservationSupervisorOptions } fr
17
17
  import type { WakeAck } from './types.js';
18
18
  import { type ConnectionPathSummary } from './diagnostics/connection-path.js';
19
19
  /**
20
- * How often a running strand re-resolves its siblings' strand-network addresses
21
- * and re-merges them into its own libp2p address book
22
- * ({@link CadreNode.refreshStrandPeerAddrs}). Overridable per node via
23
- * `network.controlCohort.strandAddrRefreshMs`.
20
+ * How often a running strand re-asks each connected sibling that last ANSWERED
21
+ * (even with nothing) for its strand-network addresses, and re-merges them into
22
+ * its own libp2p address book ({@link CadreNode.refreshStrandPeerAddrs}).
23
+ * Overridable per node via `network.controlCohort.strandAddrRefreshMs`.
24
24
  *
25
25
  * Ten minutes sits comfortably inside the peerStore's one-hour address expiry
26
26
  * (`MAX_ADDRESS_AGE`, see `peer-addr-book.ts`) with headroom for several missed
@@ -28,6 +28,16 @@ import { type ConnectionPathSummary } from './diagnostics/connection-path.js';
28
28
  * strand-addr RPC fan-out stays cheap.
29
29
  */
30
30
  export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
31
+ /**
32
+ * How soon {@link CadreNode.refreshStrandPeerAddrs} re-asks a sibling that did NOT
33
+ * answer — unreachable, `unavailable`, or `refused`. A refusal matters most: a
34
+ * phone asking before its `CadrePeer` row has replicated to the sibling is refused
35
+ * and its delegate grant goes unrecorded, and waiting the full
36
+ * {@link STRAND_PEER_ADDR_REFRESH_MS} for the next try is the delay this bounds.
37
+ * Four reconcile ticks, so a sibling that keeps failing costs one timed-out RPC a
38
+ * minute rather than one a tick.
39
+ */
40
+ export declare const STRAND_PEER_ADDR_RETRY_MS: number;
31
41
  type EventHandler<T> = (data: T) => void;
32
42
  /**
33
43
  * CadreNode is the main entry point for a cadre member.
@@ -161,6 +171,28 @@ export declare class CadreNode implements SAppIdLookup {
161
171
  * {@link getDiscoveredStrands}.
162
172
  */
163
173
  private discoveredStrands;
174
+ /**
175
+ * Whether an unclaimed strand the filter admits is launched as a storage replica
176
+ * ({@link CadreNodeConfig.hostUnclaimedStrands}), resolved once from the config.
177
+ *
178
+ * NOTE: a replica host stores every admitted strand of its party with no quota (Arachnode
179
+ * quotas are unimplemented). Fine at a party's handful of strands; if always-on nodes come
180
+ * to host strands by the hundred, per-strand quotas or a narrower default filter is the lever.
181
+ */
182
+ private readonly hostUnclaimedStrands;
183
+ /**
184
+ * The sApp id each storage replica read from its own `Strand.Header` — {@link getSAppId}'s
185
+ * answer for a strand no local config claims, so an `sAppId` strand filter can reject a
186
+ * replica of some other app instead of admitting it provisionally forever.
187
+ *
188
+ * Cleared by {@link cleanup} only, deliberately NOT by {@link detachStrand}: the watcher's
189
+ * filter rejection stops a replica through that very method, and forgetting the id there
190
+ * would have the next poll find it unknown again, re-admit the strand provisionally and
191
+ * relaunch the replica — once every other poll, forever. A strand id's sApp never changes,
192
+ * and the map is bounded by the strands this party has published, like
193
+ * {@link discoveredStrands}.
194
+ */
195
+ private replicaSAppIds;
164
196
  /**
165
197
  * Most-recently pushed invite addresses (see {@link setInviteAddresses}).
166
198
  * When non-null these take priority over `libp2pNode.getMultiaddrs()` when
@@ -235,9 +267,20 @@ export declare class CadreNode implements SAppIdLookup {
235
267
  * `denyInboundRelayReservation` policy); authorized members and announced
236
268
  * delegates are admitted before it and never counted. Cap from
237
269
  * `network.unauthorizedRelayReservationCap` (default
238
- * `MAX_UNAUTHORIZED_RELAY_RESERVATIONS`).
270
+ * `MAX_UNAUTHORIZED_RELAY_RESERVATIONS`); entries expire on the relay
271
+ * server's own resolved `reservationTtl` ({@link relayServer}).
239
272
  */
240
273
  private readonly unauthorizedRelayReservations;
274
+ /**
275
+ * Does this node's CONTROL libp2p run the circuit-relay server, and with which
276
+ * init? Resolved once from `network` and `profile` by the same function every
277
+ * strand node's build uses (`relay-server.ts`). Read by
278
+ * {@link buildControlNodeOptions} (which configures the server from it),
279
+ * {@link admitInboundControlConnection} (whose deny/admit-for-relay branch must
280
+ * agree with whether a reservation is even servable here), and the
281
+ * unauthorized budget above (whose TTL is the server's).
282
+ */
283
+ private readonly relayServer;
241
284
  /**
242
285
  * When this node last ANNOUNCED a delegate, keyed `<targetPeerId>\n<strandId>`
243
286
  * (the peer announced TO, not the delegate). Throttles
@@ -248,15 +291,18 @@ export declare class CadreNode implements SAppIdLookup {
248
291
  */
249
292
  private readonly delegateAnnounceAt;
250
293
  /**
251
- * When each running strand last had its siblings' strand addresses re-resolved
252
- * into its own address book, keyed by strandId. Throttles
253
- * {@link refreshStrandPeerAddrs} to once per {@link STRAND_PEER_ADDR_REFRESH_MS}
254
- * per strand so the 15 s reconcile tick never becomes per-tick RPC chatter.
255
- * Entries for strands that are no longer running are pruned on each pass, so a
256
- * hibernated-then-resumed strand refreshes immediately rather than inheriting a
257
- * stale stamp.
294
+ * When each connected sibling is next due a strand-addr RPC for each running
295
+ * strand, keyed `peerStrandKey(siblingControlPeerId, strandId)` → epoch ms; a
296
+ * missing key is due. Bounds {@link refreshStrandPeerAddrs}'s fan-out per
297
+ * (sibling, strand) rather than per strand, so a sibling that connects after a
298
+ * pass is asked on the next 15 s tick instead of waiting out a stamp another
299
+ * sibling's answer set. An answer (even an empty one) makes the sibling due again
300
+ * in {@link STRAND_PEER_ADDR_REFRESH_MS}; no answer, in
301
+ * {@link STRAND_PEER_ADDR_RETRY_MS}. Keys whose strand stopped running or whose
302
+ * sibling is no longer connected are pruned every pass, so a resumed strand and a
303
+ * reconnected sibling are both asked at once.
258
304
  */
259
- private readonly strandPeerAddrRefreshAt;
305
+ private readonly strandAddrAskDueAt;
260
306
  /**
261
307
  * Node-local strand peer book (see `strand-peer-book.ts`): per strand, the strand
262
308
  * peers this node has met and their last-known strand-network addresses. Three
@@ -387,22 +433,22 @@ export declare class CadreNode implements SAppIdLookup {
387
433
  */
388
434
  private enrolledMachineStore;
389
435
  /**
390
- * Node-local record of the strands this node joined from ANOTHER party (see
391
- * `joined-strand-store.ts`) — the only thing that names them after a restart, since
392
- * this party's control database holds no row for them. Written by {@link formStrand}
393
- * and by an {@link addStrand} of a foreign row; forgotten by {@link forgetJoinedStrand}
394
- * and on self-revocation. Adopted from `config.joinedStrands.store`, else built over
436
+ * Node-local record of the strands this node joined from ANOTHER party and has not yet
437
+ * published party-wide (see `joined-strand-store.ts`). Written by {@link formStrand}
438
+ * and by an {@link addStrand} of a foreign row; drained once the party-wide
439
+ * `JoinedStrand` row is visible. Adopted from `config.joinedStrands.store`, else built over
395
440
  * `config.keyStore`, else in-memory, by {@link initializeJoinedStrandStore}. Like
396
441
  * {@link bootstrapPeerStore} it is deliberately NOT cleared by {@link cleanup}.
397
442
  */
398
443
  private joinedStrandStore;
399
444
  /**
400
- * The strand watcher's view of {@link joinedStrandStore}: its records unioned with the
401
- * control rows each poll. Per SESSION, unlike the store — rebuilt at every
445
+ * This session's joined strands: {@link joinedStrandStore} and the party-wide
446
+ * `JoinedStrand` table, unioned with the control rows each watcher poll, published and
447
+ * removed by the reconcile pass. Per SESSION, unlike the store — rebuilt at every
402
448
  * {@link start}, which is what ends a revoked join's "keep offering until this session
403
- * ends" (see `JoinedStrandRows.forgetAfterThisSession`).
449
+ * ends" (see `JoinedStrandSession.forgetAfterThisSession`).
404
450
  */
405
- private joinedStrandRows;
451
+ private joinedStrands;
406
452
  /**
407
453
  * The enrolled-machine count this node's CONTROL libp2p node was (or will be)
408
454
  * built with — read out of {@link enrolledMachineStore} during {@link start},
@@ -544,7 +590,8 @@ export declare class CadreNode implements SAppIdLookup {
544
590
  private turnConnectionCloseHandler;
545
591
  constructor(config: CadreNodeConfig);
546
592
  /**
547
- * SAppIdLookup implementation - get sAppId for a strand
593
+ * SAppIdLookup implementation - get sAppId for a strand: the claiming config's, else
594
+ * the one a storage replica read from the strand's own `Strand.Header`.
548
595
  */
549
596
  getSAppId(strandId: string): string | undefined;
550
597
  /**
@@ -712,7 +759,7 @@ export declare class CadreNode implements SAppIdLookup {
712
759
  private initializeEnrolledMachineStore;
713
760
  /**
714
761
  * Construct (or adopt) the node-local joined-strand store (see
715
- * {@link joinedStrandStore}) and this session's watcher view over it.
762
+ * {@link joinedStrandStore}) and this session's {@link joinedStrands} over it.
716
763
  *
717
764
  * The store instance is kept across stop()→start(); the view is rebuilt. A store
718
765
  * scoped to a different party is a configuration error, fail closed: its joins
@@ -867,14 +914,12 @@ export declare class CadreNode implements SAppIdLookup {
867
914
  */
868
915
  private buildControlNodeOptions;
869
916
  /**
870
- * Does this node's CONTROL libp2p run the circuit-relay server? An explicit
871
- * `network.enableRelay` wins; otherwise storage-profile nodes default to on
872
- * (better connectivity/uptime). Shared by {@link buildControlNodeOptions}
873
- * (which configures the server from it) and
874
- * {@link admitInboundControlConnection} (whose deny/admit-for-relay branch
875
- * must agree with whether a reservation is even servable here).
917
+ * One debug line naming what this machine's relay server forwards and holds —
918
+ * the same fields the dedicated relay container prints at boot
919
+ * (`ops/docker/libp2p-infra/src/main.ts`). Once per start, not per strand:
920
+ * strand nodes resolve the same settings from the same config.
876
921
  */
877
- private relayServerEnabled;
922
+ private logRelayServerSettings;
878
923
  /**
879
924
  * Decide whether an inbound CONTROL-network connection from `remotePeerId`
880
925
  * should be admitted — the policy behind the connection gater wired in
@@ -922,7 +967,7 @@ export declare class CadreNode implements SAppIdLookup {
922
967
  * accepted it, exactly like the unreplicated-membership-row case below.
923
968
  *
924
969
  * When every check falls through, the verdict depends on whether this node
925
- * runs the circuit-relay server ({@link relayServerEnabled}): without one,
970
+ * runs the circuit-relay server ({@link relayServer}): without one,
926
971
  * `'deny'`; with one, `'admit-for-relay'` — a circuit-relay reservation is
927
972
  * established by the reserving peer DIALING the relay, so a connection deny
928
973
  * here kills the reservation, and that deny is NOT self-healing: an outbound
@@ -1089,11 +1134,24 @@ export declare class CadreNode implements SAppIdLookup {
1089
1134
  */
1090
1135
  private getBootstrapPeerIds;
1091
1136
  /**
1092
- * What the strand watcher polls: this party's control rows plus a row per remembered
1093
- * cross-party join (see {@link joinedStrandRows}), so a joined strand is re-offered
1094
- * and relaunched on the same path as the party's own.
1137
+ * What the strand watcher polls: this party's control rows plus a row per cross-party
1138
+ * join (see {@link joinedStrands}), so a joined strand is offered and relaunched on
1139
+ * the same path as the party's own.
1095
1140
  */
1096
1141
  private createStrandQueryable;
1142
+ /**
1143
+ * The party-wide `JoinedStrand` table as {@link joinedStrands} reads and writes it.
1144
+ * Reads {@link controlDatabase} at call time, so start-up order does not matter.
1145
+ */
1146
+ private createPartyJoinedStrandLedger;
1147
+ /**
1148
+ * This machine's owner signing key when it can make owner-gated control writes: its seed
1149
+ * bootstrap holds an owner key (`SeedBootstrapService.canAuthorize`) and the party's
1150
+ * `OwnerKey` table enrolls it. A phone that ran self-genesis after another machine
1151
+ * founded the party passes the first test and not the second (`runOwnerGenesis` in the
1152
+ * reference apps), and a write it signed would only be refused.
1153
+ */
1154
+ private enrolledOwnerSigningKey;
1097
1155
  /**
1098
1156
  * Schedule the node's initial self-record publish + ongoing refresh shortly
1099
1157
  * after start (non-blocking). {@link registerSelf} is idempotent and safely
@@ -1417,6 +1475,17 @@ export declare class CadreNode implements SAppIdLookup {
1417
1475
  * test that drives dead addresses on purpose keeps the duration it chose.
1418
1476
  */
1419
1477
  private controlDialBudget;
1478
+ /**
1479
+ * The link-derived limits shared by every {@link SeedBootstrapService} this node builds: its
1480
+ * owner and invite dials ({@link controlDialBudget}) and its seed delivery deadline, derived
1481
+ * from `network.linkRoundTripMs`. One helper so the four construction sites cannot drift.
1482
+ */
1483
+ private seedServiceBudgets;
1484
+ /**
1485
+ * {@link collectStrandAddrs} at this node's declared `network.linkRoundTripMs`, so each ask's
1486
+ * deadline derives from it. One helper so the four call sites cannot drift.
1487
+ */
1488
+ private collectSiblingStrandAddrs;
1420
1489
  /**
1421
1490
  * Dial one sibling from its already-resolved addresses, best-effort. Returns
1422
1491
  * whether the dial resolved (false when no address resolves or the dial fails).
@@ -1425,7 +1494,9 @@ export declare class CadreNode implements SAppIdLookup {
1425
1494
  * the {@link controlDialBudget} expiring) is logged and swallowed so one
1426
1495
  * unreachable sibling never aborts the pass — exactly like
1427
1496
  * {@link SeedBootstrapService.applySeed}'s owner-dial loop. A failed dial is
1428
- * simply retried on the next pass.
1497
+ * simply retried on the next pass. A sibling whose every address relays
1498
+ * through this node ({@link SelfRelayOnlyError}) is not dialed at all and gets
1499
+ * a one-line log instead: only the sibling can reconnect.
1429
1500
  */
1430
1501
  private dialControlSibling;
1431
1502
  /**
@@ -1815,6 +1886,37 @@ export declare class CadreNode implements SAppIdLookup {
1815
1886
  */
1816
1887
  expireDeviceToken(peerId: string): Promise<void>;
1817
1888
  private handleStrandAdded;
1889
+ /**
1890
+ * Offer a strand no local config claims to the hosting app as `strand:discovered`, so
1891
+ * it can decide whether to join (register a config + addStrand); the strand-agnostic
1892
+ * seam keeps this class free of any app's join policy.
1893
+ *
1894
+ * Once per strand: a storage-replica launch that fails is retried by the watcher
1895
+ * (`StrandWatcher.forgetStrand` + backoff), and each retry comes back through
1896
+ * {@link handleStrandAdded} with no config — the app must still see one announcement.
1897
+ * The other thing that un-retains a strand in the watcher (a failed `addStrand`) leaves
1898
+ * the sApp config registered, so that retry takes the claimed branch instead.
1899
+ *
1900
+ * Recorded BEFORE the emit so a handler that synchronously drains
1901
+ * `getDiscoveredStrands()` sees this strand too. So this map — not the event — is what
1902
+ * a late subscriber reads. See the `strand:discovered` doc in types.ts.
1903
+ *
1904
+ * NOTE: a replica launch that keeps failing leaves the watcher not tracking the strand,
1905
+ * so if its control row is removed during the backoff no `handleStrandRemoved` arrives
1906
+ * and this entry outlives the row. Needs a persistently failing launch AND a removal
1907
+ * inside the backoff; if it is ever seen, have the watcher report removal of rows it
1908
+ * forgot, since `detachStrand` is already a no-op for an untracked instance.
1909
+ */
1910
+ private announceDiscoveredStrand;
1911
+ /**
1912
+ * Record the sApp id a storage replica's `Strand.Header` names, for {@link getSAppId}.
1913
+ * Runs whenever a replica's database may have just been published (launch, the
1914
+ * first-sync gate opening, a wake); a no-op for a claimed strand, an unpublished
1915
+ * database, or an id already recorded. Never throws: a failed read logs and leaves the
1916
+ * id unknown, so an `sAppId` filter keeps the strand provisionally admitted — a
1917
+ * syncing replica, which is harmless.
1918
+ */
1919
+ private recordReplicaSAppId;
1818
1920
  private handleStrandRemoved;
1819
1921
  /**
1820
1922
  * Return this node to its pre-{@link start} state. The ONE teardown — `stop()` and
@@ -1835,10 +1937,10 @@ export declare class CadreNode implements SAppIdLookup {
1835
1937
  private handleStrandIdle;
1836
1938
  /**
1837
1939
  * Hibernate a strand: release its strand-network resources via the strand
1838
- * manager (stop the libp2p node, close the StrandDatabase) and mark it
1940
+ * manager (stop the libp2p node, close the StrandDatabase), which marks it
1839
1941
  * `hibernating`. A quiesced strand holds no open strand-network connections,
1840
- * transports, or DB handles. No-ops if the strand is missing; if already
1841
- * quiesced (defensive), just marks status and emits.
1942
+ * transports, or DB handles. No-ops if the strand is missing; one already quiesced
1943
+ * is only marked, and still emits.
1842
1944
  */
1843
1945
  private handleStrandHibernate;
1844
1946
  /**
@@ -1847,15 +1949,42 @@ export declare class CadreNode implements SAppIdLookup {
1847
1949
  * its runtime via the strand manager. If it is still live (e.g. waking an idle
1848
1950
  * strand, which retains its resources), just flip the status. Overlapping wake
1849
1951
  * triggers are coalesced upstream by `HibernationManager`, so this runs once
1850
- * per wake; `resumeStrand` is itself idempotent as a backstop.
1952
+ * per wake; a wake racing a check-in joins the check-in's rebuild in `resumeStrand`.
1953
+ *
1954
+ * A failed rebuild re-hibernates the strand (see {@link rehibernateIfResumeFailed});
1955
+ * every failure rethrows, so the waker still sees the error.
1956
+ *
1957
+ * Records no activity itself: whoever asked for the wake did ({@link wakeStrand},
1958
+ * `HibernationManager.recordActivity`), and {@link serviceWake}'s own probe must not.
1851
1959
  */
1852
1960
  private handleStrandWake;
1961
+ /**
1962
+ * Undo a failed {@link resumeStrandRuntime}, but only when the rebuild itself failed — this
1963
+ * caller's, or the one it joined in `resumeStrand` — which leaves the instance `'error'`. A
1964
+ * failure before the rebuild (the cohort seed read) built nothing, and may find the strand
1965
+ * mid-build or just brought up by a concurrent wake or check-in; that runtime belongs to
1966
+ * whoever is building or holding it, so quiescing it here would stop a node still in use.
1967
+ */
1968
+ private rehibernateIfResumeFailed;
1969
+ /**
1970
+ * Put a strand whose rebuild or wake window failed back to `'hibernating'`, through a
1971
+ * best-effort quiesce that releases any runtime left up. `resumeStrand` leaves a failed
1972
+ * strand `'error'`, which nothing retries: `HibernationManager` wakes only
1973
+ * `idle`/`hibernating` strands, and reads any other status after a check-in as "woke",
1974
+ * ending its chain. Safe to run twice for one failure (a wake that joined a failed
1975
+ * check-in's rebuild): quiescing a quiesced strand only marks it.
1976
+ *
1977
+ * The quiesce writes the status itself, ordered before any resume requested meanwhile;
1978
+ * this writes it only when the quiesce failed, since a failed release leaves the status
1979
+ * alone.
1980
+ */
1981
+ private rehibernateAfterFailedResume;
1853
1982
  /**
1854
1983
  * Rebuild a quiesced strand's runtime, re-resolving the volatile cohort input
1855
1984
  * first: the discovery seed may have grown since the strand last ran. Shared by
1856
1985
  * the wake (`handleStrandWake`) and check-in (`handleStrandCheckIn`) paths so both
1857
- * apply the same fresh resolution. `resumeStrand` is idempotent
1858
- * (returns the live instance unchanged) as a backstop against double-resume.
1986
+ * apply the same fresh resolution. `resumeStrand` returns a live instance unchanged
1987
+ * and joins a rebuild already in flight, so the two paths never build twice.
1859
1988
  */
1860
1989
  private resumeStrandRuntime;
1861
1990
  /**
@@ -1884,15 +2013,17 @@ export declare class CadreNode implements SAppIdLookup {
1884
2013
  * Window-then-decide for a just-resumed strand, shared by the check-in timer
1885
2014
  * path ({@link handleStrandCheckIn}) and the on-demand {@link serviceWake}:
1886
2015
  *
1887
- * 1. Capture the post-resume activity marker. `recordActivity` assigns a
1888
- * FRESH `Date`, so a changed reference after the window means real
1889
- * activity landed during it — not millisecond-resolution noise.
1890
- * 2. Hold the strand live for `windowMs` so its strand network reaches the
2016
+ * 1. Hold the strand live for `windowMs` so its strand network reaches the
1891
2017
  * cohort and the app can drive pull-on-read activity.
1892
- * 3. If activity landed, leave the strand `active` (return `true`); otherwise
1893
- * quiesce and mark it `hibernating` again (return `false`).
2018
+ * 2. If activity landed since `activityMark`, leave the strand `active` (return
2019
+ * `true`); otherwise quiesce it, which marks it `hibernating` again (return `false`).
2020
+ * A wake or activity that lands during that quiesce rebuilds the strand after it.
1894
2021
  *
1895
- * @returns whether activity was observed during the window (strand left active).
2022
+ * @param activityMark - `instance.lastActivity` as the caller read it BEFORE bringing the
2023
+ * strand up. The bring-up records none, and every writer assigns a FRESH `Date`, so a
2024
+ * changed reference means a wake or activity landed during the resume or the window —
2025
+ * not millisecond-resolution noise.
2026
+ * @returns whether activity was observed (strand left active).
1896
2027
  */
1897
2028
  private runWakeWindow;
1898
2029
  /**
@@ -1923,9 +2054,10 @@ export declare class CadreNode implements SAppIdLookup {
1923
2054
  * published (e.g. re-attaching its own orphan after a restart) founds it, and attaching
1924
2055
  * anyone else's row joins, without the caller needing to know which it is.
1925
2056
  *
1926
- * A joined row (no `founder: true`) this party's control database does not hold — a
1927
- * strand joined from another party — is remembered in the node's joined-strand store, so it is re-offered as
1928
- * `strand:discovered` after a restart (see {@link forgetJoinedStrand}).
2057
+ * A joined row (no `founder: true`) this party's control database does not name — a
2058
+ * strand joined from another party — is remembered in the node's joined-strand store, so it
2059
+ * is re-offered as `strand:discovered` after a restart, and published party-wide by an
2060
+ * owner machine's reconcile pass (see {@link forgetJoinedStrand}).
1929
2061
  *
1930
2062
  * A rejected call leaves nothing running but DOES leave the sApp config
1931
2063
  * registered, deliberately: both an explicit retry and the {@link StrandWatcher}'s
@@ -2104,7 +2236,7 @@ export declare class CadreNode implements SAppIdLookup {
2104
2236
  * — and with it the consent re-seat foreclosure — does propagate; but the physical row
2105
2237
  * deletion cannot be replayed, and `queryStrands` reads raw (no retired-stamp filter),
2106
2238
  * so siblings that already hold the row keep running the strand until the collection
2107
- * itself converges (logged loudly; see the delete-while-alone durability note in
2239
+ * itself converges (logged loudly; see "Deletes made while alone" in
2108
2240
  * docs/architecture.md).
2109
2241
  *
2110
2242
  * A no-op (no throw, no tombstone) when the row is already absent — but a
@@ -2167,10 +2299,10 @@ export declare class CadreNode implements SAppIdLookup {
2167
2299
  * NOTE: "founding machine" here is the ROW's provenance, not the launch's resolved
2168
2300
  * `founder` flag — so an explicit `founder: false` over a row this machine published
2169
2301
  * still mints, an owner-signed write a caller that said "I am not founding" did not ask
2170
- * for. Harmless (the party needs that identity for the strand either way, and the mint
2171
- * is insert-if-absent) and unreachable today: the one explicit `founder: false` caller
2172
- * passes a row with a null `FounderOwnerKey`. If a caller ever pairs `founder: false`
2173
- * with its own published row, gate the mint on the resolved flag too.
2302
+ * for. Reached by a storage replica of a row this machine published (always a joiner,
2303
+ * see {@link launchStrand}) and kept on purpose: the party needs that identity for the
2304
+ * strand either way, the mint is insert-if-absent, and a closed-strand replica runs its
2305
+ * membership reconciler — which seats its own `MemberPeer` binding — only with a party key.
2174
2306
  */
2175
2307
  private resolveStrandPartyKey;
2176
2308
  /**
@@ -2291,6 +2423,15 @@ export declare class CadreNode implements SAppIdLookup {
2291
2423
  * A founder launch refused as pre-split (`PreSplitStrandIdentityError`) is recorded in
2292
2424
  * {@link strandLaunchRefusals} for the formation arm, then rethrown; a founder launch
2293
2425
  * that succeeds clears the record (it ran the bootstrap's pre-split check and passed).
2426
+ *
2427
+ * An absent `sAppConfig` launches a storage replica ({@link CadreNodeConfig.hostUnclaimedStrands}),
2428
+ * which is ALWAYS a joiner whatever the row says: the founder bootstrap writes the sApp
2429
+ * into `Strand.Header`, and a replica has none. So a self-founded row whose watcher poll
2430
+ * wins the race against its app's `addStrand` after a restart comes up as a replica — and
2431
+ * so does a `foundStrand` whose publish a watcher poll saw before its attach. A claim
2432
+ * that finds a tracked replica upgrades it in place, over the same node and store
2433
+ * ({@link StrandInstanceManager.attachSApp}), BEFORE any founding: the founder bootstrap
2434
+ * needs the sApp the attach supplies.
2294
2435
  */
2295
2436
  private launchStrand;
2296
2437
  /**
@@ -2298,6 +2439,19 @@ export declare class CadreNode implements SAppIdLookup {
2298
2439
  * tracked instance in place, or start a fresh one.
2299
2440
  */
2300
2441
  private startOrFoundStrand;
2442
+ /**
2443
+ * {@link startOrFoundStrand} for a strand the manager already tracks: a claim of a
2444
+ * storage replica gives it the app's schema in place, then a founder request founds it,
2445
+ * waking a quiesced instance so the bootstrap has run before this resolves. Emits
2446
+ * nothing — `strand:started` fired when the instance launched.
2447
+ *
2448
+ * A launch of the strand still in flight (the watcher's launch racing an app's
2449
+ * `addStrand`, in either order) is waited out first: until its build settles the tracked
2450
+ * instance is `'starting'` with no database, and returning it would resolve `addStrand`
2451
+ * with a strand the app cannot use — and skip that call's first-sync wait, which only a
2452
+ * `'syncing'` instance gets. If that launch fails, this rejects too: its record is gone.
2453
+ */
2454
+ private claimTrackedStrand;
2301
2455
  /**
2302
2456
  * Resolve a strand's discovery seed — the dialable strand-network multiaddr
2303
2457
  * strings for cohort siblings. Membership comes from the control network's
@@ -2320,7 +2474,7 @@ export declare class CadreNode implements SAppIdLookup {
2320
2474
  * relay reservation — the gate denial there is fatal, not degraded. A
2321
2475
  * party-member relay answers the RPC (its control node admits us as a
2322
2476
  * member); a dedicated ops/ relay does not speak the protocol and the
2323
- * per-peer failure folds to `[]` — harmless, it has no membership gate and
2477
+ * per-peer failure comes back `unreachable` — harmless, it has no membership gate and
2324
2478
  * needs no grant. The relay's direct addr rides along as the dial fallback
2325
2479
  * for a relay we are not yet connected to.
2326
2480
  */
@@ -2401,9 +2555,10 @@ export declare class CadreNode implements SAppIdLookup {
2401
2555
  * Record the announce timestamps {@link refreshDelegateGrants} throttles on,
2402
2556
  * for every relay a delegate-carrying announce pass dialed.
2403
2557
  *
2404
- * Recorded OPTIMISTICALLY at announce time: `collectStrandAddrs` folds
2405
- * per-peer failure to `[]` and reports no per-peer success, and threading
2406
- * success out would change its API for little gain. A failed INITIAL announce
2558
+ * Recorded OPTIMISTICALLY at announce time, whatever `collectStrandAddrs`
2559
+ * reports per peer: a dedicated `ops/` relay never speaks the strand-addr
2560
+ * protocol, so recording only on success would re-announce to it on every
2561
+ * reconcile tick, one wasted protocol negotiation each. A failed INITIAL announce
2407
2562
  * costs the strand supervisor its first attempt only (the relay denies the
2408
2563
  * reservation; every re-drive re-announces first through
2409
2564
  * {@link announceDelegateToRelay}); a failed REFRESH retries within
@@ -2441,16 +2596,16 @@ export declare class CadreNode implements SAppIdLookup {
2441
2596
  * again right away.
2442
2597
  *
2443
2598
  * Against a dedicated ops relay (no strand-addr RPC) the request fails per-peer
2444
- * and `collectStrandAddrs` folds it to `[]`: one wasted protocol negotiation per
2599
+ * and `collectStrandAddrs` reports it `unreachable`: one wasted protocol negotiation per
2445
2600
  * re-drive attempt, bounded by the supervisor's backoff. Never throws on that
2446
2601
  * path; a relay addr that names no peer id is logged and skipped (the hook's
2447
2602
  * caller runs the drive regardless).
2448
2603
  *
2449
2604
  * NOTE: against a relay that is DOWN this hook costs up to two strand-addr
2450
- * timeouts (10 s each: dial by peer id, then by addr) before the reservation drive even
2451
- * starts, so one failed re-drive holds the supervisor `driving` for those 20 s plus the
2452
- * drive's own deadline — 34 s at the default declared link round trip, and longer on a host
2453
- * that declared a slower one (`link-budget.ts`).
2605
+ * timeouts (dial by peer id, then by addr; 23 s each at the default declared link round trip)
2606
+ * before the reservation drive even starts, so one failed re-drive holds the supervisor
2607
+ * `driving` for those 46 s plus the drive's own deadline — 64 s at the default, and longer on
2608
+ * a host that declared a slower one (`link-budget.ts`).
2454
2609
  * Bounded and harmless while the relay is unreachable anyway; if recovery
2455
2610
  * latency after a relay comes back ever matters, skip the announce when the
2456
2611
  * control node holds no connection to the relay (the drive's own dial fails
@@ -2460,9 +2615,10 @@ export declare class CadreNode implements SAppIdLookup {
2460
2615
  /** One strand's share of {@link refreshDelegateGrants}: announce to the relays whose grant is due. */
2461
2616
  private announceDelegateToDueRelays;
2462
2617
  /**
2463
- * Re-resolve each running strand's SIBLING addresses over the control mesh and
2464
- * merge them into that strand's own libp2p address book, throttled to once per
2465
- * {@link STRAND_PEER_ADDR_REFRESH_MS} per strand.
2618
+ * Keep each running strand's own libp2p address book warm: re-merge its strand
2619
+ * peer book on every pass, and re-ask each connected SIBLING for its strand
2620
+ * addresses over the control mesh when that (sibling, strand) is due
2621
+ * ({@link strandAddrAskDueAt}).
2466
2622
  *
2467
2623
  * Without this, a strand's address book is written exactly once — the
2468
2624
  * launch/resume seed — and everything below cadre-core that dials a strand peer
@@ -2472,6 +2628,12 @@ export declare class CadreNode implements SAppIdLookup {
2472
2628
  * re-resolved, and even the original seed addresses fall off at the peerStore's
2473
2629
  * one-hour expiry (see `peer-addr-book.ts`).
2474
2630
  *
2631
+ * Due times are per (sibling, strand) and set from each sibling's own outcome, so
2632
+ * a sibling that connects late — a phone joining after the party's always-on
2633
+ * machines — is asked on the next tick, and one that could not answer or refused
2634
+ * (its view of the membership may not include us yet) is retried within
2635
+ * {@link STRAND_PEER_ADDR_RETRY_MS} rather than {@link STRAND_PEER_ADDR_REFRESH_MS}.
2636
+ *
2475
2637
  * Distinct from {@link refreshDelegateGrants}, deliberately: that pass covers
2476
2638
  * RELAYS on a `DELEGATE_GRANT_TTL_MS / 2` throttle to keep circuit-relay
2477
2639
  * admission grants alive, this one covers SIBLINGS on an address-expiry
@@ -2485,20 +2647,49 @@ export declare class CadreNode implements SAppIdLookup {
2485
2647
  */
2486
2648
  private refreshStrandPeerAddrs;
2487
2649
  /**
2488
- * One strand's share of {@link refreshStrandPeerAddrs}: RPC the siblings, union their
2489
- * answers with this strand's peer-book addresses, and merge the lot into the strand's
2490
- * address book. Errors are logged and swallowed so one strand's failure never costs
2491
- * the others their refresh.
2650
+ * The siblings {@link refreshStrandPeerAddrs} may ask this pass: the connected
2651
+ * cohort, or none when enumeration fails (logged, never thrown).
2652
+ *
2653
+ * `connectedSiblingTargets`' membership read is unbounded, and with zero
2654
+ * connections its answer is empty whatever the table holds, so decide it from the
2655
+ * connection list instead of paying for the read once a tick.
2656
+ *
2657
+ * NOTE: otherwise the read runs on EVERY tick with a running strand, not only when
2658
+ * some (sibling, strand) is due: pruning a departed sibling needs the current
2659
+ * target set, and whether anyone is due cannot be told without it (a connected
2660
+ * non-member never gets a due time to compare). One more `CadrePeer` read per 15 s
2661
+ * tick, on top of the two `runReconcileControlCohort` already makes; if those reads
2662
+ * get costly, share one row-set across the pass (see the NOTE there) rather than
2663
+ * skipping ticks here.
2664
+ *
2665
+ * NOTE: one strand-addr RPC per (running strand × due sibling) — each a tiny
2666
+ * request/response on an already-open control connection. If a node ever runs
2667
+ * MANY strands at once, batch the RPC to carry several strand ids per request
2668
+ * rather than one fan-out per strand.
2669
+ */
2670
+ private strandAddrRefreshTargets;
2671
+ /**
2672
+ * One strand's share of {@link refreshStrandPeerAddrs}: RPC the siblings that are
2673
+ * due, union their answers with this strand's peer-book addresses, and merge the
2674
+ * lot into the strand's address book. Errors are logged and swallowed so one
2675
+ * strand's failure never costs the others their refresh.
2492
2676
  *
2493
- * `targets` may be EMPTY — a solo node, or one whose only control peers are strangers.
2494
- * The peer-book addresses still have to be re-merged in that case: nothing re-resolves
2495
- * another party's addresses, so this periodic re-merge is the ONLY thing standing
2496
- * between them and the peerStore's one-hour expiry. A strand with neither a sibling to
2497
- * ask nor a book entry does nothing and leaves its throttle unstamped, so the next
2498
- * reconcile tick retries rather than sitting out the whole refresh interval having
2499
- * done nothing.
2677
+ * The peer book merges on EVERY pass, whether or not any sibling is due or
2678
+ * connected: nothing re-resolves another party's addresses, so this re-merge is the
2679
+ * ONLY thing standing between them and the peerStore's one-hour expiry — and it is
2680
+ * also how a re-formation's freshly carried addresses reach a running strand on the
2681
+ * next tick. A strand with nobody due and no book entry does nothing.
2500
2682
  */
2501
2683
  private refreshOneStrandPeerAddrs;
2684
+ /**
2685
+ * RPC `due` siblings for `strandId` and set each one's next due time from its own
2686
+ * outcome: an answer, even an empty one, waits the full refresh interval; anything
2687
+ * else retries in {@link STRAND_PEER_ADDR_RETRY_MS} (or the refresh interval, if
2688
+ * configured shorter). Stamped before the caller
2689
+ * re-checks that the strand is still running, so a strand torn down mid-pass still
2690
+ * records who was asked.
2691
+ */
2692
+ private askSiblingsForStrandAddrs;
2502
2693
  /**
2503
2694
  * Merge strand-network addresses into ONE strand node's libp2p address book,
2504
2695
  * attributed per peer. Best-effort throughout: an address-book write must never
@@ -2539,20 +2730,28 @@ export declare class CadreNode implements SAppIdLookup {
2539
2730
  */
2540
2731
  private getStrandMultiaddrs;
2541
2732
  /**
2542
- * Leave a strand joined from another party: forget its remembered join, so no later
2543
- * start re-offers it, then {@link stopStrand} it here. The "leave" a joiner has, since it
2544
- * cannot {@link unpublishStrand} a row its party's control database never held. For one
2545
- * of this party's own strands the forget is a no-op and this is just {@link stopStrand}.
2733
+ * Leave a strand joined from another party, for the whole party: remove its party-wide
2734
+ * `JoinedStrand` row (owner-signed, with a `Revocation` tombstone), then this machine's
2735
+ * unpublished record, its strand peer book entries and any session-kept entry, then
2736
+ * {@link stopStrand} it here. Every other machine's watcher then sees the row gone and
2737
+ * detaches the strand, a storage replica included; a machine offline at the time reaps the
2738
+ * row once the tombstone reaches it. The joiner's counterpart of {@link unpublishStrand},
2739
+ * which only works on a row the party's own `Strand` table holds. For one of those, or for
2740
+ * a join still local-only, there is no party-wide row and this is a local forget plus
2741
+ * {@link stopStrand}.
2742
+ *
2743
+ * The other members of the strand are not told, and this party's membership row in the
2744
+ * strand stays, so a later re-formation reuses the same identity.
2546
2745
  *
2547
- * Touches nothing beyond this node — the other members are not told, and this party's
2548
- * membership row in the strand stays.
2746
+ * @throws when a party-wide row exists and this machine cannot sign its removal (not an
2747
+ * enrolled owner) — {@link stopStrand} stops the strand on this machine only — or when
2748
+ * the party-wide table cannot be read.
2549
2749
  */
2550
2750
  forgetJoinedStrand(strandId: string): Promise<void>;
2551
2751
  /**
2552
- * Record `row` as a join from another party when this party's control database holds no
2553
- * row for it — the one case nothing else would re-offer after a restart. Skips the write
2554
- * when an identical record is already there (an app re-claiming a remembered join on
2555
- * every start), and keeps the first `joinedAt`.
2752
+ * Record `row` as a join from another party when this party's control database names it
2753
+ * neither as its own strand nor as a party-wide join — the one case nothing else would
2754
+ * re-offer after a restart (`JoinedStrandSession.rememberForeign`).
2556
2755
  *
2557
2756
  * Best-effort, for {@link addStrand}: a failure costs the strand its re-offer after the
2558
2757
  * next restart, not this attach, so it is reported and the attach goes on.
@@ -2567,16 +2766,25 @@ export declare class CadreNode implements SAppIdLookup {
2567
2766
  */
2568
2767
  private rememberForeignStrand;
2569
2768
  /**
2570
- * Self-revocation arm of the joined-strand record: a party removed from a strand must
2571
- * not re-attach it on every launch. The strand keeps running this session — the
2572
- * `strand:revoked` contract is that nothing is torn down for the app — and is gone
2573
- * after the next start. The join record is a no-op for this party's own strands, which
2574
- * have none; the strand peer book is forgotten either way, since a removed party must
2575
- * not keep dialing the strand's peers.
2769
+ * Self-revocation arm of the joined-strand records: a party removed from a strand must
2770
+ * not re-attach it on every launch of every machine. The strand keeps running this
2771
+ * session here — the `strand:revoked` contract is that nothing is torn down for the app —
2772
+ * and its party-wide row is queued for removal by the next connected owner reconcile pass
2773
+ * (`JoinedStrandSession.forgetAfterThisSession`). A no-op for this party's own strands,
2774
+ * which have neither record; the strand peer book is forgotten either way, since a
2775
+ * removed party must not keep dialing the strand's peers.
2776
+ *
2777
+ * NOTE: accepted tradeoff — a sibling machine that has not raised `strand:revoked` itself
2778
+ * detaches the strand (`strand:stopped`) when the party-wide removal reaches it, instead
2779
+ * of keeping it for the session, which weakens "nothing is stopped on the removed
2780
+ * machine's behalf" for siblings. Weighed against a party-wide row that brings a revoked
2781
+ * strand back on every start of every machine. Revisit if apps need the "you were
2782
+ * removed" screen on every device: the sibling could re-check its own revocation state
2783
+ * (`refreshRevocationEnforcement`) before detaching a vanished joined row.
2576
2784
  *
2577
2785
  * NOTE: a manager can re-admit a removed party directly (`addMemberByManager`), and the
2578
- * membership loop then finishes the join on its own — but the record is gone by then, so
2579
- * the next start does not re-attach the strand. Re-forming records it again. If direct
2786
+ * membership loop then finishes the join on its own — but the records are gone by then,
2787
+ * so the next start does not re-attach the strand. Re-forming records it again. If direct
2580
2788
  * re-admission becomes a routine flow, re-record the join when the revoked-peer gate
2581
2789
  * clears for this node.
2582
2790
  */
@@ -2661,7 +2869,10 @@ export declare class CadreNode implements SAppIdLookup {
2661
2869
  */
2662
2870
  notifyStrandActivity(strandId: string, reason?: string): void;
2663
2871
  /**
2664
- * Force wake a hibernating strand
2872
+ * Force wake a hibernating strand. A requested wake is activity, recorded before the
2873
+ * wake starts: a check-in holding the strand — or rebuilding it, a rebuild this wake then
2874
+ * joins — leaves it up instead of re-quiescing it at the end of its window. Once awake the
2875
+ * strand's idle countdown runs, so it hibernates again if nothing uses it.
2665
2876
  */
2666
2877
  wakeStrand(strandId: string): Promise<void>;
2667
2878
  /**
@@ -2672,7 +2883,7 @@ export declare class CadreNode implements SAppIdLookup {
2672
2883
  * Routes through {@link HibernationManager.forceHibernate}, which cancels the
2673
2884
  * strand's pending idle/hibernate (and check-in) timers — so a stale timer
2674
2885
  * can't re-fire on or resurrect the strand — then runs the same `onHibernate`
2675
- * path as the timer (`quiesceStrand` + `status='hibernating'` +
2886
+ * path as the timer (`quiesceStrand`, which marks it `hibernating`, then
2676
2887
  * `strand:hibernating`). Unlike the timer path it does NOT re-arm check-ins:
2677
2888
  * the strand stays down until the caller drives a wake (e.g. {@link serviceWake}).
2678
2889
  */
@@ -2697,9 +2908,14 @@ export declare class CadreNode implements SAppIdLookup {
2697
2908
  * strand share one in-flight operation ({@link serviceWakePromises}), and the
2698
2909
  * underlying resume coalesces with a racing push-wake via
2699
2910
  * {@link HibernationManager}'s wake coalescing — one runtime build, one window,
2700
- * one re-hibernate decision. Returns `{ serviced: false }` (never throws) when
2701
- * the node is not running or the strand is unknown, and surfaces a resume
2702
- * failure as `{ serviced: true, hadActivity: false }` after re-hibernating.
2911
+ * one re-hibernate decision. A wake or activity from elsewhere that lands during
2912
+ * the resume or the window leaves the strand up; this call's own wake does not.
2913
+ * Afterwards a strand left up has its idle countdown running, and a re-hibernated one
2914
+ * gets back the check-in chain this call interrupted — it never gains one it lacked,
2915
+ * so a strand the mobile runner force-hibernated stays down until the next push.
2916
+ * Returns `{ serviced: false }` (never throws) when
2917
+ * the node is not running or the strand is unknown, and surfaces a wake or
2918
+ * window failure as `{ serviced: true, hadActivity: false }`.
2703
2919
  *
2704
2920
  * @param strandId - the strand a push said has pending activity.
2705
2921
  * @param opts.windowMs - override the live-window duration (defaults to the
@@ -2710,6 +2926,12 @@ export declare class CadreNode implements SAppIdLookup {
2710
2926
  }): Promise<ServiceWakeResult>;
2711
2927
  /** Body of {@link serviceWake}; serialised per-strand by its coalescing guard. */
2712
2928
  private runServiceWake;
2929
+ /**
2930
+ * The wake and window of {@link runServiceWake}. A failure in either — the network
2931
+ * unreachable inside a Doze grant, say — must not throw out of a background task, so it
2932
+ * reports no activity. Each stage cleans up only what it owns.
2933
+ */
2934
+ private wakeAndHoldForService;
2713
2935
  /**
2714
2936
  * Get the control network node (for advanced use)
2715
2937
  */
@@ -2737,8 +2959,8 @@ export declare class CadreNode implements SAppIdLookup {
2737
2959
  *
2738
2960
  * BUDGET: the supervisor's first attempt is what `start()` waits on — deliberately the drive's
2739
2961
  * ordinary deadline rather than a boot-specific one. That deadline is now COUNTED, four link
2740
- * round trips at the declared `network.linkRoundTripMs` (14 s at its default, where it was a
2741
- * fixed 10 s): a healthy dial-plus-reserve is sub-second even over a WAN, so this is slack for
2962
+ * round trips at the declared `network.linkRoundTripMs` plus the relay's two admission
2963
+ * decisions (18 s at its default, where it was a fixed 10 s): a healthy dial-plus-reserve is sub-second even over a WAN, so this is slack for
2742
2964
  * a slow link, while going much longer would make a dead relay indistinguishable from a hung
2743
2965
  * start and much shorter would fail nodes on links that were merely slow. A host that declares
2744
2966
  * a slower link lengthens it without touching this path — `link-budget.ts`. The retries carry
@@ -2819,7 +3041,7 @@ export declare class CadreNode implements SAppIdLookup {
2819
3041
  *
2820
3042
  * @param ownerPrivateKey - The owner's private key (base64url encoded)
2821
3043
  */
2822
- initializeSeedBootstrap(ownerPrivateKey: string): void;
3044
+ initializeSeedBootstrap(ownerPrivateKey: string): Promise<void>;
2823
3045
  /**
2824
3046
  * Push the multiaddrs that future invites should advertise. Pass `null` to
2825
3047
  * revert to the libp2p-reported addresses (the default). The host calls this
@@ -2954,7 +3176,8 @@ export declare class CadreNode implements SAppIdLookup {
2954
3176
  * is reachable through its circuit-relay address), dials `WAKE_PROTOCOL`, sends
2955
3177
  * the {@link WakeRequest}, and returns the peer's {@link WakeAck}. The receiver
2956
3178
  * gates the request on cadre membership and only resumes a strand it already
2957
- * participates in.
3179
+ * participates in; it acks once it has decided, before the strand is up. The
3180
+ * dial deadlines derive from this node's `network.linkRoundTripMs`.
2958
3181
  *
2959
3182
  * @param targetPeerId - The hibernating cadre peer to wake.
2960
3183
  * @param strandId - The strand the caller knows has pending activity.
@@ -2967,11 +3190,23 @@ export declare class CadreNode implements SAppIdLookup {
2967
3190
  * This is for drone nodes that need to receive seeds without being an owner.
2968
3191
  * Does not require an owner key.
2969
3192
  */
2970
- enableSeedListener(): void;
3193
+ enableSeedListener(): Promise<void>;
2971
3194
  /**
2972
3195
  * Get the seed bootstrap service (for advanced use)
2973
3196
  */
2974
3197
  getSeedBootstrapService(): SeedBootstrapService | null;
3198
+ /**
3199
+ * Make `service` this node's seed service and register its inbound seed handler.
3200
+ * The field is set before the registration is awaited, so a concurrent
3201
+ * {@link enableSeedListener} finds it and does not register a second handler; a
3202
+ * failed registration puts the previous service back and rethrows.
3203
+ *
3204
+ * NOTE: libp2p's registrar stores the handler before its peer-store merge, so a
3205
+ * failed merge leaves SEED_PROTOCOL registered with no service owning it and a retry
3206
+ * here rejects as a duplicate; if peer-store writes can fail in practice, unhandle on
3207
+ * a non-duplicate failure (never on a duplicate — that handler belongs to someone else).
3208
+ */
3209
+ private installSeedBootstrapService;
2975
3210
  /**
2976
3211
  * The event callbacks every {@link SeedBootstrapService} this node owns is
2977
3212
  * wired with — shared by the owner-capable service ({@link initializeSeedBootstrap})
@@ -3162,7 +3397,7 @@ export declare class CadreNode implements SAppIdLookup {
3162
3397
  *
3163
3398
  * @param options - Configuration for the solicitation service
3164
3399
  */
3165
- initializeStrandSolicitation(options?: StrandSolicitationServiceOptions): void;
3400
+ initializeStrandSolicitation(options?: StrandSolicitationServiceOptions): Promise<void>;
3166
3401
  /**
3167
3402
  * Get the strand solicitation service (for advanced use)
3168
3403
  */
@@ -3185,7 +3420,9 @@ export declare class CadreNode implements SAppIdLookup {
3185
3420
  formStrand(invitation: OpenInvitation, disclosure?: StrandFormationDisclosure): Promise<FormStrandResult>;
3186
3421
  /**
3187
3422
  * Remember the strand a formation just joined (see {@link joinedStrandStore}), so it
3188
- * is re-offered after a restart even when the app is killed before its `addStrand`.
3423
+ * is re-offered after a restart even when the app is killed before its `addStrand`, and
3424
+ * published party-wide by the next connected owner reconcile pass. A re-join also
3425
+ * cancels a party-wide removal a self-revocation queued for the strand.
3189
3426
  * Last in {@link formStrand}, after the membership adoption, so a failure here leaves
3190
3427
  * the party key and staged invitation in place for the re-formation it asks for.
3191
3428
  *
@@ -3249,9 +3486,15 @@ export declare class CadreNode implements SAppIdLookup {
3249
3486
  * founder's publish/launch paths mint it, so this is a not-yet-converged sibling.)
3250
3487
  * The manager maps the throw to a clean retryable rejection BEFORE the formation
3251
3488
  * token is spent.
3252
- * - Closed host strand with no running local instance/database → throw, same mapping:
3253
- * a joiner admitted without an invitation would look joined and never become a
3254
- * member, and a responder not running the strand cannot serve its sync anyway.
3489
+ * - Closed host strand whose runtime is HIBERNATING or being quiesced → woken first
3490
+ * ({@link wakeHostStrandForFormation}, bounded by `signal`), then issued as below. In
3491
+ * every state the redemption counts as activity, so the host stays up for the
3492
+ * joiner's first sync.
3493
+ * - Closed host strand with no running local instance/database (never launched, still
3494
+ * starting, or a hibernating one whose wake failed or outran `signal`) →
3495
+ * throw, same mapping: a joiner admitted without an invitation would look joined and
3496
+ * never become a member, and a responder not running the strand cannot serve its sync
3497
+ * anyway.
3255
3498
  * - Closed host strand whose LIVE rows carry the pre-split fingerprint
3256
3499
  * (`assertNotPreSplitStrand`) → throw `PreSplitStrandIdentityError`, same mapping as
3257
3500
  * the recorded refusal. Covers the responders that never ran a refused founder launch:
@@ -3261,7 +3504,12 @@ export declare class CadreNode implements SAppIdLookup {
3261
3504
  * permanent diagnosis. Identity is checked BEFORE the runtime: it is the cheaper read
3262
3505
  * and the more actionable diagnosis when both are missing (a missing runtime is
3263
3506
  * transient, a missing identity is not), and it keeps the branch reachable without
3264
- * standing a strand runtime up.
3507
+ * standing a strand runtime up. For the same reason every control-database check runs
3508
+ * before a wake: a strand that cannot issue anyway is not woken.
3509
+ *
3510
+ * `signal` is the formation's provisioning budget. Once it has aborted nothing is
3511
+ * issued — the joiner has already been told to retry, and an invitation written now
3512
+ * would only sit in the strand until it expires.
3265
3513
  *
3266
3514
  * The invitation expires `MEMBERSHIP_INVITE_TTL_MS` from now — see that constant for
3267
3515
  * the slow-joiner / lost-result tradeoff.
@@ -3277,6 +3525,30 @@ export declare class CadreNode implements SAppIdLookup {
3277
3525
  * not a retry.
3278
3526
  */
3279
3527
  private issueStrandMembershipInvite;
3528
+ /**
3529
+ * Count a bound closed-strand redemption as activity on the host strand, and wake the
3530
+ * strand when it is HIBERNATING or being quiesced (its database is closing) so the
3531
+ * membership invitation can be issued. Only reached after the formation manager has
3532
+ * authorized the redemption (token, disclosure, outside approval, seat pre-check), so only
3533
+ * a caller already entitled to the strand's member key can cause a wake. No other state is
3534
+ * woken — never launched or still starting is not something this node recovers from on
3535
+ * demand; the caller's live-database check refuses those.
3536
+ *
3537
+ * The activity is recorded in every state, through the hibernation manager rather than
3538
+ * {@link recordStrandActivity}, whose push fan-out would wake this party's phones for
3539
+ * nothing. It keeps the host up for the joiner's first sync: a live strand's idle timer
3540
+ * restarts, a check-in window that happens to have the strand live sees activity and
3541
+ * leaves it up instead of re-quiescing it, and a hibernating strand has its idle →
3542
+ * hibernate timers re-armed once the wake leaves it `active`. The explicit
3543
+ * {@link wakeStrand} coalesces onto the wake `recordActivity` began, and still wakes when
3544
+ * `recordActivity` is a no-op (hibernation disabled but the strand force-hibernated, or
3545
+ * the manager stopped).
3546
+ *
3547
+ * Bounded by `signal` (the formation's provisioning budget): when it aborts first this
3548
+ * throws — a retryable rejection, token unspent — and leaves the wake running, so the
3549
+ * joiner's retry finds the strand live.
3550
+ */
3551
+ private wakeHostStrandForFormation;
3280
3552
  /**
3281
3553
  * Remember the responder's strand-network addresses for a strand this node just
3282
3554
  * formed in the strand peer book (see {@link strandPeerBookStore}), so the strand's