@soulcraft/brainy 5.7.0 β†’ 5.7.2

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/CHANGELOG.md CHANGED
@@ -2,8 +2,85 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [5.7.2](https://github.com/soulcraftlabs/brainy/compare/v5.7.1...v5.7.2) (2025-11-12)
6
+
7
+
8
+ ### πŸ› Bug Fixes
9
+
10
+ * resolve v5.7.x race condition with write-through cache (v5.7.2) ([732d23b](https://github.com/soulcraftlabs/brainy/commit/732d23bd2afb4ac9559a9beb7835e0f623065ff2))
11
+
12
+ ### [5.7.1](https://github.com/soulcraftlabs/brainy/compare/v5.7.0...v5.7.1) (2025-11-11)
13
+
14
+ - fix: resolve v5.7.0 deadlock by restoring storage layer separation (v5.7.1) (eb9af45)
15
+
16
+
17
+ ## [5.7.1](https://github.com/soulcraftlabs/brainy/compare/v5.7.0...v5.7.1) (2025-11-11)
18
+
19
+ ### 🚨 CRITICAL BUG FIX
20
+
21
+ **v5.7.0 caused complete production failure - ALL imports hung indefinitely. This hotfix restores functionality.**
22
+
23
+ ### Bug Description
24
+ v5.7.0 introduced a circular dependency deadlock during GraphAdjacencyIndex initialization:
25
+ - `GraphAdjacencyIndex.rebuild()` β†’ `storage.getVerbs()`
26
+ - `storage.getVerbsBySource_internal()` β†’ `getGraphIndex()` (NEW in v5.7.0)
27
+ - `getGraphIndex()` waiting for rebuild to complete
28
+ - **DEADLOCK**: Each component waiting for the other
29
+
30
+ ### Symptoms
31
+ - ❌ ALL imports hung at "Reading Data Structure" stage for 760+ seconds
32
+ - ❌ `brain.add()` operations took 12+ seconds per entity (50x slower than expected)
33
+ - ❌ No errors thrown - infinite wait
34
+ - ❌ Zero entities imported successfully
35
+ - ❌ 100% of users unable to import files
36
+
37
+ ### Root Cause
38
+ v5.7.0 modified storage internal methods (`getVerbsBySource_internal`, `getVerbsByTarget_internal`) to use GraphAdjacencyIndex, creating tight coupling where:
39
+ - Storage layer depends on index
40
+ - Index depends on storage layer
41
+ - Circular dependency = deadlock during initialization
42
+
43
+ ### Fix (Architectural)
44
+ Reverted storage internals to v5.6.3 implementation:
45
+ - βœ… Storage layer is now simple and has no index dependencies
46
+ - βœ… GraphAdjacencyIndex can safely call storage.getVerbs() to rebuild
47
+ - βœ… No circular dependency possible
48
+ - βœ… Proper separation of concerns restored
49
+
50
+ **Files changed**:
51
+ - `src/storage/baseStorage.ts`: Reverted lines 2320-2444 to v5.6.3 implementation
52
+ - `tests/regression/v5.7.0-deadlock.test.ts`: Added comprehensive regression tests
53
+
54
+ ### Performance Impact
55
+ - Slightly slower GraphAdjacencyIndex initialization (one-time cost during rebuild)
56
+ - High-level query operations still use optimized index
57
+ - Import performance unaffected (writes don't trigger index initialization)
58
+ - **NO breaking changes to public API**
59
+
60
+ ### Testing
61
+ - βœ… 4 new regression tests verify no deadlock
62
+ - βœ… All 1146 existing tests pass
63
+ - βœ… Import + relationships complete in <1 second (not 760+ seconds)
64
+ - βœ… No 12+ second delays per entity
65
+
66
+ ### Verification
67
+ Workshop team (production users) should upgrade immediately:
68
+ ```bash
69
+ npm install @soulcraft/brainy@5.7.1
70
+ ```
71
+
72
+ Expected behavior after upgrade:
73
+ - βœ… Imports work again
74
+ - βœ… Fast entity creation (<100ms per entity)
75
+ - βœ… No hangs or infinite waits
76
+ - βœ… File operations responsive
77
+
78
+ ---
79
+
5
80
  ### [5.7.0](https://github.com/soulcraftlabs/brainy/compare/v5.6.3...v5.7.0) (2025-11-11)
6
81
 
82
+ **⚠️ WARNING: This version has a critical deadlock bug. Use v5.7.1 instead.**
83
+
7
84
  - test: skip flaky concurrent relationship test (race condition in duplicate detection) (a71785b)
8
85
  - perf: optimize imports with background deduplication (12-24x speedup) (02c80a0)
9
86
 
@@ -53,6 +53,7 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
53
53
  protected graphIndex?: GraphAdjacencyIndex;
54
54
  protected graphIndexPromise?: Promise<GraphAdjacencyIndex>;
55
55
  protected readOnly: boolean;
56
+ private writeCache;
56
57
  refManager?: RefManager;
57
58
  blobStorage?: BlobStorage;
58
59
  commitLog?: CommitLog;
@@ -488,7 +489,8 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
488
489
  protected getVerbsBySource_internal(sourceId: string): Promise<HNSWVerbWithMetadata[]>;
489
490
  /**
490
491
  * Get verbs by target (COW-aware implementation)
491
- * v5.7.0: BILLION-SCALE OPTIMIZATION - Use GraphAdjacencyIndex for O(log n) lookup
492
+ * v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
493
+ * v5.4.0: Fixed to directly list verb files instead of directories
492
494
  */
493
495
  protected getVerbsByTarget_internal(targetId: string): Promise<HNSWVerbWithMetadata[]>;
494
496
  /**
@@ -74,6 +74,12 @@ export class BaseStorage extends BaseStorageAdapter {
74
74
  super(...arguments);
75
75
  this.isInitialized = false;
76
76
  this.readOnly = false;
77
+ // v5.7.2: Write-through cache for read-after-write consistency
78
+ // Guarantees that immediately after writeObjectToBranch(), readWithInheritance() returns the data
79
+ // Cache key: resolved branchPath (includes branch scope for COW isolation)
80
+ // Cache lifetime: write start β†’ write completion (microseconds to milliseconds)
81
+ // Memory footprint: Typically <10 items (only in-flight writes), <1KB total
82
+ this.writeCache = new Map();
77
83
  this.currentBranch = 'main';
78
84
  this.cowEnabled = false;
79
85
  // Type-first indexing support (v5.4.0)
@@ -353,7 +359,17 @@ export class BaseStorage extends BaseStorageAdapter {
353
359
  */
354
360
  async writeObjectToBranch(path, data, branch) {
355
361
  const branchPath = this.resolveBranchPath(path, branch);
356
- return this.writeObjectToPath(branchPath, data);
362
+ // v5.7.2: Add to write cache BEFORE async write (guarantees read-after-write consistency)
363
+ // This ensures readWithInheritance() returns data immediately, fixing "Source entity not found" bug
364
+ this.writeCache.set(branchPath, data);
365
+ try {
366
+ await this.writeObjectToPath(branchPath, data);
367
+ }
368
+ finally {
369
+ // v5.7.2: Remove from cache after write completes (success or failure)
370
+ // Small memory footprint: cache only holds in-flight writes (typically <10 items)
371
+ this.writeCache.delete(branchPath);
372
+ }
357
373
  }
358
374
  /**
359
375
  * Read object with inheritance from parent branches (COW layer)
@@ -362,12 +378,24 @@ export class BaseStorage extends BaseStorageAdapter {
362
378
  */
363
379
  async readWithInheritance(path, branch) {
364
380
  if (!this.cowEnabled) {
365
- // COW disabled, direct read
381
+ // COW disabled: check write cache, then direct read
382
+ // v5.7.2: Check cache first for read-after-write consistency
383
+ const cachedData = this.writeCache.get(path);
384
+ if (cachedData !== undefined) {
385
+ return cachedData;
386
+ }
366
387
  return this.readObjectFromPath(path);
367
388
  }
368
389
  const targetBranch = branch || this.currentBranch || 'main';
369
- // Try current branch first
370
390
  const branchPath = this.resolveBranchPath(path, targetBranch);
391
+ // v5.7.2: Check write cache FIRST (synchronous, instant)
392
+ // This guarantees read-after-write consistency within the same process
393
+ // Fixes bug: brain.add() β†’ brain.relate() β†’ "Source entity not found"
394
+ const cachedData = this.writeCache.get(branchPath);
395
+ if (cachedData !== undefined) {
396
+ return cachedData;
397
+ }
398
+ // Try current branch first
371
399
  let data = await this.readObjectFromPath(branchPath);
372
400
  if (data !== null) {
373
401
  return data; // Found in current branch
@@ -411,6 +439,9 @@ export class BaseStorage extends BaseStorageAdapter {
411
439
  */
412
440
  async deleteObjectFromBranch(path, branch) {
413
441
  const branchPath = this.resolveBranchPath(path, branch);
442
+ // v5.7.2: Remove from write cache immediately (before async delete)
443
+ // Ensures subsequent reads don't return stale cached data
444
+ this.writeCache.delete(branchPath);
414
445
  return this.deleteObjectFromPath(branchPath);
415
446
  }
416
447
  /**
@@ -1868,71 +1899,115 @@ export class BaseStorage extends BaseStorageAdapter {
1868
1899
  * v5.4.0: Fixed to directly list verb files instead of directories
1869
1900
  */
1870
1901
  async getVerbsBySource_internal(sourceId) {
1871
- // v5.7.0: BILLION-SCALE OPTIMIZATION - Use GraphAdjacencyIndex for O(log n) lookup
1872
- // Previous: O(total_verbs) - scanned all 127 verb types
1873
- // Now: O(log n) LSM-tree lookup + O(verbs_for_source) load
1902
+ // v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
1903
+ // v5.7.0 called getGraphIndex() here, creating deadlock during initialization:
1904
+ // GraphAdjacencyIndex.rebuild() β†’ storage.getVerbs() β†’ getVerbsBySource_internal() β†’ getGraphIndex() β†’ [deadlock]
1905
+ // v5.4.0: Type-first implementation - scan across all verb types
1906
+ // COW-aware: uses readWithInheritance for each verb
1874
1907
  await this.ensureInitialized();
1875
- const startTime = performance.now();
1876
- // Get GraphAdjacencyIndex (lazy-initialized)
1877
- const graphIndex = await this.getGraphIndex();
1878
- // O(log n) lookup with bloom filter optimization
1879
- const verbIds = await graphIndex.getVerbIdsBySource(sourceId);
1880
- // Load each verb by ID (uses existing optimized getVerb())
1881
1908
  const results = [];
1882
- for (const verbId of verbIds) {
1909
+ // Iterate through all verb types
1910
+ for (let i = 0; i < VERB_TYPE_COUNT; i++) {
1911
+ const type = TypeUtils.getVerbFromIndex(i);
1912
+ const typeDir = `entities/verbs/${type}/vectors`;
1883
1913
  try {
1884
- const verb = await this.getVerb(verbId);
1885
- if (verb) {
1886
- results.push(verb);
1914
+ // v5.4.0 FIX: List all verb files directly (not shard directories)
1915
+ // listObjectsInBranch returns full paths to .json files, not directories
1916
+ const verbFiles = await this.listObjectsInBranch(typeDir);
1917
+ for (const verbPath of verbFiles) {
1918
+ // Skip if not a .json file
1919
+ if (!verbPath.endsWith('.json'))
1920
+ continue;
1921
+ try {
1922
+ const verb = await this.readWithInheritance(verbPath);
1923
+ if (verb && verb.sourceId === sourceId) {
1924
+ // v5.4.0: Use proper path helper instead of string replacement
1925
+ const metadataPath = getVerbMetadataPath(type, verb.id);
1926
+ const metadata = await this.readWithInheritance(metadataPath);
1927
+ // v5.4.0: Extract standard fields from metadata to top-level (like nouns)
1928
+ results.push({
1929
+ ...verb,
1930
+ weight: metadata?.weight,
1931
+ confidence: metadata?.confidence,
1932
+ createdAt: metadata?.createdAt
1933
+ ? (typeof metadata.createdAt === 'number' ? metadata.createdAt : metadata.createdAt.seconds * 1000)
1934
+ : Date.now(),
1935
+ updatedAt: metadata?.updatedAt
1936
+ ? (typeof metadata.updatedAt === 'number' ? metadata.updatedAt : metadata.updatedAt.seconds * 1000)
1937
+ : Date.now(),
1938
+ service: metadata?.service,
1939
+ createdBy: metadata?.createdBy,
1940
+ metadata: metadata || {}
1941
+ });
1942
+ }
1943
+ }
1944
+ catch (error) {
1945
+ // Skip verbs that fail to load
1946
+ }
1887
1947
  }
1888
1948
  }
1889
1949
  catch (error) {
1890
- // Skip verbs that fail to load (handles deleted/corrupted verbs gracefully)
1950
+ // Skip types that have no data
1891
1951
  }
1892
1952
  }
1893
- const elapsed = performance.now() - startTime;
1894
- // Performance monitoring - should be 100-10,000x faster than old O(n) scan
1895
- if (elapsed > 50.0) {
1896
- prodLog.warn(`getVerbsBySource_internal: Slow query for ${sourceId} ` +
1897
- `(${verbIds.length} verbs, ${elapsed.toFixed(2)}ms). ` +
1898
- `Expected <50ms with index optimization.`);
1899
- }
1900
1953
  return results;
1901
1954
  }
1902
1955
  /**
1903
1956
  * Get verbs by target (COW-aware implementation)
1904
- * v5.7.0: BILLION-SCALE OPTIMIZATION - Use GraphAdjacencyIndex for O(log n) lookup
1957
+ * v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
1958
+ * v5.4.0: Fixed to directly list verb files instead of directories
1905
1959
  */
1906
1960
  async getVerbsByTarget_internal(targetId) {
1907
- // v5.7.0: BILLION-SCALE OPTIMIZATION - Use GraphAdjacencyIndex for O(log n) lookup
1908
- // Previous: O(total_verbs) - scanned all 127 verb types
1909
- // Now: O(log n) LSM-tree lookup + O(verbs_for_target) load
1961
+ // v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
1962
+ // v5.7.0 called getGraphIndex() here, creating deadlock during initialization
1963
+ // v5.4.0: Type-first implementation - scan across all verb types
1964
+ // COW-aware: uses readWithInheritance for each verb
1910
1965
  await this.ensureInitialized();
1911
- const startTime = performance.now();
1912
- // Get GraphAdjacencyIndex (lazy-initialized)
1913
- const graphIndex = await this.getGraphIndex();
1914
- // O(log n) lookup with bloom filter optimization
1915
- const verbIds = await graphIndex.getVerbIdsByTarget(targetId);
1916
- // Load each verb by ID (uses existing optimized getVerb())
1917
1966
  const results = [];
1918
- for (const verbId of verbIds) {
1967
+ // Iterate through all verb types
1968
+ for (let i = 0; i < VERB_TYPE_COUNT; i++) {
1969
+ const type = TypeUtils.getVerbFromIndex(i);
1970
+ const typeDir = `entities/verbs/${type}/vectors`;
1919
1971
  try {
1920
- const verb = await this.getVerb(verbId);
1921
- if (verb) {
1922
- results.push(verb);
1972
+ // v5.4.0 FIX: List all verb files directly (not shard directories)
1973
+ // listObjectsInBranch returns full paths to .json files, not directories
1974
+ const verbFiles = await this.listObjectsInBranch(typeDir);
1975
+ for (const verbPath of verbFiles) {
1976
+ // Skip if not a .json file
1977
+ if (!verbPath.endsWith('.json'))
1978
+ continue;
1979
+ try {
1980
+ const verb = await this.readWithInheritance(verbPath);
1981
+ if (verb && verb.targetId === targetId) {
1982
+ // v5.4.0: Use proper path helper instead of string replacement
1983
+ const metadataPath = getVerbMetadataPath(type, verb.id);
1984
+ const metadata = await this.readWithInheritance(metadataPath);
1985
+ // v5.4.0: Extract standard fields from metadata to top-level (like nouns)
1986
+ results.push({
1987
+ ...verb,
1988
+ weight: metadata?.weight,
1989
+ confidence: metadata?.confidence,
1990
+ createdAt: metadata?.createdAt
1991
+ ? (typeof metadata.createdAt === 'number' ? metadata.createdAt : metadata.createdAt.seconds * 1000)
1992
+ : Date.now(),
1993
+ updatedAt: metadata?.updatedAt
1994
+ ? (typeof metadata.updatedAt === 'number' ? metadata.updatedAt : metadata.updatedAt.seconds * 1000)
1995
+ : Date.now(),
1996
+ service: metadata?.service,
1997
+ createdBy: metadata?.createdBy,
1998
+ metadata: metadata || {}
1999
+ });
2000
+ }
2001
+ }
2002
+ catch (error) {
2003
+ // Skip verbs that fail to load
2004
+ }
1923
2005
  }
1924
2006
  }
1925
2007
  catch (error) {
1926
- // Skip verbs that fail to load (handles deleted/corrupted verbs gracefully)
2008
+ // Skip types that have no data
1927
2009
  }
1928
2010
  }
1929
- const elapsed = performance.now() - startTime;
1930
- // Performance monitoring - should be 100-10,000x faster than old O(n) scan
1931
- if (elapsed > 50.0) {
1932
- prodLog.warn(`getVerbsByTarget_internal: Slow query for ${targetId} ` +
1933
- `(${verbIds.length} verbs, ${elapsed.toFixed(2)}ms). ` +
1934
- `Expected <50ms with index optimization.`);
1935
- }
1936
2011
  return results;
1937
2012
  }
1938
2013
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "5.7.0",
3
+ "version": "5.7.2",
4
4
  "description": "Universal Knowledge Protocolβ„’ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns Γ— 127 verbs covering 96-97% of all human knowledge.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",