@soulcraft/brainy 3.49.0 → 3.50.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/coreTypes.d.ts +17 -1
  3. package/dist/neural/embeddedTypeEmbeddings.d.ts +1 -1
  4. package/dist/neural/embeddedTypeEmbeddings.js +2 -2
  5. package/dist/storage/adapters/baseStorageAdapter.d.ts +13 -13
  6. package/dist/storage/adapters/fileSystemStorage.js +25 -6
  7. package/dist/storage/adapters/gcsStorage.js +17 -5
  8. package/dist/storage/adapters/memoryStorage.js +17 -9
  9. package/dist/storage/adapters/opfsStorage.js +25 -6
  10. package/dist/storage/adapters/r2Storage.js +17 -2
  11. package/dist/storage/adapters/s3CompatibleStorage.js +14 -2
  12. package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +11 -1
  13. package/dist/storage/adapters/typeAwareStorageAdapter.js +25 -16
  14. package/dist/storage/baseStorage.d.ts +7 -0
  15. package/dist/storage/baseStorage.js +47 -28
  16. package/dist/utils/fieldTypeInference.d.ts +181 -0
  17. package/dist/utils/fieldTypeInference.js +420 -0
  18. package/dist/utils/metadataIndex.d.ts +11 -1
  19. package/dist/utils/metadataIndex.js +67 -18
  20. package/dist/utils/metadataIndexChunking.d.ts +7 -0
  21. package/dist/utils/metadataIndexChunking.js +14 -0
  22. package/package.json +1 -1
  23. package/dist/augmentations/KnowledgeAugmentation.d.ts +0 -40
  24. package/dist/augmentations/KnowledgeAugmentation.js +0 -251
  25. package/dist/query/typeInference.d.ts +0 -158
  26. package/dist/query/typeInference.js +0 -760
  27. package/dist/types/brainyDataInterface.d.ts +0 -52
  28. package/dist/types/brainyDataInterface.js +0 -10
  29. package/dist/vfs/ConceptSystem.d.ts +0 -203
  30. package/dist/vfs/ConceptSystem.js +0 -545
  31. package/dist/vfs/EntityManager.d.ts +0 -75
  32. package/dist/vfs/EntityManager.js +0 -216
  33. package/dist/vfs/EventRecorder.d.ts +0 -84
  34. package/dist/vfs/EventRecorder.js +0 -269
  35. package/dist/vfs/GitBridge.d.ts +0 -167
  36. package/dist/vfs/GitBridge.js +0 -537
  37. package/dist/vfs/KnowledgeLayer.d.ts +0 -35
  38. package/dist/vfs/KnowledgeLayer.js +0 -443
  39. package/dist/vfs/PersistentEntitySystem.d.ts +0 -165
  40. package/dist/vfs/PersistentEntitySystem.js +0 -503
  41. package/dist/vfs/SemanticVersioning.d.ts +0 -105
  42. package/dist/vfs/SemanticVersioning.js +0 -309
package/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/soulcraftlabs/standard-version) for commit guidelines.
4
4
 
