@soulcraft/brainy 3.37.7 → 3.37.8
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.
|
@@ -342,8 +342,14 @@ export class GcsStorage extends BaseStorage {
|
|
|
342
342
|
contentType: 'application/json',
|
|
343
343
|
resumable: false // For small objects, non-resumable is faster
|
|
344
344
|
});
|
|
345
|
-
//
|
|
346
|
-
|
|
345
|
+
// CRITICAL FIX (v3.37.8): Only cache nodes with non-empty vectors
|
|
346
|
+
// This prevents cache pollution from HNSW's lazy-loading nodes (vector: [])
|
|
347
|
+
if (node.vector && Array.isArray(node.vector) && node.vector.length > 0) {
|
|
348
|
+
this.nounCacheManager.set(node.id, node);
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
prodLog.warn(`[saveNode] Not caching node ${node.id.substring(0, 8)}... with empty vector (HNSW lazy mode)`);
|
|
352
|
+
}
|
|
347
353
|
// Increment noun count
|
|
348
354
|
const metadata = await this.getNounMetadata(node.id);
|
|
349
355
|
if (metadata && metadata.type) {
|
|
@@ -387,7 +393,7 @@ export class GcsStorage extends BaseStorage {
|
|
|
387
393
|
async getNode(id) {
|
|
388
394
|
await this.ensureInitialized();
|
|
389
395
|
// Check cache first WITH LOGGING
|
|
390
|
-
const cached = this.nounCacheManager.get(id);
|
|
396
|
+
const cached = await this.nounCacheManager.get(id);
|
|
391
397
|
// DIAGNOSTIC LOGGING: Reveal cache poisoning
|
|
392
398
|
prodLog.info(`[getNode] 🔍 Cache check for ${id.substring(0, 8)}...:`, {
|
|
393
399
|
hasCached: cached !== undefined,
|
|
@@ -395,11 +401,38 @@ export class GcsStorage extends BaseStorage {
|
|
|
395
401
|
isObject: cached !== null && typeof cached === 'object',
|
|
396
402
|
type: typeof cached
|
|
397
403
|
});
|
|
398
|
-
// CRITICAL FIX:
|
|
404
|
+
// CRITICAL FIX (v3.37.8): Validate cached object before returning
|
|
399
405
|
if (cached !== undefined && cached !== null) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
406
|
+
// Log cached object structure to diagnose incomplete objects
|
|
407
|
+
prodLog.info(`[getNode] Cached object structure:`, {
|
|
408
|
+
hasId: !!cached.id,
|
|
409
|
+
idMatches: cached.id === id,
|
|
410
|
+
hasVector: !!cached.vector,
|
|
411
|
+
vectorLength: cached.vector?.length,
|
|
412
|
+
hasConnections: !!cached.connections,
|
|
413
|
+
connectionsType: typeof cached.connections,
|
|
414
|
+
hasLevel: cached.level !== undefined,
|
|
415
|
+
level: cached.level,
|
|
416
|
+
objectKeys: Object.keys(cached || {})
|
|
417
|
+
});
|
|
418
|
+
// Validate cached object has required fields (including non-empty vector!)
|
|
419
|
+
if (!cached.id || !cached.vector || !Array.isArray(cached.vector) || cached.vector.length === 0) {
|
|
420
|
+
prodLog.error(`[getNode] ❌ INVALID cached object for ${id.substring(0, 8)}...:`, {
|
|
421
|
+
reason: !cached.id ? 'missing id' :
|
|
422
|
+
!cached.vector ? 'missing vector' :
|
|
423
|
+
!Array.isArray(cached.vector) ? 'vector not array' :
|
|
424
|
+
cached.vector.length === 0 ? 'vector is empty array' :
|
|
425
|
+
'unknown'
|
|
426
|
+
});
|
|
427
|
+
prodLog.error(`[getNode] Removing invalid object from cache and loading from GCS`);
|
|
428
|
+
this.nounCacheManager.delete(id);
|
|
429
|
+
// Fall through to load from GCS
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
prodLog.info(`[getNode] ✅ Valid cached object - returning`);
|
|
433
|
+
this.logger.trace(`Cache hit for noun ${id}`);
|
|
434
|
+
return cached;
|
|
435
|
+
}
|
|
403
436
|
}
|
|
404
437
|
else if (cached === null) {
|
|
405
438
|
prodLog.warn(`[getNode] ⚠️ Cache contains NULL for ${id.substring(0, 8)}... - ignoring and loading from GCS`);
|
|
@@ -441,13 +474,13 @@ export class GcsStorage extends BaseStorage {
|
|
|
441
474
|
level: data.level || 0
|
|
442
475
|
// NO metadata field - retrieved separately for scalability
|
|
443
476
|
};
|
|
444
|
-
// CRITICAL FIX: Only cache valid nodes (never cache null)
|
|
445
|
-
if (node && node.id && node.vector && Array.isArray(node.vector)) {
|
|
477
|
+
// CRITICAL FIX: Only cache valid nodes with non-empty vectors (never cache null or empty)
|
|
478
|
+
if (node && node.id && node.vector && Array.isArray(node.vector) && node.vector.length > 0) {
|
|
446
479
|
this.nounCacheManager.set(id, node);
|
|
447
480
|
prodLog.info(`[getNode] 💾 Cached node ${id.substring(0, 8)}... successfully`);
|
|
448
481
|
}
|
|
449
482
|
else {
|
|
450
|
-
prodLog.warn(`[getNode] ⚠️ NOT caching invalid node for ${id.substring(0, 8)}
|
|
483
|
+
prodLog.warn(`[getNode] ⚠️ NOT caching invalid node for ${id.substring(0, 8)}... (missing id/vector or empty vector)`);
|
|
451
484
|
}
|
|
452
485
|
this.logger.trace(`Successfully retrieved node ${id}`);
|
|
453
486
|
this.releaseBackpressure(true, requestId);
|
|
@@ -827,11 +827,36 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
827
827
|
isObject: cached !== null && typeof cached === 'object',
|
|
828
828
|
type: typeof cached
|
|
829
829
|
});
|
|
830
|
-
// CRITICAL FIX:
|
|
830
|
+
// CRITICAL FIX (v3.37.8): Validate cached object before returning
|
|
831
831
|
if (cached !== undefined && cached !== null) {
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
832
|
+
// Log cached object structure to diagnose incomplete objects
|
|
833
|
+
prodLog.info(`[getNode] Cached object structure:`, {
|
|
834
|
+
hasId: !!cached.id,
|
|
835
|
+
idMatches: cached.id === id,
|
|
836
|
+
hasVector: !!cached.vector,
|
|
837
|
+
vectorLength: cached.vector?.length,
|
|
838
|
+
hasConnections: !!cached.connections,
|
|
839
|
+
connectionsType: typeof cached.connections,
|
|
840
|
+
objectKeys: Object.keys(cached || {})
|
|
841
|
+
});
|
|
842
|
+
// Validate cached object has required fields (including non-empty vector!)
|
|
843
|
+
if (!cached.id || !cached.vector || !Array.isArray(cached.vector) || cached.vector.length === 0) {
|
|
844
|
+
prodLog.error(`[getNode] ❌ INVALID cached object for ${id.substring(0, 8)}...:`, {
|
|
845
|
+
reason: !cached.id ? 'missing id' :
|
|
846
|
+
!cached.vector ? 'missing vector' :
|
|
847
|
+
!Array.isArray(cached.vector) ? 'vector not array' :
|
|
848
|
+
cached.vector.length === 0 ? 'vector is empty array' :
|
|
849
|
+
'unknown'
|
|
850
|
+
});
|
|
851
|
+
prodLog.error(`[getNode] Removing invalid object from cache and loading from S3`);
|
|
852
|
+
this.nodeCache.delete(id);
|
|
853
|
+
// Fall through to load from S3
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
prodLog.info(`[getNode] ✅ Valid cached object - returning`);
|
|
857
|
+
this.logger.trace(`Cache hit for node ${id}`);
|
|
858
|
+
return cached;
|
|
859
|
+
}
|
|
835
860
|
}
|
|
836
861
|
else if (cached === null) {
|
|
837
862
|
prodLog.warn(`[getNode] ⚠️ Cache contains NULL for ${id.substring(0, 8)}... - ignoring and loading from S3`);
|
|
@@ -889,13 +914,13 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
889
914
|
connections,
|
|
890
915
|
level: parsedNode.level || 0
|
|
891
916
|
};
|
|
892
|
-
// CRITICAL FIX: Only cache valid nodes (never cache null)
|
|
893
|
-
if (node && node.id && node.vector && Array.isArray(node.vector)) {
|
|
917
|
+
// CRITICAL FIX: Only cache valid nodes with non-empty vectors (never cache null or empty)
|
|
918
|
+
if (node && node.id && node.vector && Array.isArray(node.vector) && node.vector.length > 0) {
|
|
894
919
|
this.nodeCache.set(id, node);
|
|
895
920
|
prodLog.info(`[getNode] 💾 Cached node ${id.substring(0, 8)}... successfully`);
|
|
896
921
|
}
|
|
897
922
|
else {
|
|
898
|
-
prodLog.warn(`[getNode] ⚠️ NOT caching invalid node for ${id.substring(0, 8)}
|
|
923
|
+
prodLog.warn(`[getNode] ⚠️ NOT caching invalid node for ${id.substring(0, 8)}... (missing id/vector or empty vector)`);
|
|
899
924
|
}
|
|
900
925
|
this.logger.trace(`Successfully retrieved node ${id}`);
|
|
901
926
|
return node;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "3.37.
|
|
3
|
+
"version": "3.37.8",
|
|
4
4
|
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|