@serfab/cadre-core 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +255 -255
  2. package/dist/cadre-node.d.ts +203 -28
  3. package/dist/cadre-node.js +445 -84
  4. package/dist/cadre-node.js.map +1 -1
  5. package/dist/control-cohort.d.ts +26 -0
  6. package/dist/control-cohort.js +26 -0
  7. package/dist/control-cohort.js.map +1 -1
  8. package/dist/control-database.d.ts +82 -117
  9. package/dist/control-database.js +217 -295
  10. package/dist/control-database.js.map +1 -1
  11. package/dist/control-formation-recorder.d.ts +8 -7
  12. package/dist/control-formation-recorder.js +8 -7
  13. package/dist/control-formation-recorder.js.map +1 -1
  14. package/dist/control-schema.d.ts +1 -1
  15. package/dist/control-schema.js +789 -774
  16. package/dist/control-schema.js.map +1 -1
  17. package/dist/control-write-retry.d.ts +15 -5
  18. package/dist/control-write-retry.js +19 -18
  19. package/dist/control-write-retry.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/peer-addr-book.d.ts +92 -0
  24. package/dist/peer-addr-book.js +220 -0
  25. package/dist/peer-addr-book.js.map +1 -0
  26. package/dist/seed-bootstrap.d.ts +7 -0
  27. package/dist/seed-bootstrap.js +15 -13
  28. package/dist/seed-bootstrap.js.map +1 -1
  29. package/dist/strand-cohort.d.ts +4 -37
  30. package/dist/strand-cohort.js +4 -15
  31. package/dist/strand-cohort.js.map +1 -1
  32. package/dist/strand-database.d.ts +11 -16
  33. package/dist/strand-database.js +19 -7
  34. package/dist/strand-database.js.map +1 -1
  35. package/dist/strand-formation-manager.d.ts +8 -8
  36. package/dist/strand-formation-manager.js +15 -18
  37. package/dist/strand-formation-manager.js.map +1 -1
  38. package/dist/strand-instance-manager.d.ts +52 -21
  39. package/dist/strand-instance-manager.js +100 -34
  40. package/dist/strand-instance-manager.js.map +1 -1
  41. package/dist/strand-membership-writer.d.ts +6 -4
  42. package/dist/strand-membership-writer.js +38 -36
  43. package/dist/strand-membership-writer.js.map +1 -1
  44. package/dist/types.d.ts +39 -31
  45. package/dist/types.js.map +1 -1
  46. package/package.json +8 -7
  47. package/src/arachnode-stub.ts +159 -159
  48. package/src/bootstrap-peer-store-file.ts +44 -44
  49. package/src/bootstrap-peer-store.ts +172 -172
  50. package/src/cadre-node.ts +5158 -4771
  51. package/src/canonical-json.ts +30 -30
  52. package/src/control-authorization.ts +114 -114
  53. package/src/control-cohort.ts +130 -103
  54. package/src/control-database.ts +2406 -2506
  55. package/src/control-formation-recorder.ts +305 -304
  56. package/src/control-schema.ts +801 -786
  57. package/src/control-stream.ts +205 -205
  58. package/src/control-write-retry.ts +483 -473
  59. package/src/delegate-admission.ts +302 -302
  60. package/src/device-token.ts +94 -94
  61. package/src/diagnostics/connection-path.ts +277 -277
  62. package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
  63. package/src/ed25519-key.ts +134 -134
  64. package/src/enrollment.ts +199 -199
  65. package/src/file-durable-slot.ts +61 -61
  66. package/src/formation-approval.ts +604 -604
  67. package/src/fs-atomic.ts +107 -107
  68. package/src/hibernation-manager.ts +437 -437
  69. package/src/identity-key.ts +109 -109
  70. package/src/index.ts +1 -0
  71. package/src/key-store-file.ts +132 -132
  72. package/src/key-store.ts +99 -99
  73. package/src/membership-connection-gater.ts +204 -204
  74. package/src/node-local-snapshot.ts +260 -260
  75. package/src/peer-addr-book.ts +250 -0
  76. package/src/peer-authorization.ts +259 -259
  77. package/src/peer-record.ts +164 -164
  78. package/src/push-credentials.ts +84 -84
  79. package/src/push-node.ts +64 -64
  80. package/src/push-notifier-apns.ts +247 -247
  81. package/src/push-notifier-fcm.ts +220 -220
  82. package/src/push-notifier-shared.ts +28 -28
  83. package/src/push-notifier.ts +52 -52
  84. package/src/relay-addrs.ts +111 -111
  85. package/src/relay-reservation.ts +823 -823
  86. package/src/seed-bootstrap.ts +15 -15
  87. package/src/seed-trust-policy.ts +140 -140
  88. package/src/strand-addr-protocol.ts +441 -441
  89. package/src/strand-backfill.ts +366 -366
  90. package/src/strand-cohort.ts +38 -77
  91. package/src/strand-database.ts +202 -204
  92. package/src/strand-formation-manager.ts +491 -494
  93. package/src/strand-formation-protocol.ts +692 -692
  94. package/src/strand-instance-manager.ts +108 -50
  95. package/src/strand-member-key.ts +39 -39
  96. package/src/strand-member-registry.ts +182 -182
  97. package/src/strand-membership-writer.ts +1432 -1430
  98. package/src/strand-solicitation.ts +472 -472
  99. package/src/strand-transport-key.ts +58 -58
  100. package/src/strand-wake-payload.ts +32 -32
  101. package/src/strand-wake-protocol.ts +314 -314
  102. package/src/strand-watcher.ts +314 -314
  103. package/src/trusted-owner-store-file.ts +41 -41
  104. package/src/trusted-owner-store.ts +171 -171
  105. package/src/types.ts +1309 -1303
