@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 +70 -0
- package/dist/storage/baseStorage.d.ts +2 -1
- package/dist/storage/baseStorage.js +87 -43
- package/package.json +1 -1
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.
|
|
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.
|
|
1872
|
-
//
|
|
1873
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
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
|
|
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.
|
|
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.
|
|
1908
|
-
//
|
|
1909
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
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
|
|
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.
|
|
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",
|