@adhd/sox-embedding-provider 0.4.1 → 0.5.1

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.
@@ -0,0 +1,78 @@
1
+ /**
2
+ * (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) Filters the ONNX Runtime CoreML
3
+ * graph-partitioning warning out of the fastembed child's inherited stderr,
4
+ * while forwarding every other byte through verbatim, unbuffered, and in
5
+ * order.
6
+ *
7
+ * ── Why this exists ─────────────────────────────────────────────────────────
8
+ *
9
+ * `sharedFastembedProcess.ts` forks `fastembedProcessHost.ts` with
10
+ * `resolveExecutionProviders()` returning `['coreml', 'cpu']` on darwin
11
+ * (`fastembedProcessHost.ts`). bge-base's `word_embeddings` tensor is shaped
12
+ * `{30522,768}`, and CoreML's execution provider cannot host any input
13
+ * dimension over 16384 — so onnxruntime's native CoreML EP logs, at EVERY
14
+ * model load:
15
+ *
16
+ * [W:onnxruntime:, helper.cc:83 IsInputSupported] CoreML does not support
17
+ * input dim > 16384. Input:embeddings.word_embeddings.weight,
18
+ * shape: {30522,768}
19
+ *
20
+ * This is emitted by the native onnxruntime addon straight to the process's
21
+ * real stderr fd (not through any JS logger this package controls), and
22
+ * `sharedFastembedProcess.ts` forks the child with
23
+ * `stdio: ['ignore', 'inherit', 'inherit', 'ipc']` — so it lands on every
24
+ * `adhd-backlog` (or any other consumer) invocation's terminal, on every
25
+ * single command, even though the node in question simply falls back to CPU
26
+ * silently and nothing is actually wrong.
27
+ *
28
+ * ── Why this is a stderr *filter*, not a log-severity option ────────────────
29
+ *
30
+ * onnxruntime-node's `InferenceSession.SessionOptions` DOES expose a
31
+ * `logSeverityLevel` (0=Verbose…4=Fatal) that would suppress Warning-level
32
+ * messages at the source — a cleaner fix, if it were reachable. It is not:
33
+ * `fastembed@2.1.0`'s `FlagEmbedding.init()` (`fastembed.js`'s
34
+ * `ort.InferenceSession.create(modelPath, { executionProviders,
35
+ * graphOptimizationLevel: "all" })`) hardcodes its own `SessionOptions`
36
+ * literal with no passthrough for caller options, and `fastembed`'s public
37
+ * `InitOptions` type has no `sessionOptions`/`logSeverityLevel` field either
38
+ * (verified against the installed `fastembed@2.1.0` and
39
+ * `onnxruntime-node@1.21.0` packages — not assumed). Filtering the child's
40
+ * stderr byte stream is therefore the only lever this package can pull.
41
+ *
42
+ * ── Why this must NOT become "throw away all child stderr" ──────────────────
43
+ *
44
+ * `fastembedProcessHost.ts`'s BL-331 lock check intentionally
45
+ * `console.error`s a loud, greppable line when a competing fastembed host is
46
+ * detected (guarding against a silent 25-50x embed-latency regression), and
47
+ * `resolveExecutionProviders()` intentionally `console.error`s when
48
+ * `SOX_EMBED_EXECUTION_PROVIDER` forces a non-default provider. A blanket
49
+ * "pipe stderr, discard everything" fix would silently destroy both of those
50
+ * — reintroducing exactly the silent-failure class BL-331 exists to prevent.
51
+ * So this filter matches ONLY the known-benign CoreML dim-limit warning text
52
+ * and passes every other line — including genuine errors and both lines
53
+ * above — straight through unmodified.
54
+ */
55
+ /**
56
+ * True if `line` is the known-benign ONNX Runtime CoreML dim-limit warning
57
+ * (see module doc comment). This is a plain substring match — deliberately
58
+ * narrow (not a broad `onnxruntime`/`[W:` pattern) so this filter can never
59
+ * swallow an unrelated onnxruntime warning or error that happens to share the
60
+ * `[W:onnxruntime:...]` prefix format.
61
+ */
62
+ export declare function isBenignOnnxCoreMlWarning(line: string): boolean;
63
+ /**
64
+ * Attach a line-buffering filter to a child process's stderr `Readable`,
65
+ * dropping only lines matching {@link isBenignOnnxCoreMlWarning} and writing
66
+ * every other line through to `dest` (defaults to `process.stderr`)
67
+ * unbuffered, verbatim (original line content, `\n`-terminated), and in the
68
+ * order it was received.
69
+ *
70
+ * Chunk boundaries never line up with `\n` boundaries in a real pipe, so
71
+ * this buffers a trailing partial line across `data` events instead of
72
+ * filtering mid-line: `source` is split on `\n` as chunks arrive, each
73
+ * complete line is tested and (if not benign) written immediately, and any
74
+ * remaining partial line is flushed on `end` (also filtered, so a benign
75
+ * warning that happens to be the final unterminated write is still caught).
76
+ */
77
+ export declare function attachOnnxStderrFilter(source: NodeJS.ReadableStream, dest?: NodeJS.WritableStream): void;
78
+ //# sourceMappingURL=onnxStderrFilter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onnxStderrFilter.d.ts","sourceRoot":"","sources":["../src/onnxStderrFilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAIH;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,cAAc,EAC7B,IAAI,GAAE,MAAM,CAAC,cAA+B,GAC3C,IAAI,CAmBN"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) Filters the ONNX Runtime CoreML
3
+ * graph-partitioning warning out of the fastembed child's inherited stderr,
4
+ * while forwarding every other byte through verbatim, unbuffered, and in
5
+ * order.
6
+ *
7
+ * ── Why this exists ─────────────────────────────────────────────────────────
8
+ *
9
+ * `sharedFastembedProcess.ts` forks `fastembedProcessHost.ts` with
10
+ * `resolveExecutionProviders()` returning `['coreml', 'cpu']` on darwin
11
+ * (`fastembedProcessHost.ts`). bge-base's `word_embeddings` tensor is shaped
12
+ * `{30522,768}`, and CoreML's execution provider cannot host any input
13
+ * dimension over 16384 — so onnxruntime's native CoreML EP logs, at EVERY
14
+ * model load:
15
+ *
16
+ * [W:onnxruntime:, helper.cc:83 IsInputSupported] CoreML does not support
17
+ * input dim > 16384. Input:embeddings.word_embeddings.weight,
18
+ * shape: {30522,768}
19
+ *
20
+ * This is emitted by the native onnxruntime addon straight to the process's
21
+ * real stderr fd (not through any JS logger this package controls), and
22
+ * `sharedFastembedProcess.ts` forks the child with
23
+ * `stdio: ['ignore', 'inherit', 'inherit', 'ipc']` — so it lands on every
24
+ * `adhd-backlog` (or any other consumer) invocation's terminal, on every
25
+ * single command, even though the node in question simply falls back to CPU
26
+ * silently and nothing is actually wrong.
27
+ *
28
+ * ── Why this is a stderr *filter*, not a log-severity option ────────────────
29
+ *
30
+ * onnxruntime-node's `InferenceSession.SessionOptions` DOES expose a
31
+ * `logSeverityLevel` (0=Verbose…4=Fatal) that would suppress Warning-level
32
+ * messages at the source — a cleaner fix, if it were reachable. It is not:
33
+ * `fastembed@2.1.0`'s `FlagEmbedding.init()` (`fastembed.js`'s
34
+ * `ort.InferenceSession.create(modelPath, { executionProviders,
35
+ * graphOptimizationLevel: "all" })`) hardcodes its own `SessionOptions`
36
+ * literal with no passthrough for caller options, and `fastembed`'s public
37
+ * `InitOptions` type has no `sessionOptions`/`logSeverityLevel` field either
38
+ * (verified against the installed `fastembed@2.1.0` and
39
+ * `onnxruntime-node@1.21.0` packages — not assumed). Filtering the child's
40
+ * stderr byte stream is therefore the only lever this package can pull.
41
+ *
42
+ * ── Why this must NOT become "throw away all child stderr" ──────────────────
43
+ *
44
+ * `fastembedProcessHost.ts`'s BL-331 lock check intentionally
45
+ * `console.error`s a loud, greppable line when a competing fastembed host is
46
+ * detected (guarding against a silent 25-50x embed-latency regression), and
47
+ * `resolveExecutionProviders()` intentionally `console.error`s when
48
+ * `SOX_EMBED_EXECUTION_PROVIDER` forces a non-default provider. A blanket
49
+ * "pipe stderr, discard everything" fix would silently destroy both of those
50
+ * — reintroducing exactly the silent-failure class BL-331 exists to prevent.
51
+ * So this filter matches ONLY the known-benign CoreML dim-limit warning text
52
+ * and passes every other line — including genuine errors and both lines
53
+ * above — straight through unmodified.
54
+ */
55
+ const BENIGN_CORE_ML_WARNING_SUBSTRING = 'CoreML does not support input dim > 16384';
56
+ /**
57
+ * True if `line` is the known-benign ONNX Runtime CoreML dim-limit warning
58
+ * (see module doc comment). This is a plain substring match — deliberately
59
+ * narrow (not a broad `onnxruntime`/`[W:` pattern) so this filter can never
60
+ * swallow an unrelated onnxruntime warning or error that happens to share the
61
+ * `[W:onnxruntime:...]` prefix format.
62
+ */
63
+ export function isBenignOnnxCoreMlWarning(line) {
64
+ return line.includes(BENIGN_CORE_ML_WARNING_SUBSTRING);
65
+ }
66
+ /**
67
+ * Attach a line-buffering filter to a child process's stderr `Readable`,
68
+ * dropping only lines matching {@link isBenignOnnxCoreMlWarning} and writing
69
+ * every other line through to `dest` (defaults to `process.stderr`)
70
+ * unbuffered, verbatim (original line content, `\n`-terminated), and in the
71
+ * order it was received.
72
+ *
73
+ * Chunk boundaries never line up with `\n` boundaries in a real pipe, so
74
+ * this buffers a trailing partial line across `data` events instead of
75
+ * filtering mid-line: `source` is split on `\n` as chunks arrive, each
76
+ * complete line is tested and (if not benign) written immediately, and any
77
+ * remaining partial line is flushed on `end` (also filtered, so a benign
78
+ * warning that happens to be the final unterminated write is still caught).
79
+ */
80
+ export function attachOnnxStderrFilter(source, dest = process.stderr) {
81
+ let buffer = '';
82
+ source.on('data', (chunk) => {
83
+ buffer += chunk.toString('utf8');
84
+ let idx;
85
+ while ((idx = buffer.indexOf('\n')) !== -1) {
86
+ const line = buffer.slice(0, idx);
87
+ buffer = buffer.slice(idx + 1);
88
+ if (!isBenignOnnxCoreMlWarning(line)) {
89
+ dest.write(`${line}\n`);
90
+ }
91
+ }
92
+ });
93
+ source.on('end', () => {
94
+ if (buffer.length > 0 && !isBenignOnnxCoreMlWarning(buffer)) {
95
+ dest.write(buffer);
96
+ }
97
+ buffer = '';
98
+ });
99
+ }
100
+ //# sourceMappingURL=onnxStderrFilter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onnxStderrFilter.js","sourceRoot":"","sources":["../src/onnxStderrFilter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,MAAM,gCAAgC,GAAG,2CAA2C,CAAC;AAErF;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,OAAO,IAAI,CAAC,QAAQ,CAAC,gCAAgC,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAA6B,EAC7B,OAA8B,OAAO,CAAC,MAAM;IAE5C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAsB,EAAE,EAAE;QAC3C,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,GAAW,CAAC;QAChB,OAAO,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAClC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC/B,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;QACpB,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,GAAG,EAAE,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhd/sox-embedding-provider",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Pluggable text→vector embedding provider — generic EmbeddingProvider interface, config-driven model resolution, async batch-first API (AsyncIterable). Default: fastembed (local ONNX, >=3 model dims proven). Loud-fail: createEmbeddingProvider() throws ResolutionError if config is invalid or model cannot load — no silent downgrade.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -28,10 +28,18 @@ export declare function __resetCompetingHostCacheForTests(): void;
28
28
  * `competing_host_pid` telemetry field would read as permanently "contended"
