@serfab/cadre-core 1.7.0 → 1.9.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 (188) hide show
  1. package/README.md +8 -6
  2. package/dist/cadre-node.d.ts +527 -206
  3. package/dist/cadre-node.js +1084 -528
  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 +10 -10
  45. package/dist/index.js +25 -20
  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/node-local-snapshot.d.ts +6 -4
  59. package/dist/node-local-snapshot.js +7 -3
  60. package/dist/node-local-snapshot.js.map +1 -1
  61. package/dist/peer-dial.d.ts +24 -4
  62. package/dist/peer-dial.js +74 -5
  63. package/dist/peer-dial.js.map +1 -1
  64. package/dist/peer-join-backfill.d.ts +2 -10
  65. package/dist/peer-join-backfill.js +8 -8
  66. package/dist/peer-join-backfill.js.map +1 -1
  67. package/dist/push-fanout.d.ts +6 -0
  68. package/dist/push-fanout.js +6 -0
  69. package/dist/push-fanout.js.map +1 -1
  70. package/dist/relay-reservation.d.ts +3 -1
  71. package/dist/relay-reservation.js +3 -1
  72. package/dist/relay-reservation.js.map +1 -1
  73. package/dist/relay-server.d.ts +79 -0
  74. package/dist/relay-server.js +74 -0
  75. package/dist/relay-server.js.map +1 -0
  76. package/dist/seed-bootstrap.d.ts +57 -4
  77. package/dist/seed-bootstrap.js +107 -83
  78. package/dist/seed-bootstrap.js.map +1 -1
  79. package/dist/storage-scope.d.ts +33 -17
  80. package/dist/storage-scope.js +44 -23
  81. package/dist/storage-scope.js.map +1 -1
  82. package/dist/strand-addr-protocol.d.ts +58 -21
  83. package/dist/strand-addr-protocol.js +133 -85
  84. package/dist/strand-addr-protocol.js.map +1 -1
  85. package/dist/strand-database.d.ts +20 -2
  86. package/dist/strand-database.js +42 -5
  87. package/dist/strand-database.js.map +1 -1
  88. package/dist/strand-first-sync-gate.d.ts +59 -21
  89. package/dist/strand-first-sync-gate.js +58 -19
  90. package/dist/strand-first-sync-gate.js.map +1 -1
  91. package/dist/strand-formation-deadlines.d.ts +151 -0
  92. package/dist/strand-formation-deadlines.js +171 -0
  93. package/dist/strand-formation-deadlines.js.map +1 -0
  94. package/dist/strand-formation-manager.d.ts +63 -30
  95. package/dist/strand-formation-manager.js +67 -41
  96. package/dist/strand-formation-manager.js.map +1 -1
  97. package/dist/strand-formation-protocol.d.ts +45 -46
  98. package/dist/strand-formation-protocol.js +62 -126
  99. package/dist/strand-formation-protocol.js.map +1 -1
  100. package/dist/strand-fret-addrs.d.ts +54 -0
  101. package/dist/strand-fret-addrs.js +108 -0
  102. package/dist/strand-fret-addrs.js.map +1 -0
  103. package/dist/strand-instance-manager.d.ts +122 -44
  104. package/dist/strand-instance-manager.js +270 -114
  105. package/dist/strand-instance-manager.js.map +1 -1
  106. package/dist/strand-membership-writer.d.ts +6 -0
  107. package/dist/strand-membership-writer.js +11 -0
  108. package/dist/strand-membership-writer.js.map +1 -1
  109. package/dist/strand-network-state-file.d.ts +12 -0
  110. package/dist/strand-network-state-file.js +38 -0
  111. package/dist/strand-network-state-file.js.map +1 -0
  112. package/dist/strand-network-state.d.ts +111 -0
  113. package/dist/strand-network-state.js +191 -0
  114. package/dist/strand-network-state.js.map +1 -0
  115. package/dist/strand-revocation-enforcer.js +2 -1
  116. package/dist/strand-revocation-enforcer.js.map +1 -1
  117. package/dist/strand-solicitation.d.ts +36 -20
  118. package/dist/strand-solicitation.js +4 -4
  119. package/dist/strand-solicitation.js.map +1 -1
  120. package/dist/strand-wake-protocol.d.ts +80 -23
  121. package/dist/strand-wake-protocol.js +89 -64
  122. package/dist/strand-wake-protocol.js.map +1 -1
  123. package/dist/types.d.ts +148 -61
  124. package/dist/types.js +4 -3
  125. package/dist/types.js.map +1 -1
  126. package/package.json +11 -10
  127. package/src/cadre-node.ts +8400 -7776
  128. package/src/control-authorization.ts +2 -1
  129. package/src/control-cohort.ts +6 -0
  130. package/src/control-database.ts +122 -18
  131. package/src/control-formation-recorder.ts +354 -305
  132. package/src/control-read-retry.ts +14 -12
  133. package/src/control-schema.ts +67 -5
  134. package/src/control-stream.ts +25 -1
  135. package/src/control-write-retry.ts +13 -2
  136. package/src/delegate-admission.ts +23 -13
  137. package/src/diagnostics/connection-path.ts +0 -7
  138. package/src/formation-approval.ts +610 -604
  139. package/src/fs-atomic.ts +14 -4
  140. package/src/hibernation-manager.ts +638 -437
  141. package/src/index.ts +43 -65
  142. package/src/joined-strand-store.ts +247 -57
  143. package/src/key-store-file.ts +14 -5
  144. package/src/link-budget.ts +393 -74
  145. package/src/membership-connection-gater.ts +31 -45
  146. package/src/node-local-snapshot.ts +12 -7
  147. package/src/peer-dial.ts +81 -7
  148. package/src/peer-join-backfill.ts +10 -9
  149. package/src/push-fanout.ts +6 -0
  150. package/src/relay-reservation.ts +3 -1
  151. package/src/relay-server.ts +98 -0
  152. package/src/seed-bootstrap.ts +133 -91
  153. package/src/storage-scope.ts +44 -23
  154. package/src/strand-addr-protocol.ts +547 -458
  155. package/src/strand-database.ts +287 -242
  156. package/src/strand-first-sync-gate.ts +58 -19
  157. package/src/strand-formation-deadlines.ts +227 -0
  158. package/src/strand-formation-manager.ts +700 -647
  159. package/src/strand-formation-protocol.ts +106 -154
  160. package/src/strand-fret-addrs.ts +145 -0
  161. package/src/strand-instance-manager.ts +307 -140
  162. package/src/strand-membership-writer.ts +12 -0
  163. package/src/strand-network-state-file.ts +40 -0
  164. package/src/strand-network-state.ts +243 -0
  165. package/src/strand-revocation-enforcer.ts +2 -1
  166. package/src/strand-solicitation.ts +519 -500
  167. package/src/strand-wake-protocol.ts +401 -355
  168. package/src/types.ts +150 -61
  169. package/dist/strand-peer-book-file.d.ts +0 -12
  170. package/dist/strand-peer-book-file.js +0 -37
  171. package/dist/strand-peer-book-file.js.map +0 -1
  172. package/dist/strand-peer-book-protocol.d.ts +0 -159
  173. package/dist/strand-peer-book-protocol.js +0 -360
  174. package/dist/strand-peer-book-protocol.js.map +0 -1
  175. package/dist/strand-peer-book-swap.d.ts +0 -104
  176. package/dist/strand-peer-book-swap.js +0 -284
  177. package/dist/strand-peer-book-swap.js.map +0 -1
  178. package/dist/strand-peer-book.d.ts +0 -159
  179. package/dist/strand-peer-book.js +0 -354
  180. package/dist/strand-peer-book.js.map +0 -1
  181. package/dist/strand-peer-observer.d.ts +0 -88
  182. package/dist/strand-peer-observer.js +0 -180
  183. package/dist/strand-peer-observer.js.map +0 -1
  184. package/src/strand-peer-book-file.ts +0 -39
  185. package/src/strand-peer-book-protocol.ts +0 -463
  186. package/src/strand-peer-book-swap.ts +0 -338
  187. package/src/strand-peer-book.ts +0 -468
  188. package/src/strand-peer-observer.ts +0 -235
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Reading the signed address records a running strand node's FRET routing table holds,
3
+ * as per-peer addresses ready for that node's own address book (`peer-addr-book.ts`).
4
+ *
5
+ * Why a strand node needs this. libp2p's peerStore hides every address one hour after
6
+ * it was first observed (the `NOTE:` on `mergePeerAddrs`), and FRET does not re-apply
7
+ * the records it holds: it hands a record to the peerStore when a neighbour's snapshot
8
+ * delivers it or a saved table is imported, and the peerStore refuses a record whose
9
+ * sequence number it already has. So a strand node that has been up for more than an
10
+ * hour would have no address for a peer whose connection then drops, although FRET
11
+ * still holds that peer's record. `CadreNode.refreshStrandPeerAddrs` re-merges the
12
+ * records on every pass, which is what keeps another party's strand nodes dialable;
13
+ * own-party siblings are re-resolved separately, over the strand-addr RPC.
14
+ *
15
+ * How long a record is available here is FRET's rule, not this module's: while FRET
16
+ * holds the entry, and at most 14 days after the record was last confirmed (a peer
17
+ * connected at export time counts as confirmed then).
18
+ *
19
+ * Every record is opened and verified here rather than taken on FRET's word. A record
20
+ * is self-certifying, the table is reached through a structural type rather than an
21
+ * interface this package depends on, and the address book write is attributed from what
22
+ * the record proves, not from the id the table filed it under.
23
+ */
24
+ import debug from 'debug';
25
+ import { PeerRecord, RecordEnvelope } from '@libp2p/peer-record';
26
+ import { peerIdFromPublicKey } from '@libp2p/peer-id';
27
+ import { multiaddr } from '@multiformats/multiaddr';
28
+ import { fromString as uint8ArrayFromString } from 'uint8arrays';
29
+ import { withTrailingPeerId } from './peer-record.js';
30
+ const log = debug('sereus:cadre:strand-fret-addrs');
31
+ /**
32
+ * The addresses of every peer `host`'s FRET table holds a usable signed record for,
33
+ * `host` itself excluded. A node with no FRET service (a db-p2p build without it, a
34
+ * test double) reads as an empty table. One bad record never costs the others: it is
35
+ * counted in `rejected` and skipped.
36
+ *
37
+ * Throws only what `exportTable()` throws, which a caller treats like any other
38
+ * failure of a pass over a node that may be mid-teardown.
39
+ */
40
+ export async function strandFretPeerAddrs(host) {
41
+ const result = { peers: new Map(), rejected: 0 };
42
+ const fret = host.services?.fret;
43
+ if (!isFretTableSource(fret)) {
44
+ return result;
45
+ }
46
+ const selfId = host.peerId.toString();
47
+ for (const entry of fret.exportTable().entries) {
48
+ if (entry.id === selfId || entry.addressRecord === undefined) {
49
+ continue;
50
+ }
51
+ const addrs = await verifiedRecordAddrs(entry.id, entry.addressRecord.envelope);
52
+ if (addrs === null) {
53
+ result.rejected++;
54
+ }
55
+ else if (addrs.length > 0) {
56
+ result.peers.set(entry.id, addrs);
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ function isFretTableSource(service) {
62
+ return typeof service === 'object' && service !== null
63
+ && typeof service.exportTable === 'function';
64
+ }
65
+ /**
66
+ * The addresses one record lists, bound to `peerId`; null when the record must not be
67
+ * used. `openAndCertify` proves only that the envelope's own key signed it, so the
68
+ * signer and the peer the payload describes are each held against `peerId` here:
69
+ * without that, any peer could sign a record rewriting another peer's addresses.
70
+ */
71
+ async function verifiedRecordAddrs(peerId, envelope) {
72
+ try {
73
+ const opened = await RecordEnvelope.openAndCertify(uint8ArrayFromString(envelope, 'base64url'), PeerRecord.DOMAIN);
74
+ const record = PeerRecord.createFromProtobuf(opened.payload);
75
+ const signer = peerIdFromPublicKey(opened.publicKey).toString();
76
+ const subject = record.peerId.toString();
77
+ if (signer !== peerId || subject !== peerId) {
78
+ log('skipping the address record filed under %s: signed by %s about %s', peerId, signer, subject);
79
+ return null;
80
+ }
81
+ return bindToPeer(record.multiaddrs.map((addr) => addr.toString()), peerId);
82
+ }
83
+ catch (error) {
84
+ log('skipping the address record filed under %s: %o', peerId, error);
85
+ return null;
86
+ }
87
+ }
88
+ /**
89
+ * A record names its peer once, in `peerId`, so its addresses carry no trailing
90
+ * `/p2p/<peerId>` and a relay-only peer's circuit address ends at the relay hop
91
+ * (`…/p2p/<relay>/p2p-circuit`). Each is given the suffix that makes it a complete
92
+ * dial target; one already terminating in a different peer id does not reach this peer
93
+ * and is dropped.
94
+ *
95
+ * Takes strings because `@libp2p/peer-record` carries its own `@multiformats/multiaddr`
96
+ * copy, a structurally different type from the one `withTrailingPeerId` takes.
97
+ */
98
+ function bindToPeer(addrs, peerId) {
99
+ const bound = [];
100
+ for (const addr of addrs) {
101
+ const withPeer = withTrailingPeerId(multiaddr(addr), peerId);
102
+ if (withPeer !== null) {
103
+ bound.push(withPeer);
104
+ }
105
+ }
106
+ return bound;
107
+ }
108
+ //# sourceMappingURL=strand-fret-addrs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strand-fret-addrs.js","sourceRoot":"","sources":["../src/strand-fret-addrs.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,6 @@
1
1
  import type { PrivateKey } from '@libp2p/interface';
2
2
  import { type PeerJoinBackfillConfig } from './peer-join-backfill.js';
3
- import { type StrandPeerObservation } from './strand-peer-observer.js';
4
- import type { StrandPeerBookStore } from './strand-peer-book.js';
3
+ import { type StrandNetworkStateStore } from './strand-network-state.js';
5
4
  import { type StrandRevocationEnforcementConfig } from './strand-revocation-enforcer.js';
6
5
  import { type PendingMembershipInviteSource, type StrandMembershipReconciliationConfig } from './strand-membership-reconciler.js';
7
6
  import { type StrandFirstSyncConfig } from './strand-first-sync-gate.js';
@@ -34,8 +33,15 @@ export declare function liveStrandStatus(instance: StrandInstance): 'active' | '
34
33
  */
35
34
  export interface StartStrandConfig {
36
35
  strandRow: StrandRow;
37
- /** sApp configuration provided by the hosting application */
38
- sAppConfig: SAppConfig;
36
+ /**
37
+ * sApp configuration provided by the hosting application. Absent for a **storage
38
+ * replica** — a strand this node stores and serves without the app installed
39
+ * (`CadreNodeConfig.hostUnclaimedStrands`): no schema signature check, no `App`
40
+ * schema, no `sAppInfo` on the instance, and the default latency hint. A replica is
41
+ * always a joiner; `StrandDatabase` refuses to found one. An app that later claims the
42
+ * strand upgrades the running replica in place ({@link StrandInstanceManager.attachSApp}).
43
+ */
44
+ sAppConfig?: SAppConfig;
39
45
  storage?: StorageConfig;
40
46
  network?: NetworkConfig;
41
47
  profile: NodeProfile;
@@ -203,25 +209,14 @@ export interface StartStrandConfig {
203
209
  */
204
210
  announceDelegateToRelay?: (strandId: string, relayAddr: string, delegatePeerId: string) => Promise<void>;
205
211
  /**
206
- * Called when the strand's libp2p node identifies a STRAND peer — one whose
207
- * identify result names this strand's block-transfer protocol, so never the
208
- * circuit relay or a bootstrap node — with the addresses it is dialable at
209
- * (`strand-peer-observer.ts`). Throttled per peer; must not throw. `CadreNode`
210
- * wires it to the node-local strand peer book, which is what lets a restarted
211
- * machine dial the peers it was talking to. Absent ⇒ nothing is observed.
212
- * Retained with the launch config, so a hibernation wake re-arms it on the
213
- * rebuilt node.
214
- */
215
- onStrandPeerIdentified?: (strandId: string, observation: StrandPeerObservation) => void;
216
- /**
217
- * The node-local strand peer book the book SWAP reads and writes
218
- * (`strand-peer-book-swap.ts`): the strand node signs its own addresses with
219
- * {@link privateKey}, exchanges signed entries with every strand peer it identifies,
220
- * and files what verifies. Absent ⇒ no swap runs — a peer that dials this node's
221
- * swap handler gets no answer. `CadreNode` passes its book store. Retained with the
222
- * launch config, so a hibernation wake re-arms the swap on the rebuilt node.
223
- */
224
- strandPeerBook?: StrandPeerBookStore;
212
+ * The node-local strand network state (`strand-network-state.ts`): this strand's
213
+ * slice of it is handed to db-p2p as `persistence`, so the strand node saves its
214
+ * FRET routing table (with each peer's signed address record) on every connection
215
+ * and re-imports it when it is next built. Absent ⇒ nothing is saved and the node
216
+ * starts with an empty table. `CadreNode` passes its store. Retained with the launch
217
+ * config, so a hibernation wake rebuilds the node over the same state.
218
+ */
219
+ networkState?: StrandNetworkStateStore;
225
220
  }
226
221
  /**
227
222
  * Volatile inputs re-resolved when resuming a quiesced strand — the ones that can
@@ -281,23 +276,6 @@ export declare class StrandInstanceManager {
281
276
  * missed writes).
282
277
  */
283
278
  private backfills;
284
- /**
285
- * The per-strand peer-book observation writer (`strand-peer-observer.ts`), keyed
286
- * by strand id. Same lifecycle rationale as {@link backfills}: started in
287
- * `buildStrandRuntime` right after the libp2p node exists, stopped and dropped in
288
- * `releaseRuntime`, so quiesce → resume re-arms it on the rebuilt node. Only
289
- * present when the launch config supplied `onStrandPeerIdentified`.
290
- */
291
- private peerObservers;
292
- /**
293
- * Per-strand strand peer book SWAP driver ({@link StrandPeerBookSwap}) — the signed
294
- * exchange of book entries between strand peers. Same lifetime as the peer-book
295
- * observer above: armed in `buildStrandRuntime` right after the libp2p node exists
296
- * (its protocol handler must be registered before an early peer dials it), stopped
297
- * and dropped in `releaseRuntime`. Only present when the launch config supplied
298
- * `strandPeerBook`.
299
- */
300
- private peerBookSwaps;
301
279
  /**
302
280
  * The per-strand revoked-peer enforcer (closed strands only), keyed by strand
303
281
  * id. Same lifecycle rationale as {@link backfills}: created in
@@ -364,6 +342,23 @@ export declare class StrandInstanceManager {
364
342
  * wake becomes the better trade.
365
343
  */
366
344
  private strandStorages;
345
+ /**
346
+ * The runtime build in flight per strand id (`buildStrandRuntime`, from `startStrand` or
347
+ * `resumeStrand`), present until it settles. The instance is tracked for the whole build,
348
+ * with its database not yet constructed or not yet initialized, so a caller that must act
349
+ * on the FINISHED runtime ({@link attachSApp}, {@link whenRuntimeBuilt}) waits on this instead. A failed build's
350
+ * rejection belongs to the call that started it.
351
+ */
352
+ private runtimeBuilds;
353
+ /**
354
+ * The latest resume or quiesce requested per strand id, present until it settles. Each one
355
+ * starts only once the one requested before it has settled, so a wake that lands during a
356
+ * quiesce rebuilds after it, and a quiesce requested during a rebuild releases what that
357
+ * rebuild finished. The WHOLE operation, not its entry in {@link runtimeBuilds}: that settles
358
+ * before a resume's own catch records `'error'`, so a joiner awaiting it could read a stale
359
+ * status.
360
+ */
361
+ private transitions;
367
362
  private stopping;
368
363
  constructor();
369
364
  /**
@@ -395,6 +390,18 @@ export declare class StrandInstanceManager {
395
390
  * hibernation wake never re-enters the embedder's provider.
396
391
  */
397
392
  startStrand(config: StartStrandConfig): Promise<StrandInstance>;
393
+ /** Hold `build` in {@link runtimeBuilds} until it settles, and settle as it does. */
394
+ private trackRuntimeBuild;
395
+ /**
396
+ * Wait until no runtime build of `strandId` is in flight. Never rejects: a failed build
397
+ * is reported by the call that started it, and the caller re-reads the instance after.
398
+ *
399
+ * NOTE: after a failed launch the re-read sees the instance gone only because
400
+ * `startStrand`'s catch deletes it synchronously, and its continuation is queued before
401
+ * this waiter's. If that cleanup ever awaits before `instances.delete`, a waiter can act
402
+ * on the dying record; move the cleanup inside the tracked promise then.
403
+ */
404
+ private settleRuntimeBuilds;
398
405
  /**
399
406
  * Build (or rebuild) the libp2p node + StrandDatabase for an instance and
400
407
  * attach them, transitioning it to `active`. Shared by `startStrand` (fresh
@@ -509,11 +516,26 @@ export declare class StrandInstanceManager {
509
516
  * Quiesce a strand: release its strand-network resources (stop the libp2p node,
510
517
  * close the StrandDatabase) while RETAINING the instance record — identity,
511
518
  * sAppInfo, keys, latency hint, metadata — and its launch config so it can be
512
- * resumed later. Mechanically this is `stopStrand` minus the instance/config
513
- * deletion. The caller sets the post-quiesce status (e.g. `hibernating`).
514
- * No-ops when the strand is missing or already quiesced.
519
+ * resumed later, then mark it `hibernating`. Mechanically this is `stopStrand` minus
520
+ * the instance/config deletion. A tracked strand with no runtime left (a failed
521
+ * rebuild already released it) is only marked; an untracked one is left alone.
522
+ *
523
+ * Ordered with {@link resumeStrand} (see {@link transitions}): it starts once the resume
524
+ * or launch build before it has settled, so it releases a finished runtime, and the
525
+ * status write is its last step, so a resume requested meanwhile writes after it. A call
526
+ * made while another quiesce is the latest request joins it. A failed release leaves the
527
+ * status as it was and rejects.
515
528
  */
516
529
  quiesceStrand(strandId: string): Promise<void>;
530
+ /** Body of {@link quiesceStrand}. */
531
+ private runQuiesce;
532
+ /**
533
+ * Whether the latest runtime transition requested for `strandId` is a quiesce that has
534
+ * not finished. Its handles may still be attached and its status still live, but it is on
535
+ * its way to `hibernating`, so a caller that needs it up must {@link resumeStrand} it — the
536
+ * resume runs after the quiesce — rather than treat it as live.
537
+ */
538
+ isQuiescing(strandId: string): boolean;
517
539
  /**
518
540
  * Resume a previously-quiesced strand: rebuild its libp2p node + StrandDatabase
519
541
  * from the retained launch config and re-attach them, transitioning it back to
@@ -521,8 +543,22 @@ export declare class StrandInstanceManager {
521
543
  * launch (the cohort `bootstrapNodes` seed and the strand's `servingMachines`
522
544
  * count) and updates the retained config so a later resume reuses the latest
523
545
  * values. Returns the live instance unchanged if it is already running.
546
+ *
547
+ * Overlapping calls share one rebuild: a call made while a resume is the latest request
548
+ * for the strand — a wake landing during a check-in — joins it and settles exactly as it
549
+ * does, `'error'` status included. The joiner's own `overrides` are ignored; the first
550
+ * resume's seed wins, and both callers resolved the cohort seed moments apart. A call made
551
+ * while a quiesce is the latest request runs after that quiesce and rebuilds (see
552
+ * {@link transitions}), and one made while `startStrand` is still building waits for that
553
+ * build rather than starting a second.
524
554
  */
525
555
  resumeStrand(strandId: string, overrides?: ResumeStrandOverrides): Promise<StrandInstance>;
556
+ /** Record `transition` as the latest for `strandId` until it settles. */
557
+ private holdTransition;
558
+ /** Wait for the transition requested before this one. Never rejects: its failure belongs to its caller. */
559
+ private settleTransition;
560
+ /** Body of {@link resumeStrand}; at most one runs per strand at a time. */
561
+ private runResume;
526
562
  /**
527
563
  * Honor a founder request against an ALREADY-TRACKED strand — the seam that
528
564
  * closes the "whoever launches first decides whether the bootstrap runs" gap:
@@ -539,7 +575,8 @@ export declare class StrandInstanceManager {
539
575
  * - `'already-founder'` — the retained config already founds; nothing to do.
540
576
  * - `'bootstrapped'` — config flipped and the live database ran the bootstrap.
541
577
  * - `'needs-resume'` — config flipped, but the instance is quiesced (no live
542
- * database), so the bootstrap could not run here: the CALLER must wake the
578
+ * database) or being quiesced (its database is closing), so the bootstrap could
579
+ * not run here: the CALLER must wake the
543
580
  * strand (`CadreNode.wakeStrand`, which owns the hibernation bookkeeping this
544
581
  * manager does not) so the rebuild — which now founds — runs it.
545
582
  * @param resolvePartyKey - Asked for the party's own membership key when (and only
@@ -555,6 +592,39 @@ export declare class StrandInstanceManager {
555
592
  * whose `'needs-resume'` wake fails owes the same withdrawal.
556
593
  */
557
594
  foundExistingStrand(strandId: string, resolvePartyKey?: () => Promise<string | undefined>): Promise<'already-founder' | 'bootstrapped' | 'needs-resume'>;
595
+ /**
596
+ * Give a tracked storage replica ({@link StartStrandConfig.sAppConfig} absent) the app's
597
+ * schema in place — the seam for an app on this machine claiming a strand this node was
598
+ * already hosting. No runtime rebuild: the libp2p node, its peer id and connections, the
599
+ * store and its warm cache all stay, and the new `App` tables read the blocks the replica
600
+ * already holds. Callers attach BEFORE {@link foundExistingStrand}: the founder bootstrap
601
+ * writes the sApp into `Strand.Header` and refuses a database that has none.
602
+ *
603
+ * A runtime build in flight (the replica's launch, or a hibernation wake) is waited out
604
+ * first, so the attach acts on the database that build produced. Then, in order: the
605
+ * schema signature is checked before anything changes; the retained launch config takes
606
+ * the sApp, so every later rebuild applies it through `composeStrand`; the live database —
607
+ * published, or still held by the first-sync gate — gets the schema; and only then does
608
+ * the instance record `sAppInfo` and the sApp's latency hint. A quiesced instance gets the
609
+ * config alone, and its next resume applies the schema.
610
+ *
611
+ * @returns `'already-attached'` when the instance already runs an sApp — a DIFFERENT one
612
+ * is logged and left as it is, as a second claim of a claimed strand always has been —
613
+ * else `'attached'`.
614
+ * @throws when the strand is not tracked (a caller bug, or a launch that failed while
615
+ * this waited on it); when the schema signature is refused, having changed nothing; and
616
+ * whatever the live apply throws (e.g. a quiesce closed the database mid-apply) — the
617
+ * retained config then still carries the sApp and `sAppInfo` stays unset, so the next
618
+ * claim retries the apply.
619
+ *
620
+ * NOTE: two claims of one replica racing each other both pass the `sAppInfo` check and
621
+ * apply concurrently; the loser of the declarative diff can reject, and its `addStrand`
622
+ * retry then resolves `'already-attached'`. Needs an app calling `addStrand` twice at once
623
+ * (or beside a watcher retry); if it is ever seen, chain attaches per strand id.
624
+ */
625
+ attachSApp(strandId: string, sAppConfig: SAppConfig, options?: {
626
+ requireSignedSchemas?: boolean;
627
+ }): Promise<'attached' | 'already-attached'>;
558
628
  /**
559
629
  * Whether `strandId` is launched but still waiting for its first sync — a joiner whose
560
630
  * runtime is up while its database is withheld (status `'syncing'`, or `'idle'` after
@@ -575,6 +645,14 @@ export declare class StrandInstanceManager {
575
645
  whenWritable(strandId: string, options?: {
576
646
  timeoutMs?: number;
577
647
  }): Promise<StrandInstance>;
648
+ /**
649
+ * The tracked instance once no runtime build of it is in flight, or `undefined` when none
650
+ * is tracked by then — never launched, or the launch waited on failed and dropped its
651
+ * record. For a caller about to hand a tracked instance to an app: until its build settles
652
+ * the instance is `'starting'` with no database, and a failed launch leaves no instance.
653
+ * Never rejects; a failed build is reported by the call that started it.
654
+ */
655
+ whenRuntimeBuilt(strandId: string): Promise<StrandInstance | undefined>;
578
656
  /**
579
657
  * Hand a launch's database to the app: set `instance.database`, retire the gate that
580
658
  * held it, release every {@link whenWritable} waiter, and — when the launch had already