@serfab/cadre-core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -253
- package/dist/bootstrap-peer-store-file.d.ts +13 -0
- package/dist/bootstrap-peer-store-file.js +42 -0
- package/dist/bootstrap-peer-store-file.js.map +1 -0
- package/dist/bootstrap-peer-store.d.ts +73 -0
- package/dist/bootstrap-peer-store.js +128 -0
- package/dist/bootstrap-peer-store.js.map +1 -0
- package/dist/cadre-node.d.ts +1121 -81
- package/dist/cadre-node.js +2276 -262
- package/dist/cadre-node.js.map +1 -1
- package/dist/control-authorization.d.ts +93 -0
- package/dist/control-authorization.js +57 -0
- package/dist/control-authorization.js.map +1 -0
- package/dist/control-cohort.d.ts +26 -0
- package/dist/control-cohort.js +26 -0
- package/dist/control-cohort.js.map +1 -1
- package/dist/control-database.d.ts +959 -54
- package/dist/control-database.js +1421 -148
- package/dist/control-database.js.map +1 -1
- package/dist/control-formation-recorder.d.ts +90 -10
- package/dist/control-formation-recorder.js +130 -14
- package/dist/control-formation-recorder.js.map +1 -1
- package/dist/control-schema.d.ts +1 -1
- package/dist/control-schema.js +651 -72
- package/dist/control-schema.js.map +1 -1
- package/dist/control-stream.d.ts +38 -4
- package/dist/control-stream.js +70 -4
- package/dist/control-stream.js.map +1 -1
- package/dist/control-write-retry.d.ts +161 -0
- package/dist/control-write-retry.js +412 -0
- package/dist/control-write-retry.js.map +1 -0
- package/dist/delegate-admission.d.ts +134 -0
- package/dist/delegate-admission.js +244 -0
- package/dist/delegate-admission.js.map +1 -0
- package/dist/device-token.d.ts +11 -11
- package/dist/device-token.js +14 -13
- package/dist/device-token.js.map +1 -1
- package/dist/ed25519-key.d.ts +26 -0
- package/dist/ed25519-key.js +56 -1
- package/dist/ed25519-key.js.map +1 -1
- package/dist/file-durable-slot.d.ts +20 -0
- package/dist/file-durable-slot.js +53 -0
- package/dist/file-durable-slot.js.map +1 -0
- package/dist/formation-approval.d.ts +157 -0
- package/dist/formation-approval.js +375 -0
- package/dist/formation-approval.js.map +1 -0
- package/dist/fs-atomic.d.ts +38 -0
- package/dist/fs-atomic.js +97 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/identity-key.d.ts +49 -0
- package/dist/identity-key.js +81 -0
- package/dist/identity-key.js.map +1 -0
- package/dist/index.d.ts +17 -7
- package/dist/index.js +40 -6
- package/dist/index.js.map +1 -1
- package/dist/key-store-file.d.ts +2 -24
- package/dist/key-store-file.js +10 -77
- package/dist/key-store-file.js.map +1 -1
- package/dist/membership-connection-gater.d.ts +146 -0
- package/dist/membership-connection-gater.js +180 -0
- package/dist/membership-connection-gater.js.map +1 -0
- package/dist/node-local-snapshot.d.ts +109 -0
- package/dist/node-local-snapshot.js +182 -0
- package/dist/node-local-snapshot.js.map +1 -0
- package/dist/peer-addr-book.d.ts +92 -0
- package/dist/peer-addr-book.js +220 -0
- package/dist/peer-addr-book.js.map +1 -0
- package/dist/peer-authorization.d.ts +135 -18
- package/dist/peer-authorization.js +152 -21
- package/dist/peer-authorization.js.map +1 -1
- package/dist/peer-record.d.ts +21 -8
- package/dist/peer-record.js +26 -10
- package/dist/peer-record.js.map +1 -1
- package/dist/relay-addrs.d.ts +45 -0
- package/dist/relay-addrs.js +103 -0
- package/dist/relay-addrs.js.map +1 -0
- package/dist/relay-reservation.d.ts +266 -0
- package/dist/relay-reservation.js +613 -0
- package/dist/relay-reservation.js.map +1 -0
- package/dist/seed-bootstrap.d.ts +228 -56
- package/dist/seed-bootstrap.js +340 -198
- package/dist/seed-bootstrap.js.map +1 -1
- package/dist/seed-trust-policy.d.ts +47 -21
- package/dist/seed-trust-policy.js +24 -15
- package/dist/seed-trust-policy.js.map +1 -1
- package/dist/strand-addr-protocol.d.ts +42 -13
- package/dist/strand-addr-protocol.js +58 -52
- package/dist/strand-addr-protocol.js.map +1 -1
- package/dist/strand-backfill.d.ts +109 -0
- package/dist/strand-backfill.js +279 -0
- package/dist/strand-backfill.js.map +1 -0
- package/dist/strand-cohort.d.ts +4 -37
- package/dist/strand-cohort.js +4 -15
- package/dist/strand-cohort.js.map +1 -1
- package/dist/strand-database.d.ts +11 -16
- package/dist/strand-database.js +19 -7
- package/dist/strand-database.js.map +1 -1
- package/dist/strand-formation-manager.d.ts +36 -7
- package/dist/strand-formation-manager.js +115 -15
- package/dist/strand-formation-manager.js.map +1 -1
- package/dist/strand-formation-protocol.d.ts +103 -4
- package/dist/strand-formation-protocol.js +232 -4
- package/dist/strand-formation-protocol.js.map +1 -1
- package/dist/strand-instance-manager.d.ts +88 -19
- package/dist/strand-instance-manager.js +178 -34
- package/dist/strand-instance-manager.js.map +1 -1
- package/dist/strand-member-registry.d.ts +5 -5
- package/dist/strand-member-registry.js +18 -10
- package/dist/strand-member-registry.js.map +1 -1
- package/dist/strand-membership-writer.d.ts +484 -88
- package/dist/strand-membership-writer.js +751 -128
- package/dist/strand-membership-writer.js.map +1 -1
- package/dist/strand-solicitation.d.ts +99 -33
- package/dist/strand-solicitation.js +100 -52
- package/dist/strand-solicitation.js.map +1 -1
- package/dist/strand-transport-key.d.ts +40 -0
- package/dist/strand-transport-key.js +56 -0
- package/dist/strand-transport-key.js.map +1 -0
- package/dist/strand-wake-protocol.d.ts +9 -7
- package/dist/strand-wake-protocol.js +22 -46
- package/dist/strand-wake-protocol.js.map +1 -1
- package/dist/strand-watcher.d.ts +17 -2
- package/dist/strand-watcher.js +47 -3
- package/dist/strand-watcher.js.map +1 -1
- package/dist/trusted-owner-store-file.d.ts +12 -0
- package/dist/trusted-owner-store-file.js +39 -0
- package/dist/trusted-owner-store-file.js.map +1 -0
- package/dist/trusted-owner-store.d.ts +64 -0
- package/dist/trusted-owner-store.js +126 -0
- package/dist/trusted-owner-store.js.map +1 -0
- package/dist/types.d.ts +307 -44
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -1
- package/package.json +20 -8
- package/src/arachnode-stub.ts +159 -159
- package/src/bootstrap-peer-store-file.ts +44 -0
- package/src/bootstrap-peer-store.ts +172 -0
- package/src/cadre-node.ts +5158 -2981
- package/src/canonical-json.ts +30 -30
- package/src/control-authorization.ts +114 -0
- package/src/control-cohort.ts +130 -103
- package/src/control-database.ts +2406 -948
- package/src/control-formation-recorder.ts +305 -151
- package/src/control-schema.ts +651 -72
- package/src/control-stream.ts +205 -128
- package/src/control-write-retry.ts +483 -0
- package/src/delegate-admission.ts +302 -0
- package/src/device-token.ts +94 -89
- package/src/diagnostics/connection-path.ts +277 -277
- package/src/diagnostics/webrtc-turn-tracker.ts +222 -222
- package/src/ed25519-key.ts +66 -1
- package/src/enrollment.ts +199 -199
- package/src/file-durable-slot.ts +61 -0
- package/src/formation-approval.ts +604 -0
- package/src/fs-atomic.ts +107 -0
- package/src/hibernation-manager.ts +437 -437
- package/src/identity-key.ts +109 -0
- package/src/index.ts +137 -5
- package/src/key-store-file.ts +132 -203
- package/src/key-store.ts +99 -99
- package/src/membership-connection-gater.ts +204 -0
- package/src/node-local-snapshot.ts +260 -0
- package/src/peer-addr-book.ts +250 -0
- package/src/peer-authorization.ts +259 -81
- package/src/peer-record.ts +164 -146
- package/src/push-credentials.ts +84 -84
- package/src/push-node.ts +64 -64
- package/src/push-notifier-apns.ts +247 -247
- package/src/push-notifier-fcm.ts +220 -220
- package/src/push-notifier-shared.ts +28 -28
- package/src/push-notifier.ts +52 -52
- package/src/relay-addrs.ts +111 -0
- package/src/relay-reservation.ts +823 -0
- package/src/seed-bootstrap.ts +409 -209
- package/src/seed-trust-policy.ts +140 -107
- package/src/strand-addr-protocol.ts +441 -410
- package/src/strand-backfill.ts +366 -0
- package/src/strand-cohort.ts +38 -77
- package/src/strand-database.ts +202 -204
- package/src/strand-formation-manager.ts +491 -368
- package/src/strand-formation-protocol.ts +692 -391
- package/src/strand-instance-manager.ts +201 -46
- package/src/strand-member-key.ts +39 -39
- package/src/strand-member-registry.ts +182 -177
- package/src/strand-membership-writer.ts +938 -146
- package/src/strand-solicitation.ts +472 -407
- package/src/strand-transport-key.ts +58 -0
- package/src/strand-wake-payload.ts +32 -32
- package/src/strand-wake-protocol.ts +314 -331
- package/src/strand-watcher.ts +314 -251
- package/src/trusted-owner-store-file.ts +41 -0
- package/src/trusted-owner-store.ts +171 -0
- package/src/types.ts +1309 -1032
|
@@ -14,17 +14,19 @@
|
|
|
14
14
|
* stream (like seed delivery), so each side reads to EOF (under a read timeout)
|
|
15
15
|
* and decodes one frame via the shared {@link decodeLengthPrefixedFrame} guard.
|
|
16
16
|
*
|
|
17
|
-
* **Authorization (v1):**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
17
|
+
* **Authorization (v1):** a wake is low-risk — it only causes the receiver to
|
|
18
|
+
* spend resources coming online for a strand it already participates in — so the
|
|
19
|
+
* receiver carries no per-request signature and instead defers entirely to the
|
|
20
|
+
* injected `isMember` predicate. `CadreNode` injects its AUTHORIZED-membership
|
|
21
|
+
* predicate there (`isAuthorizedMember`: the sender's `CadrePeer` row must carry
|
|
22
|
+
* a voucher that verifies against an owner key in the receiver's node-local
|
|
23
|
+
* trusted-owner anchor), so a peer that merely published rows into the replicated
|
|
24
|
+
* control DB is refused. This module stays agnostic: it enforces whatever
|
|
25
|
+
* predicate it is given.
|
|
24
26
|
*/
|
|
25
27
|
import debug from 'debug';
|
|
26
28
|
import { decodeLengthPrefixedFrame } from './seed-bootstrap.js';
|
|
27
|
-
import { writeFrame,
|
|
29
|
+
import { writeFrame, withDeadline, exchangeFrame, readStreamToEnd } from './control-stream.js';
|
|
28
30
|
const log = debug('sereus:cadre:strand-wake');
|
|
29
31
|
/** Protocol id for control-network push-wake (parallel to `/sereus/seed/1.0.0`). */
|
|
30
32
|
export const WAKE_PROTOCOL = '/sereus/strand-wake/1.0.0';
|
|
@@ -159,8 +161,8 @@ export class StrandWakeService {
|
|
|
159
161
|
* - Already-live strand → no-op, `accepted` with current status.
|
|
160
162
|
*/
|
|
161
163
|
async processWakeRequest(request, remotePeerId) {
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
+
// The injected membership predicate is the whole v1 authorization (CadreNode
|
|
165
|
+
// supplies the voucher-anchored one); only a peer it admits may ask us to wake.
|
|
164
166
|
if (!(await this.options.isMember(remotePeerId))) {
|
|
165
167
|
log('Rejecting wake from non-member %s', remotePeerId);
|
|
166
168
|
return { accepted: false, reason: 'Sender is not a cadre member' };
|
|
@@ -197,12 +199,10 @@ export async function dialWake(node, addrs, request, options = {}) {
|
|
|
197
199
|
const timeoutMs = options.timeoutMs ?? DEFAULT_WAKE_TIMEOUT_MS;
|
|
198
200
|
let lastError = null;
|
|
199
201
|
for (const addr of addrs) {
|
|
200
|
-
// One controller per attempt: on timeout, `withTimeout`'s `onTimeout` aborts
|
|
201
|
-
// it, which aborts the in-flight dialProtocol (via the dial `signal`) and the
|
|
202
|
-
// live stream — so neither the connect nor the unbounded ack-read leaks.
|
|
203
|
-
const controller = new AbortController();
|
|
204
202
|
try {
|
|
205
|
-
|
|
203
|
+
// One deadline per attempt: its signal aborts the in-flight dialProtocol and
|
|
204
|
+
// resets the live stream, so neither the connect nor the ack-read leaks.
|
|
205
|
+
return await withDeadline(timeoutMs, `Wake dial ${addr.toString()}`, (signal) => sendWake(node, addr, protocolId, request, timeoutMs, signal));
|
|
206
206
|
}
|
|
207
207
|
catch (err) {
|
|
208
208
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -214,12 +214,10 @@ export async function dialWake(node, addrs, request, options = {}) {
|
|
|
214
214
|
/**
|
|
215
215
|
* Open one stream, send the request, half-close, and read the ack.
|
|
216
216
|
*
|
|
217
|
-
* `signal` is the per-attempt
|
|
218
|
-
* `dialProtocol` so a timeout during connect aborts the dial, and
|
|
219
|
-
*
|
|
220
|
-
* unbounded ack-read.
|
|
221
|
-
* case where an abort does not propagate (e.g. a stream double whose `abort()` is
|
|
222
|
-
* a no-op).
|
|
217
|
+
* `signal` is the per-attempt deadline from {@link dialWake}: it goes to
|
|
218
|
+
* `dialProtocol` so a timeout during connect aborts the dial, and into
|
|
219
|
+
* {@link exchangeFrame} so a timeout after the stream is open resets it —
|
|
220
|
+
* releasing the otherwise unbounded ack-read.
|
|
223
221
|
*/
|
|
224
222
|
async function sendWake(node, addr, protocolId, request, timeoutMs, signal) {
|
|
225
223
|
// `runOnLimitedConnection: true`: the target may be reachable only over a
|
|
@@ -228,30 +226,8 @@ async function sendWake(node, addr, protocolId, request, timeoutMs, signal) {
|
|
|
228
226
|
// relay's data/duration cap, so opening it on the limited connection is safe
|
|
229
227
|
// and is the whole point of dialing the relay address.
|
|
230
228
|
const rawStream = await node.dialProtocol(addr, protocolId, { runOnLimitedConnection: true, signal });
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// If the timeout already fired during connect, release the freshly-opened stream now.
|
|
235
|
-
if (signal.aborted) {
|
|
236
|
-
onAbort();
|
|
237
|
-
throw abortErr;
|
|
238
|
-
}
|
|
239
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
240
|
-
try {
|
|
241
|
-
writeFrame(stream, request);
|
|
242
|
-
// close() half-closes the write end (EOF) while the read end stays open for
|
|
243
|
-
// the ack — the same libp2p 3.x pattern as seed delivery.
|
|
244
|
-
await stream.close();
|
|
245
|
-
const ack = await readFrame(stream, timeoutMs);
|
|
246
|
-
log('Wake ack from %s: accepted=%s status=%s', addr.toString(), ack.accepted, ack.status ?? '-');
|
|
247
|
-
return ack;
|
|
248
|
-
}
|
|
249
|
-
catch (err) {
|
|
250
|
-
stream.abort(err instanceof Error ? err : new Error(String(err)));
|
|
251
|
-
throw err;
|
|
252
|
-
}
|
|
253
|
-
finally {
|
|
254
|
-
signal.removeEventListener('abort', onAbort);
|
|
255
|
-
}
|
|
229
|
+
const ack = await exchangeFrame(rawStream, signal, request, (stream) => readFrame(stream, timeoutMs), 'Wake dial aborted by timeout');
|
|
230
|
+
log('Wake ack from %s: accepted=%s status=%s', addr.toString(), ack.accepted, ack.status ?? '-');
|
|
231
|
+
return ack;
|
|
256
232
|
}
|
|
257
233
|
//# sourceMappingURL=strand-wake-protocol.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-wake-protocol.js","sourceRoot":"","sources":["../src/strand-wake-protocol.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"strand-wake-protocol.js","sourceRoot":"","sources":["../src/strand-wake-protocol.ts"],"names":[],"mappings":"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"}
|
package/dist/strand-watcher.d.ts
CHANGED
|
@@ -39,13 +39,23 @@ export declare class StrandWatcher {
|
|
|
39
39
|
private readonly callbacks;
|
|
40
40
|
private readonly queryable;
|
|
41
41
|
private readonly sAppIdLookup?;
|
|
42
|
+
/**
|
|
43
|
+
* Wall-clock source for the retry backoff. Read fresh at each use rather than
|
|
44
|
+
* snapshotted per poll: a poll awaits `onStrandAdded`, and a launch that fails
|
|
45
|
+
* slowly (e.g. a dial timeout) would otherwise be scheduled off the clock as it
|
|
46
|
+
* read *before* the attempt, putting `nextAttemptAt` in the past. Injectable so
|
|
47
|
+
* tests can advance time deterministically without fake timers.
|
|
48
|
+
*/
|
|
49
|
+
private readonly now;
|
|
42
50
|
private knownStrands;
|
|
43
51
|
/** Ids admitted under a `defer` decision; re-evaluated each poll until they resolve. */
|
|
44
52
|
private provisional;
|
|
53
|
+
/** Ids whose last launch attempt threw, with the backoff gating their retry. */
|
|
54
|
+
private failureStates;
|
|
45
55
|
private pollTimer;
|
|
46
56
|
private initialPollTimer;
|
|
47
57
|
private running;
|
|
48
|
-
constructor(queryable: StrandQueryable, callbacks: StrandWatcherCallbacks, filter?: StrandFilter, pollInterval?: number, sAppIdLookup?: SAppIdLookup);
|
|
58
|
+
constructor(queryable: StrandQueryable, callbacks: StrandWatcherCallbacks, filter?: StrandFilter, pollInterval?: number, sAppIdLookup?: SAppIdLookup, now?: () => number);
|
|
49
59
|
/**
|
|
50
60
|
* Evaluate a strand against the current filter, distinguishing a not-yet-known
|
|
51
61
|
* sAppId (`defer`) from a known non-match (`reject`). A `defer` admission is
|
|
@@ -53,7 +63,12 @@ export declare class StrandWatcher {
|
|
|
53
63
|
*/
|
|
54
64
|
private evaluateFilter;
|
|
55
65
|
/**
|
|
56
|
-
*
|
|
66
|
+
* Record a failed launch attempt and schedule when the strand may be retried:
|
|
67
|
+
* `pollInterval * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
|
|
68
|
+
*/
|
|
69
|
+
private recordFailure;
|
|
70
|
+
/**
|
|
71
|
+
* Poll for strand changes.
|
|
57
72
|
*/
|
|
58
73
|
private poll;
|
|
59
74
|
/**
|
package/dist/strand-watcher.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
2
|
const log = debug('sereus:cadre:strand-watcher');
|
|
3
|
+
/**
|
|
4
|
+
* Ceiling on the retry delay for a strand whose launch keeps failing. Attempts
|
|
5
|
+
* are never abandoned — the failure this backoff exists for (a transient network
|
|
6
|
+
* or storage fault) can last a long time — but a permanently-unlaunchable strand
|
|
7
|
+
* must not re-attempt on every poll and storm the host app with `strand:error`.
|
|
8
|
+
*/
|
|
9
|
+
const MAX_RETRY_BACKOFF_MS = 5 * 60 * 1000;
|
|
3
10
|
/**
|
|
4
11
|
* Watches the control network's Strand table for changes and triggers
|
|
5
12
|
* strand instance start/stop via callbacks.
|
|
@@ -7,10 +14,12 @@ const log = debug('sereus:cadre:strand-watcher');
|
|
|
7
14
|
* Uses polling until Optimystic supports reactive subscriptions.
|
|
8
15
|
*/
|
|
9
16
|
export class StrandWatcher {
|
|
10
|
-
constructor(queryable, callbacks, filter = { mode: 'all' }, pollInterval = 5000, sAppIdLookup) {
|
|
17
|
+
constructor(queryable, callbacks, filter = { mode: 'all' }, pollInterval = 5000, sAppIdLookup, now = Date.now) {
|
|
11
18
|
this.knownStrands = new Map();
|
|
12
19
|
/** Ids admitted under a `defer` decision; re-evaluated each poll until they resolve. */
|
|
13
20
|
this.provisional = new Set();
|
|
21
|
+
/** Ids whose last launch attempt threw, with the backoff gating their retry. */
|
|
22
|
+
this.failureStates = new Map();
|
|
14
23
|
this.pollTimer = null;
|
|
15
24
|
this.initialPollTimer = null;
|
|
16
25
|
this.running = false;
|
|
@@ -19,6 +28,7 @@ export class StrandWatcher {
|
|
|
19
28
|
this.filter = filter;
|
|
20
29
|
this.pollInterval = pollInterval;
|
|
21
30
|
this.sAppIdLookup = sAppIdLookup;
|
|
31
|
+
this.now = now;
|
|
22
32
|
log('StrandWatcher created with filter: %o, interval: %dms', filter, pollInterval);
|
|
23
33
|
}
|
|
24
34
|
/**
|
|
@@ -54,7 +64,17 @@ export class StrandWatcher {
|
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
/**
|
|
57
|
-
*
|
|
67
|
+
* Record a failed launch attempt and schedule when the strand may be retried:
|
|
68
|
+
* `pollInterval * 2^(failures-1)`, capped at {@link MAX_RETRY_BACKOFF_MS}.
|
|
69
|
+
*/
|
|
70
|
+
recordFailure(strandId) {
|
|
71
|
+
const failures = (this.failureStates.get(strandId)?.failures ?? 0) + 1;
|
|
72
|
+
const delay = Math.min(this.pollInterval * 2 ** (failures - 1), MAX_RETRY_BACKOFF_MS);
|
|
73
|
+
this.failureStates.set(strandId, { failures, nextAttemptAt: this.now() + delay });
|
|
74
|
+
log('Strand %s launch failed (%d consecutive) - next attempt in %dms', strandId, failures, delay);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Poll for strand changes.
|
|
58
78
|
*/
|
|
59
79
|
async poll() {
|
|
60
80
|
if (!this.running)
|
|
@@ -66,10 +86,17 @@ export class StrandWatcher {
|
|
|
66
86
|
for (const strand of currentStrands) {
|
|
67
87
|
if (this.knownStrands.has(strand.Id))
|
|
68
88
|
continue;
|
|
89
|
+
const failure = this.failureStates.get(strand.Id);
|
|
90
|
+
// Clock read per candidate, not once per poll: an earlier strand's launch
|
|
91
|
+
// may have taken a long time, and this one's gate must reflect that.
|
|
92
|
+
if (failure && this.now() < failure.nextAttemptAt)
|
|
93
|
+
continue; // backing off after a failed launch
|
|
69
94
|
const decision = this.evaluateFilter(strand);
|
|
70
95
|
if (decision === 'reject')
|
|
71
96
|
continue;
|
|
72
97
|
log('Strand added: %s', strand.Id);
|
|
98
|
+
// Set BEFORE the await: this doubles as the in-flight guard that stops the
|
|
99
|
+
// interval timer from starting a second concurrent launch for the same strand.
|
|
73
100
|
this.knownStrands.set(strand.Id, strand);
|
|
74
101
|
if (decision === 'defer') {
|
|
75
102
|
// Provisional admission: sAppId unknown, re-check on later polls.
|
|
@@ -77,9 +104,16 @@ export class StrandWatcher {
|
|
|
77
104
|
}
|
|
78
105
|
try {
|
|
79
106
|
await this.callbacks.onStrandAdded(strand);
|
|
107
|
+
this.failureStates.delete(strand.Id);
|
|
80
108
|
}
|
|
81
109
|
catch (error) {
|
|
82
110
|
log('Error handling strand add for %s: %o', strand.Id, error);
|
|
111
|
+
// A failed launch leaves nothing running (StrandInstanceManager drops the
|
|
112
|
+
// record), so forget the strand and let a later poll retry it — gated by
|
|
113
|
+
// the backoff recorded here.
|
|
114
|
+
this.knownStrands.delete(strand.Id);
|
|
115
|
+
this.provisional.delete(strand.Id);
|
|
116
|
+
this.recordFailure(strand.Id);
|
|
83
117
|
}
|
|
84
118
|
}
|
|
85
119
|
// Re-evaluate provisional admissions whose sAppId may now be resolvable.
|
|
@@ -107,7 +141,9 @@ export class StrandWatcher {
|
|
|
107
141
|
}
|
|
108
142
|
// decision === 'defer': still unknown, leave provisional for next poll.
|
|
109
143
|
}
|
|
110
|
-
// Find removed strands
|
|
144
|
+
// Find removed strands. A strand whose launch failed is no longer in
|
|
145
|
+
// knownStrands, so it correctly never fires onStrandRemoved — nothing was
|
|
146
|
+
// ever started for it.
|
|
111
147
|
for (const [strandId] of this.knownStrands) {
|
|
112
148
|
if (!currentMap.has(strandId)) {
|
|
113
149
|
log('Strand removed: %s', strandId);
|
|
@@ -121,6 +157,13 @@ export class StrandWatcher {
|
|
|
121
157
|
}
|
|
122
158
|
}
|
|
123
159
|
}
|
|
160
|
+
// Drop backoff state for strands whose control-network row is gone; a row
|
|
161
|
+
// that reappears is a fresh strand and gets a fresh first attempt.
|
|
162
|
+
for (const strandId of [...this.failureStates.keys()]) {
|
|
163
|
+
if (!currentMap.has(strandId)) {
|
|
164
|
+
this.failureStates.delete(strandId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
124
167
|
}
|
|
125
168
|
catch (error) {
|
|
126
169
|
log('Error polling strands: %o', error);
|
|
@@ -167,6 +210,7 @@ export class StrandWatcher {
|
|
|
167
210
|
}
|
|
168
211
|
this.knownStrands.clear();
|
|
169
212
|
this.provisional.clear();
|
|
213
|
+
this.failureStates.clear();
|
|
170
214
|
log('StrandWatcher stopped');
|
|
171
215
|
}
|
|
172
216
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,GAAG,GAAG,KAAK,CAAC,6BAA6B,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"strand-watcher.js","sourceRoot":"","sources":["../src/strand-watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TrustedOwnerStore } from './trusted-owner-store.js';
|
|
2
|
+
/**
|
|
3
|
+
* File-backed {@link TrustedOwnerStore}. Open via {@link open}, which loads the
|
|
4
|
+
* existing file and returns the cross-platform store over it; an absent, corrupt, or wrong-party file is a cold
|
|
5
|
+
* start (empty anchor), while a present-but-unreadable file throws. Those rules
|
|
6
|
+
* and their reasoning live on `NodeLocalSnapshot.open` — they are properties of
|
|
7
|
+
* every persistent backend, not of this one.
|
|
8
|
+
*/
|
|
9
|
+
export declare const FileTrustedOwnerStore: {
|
|
10
|
+
/** Load (or cold-start) the party's anchor from a file in `dir`. */
|
|
11
|
+
open(dir: string, partyId: string): Promise<TrustedOwnerStore>;
|
|
12
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-only file-backed {@link TrustedOwnerStore}: one JSON file per party under
|
|
3
|
+
* a configured directory (the node's state directory — `cadre-cli` passes
|
|
4
|
+
* `ResolvedConfig.nodeStateDir`), holding the node-local, non-replicated
|
|
5
|
+
* trusted-owner anchor.
|
|
6
|
+
*
|
|
7
|
+
* Nothing but the file itself lives here: the anchor's envelope, load policy,
|
|
8
|
+
* per-entry validation and serialised snapshot writes are cross-platform in
|
|
9
|
+
* `PersistentTrustedOwnerStore` / `node-local-snapshot.ts`, and the file is a
|
|
10
|
+
* `FileDurableSlot`. This module exists only so the `node:fs` edge stays out of
|
|
11
|
+
* the package's cross-platform default entry (`./index.js`) — import it from
|
|
12
|
+
* the dedicated subpath instead:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { FileTrustedOwnerStore } from '@serfab/cadre-core/trusted-owner-store-file';
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* A React Native / browser entry graph never resolves this path, so the
|
|
19
|
+
* `node:fs` edge never reaches a bundler that cannot satisfy it (same
|
|
20
|
+
* isolation pattern as `key-store-file.ts`).
|
|
21
|
+
*/
|
|
22
|
+
import { FileDurableSlot } from './file-durable-slot.js';
|
|
23
|
+
import { PersistentTrustedOwnerStore } from './trusted-owner-store.js';
|
|
24
|
+
/** Base name of the anchor file: `<dir>/trusted-owners.<encoded partyId>.json`. */
|
|
25
|
+
const SLOT_NAME = 'trusted-owners';
|
|
26
|
+
/**
|
|
27
|
+
* File-backed {@link TrustedOwnerStore}. Open via {@link open}, which loads the
|
|
28
|
+
* existing file and returns the cross-platform store over it; an absent, corrupt, or wrong-party file is a cold
|
|
29
|
+
* start (empty anchor), while a present-but-unreadable file throws. Those rules
|
|
30
|
+
* and their reasoning live on `NodeLocalSnapshot.open` — they are properties of
|
|
31
|
+
* every persistent backend, not of this one.
|
|
32
|
+
*/
|
|
33
|
+
export const FileTrustedOwnerStore = {
|
|
34
|
+
/** Load (or cold-start) the party's anchor from a file in `dir`. */
|
|
35
|
+
async open(dir, partyId) {
|
|
36
|
+
return PersistentTrustedOwnerStore.open(new FileDurableSlot(dir, SLOT_NAME, partyId), partyId);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=trusted-owner-store-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trusted-owner-store-file.js","sourceRoot":"","sources":["../src/trusted-owner-store-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAGvE,mFAAmF;AACnF,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACpC,oEAAoE;IACpE,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,OAAe;QACtC,OAAO,2BAA2B,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;IAChG,CAAC;CACD,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type DurableSlot } from './node-local-snapshot.js';
|
|
2
|
+
/** How an owner key entered the anchor (out-of-band provenance). */
|
|
3
|
+
export type TrustSource = 'genesis' | 'invite' | 'operator';
|
|
4
|
+
export interface TrustedOwnerStore {
|
|
5
|
+
/** Party this anchor is scoped to. */
|
|
6
|
+
readonly partyId: string;
|
|
7
|
+
/** Is this ed25519 (base64url) key one of my party's out-of-band-trusted owners? */
|
|
8
|
+
has(ownerKey: string): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* All anchored owner keys (e.g. for seed-trust `knownOwnerKeys`).
|
|
11
|
+
*
|
|
12
|
+
* NOTE: every backend copies into a fresh Set per call (so the result is a
|
|
13
|
+
* snapshot decoupled from later `trust()` calls). Anchors hold a handful of
|
|
14
|
+
* keys and callers are per-seed, so the copy is free today; if a hot path
|
|
15
|
+
* ever calls this per message, prefer `has()` or cache the snapshot.
|
|
16
|
+
*/
|
|
17
|
+
all(): ReadonlySet<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Add a key established out of band (genesis self-trust / invite pin /
|
|
20
|
+
* operator pin). Idempotent: re-trusting a known key is a no-op that keeps
|
|
21
|
+
* the original source. Implementations MUST reflect the key in {@link has} /
|
|
22
|
+
* {@link all} synchronously; the returned promise tracks durability only
|
|
23
|
+
* (a persistent backend's write), so a synchronous caller may safely consult
|
|
24
|
+
* the store right after invoking this.
|
|
25
|
+
*/
|
|
26
|
+
trust(ownerKey: string, source: TrustSource): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Ephemeral in-memory anchor for nodes without durable storage (tests, browser
|
|
30
|
+
* demos, not-yet-persisted mobile). Same contract, no disk: trust established
|
|
31
|
+
* here must be re-supplied (invite / operator pin) on the next process.
|
|
32
|
+
*/
|
|
33
|
+
export declare class MemoryTrustedOwnerStore implements TrustedOwnerStore {
|
|
34
|
+
readonly partyId: string;
|
|
35
|
+
private readonly keys;
|
|
36
|
+
constructor(partyId: string);
|
|
37
|
+
has(ownerKey: string): boolean;
|
|
38
|
+
all(): ReadonlySet<string>;
|
|
39
|
+
trust(ownerKey: string, source: TrustSource): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Durable {@link TrustedOwnerStore} over an app-supplied {@link DurableSlot} —
|
|
43
|
+
* the cross-platform half of every persistent backend (the Node
|
|
44
|
+
* `FileTrustedOwnerStore` is this class over a file slot).
|
|
45
|
+
*
|
|
46
|
+
* Construct via {@link open}. Load and persist policy — what an absent,
|
|
47
|
+
* corrupt, foreign-party or unreadable slot does, and what a failed persist
|
|
48
|
+
* does — is documented once on `NodeLocalSnapshot`; this class only supplies
|
|
49
|
+
* the payload shape and the anchor's whole-record-on-bad-entry policy.
|
|
50
|
+
*/
|
|
51
|
+
export declare class PersistentTrustedOwnerStore implements TrustedOwnerStore {
|
|
52
|
+
private readonly snapshot;
|
|
53
|
+
private constructor();
|
|
54
|
+
/** Load (or cold-start) the party's anchor from `slot`. */
|
|
55
|
+
static open(slot: DurableSlot, partyId: string): Promise<PersistentTrustedOwnerStore>;
|
|
56
|
+
get partyId(): string;
|
|
57
|
+
has(ownerKey: string): boolean;
|
|
58
|
+
all(): ReadonlySet<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Anchor a key: visible via {@link has} / {@link all} synchronously, then the
|
|
61
|
+
* full snapshot is persisted (see `NodeLocalSnapshot.put`).
|
|
62
|
+
*/
|
|
63
|
+
trust(ownerKey: string, source: TrustSource): Promise<void>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-local, NON-replicated trusted-owner anchor.
|
|
3
|
+
*
|
|
4
|
+
* The replicated `CadreControl.OwnerKey` table cannot anchor trust: any
|
|
5
|
+
* connecting node can genesis-insert its own key into its local table and that
|
|
6
|
+
* row replicates into every peer's copy, so "is this key in `OwnerKey`?" can be
|
|
7
|
+
* made true by a stranger. This store is the anchor that CAN be trusted — a
|
|
8
|
+
* per-party, on-device record of owner keys established OUT OF BAND: founding
|
|
9
|
+
* the party (genesis self-trust), the pinned keys carried by the `CadreInvite`
|
|
10
|
+
* that enrolled this node, or an explicit operator pin. It is never written
|
|
11
|
+
* from replicated control state; that is the whole point.
|
|
12
|
+
*
|
|
13
|
+
* Three implementations:
|
|
14
|
+
* - {@link MemoryTrustedOwnerStore} (this module, cross-platform) — ephemeral;
|
|
15
|
+
* the default when no store is injected via `CadreNodeConfig.trustedOwners`.
|
|
16
|
+
* - {@link PersistentTrustedOwnerStore} (this module, cross-platform) — durable
|
|
17
|
+
* over any `DurableSlot` the embedding app supplies (IndexedDB, SecureStore,
|
|
18
|
+
* SQLite, …). The load/persist policy lives in `node-local-snapshot.ts`; read
|
|
19
|
+
* {@link NodeLocalSnapshot.open} for what an absent, corrupt, foreign-party
|
|
20
|
+
* or unreadable slot does.
|
|
21
|
+
* - `FileTrustedOwnerStore` — the above over a Node file in the node's state
|
|
22
|
+
* directory; Node-only, behind the subpath
|
|
23
|
+
* `@serfab/cadre-core/trusted-owner-store-file` (same isolation pattern as
|
|
24
|
+
* `key-store-file`) so `node:fs` never lands in the RN/browser entry graph.
|
|
25
|
+
*
|
|
26
|
+
* Keys are additive: nothing in this interface removes a key (owner revocation
|
|
27
|
+
* is out of scope here and tracked with the broader control-sync design).
|
|
28
|
+
*/
|
|
29
|
+
import debug from 'debug';
|
|
30
|
+
import { NodeLocalSnapshot } from './node-local-snapshot.js';
|
|
31
|
+
const log = debug('sereus:cadre:trusted-owner-store');
|
|
32
|
+
/**
|
|
33
|
+
* Ephemeral in-memory anchor for nodes without durable storage (tests, browser
|
|
34
|
+
* demos, not-yet-persisted mobile). Same contract, no disk: trust established
|
|
35
|
+
* here must be re-supplied (invite / operator pin) on the next process.
|
|
36
|
+
*/
|
|
37
|
+
export class MemoryTrustedOwnerStore {
|
|
38
|
+
constructor(partyId) {
|
|
39
|
+
this.partyId = partyId;
|
|
40
|
+
this.keys = new Map();
|
|
41
|
+
}
|
|
42
|
+
has(ownerKey) {
|
|
43
|
+
return this.keys.has(ownerKey);
|
|
44
|
+
}
|
|
45
|
+
all() {
|
|
46
|
+
return new Set(this.keys.keys());
|
|
47
|
+
}
|
|
48
|
+
async trust(ownerKey, source) {
|
|
49
|
+
if (this.keys.has(ownerKey)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.keys.set(ownerKey, source);
|
|
53
|
+
log('trusted owner key anchored (party=%s, source=%s)', this.partyId, source);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const KNOWN_SOURCES = new Set(['genesis', 'invite', 'operator']);
|
|
57
|
+
/**
|
|
58
|
+
* What the anchor persists: `owners` maps ownerKey (base64url) -> provenance.
|
|
59
|
+
*
|
|
60
|
+
* One unusable entry discards the WHOLE record: trusting a subset of the keys a
|
|
61
|
+
* record claims is a silent, security-relevant downgrade, so a record that
|
|
62
|
+
* cannot be read in full is treated as no anchor at all (see
|
|
63
|
+
* `UnusableEntryPolicy` in `node-local-snapshot.ts`).
|
|
64
|
+
*/
|
|
65
|
+
const TRUSTED_OWNER_SNAPSHOT = {
|
|
66
|
+
label: 'trusted-owner anchor',
|
|
67
|
+
payloadKey: 'owners',
|
|
68
|
+
unusableEntry: 'discard-all',
|
|
69
|
+
acceptEntry: (_ownerKey, entry) => {
|
|
70
|
+
if (typeof entry !== 'object' || entry === null)
|
|
71
|
+
return undefined;
|
|
72
|
+
const { source, trustedAt } = entry;
|
|
73
|
+
if (typeof source !== 'string' || !KNOWN_SOURCES.has(source))
|
|
74
|
+
return undefined;
|
|
75
|
+
if (typeof trustedAt !== 'number')
|
|
76
|
+
return undefined;
|
|
77
|
+
return { source: source, trustedAt };
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Durable {@link TrustedOwnerStore} over an app-supplied {@link DurableSlot} —
|
|
82
|
+
* the cross-platform half of every persistent backend (the Node
|
|
83
|
+
* `FileTrustedOwnerStore` is this class over a file slot).
|
|
84
|
+
*
|
|
85
|
+
* Construct via {@link open}. Load and persist policy — what an absent,
|
|
86
|
+
* corrupt, foreign-party or unreadable slot does, and what a failed persist
|
|
87
|
+
* does — is documented once on `NodeLocalSnapshot`; this class only supplies
|
|
88
|
+
* the payload shape and the anchor's whole-record-on-bad-entry policy.
|
|
89
|
+
*/
|
|
90
|
+
export class PersistentTrustedOwnerStore {
|
|
91
|
+
constructor(snapshot) {
|
|
92
|
+
this.snapshot = snapshot;
|
|
93
|
+
}
|
|
94
|
+
/** Load (or cold-start) the party's anchor from `slot`. */
|
|
95
|
+
static async open(slot, partyId) {
|
|
96
|
+
return new PersistentTrustedOwnerStore(await NodeLocalSnapshot.open(slot, partyId, TRUSTED_OWNER_SNAPSHOT));
|
|
97
|
+
}
|
|
98
|
+
get partyId() {
|
|
99
|
+
return this.snapshot.partyId;
|
|
100
|
+
}
|
|
101
|
+
has(ownerKey) {
|
|
102
|
+
return this.snapshot.has(ownerKey);
|
|
103
|
+
}
|
|
104
|
+
all() {
|
|
105
|
+
return this.snapshot.keySnapshot();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Anchor a key: visible via {@link has} / {@link all} synchronously, then the
|
|
109
|
+
* full snapshot is persisted (see `NodeLocalSnapshot.put`).
|
|
110
|
+
*/
|
|
111
|
+
trust(ownerKey, source) {
|
|
112
|
+
if (this.snapshot.has(ownerKey)) {
|
|
113
|
+
// Idempotent: re-enrollment / restart re-seeding keeps the original
|
|
114
|
+
// provenance and skips the write entirely.
|
|
115
|
+
// NOTE: this resolves immediately rather than joining the write chain, so
|
|
116
|
+
// re-trusting a key whose first persist is still in flight does not await
|
|
117
|
+
// that persist. Harmless for the seeding callers (they re-trust only after
|
|
118
|
+
// the original settled, or do not await at all); chain it if a caller ever
|
|
119
|
+
// needs "durable by the time trust() resolves" for a repeat key.
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
}
|
|
122
|
+
log('trusted owner key anchored (party=%s, source=%s); persisting', this.partyId, source);
|
|
123
|
+
return this.snapshot.put(ownerKey, { source, trustedAt: Date.now() });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=trusted-owner-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trusted-owner-store.js","sourceRoot":"","sources":["../src/trusted-owner-store.ts"],"names":[],"mappings":"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"}
|