@soulcraft/brainy 5.7.0 β†’ 5.7.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.
package/CHANGELOG.md CHANGED
@@ -2,8 +2,78 @@
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.1](https://github.com/soulcraftlabs/brainy/compare/v5.7.0...v5.7.1) (2025-11-11)
6
+
7
+ - fix: resolve v5.7.0 deadlock by restoring storage layer separation (v5.7.1) (eb9af45)
8
+
9
+
10
+ ## [5.7.1](https://github.com/soulcraftlabs/brainy/compare/v5.7.0...v5.7.1) (2025-11-11)
11
+
12
+ ### 🚨 CRITICAL BUG FIX
13
+
14
+ **v5.7.0 caused complete production failure - ALL imports hung indefinitely. This hotfix restores functionality.**
15
+
16
+ ### Bug Description
17
+ v5.7.0 introduced a circular dependency deadlock during GraphAdjacencyIndex initialization:
18
+ - `GraphAdjacencyIndex.rebuild()` β†’ `storage.getVerbs()`
19
+ - `storage.getVerbsBySource_internal()` β†’ `getGraphIndex()` (NEW in v5.7.0)
20
+ - `getGraphIndex()` waiting for rebuild to complete
21
+ - **DEADLOCK**: Each component waiting for the other
22
+
23
+ ### Symptoms
24
+ - ❌ ALL imports hung at "Reading Data Structure" stage for 760+ seconds
25
+ - ❌ `brain.add()` operations took 12+ seconds per entity (50x slower than expected)
26
+ - ❌ No errors thrown - infinite wait
27
+ - ❌ Zero entities imported successfully
28
+ - ❌ 100% of users unable to import files
29
+
30
+ ### Root Cause
31
+ v5.7.0 modified storage internal methods (`getVerbsBySource_internal`, `getVerbsByTarget_internal`) to use GraphAdjacencyIndex, creating tight coupling where:
32
+ - Storage layer depends on index
33
+ - Index depends on storage layer
34
+ - Circular dependency = deadlock during initialization
35
+
36
+ ### Fix (Architectural)
37
+ Reverted storage internals to v5.6.3 implementation:
38
+ - βœ… Storage layer is now simple and has no index dependencies
39
+ - βœ… GraphAdjacencyIndex can safely call storage.getVerbs() to rebuild
40
+ - βœ… No circular dependency possible
41
+ - βœ… Proper separation of concerns restored
42
+
43
+ **Files changed**:
44
+ - `src/storage/baseStorage.ts`: Reverted lines 2320-2444 to v5.6.3 implementation
45
+ - `tests/regression/v5.7.0-deadlock.test.ts`: Added comprehensive regression tests
46
+
47
+ ### Performance Impact
48
+ - Slightly slower GraphAdjacencyIndex initialization (one-time cost during rebuild)
49
+ - High-level query operations still use optimized index
50
+ - Import performance unaffected (writes don't trigger index initialization)
51
+ - **NO breaking changes to public API**
52
+
53
+ ### Testing
54
+ - βœ… 4 new regression tests verify no deadlock
55
+ - βœ… All 1146 existing tests pass
56
+ - βœ… Import + relationships complete in <1 second (not 760+ seconds)
57
+ - βœ… No 12+ second delays per entity
58
+
59
+ ### Verification
60
+ Workshop team (production users) should upgrade immediately:
61
+ ```bash
62
+ npm install @soulcraft/brainy@5.7.1
63
+ ```
64
+
65
+ Expected behavior after upgrade:
66
+ - βœ… Imports work again
67
+ - βœ… Fast entity creation (<100ms per entity)
68
+ - βœ… No hangs or infinite waits
69
+ - βœ… File operations responsive
70
+
71
+ ---
72
+
5
73
  ### [5.7.0](https://github.com/soulcraftlabs/brainy/compare/v5.6.3...v5.7.0) (2025-11-11)
6
74
 
75
+ **⚠️ WARNING: This version has a critical deadlock bug. Use v5.7.1 instead.**
76
+
7
77
  - test: skip flaky concurrent relationship test (race condition in duplicate detection) (a71785b)
8
78
  - perf: optimize imports with background deduplication (12-24x speedup) (02c80a0)
9
79
 
@@ -488,7 +488,8 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
488
488
  protected getVerbsBySource_internal(sourceId: string): Promise<HNSWVerbWithMetadata[]>;
489
489
  /**
490
490
  * Get verbs by target (COW-aware implementation)
491
- * v5.7.0: BILLION-SCALE OPTIMIZATION - Use GraphAdjacencyIndex for O(log n) lookup
491
+ * v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
492
+ * v5.4.0: Fixed to directly list verb files instead of directories
492
493
  */
493
494
  protected getVerbsByTarget_internal(targetId: string): Promise<HNSWVerbWithMetadata[]>;
494
495
  /**
@@ -1868,71 +1868,115 @@ export class BaseStorage extends BaseStorageAdapter {
1868
1868
  * v5.4.0: Fixed to directly list verb files instead of directories
1869
1869
  */
1870
1870
  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
1871
+ // v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
1872
+ // v5.7.0 called getGraphIndex() here, creating deadlock during initialization:
1873
+ // GraphAdjacencyIndex.rebuild() β†’ storage.getVerbs() β†’ getVerbsBySource_internal() β†’ getGraphIndex() β†’ [deadlock]
1874
+ // v5.4.0: Type-first implementation - scan across all verb types
1875
+ // COW-aware: uses readWithInheritance for each verb
1874
1876
  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
1877
  const results = [];
1882
- for (const verbId of verbIds) {
1878
+ // Iterate through all verb types
1879
+ for (let i = 0; i < VERB_TYPE_COUNT; i++) {
1880
+ const type = TypeUtils.getVerbFromIndex(i);
1881
+ const typeDir = `entities/verbs/${type}/vectors`;
1883
1882
  try {
1884
- const verb = await this.getVerb(verbId);
1885
- if (verb) {
1886
- results.push(verb);
1883
+ // v5.4.0 FIX: List all verb files directly (not shard directories)
1884
+ // listObjectsInBranch returns full paths to .json files, not directories
1885
+ const verbFiles = await this.listObjectsInBranch(typeDir);
1886
+ for (const verbPath of verbFiles) {
1887
+ // Skip if not a .json file
1888
+ if (!verbPath.endsWith('.json'))
1889
+ continue;
1890
+ try {
1891
+ const verb = await this.readWithInheritance(verbPath);
1892
+ if (verb && verb.sourceId === sourceId) {
1893
+ // v5.4.0: Use proper path helper instead of string replacement
1894
+ const metadataPath = getVerbMetadataPath(type, verb.id);
1895
+ const metadata = await this.readWithInheritance(metadataPath);
1896
+ // v5.4.0: Extract standard fields from metadata to top-level (like nouns)
1897
+ results.push({
1898
+ ...verb,
1899
+ weight: metadata?.weight,
1900
+ confidence: metadata?.confidence,
1901
+ createdAt: metadata?.createdAt
1902
+ ? (typeof metadata.createdAt === 'number' ? metadata.createdAt : metadata.createdAt.seconds * 1000)
1903
+ : Date.now(),
1904
+ updatedAt: metadata?.updatedAt
1905
+ ? (typeof metadata.updatedAt === 'number' ? metadata.updatedAt : metadata.updatedAt.seconds * 1000)
1906
+ : Date.now(),
1907
+ service: metadata?.service,
1908
+ createdBy: metadata?.createdBy,
1909
+ metadata: metadata || {}
1910
+ });
1911
+ }
1912
+ }
1913
+ catch (error) {
1914
+ // Skip verbs that fail to load
1915
+ }
1887
1916
  }
1888
1917
  }
1889
1918
  catch (error) {
1890
- // Skip verbs that fail to load (handles deleted/corrupted verbs gracefully)
1919
+ // Skip types that have no data
1891
1920
  }
1892
1921
  }
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
1922
  return results;
1901
1923
  }
1902
1924
  /**
1903
1925
  * Get verbs by target (COW-aware implementation)
1904
- * v5.7.0: BILLION-SCALE OPTIMIZATION - Use GraphAdjacencyIndex for O(log n) lookup
1926
+ * v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
1927
+ * v5.4.0: Fixed to directly list verb files instead of directories
1905
1928
  */
1906
1929
  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
1930
+ // v5.7.1: Reverted to v5.6.3 implementation to fix circular dependency deadlock
1931
+ // v5.7.0 called getGraphIndex() here, creating deadlock during initialization
1932
+ // v5.4.0: Type-first implementation - scan across all verb types
1933
+ // COW-aware: uses readWithInheritance for each verb
1910
1934
  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
1935
  const results = [];
1918
- for (const verbId of verbIds) {
1936
+ // Iterate through all verb types
1937
+ for (let i = 0; i < VERB_TYPE_COUNT; i++) {
1938
+ const type = TypeUtils.getVerbFromIndex(i);
1939
+ const typeDir = `entities/verbs/${type}/vectors`;
1919
1940
  try {
1920
- const verb = await this.getVerb(verbId);
1921
- if (verb) {
1922
- results.push(verb);
1941
+ // v5.4.0 FIX: List all verb files directly (not shard directories)
1942
+ // listObjectsInBranch returns full paths to .json files, not directories
1943
+ const verbFiles = await this.listObjectsInBranch(typeDir);
1944
+ for (const verbPath of verbFiles) {
1945
+ // Skip if not a .json file
1946
+ if (!verbPath.endsWith('.json'))
1947
+ continue;
1948
+ try {
1949
+ const verb = await this.readWithInheritance(verbPath);
1950
+ if (verb && verb.targetId === targetId) {
1951
+ // v5.4.0: Use proper path helper instead of string replacement
1952
+ const metadataPath = getVerbMetadataPath(type, verb.id);
1953
+ const metadata = await this.readWithInheritance(metadataPath);
1954
+ // v5.4.0: Extract standard fields from metadata to top-level (like nouns)
1955
+ results.push({
1956
+ ...verb,
1957
+ weight: metadata?.weight,
1958
+ confidence: metadata?.confidence,
1959
+ createdAt: metadata?.createdAt
1960
+ ? (typeof metadata.createdAt === 'number' ? metadata.createdAt : metadata.createdAt.seconds * 1000)
1961
+ : Date.now(),
1962
+ updatedAt: metadata?.updatedAt
1963
+ ? (typeof metadata.updatedAt === 'number' ? metadata.updatedAt : metadata.updatedAt.seconds * 1000)
1964
+ : Date.now(),
1965
+ service: metadata?.service,
1966
+ createdBy: metadata?.createdBy,
1967
+ metadata: metadata || {}
1968
+ });
1969
+ }
1970
+ }
1971
+ catch (error) {
1972
+ // Skip verbs that fail to load
1973
+ }
1923
1974
  }
1924
1975
  }
1925
1976
  catch (error) {
1926
- // Skip verbs that fail to load (handles deleted/corrupted verbs gracefully)
1977
+ // Skip types that have no data
1927
1978
  }
1928
1979
  }
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
1980
  return results;
1937
1981
  }
1938
1982
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "5.7.0",
3
+ "version": "5.7.1",
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",