@serfab/cadre-core 0.12.0 → 1.0.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 (155) hide show
  1. package/README.md +23 -8
  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 +883 -102
  7. package/dist/cadre-node.js +1800 -209
  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 +318 -44
  16. package/dist/control-database.js +516 -74
  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 +133 -21
  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 +168 -29
  29. package/dist/control-write-retry.js.map +1 -1
  30. package/dist/enrolled-machine-store-file.d.ts +13 -0
  31. package/dist/enrolled-machine-store-file.js +41 -0
  32. package/dist/enrolled-machine-store-file.js.map +1 -0
  33. package/dist/enrolled-machine-store.d.ts +81 -0
  34. package/dist/enrolled-machine-store.js +225 -0
  35. package/dist/enrolled-machine-store.js.map +1 -0
  36. package/dist/file-durable-slot.js +5 -3
  37. package/dist/file-durable-slot.js.map +1 -1
  38. package/dist/index.d.ts +17 -8
  39. package/dist/index.js +54 -9
  40. package/dist/index.js.map +1 -1
  41. package/dist/membership-connection-gater.d.ts +15 -1
  42. package/dist/membership-connection-gater.js +9 -3
  43. package/dist/membership-connection-gater.js.map +1 -1
  44. package/dist/node-local-snapshot.d.ts +21 -7
  45. package/dist/node-local-snapshot.js +30 -9
  46. package/dist/node-local-snapshot.js.map +1 -1
  47. package/dist/peer-dial.d.ts +143 -0
  48. package/dist/peer-dial.js +176 -0
  49. package/dist/peer-dial.js.map +1 -0
  50. package/dist/{strand-backfill.d.ts → peer-join-backfill.d.ts} +49 -20
  51. package/dist/{strand-backfill.js → peer-join-backfill.js} +133 -52
  52. package/dist/peer-join-backfill.js.map +1 -0
  53. package/dist/relay-addrs.d.ts +160 -49
  54. package/dist/relay-addrs.js +287 -60
  55. package/dist/relay-addrs.js.map +1 -1
  56. package/dist/relay-reservation.d.ts +71 -3
  57. package/dist/relay-reservation.js +116 -8
  58. package/dist/relay-reservation.js.map +1 -1
  59. package/dist/seed-bootstrap.d.ts +30 -1
  60. package/dist/seed-bootstrap.js +62 -17
  61. package/dist/seed-bootstrap.js.map +1 -1
  62. package/dist/storage-scope.d.ts +62 -0
  63. package/dist/storage-scope.js +75 -0
  64. package/dist/storage-scope.js.map +1 -0
  65. package/dist/strand-database.d.ts +41 -11
  66. package/dist/strand-database.js +48 -18
  67. package/dist/strand-database.js.map +1 -1
  68. package/dist/strand-first-sync-gate.d.ts +152 -0
  69. package/dist/strand-first-sync-gate.js +208 -0
  70. package/dist/strand-first-sync-gate.js.map +1 -0
  71. package/dist/strand-formation-manager.d.ts +99 -5
  72. package/dist/strand-formation-manager.js +119 -8
  73. package/dist/strand-formation-manager.js.map +1 -1
  74. package/dist/strand-formation-protocol.d.ts +97 -3
  75. package/dist/strand-formation-protocol.js +108 -2
  76. package/dist/strand-formation-protocol.js.map +1 -1
  77. package/dist/strand-instance-manager.d.ts +389 -19
  78. package/dist/strand-instance-manager.js +678 -65
  79. package/dist/strand-instance-manager.js.map +1 -1
  80. package/dist/strand-member-key.d.ts +20 -12
  81. package/dist/strand-member-key.js +20 -12
  82. package/dist/strand-member-key.js.map +1 -1
  83. package/dist/strand-member-registry.js +3 -0
  84. package/dist/strand-member-registry.js.map +1 -1
  85. package/dist/strand-membership-reconciler.d.ts +361 -0
  86. package/dist/strand-membership-reconciler.js +548 -0
  87. package/dist/strand-membership-reconciler.js.map +1 -0
  88. package/dist/strand-membership-writer.d.ts +143 -11
  89. package/dist/strand-membership-writer.js +419 -135
  90. package/dist/strand-membership-writer.js.map +1 -1
  91. package/dist/strand-network-config.d.ts +116 -0
  92. package/dist/strand-network-config.js +192 -0
  93. package/dist/strand-network-config.js.map +1 -0
  94. package/dist/strand-revocation-enforcer.d.ts +392 -0
  95. package/dist/strand-revocation-enforcer.js +533 -0
  96. package/dist/strand-revocation-enforcer.js.map +1 -0
  97. package/dist/strand-solicitation.d.ts +17 -1
  98. package/dist/strand-solicitation.js +13 -2
  99. package/dist/strand-solicitation.js.map +1 -1
  100. package/dist/strand-wake-protocol.d.ts +11 -7
  101. package/dist/strand-wake-protocol.js +20 -52
  102. package/dist/strand-wake-protocol.js.map +1 -1
  103. package/dist/strand-watcher.js +12 -1
  104. package/dist/strand-watcher.js.map +1 -1
  105. package/dist/timed-step.d.ts +12 -0
  106. package/dist/timed-step.js +28 -0
  107. package/dist/timed-step.js.map +1 -0
  108. package/dist/timeout-scheduler.d.ts +19 -0
  109. package/dist/timeout-scheduler.js +22 -0
  110. package/dist/timeout-scheduler.js.map +1 -0
  111. package/dist/types.d.ts +472 -46
  112. package/dist/types.js +7 -1
  113. package/dist/types.js.map +1 -1
  114. package/package.json +16 -8
  115. package/src/bootstrap-peer-store-file.ts +2 -2
  116. package/src/bootstrap-peer-store.ts +69 -27
  117. package/src/cadre-node.ts +1926 -228
  118. package/src/control-authorization.ts +2 -1
  119. package/src/control-cohort.ts +122 -130
  120. package/src/control-database.ts +586 -74
  121. package/src/control-read-retry.ts +202 -167
  122. package/src/control-retry.ts +297 -190
  123. package/src/control-schema.ts +133 -21
  124. package/src/control-write-retry.ts +525 -383
  125. package/src/enrolled-machine-store-file.ts +43 -0
  126. package/src/enrolled-machine-store.ts +280 -0
  127. package/src/file-durable-slot.ts +5 -3
  128. package/src/index.ts +134 -15
  129. package/src/membership-connection-gater.ts +9 -3
  130. package/src/node-local-snapshot.ts +38 -9
  131. package/src/peer-addr-book.ts +251 -250
  132. package/src/peer-dial.ts +215 -0
  133. package/src/{strand-backfill.ts → peer-join-backfill.ts} +478 -389
  134. package/src/relay-addrs.ts +458 -218
  135. package/src/relay-reservation.ts +951 -828
  136. package/src/seed-bootstrap.ts +76 -18
  137. package/src/storage-scope.ts +78 -0
  138. package/src/strand-database.ts +61 -21
  139. package/src/strand-first-sync-gate.ts +258 -0
  140. package/src/strand-formation-manager.ts +162 -8
  141. package/src/strand-formation-protocol.ts +850 -692
  142. package/src/strand-instance-manager.ts +877 -74
  143. package/src/strand-member-key.ts +20 -12
  144. package/src/strand-member-registry.ts +3 -0
  145. package/src/strand-membership-reconciler.ts +680 -0
  146. package/src/strand-membership-writer.ts +550 -190
  147. package/src/strand-network-config.ts +250 -0
  148. package/src/strand-revocation-enforcer.ts +653 -0
  149. package/src/strand-solicitation.ts +499 -471
  150. package/src/strand-wake-protocol.ts +21 -66
  151. package/src/strand-watcher.ts +325 -314
  152. package/src/timed-step.ts +28 -0
  153. package/src/timeout-scheduler.ts +28 -0
  154. package/src/types.ts +487 -52
  155. package/dist/strand-backfill.js.map +0 -1