@@ -13,6 +13,18 @@ import { StrandSolicitationService, type StrandSolicitationServiceOptions } from
13
13
  import { type RelayReservationState, type RelayReservationSupervisorOptions } from './relay-reservation.js';
14
14
  import type { WakeAck } from './types.js';
15
15
  import { type ConnectionPathSummary } from './diagnostics/connection-path.js';
16
+ /**
17
+ * How often a running strand re-resolves its siblings' strand-network addresses
18
+ * and re-merges them into its own libp2p address book
19
+ * ({@link CadreNode.refreshStrandPeerAddrs}). Overridable per node via
20
+ * `network.controlCohort.strandAddrRefreshMs`.
21
+ *
22
+ * Ten minutes sits comfortably inside the peerStore's one-hour address expiry
23
+ * (`MAX_ADDRESS_AGE`, see `peer-addr-book.ts`) with headroom for several missed
24
+ * passes, and far above the 15 s reconcile cadence the refresh rides on, so the
25
+ * strand-addr RPC fan-out stays cheap.
26
+ */
27
+ export declare const STRAND_PEER_ADDR_REFRESH_MS: number;
16
28
  type EventHandler<T> = (data: T) => void;
17
29
  /**
18
30
  * CadreNode is the main entry point for a cadre member.
@@ -68,6 +80,14 @@ export declare class CadreNode implements SAppIdLookup {
68
80
  private pushFanoutService;
69
81
  /** Backing field for the {@link running} / {@link isRunning} getters. */
70
82
  private _running;
83
+ /**
84
+ * The control database's own raw storage (cache-wrapped), resolved once per
85
+ * `start()` and released in {@link cleanup}. Owning it is what keeps
86
+ * `config.storage.provider('control')` a once-per-runtime call: a `stop()` then
87
+ * `start()` cycle on this object re-resolves against a live cache, and never
88
+ * orphans the previous wrapper's registration in the shared cache pool.
89
+ */
90
+ private controlStorage;
71
91
  /**
72
92
  * In-flight {@link serviceWake} operations keyed by strandId. Coalesces
73
93
  * concurrent on-demand wakes for the same strand into one runtime build + one
@@ -176,6 +196,16 @@ export declare class CadreNode implements SAppIdLookup {
176
196
  * strand is no longer running are pruned on each reconcile pass.
177
197
  */
178
198
  private readonly delegateAnnounceAt;
199
+ /**
200
+ * When each running strand last had its siblings' strand addresses re-resolved
201
+ * into its own address book, keyed by strandId. Throttles
202
+ * {@link refreshStrandPeerAddrs} to once per {@link STRAND_PEER_ADDR_REFRESH_MS}
203
+ * per strand so the 15 s reconcile tick never becomes per-tick RPC chatter.
204
+ * Entries for strands that are no longer running are pruned on each pass, so a
205
+ * hibernated-then-resumed strand refreshes immediately rather than inheriting a
206
+ * stale stamp.
207
+ */
208
+ private readonly strandPeerAddrRefreshAt;
179
209
  /**
180
210
  * Cold-start bootstrap dial targets: the owner-flagged peers of every seed
181
211
  * this node has applied, keyed by peer id, valued by the seed's multiaddr
@@ -473,6 +503,25 @@ export declare class CadreNode implements SAppIdLookup {
473
503
  */