29
29
  * for every pooled request, which is exactly the false-positive noise BL-331's
30
30
  * own postmortem warns against trusting.
31
+ *
32
+ * (BL-432) `ownService`, when supplied, does the same for SERVICE identity: a
33
+ * lock entry whose `service` equals our own is the sequential-CLI false
34
+ * positive (our own service's earlier/second host), not cross-service
35
+ * contention, and is suppressed. Requires BOTH sides to carry a real identity —
36
+ * `ownService === undefined` or an unlabelled/old lock keeps the original
37
+ * behaviour. The returned `service` is `null` when the lock carries none.
31
38
  */
32
- export declare function detectCompetingFastembedHost(ownPid: number | undefined, ownPoolGroup?: string): {
39
+ export declare function detectCompetingFastembedHost(ownPid: number | undefined, ownPoolGroup?: string, ownService?: string): {
33
40
  pid: number;
34
41
  startedAt: string;
42
+ service: string | null;
35
43
  } | null;
36
44
  /**
37
45
  * (BUG-MEMORY-EMBED-HEAD-OF-LINE-BLOCKING-001) The structural shape both
@@ -224,9 +232,12 @@ export declare class SharedFastembedProcessClient implements SharedFastembedClie
224
232
  * `queue_depth` > 0 alongside a `response_ms` that scales with it) is
225
233
  * distinguishable from "the child itself was slow on a solo request"
226
234
  * (`queue_depth` === 0 with a large `response_ms`).
227
- * 3. `competing_host_pid` present only when a second, still-live
228
- * `fastembedProcessHost` process is detected (BL-331's advisory lock),
229
- * since that changes embed latency 25-50x independent of queueing.
235
+ * 3. `competing_host_pid` (+ `competing_host_service`, BL-432) present
236
+ * only when a second, still-live `fastembedProcessHost` process belonging
237
+ * to a DIFFERENT service is detected (BL-331's advisory lock), since
238
+ * that changes embed latency 25-50x independent of queueing. A
239
+ * same-service lock (the sequential-CLI false positive) is suppressed,
240
+ * so this field means genuine cross-service contention.
230
241
  *
231
242
  * BL-576: `signal`, when supplied, is an external cancellation source —
232
243
  * e.g. `operation-guard.ts`'s `withOperationDeadline` abort controller, so
@@ -541,4 +552,23 @@ export declare function getSharedFastembedProcess(): SharedFastembedClient;
541
552
  * needed.
542
553
  */
543
554
  export declare function __resetSharedFastembedProcessForTests(): void;
555
+ /**
556
+ * (BUG-MEMORYSERVER-EMBED-HEAL-NOOPERATOR-001) Kill AND clear the shared
557
+ * fastembed host singleton, so the next `getSharedFastembedProcess()` forks a
558
+ * genuinely fresh child process (and re-inits the model). This is the
559
+ * embedding-provider half of memory-core's `reinitEmbedProvider()` — the
560
+ * self-heal path that recovers a wedged fastembed child (BUG-021's
561
+ * "Model not initialized" respawn-without-reinit state) by tearing the whole
562
+ * shared host down and re-forking from scratch, rather than waiting for the
563
+ * dead child to be reaped.
564
+ *
565
+ * Unlike `__resetSharedFastembedProcessForTests` (test-only, does NOT
566
+ * terminate), this TERMINATES the existing singleton first — killing the child
567
+ * process(es) via the normal `terminate()` shutdown sequence (BL-405's clean
568
+ * `__shutdown` message, `kill()` fallback) — and only then drops the singleton
569
+ * reference. Safe to call when no singleton has ever been constructed (no-op).
570
+ * Any in-flight request is rejected with the standard termination error, which
571
+ * the heal loop surfaces as a row failure — never a hang.
572
+ */
573
+ export declare function resetSharedFastembedProcess(): Promise<void>;
544
574
  //# sourceMappingURL=sharedFastembedProcess.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sharedFastembedProcess.d.ts","sourceRoot":"","sources":["../src/sharedFastembedProcess.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sharedFastembedProcess.d.ts","sourceRoot":"","sources":["../src/sharedFastembedProcess.ts"],"names":[],"mappings":"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"}
@@ -15,14 +15,15 @@
15
15
  * process alive) but forks a child **process** instead of constructing a
16
16
  * `worker_threads.Worker`.
17
17
  */
18
- import { fork, execSync } from 'node:child_process';
18
+ import { execSync } from 'node:child_process';
19
19
  import { fileURLToPath } from 'node:url';
20
20
  import { dirname, join } from 'node:path';
21
21
  import { existsSync, readFileSync } from 'node:fs';
22
22
  import * as os from 'node:os';
23
23
  import { performance } from 'node:perf_hooks';
24
- import { log } from '@adhd/sox-telemetry';
25
- import { resolveFastembedLockPath } from './fastembedLock.js';
24
+ import { log, forkChild, _recordChildTelemetry, currentRuntimeState } from '@adhd/sox-telemetry';
25
+ import { resolveFastembedLockPath, resolveFastembedServiceLabel } from './fastembedLock.js';
26
+ import { attachOnnxStderrFilter } from './onnxStderrFilter.js';
26
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
27
28
  /**
28
29
  * Resolve `fastembedProcessHost.js` regardless of whether this module is
@@ -97,12 +98,14 @@ const COMPETING_HOST_CACHE_TTL_MS = 3000;
97
98
  let _competingHostCache = null;
98
99
  let _competingHostCacheOwnPid;
99
100
  let _competingHostCacheOwnPoolGroup;
101
+ let _competingHostCacheOwnService;
100
102
  let _competingHostCacheAt = -Infinity;
101
103
  /** TEST-ONLY: clear the TTL cache so a test can force a fresh fs read. */
102
104
  export function __resetCompetingHostCacheForTests() {
103
105
  _competingHostCache = null;
104
106
  _competingHostCacheOwnPid = undefined;
105
107
  _competingHostCacheOwnPoolGroup = undefined;
108
+ _competingHostCacheOwnService = undefined;
106
109
  _competingHostCacheAt = -Infinity;
107
110
  }
108
111
  /**
@@ -116,17 +119,26 @@ export function __resetCompetingHostCacheForTests() {
116
119
  * `competing_host_pid` telemetry field would read as permanently "contended"
117
120
  * for every pooled request, which is exactly the false-positive noise BL-331's
118
121
  * own postmortem warns against trusting.
122
+ *
123
+ * (BL-432) `ownService`, when supplied, does the same for SERVICE identity: a
124
+ * lock entry whose `service` equals our own is the sequential-CLI false
125
+ * positive (our own service's earlier/second host), not cross-service
126
+ * contention, and is suppressed. Requires BOTH sides to carry a real identity —
127
+ * `ownService === undefined` or an unlabelled/old lock keeps the original
128
+ * behaviour. The returned `service` is `null` when the lock carries none.
119
129
  */
120
- export function detectCompetingFastembedHost(ownPid, ownPoolGroup) {
130
+ export function detectCompetingFastembedHost(ownPid, ownPoolGroup, ownService) {
121
131
  const now = performance.now();
122
132
  if (now - _competingHostCacheAt < COMPETING_HOST_CACHE_TTL_MS &&
123
133
  _competingHostCacheOwnPid === ownPid &&
124
- _competingHostCacheOwnPoolGroup === ownPoolGroup) {
134
+ _competingHostCacheOwnPoolGroup === ownPoolGroup &&
135
+ _competingHostCacheOwnService === ownService) {
125
136
  return _competingHostCache;
126
137
  }
127
138
  _competingHostCacheAt = now;
128
139
  _competingHostCacheOwnPid = ownPid;
129
140
  _competingHostCacheOwnPoolGroup = ownPoolGroup;
141
+ _competingHostCacheOwnService = ownService;
130
142
  try {
131
143
  const lockPath = resolveFastembedLockPath();
132
144
  if (!existsSync(lockPath)) {
@@ -136,11 +148,17 @@ export function detectCompetingFastembedHost(ownPid, ownPoolGroup) {
136
148
  const raw = JSON.parse(readFileSync(lockPath, 'utf8'));
137
149
  const pid = typeof raw.pid === 'number' ? raw.pid : null;
138
150
  const isKnownPoolSibling = typeof raw.poolGroup === 'string' && ownPoolGroup !== undefined && raw.poolGroup === ownPoolGroup;
139
- if (pid === null || pid === ownPid || !isPidAlive(pid) || isKnownPoolSibling) {
151
+ // (BL-432) Same-service suppression see this function's doc comment.
152
+ const isSameService = typeof raw.service === 'string' && ownService !== undefined && raw.service === ownService;
153
+ if (pid === null || pid === ownPid || !isPidAlive(pid) || isKnownPoolSibling || isSameService) {
140
154
  _competingHostCache = null;
141
155
  return null;
142
156
  }
143
- _competingHostCache = { pid, startedAt: typeof raw.startedAt === 'string' ? raw.startedAt : 'unknown' };
157
+ _competingHostCache = {
158
+ pid,
159
+ startedAt: typeof raw.startedAt === 'string' ? raw.startedAt : 'unknown',
160
+ service: typeof raw.service === 'string' ? raw.service : null,
161
+ };
144
162
  return _competingHostCache;
145
163
  }
146
164
  catch {
@@ -317,17 +335,53 @@ export class SharedFastembedProcessClient {
317
335
  return this.startingPromise;
318
336
  this.startingPromise = new Promise((resolveStart) => {
319
337
  const hostPath = this.hostPath ?? resolveFastembedHostPath();
320
- const c = fork(hostPath, [], {
321
- stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
338
+ // (BL-432) This client's owning-service identity, forwarded to the child
339
+ // via `SOX_FASTEMBED_SERVICE` so the BL-331 lock it writes names the
340
+ // SERVICE, and so same-service locks can be suppressed. Resolved from the
341
+ // parent's own telemetry service (`currentRuntimeState().service`), or a
342
+ // propagated `SOX_FASTEMBED_SERVICE` if one is already set on us.
343
+ const childService = resolveFastembedServiceLabel(currentRuntimeState().service);
344
+ const c = forkChild(hostPath, { service: 'embedding-provider', role: 'harness', logSink: 'file' }, {
345
+ // (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) stderr is 'pipe', not
346
+ // 'inherit' — see `onnxStderrFilter.ts`'s doc comment for why: the
347
+ // native onnxruntime CoreML EP logs a known-benign "CoreML does not
348
+ // support input dim > 16384" warning on every model load (bge-base's
349
+ // {30522,768} word_embeddings tensor), and it must be dropped
350
+ // without touching any other line (the BL-331 competing-host
351
+ // warning and the forced-execution-provider line above both go
352
+ // through `console.error` in the SAME child and must reach the
353
+ // real terminal unmodified). stdout stays 'inherit' — this package
354
+ // never wanted stdout piped, only stderr filtered.
355
+ stdio: ['ignore', 'inherit', 'pipe', 'ipc'],
322
356
  // Real inference is CPU-bound in native code; no need to keep the
323
357
  // parent process alive on this child's account.
324
358
  detached: false,
325
- ...(this.poolGroup !== undefined
326
- ? { env: { ...process.env, SOX_FASTEMBED_POOL_GROUP: this.poolGroup } }
327
- : {}),
359
+ env: {
360
+ ...process.env,
361
+ ...(this.poolGroup !== undefined ? { SOX_FASTEMBED_POOL_GROUP: this.poolGroup } : {}),
362
+ ...(childService !== undefined ? { SOX_FASTEMBED_SERVICE: childService } : {}),
363
+ },
364
+ });
365
+ if (c.stderr)
366
+ attachOnnxStderrFilter(c.stderr);
367
+ _recordChildTelemetry({
368
+ service: 'embedding-provider',
369
+ role: 'harness',
370
+ logSink: 'file',
371
+ filePath: null,
372
+ pid: c.pid ?? 0,
373
+ source: 'embedding-provider',
328
374
  });
329
375
  c.unref();
330
376
  c.on('message', (msg) => {
377
+ // BL-618: the child's telemetry.ready ack carries no request `id` — it
378
+ // must be handled BEFORE the pending lookup, and it never settles a
379
+ // pending request, only records the child's telemetry state.
380
+ const ready = msg;
381
+ if (ready.type === 'telemetry.ready' && ready.telemetry && typeof ready.telemetry === 'object') {
382
+ _recordChildTelemetry({ ...ready.telemetry, source: 'embedding-provider', acked: true });
383
+ return;
384
+ }
331
385
  const pending = this.pending.get(msg.id);
332
386
  if (!pending)
333
387
  return;
@@ -385,6 +439,19 @@ export class SharedFastembedProcessClient {
385
439
  // idle at 0% CPU. Cross-process ANE contention remains unproven; the
386
440
  // measured cause of the live slowdown was scheduling QoS (BL-331).
387
441
  c.channel?.unref();
442
+ // (BUG-EMBED-ONNX-COREML-STDERR-NOISE-001) Same BL-370 lesson applies to
443
+ // the new 'pipe' stderr handle: attaching the 'data' listener inside
444
+ // `attachOnnxStderrFilter()` puts the underlying Socket into flowing
445
+ // mode, which re-refs it — re-assert `unref()` here so a standalone
446
+ // script with no other outstanding work can still exit promptly instead
447
+ // of hanging on this pipe forever. `child.stderr` is typed as a plain
448
+ // `Readable`, but for `stdio: 'pipe'` it is actually a `net.Socket`
449
+ // (which alone carries `unref()`) — narrowed via a structural check
450
+ // instead of a blind cast so a future Node stdio-shape change fails
451
+ // safe (silently skips the unref) rather than throwing.
452
+ const stderrHandle = c.stderr;
453
+ if (typeof stderrHandle?.unref === 'function')
454
+ stderrHandle.unref();
388
455
  this.child = c;
389
456
  resolveStart(c);
390
457
  });
@@ -437,9 +504,12 @@ export class SharedFastembedProcessClient {
437
504
  * `queue_depth` > 0 alongside a `response_ms` that scales with it) is
438
505
  * distinguishable from "the child itself was slow on a solo request"
439
506
  * (`queue_depth` === 0 with a large `response_ms`).
440
- * 3. `competing_host_pid` present only when a second, still-live
441
- * `fastembedProcessHost` process is detected (BL-331's advisory lock),
442
- * since that changes embed latency 25-50x independent of queueing.
507
+ * 3. `competing_host_pid` (+ `competing_host_service`, BL-432) present
508
+ * only when a second, still-live `fastembedProcessHost` process belonging
509
+ * to a DIFFERENT service is detected (BL-331's advisory lock), since
510
+ * that changes embed latency 25-50x independent of queueing. A
511
+ * same-service lock (the sequential-CLI false positive) is suppressed,
512
+ * so this field means genuine cross-service contention.
443
513
  *
444
514
  * BL-576: `signal`, when supplied, is an external cancellation source —
445
515
  * e.g. `operation-guard.ts`'s `withOperationDeadline` abort controller, so
@@ -488,10 +558,19 @@ export class SharedFastembedProcessClient {
488
558
  }
489
559
  const id = this.nextId++;
490
560
  const queueDepth = this.pending.size;
491
- const competing = detectCompetingFastembedHost(child.pid, this.poolGroup);
561
+ // (BL-432) Our own service identity, so `detectCompetingFastembedHost` can
562
+ // suppress a same-service lock (the sequential-CLI false positive) while
563
+ // still reporting genuine cross-service contention with identity.
564
+ const ownService = resolveFastembedServiceLabel(currentRuntimeState().service);
565
+ const competing = detectCompetingFastembedHost(child.pid, this.poolGroup, ownService);
492
566
  const baseFields = {
493
567
  queue_depth: queueDepth,
494
- ...(competing ? { competing_host_pid: competing.pid } : {}),
568
+ ...(competing
569
+ ? {
570
+ competing_host_pid: competing.pid,
571
+ competing_host_service: competing.service ?? 'unknown',
572
+ }
573
+ : {}),
495
574
  // (BUG-EMBED-POOL-SIZE-DARWIN-FREEMEM-001) See the constructor's
496
575
  // `poolSize`/`memberIndex` doc comment: makes "was this process even
497
576
  // pooled, and at what size" a directly observable telemetry field
@@ -1371,4 +1450,35 @@ export function getSharedFastembedProcess() {
1371
1450
  export function __resetSharedFastembedProcessForTests() {
1372
1451
  _singleton = null;
1373
1452
  }
1453
+ /**
1454
+ * (BUG-MEMORYSERVER-EMBED-HEAL-NOOPERATOR-001) Kill AND clear the shared
1455
+ * fastembed host singleton, so the next `getSharedFastembedProcess()` forks a
1456
+ * genuinely fresh child process (and re-inits the model). This is the
1457
+ * embedding-provider half of memory-core's `reinitEmbedProvider()` — the
1458
+ * self-heal path that recovers a wedged fastembed child (BUG-021's
1459
+ * "Model not initialized" respawn-without-reinit state) by tearing the whole
1460
+ * shared host down and re-forking from scratch, rather than waiting for the
1461
+ * dead child to be reaped.
1462
+ *
1463
+ * Unlike `__resetSharedFastembedProcessForTests` (test-only, does NOT
1464
+ * terminate), this TERMINATES the existing singleton first — killing the child
1465
+ * process(es) via the normal `terminate()` shutdown sequence (BL-405's clean
1466
+ * `__shutdown` message, `kill()` fallback) — and only then drops the singleton
1467
+ * reference. Safe to call when no singleton has ever been constructed (no-op).
1468
+ * Any in-flight request is rejected with the standard termination error, which
1469
+ * the heal loop surfaces as a row failure — never a hang.
1470
+ */
1471
+ export async function resetSharedFastembedProcess() {
1472
+ const prev = _singleton;
1473
+ _singleton = null;
1474
+ if (prev) {
1475
+ try {
1476
+ await prev.terminate();
1477
+ }
1478
+ catch {
1479
+ // Terminate must never throw into the reset path — the singleton is
1480
+ // already dropped; a best-effort kill is all that remains.
1481
+ }
1482
+ }
1483
+ }
1374
1484
  //# sourceMappingURL=sharedFastembedProcess.js.map