@serfab/cadre-core 0.13.0 → 1.1.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 (131) hide show
  1. package/README.md +20 -6
  2. package/dist/bootstrap-peer-store-file.js +2 -2
  3. package/dist/bootstrap-peer-store.d.ts +32 -12
  4. package/dist/bootstrap-peer-store.js +41 -15
  5. package/dist/bootstrap-peer-store.js.map +1 -1
  6. package/dist/cadre-node.d.ts +558 -80
  7. package/dist/cadre-node.js +1060 -170
  8. package/dist/cadre-node.js.map +1 -1
  9. package/dist/control-authorization.d.ts +2 -2
  10. package/dist/control-authorization.js +1 -0
  11. package/dist/control-authorization.js.map +1 -1
  12. package/dist/control-cohort.d.ts +13 -26
  13. package/dist/control-cohort.js +0 -26
  14. package/dist/control-cohort.js.map +1 -1
  15. package/dist/control-database.d.ts +299 -44
  16. package/dist/control-database.js +469 -69
  17. package/dist/control-database.js.map +1 -1
  18. package/dist/control-read-retry.d.ts +40 -11
  19. package/dist/control-read-retry.js +48 -15
  20. package/dist/control-read-retry.js.map +1 -1
  21. package/dist/control-retry.d.ts +58 -3
  22. package/dist/control-retry.js +58 -3
  23. package/dist/control-retry.js.map +1 -1
  24. package/dist/control-schema.d.ts +1 -1
  25. package/dist/control-schema.js +103 -19
  26. package/dist/control-schema.js.map +1 -1
  27. package/dist/control-write-retry.d.ts +80 -11
  28. package/dist/control-write-retry.js +176 -29
  29. package/dist/control-write-retry.js.map +1 -1
  30. package/dist/enrolled-machine-store.js +2 -2
  31. package/dist/index.d.ts +14 -7
  32. package/dist/index.js +33 -6
  33. package/dist/index.js.map +1 -1
  34. package/dist/node-local-snapshot.d.ts +15 -7
  35. package/dist/node-local-snapshot.js +23 -8
  36. package/dist/node-local-snapshot.js.map +1 -1
  37. package/dist/peer-dial.d.ts +143 -0
  38. package/dist/peer-dial.js +176 -0
  39. package/dist/peer-dial.js.map +1 -0
  40. package/dist/relay-addrs.d.ts +90 -52
  41. package/dist/relay-addrs.js +91 -59
  42. package/dist/relay-addrs.js.map +1 -1
  43. package/dist/relay-reservation.d.ts +71 -3
  44. package/dist/relay-reservation.js +116 -8
  45. package/dist/relay-reservation.js.map +1 -1
  46. package/dist/seed-bootstrap.d.ts +30 -1
  47. package/dist/seed-bootstrap.js +62 -17
  48. package/dist/seed-bootstrap.js.map +1 -1
  49. package/dist/storage-scope.d.ts +62 -0
  50. package/dist/storage-scope.js +75 -0
  51. package/dist/storage-scope.js.map +1 -0
  52. package/dist/strand-database.d.ts +34 -16
  53. package/dist/strand-database.js +38 -24
  54. package/dist/strand-database.js.map +1 -1
  55. package/dist/strand-first-sync-gate.d.ts +152 -0
  56. package/dist/strand-first-sync-gate.js +208 -0
  57. package/dist/strand-first-sync-gate.js.map +1 -0
  58. package/dist/strand-formation-manager.d.ts +87 -5
  59. package/dist/strand-formation-manager.js +109 -5
  60. package/dist/strand-formation-manager.js.map +1 -1
  61. package/dist/strand-formation-protocol.d.ts +21 -1
  62. package/dist/strand-formation-protocol.js +30 -0
  63. package/dist/strand-formation-protocol.js.map +1 -1
  64. package/dist/strand-instance-manager.d.ts +243 -6
  65. package/dist/strand-instance-manager.js +470 -37
  66. package/dist/strand-instance-manager.js.map +1 -1
  67. package/dist/strand-member-key.d.ts +20 -12
  68. package/dist/strand-member-key.js +20 -12
  69. package/dist/strand-member-key.js.map +1 -1
  70. package/dist/strand-member-registry.js +3 -0
  71. package/dist/strand-member-registry.js.map +1 -1
  72. package/dist/strand-membership-reconciler.d.ts +361 -0
  73. package/dist/strand-membership-reconciler.js +548 -0
  74. package/dist/strand-membership-reconciler.js.map +1 -0
  75. package/dist/strand-membership-writer.d.ts +131 -11
  76. package/dist/strand-membership-writer.js +396 -134
  77. package/dist/strand-membership-writer.js.map +1 -1
  78. package/dist/strand-network-config.d.ts +46 -14
  79. package/dist/strand-network-config.js +79 -23
  80. package/dist/strand-network-config.js.map +1 -1
  81. package/dist/strand-solicitation.d.ts +10 -1
  82. package/dist/strand-solicitation.js +5 -0
  83. package/dist/strand-solicitation.js.map +1 -1
  84. package/dist/strand-wake-protocol.d.ts +11 -7
  85. package/dist/strand-wake-protocol.js +20 -52
  86. package/dist/strand-wake-protocol.js.map +1 -1
  87. package/dist/strand-watcher.js +6 -0
  88. package/dist/strand-watcher.js.map +1 -1
  89. package/dist/timed-step.d.ts +12 -0
  90. package/dist/timed-step.js +28 -0
  91. package/dist/timed-step.js.map +1 -0
  92. package/dist/timeout-scheduler.d.ts +19 -0
  93. package/dist/timeout-scheduler.js +22 -0
  94. package/dist/timeout-scheduler.js.map +1 -0
  95. package/dist/types.d.ts +262 -30
  96. package/dist/types.js.map +1 -1
  97. package/package.json +8 -8
  98. package/src/bootstrap-peer-store-file.ts +2 -2
  99. package/src/bootstrap-peer-store.ts +69 -27
  100. package/src/cadre-node.ts +7301 -6353
  101. package/src/control-authorization.ts +2 -1
  102. package/src/control-cohort.ts +122 -130
  103. package/src/control-database.ts +537 -69
  104. package/src/control-read-retry.ts +202 -167
  105. package/src/control-retry.ts +297 -190
  106. package/src/control-schema.ts +103 -19
  107. package/src/control-write-retry.ts +533 -383
  108. package/src/enrolled-machine-store.ts +2 -2
  109. package/src/index.ts +83 -6
  110. package/src/node-local-snapshot.ts +25 -8
  111. package/src/peer-dial.ts +215 -0
  112. package/src/relay-addrs.ts +458 -436
  113. package/src/relay-reservation.ts +951 -828
  114. package/src/seed-bootstrap.ts +76 -18
  115. package/src/storage-scope.ts +78 -0
  116. package/src/strand-database.ts +50 -27
  117. package/src/strand-first-sync-gate.ts +258 -0
  118. package/src/strand-formation-manager.ts +140 -5
  119. package/src/strand-formation-protocol.ts +43 -1
  120. package/src/strand-instance-manager.ts +577 -37
  121. package/src/strand-member-key.ts +20 -12
  122. package/src/strand-member-registry.ts +3 -0
  123. package/src/strand-membership-reconciler.ts +680 -0
  124. package/src/strand-membership-writer.ts +527 -189
  125. package/src/strand-network-config.ts +102 -24
  126. package/src/strand-solicitation.ts +16 -1
  127. package/src/strand-wake-protocol.ts +21 -66
  128. package/src/strand-watcher.ts +6 -0
  129. package/src/timed-step.ts +28 -0
  130. package/src/timeout-scheduler.ts +28 -0
  131. package/src/types.ts +1789 -1554