474
504
  trustOwnerKeys(keys: Iterable<string>, source: Exclude<TrustSource, 'genesis'>): Promise<void>;
475
505
  private createControlNode;
506
+ /**
507
+ * The control database's cache-wrapped raw storage, resolved from
508
+ * `config.storage.provider` on first use and then held in {@link controlStorage}
509
+ * for the rest of this runtime.
510
+ *
511
+ * Resolving once is the contract `RawStorageProvider` (types.ts) states: a provider is
512
+ * called once per scope per runtime lifetime. Re-entering it per call would mint a
513
+ * second store over one backend — a fresh, cold cache, with the previous wrapper's
514
+ * registration orphaned in the process-wide pool.
515
+ *
516
+ * Lazy rather than eager in `start()` so the pure-unit call path
517
+ * (`cadre-node-control-node-options.spec.ts` calls `buildControlNodeOptions` on a
518
+ * bare `new CadreNode`) still resolves — and, on that path too, only once.
519
+ *
520
+ * Wrapped in the write-through raw-storage cache (quereus-plugin-sereus's
521
+ * `cached-storage.ts`) because a control start's cost is its raw-storage
522
+ * operation count.
523
+ */
524
+ private resolveControlStorage;
476
525
  /**
477
526
  * Map this node's config onto the control network's libp2p node options.
478
527
  *
@@ -759,23 +808,67 @@ export declare class CadreNode implements SAppIdLookup {
759
808
  /** Body of {@link reconcileControlCohort}; serialised by its single-flight guard. */
760
809
  private runReconcileControlCohort;
761
810
  /**
762
- * Resolve one sibling's control-network dial addresses and dial it, best-effort.
763
- * Returns whether a dial was attempted (false when no address resolves).
811
+ * Resolve every sibling's signed address record once and merge what resolves
812
+ * into the control node's libp2p **address book** (peerStore). Returns the
813
+ * resolved addresses per sibling so the dial loop reuses them rather than
814
+ * re-resolving — one `queryPeerRecord` per sibling per pass, not two.
815
+ *
816
+ * Deliberately every sibling, not the {@link selectControlCohortDials} subset,
817
+ * and deliberately including already-connected ones: everything below
818
+ * cadre-core dials by bare peer id (Optimystic's cluster/repo clients, FRET
819
+ * ping/announce), so the address book has to be warm BEFORE a live connection
820
+ * drops, and a sibling the out-degree cap declines to dial is still one those
821
+ * layers may need to reach.
764
822
  *
765
- * A per-peer failure (NAT, offline, relay down, connection-gater denial) is
766
- * logged and swallowed so one unreachable sibling never aborts the pass —
767
- * exactly like {@link SeedBootstrapService.applySeed}'s owner-dial loop. A
768
- * failed dial is simply retried on the next pass.
823
+ * Only `resolvePeerAddrs` output is merged — never the cold-start
824
+ * `peerStoreAddrs` fallback, which came out of the address book to begin with
825
+ * and would restamp unverified seed addresses indefinitely. A sibling that
826
+ * resolves to nothing (revoked, stale, untrusted) is not written at all, so its
827
+ * existing entry ages out on its own.
828
+ *
829
+ * NOTE: this resolves EVERY sibling serially before the dial loop below runs,
830
+ * so it costs one record query per sibling per reconcile pass (~15s) and each
831
+ * one delays the pass's first dial. A cadre is a handful of devices, so today
832
+ * that is a few extra local reads; if cadres ever grow large, batch the records
833
+ * into one query, merge only on change, or move the warm pass after the dials.
834
+ */
835
+ private warmSiblingAddrBook;
836
+ /**
837
+ * {@link resolvePeerAddrs} for one sibling, best-effort: a control-DB read
838
+ * failure yields `[]` (and the cold-start fallback then gets its turn) rather
839
+ * than aborting the whole pass, like every other step here.
840
+ */
841
+ private resolveSiblingAddrs;
842
+ /**
843
+ * Budget for ONE proactive dial of the reconcile pass — steady-state sibling
844
+ * ({@link dialControlSibling}) and cold-start bootstrap peer
845
+ * ({@link dialBootstrapPeer}) alike, since both feed a whole address list to
846
+ * one `dial()` and both run in a sequential loop that the next entry waits on.
847
+ *
848
+ * See {@link DEFAULT_CONTROL_COHORT_DIAL_TIMEOUT_MS} for why the pass bounds
849
+ * this itself rather than inheriting each address's libp2p attempt timeout.
850
+ */
851
+ private controlDialTimeoutMs;
852
+ /**
853
+ * Dial one sibling from its already-resolved addresses, best-effort. Returns
854
+ * whether a dial was attempted (false when no address resolves).
855
+ *
856
+ * A per-peer failure (NAT, offline, relay down, connection-gater denial, or
857
+ * the {@link controlDialTimeoutMs} budget expiring) is logged and swallowed so
858
+ * one unreachable sibling never aborts the pass — exactly like
859
+ * {@link SeedBootstrapService.applySeed}'s owner-dial loop. A failed dial is
860
+ * simply retried on the next pass.
769
861
  */
