@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 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 this.ensureCryptoReady();
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 this.ensureCryptoReady();
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
- if (typeof process !== "undefined" && process.env?.SECURECOMS_TRACE_DELIVERY) {
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
- import { generateEphemeralKeypair } from "@agentvault/crypto";
5120
- var warming = null;
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: () => ensureSodiumReady(),
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: () => ensureSodiumReady(),
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 this.ensureCryptoReady();
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 this.ensureCryptoReady();
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
- if (typeof process !== "undefined" && process.env?.SECURECOMS_TRACE_DELIVERY) {
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
- import { generateEphemeralKeypair } from "@agentvault/crypto";
4976
- var warming = null;
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: () => ensureSodiumReady(),
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: () => ensureSodiumReady(),
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,
@@ -1,43 +1,65 @@
1
1
  /**
2
2
  * Warm libsodium once per process, before any file-crypto call.
3
3
  *
4
- * ⛔ THE DISCARDED KEYPAIR IS THE READINESS GATE, NOT DEAD CODE. Do not
5
- * delete it. `@agentvault/crypto` has no readiness contract on its entire
6
- * synchronous surface: sixteen exported synchronous functions throw when
7
- * libsodium's WASM heap is not yet initialised, with either
8
- * `<member> is not a function` or
9
- * `Cannot read properties of undefined (reading '_malloc')`.
10
- * `generateEphemeralKeypair` is an ASYNC export that awaits `sodium.ready`
11
- * internally, on the SAME module instance the synchronous exports use, so
12
- * awaiting it is what makes those exports safe to call afterwards. The X25519
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
- * ⛔ DO NOT IMPORT `libsodium-wrappers-sumo` DIRECTLY HERE. That specifier is
17
- * absent from esbuild.config.mjs's EXTERNAL list, so esbuild would bundle a
18
- * SECOND libsodium instance. The await would then warm a heap that nothing
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
- * ⛔ MEMOIZE THE PROMISE, NOT A BOOLEAN. Measured: under
22
- * `if (warmed) return; await ...; warmed = true;` two CONCURRENT callers both
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
- * ⛔⛔ THE `catch` THAT CLEARS THE MEMO IS NOT REDUNDANT. DO NOT DELETE IT.
27
- * Caching the promise fixes the double-warm race, but a bare
28
- * `warming ??= ...` introduces a failure mode the boolean form did NOT have:
29
- * if the FIRST warm-up REJECTS (WASM instantiation failure, OOM, a corrupt
30
- * binary), `warming` holds a REJECTED promise for the life of the process, and
31
- * every later prepareAttachment AND materializeAttachment then rejects at its
32
- * first statement, forever. ⛔ ON THE RECEIVE SIDE THAT IS SILENT: the ingest
33
- * pump's fail-soft catch acks the message, increments the failed-attachment
34
- * counter and logs, so one transient failure becomes a PERMANENT,
35
- * PROCESS-WIDE attachment outage that never surfaces as a crash. The boolean
36
- * form would have retried. Clearing the memo on rejection restores that retry
37
- * while keeping the single-warm guarantee on the success path.
38
- * ⛔ It RETHROWS: the first caller must still see the real failure. This is
39
- * not a swallow-and-proceed, which is the exact shape that caused the
40
- * production incident this whole finding catalogues.
41
- * Pinned by src/sodium-ready.retry.test.ts, both halves.
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>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securecoms/agent-bridge",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",