@serfab/cadre-core 1.17.0 → 1.19.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.
- package/README.md +3 -2
- package/dist/cadre-invite-protocol.d.ts +35 -13
- package/dist/cadre-invite-protocol.js +90 -25
- package/dist/cadre-invite-protocol.js.map +1 -1
- package/dist/cadre-node.d.ts +586 -255
- package/dist/cadre-node.js +1182 -566
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-cohort.d.ts +66 -9
- package/dist/control-cohort.js +67 -9
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +451 -193
- package/dist/control-database.js +701 -359
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +46 -43
- package/dist/control-formation-recorder.js +77 -78
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-protocol-guard.d.ts +6 -0
- package/dist/control-protocol-guard.js +14 -2
- package/dist/control-protocol-guard.js.map +1 -1
- package/dist/control-schema-version.d.ts +48 -31
- package/dist/control-schema-version.js +67 -45
- package/dist/control-schema-version.js.map +1 -1
- package/dist/control-schema.d.ts +8 -8
- package/dist/control-schema.js +16 -16
- package/dist/control-schema.js.map +1 -1
- package/dist/control-write-retry.d.ts +42 -7
- package/dist/control-write-retry.js +87 -19
- package/dist/control-write-retry.js.map +1 -1
- package/dist/enrolled-machine-store.d.ts +3 -3
- package/dist/enrolled-machine-store.js +3 -3
- package/dist/formation-token.d.ts +48 -0
- package/dist/formation-token.js +26 -0
- package/dist/formation-token.js.map +1 -0
- package/dist/index.d.ts +15 -14
- package/dist/index.js +17 -14
- package/dist/index.js.map +1 -1
- package/dist/joined-strand-store.d.ts +26 -9
- package/dist/joined-strand-store.js +63 -24
- package/dist/joined-strand-store.js.map +1 -1
- package/dist/link-budget.d.ts +9 -7
- package/dist/link-budget.js +9 -7
- package/dist/link-budget.js.map +1 -1
- package/dist/member-redemption.d.ts +15 -9
- package/dist/member-redemption.js +13 -8
- package/dist/member-redemption.js.map +1 -1
- package/dist/membership-view.d.ts +91 -0
- package/dist/membership-view.js +110 -0
- package/dist/membership-view.js.map +1 -0
- package/dist/owner-grant-protocol.d.ts +11 -7
- package/dist/owner-grant-protocol.js +31 -17
- package/dist/owner-grant-protocol.js.map +1 -1
- package/dist/peer-addr-book.d.ts +10 -1
- package/dist/peer-addr-book.js +4 -3
- package/dist/peer-addr-book.js.map +1 -1
- package/dist/peer-join-backfill.d.ts +96 -30
- package/dist/peer-join-backfill.js +272 -128
- package/dist/peer-join-backfill.js.map +1 -1
- package/dist/pending-join-runner.d.ts +48 -18
- package/dist/pending-join-runner.js +74 -56
- package/dist/pending-join-runner.js.map +1 -1
- package/dist/seed-bootstrap.d.ts +47 -40
- package/dist/seed-bootstrap.js +66 -61
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/self-address-update.d.ts +13 -0
- package/dist/self-address-update.js +19 -0
- package/dist/self-address-update.js.map +1 -0
- package/dist/strand-database.d.ts +23 -2
- package/dist/strand-database.js +26 -6
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-first-sync-gate.d.ts +176 -89
- package/dist/strand-first-sync-gate.js +288 -122
- package/dist/strand-first-sync-gate.js.map +1 -1
- package/dist/strand-formation-deadlines.d.ts +9 -9
- package/dist/strand-formation-deadlines.js +9 -9
- package/dist/strand-formation-manager.d.ts +23 -17
- package/dist/strand-formation-manager.js +46 -44
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +5 -8
- package/dist/strand-formation-protocol.js +101 -16
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +26 -17
- package/dist/strand-instance-manager.js +119 -50
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-membership-reconciler.d.ts +16 -13
- package/dist/strand-membership-reconciler.js +28 -22
- package/dist/strand-membership-reconciler.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +40 -28
- package/dist/strand-membership-writer.js +212 -153
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-move.js +5 -2
- package/dist/strand-move.js.map +1 -1
- package/dist/strand-revocation-enforcer.d.ts +134 -60
- package/dist/strand-revocation-enforcer.js +144 -71
- package/dist/strand-revocation-enforcer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +50 -39
- package/dist/strand-solicitation.js +10 -3
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-watcher.d.ts +80 -13
- package/dist/strand-watcher.js +152 -48
- package/dist/strand-watcher.js.map +1 -1
- package/dist/table-change-watch.d.ts +63 -0
- package/dist/table-change-watch.js +209 -0
- package/dist/table-change-watch.js.map +1 -0
- package/dist/timeout-scheduler.d.ts +4 -3
- package/dist/timeout-scheduler.js +4 -3
- package/dist/timeout-scheduler.js.map +1 -1
- package/dist/types.d.ts +90 -49
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/package.json +8 -8
- package/src/cadre-invite-protocol.ts +119 -31
- package/src/cadre-node.ts +1289 -588
- package/src/control-cohort.ts +86 -9
- package/src/control-database.ts +848 -403
- package/src/control-formation-recorder.ts +359 -353
- package/src/control-protocol-guard.ts +16 -2
- package/src/control-schema-version.ts +101 -51
- package/src/control-schema.ts +16 -16
- package/src/control-write-retry.ts +91 -19
- package/src/delegate-admission.ts +351 -351
- package/src/enrolled-machine-store.ts +3 -3
- package/src/formation-token.ts +63 -0
- package/src/index.ts +30 -18
- package/src/joined-strand-store.ts +69 -24
- package/src/link-budget.ts +9 -7
- package/src/member-redemption.ts +20 -13
- package/src/membership-view.ts +144 -0
- package/src/owner-grant-protocol.ts +33 -18
- package/src/peer-addr-book.ts +4 -3
- package/src/peer-join-backfill.ts +961 -761
- package/src/pending-join-runner.ts +76 -53
- package/src/seed-bootstrap.ts +72 -67
- package/src/self-address-update.ts +19 -0
- package/src/strand-database.ts +352 -322
- package/src/strand-first-sync-gate.ts +334 -134
- package/src/strand-formation-deadlines.ts +227 -227
- package/src/strand-formation-manager.ts +731 -725
- package/src/strand-formation-protocol.ts +122 -24
- package/src/strand-instance-manager.ts +2092 -2026
- package/src/strand-membership-reconciler.ts +31 -25
- package/src/strand-membership-writer.ts +2093 -2019
- package/src/strand-move.ts +5 -2
- package/src/strand-revocation-enforcer.ts +740 -654
- package/src/strand-solicitation.ts +61 -34
- package/src/strand-watcher.ts +175 -56
- package/src/table-change-watch.ts +285 -0
- package/src/timeout-scheduler.ts +29 -28
- package/src/types.ts +89 -51
|
@@ -9,13 +9,21 @@
|
|
|
9
9
|
* every `Strand.MemberPeer.PeerId` whose `MemberKey` has no live `Strand.Member`
|
|
10
10
|
* row. `MemberPeer.MemberExists` runs on insert, so an orphaned binding can only
|
|
11
11
|
* mean "this member existed and was removed" — it is exactly the durable,
|
|
12
|
-
* replicated record of a removed party's machines.
|
|
13
|
-
*
|
|
12
|
+
* replicated record of a removed party's machines. That holds on the network
|
|
13
|
+
* only because every writer commits a party's `Member` row before its
|
|
14
|
+
* `MemberPeer` row, as separate commits (the membership reconciler does, and
|
|
15
|
+
* `registerMemberPeer` requires an existing member): Optimystic can save a
|
|
16
|
+
* multi-collection transaction's tables separately, and remaining members can
|
|
17
|
+
* apply them in either order. The one exception is the founder's bootstrap,
|
|
18
|
+
* which writes its own `Member` and `MemberPeer` in one transaction; that is
|
|
19
|
+
* acceptable because it commits before any other party has joined, so no other
|
|
20
|
+
* party's enforcer is reading yet. Everything NOT in the set is admitted.
|
|
21
|
+
* Deliberate consequences:
|
|
14
22
|
*
|
|
15
23
|
* - **Open strands are untouched.** `Member`/`MemberPeer` carry `OnlyClosed`
|
|
16
24
|
* checks, so an open strand has no rows and the set is empty — and
|
|
17
25
|
* `StrandInstanceManager` additionally skips arming this enforcer entirely for
|
|
18
|
-
* `Type !== 'c'`, so an open strand
|
|
26
|
+
* `Type !== 'c'`, so an open strand neither subscribes nor reads.
|
|
19
27
|
* - **A joining party mid-admission is admitted** (it has no orphaned binding).
|
|
20
28
|
* An allowlist ("only peers bound to live members") was considered and
|
|
21
29
|
* rejected: a joiner's first membership writes must travel through the very
|
|
@@ -44,22 +52,19 @@
|
|
|
44
52
|
* Refusing NEW connections only half-honors "no longer be answered to or
|
|
45
53
|
* communicated with": a long-lived strand connection may never re-dial, so
|
|
46
54
|
* without teardown a removed party keeps whatever session it already holds.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
|
55
|
+
* Teardown rides the snapshot refresh itself, which runs when the membership
|
|
56
|
+
* tables change (see "When the deny set is refreshed"). `refresh()` is public
|
|
57
|
+
* for a caller that wants to await the cut
|
|
53
58
|
* (`StrandInstanceManager.refreshRevocationEnforcement` →
|
|
54
|
-
* `CadreNode.refreshRevocationEnforcement`).
|
|
59
|
+
* `CadreNode.refreshRevocationEnforcement`): it resolves only after the sweep.
|
|
55
60
|
*
|
|
56
61
|
* The sweep is driven by MEMBERSHIP in the deny set, never by an
|
|
57
62
|
* entered-the-set diff: every refresh hangs up every currently-connected
|
|
58
63
|
* revoked peer, so a resume, a crashed refresh, or a connection established
|
|
59
64
|
* between two refreshes cannot leave a session standing (a diff would be a
|
|
60
65
|
* 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
|
|
62
|
-
* refresh
|
|
66
|
+
* simply not swept, and a failed `hangUp` is logged, never thrown, and retried
|
|
67
|
+
* by a refresh {@link REVOCATION_REFRESH_RETRY_MS} later. `hangUp` closes DIRECT and RELAYED connections alike, and
|
|
63
68
|
* on a strand node running the relay server it also drops any reservation the
|
|
64
69
|
* revoked peer holds there (a reservation dies with its connection — see the
|
|
65
70
|
* relay-seam doc in `membership-connection-gater.ts`).
|
|
@@ -81,6 +86,25 @@
|
|
|
81
86
|
* Best-effort by nature: a removed party learns only if the tombstone
|
|
82
87
|
* replicated to it before the cut.
|
|
83
88
|
*
|
|
89
|
+
* ## When the deny set is refreshed
|
|
90
|
+
*
|
|
91
|
+
* Every refresh runs on one table change subscription (`table-change-watch.ts`),
|
|
92
|
+
* single-flight, so no two scheduled refreshes overlap:
|
|
93
|
+
*
|
|
94
|
+
* - **On a change** to `Strand.Member` or `Strand.MemberPeer`. App writes
|
|
95
|
+
* (`revokeMember`, `leaveStrand`) commit on the same Quereus `Database` the
|
|
96
|
+
* subscription is on, so a local removal is acted on within the coalescing
|
|
97
|
+
* window; a replicated one when this node's storage takes it in.
|
|
98
|
+
* - **Once at start**, when the strand's database is published.
|
|
99
|
+
* - **On a backstop** ({@link DEFAULT_REVOCATION_BACKSTOP_MS} after the last
|
|
100
|
+
* refresh settled), for a change that raised no event.
|
|
101
|
+
* - **After a failed refresh**, {@link REVOCATION_REFRESH_RETRY_MS} later. The
|
|
102
|
+
* first successful read also arms the plugin's bridge for replicated changes
|
|
103
|
+
* (the NOTE on `watchTables`), so until one succeeds only local commits wake it.
|
|
104
|
+
* - **After each successful refresh on a wide strand** — more distinct bound peer ids than
|
|
105
|
+
* the cluster size — {@link REVOCATION_WIDE_STRAND_REFRESH_MS} later, because
|
|
106
|
+
* this node may be outside a membership block's cohort and so hear no change.
|
|
107
|
+
*
|
|
84
108
|
* ## What transfers from the control gater (`membership-connection-gater.ts`)
|
|
85
109
|
*
|
|
86
110
|
* Transfers: the two-layer shape (fail-closed per-stream gate as PRIMARY, a
|
|
@@ -117,23 +141,33 @@
|
|
|
117
141
|
import debug from 'debug';
|
|
118
142
|
import { decideWithinDeadline } from './membership-connection-gater.js';
|
|
119
143
|
import { ADMISSION_DECISION_TIMEOUT_MS } from './link-budget.js';
|
|
144
|
+
import { scheduleRuns } from './table-change-watch.js';
|
|
120
145
|
const log = debug('sereus:cadre:strand-revocation');
|
|
121
146
|
/**
|
|
122
|
-
* Default
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
147
|
+
* Default longest wait between two deny-set refreshes when no membership change
|
|
148
|
+
* is heard. Overridable via {@link StrandRevocationEnforcementConfig.backstopMs}.
|
|
149
|
+
*
|
|
150
|
+
* NOTE: the backstop covers two paths the change subscription does not: a
|
|
151
|
+
* machine that was offline when the removal was committed (it normally takes
|
|
152
|
+
* the blocks in by join backfill or read repair on reconnect, which raise the
|
|
153
|
+
* change too), and any change that reaches storage without an event. The case
|
|
154
|
+
* of a node outside a membership block's cohort is covered by
|
|
155
|
+
* {@link REVOCATION_WIDE_STRAND_REFRESH_MS}, not by this.
|
|
156
|
+
*/
|
|
157
|
+
export const DEFAULT_REVOCATION_BACKSTOP_MS = 5 * 60000;
|
|
158
|
+
/**
|
|
159
|
+
* How soon a failed refresh is retried. Also how soon a replicated removal is
|
|
160
|
+
* heard after a first read that failed, since that read is what arms the
|
|
161
|
+
* plugin's replicated-change bridge for the two tables.
|
|
127
162
|
*/
|
|
128
|
-
export const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
};
|
|
163
|
+
export const REVOCATION_REFRESH_RETRY_MS = 30000;
|
|
164
|
+
/**
|
|
165
|
+
* How soon a strand with more distinct bound peer ids than its cluster size is
|
|
166
|
+
* refreshed again after a successful refresh. On such a strand this node may be
|
|
167
|
+
* outside a membership block's cohort, so it takes in no storage event for a
|
|
168
|
+
* removal (`docs/architecture.md` → "Table change subscriptions").
|
|
169
|
+
*/
|
|
170
|
+
export const REVOCATION_WIDE_STRAND_REFRESH_MS = 30000;
|
|
137
171
|
/** Every `Strand.MemberPeer` binding — live-membered and orphaned alike. */
|
|
138
172
|
async function scanBindings(db) {
|
|
139
173
|
const bindings = [];
|
|
@@ -162,17 +196,17 @@ async function scanMemberKeys(db) {
|
|
|
162
196
|
* separate reads with nothing holding them to one snapshot, so a write that
|
|
163
197
|
* lands between them is seen by the second scan and not the first. A member
|
|
164
198
|
* joins as `Member` row then `MemberPeer` row (`MemberExists` forces that
|
|
165
|
-
* order
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* missing from the NEWER key scan).
|
|
172
|
-
* matching the module doc.
|
|
199
|
+
* order; the module doc says why it holds on the network), so reading `Member`
|
|
200
|
+
* first would let a join that commits mid-read produce a binding whose member
|
|
201
|
+
* key is absent from the key set — a brand-new member classified as REVOKED,
|
|
202
|
+
* the one fail-CLOSED outcome this module exists to avoid. Reading bindings
|
|
203
|
+
* first inverts the skew: the new binding is simply not in the older bindings
|
|
204
|
+
* snapshot, so the joiner is unclassified and admitted, and a revocation that
|
|
205
|
+
* lands mid-read is still seen (the key is missing from the NEWER key scan).
|
|
206
|
+
* Every skew window then resolves fail-open, matching the module doc.
|
|
173
207
|
*
|
|
174
|
-
* NOTE: two whole-table scans per refresh (
|
|
175
|
-
* strand). Fine at strand scale (a handful of members and bindings); if
|
|
208
|
+
* NOTE: two whole-table scans per refresh (on each membership change and each
|
|
209
|
+
* backstop, per closed strand). Fine at strand scale (a handful of members and bindings); if
|
|
176
210
|
* membership churn ever makes these tables large, the fix is a reliable
|
|
177
211
|
* filtered read — which needs the networked point-lookup gap closed
|
|
178
212
|
* (`debt-composite-pk-point-lookup-unreliable-untracked`) — not a bigger scan.
|
|
@@ -185,14 +219,13 @@ export async function readStrandRevocationRows(db) {
|
|
|
185
219
|
/**
|
|
186
220
|
* The per-strand revoked-peer deny set and the synchronous predicates over it.
|
|
187
221
|
*
|
|
188
|
-
* Lifecycle
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* never blocked on a refresh.
|
|
222
|
+
* Lifecycle: created in `StrandInstanceManager.buildStrandRuntime` (closed
|
|
223
|
+
* strands only), started when `publishDatabase` hands the strand's database to
|
|
224
|
+
* the app, stopped and dropped in `releaseRuntime` — so quiesce → resume
|
|
225
|
+
* rebuilds it with a fresh snapshot. The snapshot starts EMPTY (admit
|
|
226
|
+
* everything) and repopulates on the first successful read; a revoked peer
|
|
227
|
+
* therefore gets a short admit window per resume at the connection level.
|
|
228
|
+
* Accepted: fail-open posture, and resume is never blocked on a refresh.
|
|
196
229
|
*/
|
|
197
230
|
export class StrandRevocationEnforcer {
|
|
198
231
|
constructor(deps, config) {
|
|
@@ -201,43 +234,38 @@ export class StrandRevocationEnforcer {
|
|
|
201
234
|
this.revokedPeerIds = new Set();
|
|
202
235
|
/** Tail of the refresh chain — what serializes refreshes (no two reads in flight). */
|
|
203
236
|
this.refreshTail = Promise.resolve();
|
|
204
|
-
this.refreshing = false;
|
|
205
237
|
this.started = false;
|
|
206
238
|
this.stopped = false;
|
|
207
239
|
/** Latch for the one-shot self-revocation signal — see {@link StrandRevocationEnforcerDeps.onSelfRevoked}. */
|
|
208
240
|
this.selfRevokedSignaled = false;
|
|
209
|
-
this.
|
|
210
|
-
this.
|
|
241
|
+
this.backstopMs = config?.backstopMs ?? DEFAULT_REVOCATION_BACKSTOP_MS;
|
|
242
|
+
this.autoRefresh = config?.autoRefresh ?? true;
|
|
211
243
|
}
|
|
212
244
|
/** Number of peers currently denied — test/diagnostic surface. */
|
|
213
245
|
get revokedCount() {
|
|
214
246
|
return this.revokedPeerIds.size;
|
|
215
247
|
}
|
|
216
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* Subscribe to membership changes and run one refresh at once (not awaited —
|
|
250
|
+
* bring-up never blocks on it); the module doc's "When the deny set is
|
|
251
|
+
* refreshed" has the full schedule. Call once the strand's database is
|
|
252
|
+
* published. A second call, or one after {@link stop}, is a no-op.
|
|
253
|
+
*/
|
|
217
254
|
start() {
|
|
218
255
|
if (this.started || this.stopped)
|
|
219
256
|
return;
|
|
220
257
|
this.started = true;
|
|
221
|
-
|
|
222
|
-
this.
|
|
223
|
-
|
|
224
|
-
// the interval exists to bound staleness, and a stack of queued reads
|
|
225
|
-
// bounds nothing extra. Explicit refresh() calls still chain (see refresh).
|
|
226
|
-
if (!this.refreshing) {
|
|
227
|
-
void this.refresh();
|
|
228
|
-
}
|
|
229
|
-
}, this.pollIntervalMs);
|
|
230
|
-
log('[%s] revocation enforcer started (poll %dms)', this.deps.label, this.pollIntervalMs);
|
|
258
|
+
this.watch = this.subscribe();
|
|
259
|
+
this.watch.trigger(0);
|
|
260
|
+
log('[%s] revocation enforcer started (backstop %dms)', this.deps.label, this.backstopMs);
|
|
231
261
|
}
|
|
232
|
-
/**
|
|
262
|
+
/** Stop scheduling refreshes; a read already in flight completes but its result is discarded. */
|
|
233
263
|
stop() {
|
|
234
264
|
if (this.stopped)
|
|
235
265
|
return;
|
|
236
266
|
this.stopped = true;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
this.intervalHandle = undefined;
|
|
240
|
-
}
|
|
267
|
+
this.watch?.dispose();
|
|
268
|
+
this.watch = undefined;
|
|
241
269
|
log('[%s] revocation enforcer stopped', this.deps.label);
|
|
242
270
|
}
|
|
243
271
|
/**
|
|
@@ -269,12 +297,41 @@ export class StrandRevocationEnforcer {
|
|
|
269
297
|
authorizeStream(remotePeerId, _protocol) {
|
|
270
298
|
return !this.isRevoked(remotePeerId);
|
|
271
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* The watch every scheduled refresh runs on: the deps' change subscription, or
|
|
302
|
+
* a timer-only one when changes are not watched or the subscription throws.
|
|
303
|
+
*
|
|
304
|
+
* NOTE: a refresh can wake itself once. Its read may start a read-repair sync
|
|
305
|
+
* (a held block read more than Optimystic's `readRepairWindowMs`, 10 s, after
|
|
306
|
+
* its last check), and that sync's landing raises a change. The follow-up
|
|
307
|
+
* refresh runs within the coalescing window plus one read — well inside the
|
|
308
|
+
* read-repair window — so it starts no second sync and the chain stops there.
|
|
309
|
+
* If idle sync counts on a strand do not fall with this subscription, suspect
|
|
310
|
+
* this loop first.
|
|
311
|
+
*/
|
|
312
|
+
subscribe() {
|
|
313
|
+
const onChange = () => this.refresh();
|
|
314
|
+
const options = {
|
|
315
|
+
label: `strand-revocation:${this.deps.label}`,
|
|
316
|
+
backstopMs: this.backstopMs,
|
|
317
|
+
scheduler: this.deps.scheduler,
|
|
318
|
+
};
|
|
319
|
+
if (this.autoRefresh && this.deps.watchChanges) {
|
|
320
|
+
try {
|
|
321
|
+
return this.deps.watchChanges(onChange, options);
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
log('[%s] membership change subscription failed — refreshing on the backstop and retries only: %o', this.deps.label, error);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return scheduleRuns(onChange, options);
|
|
328
|
+
}
|
|
272
329
|
/**
|
|
273
330
|
* One serialized snapshot rebuild; contains every failure (contract: never rejects).
|
|
274
331
|
*
|
|
275
332
|
* NOTE: nothing here carries a deadline — a `readRows` or a `hangUp` that never
|
|
276
|
-
* settles wedges the refresh chain, which freezes the deny set (the
|
|
277
|
-
*
|
|
333
|
+
* settles wedges the refresh chain, which freezes the deny set (the watch runs
|
|
334
|
+
* one refresh at a time, and every later `refresh()` chains behind the stuck one).
|
|
278
335
|
* Both are bounded in practice (Quereus reads settle; libp2p's connection close
|
|
279
336
|
* has its own timeout). If a strand is ever seen with a stale deny set and no
|
|
280
337
|
* refresh log line, wrap both awaits in a timeout rather than hunting further.
|
|
@@ -282,13 +339,13 @@ export class StrandRevocationEnforcer {
|
|
|
282
339
|
async doRefresh() {
|
|
283
340
|
if (this.stopped)
|
|
284
341
|
return;
|
|
285
|
-
this.refreshing = true;
|
|
286
342
|
try {
|
|
287
343
|
const rows = await this.deps.readRows();
|
|
288
344
|
if (this.stopped)
|
|
289
345
|
return;
|
|
290
346
|
this.revokedPeerIds = deriveRevokedPeerIds(rows);
|
|
291
347
|
log('[%s] deny set refreshed: %d revoked peer(s)', this.deps.label, this.revokedPeerIds.size);
|
|
348
|
+
this.followUpWideStrand(rows);
|
|
292
349
|
// Awaited inside the refresh, so an on-demand refresh() resolves only once
|
|
293
350
|
// the cut has been made — that is what makes "revoke, then refresh" an
|
|
294
351
|
// immediate teardown for the caller rather than a scheduled one.
|
|
@@ -296,16 +353,31 @@ export class StrandRevocationEnforcer {
|
|
|
296
353
|
}
|
|
297
354
|
catch (error) {
|
|
298
355
|
log('[%s] deny-set refresh failed — keeping previous snapshot: %o', this.deps.label, error);
|
|
356
|
+
this.scheduleFollowUp(REVOCATION_REFRESH_RETRY_MS, 'refresh failed');
|
|
299
357
|
}
|
|
300
|
-
|
|
301
|
-
|
|
358
|
+
}
|
|
359
|
+
/** Arm one more refresh `delayMs` from now, unless {@link StrandRevocationEnforcementConfig.autoRefresh} is off or the enforcer is not running. */
|
|
360
|
+
scheduleFollowUp(delayMs, reason) {
|
|
361
|
+
if (!this.autoRefresh || !this.watch)
|
|
362
|
+
return;
|
|
363
|
+
log('[%s] %s — refreshing again in %dms', this.deps.label, reason, delayMs);
|
|
364
|
+
this.watch.trigger(delayMs);
|
|
365
|
+
}
|
|
366
|
+
/** Schedule {@link REVOCATION_WIDE_STRAND_REFRESH_MS} when `rows` bind more distinct peers than the cluster holds. */
|
|
367
|
+
followUpWideStrand(rows) {
|
|
368
|
+
const breadth = this.deps.cohortBreadth;
|
|
369
|
+
if (breadth === undefined)
|
|
370
|
+
return;
|
|
371
|
+
const boundPeers = new Set(rows.bindings.map((binding) => binding.peerId)).size;
|
|
372
|
+
if (boundPeers > breadth) {
|
|
373
|
+
this.scheduleFollowUp(REVOCATION_WIDE_STRAND_REFRESH_MS, `${boundPeers} bound peers on a cluster of ${breadth}`);
|
|
302
374
|
}
|
|
303
375
|
}
|
|
304
376
|
/**
|
|
305
377
|
* Close what is already OPEN to the peers of `revoked` — the teardown half of
|
|
306
378
|
* enforcement (module doc: "Removal cuts LIVE sessions too"). Never rejects:
|
|
307
|
-
* every failure is contained, so the refresh
|
|
308
|
-
*
|
|
379
|
+
* every failure is contained, so the refresh chain survives, and a failed
|
|
380
|
+
* `hangUp` schedules a retry.
|
|
309
381
|
*
|
|
310
382
|
* `revoked` is passed IN rather than read off the field, so the sweep judges
|
|
311
383
|
* the exact snapshot its own refresh produced; likewise the connection list is
|
|
@@ -372,7 +444,8 @@ export class StrandRevocationEnforcer {
|
|
|
372
444
|
log('[%s] hung up revoked peer %s', this.deps.label, peer.toString());
|
|
373
445
|
}
|
|
374
446
|
catch (error) {
|
|
375
|
-
log('[%s] hangUp of revoked peer %s failed
|
|
447
|
+
log('[%s] hangUp of revoked peer %s failed: %o', this.deps.label, peer.toString(), error);
|
|
448
|
+
this.scheduleFollowUp(REVOCATION_REFRESH_RETRY_MS, 'hangUp failed');
|
|
376
449
|
}
|
|
377
450
|
}
|
|
378
451
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-revocation-enforcer.js","sourceRoot":"","sources":["../src/strand-revocation-enforcer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"strand-revocation-enforcer.js","sourceRoot":"","sources":["../src/strand-revocation-enforcer.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import type { Libp2p } from '@libp2p/interface';
|
|
|
2
2
|
import type { OpenInvitation, FormStrandResult, StrandFormationDisclosure } from './types.js';
|
|
3
3
|
import { type CadrePeerAddrsSource, type MembershipInviteIssuer, type StrandFormationManagerConfig } from './strand-formation-manager.js';
|
|
4
4
|
import { type FormationResultMessage } from './strand-formation-protocol.js';
|
|
5
|
+
import { type FormationTokenRead } from './formation-token.js';
|
|
5
6
|
/**
|
|
6
7
|
* Interface for validating formation disclosures
|
|
7
8
|
*/
|
|
@@ -14,6 +15,24 @@ export interface DisclosureValidator {
|
|
|
14
15
|
*/
|
|
15
16
|
validateDisclosure(token: string, disclosure: StrandFormationDisclosure): Promise<boolean>;
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A bound invite's host strand as {@link FormationUsageRecorder.resolveStrand} read it, once
|
|
20
|
+
* per session: the membership invitation is issued from it and the consent row recorded
|
|
21
|
+
* against it without reading the `Strand` row again.
|
|
22
|
+
*/
|
|
23
|
+
export interface BoundHostStrand {
|
|
24
|
+
strandId: string;
|
|
25
|
+
/** `'c'` closed: the joiner gets a membership invitation; `'o'` open: nothing to invite into. */
|
|
26
|
+
strandType: 'o' | 'c';
|
|
27
|
+
/**
|
|
28
|
+
* The row's one-off `StampId`, which the consent row names (`FormationUsage.StrandStampId`).
|
|
29
|
+
* Fixed for the life of the row; a row removed and re-seated in between gets a new one, and
|
|
30
|
+
* the `StrandExists` CHECK then refuses the consent row.
|
|
31
|
+
*/
|
|
32
|
+
strandStampId: string;
|
|
33
|
+
/** The closed-strand read-gating secret; null for an open strand. */
|
|
34
|
+
memberPrivateKey: string | null;
|
|
35
|
+
}
|
|
17
36
|
/**
|
|
18
37
|
* Discriminated result of resolving the host strand an invite binds to (responder side):
|
|
19
38
|
*
|
|
@@ -26,20 +45,15 @@ export interface DisclosureValidator {
|
|
|
26
45
|
* non-existent strand, which would fail the deferred `FormationUsage.StrandExists` CHECK at
|
|
27
46
|
* commit and drop the result frame.
|
|
28
47
|
*
|
|
29
|
-
* Replaces the older `{ strandId, memberPrivateKey } | null` shape, which conflated `bound`
|
|
30
|
-
* and `missing` (both produced a non-null result whenever the invite had a `StrandId`).
|
|
31
|
-
*
|
|
32
48
|
* The invite's `ValidationUrl` (the outside approval hook) is deliberately NOT surfaced here:
|
|
33
|
-
* the recorder that performs the write
|
|
34
|
-
* never travels through a caller.
|
|
49
|
+
* the recorder that performs the write takes it from the session's {@link FormationTokenRead},
|
|
50
|
+
* so the choice of which hook to trust never travels through a caller.
|
|
35
51
|
*/
|
|
36
52
|
export type ResolvedHostStrand = {
|
|
37
53
|
kind: 'unbound';
|
|
38
|
-
} | {
|
|
54
|
+
} | ({
|
|
39
55
|
kind: 'bound';
|
|
40
|
-
|
|
41
|
-
memberPrivateKey: string | null;
|
|
42
|
-
} | {
|
|
56
|
+
} & BoundHostStrand) | {
|
|
43
57
|
kind: 'missing';
|
|
44
58
|
strandId: string;
|
|
45
59
|
};
|
|
@@ -64,47 +78,44 @@ export interface AuthorizedFormationUsage {
|
|
|
64
78
|
record(): Promise<void>;
|
|
65
79
|
}
|
|
66
80
|
/**
|
|
67
|
-
*
|
|
81
|
+
* The responder's view of the formation-invite tables. A session reads its token once
|
|
82
|
+
* ({@link readToken}) and hands that read to every later step, so no step reads the invite again.
|
|
68
83
|
*/
|
|
69
84
|
export interface FormationUsageRecorder {
|
|
70
85
|
/**
|
|
71
|
-
* Record that a formation invite was used
|
|
72
|
-
* is the same as
|
|
86
|
+
* Record that a formation invite was used, reading the invite for itself. Where
|
|
87
|
+
* {@link authorizeUsage} is implemented, this is the same as reading the token, authorizing,
|
|
88
|
+
* and calling `record()` straight away.
|
|
73
89
|
*/
|
|
74
90
|
recordUsage(params: FormationUsageParams): Promise<void>;
|
|
75
91
|
/**
|
|
76
92
|
* Everything {@link recordUsage} does short of the write: obtain the outside approval (when
|
|
77
|
-
* the invite demands one) and
|
|
78
|
-
* writes the consent row with exactly the fields
|
|
79
|
-
* before it writes anything into the host strand,
|
|
93
|
+
* `read` says the invite demands one) and, after an obtained approval, re-check the seat
|
|
94
|
+
* budget, returning a handle whose `record()` writes the consent row with exactly the fields
|
|
95
|
+
* that were approved. Lets the manager ask before it writes anything into the host strand,
|
|
96
|
+
* and write consent only after. `host` is the resolved host strand the consent row names.
|
|
80
97
|
* Optional: a recorder without it is treated as having nothing to ask up front.
|
|
81
98
|
*/
|
|
82
|
-
authorizeUsage?(params: FormationUsageParams): Promise<AuthorizedFormationUsage>;
|
|
99
|
+
authorizeUsage?(params: FormationUsageParams, read: FormationTokenRead, host: BoundHostStrand): Promise<AuthorizedFormationUsage>;
|
|
83
100
|
/**
|
|
84
|
-
*
|
|
101
|
+
* Read one token as a formation session acts on it: the invite row (null when there is none
|
|
102
|
+
* here — it may not have replicated yet) and, for a metered unexpired invite, its recorded
|
|
103
|
+
* use count. One read per session; `classifyTokenRead` turns it into the token check.
|
|
85
104
|
*/
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Check if a token is valid and not expired. An invalid answer says why: `'unknown'` (no
|
|
89
|
-
* invitation row here — it may not have replicated yet) or `'expired'`. An answer without
|
|
90
|
-
* a reason is treated as `'unknown'`, the retryable one.
|
|
91
|
-
*/
|
|
92
|
-
isTokenValid(token: string): Promise<{
|
|
93
|
-
valid: boolean;
|
|
94
|
-
reason?: 'unknown' | 'expired';
|
|
95
|
-
invitation?: OpenInvitation;
|
|
96
|
-
}>;
|
|
105
|
+
readToken(token: string): Promise<FormationTokenRead>;
|
|
97
106
|
/**
|
|
98
|
-
* Resolve the host strand
|
|
99
|
-
* missing (see {@link ResolvedHostStrand}).
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
107
|
+
* Resolve the host strand the read invite binds to, classifying it as unbound / bound /
|
|
108
|
+
* missing (see {@link ResolvedHostStrand}). Reads the `Strand` row, never the invite.
|
|
109
|
+
* Optional: a recorder that does not bind strands omits it, and
|
|
110
|
+
* {@link StrandFormationManager} treats every invite as `unbound` and falls back to its
|
|
111
|
+
* provisioner. Keeping resolution behind this interface keeps the manager DB-agnostic and
|
|
112
|
+
* unit-testable with an in-memory fake.
|
|
103
113
|
*/
|
|
104
|
-
resolveStrand?(
|
|
114
|
+
resolveStrand?(read: FormationTokenRead): Promise<ResolvedHostStrand>;
|
|
105
115
|
/**
|
|
106
116
|
* Provision a NEW strand for an UNBOUND invite and record consent against it
|
|
107
117
|
* ATOMICALLY (single `FormationUsage` row → single-use enforced on the next redemption).
|
|
118
|
+
* Takes the invite's approval hook and seat budget from `read`.
|
|
108
119
|
* Optional: a recorder that only supports provision-then-record omits it, and the
|
|
109
120
|
* manager falls back to its {@link StrandProvisioner}. Mirrors the create-strand-by-consent
|
|
110
121
|
* path the schema's `Strand.AuthorizedInsert` `FormationUsage` branch authorizes. Returns the
|
|
@@ -123,7 +134,7 @@ export interface FormationUsageRecorder {
|
|
|
123
134
|
disclosure: string;
|
|
124
135
|
/** Aborted when the caller has given up; observed BEFORE any write so the invite stays unspent. */
|
|
125
136
|
signal?: AbortSignal;
|
|
126
|
-
}): Promise<{
|
|
137
|
+
}, read: FormationTokenRead): Promise<{
|
|
127
138
|
strandId: string;
|
|
128
139
|
memberPrivateKey: string | null;
|
|
129
140
|
}>;
|
|
@@ -131,8 +142,8 @@ export interface FormationUsageRecorder {
|
|
|
131
142
|
* Is ANY invitation this recorder knows about unexpired and not fully
|
|
132
143
|
* consumed? Optional: a recorder that cannot enumerate invitations omits it,
|
|
133
144
|
* and only locally-minted invitations hold the connection gate's formation
|
|
134
|
-
* exemption open. Distinct from the per-token {@link
|
|
135
|
-
*
|
|
145
|
+
* exemption open. Distinct from the per-token {@link readToken} — the
|
|
146
|
+
* connection gate has no token to ask about.
|
|
136
147
|
*/
|
|
137
148
|
hasOutstandingInvitation?(): Promise<boolean>;
|
|
138
149
|
}
|
|
@@ -297,8 +308,8 @@ export declare class StrandSolicitationService {
|
|
|
297
308
|
* in-memory and dies with the process. Expired entries are dropped. With
|
|
298
309
|
* no {@link FormationUsageRecorder} configured there is no consumption
|
|
299
310
|
* oracle, so an unexpired minted token counts as outstanding by
|
|
300
|
-
* construction; with one, a token it
|
|
301
|
-
* is permanent, so the registry never reconsiders it).
|
|
311
|
+
* construction; with one, a token whose row it reads as expired or used up
|
|
312
|
+
* is deleted (both are permanent, so the registry never reconsiders it).
|
|
302
313
|
* 2. The recorder's optional {@link FormationUsageRecorder.hasOutstandingInvitation},
|
|
303
314
|
* which survives restarts and sees invitations replicated in from siblings.
|
|
304
315
|
*
|
|
@@ -10,6 +10,7 @@ import { ed25519KeyPairFromLibp2p } from './ed25519-key.js';
|
|
|
10
10
|
import { mintPlaceholderStrandId } from './strand-id.js';
|
|
11
11
|
import { StrandFormationManager } from './strand-formation-manager.js';
|
|
12
12
|
import { isValidResponderCreatesResult } from './strand-formation-protocol.js';
|
|
13
|
+
import { classifyTokenRead } from './formation-token.js';
|
|
13
14
|
const log = debug('sereus:cadre:solicitation');
|
|
14
15
|
/**
|
|
15
16
|
* Built-in structural {@link FormationResponseValidator}.
|
|
@@ -214,8 +215,8 @@ export class StrandSolicitationService {
|
|
|
214
215
|
* in-memory and dies with the process. Expired entries are dropped. With
|
|
215
216
|
* no {@link FormationUsageRecorder} configured there is no consumption
|
|
216
217
|
* oracle, so an unexpired minted token counts as outstanding by
|
|
217
|
-
* construction; with one, a token it
|
|
218
|
-
* is permanent, so the registry never reconsiders it).
|
|
218
|
+
* construction; with one, a token whose row it reads as expired or used up
|
|
219
|
+
* is deleted (both are permanent, so the registry never reconsiders it).
|
|
219
220
|
* 2. The recorder's optional {@link FormationUsageRecorder.hasOutstandingInvitation},
|
|
220
221
|
* which survives restarts and sees invitations replicated in from siblings.
|
|
221
222
|
*
|
|
@@ -232,7 +233,13 @@ export class StrandSolicitationService {
|
|
|
232
233
|
if (!this.formationUsageRecorder) {
|
|
233
234
|
return true;
|
|
234
235
|
}
|
|
235
|
-
|
|
236
|
+
// NOTE: a read of its own, not shared with a formation session's: this runs while a
|
|
237
|
+
// stranger's connection is being admitted, before any stream or token exists, under the
|
|
238
|
+
// gate's fail-open deadline, and the session that follows reads on a different call.
|
|
239
|
+
// An unknown token stays outstanding (its row may not be published yet); an expired or
|
|
240
|
+
// used-up row is dropped, the same `token-spent` the formation handler would answer.
|
|
241
|
+
const check = classifyTokenRead(await this.formationUsageRecorder.readToken(token), now);
|
|
242
|
+
if (check.valid || check.code === 'token-unknown') {
|
|
236
243
|
return true;
|
|
237
244
|
}
|
|
238
245
|
this.mintedInvitations.delete(token);
|
|
@@ -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,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EACL,sBAAsB,EAIvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,6BAA6B,EAE9B,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"strand-solicitation.js","sourceRoot":"","sources":["../src/strand-solicitation.ts"],"names":[],"mappings":"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"}
|