770
862
  private dialControlSibling;
771
863
  /**
772
- * Resolve a sibling's control-network dial addresses for the reconcile pass.
864
+ * A sibling's control-network dial addresses for the reconcile pass.
773
865
  *
774
- * Primary (steady state): the signed, fresh, trust-gated control addresses from
775
- * the converged `CadrePeer` record via {@link resolvePeerAddrs}. Fallback (cold
776
- * start): the libp2p peerStore entries `applySeed` populated, used only while the
777
- * record is not yet resolvable. Returns `[]` (never throws) when neither yields
778
- * an address — that sibling is skipped this pass.
866
+ * Primary (steady state): the signed, fresh, trust-gated control addresses
867
+ * `resolved` for it by {@link warmSiblingAddrBook} — passed in rather than
868
+ * re-resolved, so the pass makes one record query per sibling. Fallback (cold
869
+ * start): the libp2p peerStore entries `applySeed` populated, used only while
870
+ * the record is not yet resolvable. Returns `[]` (never throws) when neither
871
+ * yields an address — that sibling is skipped this pass.
779
872
  */
780
873
  private resolveControlDialAddrs;
781
874
  /**
@@ -1099,7 +1192,7 @@ export declare class CadreNode implements SAppIdLookup {
1099
1192
  private handleStrandHibernate;
1100
1193
  /**
1101
1194
  * Wake a strand. If it was hibernating (quiesced — no libp2p node), re-resolve
1102
- * the cohort discovery seed and mode exactly as `launchStrand` does and rebuild
1195
+ * the cohort discovery seed exactly as `launchStrand` does and rebuild
1103
1196
  * its runtime via the strand manager. If it is still live (e.g. waking an idle
1104
1197
  * strand, which retains its resources), just flip the status. Overlapping wake
1105
1198
  * triggers are coalesced upstream by `HibernationManager`, so this runs once
@@ -1107,11 +1200,10 @@ export declare class CadreNode implements SAppIdLookup {
1107
1200
  */
1108
1201
  private handleStrandWake;
1109
1202
  /**
1110
- * Rebuild a quiesced strand's runtime, re-resolving the volatile cohort inputs
1111
- * first: the discovery seed may have grown and cohort membership may have
1112
- * shifted the mode `bootstrap → networked` since the strand last ran. Shared by
1203
+ * Rebuild a quiesced strand's runtime, re-resolving the volatile cohort input
1204
+ * first: the discovery seed may have grown since the strand last ran. Shared by
1113
1205
  * the wake (`handleStrandWake`) and check-in (`handleStrandCheckIn`) paths so
1114
- * both apply the same fresh seed/mode resolution. `resumeStrand` is idempotent
1206
+ * both apply the same fresh seed resolution. `resumeStrand` is idempotent
1115
1207
  * (returns the live instance unchanged) as a backstop against double-resume.
1116
1208
  */
1117
1209
  private resumeStrandRuntime;