5
+ ### [3.50.1](https://github.com/soulcraftlabs/brainy/compare/v3.50.0...v3.50.1) (2025-10-16)
6
+
7
+ ### 🐛 Critical Bug Fixes
8
+
9
+ **Fixed: Metadata Explosion Bug - 69K Files Reduced to ~1K**
10
+
11
+ **Issue**: Metadata indexing was creating 60+ chunk files per entity (69,429 files for 1,143 entities)
12
+ - Root cause: Vector embeddings (384-dimensional arrays) were being indexed in metadata
13
+ - Each vector dimension created a separate chunk file with numeric field names
14
+ - Caused server hangs, VFS operations timing out, and Graph View UI failures
15
+
16
+ **Impact**:
17
+ - ✅ File reduction: 69,429 → ~1,200 files (58x reduction / 1,200x per entity)
18
+ - ✅ Storage reduction: 3.3GB → ~10MB metadata (330x reduction)
19
+ - ✅ Fixes server initialization hangs (loading 69K files)
20
+ - ✅ Fixes metadata batch loading stalling at batch 23
21
+ - ✅ Fixes VFS getDescendants() hanging indefinitely
22
+ - ✅ Fixes Graph View UI not loading in Soulcraft Studio
23
+
24
+ **Solution**:
25
+ - Added `NEVER_INDEX` Set excluding vector field names: `['vector', 'embedding', 'embeddings', 'connections']`
26
+ - Added safety check to skip arrays > 10 elements
27
+ - Preserves small array indexing (tags, categories, roles)
28
+
29
+ **Test Results**:
30
+ - ✅ 7/7 integration tests passing
31
+ - ✅ Verified: 6 chunk files for 10 entities (was 7,210 before fix)
32
+ - ✅ 611/622 unit tests passing
33
+
34
+ **Files Modified**:
35
+ - `src/utils/metadataIndex.ts` - Core metadata explosion fix
36
+ - `src/coreTypes.ts` - HNSWVerb type enforcement with VerbType enum
37
+ - `src/storage/adapters/*` - Include core relational fields (verb, sourceId, targetId)
38
+ - `src/storage/adapters/baseStorageAdapter.ts` - Type enforcement (HNSWNoun, GraphVerb)
39
+ - `tests/integration/metadata-vector-exclusion.test.ts` - Comprehensive test coverage
40
+
41
+ ---
42
+
5
43
  ### [3.47.0](https://github.com/soulcraftlabs/brainy/compare/v3.46.0...v3.47.0) (2025-10-15)
6
44
 
7
45
  ### ✨ Features
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Type definitions for the Soulcraft Brainy
3
3
  */
4
+ import type { VerbType } from './types/graphTypes.js';
4
5
  /**
5
6
  * Vector representation - an array of numbers
6
7
  */
@@ -76,12 +77,27 @@ export interface HNSWNoun {
76
77
  }
77
78
  /**
78
79
  * Lightweight verb for HNSW index storage
79
- * Contains only essential data needed for vector operations
80
+ * Contains essential data including core relational fields
81
+ *
82
+ * ARCHITECTURAL FIX (v3.50.1): verb/sourceId/targetId are now first-class fields
83
+ * These are NOT metadata - they're the essence of what a verb IS:
84
+ * - verb: The relationship type (creates, contains, etc.) - needed for routing & display
85
+ * - sourceId: What entity this verb connects FROM - needed for graph traversal
86
+ * - targetId: What entity this verb connects TO - needed for graph traversal
87
+ *
88
+ * Benefits:
89
+ * - ONE file read instead of two for 90% of operations
90
+ * - No type caching needed (type is always available)
91
+ * - Faster graph traversal (source/target immediately available)
92
+ * - Aligns with actual usage patterns
80
93
  */
81
94
  export interface HNSWVerb {
82
95
  id: string;
83
96
  vector: Vector;
84
97
  connections: Map<number, Set<string>>;
98
+ verb: VerbType;
99
+ sourceId: string;
100
+ targetId: string;
85
101
  metadata?: any;
86
102
  }
87
103
  /**
@@ -2,7 +2,7 @@
2
2
  * 🧠 BRAINY EMBEDDED TYPE EMBEDDINGS
3
3
  *
4
4
  * AUTO-GENERATED - DO NOT EDIT
5
- * Generated: 2025-10-15T19:24:11.910Z
5
+ * Generated: 2025-10-16T20:17:08.371Z
6
6
  * Noun Types: 31
7
7
  * Verb Types: 40
8
8
  *
@@ -2,7 +2,7 @@
2
2
  * 🧠 BRAINY EMBEDDED TYPE EMBEDDINGS
3
3
  *
4
4
  * AUTO-GENERATED - DO NOT EDIT
5
- * Generated: 2025-10-15T19:24:11.910Z
5
+ * Generated: 2025-10-16T20:17:08.371Z
6
6
  * Noun Types: 31
7
7
  * Verb Types: 40
8
8
  *
@@ -15,7 +15,7 @@ export const TYPE_METADATA = {
15
15
  verbTypes: 40,
16
16
  totalTypes: 71,
17
17
  embeddingDimensions: 384,
18
- generatedAt: "2025-10-15T19:24:11.910Z",
18
+ generatedAt: "2025-10-16T20:17:08.371Z",
19
19
  sizeBytes: {
20
20
  embeddings: 109056,
21
21
  base64: 145408
@@ -2,21 +2,21 @@
2
2
  * Base Storage Adapter
3
3
  * Provides common functionality for all storage adapters, including statistics tracking
4
4
  */
