@soulcraft/cor 3.0.7 → 3.0.9
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/hnsw/NativeDiskAnnWrapper.d.ts +8 -0
- package/dist/hnsw/NativeDiskAnnWrapper.js +80 -10
- package/dist/native/index.d.ts +9 -1
- package/dist/native/index.js +38 -3
- package/dist/native/napi.js +13 -10
- package/dist/plugin.js +36 -19
- package/package.json +3 -3
|
@@ -209,6 +209,14 @@ export declare class NativeDiskAnnWrapper implements VectorIndexProvider {
|
|
|
209
209
|
private manifestNextId;
|
|
210
210
|
/** Segment-manifest cold-load memo (see {@link ensureSegments}). */
|
|
211
211
|
private segmentsLoaded;
|
|
212
|
+
/** Consecutive backpressure-flush failures (see
|
|
213
|
+
* {@link FLUSH_FAILURE_BACKPRESSURE_THRESHOLD}); reset on a clean flush. */
|
|
214
|
+
private consecutiveFlushFailures;
|
|
215
|
+
/** Consecutive background-consolidation failures + the earliest time the next
|
|
216
|
+
* attempt may fire — exponential backoff so a stranded consolidation is not
|
|
217
|
+
* re-triggered on every flush (CORTEX-CONSOLIDATION-SILENT-DROP). */
|
|
218
|
+
private consolidationFailures;
|
|
219
|
+
private consolidationCooldownUntil;
|
|
212
220
|
/**
|
|
213
221
|
* Canonical storage holds vectored nouns but NO durable vector index exists —
|
|
214
222
|
* the 7.x → 3.0 migration / restore-dir shape. Detected once per cold open by
|
|
@@ -110,6 +110,23 @@ const DELTA_HARD_CAP_BYTES = 256 * 1024 * 1024;
|
|
|
110
110
|
* consolidation reads them back via `readVectorChunk` — bounded RAM.
|
|
111
111
|
*/
|
|
112
112
|
const L0_COMPACT_THRESHOLD = 4;
|
|
113
|
+
/**
|
|
114
|
+
* Consecutive backpressure-flush failures before `addItem` stops swallowing and
|
|
115
|
+
* REFUSES writes (CORTEX-CONSOLIDATION-SILENT-DROP). A committed write must not
|
|
116
|
+
* fail on a transient post-commit blip — but if the flush PERSISTENTLY fails,
|
|
117
|
+
* the delta can never drain and accepting more writes grows it without bound
|
|
118
|
+
* until the process OOMs (which is what stranded memory's base). Past this
|
|
119
|
+
* count the brake engages: refuse loudly so the write-storm halts instead of
|
|
120
|
+
* crashing. A single successful flush resets the counter.
|
|
121
|
+
*/
|
|
122
|
+
const FLUSH_FAILURE_BACKPRESSURE_THRESHOLD = 4;
|
|
123
|
+
/**
|
|
124
|
+
* Cap on the exponential backoff between failed background consolidations
|
|
125
|
+
* (CORTEX-CONSOLIDATION-SILENT-DROP). A persistently-failing consolidation must
|
|
126
|
+
* not retry on every ~6 s flush (memory-vm hammered it for hours); back off up
|
|
127
|
+
* to this ceiling. A successful consolidation resets to zero.
|
|
128
|
+
*/
|
|
129
|
+
const CONSOLIDATION_BACKOFF_MAX_MS = 5 * 60_000;
|
|
113
130
|
/**
|
|
114
131
|
* Storage key of the vector segment manifest (flat key — slashed keys trip
|
|
115
132
|
* the storage adapters' unknown-key warning; same lesson as the metadata
|
|
@@ -311,6 +328,14 @@ export class NativeDiskAnnWrapper {
|
|
|
311
328
|
manifestNextId = 1;
|
|
312
329
|
/** Segment-manifest cold-load memo (see {@link ensureSegments}). */
|
|
313
330
|
segmentsLoaded = false;
|
|
331
|
+
/** Consecutive backpressure-flush failures (see
|
|
332
|
+
* {@link FLUSH_FAILURE_BACKPRESSURE_THRESHOLD}); reset on a clean flush. */
|
|
333
|
+
consecutiveFlushFailures = 0;
|
|
334
|
+
/** Consecutive background-consolidation failures + the earliest time the next
|
|
335
|
+
* attempt may fire — exponential backoff so a stranded consolidation is not
|
|
336
|
+
* re-triggered on every flush (CORTEX-CONSOLIDATION-SILENT-DROP). */
|
|
337
|
+
consolidationFailures = 0;
|
|
338
|
+
consolidationCooldownUntil = 0;
|
|
314
339
|
/**
|
|
315
340
|
* Canonical storage holds vectored nouns but NO durable vector index exists —
|
|
316
341
|
* the 7.x → 3.0 migration / restore-dir shape. Detected once per cold open by
|
|
@@ -426,17 +451,29 @@ export class NativeDiskAnnWrapper {
|
|
|
426
451
|
// its retirement filter and the serialized manifest saves keep the
|
|
427
452
|
// on-disk list consistent).
|
|
428
453
|
//
|
|
429
|
-
// A committed write must
|
|
454
|
+
// A committed write must not fail on a TRANSIENT post-commit blip — the
|
|
430
455
|
// caller would retry and double-write (MEMORY-COR-RANGE-ERROR isolation
|
|
431
|
-
// ask)
|
|
432
|
-
//
|
|
456
|
+
// ask), so a one-off flush failure is logged and the write is accepted,
|
|
457
|
+
// delta retained. BUT this is pre-delta-append backpressure: if the flush
|
|
458
|
+
// PERSISTENTLY fails the delta can never drain, and swallowing forever
|
|
459
|
+
// grows it without bound until OOM — which is exactly what stranded
|
|
460
|
+
// memory-vm's base (CORTEX-CONSOLIDATION-SILENT-DROP). Past the threshold
|
|
461
|
+
// the brake engages: refuse loudly so the write-storm halts. Recovers
|
|
462
|
+
// itself once a flush succeeds (self-heal drops a stranded base).
|
|
433
463
|
try {
|
|
434
464
|
await (this.flushInFlight ?? this.flushDeltaToL0());
|
|
465
|
+
this.consecutiveFlushFailures = 0;
|
|
435
466
|
}
|
|
436
467
|
catch (error) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
468
|
+
this.consecutiveFlushFailures++;
|
|
469
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
470
|
+
prodLog.error(`NativeDiskAnnWrapper: backpressure flush failed ` +
|
|
471
|
+
`(${this.consecutiveFlushFailures}× consecutively): ${msg}`);
|
|
472
|
+
if (this.consecutiveFlushFailures >= FLUSH_FAILURE_BACKPRESSURE_THRESHOLD) {
|
|
473
|
+
throw new Error(`NativeDiskAnnWrapper: vector index flush has failed ` +
|
|
474
|
+
`${this.consecutiveFlushFailures}× consecutively — refusing writes to ` +
|
|
475
|
+
`prevent unbounded delta growth (OOM). Last cause: ${msg}`);
|
|
476
|
+
}
|
|
440
477
|
}
|
|
441
478
|
}
|
|
442
479
|
if (this.tombstones.has(item.id)) {
|
|
@@ -933,6 +970,26 @@ export class NativeDiskAnnWrapper {
|
|
|
933
970
|
if (!NativeDiskANN) {
|
|
934
971
|
throw new Error('NativeDiskANN binding missing — rebuild requires the cor native module');
|
|
935
972
|
}
|
|
973
|
+
// **Self-heal a stranded base (CORTEX-CONSOLIDATION-SILENT-DROP).** We hold
|
|
974
|
+
// `this.native` as a live mmap handle to `indexPath`. On Linux that handle
|
|
975
|
+
// survives its file being unlinked — so if a crash interrupted a prior
|
|
976
|
+
// consolidation mid-write (native rebuild unlinks the old base to rewrite
|
|
977
|
+
// it), or an external move took the file, `this.native` stays truthy while
|
|
978
|
+
// the PATH is dead. Trusting the handle then makes `rebuildFromExistingAsync`
|
|
979
|
+
// open a path that no longer exists → `open existing index failed: No such
|
|
980
|
+
// file or directory`, looping every trigger with segments piling up
|
|
981
|
+
// unbounded. Drop the stale handle here so the fold below rebuilds from the
|
|
982
|
+
// durable L0 segments + canonical (the source of truth) and re-creates the
|
|
983
|
+
// base, instead of looping on a ghost. `existsSync` is O(1); this runs only
|
|
984
|
+
// on the (rare) consolidation path, never per-write.
|
|
985
|
+
if (this.native && !existsSync(this.config.indexPath)) {
|
|
986
|
+
prodLog.error(`NativeDiskAnnWrapper: durable base index missing under a live handle ` +
|
|
987
|
+
`(${this.config.indexPath}) — crash-stranded or externally moved. ` +
|
|
988
|
+
`Rebuilding from segments + canonical instead of looping on the dead path.`);
|
|
989
|
+
this.native = null;
|
|
990
|
+
// A rebuild is about to regenerate the base; clear any failed-load flag.
|
|
991
|
+
this.durableBaseLoadFailed = false;
|
|
992
|
+
}
|
|
936
993
|
// Build the new logical slot ordering: (live old slots) + (delta).
|
|
937
994
|
// **Critical for billion-scale correctness**: the old vectors stay
|
|
938
995
|
// mmap'd inside the native module — we only pass slot IDs across
|
|
@@ -1198,6 +1255,10 @@ export class NativeDiskAnnWrapper {
|
|
|
1198
1255
|
// The rebuild's durable output now covers canonical — the cold-open
|
|
1199
1256
|
// not-ready verdict (migration/restore dir) is satisfied.
|
|
1200
1257
|
this.canonicalUnindexed = false;
|
|
1258
|
+
// A successful fold clears the consolidation-failure backoff (any caller:
|
|
1259
|
+
// the auto-trigger, brainy's cold-open gate, or a host repairIndex).
|
|
1260
|
+
this.consolidationFailures = 0;
|
|
1261
|
+
this.consolidationCooldownUntil = 0;
|
|
1201
1262
|
}
|
|
1202
1263
|
/**
|
|
1203
1264
|
* Flush the delta buffer to disk. For DiskANN the delta is in-memory
|
|
@@ -1464,11 +1525,20 @@ export class NativeDiskAnnWrapper {
|
|
|
1464
1525
|
`(${this.l0Segments.length} live segments)`);
|
|
1465
1526
|
// Tiered maintenance: enough small segments → one BACKGROUND consolidation
|
|
1466
1527
|
// (the async rebuild folds base + L0s + delta into a fresh base off-thread).
|
|
1467
|
-
|
|
1528
|
+
// Guarded by an exponential backoff: a persistently-failing consolidation
|
|
1529
|
+
// must not re-fire on every ~6 s flush (memory-vm hammered it for hours) —
|
|
1530
|
+
// it backs off up to CONSOLIDATION_BACKOFF_MAX_MS, and a success resets it
|
|
1531
|
+
// (see the tail of runRebuild). CORTEX-CONSOLIDATION-SILENT-DROP.
|
|
1532
|
+
if (this.l0Segments.length >= L0_COMPACT_THRESHOLD &&
|
|
1533
|
+
!this.rebuildInFlight &&
|
|
1534
|
+
Date.now() >= this.consolidationCooldownUntil) {
|
|
1468
1535
|
this.rebuild().catch((error) => {
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1536
|
+
this.consolidationFailures++;
|
|
1537
|
+
const backoffMs = Math.min(CONSOLIDATION_BACKOFF_MAX_MS, 1000 * 2 ** Math.min(this.consolidationFailures, 8));
|
|
1538
|
+
this.consolidationCooldownUntil = Date.now() + backoffMs;
|
|
1539
|
+
prodLog.error(`NativeDiskAnnWrapper: background consolidation failed ` +
|
|
1540
|
+
`(${this.consolidationFailures}× — next attempt in ~${Math.round(backoffMs / 1000)}s; ` +
|
|
1541
|
+
`segments retained): ${error instanceof Error ? error.message : String(error)}`);
|
|
1472
1542
|
});
|
|
1473
1543
|
}
|
|
1474
1544
|
}
|
package/dist/native/index.d.ts
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* Exports the unified NativeBindings interface that all consumers use.
|
|
9
9
|
*/
|
|
10
10
|
import type { NativeBindings } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Diagnostic accessor for the native-module load failure. `null` when the
|
|
13
|
+
* module loaded successfully or no load has been attempted yet.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getNativeLoadError(): Error | null;
|
|
11
16
|
/**
|
|
12
17
|
* Open-file-limit raise result, captured once when the native module
|
|
13
18
|
* first loads. `null` until then (or if the platform/loader doesn't
|
|
@@ -39,7 +44,10 @@ export declare function getOpenFileLimitInfo(): OpenFileLimitInfo | null;
|
|
|
39
44
|
export declare function loadNativeModule(): NativeBindings;
|
|
40
45
|
/**
|
|
41
46
|
* Check if the native module is available.
|
|
42
|
-
* Does not throw
|
|
47
|
+
* Does not throw — returns false if loading fails, and logs the underlying
|
|
48
|
+
* load error ONCE per process. cor exists to accelerate; a native module
|
|
49
|
+
* that fails to load must never be a silent no (the dlopen cause — e.g. a
|
|
50
|
+
* glibc version mismatch — is the only actionable diagnostic).
|
|
43
51
|
*/
|
|
44
52
|
export declare function isNativeAvailable(): boolean;
|
|
45
53
|
export type { NativeBindings, NativeEmbeddingEngineInstance, NativeRoaringBitmap32Instance, EmbeddingResult, EngineStats, DeviceInfo, } from './types.js';
|
package/dist/native/index.js
CHANGED
|
@@ -11,6 +11,25 @@ import { loadViaNapi } from './napi.js';
|
|
|
11
11
|
import { loadViaFFI } from './ffi.js';
|
|
12
12
|
// Cached module reference
|
|
13
13
|
let cachedModule = null;
|
|
14
|
+
/**
|
|
15
|
+
* The error from the most recent failed native-module load, or `null` if the
|
|
16
|
+
* module loaded (or was never attempted). Carries the underlying dlopen
|
|
17
|
+
* message — e.g. a glibc version mismatch — which is the only actionable
|
|
18
|
+
* diagnostic when native acceleration silently fails to engage
|
|
19
|
+
* (ACC-COR-SILENT-NATIVE-FAIL).
|
|
20
|
+
*/
|
|
21
|
+
let lastLoadError = null;
|
|
22
|
+
/** One-shot latch so the load-failure warning prints once per process, not
|
|
23
|
+
* once per {@link isNativeAvailable} probe (benchmarks and test gates call
|
|
24
|
+
* it repeatedly). */
|
|
25
|
+
let loadFailureWarned = false;
|
|
26
|
+
/**
|
|
27
|
+
* Diagnostic accessor for the native-module load failure. `null` when the
|
|
28
|
+
* module loaded successfully or no load has been attempted yet.
|
|
29
|
+
*/
|
|
30
|
+
export function getNativeLoadError() {
|
|
31
|
+
return lastLoadError;
|
|
32
|
+
}
|
|
14
33
|
let fileLimitInfo = null;
|
|
15
34
|
/**
|
|
16
35
|
* Raise the process's soft open-file limit toward its hard cap, once.
|
|
@@ -56,6 +75,7 @@ export function loadNativeModule() {
|
|
|
56
75
|
try {
|
|
57
76
|
cachedModule = loadViaFFI();
|
|
58
77
|
raiseFileLimitOnce(cachedModule);
|
|
78
|
+
lastLoadError = null;
|
|
59
79
|
return cachedModule;
|
|
60
80
|
}
|
|
61
81
|
catch {
|
|
@@ -63,20 +83,35 @@ export function loadNativeModule() {
|
|
|
63
83
|
}
|
|
64
84
|
}
|
|
65
85
|
// Strategy 2: All platforms (napi-rs)
|
|
66
|
-
|
|
86
|
+
try {
|
|
87
|
+
cachedModule = loadViaNapi();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
lastLoadError = err instanceof Error ? err : new Error(String(err));
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
67
93
|
raiseFileLimitOnce(cachedModule);
|
|
94
|
+
lastLoadError = null;
|
|
68
95
|
return cachedModule;
|
|
69
96
|
}
|
|
70
97
|
/**
|
|
71
98
|
* Check if the native module is available.
|
|
72
|
-
* Does not throw
|
|
99
|
+
* Does not throw — returns false if loading fails, and logs the underlying
|
|
100
|
+
* load error ONCE per process. cor exists to accelerate; a native module
|
|
101
|
+
* that fails to load must never be a silent no (the dlopen cause — e.g. a
|
|
102
|
+
* glibc version mismatch — is the only actionable diagnostic).
|
|
73
103
|
*/
|
|
74
104
|
export function isNativeAvailable() {
|
|
75
105
|
try {
|
|
76
106
|
loadNativeModule();
|
|
77
107
|
return true;
|
|
78
108
|
}
|
|
79
|
-
catch {
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (!loadFailureWarned) {
|
|
111
|
+
loadFailureWarned = true;
|
|
112
|
+
console.warn(`[cor] native module failed to load — acceleration unavailable: ` +
|
|
113
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
114
|
+
}
|
|
80
115
|
return false;
|
|
81
116
|
}
|
|
82
117
|
}
|
package/dist/native/napi.js
CHANGED
|
@@ -46,30 +46,33 @@ export function loadViaNapi() {
|
|
|
46
46
|
// This file lives at dist/native/napi.js → package root is ../../
|
|
47
47
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
48
48
|
const nativeDir = join(__dirname, '..', '..', 'native');
|
|
49
|
+
// Each strategy's real failure is captured and carried in the final throw:
|
|
50
|
+
// the underlying dlopen error (e.g. "GLIBC_2.39 not found" on an older
|
|
51
|
+
// distro) is the ONLY actionable diagnostic, and swallowing it produced
|
|
52
|
+
// native-silently-off deployments (ACC-COR-SILENT-NATIVE-FAIL).
|
|
53
|
+
const failures = [];
|
|
49
54
|
// Strategy 1: Bundled platform binary (shipped in npm package)
|
|
55
|
+
const binaryPath = join(nativeDir, `brainy-native.${platformKey}.node`);
|
|
50
56
|
try {
|
|
51
|
-
const binaryPath = join(nativeDir, `brainy-native.${platformKey}.node`);
|
|
52
57
|
const bindings = require(binaryPath);
|
|
53
58
|
cachedBindings = bindings;
|
|
54
59
|
return bindings;
|
|
55
60
|
}
|
|
56
|
-
catch {
|
|
57
|
-
|
|
61
|
+
catch (err) {
|
|
62
|
+
failures.push(` - ${binaryPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
63
|
}
|
|
59
64
|
// Strategy 2: Local dev build (no platform suffix)
|
|
65
|
+
const devPath = join(nativeDir, 'brainy-native.node');
|
|
60
66
|
try {
|
|
61
|
-
const devPath = join(nativeDir, 'brainy-native.node');
|
|
62
67
|
const bindings = require(devPath);
|
|
63
68
|
cachedBindings = bindings;
|
|
64
69
|
return bindings;
|
|
65
70
|
}
|
|
66
|
-
catch {
|
|
67
|
-
|
|
71
|
+
catch (err) {
|
|
72
|
+
failures.push(` - ${devPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
68
73
|
}
|
|
69
|
-
throw new Error(`Failed to load Brainy native module for ${key}
|
|
70
|
-
|
|
71
|
-
` - native/brainy-native.${platformKey}.node (bundled binary)\n` +
|
|
72
|
-
` - native/brainy-native.node (local dev build)\n` +
|
|
74
|
+
throw new Error(`Failed to load Brainy native module for ${key}:\n` +
|
|
75
|
+
`${failures.join('\n')}\n` +
|
|
73
76
|
`Build locally: cd native && npm run build`);
|
|
74
77
|
}
|
|
75
78
|
/**
|
package/dist/plugin.js
CHANGED
|
@@ -42,12 +42,27 @@
|
|
|
42
42
|
* with a single code path (in-memory / hybrid / on-disk modes
|
|
43
43
|
* selected at brain-open time from observed memory).
|
|
44
44
|
*/
|
|
45
|
-
import { loadNativeModule, isNativeAvailable } from './native/index.js';
|
|
45
|
+
import { loadNativeModule, isNativeAvailable, getNativeLoadError } from './native/index.js';
|
|
46
46
|
import { validateLicense, readLicenseToken, readConfig, isShortCode } from './license.js';
|
|
47
47
|
import { MigrationCoordinator } from './migration/MigrationCoordinator.js';
|
|
48
48
|
import { prodLog } from '@soulcraft/brainy/internals';
|
|
49
49
|
import { setEntityCountSource } from './license/onlineValidator.js';
|
|
50
50
|
const PRICING_URL = 'https://soulcraft.com/pricing?focus=pro';
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the RAW configured license value with the env-then-file precedence
|
|
53
|
+
* `readLicenseToken()` uses (env `COR_LICENSE_KEY` / legacy `CORTEX_LICENSE_KEY`
|
|
54
|
+
* wins over `.soulcraft.json`, dual-reading the new `cor` key and the legacy
|
|
55
|
+
* `cortex` key), WITHOUT exchanging short codes — callers need the raw shape
|
|
56
|
+
* (is anything configured at all? is it a short code?). `undefined` when no
|
|
57
|
+
* license is configured anywhere.
|
|
58
|
+
*/
|
|
59
|
+
async function resolveRawLicenseValue() {
|
|
60
|
+
const envValue = (process.env.COR_LICENSE_KEY ?? process.env.CORTEX_LICENSE_KEY)?.trim();
|
|
61
|
+
if (envValue)
|
|
62
|
+
return envValue;
|
|
63
|
+
const config = await readConfig();
|
|
64
|
+
return config.cor ?? config.cortex;
|
|
65
|
+
}
|
|
51
66
|
// `brainyRange` engages brainy 8.0's version-coupling gate (handoff LV.3): a
|
|
52
67
|
// brainy outside this range fails loud at init() instead of silently dropping to
|
|
53
68
|
// the JS engine. Typed via intersection until the devDependency moves to brainy
|
|
@@ -91,8 +106,21 @@ const corPlugin = {
|
|
|
91
106
|
// storage. On a cloud adapter the compute providers fail at
|
|
92
107
|
// init with a clear error rather than silently degrading.
|
|
93
108
|
// Brainy continues to work unaccelerated on any adapter.
|
|
94
|
-
// Gate: native module must be available for compute acceleration
|
|
109
|
+
// Gate: native module must be available for compute acceleration.
|
|
110
|
+
// A silent return here with a license configured is the worst failure
|
|
111
|
+
// shape cor has: the operator PAID for acceleration, the module didn't
|
|
112
|
+
// load (wrong glibc, missing binary), and every provider quietly ran
|
|
113
|
+
// brainy-JS with no journal line (ACC-COR-SILENT-NATIVE-FAIL — accord-vm
|
|
114
|
+
// ran 0/10 native for a day). isNativeAvailable() logs the load error
|
|
115
|
+
// once; this adds the license-aware alarm.
|
|
95
116
|
if (!isNativeAvailable()) {
|
|
117
|
+
const configured = await resolveRawLicenseValue();
|
|
118
|
+
if (configured) {
|
|
119
|
+
const cause = getNativeLoadError();
|
|
120
|
+
console.warn(`[cor] a license is configured but the native module FAILED TO LOAD — ` +
|
|
121
|
+
`compute acceleration is DISABLED and brainy runs on its JS engines. ` +
|
|
122
|
+
`Cause: ${cause?.message ?? 'unknown (no load error recorded)'}`);
|
|
123
|
+
}
|
|
96
124
|
return true;
|
|
97
125
|
}
|
|
98
126
|
// Gate: license tier determines compute access
|
|
@@ -108,23 +136,12 @@ const corPlugin = {
|
|
|
108
136
|
// The CLI now writes JWTs directly (sc_cor_...), so short codes in .soulcraft.json
|
|
109
137
|
// only appear in old or manually-written files. Exchange at runtime is a fallback path.
|
|
110
138
|
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
// network blip — the exact gap this fixes.
|
|
118
|
-
const envValue = (process.env.COR_LICENSE_KEY ?? process.env.CORTEX_LICENSE_KEY)?.trim();
|
|
119
|
-
let licenseValue;
|
|
120
|
-
if (envValue) {
|
|
121
|
-
licenseValue = envValue;
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const config = await readConfig();
|
|
125
|
-
// Dual-read during the cortex→cor rename: new `cor` key, legacy `cortex`.
|
|
126
|
-
licenseValue = config.cor ?? config.cortex;
|
|
127
|
-
}
|
|
139
|
+
// We can't reuse readLicenseToken() here because it exchanges short codes
|
|
140
|
+
// into JWTs (or returns null on a failed exchange), erasing the
|
|
141
|
+
// short-code shape this branch needs to test. Reading the file alone
|
|
142
|
+
// would drop offline grace for an env-supplied short code on a network
|
|
143
|
+
// blip — the exact gap this fixes.
|
|
144
|
+
const licenseValue = await resolveRawLicenseValue();
|
|
128
145
|
if (!licenseValue) {
|
|
129
146
|
return true; // No license configured — free tier, no warning
|
|
130
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/cor",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
4
4
|
"description": "Native Rust acceleration for Brainy — SIMD distance, vector quantization, zero-copy mmap, native embeddings. Free tier for storage, Pro license for compute acceleration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"build:native": "cd native && napi build --release",
|
|
23
23
|
"build:native:dev": "cd native && napi build",
|
|
24
24
|
"prepublishOnly": "npm run build && cd native && napi prepublish -t npm",
|
|
25
|
-
"test": "vitest run",
|
|
25
|
+
"test": "vitest run --exclude src/native/lsmPerfGates.test.ts && vitest run --no-file-parallelism src/native/lsmPerfGates.test.ts",
|
|
26
26
|
"bench": "vitest bench",
|
|
27
27
|
"bench:compare": "npx tsx src/benchmarks/comparison.ts",
|
|
28
28
|
"test:ci-native": "node --input-type=module -e \"import { loadNativeModule } from './dist/native/index.js'; const n = loadNativeModule(); console.log('Native module loaded:', Object.keys(n).length, 'exports');\"",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@napi-rs/cli": "^3.0.0",
|
|
85
|
-
"@soulcraft/brainy": "8.
|
|
85
|
+
"@soulcraft/brainy": "8.2.2",
|
|
86
86
|
"@types/node": "^22.0.0",
|
|
87
87
|
"pg": "^8.21.0",
|
|
88
88
|
"tsx": "^4.21.0",
|