@@ -0,0 +1,533 @@
1
+ /**
2
+ * Strand revoked-peer enforcement: a per-strand deny gate that refuses the
3
+ * network nodes of a REMOVED party, so removal means "we stop talking to you"
4
+ * rather than only deleting a row.
5
+ *
6
+ * ## The deny set is revocation-keyed, never membership-keyed
7
+ *
8
+ * A strand node maintains a materialized in-memory set of revoked peer ids:
9
+ * every `Strand.MemberPeer.PeerId` whose `MemberKey` has no live `Strand.Member`
10
+ * row. `MemberPeer.MemberExists` runs on insert, so an orphaned binding can only
11
+ * mean "this member existed and was removed" — it is exactly the durable,
12
+ * replicated record of a removed party's machines. Everything NOT in the set is
13
+ * admitted. Deliberate consequences:
14
+ *
15
+ * - **Open strands are untouched.** `Member`/`MemberPeer` carry `OnlyClosed`
16
+ * checks, so an open strand has no rows and the set is empty — and
17
+ * `StrandInstanceManager` additionally skips arming this enforcer entirely for
18
+ * `Type !== 'c'`, so an open strand does not even poll.
19
+ * - **A joining party mid-admission is admitted** (it has no orphaned binding).
20
+ * An allowlist ("only peers bound to live members") was considered and
21
+ * rejected: a joiner's first membership writes must travel through the very
22
+ * cohort an allowlist would close to it — the same mutual-denial trap the
23
+ * control network's gater doc describes (`membership-connection-gater.ts`),
24
+ * with no enrollment window to carve out. A deny-list on positive evidence
25
+ * has no such trap.
26
+ * - **Stale-view failure direction: fail-open.** A remaining member that has not
27
+ * yet replicated the revocation keeps serving the removed party until the
28
+ * tombstoned state arrives — bounded by replication, consistent with
29
+ * "revocation is forward-looking". The opposite error (denying a legitimate
30
+ * member) is only reachable for a revoked-then-re-admitted party at a node
31
+ * that saw the revoke but not the re-add; the re-add gives the member key a
32
+ * live `Member` row again, which removes its peers from the deny set as the
33
+ * row replicates — self-healing, and the re-adding manager's own node updates
34
+ * first, so the heal never depends on talking to the peer being wrongly denied.
35
+ * - **Orphaned `MemberPeer` rows are load-bearing**: they ARE the deny record —
36
+ * the `Strand.Revocation` tombstone keeps only the stamp, not the peer id — so
37
+ * nothing here (or anywhere) tombstones or auto-cleans them at revocation.
38
+ * The manager-cleanup path (`removeMemberPeer`'s manager branch) stays as the
39
+ * only way to clear a wrongly-registered binding, and clearing an orphan
40
+ * forgets the denial (the NOTE at that branch says so).
41
+ *
42
+ * ## Removal cuts LIVE sessions too — the refresh IS the teardown seam
43
+ *
44
+ * Refusing NEW connections only half-honors "no longer be answered to or
45
+ * communicated with": a long-lived strand connection may never re-dial, so
46
+ * without teardown a removed party keeps whatever session it already holds.
47
+ * There is no push notification to hang teardown off — a REPLICATED revocation
48
+ * arrives with no change event (the strand database has no reactive
49
+ * subscription; `StrandWatcher` polls for the same reason), and a LOCAL
50
+ * `revokeMember`/`leaveStrand` writes through a bare `Database` handle this
51
+ * runtime never sees — so teardown rides the snapshot refresh itself, with
52
+ * `refresh()` exposed for immediacy
53
+ * (`StrandInstanceManager.refreshRevocationEnforcement` →
54
+ * `CadreNode.refreshRevocationEnforcement`).
55
+ *
56
+ * The sweep is driven by MEMBERSHIP in the deny set, never by an
57
+ * entered-the-set diff: every refresh hangs up every currently-connected
58
+ * revoked peer, so a resume, a crashed refresh, or a connection established
59
+ * between two refreshes cannot leave a session standing (a diff would be a
60
+ * strict subset of this, so none is kept). A peer with no open connection is
61
+ * simply not swept, and a failed `hangUp` is logged, never thrown — the next
62
+ * refresh retries it. `hangUp` closes DIRECT and RELAYED connections alike, and
63
+ * on a strand node running the relay server it also drops any reservation the
64
+ * revoked peer holds there (a reservation dies with its connection — see the
65
+ * relay-seam doc in `membership-connection-gater.ts`).
66
+ *
67
+ * Teardown is also what stops OUTBOUND traffic. The gate is one-directional —
68
+ * `authorizeStream` judges streams the revoked peer opens to US — so nothing
69
+ * else prevents this node from pushing over an already-open connection, which
70
+ * `PeerJoinBackfill` does for that connection's whole life. Closing the
71
+ * connection stops both directions at once, which is why no separate outbound
72
+ * gate exists.
73
+ *
74
+ * If this node's OWN peer id is in the set, it was itself removed (or it left —
75
+ * a voluntary departure files the same tombstone). Nothing is torn down for
76
+ * that: the other side's gate already refuses us, and every other revoked peer
77
+ * is still swept. Instead `onSelfRevoked` fires ONCE per entry into the set
78
+ * (re-armed if the party is re-admitted, so a second removal signals again),
79
+ * which `CadreNode` surfaces as `strand:revoked` so the embedding app decides
80
+ * what to do — this module never stops or leaves a strand on its own.
81
+ * Best-effort by nature: a removed party learns only if the tombstone
82
+ * replicated to it before the cut.
83
+ *
84
+ * ## What transfers from the control gater (`membership-connection-gater.ts`)
85
+ *
86
+ * Transfers: the two-layer shape (fail-closed per-stream gate as PRIMARY, an
87
+ * opportunistic connection-level deny on top), the synchronous in-memory
88
+ * snapshot judged by the stream gate (a live DB read inside a gate deadlocks —
89
+ * the same argument as the control node's `authorizeInboundControlStream`), the
90
+ * fail-open-on-error/timeout posture for the connection hooks
91
+ * (`decideWithinDeadline`, reused as-is), and the "compose over the caller's
92
+ * gater, every base hook preserved" mechanics.
93
+ *
94
+ * Does NOT transfer — the relay-reservation seam (`'admit-for-relay'` + the
95
+ * unauthorized budget). That machinery exists because the control gate denies on
96
+ * ABSENCE of placement, where a member whose row is in flight is
97
+ * indistinguishable from an outsider and a wrong reservation deny is
98
+ * unrecoverable. This gate denies on POSITIVE revocation evidence, so a
99
+ * reservation deny is never that kind of wrong answer; the one stale-wrong case
100
+ * (a missed re-add) heals via replication as above. `denyInboundRelayReservation`
101
+ * here is therefore a plain "deny iff revoked" — no budget, no admit-for-relay
102
+ * verdict, no reserve deadline.
103
+ *
104
+ * Does NOT transfer — the bring-up quiet period. A strand node's DB bring-up
105
+ * NEEDS its cross-party cohort connections; this enforcer simply starts with an
106
+ * empty snapshot (admit everything) until its first successful read — the same
107
+ * fail-open posture as everywhere else here.
108
+ *
109
+ * Does NOT transfer — every stranger carve-out (enrollment window, formation
110
+ * window, delegate grants). None of those protocols exist on a strand node.
111
+ *
112
+ * Unlike the control stream gate, no empty-snapshot carve-out is needed: an
113
+ * empty deny set means "nothing revoked", which correctly admits everyone — the
114
+ * deny-list shape makes the cold-start case and the steady case the same code
115
+ * path.
116
+ */
117
+ import debug from 'debug';
118
+ import { ADMISSION_DECISION_TIMEOUT_MS, decideWithinDeadline } from './membership-connection-gater.js';
119
+ const log = debug('sereus:cadre:strand-revocation');
120
+ /**
121
+ * Default cadence of the deny-set refresh. Bounded staleness is the accepted
122
+ * failure direction (see the module doc's stale-view paragraph); 30 s keeps a
123
+ * revoked peer's post-replication admit window in the same order as the
124
+ * replication delay itself. Overridable via
125
+ * {@link StrandRevocationEnforcementConfig.pollIntervalMs}.
126
+ */
127
+ export const DEFAULT_REVOCATION_POLL_INTERVAL_MS = 30000;
128
+ const defaultScheduler = {
129
+ setInterval: (fn, ms) => {
130
+ const handle = setInterval(fn, ms);
131
+ handle.unref?.();
132
+ return handle;
133
+ },
134
+ clearInterval: (handle) => clearInterval(handle)
135
+ };
136
+ /** Every `Strand.MemberPeer` binding — live-membered and orphaned alike. */
137
+ async function scanBindings(db) {
138
+ const bindings = [];
139
+ for await (const row of db.eval('select MemberKey, PeerId from Strand.MemberPeer')) {
140
+ bindings.push({ memberKey: row.MemberKey, peerId: row.PeerId });
141
+ }
142
+ return bindings;
143
+ }
144
+ /** Every live `Strand.Member.Key`. */
145
+ async function scanMemberKeys(db) {
146
+ const memberKeys = new Set();
147
+ for await (const row of db.eval('select Key from Strand.Member')) {
148
+ memberKeys.add(row.Key);
149
+ }
150
+ return memberKeys;
151
+ }
152
+ /**
153
+ * Read the deny-set inputs from a strand database: full-scan
154
+ * `Strand.MemberPeer` and `Strand.Member`, to be joined in JavaScript by the
155
+ * enforcer. Scan-and-filter, never seek — the composite-PK point-lookup
156
+ * unreliability documented at `scanMemberPeers` in `strand-membership-writer.ts`
157
+ * applies, so correctness depends only on each scan returning a superset of the
158
+ * live rows.
159
+ *
160
+ * **Scan order is load-bearing, and it is BINDINGS FIRST.** The two scans are
161
+ * separate reads with nothing holding them to one snapshot, so a write that
162
+ * lands between them is seen by the second scan and not the first. A member
163
+ * joins as `Member` row then `MemberPeer` row (`MemberExists` forces that
164
+ * order), so reading `Member` first would let a join that commits mid-read
165
+ * produce a binding whose member key is absent from the key set — a brand-new
166
+ * member classified as REVOKED, the one fail-CLOSED outcome this module exists
167
+ * to avoid. Reading bindings first inverts the skew: the new binding is simply
168
+ * not in the older bindings snapshot, so the joiner is unclassified and
169
+ * admitted, and a revocation that lands mid-read is still seen (the key is
170
+ * missing from the NEWER key scan). Every skew window then resolves fail-open,
171
+ * matching the module doc.
172
+ *
173
+ * NOTE: two whole-table scans per refresh (default every 30 s per closed
174
+ * strand). Fine at strand scale (a handful of members and bindings); if
175
+ * membership churn ever makes these tables large, the fix is a reliable
176
+ * filtered read — which needs the networked point-lookup gap closed
177
+ * (`debt-composite-pk-point-lookup-unreliable-untracked`) — not a bigger scan.
178
+ */
179
+ export async function readStrandRevocationRows(db) {
180
+ const bindings = await scanBindings(db);
181
+ const memberKeys = await scanMemberKeys(db);
182
+ return { memberKeys, bindings };
183
+ }
184
+ /**
185
+ * The per-strand revoked-peer deny set and the synchronous predicates over it.
186
+ *
187
+ * Lifecycle mirrors `PeerJoinBackfill`: created in
188
+ * `StrandInstanceManager.buildStrandRuntime` (closed strands only), started
189
+ * after the strand database initializes, stopped and dropped in
190
+ * `releaseRuntime` — so quiesce → resume rebuilds it with a fresh snapshot.
191
+ * The snapshot starts EMPTY (admit everything) and repopulates on the first
192
+ * successful read; a revoked peer therefore gets a short admit window per
193
+ * resume at the connection level. Accepted: fail-open posture, and resume is
194
+ * never blocked on a refresh.
195
+ */
196
+ export class StrandRevocationEnforcer {
197
+ constructor(deps, config) {
198
+ this.deps = deps;
199
+ /** The materialized deny set — peer ids with an orphaned binding and no live one. */
200
+ this.revokedPeerIds = new Set();
201
+ /** Tail of the refresh chain — what serializes refreshes (no two reads in flight). */
202
+ this.refreshTail = Promise.resolve();
203
+ this.refreshing = false;
204
+ this.started = false;
205
+ this.stopped = false;
206
+ /** Latch for the one-shot self-revocation signal — see {@link StrandRevocationEnforcerDeps.onSelfRevoked}. */
207
+ this.selfRevokedSignaled = false;
208
+ this.scheduler = deps.scheduler ?? defaultScheduler;
209
+ this.pollIntervalMs = config?.pollIntervalMs ?? DEFAULT_REVOCATION_POLL_INTERVAL_MS;
210
+ }
211
+ /** Number of peers currently denied — test/diagnostic surface. */
212
+ get revokedCount() {
213
+ return this.revokedPeerIds.size;
214
+ }
215
+ /** Kick an immediate refresh (not awaited — bring-up never blocks on it) and arm the poll. */
216
+ start() {
217
+ if (this.started || this.stopped)
218
+ return;
219
+ this.started = true;
220
+ void this.refresh();
221
+ this.intervalHandle = this.scheduler.setInterval(() => {
222
+ // Skip a tick while a refresh is in flight rather than queueing behind it —
223
+ // the interval exists to bound staleness, and a stack of queued reads
224
+ // bounds nothing extra. Explicit refresh() calls still chain (see refresh).
225
+ if (!this.refreshing) {
226
+ void this.refresh();
227
+ }
228
+ }, this.pollIntervalMs);
229
+ log('[%s] revocation enforcer started (poll %dms)', this.deps.label, this.pollIntervalMs);
230
+ }
231
+ /** Disarm the poll; a read already in flight completes but its result is discarded. */
232
+ stop() {
233
+ if (this.stopped)
234
+ return;
235
+ this.stopped = true;
236
+ if (this.intervalHandle != null) {
237
+ this.scheduler.clearInterval(this.intervalHandle);
238
+ this.intervalHandle = undefined;
239
+ }
240
+ log('[%s] revocation enforcer stopped', this.deps.label);
241
+ }
242
+ /**
243
+ * Refresh the deny set now. Never rejects. Serialized: concurrent calls chain,
244
+ * so no two reads overlap, and each explicit call gets a read that STARTS
245
+ * after the call (an on-demand refresh after a revocation commit observes that
246
+ * commit). A failed or slow read keeps the previous snapshot — never clears it.
247
+ */
248
+ refresh() {
249
+ const run = this.refreshTail.then(() => this.doRefresh());
250
+ this.refreshTail = run;
251
+ return run;
252
+ }
253
+ /**
254
+ * Is `remotePeerId` positively revoked in the current snapshot? Synchronous by
255
+ * design — this is judged inside libp2p gates where an await against the DB
256
+ * would deadlock (the control stream gate's argument).
257
+ */
258
+ isRevoked(remotePeerId) {
259
+ return this.revokedPeerIds.has(remotePeerId);
260
+ }
261
+ /**
262
+ * The fail-closed PRIMARY layer: the per-stream predicate wired as
263
+ * `createLibp2pNode({ authorizeInboundStream })`, gating all four
264
+ * `/optimystic/strand-<id>/{repo,cluster,sync,block-transfer}` protocols.
265
+ * Also what catches a revoked peer arriving over a RELAYED connection, or
266
+ * over a connection admitted before the revocation replicated here.
267
+ */
268
+ authorizeStream(remotePeerId, _protocol) {
269
+ return !this.isRevoked(remotePeerId);
270
+ }
271
+ /**
272
+ * One serialized snapshot rebuild; contains every failure (contract: never rejects).
273
+ *
274
+ * NOTE: nothing here carries a deadline — a `readRows` or a `hangUp` that never
275
+ * settles wedges the refresh chain, which freezes the deny set (the poll skips
276
+ * while `refreshing`, and every later `refresh()` chains behind the stuck one).
277
+ * Both are bounded in practice (Quereus reads settle; libp2p's connection close
278
+ * has its own timeout). If a strand is ever seen with a stale deny set and no
279
+ * refresh log line, wrap both awaits in a timeout rather than hunting further.
280
+ */
281
+ async doRefresh() {
282
+ if (this.stopped)
283
+ return;
284
+ this.refreshing = true;
285
+ try {
286
+ const rows = await this.deps.readRows();
287
+ if (this.stopped)
288
+ return;
289
+ this.revokedPeerIds = deriveRevokedPeerIds(rows);
290
+ log('[%s] deny set refreshed: %d revoked peer(s)', this.deps.label, this.revokedPeerIds.size);
291
+ // Awaited inside the refresh, so an on-demand refresh() resolves only once
292
+ // the cut has been made — that is what makes "revoke, then refresh" an
293
+ // immediate teardown for the caller rather than a scheduled one.
294
+ await this.tearDownRevoked(this.revokedPeerIds);
295
+ }
296
+ catch (error) {
297
+ log('[%s] deny-set refresh failed — keeping previous snapshot: %o', this.deps.label, error);
298
+ }
299
+ finally {
300
+ this.refreshing = false;
301
+ }
302
+ }
303
+ /**
304
+ * Close what is already OPEN to the peers of `revoked` — the teardown half of
305
+ * enforcement (module doc: "Removal cuts LIVE sessions too"). Never rejects:
306
+ * every failure is contained, so the refresh loop survives and simply retries
307
+ * on the next tick.
308
+ *
309
+ * `revoked` is passed IN rather than read off the field, so the sweep judges
310
+ * the exact snapshot its own refresh produced; likewise the connection list is
311
+ * enumerated once, before any await, so a peer re-admitted mid-sweep is at
312
+ * worst hung up once and reconnects.
313
+ *
314
+ * NOTE: dropping the revoked peer from the strand's Optimystic cohort needs no
315
+ * separate action — machines quiesce strands routinely and the remaining cohort
316
+ * keeps committing, so an unreachable cohort peer is already tolerated, and
317
+ * `denyDialPeer` makes the exclusion fast (a local refusal, not a dial timeout).
318
+ * Two residual facts, both accepted: the removed node keeps every block it had
319
+ * already replicated (revocation is forward-looking), and a block whose only
320
+ * holders were the removed party's machines can be unavailable to the rest
321
+ * until repair.
322
+ *
323
+ * NOTE: post-removal commit latency is real and MEASURED, not hypothetical. On
324
+ * the two-parties-by-two-machines fixture in
325
+ * `integration-tests/src/scenarios/strand-removal-cuts-network.integration.ts`
326
+ * the remaining cohort's FIRST write after a cut fails repeatedly with
327
+ * `BlockUnavailableError: … (peers-unreachable)` before it commits — three or
328
+ * four failures, 3.5-4.3 s, over four runs — because the cohort still listed
329
+ * the removed party's now unreachable machines and had to downsize to the
330
+ * live holders first. It
331
+ * recovers on its own, so this is a latency cost, not a defect, and it is
332
+ * stated for app authors in `docs/strands.md` → Removing Members. If it ever
333
+ * stops recovering — a write that never commits, or `cluster-fetch:no-quorum` —
334
+ * that is an upstream (Optimystic) conversation, not a workaround here.
335
+ */
336
+ async tearDownRevoked(revoked) {
337
+ if (this.stopped) {
338
+ return;
339
+ }
340
+ const network = this.resolveNetwork();
341
+ if (!network) {
342
+ return;
343
+ }
344
+ const selfPeerId = network.peerId.toString();
345
+ // Judged on EVERY pass, including the empty-set one: leaving the set is what
346
+ // re-arms the signal (see trackSelfRevoked). Being revoked ourselves tears
347
+ // nothing down on our own behalf — the other side's gate already refuses us —
348
+ // so the sweep below still runs for every OTHER revoked peer in the pass.
349
+ this.trackSelfRevoked(revoked.has(selfPeerId));
350
+ if (revoked.size === 0) {
351
+ return;
352
+ }
353
+ for (const peer of connectedRevokedPeers(network, revoked, selfPeerId, this.deps.label)) {
354
+ await this.hangUpRevoked(network, peer);
355
+ }
356
+ }
357
+ /** The lazily-read network handle; an accessor that throws costs this pass only. */
358
+ resolveNetwork() {
359
+ try {
360
+ return this.deps.getNetwork?.();
361
+ }
362
+ catch (error) {
363
+ log('[%s] network handle unavailable — teardown skipped this pass: %o', this.deps.label, error);
364
+ return undefined;
365
+ }
366
+ }
367
+ /** One hangUp, contained: a stopping node or an already-closed connection is not an error here. */
368
+ async hangUpRevoked(network, peer) {
369
+ try {
370
+ await network.hangUp(peer);
371
+ log('[%s] hung up revoked peer %s', this.deps.label, peer.toString());
372
+ }
373
+ catch (error) {
374
+ log('[%s] hangUp of revoked peer %s failed — next refresh retries: %o', this.deps.label, peer.toString(), error);
375
+ }
376
+ }
377
+ /**
378
+ * Latch {@link StrandRevocationEnforcerDeps.onSelfRevoked} to the TRANSITION
379
+ * into the revoked state: fired once on entry, and re-armed the moment this
380
+ * node is out of the set again — a manager can re-admit a party it removed
381
+ * (a fresh `Member` row makes the orphaned bindings live again), and a second
382
+ * removal has to be reported like the first.
383
+ */
384
+ trackSelfRevoked(selfRevoked) {
385
+ if (!selfRevoked) {
386
+ this.selfRevokedSignaled = false;
387
+ return;
388
+ }
389
+ if (this.selfRevokedSignaled) {
390
+ return;
391
+ }
392
+ this.selfRevokedSignaled = true;
393
+ log('[%s] THIS node is revoked from the strand (removed or left) — signalling', this.deps.label);
394
+ try {
395
+ this.deps.onSelfRevoked?.();
396
+ }
397
+ catch (error) {
398
+ log('[%s] onSelfRevoked handler threw: %o', this.deps.label, error);
399
+ }
400
+ }
401
+ }
402
+ /**
403
+ * The distinct peer ids of `network`'s open connections that are in `revoked`,
404
+ * as the `PeerId` objects `hangUp` takes — reusing each connection's own
405
+ * `remotePeer` rather than parsing the string back, so no peer-id codec is
406
+ * needed here. Self is excluded (a node holds no connection to itself, and
407
+ * hanging one up would be nonsense if it did), and a peer connected twice — say
408
+ * one direct and one relayed — yields ONE hangUp, which closes both. A throw
409
+ * mid-enumeration is logged and keeps whatever was collected before it: a
410
+ * partial sweep beats none, and the next refresh re-enumerates anyway.
411
+ */
412
+ function connectedRevokedPeers(network, revoked, selfPeerId, label) {
413
+ const targets = new Map();
414
+ try {
415
+ for (const connection of network.getConnections()) {
416
+ const remotePeerId = connection.remotePeer.toString();
417
+ if (remotePeerId !== selfPeerId && revoked.has(remotePeerId)) {
418
+ targets.set(remotePeerId, connection.remotePeer);
419
+ }
420
+ }
421
+ }
422
+ catch (error) {
423
+ log('[%s] connection enumeration failed — teardown skipped this pass: %o', label, error);
424
+ }
425
+ return [...targets.values()];
426
+ }
427
+ /**
428
+ * Derive the deny set: a peer id is revoked iff it has at least one ORPHANED
429
+ * binding (a `MemberPeer` row whose member key has no live `Member` row) AND no
430
+ * live-membered binding. The second conjunct is what lets a peer id bound to
431
+ * TWO member keys — revoked under one, live under the other — keep serving the
432
+ * live member: a machine serving a live member must not be cut. Per-binding,
433
+ * not per-member, so every machine of a multi-device removed party lands in
434
+ * the set.
435
+ */
436
+ function deriveRevokedPeerIds(rows) {
437
+ const orphaned = new Set();
438
+ const live = new Set();
439
+ for (const binding of rows.bindings) {
440
+ (rows.memberKeys.has(binding.memberKey) ? live : orphaned).add(binding.peerId);
441
+ }
442
+ for (const peerId of live) {
443
+ orphaned.delete(peerId);
444
+ }
445
+ return orphaned;
446
+ }
447
+ /**
448
+ * Build a closed strand's connection gater: the caller-supplied gater (if any)
449
+ * with revoked-peer denial composed onto three hooks. The opportunistic
450
+ * SECONDARY layer — the per-stream gate above is the fail-closed primary.
451
+ *
452
+ * - `denyInboundEncryptedConnection`: deny iff revoked, so a removed party's
453
+ * node is refused before any protocol negotiation.
454
+ * - `denyDialPeer`: deny iff revoked — the owner ruling covers "communicated
455
+ * with", not just "answered to", and refusing the dial locally is instant,
456
+ * which keeps Optimystic from wasting a dial timeout on a peer this node
457
+ * will not talk to anyway.
458
+ * - `denyInboundRelayReservation`: deny iff revoked — matters when the strand
459
+ * node runs the relay server (the storage-profile default), where admitting
460
+ * the reservation would let a removed party keep using this node's
461
+ * forwarding capacity.
462
+ *
463
+ * Composition semantics: every hook of `base` is preserved as-is; on the three
464
+ * composed hooks a deny from EITHER the base gater or the revocation check
465
+ * denies, and an error (or a decision slower than `decisionTimeoutMs`) in
466
+ * either is fail-open — the base error admits through to the revocation check,
467
+ * a revocation-check error admits outright, and the fail-closed stream gate
468
+ * still stands behind both. The deadline is belt-and-braces: the snapshot read
469
+ * is synchronous.
470
+ *
471
+ * NOTE: accepted tradeoff — swallowing a BASE hook's throw diverges from
472
+ * `createMembershipConnectionGater`, which lets it propagate to libp2p (a
473
+ * fail-closed outcome there). Uniform fail-open was chosen for this module so
474
+ * every hook has one failure direction, and it was the shape the plan specified;
475
+ * the cost is that an embedder gater which throws is silently admitted here
476
+ * while the same gater passed raw to an OPEN strand would refuse. Loud in the
477
+ * log either way. Revisit if an embedder ever ships a gater whose throw is a
478
+ * meaningful deny, or if the two gaters are unified.
479
+ *
480
+ * NOTE: `base` is spread, so a gater passed as a CLASS INSTANCE would lose its
481
+ * prototype methods — same caveat as `createMembershipConnectionGater`; every
482
+ * caller in this repo supplies a plain object (the RN/web permissive gaters,
483
+ * e.g. `{ denyDialMultiaddr: () => false }`, keep working unchanged).
484
+ */
485
+ export function createRevocationConnectionGater(enforcer, base, decisionTimeoutMs = ADMISSION_DECISION_TIMEOUT_MS) {
486
+ /** The base gater's verdict, fail-open: an error or absence is "not denied". */
487
+ const baseDenies = async (decide, hook) => {
488
+ try {
489
+ return await decide() === true;
490
+ }
491
+ catch (error) {
492
+ log('base gater %s threw — treating as not-denied (fail-open): %o', hook, error);
493
+ return false;
494
+ }
495
+ };
496
+ /** The revocation verdict, bounded and fail-open. */
497
+ const revokedDenies = async (peerId, hook) => {
498
+ const remotePeerId = peerId.toString();
499
+ try {
500
+ const revoked = await decideWithinDeadline(() => enforcer.isRevoked(remotePeerId), false, decisionTimeoutMs, `${hook}(${remotePeerId})`);
501
+ if (revoked) {
502
+ log('%s: %s is revoked — denying', hook, remotePeerId);
503
+ }
504
+ return revoked;
505
+ }
506
+ catch (error) {
507
+ log('%s revocation check threw for %s — admitting (fail-open; stream gate decides): %o', hook, remotePeerId, error);
508
+ return false;
509
+ }
510
+ };
511
+ return {
512
+ ...base,
513
+ denyDialPeer: async (peerId) => {
514
+ if (await baseDenies(() => base?.denyDialPeer?.(peerId), 'denyDialPeer')) {
515
+ return true;
516
+ }
517
+ return revokedDenies(peerId, 'denyDialPeer');
518
+ },
519
+ denyInboundEncryptedConnection: async (peerId, maConn) => {
520
+ if (await baseDenies(() => base?.denyInboundEncryptedConnection?.(peerId, maConn), 'denyInboundEncryptedConnection')) {
521
+ return true;
522
+ }
523
+ return revokedDenies(peerId, 'denyInboundEncryptedConnection');
524
+ },
525
+ denyInboundRelayReservation: async (peerId) => {
526
+ if (await baseDenies(() => base?.denyInboundRelayReservation?.(peerId), 'denyInboundRelayReservation')) {
527
+ return true;
528
+ }
529
+ return revokedDenies(peerId, 'denyInboundRelayReservation');
530
+ }
531
+ };
532
+ }
533
+ //# sourceMappingURL=strand-revocation-enforcer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strand-revocation-enforcer.js","sourceRoot":"","sources":["../src/strand-revocation-enforcer.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { Libp2p } from '@libp2p/interface';
2
- import type { OpenInvitation, FormStrandResult, StrandFormationDisclosure } from './types.js';
2
+ import type { OpenInvitation, FormStrandResult, StrandFormationDisclosure, StrandMembershipInvite } from './types.js';
3
3
  import { type StrandFormationManagerConfig } from './strand-formation-manager.js';