5
- import { StatisticsData, StorageAdapter } from '../../coreTypes.js';
5
+ import { StatisticsData, StorageAdapter, HNSWNoun, GraphVerb } from '../../coreTypes.js';
6
6
  /**
7
7
  * Base class for storage adapters that implements statistics tracking
8
8
  */
9
9
  export declare abstract class BaseStorageAdapter implements StorageAdapter {
10
10
  abstract init(): Promise<void>;
11
- abstract saveNoun(noun: any): Promise<void>;
12
- abstract getNoun(id: string): Promise<any | null>;
13
- abstract getNounsByNounType(nounType: string): Promise<any[]>;
11
+ abstract saveNoun(noun: HNSWNoun): Promise<void>;
12
+ abstract getNoun(id: string): Promise<HNSWNoun | null>;
13
+ abstract getNounsByNounType(nounType: string): Promise<HNSWNoun[]>;
14
14
  abstract deleteNoun(id: string): Promise<void>;
15
- abstract saveVerb(verb: any): Promise<void>;
16
- abstract getVerb(id: string): Promise<any | null>;
17
- abstract getVerbsBySource(sourceId: string): Promise<any[]>;
18
- abstract getVerbsByTarget(targetId: string): Promise<any[]>;
19
- abstract getVerbsByType(type: string): Promise<any[]>;
15
+ abstract saveVerb(verb: GraphVerb): Promise<void>;
16
+ abstract getVerb(id: string): Promise<GraphVerb | null>;
17
+ abstract getVerbsBySource(sourceId: string): Promise<GraphVerb[]>;
18
+ abstract getVerbsByTarget(targetId: string): Promise<GraphVerb[]>;
19
+ abstract getVerbsByType(type: string): Promise<GraphVerb[]>;
20
20
  abstract deleteVerb(id: string): Promise<void>;
21
21
  abstract saveMetadata(id: string, metadata: any): Promise<void>;
22
22
  abstract getMetadata(id: string): Promise<any | null>;
@@ -64,7 +64,7 @@ export declare abstract class BaseStorageAdapter implements StorageAdapter {
64
64
  metadata?: Record<string, any>;
65
65
  };
66
66
  }): Promise<{
67
- items: any[];
67
+ items: HNSWNoun[];
68
68
  totalCount?: number;
69
69
  hasMore: boolean;
70
70
  nextCursor?: string;
@@ -88,7 +88,7 @@ export declare abstract class BaseStorageAdapter implements StorageAdapter {
88
88
  metadata?: Record<string, any>;
89
89
  };
90
90
  }): Promise<{
91
- items: any[];
91
+ items: GraphVerb[];
92
92
  totalCount?: number;
93
93
  hasMore: boolean;
94
94
  nextCursor?: string;
@@ -108,7 +108,7 @@ export declare abstract class BaseStorageAdapter implements StorageAdapter {
108
108
  metadata?: Record<string, any>;
109
109
  };
110
110
  }): Promise<{
111
- items: any[];
111
+ items: HNSWNoun[];
112
112
  totalCount?: number;
113
113
  hasMore: boolean;
114
114
  nextCursor?: string;
@@ -130,7 +130,7 @@ export declare abstract class BaseStorageAdapter implements StorageAdapter {
130
130
  metadata?: Record<string, any>;
131
131
  };
132
132
  }): Promise<{
133
- items: any[];
133
+ items: GraphVerb[];
134
134
  totalCount?: number;
135
135
  hasMore: boolean;
136
136
  nextCursor?: string;
@@ -341,13 +341,18 @@ export class FileSystemStorage extends BaseStorage {
341
341
  // Check if this is a new edge to update counts
342
342
  const isNew = !(await this.fileExists(this.getVerbPath(edge.id)));
343
343
  // Convert connections Map to a serializable format
344
- // CRITICAL: Only save lightweight vector data (no metadata)
345
- // Metadata is saved separately via saveVerbMetadata() (2-file system)
344
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields in verb vector file
345
+ // These fields are essential for 90% of operations - no metadata lookup needed
346
346
  const serializableEdge = {
347
347
  id: edge.id,
348
348
  vector: edge.vector,
349
- connections: this.mapToObject(edge.connections, (set) => Array.from(set))
350
- // NO metadata field - saved separately for scalability
349
+ connections: this.mapToObject(edge.connections, (set) => Array.from(set)),
350
+ // CORE RELATIONAL DATA (v3.50.1+)
351
+ verb: edge.verb,
352
+ sourceId: edge.sourceId,
353
+ targetId: edge.targetId,
354
+ // User metadata (if any) - saved separately for scalability
355
+ // metadata field is saved separately via saveVerbMetadata()
351
356
  };
352
357
  const filePath = this.getVerbPath(edge.id);
353
358
  await this.ensureDirectoryExists(path.dirname(filePath));
@@ -375,10 +380,17 @@ export class FileSystemStorage extends BaseStorage {
375
380
  for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) {
376
381
  connections.set(Number(level), new Set(nodeIds));
377
382
  }
383
+ // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
378
384
  return {
379
385
  id: parsedEdge.id,
380
386
  vector: parsedEdge.vector,
381
- connections
387
+ connections,
388
+ // CORE RELATIONAL DATA (read from vector file)
389
+ verb: parsedEdge.verb,
390
+ sourceId: parsedEdge.sourceId,
391
+ targetId: parsedEdge.targetId,
392
+ // User metadata (retrieved separately via getVerbMetadata())
393
+ metadata: parsedEdge.metadata
382
394
  };
383
395
  }
384
396
  catch (error) {
@@ -411,10 +423,17 @@ export class FileSystemStorage extends BaseStorage {
411
423
  for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) {
412
424
  connections.set(Number(level), new Set(nodeIds));
413
425
  }
426
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields
414
427
  allEdges.push({
415
428
  id: parsedEdge.id,
416
429
  vector: parsedEdge.vector,
417
- connections
430
+ connections,
431
+ // CORE RELATIONAL DATA
432
+ verb: parsedEdge.verb,
433
+ sourceId: parsedEdge.sourceId,
434
+ targetId: parsedEdge.targetId,
435
+ // User metadata
436
+ metadata: parsedEdge.metadata
418
437
  });
419
438
  }
420
439
  }
