@securecoms/agent-bridge 0.11.1 → 0.11.2
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/dist/bin.js +34 -45
- package/dist/index.js +35 -46
- package/dist/sodium-ready.d.ts +57 -35
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1471,6 +1471,16 @@ function isServerExpired(exp, nowMs) {
|
|
|
1471
1471
|
return nowMs - SKEW_GUARD_MS >= exp.getTime();
|
|
1472
1472
|
}
|
|
1473
1473
|
|
|
1474
|
+
// ../connect-sdk-ts/dist/sodium-ready.js
|
|
1475
|
+
import { generateEphemeralKeypair } from "@agentvault/crypto";
|
|
1476
|
+
var warming = null;
|
|
1477
|
+
function ensureSodiumReady() {
|
|
1478
|
+
return warming ??= generateEphemeralKeypair().then(() => void 0).catch((e) => {
|
|
1479
|
+
warming = null;
|
|
1480
|
+
throw e;
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1474
1484
|
// ../connect-sdk-ts/dist/groupkey/mls.js
|
|
1475
1485
|
var LABEL = "agentvault-connect-v1";
|
|
1476
1486
|
var DEFAULT_STUCK_THRESHOLD = 2;
|
|
@@ -1546,8 +1556,6 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
1546
1556
|
o;
|
|
1547
1557
|
suite;
|
|
1548
1558
|
managers = /* @__PURE__ */ new Map();
|
|
1549
|
-
/** One-time libsodium (WASM) warm-up guard — see ensureCryptoReady(). */
|
|
1550
|
-
cryptoWarmed = false;
|
|
1551
1559
|
/**
|
|
1552
1560
|
* Per-CHANNEL Promise-chain mutex (gen2's MlsOperationQueue, ported). Every
|
|
1553
1561
|
* manager-mutating operation on a channel takes this lock keyed by channelId,
|
|
@@ -2072,37 +2080,8 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
2072
2080
|
const w = rows.find((r) => r.message_type === "welcome" && r.connect_channel_id != null);
|
|
2073
2081
|
return w?.connect_channel_id ?? null;
|
|
2074
2082
|
}
|
|
2075
|
-
/**
|
|
2076
|
-
* Give the ts-mls libsodium (WASM) backend time to initialize before any
|
|
2077
|
-
* store operation that deserializes a persisted KeyPackage. generateKeyPackage
|
|
2078
|
-
* is pure in-memory (it neither publishes to the backend nor writes the
|
|
2079
|
-
* store), so the throwaway bundle is a side-effect-free warm-up.
|
|
2080
|
-
*
|
|
2081
|
-
* ⛔ IT IS A RACE, NOT A BARRIER, and do not read it as one. Nothing on this
|
|
2082
|
-
* path awaits sodium.ready: MLSGroupManager defaults _provider to
|
|
2083
|
-
* nobleCryptoProvider, and _ensureCiphersuite passes that provider to
|
|
2084
|
-
* getCiphersuiteImpl. What the warm-up actually does is BURN WALL CLOCK while
|
|
2085
|
-
* libsodium's WASM initializes in PARALLEL. The margin is roughly 1.7 ms and
|
|
2086
|
-
* it DRIFTS: libsodium-sumo is reached through a caret and its init timing
|
|
2087
|
-
* sets the margin, so an install can move it with no change in this repo.
|
|
2088
|
-
* See 02f47e15 (PR #87), whose commit body carries the correct diagnosis.
|
|
2089
|
-
*
|
|
2090
|
-
* Without it, the FIRST loadPendingBundle at boot (the republish
|
|
2091
|
-
* guard, and the join path) runs while WASM is still cold, throws deep in the
|
|
2092
|
-
* crypto ("Cannot read properties of undefined (reading '_malloc')"), is caught
|
|
2093
|
-
* and misread as "no held bundle", and the daemon mints + republishes a FRESH
|
|
2094
|
-
* KeyPackage on every restart — orphaning every Welcome sealed to the prior
|
|
2095
|
-
* published KP (the root cause of "fresh DM never opens" across restarts).
|
|
2096
|
-
* Idempotent and cheap (~ms); the flag makes repeat calls no-ops.
|
|
2097
|
-
*/
|
|
2098
|
-
async ensureCryptoReady() {
|
|
2099
|
-
if (this.cryptoWarmed)
|
|
2100
|
-
return;
|
|
2101
|
-
await new MLSGroupManager2().generateKeyPackage(this.o.identity);
|
|
2102
|
-
this.cryptoWarmed = true;
|
|
2103
|
-
}
|
|
2104
2083
|
async publishKeyPackage() {
|
|
2105
|
-
await
|
|
2084
|
+
await ensureSodiumReady();
|
|
2106
2085
|
return this.publishLock.run(this.o.deviceId, () => this.publishKeyPackageUnlocked());
|
|
2107
2086
|
}
|
|
2108
2087
|
/**
|
|
@@ -2189,7 +2168,7 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
2189
2168
|
async ensureSessionUnlocked(channelId, opts) {
|
|
2190
2169
|
if (!opts.forceReinit && await this.o.store.load(channelId))
|
|
2191
2170
|
return;
|
|
2192
|
-
await
|
|
2171
|
+
await ensureSodiumReady();
|
|
2193
2172
|
let bundle = await this.o.store.loadPendingBundle(this.o.deviceId);
|
|
2194
2173
|
if (!bundle && (opts.bootstrap || opts.forceReinit)) {
|
|
2195
2174
|
await this.publishKeyPackage();
|
|
@@ -4619,6 +4598,22 @@ var SqliteMlsStateStore = class {
|
|
|
4619
4598
|
async discardStaging(channelId) {
|
|
4620
4599
|
this.db.prepare("UPDATE mls_group_state SET sealed_staging=NULL WHERE identity_id=? AND channel_id=?").run(this.id, channelId);
|
|
4621
4600
|
}
|
|
4601
|
+
/**
|
|
4602
|
+
* ⛔ THE WRITE PATH GETS NO READINESS BARRIER, AND THAT IS DELIBERATE. Two
|
|
4603
|
+
* independent reasons, and the FIRST is the primary one.
|
|
4604
|
+
*
|
|
4605
|
+
* 1. `:171` below populates `this.bundles` BEFORE the seal on the next line.
|
|
4606
|
+
* A same-process reload therefore hits the cache at `loadPendingBundle`'s
|
|
4607
|
+
* `:182-183` and NEVER decodes. The decode path is only ever taken for a
|
|
4608
|
+
* bundle written by a PREVIOUS process, which by definition is not this
|
|
4609
|
+
* one. A barrier here could not protect the read that actually fails.
|
|
4610
|
+
* 2. `encodeBundle`'s `bytesToHex` is cold-safe BECAUSE `to_hex` needs no
|
|
4611
|
+
* `_malloc`. ⛔ IT IS NOT "PURE JS" AND THE COMMENT MUST NEVER SAY THAT.
|
|
4612
|
+
* It is a libsodium call like the others; it simply does not touch the
|
|
4613
|
+
* WASM heap. Recording the wrong reason invites a later reader to
|
|
4614
|
+
* "correct" the asymmetry between this method and `loadPendingBundle` in
|
|
4615
|
+
* the wrong direction.
|
|
4616
|
+
*/
|
|
4622
4617
|
async savePendingBundle(ref, bundle) {
|
|
4623
4618
|
this.bundles.set(ref, bundle);
|
|
4624
4619
|
const sealed = await this.cipher.seal(encodeBundle(bundle));
|
|
@@ -4633,13 +4628,12 @@ var SqliteMlsStateStore = class {
|
|
|
4633
4628
|
const row = this.db.prepare("SELECT sealed_bundle FROM mls_pending_bundle WHERE identity_id=? AND ref=?").get(this.id, ref);
|
|
4634
4629
|
if (!row?.sealed_bundle)
|
|
4635
4630
|
return null;
|
|
4631
|
+
await ensureSodiumReady();
|
|
4636
4632
|
let bundle;
|
|
4637
4633
|
try {
|
|
4638
4634
|
bundle = decodeBundle(await this.cipher.open(row.sealed_bundle));
|
|
4639
4635
|
} catch (e) {
|
|
4640
|
-
|
|
4641
|
-
console.warn(`[trace] loadPendingBundle: unseal/decode FAILED for ref ${ref.slice(0, 8)}: ${e?.message}`);
|
|
4642
|
-
}
|
|
4636
|
+
console.warn(`[trace] loadPendingBundle: unseal/decode FAILED for ref ${ref.slice(0, 8)}: ${e?.message}`);
|
|
4643
4637
|
return null;
|
|
4644
4638
|
}
|
|
4645
4639
|
this.bundles.set(ref, bundle);
|
|
@@ -5116,13 +5110,8 @@ async function materializeAttachment(body, ctx, deps) {
|
|
|
5116
5110
|
}
|
|
5117
5111
|
|
|
5118
5112
|
// src/sodium-ready.ts
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
function ensureSodiumReady() {
|
|
5122
|
-
return warming ??= generateEphemeralKeypair().then(() => void 0).catch((e) => {
|
|
5123
|
-
warming = null;
|
|
5124
|
-
throw e;
|
|
5125
|
-
});
|
|
5113
|
+
function ensureSodiumReady2() {
|
|
5114
|
+
return ensureSodiumReady();
|
|
5126
5115
|
}
|
|
5127
5116
|
|
|
5128
5117
|
// src/memory/hermes-session-map.ts
|
|
@@ -6538,7 +6527,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
6538
6527
|
}
|
|
6539
6528
|
sendDeps() {
|
|
6540
6529
|
return {
|
|
6541
|
-
ready: () =>
|
|
6530
|
+
ready: () => ensureSodiumReady2(),
|
|
6542
6531
|
uploadBlob: (ciphertext, channelId) => this.uploadBlobImpl(ciphertext, channelId),
|
|
6543
6532
|
encryptFile,
|
|
6544
6533
|
bytesToBase64: bytesToBase642,
|
|
@@ -6547,7 +6536,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
6547
6536
|
}
|
|
6548
6537
|
receiveDeps() {
|
|
6549
6538
|
return {
|
|
6550
|
-
ready: () =>
|
|
6539
|
+
ready: () => ensureSodiumReady2(),
|
|
6551
6540
|
downloadBlob: this.downloadBlobImpl,
|
|
6552
6541
|
decryptFile,
|
|
6553
6542
|
computeFileDigest,
|
package/dist/index.js
CHANGED
|
@@ -1368,6 +1368,16 @@ function isServerExpired(exp, nowMs) {
|
|
|
1368
1368
|
return nowMs - SKEW_GUARD_MS >= exp.getTime();
|
|
1369
1369
|
}
|
|
1370
1370
|
|
|
1371
|
+
// ../connect-sdk-ts/dist/sodium-ready.js
|
|
1372
|
+
import { generateEphemeralKeypair } from "@agentvault/crypto";
|
|
1373
|
+
var warming = null;
|
|
1374
|
+
function ensureSodiumReady() {
|
|
1375
|
+
return warming ??= generateEphemeralKeypair().then(() => void 0).catch((e) => {
|
|
1376
|
+
warming = null;
|
|
1377
|
+
throw e;
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1371
1381
|
// ../connect-sdk-ts/dist/groupkey/mls.js
|
|
1372
1382
|
var LABEL = "agentvault-connect-v1";
|
|
1373
1383
|
var DEFAULT_STUCK_THRESHOLD = 2;
|
|
@@ -1443,8 +1453,6 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
1443
1453
|
o;
|
|
1444
1454
|
suite;
|
|
1445
1455
|
managers = /* @__PURE__ */ new Map();
|
|
1446
|
-
/** One-time libsodium (WASM) warm-up guard — see ensureCryptoReady(). */
|
|
1447
|
-
cryptoWarmed = false;
|
|
1448
1456
|
/**
|
|
1449
1457
|
* Per-CHANNEL Promise-chain mutex (gen2's MlsOperationQueue, ported). Every
|
|
1450
1458
|
* manager-mutating operation on a channel takes this lock keyed by channelId,
|
|
@@ -1969,37 +1977,8 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
1969
1977
|
const w = rows.find((r) => r.message_type === "welcome" && r.connect_channel_id != null);
|
|
1970
1978
|
return w?.connect_channel_id ?? null;
|
|
1971
1979
|
}
|
|
1972
|
-
/**
|
|
1973
|
-
* Give the ts-mls libsodium (WASM) backend time to initialize before any
|
|
1974
|
-
* store operation that deserializes a persisted KeyPackage. generateKeyPackage
|
|
1975
|
-
* is pure in-memory (it neither publishes to the backend nor writes the
|
|
1976
|
-
* store), so the throwaway bundle is a side-effect-free warm-up.
|
|
1977
|
-
*
|
|
1978
|
-
* ⛔ IT IS A RACE, NOT A BARRIER, and do not read it as one. Nothing on this
|
|
1979
|
-
* path awaits sodium.ready: MLSGroupManager defaults _provider to
|
|
1980
|
-
* nobleCryptoProvider, and _ensureCiphersuite passes that provider to
|
|
1981
|
-
* getCiphersuiteImpl. What the warm-up actually does is BURN WALL CLOCK while
|
|
1982
|
-
* libsodium's WASM initializes in PARALLEL. The margin is roughly 1.7 ms and
|
|
1983
|
-
* it DRIFTS: libsodium-sumo is reached through a caret and its init timing
|
|
1984
|
-
* sets the margin, so an install can move it with no change in this repo.
|
|
1985
|
-
* See 02f47e15 (PR #87), whose commit body carries the correct diagnosis.
|
|
1986
|
-
*
|
|
1987
|
-
* Without it, the FIRST loadPendingBundle at boot (the republish
|
|
1988
|
-
* guard, and the join path) runs while WASM is still cold, throws deep in the
|
|
1989
|
-
* crypto ("Cannot read properties of undefined (reading '_malloc')"), is caught
|
|
1990
|
-
* and misread as "no held bundle", and the daemon mints + republishes a FRESH
|
|
1991
|
-
* KeyPackage on every restart — orphaning every Welcome sealed to the prior
|
|
1992
|
-
* published KP (the root cause of "fresh DM never opens" across restarts).
|
|
1993
|
-
* Idempotent and cheap (~ms); the flag makes repeat calls no-ops.
|
|
1994
|
-
*/
|
|
1995
|
-
async ensureCryptoReady() {
|
|
1996
|
-
if (this.cryptoWarmed)
|
|
1997
|
-
return;
|
|
1998
|
-
await new MLSGroupManager2().generateKeyPackage(this.o.identity);
|
|
1999
|
-
this.cryptoWarmed = true;
|
|
2000
|
-
}
|
|
2001
1980
|
async publishKeyPackage() {
|
|
2002
|
-
await
|
|
1981
|
+
await ensureSodiumReady();
|
|
2003
1982
|
return this.publishLock.run(this.o.deviceId, () => this.publishKeyPackageUnlocked());
|
|
2004
1983
|
}
|
|
2005
1984
|
/**
|
|
@@ -2086,7 +2065,7 @@ var MlsGroupKeyProvider = class _MlsGroupKeyProvider {
|
|
|
2086
2065
|
async ensureSessionUnlocked(channelId, opts) {
|
|
2087
2066
|
if (!opts.forceReinit && await this.o.store.load(channelId))
|
|
2088
2067
|
return;
|
|
2089
|
-
await
|
|
2068
|
+
await ensureSodiumReady();
|
|
2090
2069
|
let bundle = await this.o.store.loadPendingBundle(this.o.deviceId);
|
|
2091
2070
|
if (!bundle && (opts.bootstrap || opts.forceReinit)) {
|
|
2092
2071
|
await this.publishKeyPackage();
|
|
@@ -4516,6 +4495,22 @@ var SqliteMlsStateStore = class {
|
|
|
4516
4495
|
async discardStaging(channelId) {
|
|
4517
4496
|
this.db.prepare("UPDATE mls_group_state SET sealed_staging=NULL WHERE identity_id=? AND channel_id=?").run(this.id, channelId);
|
|
4518
4497
|
}
|
|
4498
|
+
/**
|
|
4499
|
+
* ⛔ THE WRITE PATH GETS NO READINESS BARRIER, AND THAT IS DELIBERATE. Two
|
|
4500
|
+
* independent reasons, and the FIRST is the primary one.
|
|
4501
|
+
*
|
|
4502
|
+
* 1. `:171` below populates `this.bundles` BEFORE the seal on the next line.
|
|
4503
|
+
* A same-process reload therefore hits the cache at `loadPendingBundle`'s
|
|
4504
|
+
* `:182-183` and NEVER decodes. The decode path is only ever taken for a
|
|
4505
|
+
* bundle written by a PREVIOUS process, which by definition is not this
|
|
4506
|
+
* one. A barrier here could not protect the read that actually fails.
|
|
4507
|
+
* 2. `encodeBundle`'s `bytesToHex` is cold-safe BECAUSE `to_hex` needs no
|
|
4508
|
+
* `_malloc`. ⛔ IT IS NOT "PURE JS" AND THE COMMENT MUST NEVER SAY THAT.
|
|
4509
|
+
* It is a libsodium call like the others; it simply does not touch the
|
|
4510
|
+
* WASM heap. Recording the wrong reason invites a later reader to
|
|
4511
|
+
* "correct" the asymmetry between this method and `loadPendingBundle` in
|
|
4512
|
+
* the wrong direction.
|
|
4513
|
+
*/
|
|
4519
4514
|
async savePendingBundle(ref, bundle) {
|
|
4520
4515
|
this.bundles.set(ref, bundle);
|
|
4521
4516
|
const sealed = await this.cipher.seal(encodeBundle(bundle));
|
|
@@ -4530,13 +4525,12 @@ var SqliteMlsStateStore = class {
|
|
|
4530
4525
|
const row = this.db.prepare("SELECT sealed_bundle FROM mls_pending_bundle WHERE identity_id=? AND ref=?").get(this.id, ref);
|
|
4531
4526
|
if (!row?.sealed_bundle)
|
|
4532
4527
|
return null;
|
|
4528
|
+
await ensureSodiumReady();
|
|
4533
4529
|
let bundle;
|
|
4534
4530
|
try {
|
|
4535
4531
|
bundle = decodeBundle(await this.cipher.open(row.sealed_bundle));
|
|
4536
4532
|
} catch (e) {
|
|
4537
|
-
|
|
4538
|
-
console.warn(`[trace] loadPendingBundle: unseal/decode FAILED for ref ${ref.slice(0, 8)}: ${e?.message}`);
|
|
4539
|
-
}
|
|
4533
|
+
console.warn(`[trace] loadPendingBundle: unseal/decode FAILED for ref ${ref.slice(0, 8)}: ${e?.message}`);
|
|
4540
4534
|
return null;
|
|
4541
4535
|
}
|
|
4542
4536
|
this.bundles.set(ref, bundle);
|
|
@@ -4972,13 +4966,8 @@ async function materializeAttachment(body, ctx, deps) {
|
|
|
4972
4966
|
}
|
|
4973
4967
|
|
|
4974
4968
|
// src/sodium-ready.ts
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
function ensureSodiumReady() {
|
|
4978
|
-
return warming ??= generateEphemeralKeypair().then(() => void 0).catch((e) => {
|
|
4979
|
-
warming = null;
|
|
4980
|
-
throw e;
|
|
4981
|
-
});
|
|
4969
|
+
function ensureSodiumReady2() {
|
|
4970
|
+
return ensureSodiumReady();
|
|
4982
4971
|
}
|
|
4983
4972
|
|
|
4984
4973
|
// src/memory/hermes-session-map.ts
|
|
@@ -6394,7 +6383,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
6394
6383
|
}
|
|
6395
6384
|
sendDeps() {
|
|
6396
6385
|
return {
|
|
6397
|
-
ready: () =>
|
|
6386
|
+
ready: () => ensureSodiumReady2(),
|
|
6398
6387
|
uploadBlob: (ciphertext, channelId) => this.uploadBlobImpl(ciphertext, channelId),
|
|
6399
6388
|
encryptFile,
|
|
6400
6389
|
bytesToBase64: bytesToBase642,
|
|
@@ -6403,7 +6392,7 @@ var BridgeAgent = class _BridgeAgent {
|
|
|
6403
6392
|
}
|
|
6404
6393
|
receiveDeps() {
|
|
6405
6394
|
return {
|
|
6406
|
-
ready: () =>
|
|
6395
|
+
ready: () => ensureSodiumReady2(),
|
|
6407
6396
|
downloadBlob: this.downloadBlobImpl,
|
|
6408
6397
|
decryptFile,
|
|
6409
6398
|
computeFileDigest,
|
|
@@ -8034,7 +8023,7 @@ export {
|
|
|
8034
8023
|
attachAgentWebSocket,
|
|
8035
8024
|
capabilities,
|
|
8036
8025
|
createHttpServer,
|
|
8037
|
-
ensureSodiumReady,
|
|
8026
|
+
ensureSodiumReady2 as ensureSodiumReady,
|
|
8038
8027
|
errorBody,
|
|
8039
8028
|
httpStatus,
|
|
8040
8029
|
isLoopback,
|
package/dist/sodium-ready.d.ts
CHANGED
|
@@ -1,43 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Warm libsodium once per process, before any file-crypto call.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* keypair it returns is minted and thrown away: it is never stored, never
|
|
14
|
-
* published, and never used for anything.
|
|
4
|
+
* ⛔⛔ THIS IS A DELEGATE, AND THE DELEGATION IS THE POINT. #331 moved the
|
|
5
|
+
* warm-up body and its memo to `packages/connect-sdk-ts/src/sodium-ready.ts`.
|
|
6
|
+
* A process that holds TWO independent readiness memos warms libsodium twice,
|
|
7
|
+
* and this bridge process holds one instance of the SDK module already. Keeping
|
|
8
|
+
* a second memo here would have made the two barriers count separately.
|
|
9
|
+
* ⛔ DO NOT restore a local memo, a local `generateEphemeralKeypair()` call, or
|
|
10
|
+
* a local `warming` variable. `src/sodium-ready.delegation.gate.test.ts` (G-E)
|
|
11
|
+
* counts the underlying warm-ups across BOTH barriers and reads 2 the moment
|
|
12
|
+
* one comes back.
|
|
15
13
|
*
|
|
16
|
-
* ⛔
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* reads, and this fix would look correct and do nothing.
|
|
14
|
+
* ⛔ THE NAME AND THE EXPORT STAY. `bridge-agent.ts:64` imports this symbol and
|
|
15
|
+
* `index.ts:7` re-exports it. Callers keep calling `ensureSodiumReady()` from
|
|
16
|
+
* this package; only where the body lives changed.
|
|
20
17
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* run the body. The bridge has two independent entry points (send and
|
|
24
|
-
* receive), so that concurrency is real. The promise form runs the body once.
|
|
18
|
+
* The four findings this file carried before #331, and where each one now
|
|
19
|
+
* lives. None is deleted, because each still binds on something.
|
|
25
20
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* ⛔
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
21
|
+
* ⛔ 1. THE DISCARDED KEYPAIR IS THE READINESS GATE, NOT DEAD CODE. ⇒ ITS
|
|
22
|
+
* SUBJECT MOVED. The `generateEphemeralKeypair()` await now sits in the SDK
|
|
23
|
+
* module named above, and the finding travels with it. It still says what it
|
|
24
|
+
* always said: `@agentvault/crypto` has no readiness contract on its entire
|
|
25
|
+
* synchronous surface, sixteen exported synchronous functions throw when
|
|
26
|
+
* libsodium's WASM heap is not yet initialised (`<member> is not a function`
|
|
27
|
+
* or `Cannot read properties of undefined (reading '_malloc')`), and
|
|
28
|
+
* `generateEphemeralKeypair` is the ASYNC export that awaits `sodium.ready`
|
|
29
|
+
* internally on the SAME module instance those synchronous exports use. ⛔ Do
|
|
30
|
+
* not "simplify" the delegate by inlining a keypair mint back here: that is a
|
|
31
|
+
* second warm-up, not a shortcut.
|
|
32
|
+
*
|
|
33
|
+
* ⛔ 2. DO NOT IMPORT `libsodium-wrappers-sumo` DIRECTLY HERE. ⇒ THIS ONE STILL
|
|
34
|
+
* BINDS ON THIS FILE, UNCHANGED. `esbuild.config.mjs:15` inlines every
|
|
35
|
+
* `@agentvault/connect-*` workspace package into the bridge bundle and that
|
|
36
|
+
* specifier is absent from the EXTERNAL list at `:3-8`, so esbuild would bundle
|
|
37
|
+
* a SECOND libsodium instance out of this file. The await would then warm a
|
|
38
|
+
* heap that nothing reads, and this fix would look correct and do nothing.
|
|
39
|
+
* ⚠️ The same inlining is why the delegate below is not a no-op in the shipped
|
|
40
|
+
* bundle: the SDK barrier is inlined into `dist/bin.js` exactly once, and this
|
|
41
|
+
* delegate points at that one copy.
|
|
42
|
+
*
|
|
43
|
+
* ⛔ 3. MEMOIZE THE PROMISE, NOT A BOOLEAN. ⇒ ITS SUBJECT MOVED. The memo is the
|
|
44
|
+
* SDK module's `warming`. Measured: under `if (warmed) return; await ...;
|
|
45
|
+
* warmed = true;` two CONCURRENT callers both run the body. The bridge has two
|
|
46
|
+
* independent entry points (send and receive), so that concurrency is real
|
|
47
|
+
* here, which is why the finding was written here first. It is pinned now by
|
|
48
|
+
* `packages/connect-sdk-ts/src/sodium-ready.memo.gate.test.ts` (G-C1) and by
|
|
49
|
+
* `src/sodium-ready.retry.test.ts:52-69` in this package, which reaches the SDK
|
|
50
|
+
* memo THROUGH this delegate.
|
|
51
|
+
*
|
|
52
|
+
* ⛔⛔ 4. THE `catch` THAT CLEARS THE MEMO IS NOT REDUNDANT. DO NOT DELETE IT.
|
|
53
|
+
* ⇒ ITS SUBJECT MOVED, AND THE CONSEQUENCE STILL LANDS IN THIS PACKAGE. A bare
|
|
54
|
+
* `warming ??= ...` in the SDK module would hold a REJECTED promise for the
|
|
55
|
+
* life of the process if the FIRST warm-up rejects (WASM instantiation failure,
|
|
56
|
+
* OOM, a corrupt binary), and every later prepareAttachment AND
|
|
57
|
+
* materializeAttachment IN THIS PACKAGE would then reject at its first
|
|
58
|
+
* statement, forever. ⛔ ON THE RECEIVE SIDE THAT IS SILENT: the ingest pump's
|
|
59
|
+
* fail-soft catch acks the message, increments the failed-attachment counter
|
|
60
|
+
* and logs, so one transient failure becomes a PERMANENT, PROCESS-WIDE
|
|
61
|
+
* attachment outage that never surfaces as a crash. The SDK module clears the
|
|
62
|
+
* memo on rejection and RETHROWS, so the first caller still sees the real
|
|
63
|
+
* failure. Pinned by `src/sodium-ready.retry.test.ts`, both halves.
|
|
42
64
|
*/
|
|
43
65
|
export declare function ensureSodiumReady(): Promise<void>;
|