@@ -1124,7 +1216,7 @@ export declare class CadreNode implements SAppIdLookup {
1124
1216
  * realized as a resume → bounded window → re-hibernate-if-idle cycle that
1125
1217
  * reuses the existing quiesce/resume primitives rather than a bespoke probe:
1126
1218
  *
1127
- * 1. Resume the strand (rebuild node + db, re-resolve cohort seed/mode) so
1219
+ * 1. Resume the strand (rebuild node + db, re-resolve the cohort seed) so
1128
1220
  * its strand network can reach cohort peers — exactly as a wake does.
1129
1221
  * 2. Hold it resumed for a bounded window, during which the app may drive
1130
1222
  * reads (pull-on-read) and record activity.
@@ -1356,8 +1448,8 @@ export declare class CadreNode implements SAppIdLookup {
1356
1448
  /**
1357
1449
  * Shared strand launch path for both the explicit (`addStrand`) and the
1358
1450
  * control-discovered (`handleStrandAdded`) entry points. Resolves the cohort
1359
- * seed, selects the mode, starts the strand, and registers it with the
1360
- * hibernation manager before emitting `strand:started`.
1451
+ * seed, starts the strand, and registers it with the hibernation manager
1452
+ * before emitting `strand:started`.
1361
1453
  *
1362
1454
  * Idempotent no-op when the strand manager already tracks `strand.Id` — the
1363
1455
  * ordinary founding sequence is `addStrand` (starts it locally) followed by
@@ -1370,18 +1462,19 @@ export declare class CadreNode implements SAppIdLookup {
1370
1462
  */
1371
1463
  private launchStrand;
1372
1464
  /**
1373
- * Resolve a strand's discovery seed. Membership (and thus mode) comes from the
1374
- * control network's CadrePeer rows; the **strand-network** bootstrap addresses
1375
- * are resolved on demand over the control mesh via the strand-addr RPC —
1376
- * deliberately NOT from `CadrePeer.Multiaddr`, which carries *control*
1377
- * addresses that must not seed the strand mesh.
1465
+ * Resolve a strand's discovery seed — the dialable strand-network multiaddr
1466
+ * strings for cohort siblings. Membership comes from the control network's
1467
+ * CadrePeer rows; the **strand-network** bootstrap addresses are resolved on
1468
+ * demand over the control mesh via the strand-addr RPC — deliberately NOT
1469
+ * from `CadrePeer.Multiaddr`, which carries *control* addresses that must not
1470
+ * seed the strand mesh.
1378
1471
  *
1379
1472
  * Only siblings we already hold an open control connection to are RPC'd: they
1380
1473
  * are the ones that can answer right now, and dialing them by peerId reuses the
1381
1474
  * live connection. When no connected sibling yet runs the strand the seed is
1382
- * empty (`[]`) — mode still follows membership, and the empty seed self-heals
1383
- * on the next resume / check-in pass. Returns an empty seed when the control DB
1384
- * or node is absent (not yet started / torn down).
1475
+ * empty (`[]`) — the empty seed self-heals on the next resume / check-in pass.
1476
+ * Returns an empty seed when the control DB or node is absent (not yet
1477
+ * started / torn down).
1385
1478
  *
1386
1479
  * When `delegatePeerId` is given (a strand launch/resume is imminent), the
1387
1480
  * pass doubles as the delegate ANNOUNCEMENT: our circuit relays are merged
@@ -1395,6 +1488,37 @@ export declare class CadreNode implements SAppIdLookup {
1395
1488
  * for a relay we are not yet connected to.
1396
1489
  */
1397
1490
  private resolveCohortSeed;
1491
+ /**
1492
+ * The co-cadre siblings worth sending a strand-addr RPC to right now: cohort
1493
+ * members (self excluded) we already hold an open control connection to. They
1494
+ * are the ones that can answer immediately, and dialing them by peerId reuses
1495
+ * the live connection instead of opening a second one.
1496
+ *
1497
+ * Shared by {@link resolveCohortSeed} (launch/resume) and
1498
+ * {@link refreshStrandPeerAddrs} (the periodic pass) so both ask the same set.
1499
+ * Empty when the control DB or node is absent (not yet started / torn down).
1500
+ *
1501
+ * The read below is issued even when the node holds no control connection and
1502
+ * the answer is therefore empty by construction. That is deliberate on the
1503
+ * launch path: `control-database-solo-warm-start.spec.ts` exists to prove this
1504
+ * exact read does not stall as an embedder's FIRST awaited control operation,
1505
+ * on a warm cohort no one can reach. A caller that re-enters often enough for
1506
+ * the read to matter should skip the call itself, as
1507
+ * {@link refreshStrandPeerAddrs} does.
1508
+ *
1509
+ * NOTE: this read is UNBOUNDED and sits on the critical path an embedding app
1510
+ * awaits during startup — `addStrand` cannot resolve until it does, and it is
1511
+ * the first control operation an embedder's boot order issues (before genesis,
1512
+ * before seed bootstrap). Measured fine today: the warm-start-alone shape a
1513
+ * report pointed at — a `CadrePeer` list naming peers that are all gone, read
1514
+ * off real files after a restart — completes in milliseconds, and
1515
+ * `control-database-solo-warm-start.spec.ts` covers it under a deadline. If a
1516
+ * control read ever CAN stall (a transactor change that consults the network
1517
+ * for a local row, a storage backend with blocking I/O), give this one a
1518
+ * timeout — and decide then whether the breach fails `addStrand` or degrades
1519
+ * to an empty seed, because those promise callers different things.
1520
+ */
1521
+ private connectedSiblingTargets;
1398
1522
  /**
1399
1523
  * The circuit relays this node's strand nodes would reserve through: the
1400
1524
  * union of the RESOLVED listen addrs' circuits — a hand-written
@@ -1437,6 +1561,57 @@ export declare class CadreNode implements SAppIdLookup {
1437
1561
  private refreshDelegateGrants;
1438
1562
  /** One strand's share of {@link refreshDelegateGrants}: announce to the relays whose grant is due. */
1439
1563
  private announceDelegateToDueRelays;
1564
+ /**
1565
+ * Re-resolve each running strand's SIBLING addresses over the control mesh and
1566
+ * merge them into that strand's own libp2p address book, throttled to once per
1567
+ * {@link STRAND_PEER_ADDR_REFRESH_MS} per strand.
1568
+ *
1569
+ * Without this, a strand's address book is written exactly once — the
1570
+ * launch/resume seed — and everything below cadre-core that dials a strand peer
1571
+ * by bare peer id (Optimystic's cluster and repo clients, FRET ping/announce)
1572
+ * loses its address for any sibling it is not currently connected to: a sibling
1573
+ * that restarted its strand node or rotated its relay reservation is never
1574
+ * re-resolved, and even the original seed addresses fall off at the peerStore's
1575
+ * one-hour expiry (see `peer-addr-book.ts`).
1576
+ *
1577
+ * Distinct from {@link refreshDelegateGrants}, deliberately: that pass covers
1578
+ * RELAYS on a `DELEGATE_GRANT_TTL_MS / 2` throttle to keep circuit-relay
1579
+ * admission grants alive, this one covers SIBLINGS on an address-expiry
1580
+ * throttle to keep the address book warm. They overlap only in that both carry
1581
+ * `delegatePeerId`, so a sibling that also runs a relay gets its grant
1582
+ * refreshed here as a side effect. Merging them would tie an admission-grant
1583
+ * TTL to an address-expiry window that has nothing to do with it.
1584
+ *
1585
+ * Strands refresh CONCURRENTLY, like `refreshDelegateGrants`, so one
1586
+ * unreachable sibling's dial timeout does not stack up per strand.
1587
+ */
1588
+ private refreshStrandPeerAddrs;
1589
+ /**
1590
+ * One strand's share of {@link refreshStrandPeerAddrs}: RPC the siblings, then
1591
+ * merge their answers into this strand's address book. Errors are logged and
1592
+ * swallowed so one strand's failure never costs the others their refresh.
1593
+ */
1594
+ private refreshOneStrandPeerAddrs;
1595
+ /**
1596
+ * Merge strand-network addresses into ONE strand node's libp2p address book,
1597
+ * attributed per peer. Best-effort throughout: an address-book write must never
1598
+ * fail a launch, a resume, or a reconcile pass.
1599
+ *
1600
+ * `addrs` is the peer-agnostic union the strand-addr RPC returns, so
1601
+ * {@link groupAddrsByPeerId} attributes each entry to the **strand transport**
1602
+ * peerId in its final `/p2p/` component — never the sibling's control peerId,
1603
+ * which names a different libp2p node entirely. Entries that name no peer are
1604
+ * dropped there and counted here, once per pass.
1605
+ *
1606
+ * NOTE: a member could answer with arbitrary multiaddrs bound to arbitrary peer
1607
+ * ids and poison this address book. That is the same exposure the launch-time
1608
+ * `bootstrapNodes` seeding already accepts, and the cost is bounded: an address
1609
+ * grants no authority, the dialed peer authenticates by peer id at the
1610
+ * handshake, and a bad entry costs one failed dial that ages out at the
1611
+ * peerStore's one-hour expiry. No new gating here — cross-party strand trust is
1612
+ * `backlog/strand-network-nat-relay-reachability`.
1613
+ */
1614
+ private mergeStrandPeerAddrs;
1440
1615
  /**
1441
1616
  * The local strand instance's dialable strand-network multiaddrs for the
1442
1617
  * strand-addr RPC, ordered signaling-first (reusing the control node's