@@ -636,16 +636,21 @@ export class GcsStorage extends BaseStorage {
636
636
  try {
637
637
  this.logger.trace(`Saving edge ${edge.id}`);
638
638
  // Convert connections Map to serializable format
639
- // CRITICAL: Only save lightweight vector data (no metadata)
640
- // Metadata is saved separately via saveVerbMetadata() (2-file system)
639
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields in verb vector file
640
+ // These fields are essential for 90% of operations - no metadata lookup needed
641
641
  const serializableEdge = {
642
642
  id: edge.id,
643
643
  vector: edge.vector,
644
644
  connections: Object.fromEntries(Array.from(edge.connections.entries()).map(([level, verbIds]) => [
645
645
  level,
646
646
  Array.from(verbIds)
647
- ]))
648
- // NO metadata field - saved separately for scalability
647
+ ])),
648
+ // CORE RELATIONAL DATA (v3.50.1+)
649
+ verb: edge.verb,
650
+ sourceId: edge.sourceId,
651
+ targetId: edge.targetId,
652
+ // User metadata (if any) - saved separately for scalability
653
+ // metadata field is saved separately via saveVerbMetadata()
649
654
  };
650
655
  // Get the GCS key with UUID-based sharding
651
656
  const key = this.getVerbKey(edge.id);