@@ -1,16 +1,25 @@
1
1
  /**
2
- * Node-local, NON-replicated cold-start bootstrap-peer store: the dial targets a
3
- * node keeps so it can retry its way back into a party it has been seeded into
4
- * but never managed to connect to.
2
+ * Node-local, NON-replicated bootstrap-peer store: dial targets a node learned
3
+ * out of band, kept so it can dial those peers when nothing better is known.
4
+ * Two sources feed it:
5
5
  *
6
- * A newcomer applies a signed seed nominating the party's owner machines and the
7
- * addresses they answer on. `SeedBootstrapService.applySeed` dials those owners
8
- * exactly once, best-effort; when that dial fails (owner briefly down, relay
9
- * reservation not up, not yet vouched) the node has an empty `CadrePeer` table
10
- * and no connection, and `CadreNode.reconcileControlCohort`'s cold-start branch
11
- * is the only way back — it re-dials these retained addresses every pass. Held
12
- * only in memory, they die with the process and the node is stranded for good,
13
- * which is what this store exists to prevent.
6
+ * - **A seed's owner peers.** A newcomer applies a signed seed nominating the
7
+ * party's owner machines and the addresses they answer on.
8
+ * `SeedBootstrapService.applySeed` dials those owners exactly once,
9
+ * best-effort; when that dial fails (owner briefly down, relay reservation not
10
+ * up, not yet vouched) the node has an empty `CadrePeer` table and no
11
+ * connection, and `CadreNode.reconcileControlCohort`'s cold-start branch is
12
+ * the only way back — it re-dials these retained addresses every pass.
13
+ * - **Nodes this node added.** `CadreNode.addDrone` is handed the added node's
14
+ * addresses, but that node's `CadrePeer` row stays unsigned, and therefore
15
+ * unresolvable, until the node self-publishes — which needs a connection, and
16
+ * a node that cannot listen (a phone) is never dialed by it. The steady-state
17
+ * reconcile pass falls back to these addresses for a sibling whose signed
18
+ * record and address-book entry both yield nothing, including after a
19
+ * relaunch that outlived the record's freshness window.
20
+ *
21
+ * Held only in memory, either kind dies with the process and the node is left
22
+ * with no address to dial, which is what this store exists to prevent.
14
23
  *
15
24
  * Three implementations, mirroring `trusted-owner-store.ts` (read that first —
16
25
  * it solves the same "must outlive the process, storage differs per platform"
@@ -28,10 +37,11 @@
28
37
  *
29
38
  * **Nothing here is trust-bearing, and a loader must not try to re-verify it.**
30
39
  * Only *dial targets* are retained — never a seed, a signature, or an authority
31
- * claim. The seed was signature-checked against the node-local trusted-owner
32
- * anchor before its addresses were retained, and a dial grants no authority:
33
- * `CadreNode.bootstrapDialAddrs` binds every address to the peer id it was
34
- * retained under, so a dial cannot be redirected to whoever answers. What a
40
+ * claim. A seed was signature-checked against the node-local trusted-owner
41
+ * anchor before its addresses were retained, and an added node's addresses were
42
+ * supplied by the owner that chose to add it. Either way a dial grants no
43
+ * authority: `CadreNode.bootstrapDialAddrs` binds every address to the peer id it
44
+ * was retained under, so a dial cannot be redirected to whoever answers. What a
35
45
  * persistent backend's loader DOES owe is dropping structurally junk entries
36
46
  * (unparseable peer id, empty address list, non-string address) rather than
37
47
  * carrying them into the dial loop.
@@ -42,9 +52,12 @@ import { NodeLocalSnapshot, type DurableSlot, type NodeLocalSnapshotSpec } from
42
52
 
43
53
  const log = debug('sereus:cadre:bootstrap-peer-store');
44
54
 
45
- /** One retained cold-start dial target: an owner peer a seed nominated. */
55
+ /**
56
+ * One retained dial target learned out of band: an owner peer a seed nominated,
57
+ * or a node this node added.
58
+ */
46
59
  export interface BootstrapPeerEntry {
47
- /** Multiaddr strings exactly as the seed carried them (parsed at dial time). */
60
+ /** Multiaddr strings exactly as they were handed over (parsed at dial time). */
48
61
  addrs: string[];
49
62
  /** Wall-clock ms the entry was last recorded (diagnostics / future eviction). */
50
63
  recordedAt: number;
@@ -56,9 +69,9 @@ export interface BootstrapPeerStore {
56
69
 
57
70
  /**
58
71
  * Every retained target, peerId -> entry. Every backend copies the map per call,
59
- * so the result is a snapshot decoupled from later {@link record} calls and is
60
- * safe to iterate while recording. The entry objects need no copy: `record`
61
- * REPLACES an entry rather than mutating it in place.
72
+ * so the result is a snapshot decoupled from later {@link record} and
73
+ * {@link forget} calls and is safe to iterate while recording. The entry objects
74
+ * need no copy: `record` REPLACES an entry rather than mutating it in place.
62
75
  */
63
76
  all(): ReadonlyMap<string, BootstrapPeerEntry>;
64
77
 
@@ -72,15 +85,26 @@ export interface BootstrapPeerStore {
72
85
  * `CadreNode.recordSeedBootstrapPeers` keep its signature while a file
73
86
  * backend persists in the background.
74
87
  *
75
- * NOTE: entries are never evicted, and a persistent backend's file therefore
76
- * grows across the node's whole lifetime rather than one process. Fine while a
77
- * seed nominates one or a few owners and entries are keyed by peer id; if a
78
- * node ever applies seeds naming many distinct owners, add eviction (oldest
79
- * {@link BootstrapPeerEntry.recordedAt} first, or a cap) rather than letting
80
- * the file grow unbounded — `recordedAt` exists so eviction has something to
81
- * sort by.
88
+ * NOTE: entries are never evicted (only a local `removePeer` {@link forget}s
89
+ * one), and a persistent backend's file therefore grows across the node's whole
90
+ * lifetime rather than one process. Fine while a seed nominates one or a few
91
+ * owners, a node adds a handful of machines, and entries are keyed by peer id;
92
+ * if a node ever applies seeds naming many distinct owners or adds many
93
+ * machines, add eviction (oldest {@link BootstrapPeerEntry.recordedAt} first, or
94
+ * a cap) rather than letting the file grow unbounded — `recordedAt` exists so
95
+ * eviction has something to sort by.
82
96
  */
83
97
  record(peerId: string, addrs: readonly string[]): Promise<void>;
98
+
99
+ /**
100
+ * Drop a peer's retained dial addresses — for a peer this node removed from the
101
+ * party, which must not stay a dial target. Forgetting a peer with no entry is a
102
+ * no-op.
103
+ *
104
+ * Same contract as {@link record}: the removal is reflected in {@link all}
105
+ * SYNCHRONOUSLY, and the returned promise tracks durability only.
106
+ */
107
+ forget(peerId: string): Promise<void>;
84
108
  }
85
109
 
86
110
  /**
@@ -101,6 +125,12 @@ export class MemoryBootstrapPeerStore implements BootstrapPeerStore {
101
125
  this.peers.set(peerId, { addrs: [...addrs], recordedAt: Date.now() });
102
126
  log('bootstrap peer retained (party=%s, peer=%s, addrs=%d)', this.partyId, peerId, addrs.length);
103
127
  }
128
+
129
+ async forget(peerId: string): Promise<void> {
130
+ if (this.peers.delete(peerId)) {
131
+ log('bootstrap peer forgotten (party=%s, peer=%s)', this.partyId, peerId);
132
+ }
133
+ }
104
134
  }
105
135
 
106
136
  /**
@@ -169,4 +199,16 @@ export class PersistentBootstrapPeerStore implements BootstrapPeerStore {
169
199
  log('bootstrap peer retained (party=%s, peer=%s, addrs=%d); persisting', this.partyId, peerId, addrs.length);
170
200
  return this.snapshot.put(peerId, { addrs: [...addrs], recordedAt: Date.now() });
171
201
  }
202
+
203
+ /**
204
+ * Drop a peer's dial addresses: gone from {@link all} synchronously, then the
205
+ * full snapshot is persisted — unless there was no entry, which writes nothing
206
+ * (see `NodeLocalSnapshot.remove`).
207
+ */
208
+ forget(peerId: string): Promise<void> {
209
+ if (this.snapshot.has(peerId)) {
210
+ log('bootstrap peer forgotten (party=%s, peer=%s); persisting', this.partyId, peerId);
211
+ }
212
+ return this.snapshot.remove(peerId);
213
+ }
172
214
  }