@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
- // Update cache
346
- this.nounCacheManager.set(node.id, node);
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: Only return cached value if it's valid (not null/undefined)
404
+ // CRITICAL FIX (v3.37.8): Validate cached object before returning
399
405
  if (cached !== undefined && cached !== null) {
400
- prodLog.info(`[getNode] Cache HIT - returning cached node for ${id.substring(0, 8)}...`);
401
- this.logger.trace(`Cache hit for noun ${id}`);
402
- return cached;
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: Only return cached value if it's valid (not null/undefined)
830
+ // CRITICAL FIX (v3.37.8): Validate cached object before returning
831
831
  if (cached !== undefined && cached !== null) {
832
- prodLog.info(`[getNode] Cache HIT - returning cached node for ${id.substring(0, 8)}...`);
833
- this.logger.trace(`Cache hit for node ${id}`);
834
- return cached;
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.7",
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",