4
4
  import { type FormationResultMessage } from './strand-formation-protocol.js';
5
5
  /**
@@ -164,6 +164,20 @@ export interface StrandSolicitationServiceOptions {
164
164
  partyId?: string;
165
165
  /** Cadre peer addresses for this node */
166
166
  cadrePeerAddrs?: string[];
167
+ /**
168
+ * This node's live STRAND-network multiaddrs for a strand it is running (responder
169
+ * side), carried back to a validated joiner as its cross-party discovery seed. Wired
170
+ * by `CadreNode`; unwired, the responder discloses no strand addresses.
171
+ */
172
+ resolveStrandAddrs?: (strandId: string) => string[];
173
+ /**
174
+ * Issue a single-use strand membership invitation against the live host strand a
175
+ * bound closed-strand redemption resolved to (responder side). Wired by `CadreNode`;
176
+ * see `StrandFormationManagerOptions.issueMembershipInvite` for the full contract
177
+ * (null = open strand, throw = reject the redemption retryably, unwired = mock/test
178
+ * posture: approve with no invitation).
179
+ */
180
+ issueMembershipInvite?: (strandId: string) => Promise<StrandMembershipInvite | null>;
167
181
  /** Configuration for the formation manager */
168
182
  formationConfig?: StrandFormationManagerConfig;