@@ -719,10 +724,17 @@ export class GcsStorage extends BaseStorage {
719
724
  for (const [level, verbIds] of Object.entries(data.connections || {})) {
720
725
  connections.set(Number(level), new Set(verbIds));
721
726
  }
727
+ // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
722
728
  const edge = {
723
729
  id: data.id,
724
730
  vector: data.vector,
725
- connections
731
+ connections,
732
+ // CORE RELATIONAL DATA (read from vector file)
733
+ verb: data.verb,
734
+ sourceId: data.sourceId,
735
+ targetId: data.targetId,
736
+ // User metadata (retrieved separately via getVerbMetadata())
737
+ metadata: data.metadata
726
738
  };
727
739
  // Update cache
728
740
  this.verbCacheManager.set(id, edge);
@@ -226,10 +226,17 @@ export class MemoryStorage extends BaseStorage {
226
226
  async saveVerb_internal(verb) {
227
227
  const isNew = !this.verbs.has(verb.id);
228
228
  // Create a deep copy to avoid reference issues
229
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields
229
230
  const verbCopy = {
230
231
  id: verb.id,
231
232
  vector: [...verb.vector],
232
- connections: new Map()
233
+ connections: new Map(),
234
+ // CORE RELATIONAL DATA
235
+ verb: verb.verb,
236
+ sourceId: verb.sourceId,
237
+ targetId: verb.targetId,
238
+ // User metadata (if any)
239
+ metadata: verb.metadata
233
240
  };
234
241
  // Copy connections
235
242
  for (const [level, connections] of verb.connections.entries()) {
@@ -252,22 +259,23 @@ export class MemoryStorage extends BaseStorage {
252
259
  return null;
253
260
  }
254
261
  // Return a deep copy of the HNSWVerb
262
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields
255
263
  const verbCopy = {
256
264
  id: verb.id,
257
265
  vector: [...verb.vector],
258
- connections: new Map()
266
+ connections: new Map(),
267
+ // CORE RELATIONAL DATA
268
+ verb: verb.verb,
269
+ sourceId: verb.sourceId,
270
+ targetId: verb.targetId,
271
+ // User metadata
272
+ metadata: verb.metadata
259
273
  };
260
274
  // Copy connections
261
275
  for (const [level, connections] of verb.connections.entries()) {
262
276
  verbCopy.connections.set(level, new Set(connections));
263
277
  }
264
- // Get metadata (relationship data in 2-file system)
265
- const metadata = await this.getVerbMetadata(id);
266
- // Combine into complete verb object
267
- return {
268
- ...verbCopy,
269
- metadata: metadata || {}
270
- };
278
+ return verbCopy;
271
279
  }
272
280
  /**
273
281
  * Get verbs with pagination and filtering
@@ -311,13 +311,18 @@ export class OPFSStorage extends BaseStorage {
311
311
  async saveEdge(edge) {
312
312
  await this.ensureInitialized();
313
313
  try {
314
- // CRITICAL: Only save lightweight vector data (no metadata)
315
- // Metadata is saved separately via saveVerbMetadata() (2-file system)
314
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields in verb vector file
315
+ // These fields are essential for 90% of operations - no metadata lookup needed
316
316
  const serializableEdge = {
317
317
  id: edge.id,
318
318
  vector: edge.vector,
319
- connections: this.mapToObject(edge.connections, (set) => Array.from(set))
320
- // NO metadata field - saved separately for scalability
319
+ connections: this.mapToObject(edge.connections, (set) => Array.from(set)),
320
+ // CORE RELATIONAL DATA (v3.50.1+)
321
+ verb: edge.verb,
322
+ sourceId: edge.sourceId,
323
+ targetId: edge.targetId,
324
+ // User metadata (if any) - saved separately for scalability
325
+ // metadata field is saved separately via saveVerbMetadata()
321
326
  };
322
327
  // Use UUID-based sharding for verbs
323
328
  const shardId = getShardIdFromUuid(edge.id);
@@ -388,10 +393,17 @@ export class OPFSStorage extends BaseStorage {
388
393
  augmentation: 'unknown',
389
394
  version: '1.0'
390
395
  };
396
+ // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
391
397
  return {
392
398
  id: data.id,
393
399
  vector: data.vector,
394
- connections
400
+ connections,
401
+ // CORE RELATIONAL DATA (read from vector file)
402
+ verb: data.verb,
403
+ sourceId: data.sourceId,
404
+ targetId: data.targetId,
405
+ // User metadata (retrieved separately via getVerbMetadata())
406
+ metadata: data.metadata
395
407
  };
396
408
  }
397
409
  catch (error) {
@@ -433,10 +445,17 @@ export class OPFSStorage extends BaseStorage {
433
445
  augmentation: 'unknown',
434
446
  version: '1.0'
435
447
  };
448
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields
436
449
  allEdges.push({
437
450
  id: data.id,
438
451
  vector: data.vector,
439
- connections
452
+ connections,
453
+ // CORE RELATIONAL DATA
454
+ verb: data.verb,
455
+ sourceId: data.sourceId,
456
+ targetId: data.targetId,
457
+ // User metadata
458
+ metadata: data.metadata
440
459
  });
441
460
  }
442
461
  catch (error) {
@@ -549,13 +549,21 @@ export class R2Storage extends BaseStorage {
549
549
  async saveEdgeDirect(edge) {
550
550
  const requestId = await this.applyBackpressure();
551
551
  try {
552
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields in verb vector file
553
+ // These fields are essential for 90% of operations - no metadata lookup needed
552
554
  const serializableEdge = {
553
555
  id: edge.id,
554
556
  vector: edge.vector,
555
557
  connections: Object.fromEntries(Array.from(edge.connections.entries()).map(([level, verbIds]) => [
556
558
  level,
557
559
  Array.from(verbIds)
558
- ]))
560
+ ])),
561
+ // CORE RELATIONAL DATA (v3.50.1+)
562
+ verb: edge.verb,
563
+ sourceId: edge.sourceId,
564
+ targetId: edge.targetId,
565
+ // User metadata (if any) - saved separately for scalability
566
+ // metadata field is saved separately via saveVerbMetadata()
559
567
  };
560
568
  const key = this.getVerbKey(edge.id);
561
569
  const { PutObjectCommand } = await import('@aws-sdk/client-s3');
@@ -612,10 +620,17 @@ export class R2Storage extends BaseStorage {
612
620
  for (const [level, verbIds] of Object.entries(data.connections || {})) {
613
621
  connections.set(Number(level), new Set(verbIds));
614
622
  }
623
+ // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
615
624
  const edge = {
616
625
  id: data.id,
617
626
  vector: data.vector,
618
- connections
627
+ connections,
628
+ // CORE RELATIONAL DATA (read from vector file)
629
+ verb: data.verb,
630
+ sourceId: data.sourceId,
631
+ targetId: data.targetId,
632
+ // User metadata (retrieved separately via getVerbMetadata())
633
+ metadata: data.metadata
619
634
  };
620
635
  this.verbCacheManager.set(id, edge);
621
636
  this.releaseBackpressure(true, requestId);
@@ -1179,10 +1179,15 @@ export class S3CompatibleStorage extends BaseStorage {
1179
1179
  // Convert connections Map to a serializable format
1180
1180
  // CRITICAL: Only save lightweight vector data (no metadata)
1181
1181
  // Metadata is saved separately via saveVerbMetadata() (2-file system)
1182
+ // ARCHITECTURAL FIX (v3.50.1): Include core relational fields in verb vector file
1182
1183
  const serializableEdge = {
1183
1184
  id: edge.id,
1184
1185
  vector: edge.vector,
1185
- connections: this.mapToObject(edge.connections, (set) => Array.from(set))
1186
+ connections: this.mapToObject(edge.connections, (set) => Array.from(set)),
1187
+ // CORE RELATIONAL DATA (v3.50.1+)
1188
+ verb: edge.verb,
1189
+ sourceId: edge.sourceId,
1190
+ targetId: edge.targetId,
1186
1191
  // NO metadata field - saved separately for scalability
1187
1192
  };
1188
1193
  // Import the PutObjectCommand only when needed
@@ -1279,10 +1284,17 @@ export class S3CompatibleStorage extends BaseStorage {
1279
1284
  for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) {
1280
1285
  connections.set(Number(level), new Set(nodeIds));
1281
1286
  }
1287
+ // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
1282
1288
  const edge = {
1283
1289
  id: parsedEdge.id,
1284
1290
  vector: parsedEdge.vector,
1285
- connections
1291
+ connections,
1292
+ // CORE RELATIONAL DATA (read from vector file)
1293
+ verb: parsedEdge.verb,
1294
+ sourceId: parsedEdge.sourceId,
1295
+ targetId: parsedEdge.targetId,
1296
+ // User metadata (retrieved separately via getVerbMetadata())
1297
+ metadata: parsedEdge.metadata
1286
1298
  };
1287
1299
  this.logger.trace(`Successfully retrieved edge ${id}`);
1288
1300
  return edge;
@@ -72,7 +72,9 @@ export declare class TypeAwareStorageAdapter extends BaseStorage {
72
72
  */
73
73
  private getNounType;
74
74
  /**
75
- * Get verb type from verb object or cache
75
+ * Get verb type from verb object
76
+ *
77
+ * ARCHITECTURAL FIX (v3.50.1): Simplified - verb field is now always present
76
78
  */
77
79
  private getVerbType;
78
80
  /**
@@ -93,10 +95,16 @@ export declare class TypeAwareStorageAdapter extends BaseStorage {
93
95
  protected deleteNoun_internal(id: string): Promise<void>;
94
96
  /**
95
97
  * Save verb (type-first path)
98
+ *
99
+ * ARCHITECTURAL FIX (v3.50.1): No more caching hack needed!
100
+ * HNSWVerb now includes verb field, so type is always available
96
101
  */
97
102
  protected saveVerb_internal(verb: HNSWVerb): Promise<void>;
98
103
  /**
99
104
  * Get verb (type-first path)
105
+ *
106
+ * ARCHITECTURAL FIX (v3.50.1): Cache still useful for performance
107
+ * Once we know where a verb is, we can retrieve it O(1) instead of searching all types
100
108
  */
101
109
  protected getVerb_internal(id: string): Promise<HNSWVerb | null>;
102
110
  /**
@@ -109,6 +117,8 @@ export declare class TypeAwareStorageAdapter extends BaseStorage {
109
117
  protected getVerbsByTarget_internal(targetId: string): Promise<GraphVerb[]>;
110
118
  /**
111
119
  * Get verbs by type (O(1) with type-first paths!)
120
+ *
121
+ * ARCHITECTURAL FIX (v3.50.1): Type is now in HNSWVerb, cached on read
112
122
  */
113
123
  protected getVerbsByType_internal(verbType: string): Promise<GraphVerb[]>;
114
124
  /**
@@ -153,24 +153,21 @@ export class TypeAwareStorageAdapter extends BaseStorage {
153
153
  return 'thing';
154
154
  }
155
155
  /**
156
- * Get verb type from verb object or cache
156
+ * Get verb type from verb object
157
+ *
158
+ * ARCHITECTURAL FIX (v3.50.1): Simplified - verb field is now always present
157
159
  */
158
160
  getVerbType(verb) {
159
- // Try verb property first
161
+ // v3.50.1+: verb is a required field in HNSWVerb
160
162
  if ('verb' in verb && verb.verb) {
161
163
  return verb.verb;
162
164
  }
163
- // Try type property
165
+ // Fallback for GraphVerb (type alias)
164
166
  if ('type' in verb && verb.type) {
165
167
  return verb.type;
166
168
  }
167
- // Try cache
168
- const cached = this.verbTypeCache.get(verb.id);
169
- if (cached) {
170
- return cached;
171
- }
172
- // Default to 'relatedTo' if unknown
173
- console.warn(`[TypeAwareStorage] Unknown verb type for ${verb.id}, defaulting to 'relatedTo'`);
169
+ // This should never happen with v3.50.1+ data
170
+ console.warn(`[TypeAwareStorage] Verb missing type field for ${verb.id}, defaulting to 'relatedTo'`);
174
171
  return 'relatedTo';
175
172
  }
176
173
  // ============================================================================
@@ -283,9 +280,13 @@ export class TypeAwareStorageAdapter extends BaseStorage {
283
280
  }
284
281
  /**
285
282
  * Save verb (type-first path)
283
+ *
284
+ * ARCHITECTURAL FIX (v3.50.1): No more caching hack needed!
285
+ * HNSWVerb now includes verb field, so type is always available
286
286
  */
287
287
  async saveVerb_internal(verb) {
288
- const type = this.getVerbType(verb);
288
+ // Type is now a first-class field in HNSWVerb - no caching needed!
289
+ const type = verb.verb;
289
290
  const path = getVerbVectorPath(type, verb.id);
290
291
  // Update type tracking
291
292
  const typeIndex = TypeUtils.getVerbIndex(type);
@@ -300,22 +301,27 @@ export class TypeAwareStorageAdapter extends BaseStorage {
300
301
  }
301
302
  /**
302
303
  * Get verb (type-first path)
304
+ *
305
+ * ARCHITECTURAL FIX (v3.50.1): Cache still useful for performance
306
+ * Once we know where a verb is, we can retrieve it O(1) instead of searching all types
303
307
  */
304
308
  async getVerb_internal(id) {
305
- // Try cache first
309
+ // Try cache first for O(1) retrieval
306
310
  const cachedType = this.verbTypeCache.get(id);
307
311
  if (cachedType) {
308
312
  const path = getVerbVectorPath(cachedType, id);
309
- return await this.u.readObjectFromPath(path);
313
+ const verb = await this.u.readObjectFromPath(path);
314
+ return verb;
310
315
  }
311
- // Search across all types
316
+ // Search across all types (only on first access)
312
317
  for (let i = 0; i < VERB_TYPE_COUNT; i++) {
313
318
  const type = TypeUtils.getVerbFromIndex(i);
314
319
  const path = getVerbVectorPath(type, id);
315
320
  try {
316
321
  const verb = await this.u.readObjectFromPath(path);
317
322
  if (verb) {
318
- this.verbTypeCache.set(id, type);
323
+ // Cache the type for next time (read from verb.verb field)
324
+ this.verbTypeCache.set(id, verb.verb);
319
325
  return verb;
320
326
  }
321
327
  }
@@ -389,6 +395,8 @@ export class TypeAwareStorageAdapter extends BaseStorage {
389
395
  }
390
396
  /**
391
397
  * Get verbs by type (O(1) with type-first paths!)
398
+ *
399
+ * ARCHITECTURAL FIX (v3.50.1): Type is now in HNSWVerb, cached on read
392
400
  */
393
401
  async getVerbsByType_internal(verbType) {
394
402
  const type = verbType;
@@ -399,11 +407,12 @@ export class TypeAwareStorageAdapter extends BaseStorage {
399
407
  try {
400
408
  const hnswVerb = await this.u.readObjectFromPath(path);
401
409
  if (hnswVerb) {
410
+ // Cache type from HNSWVerb for future O(1) retrievals
411
+ this.verbTypeCache.set(hnswVerb.id, hnswVerb.verb);
402
412
  // Convert to GraphVerb
403
413
  const graphVerb = await this.convertHNSWVerbToGraphVerb(hnswVerb);
404
414
  if (graphVerb) {
405
415
  verbs.push(graphVerb);
406
- this.verbTypeCache.set(hnswVerb.id, type);
407
416
  }
408
417
  }
409
418
  }
@@ -71,6 +71,10 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
71
71
  deleteNoun(id: string): Promise<void>;
72
72
  /**
73
73
  * Save a verb to storage
74
+ *
75
+ * ARCHITECTURAL FIX (v3.50.1): HNSWVerb now includes verb/sourceId/targetId
76
+ * These are core relational fields, not metadata. They're stored in the vector
77
+ * file for fast access and to align with actual usage patterns.
74
78
  */
75
79
  saveVerb(verb: GraphVerb): Promise<void>;
76
80
  /**
@@ -79,6 +83,9 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
79
83
  getVerb(id: string): Promise<GraphVerb | null>;
80
84
  /**
81
85
  * Convert HNSWVerb to GraphVerb by combining with metadata
86
+ *
87
+ * ARCHITECTURAL FIX (v3.50.1): Core fields (verb/sourceId/targetId) are now in HNSWVerb
88
+ * Only optional fields (weight, timestamps, etc.) come from metadata file
82
89
  */
83
90
  protected convertHNSWVerbToGraphVerb(hnswVerb: HNSWVerb): Promise<GraphVerb | null>;
84
91
  /**