169
183
  }
@@ -187,6 +201,8 @@ export declare class StrandSolicitationService {
187
201
  private readonly formationResponseValidator?;
188
202
  private readonly partyId;
189
203
  private readonly cadrePeerAddrs;
204
+ private readonly resolveStrandAddrs?;
205
+ private readonly issueMembershipInvite?;
190
206
  private formationManager?;
191
207
  private readonly formationConfig?;
192
208
  /**
@@ -58,6 +58,8 @@ export class StrandSolicitationService {
58
58
  this.formationResponseValidator = options?.formationResponseValidator;
59
59
  this.partyId = options?.partyId ?? `party-${Date.now()}`;
60
60
  this.cadrePeerAddrs = options?.cadrePeerAddrs ?? [];
61
+ this.resolveStrandAddrs = options?.resolveStrandAddrs;
62
+ this.issueMembershipInvite = options?.issueMembershipInvite;
61
63
  this.formationConfig = options?.formationConfig;
62
64
  log('StrandSolicitationService created for party: %s', this.partyId);
63
65
  }
@@ -74,6 +76,8 @@ export class StrandSolicitationService {
74
76
  formationResponseValidator: this.formationResponseValidator,
75
77
  partyId: this.partyId,
76
78
  cadrePeerAddrs: this.cadrePeerAddrs,
79
+ resolveStrandAddrs: this.resolveStrandAddrs,
80
+ issueMembershipInvite: this.issueMembershipInvite,
77
81
  config: this.formationConfig
78
82
  });
79
83
  }
@@ -138,7 +142,12 @@ export class StrandSolicitationService {
138
142
  strandId: result.strandId,
139
143
  // Carry the host strand's membership key delivered over the protocol (closed-strand
140
144
  // provision-then-record). invitePrivateKey stays the initiator's generated signing key.
141
- memberPrivateKey: result.memberPrivateKey
145
+ memberPrivateKey: result.memberPrivateKey,
146
+ // The joiner's own single-use membership invitation into a closed host strand
147
+ // (already shape-checked by the manager); absent for open/unbound.
148
+ membershipInvite: result.membershipInvite,
149
+ // The responder's live strand-network addresses, or `[]` when it had none to give.
150
+ strandAddrs: result.strandAddrs
142
151
  };
143
152
  }
144
153
  // Fallback: placeholder strandId (for testing without network)
@@ -147,7 +156,9 @@ export class StrandSolicitationService {
147
156
  return {
148
157
  memberKey,
149
158
  invitePrivateKey,
150
- strandId
159
+ strandId,
160
+ // No wire, so nothing was disclosed — an empty seed, never absent.
161
+ strandAddrs: []
151
162
  };
152
163
  }
153
164
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"strand-solicitation.js","sourceRoot":"","sources":["../src/strand-solicitation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC;AAMzD,sFAAsF;AACtF,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EACL,sBAAsB,EAEvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAE9B,MAAM,gCAAgC,CAAC;AAExC,MAAM,GAAG,GAAG,KAAK,CAAC,2BAA2B,CAAC,CAAC;AAiJ/C;;;;;;;GAOG;AACH,MAAM,UAAU,uCAAuC;IACrD,OAAO;QACL,KAAK,CAAC,gBAAgB,CAAC,EAAE,QAAQ,EAAE;YACjC,OAAO,6BAA6B,CAAC,QAAQ,CAAC,CAAC;QACjD,CAAC;KACF,CAAC;AACJ,CAAC;AAgBD;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,yBAAyB;IAsBpC,YAAY,OAA0C;QAbtD;;;;;;;;;;WAUG;QACc,sBAAiB,GAAG,IAAI,GAAG,EAAkB,CAAC;QAG7D,IAAI,CAAC,mBAAmB,GAAG,OAAO,EAAE,mBAAmB,CAAC;QACxD,IAAI,CAAC,sBAAsB,GAAG,OAAO,EAAE,sBAAsB,CAAC;QAC9D,IAAI,CAAC,iBAAiB,GAAG,OAAO,EAAE,iBAAiB,CAAC;QACpD,IAAI,CAAC,0BAA0B,GAAG,OAAO,EAAE,0BAA0B,CAAC;QACtE,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,SAAS,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACzD,IAAI,CAAC,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,EAAE,CAAC;QACpD,IAAI,CAAC,eAAe,GAAG,OAAO,EAAE,eAAe,CAAC;QAChD,GAAG,CAAC,iDAAiD,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACvE,CAAC;IAED;;;OAGG;IACK,mBAAmB;QACzB,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3B,IAAI,CAAC,gBAAgB,GAAG,IAAI,sBAAsB,CAAC;gBACjD,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;gBAC7C,sBAAsB,EAAE,IAAI,CAAC,sBAAsB;gBACnD,iBAAiB,EAAE,IAAI,CAAC,iBAAiB;gBACzC,0BAA0B,EAAE,IAAI,CAAC,0BAA0B;gBAC3D,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,MAAM,EAAE,IAAI,CAAC,eAAe;aAC7B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC,gBAAgB,CAAC;IAC/B,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,IAAY;QAC5B,IAAI,CAAC,mBAAmB,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACnD,GAAG,CAAC,yBAAyB,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,mBAAmB,CAAC,IAAY;QAC9B,IAAI,CAAC,mBAAmB,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACrD,GAAG,CAAC,2BAA2B,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,UAAU,CACd,UAA0B,EAC1B,UAAqC,EACrC,IAAa;QAEb,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;QAC/B,GAAG,CAAC,+BAA+B,EAAE,KAAK,CAAC,CAAC;QAE5C,oDAAoD;QACpD,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,SAAS,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;QAChD,MAAM,eAAe,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;QAEzD,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;QAEvE,GAAG,CAAC,0BAA0B,EAAE,SAAS,CAAC,CAAC;QAE3C,2CAA2C;QAC3C,IAAI,IAAI,EAAE,CAAC;YACT,GAAG,CAAC,6CAA6C,CAAC,CAAC;YACnD,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;YAC7E,MAAM,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;YAChD,2EAA2E;YAC3E,gFAAgF;YAChF,4EAA4E;YAC5E,MAAM,cAAc,GAAG,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;YAC7D,MAAM,aAAa,GAAG,IAAI,CACxB,uBAAuB,CAAC;gBACtB,KAAK;gBACL,YAAY;gBACZ,OAAO,EAAE,YAAY;gBACrB,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC;aAC1C,CAAC,EACF,aAAa,EACb,SAAS,EACT,OAAO,EACP,WAAW,EACX,WAAW,CACF,CAAC;YACZ,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,mBAAmB,EAAE,CAAC,UAAU,CACxD,UAAU,EACV,cAAc,EACd,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,EACtD,IAAI,CACL,CAAC;YACF,OAAO;gBACL,SAAS;gBACT,gBAAgB;gBAChB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,oFAAoF;gBACpF,wFAAwF;gBACxF,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;aAC1C,CAAC;QACJ,CAAC;QAED,+DAA+D;QAC/D,GAAG,CAAC,8CAA8C,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,UAAU,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAElF,OAAO;YACL,SAAS;YACT,gBAAgB;YAChB,QAAQ;SACT,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,oBAAoB,CACxB,MAAc,EACd,YAAoB,EACpB,SAAmB;QAEnB,0BAA0B;QAC1B,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QAChF,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,CAAC;QACvD,IAAI,CAAC,wBAAwB,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;QAE3D,GAAG,CAAC,0CAA0C,EAAE,KAAK,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;QAEjF,OAAO;YACL,KAAK;YACL,MAAM;YACN,UAAU;YACV,SAAS;SACV,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,wBAAwB,CAAC,KAAa,EAAE,WAAmB;QACzD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAC/C,GAAG,CAAC,8CAA8C,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;IAC1E,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,CAAC,wBAAwB;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC1D,IAAI,WAAW,IAAI,GAAG,EAAE,CAAC;gBACvB,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACrC,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,CAAC;gBACjC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAC5D,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,CAAC,MAAM,IAAI,CAAC,sBAAsB,EAAE,wBAAwB,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC;IACpF,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,uBAAuB,CAC3B,KAAa,EACb,OAAe,EACf,QAAgB,EAChB,OAAwD,EACxD,UAAU,GAAG,EAAE;QAEf,IAAI,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,sBAAsB,CAAC,WAAW,CAAC;gBAC5C,KAAK;gBACL,OAAO;gBACP,aAAa,EAAE,OAAO,CAAC,aAAa;gBACpC,YAAY,EAAE,OAAO,CAAC,YAAY;gBAClC,QAAQ;gBACR,UAAU;aACX,CAAC,CAAC;YACH,GAAG,CAAC,8CAA8C,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"strand-solicitation.js","sourceRoot":"","sources":["../src/strand-solicitation.ts"],"names":[],"mappings":"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"}