@soulcraft/brainy 3.14.2 → 3.16.0

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,6 +2,22 @@
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
+ ## [3.15.0](https://github.com/soulcraftlabs/brainy/compare/v3.14.2...v3.15.0) (2025-09-26)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **vfs**: Ensure Contains relationships are maintained when updating files
10
+ * **vfs**: Fix root directory metadata handling to prevent "Not a directory" errors
11
+ * **vfs**: Add entity metadata compatibility layer for proper VFS operations
12
+ * **vfs**: Fix resolvePath() to return entity IDs instead of path strings
13
+ * **vfs**: Improve error handling in ensureDirectory() method
14
+
15
+ ### Features
16
+
17
+ * **vfs**: Add comprehensive tests for Contains relationship integrity
18
+ * **vfs**: Ensure all VFS entities use standard Brainy NounType and VerbType enums
19
+ * **vfs**: Add metadata validation and repair for existing entities
20
+
5
21
  ## [3.0.1](https://github.com/soulcraftlabs/brainy/compare/v2.14.3...v3.0.1) (2025-09-15)
6
22
 
7
23
  **Brainy 3.0 Production Release** - World's first Triple Intelligence™ database unifying vector, graph, and document search
@@ -0,0 +1,78 @@
1
+ import { Brainy } from '../brainy.js';
2
+ import { VirtualFileSystem } from './VirtualFileSystem.js';
3
+ export interface VFSHealthReport {
4
+ healthy: boolean;
5
+ rootDirectory: {
6
+ exists: boolean;
7
+ isDirectory: boolean;
8
+ accessible: boolean;
9
+ };
10
+ containsRelationships: {
11
+ intact: boolean;
12
+ orphanedFiles: string[];
13
+ missingRelationships: number;
14
+ };
15
+ metadataIntegrity: {
16
+ valid: boolean;
17
+ malformedEntities: string[];
18
+ };
19
+ performance: {
20
+ readLatency: number;
21
+ writeLatency: number;
22
+ cacheHitRate: number;
23
+ };
24
+ recommendations: string[];
25
+ }
26
+ /**
27
+ * VFS Health Check and Recovery System
28
+ *
29
+ * Provides automated health checks, diagnostics, and recovery
30
+ * procedures for the Virtual File System.
31
+ */
32
+ export declare class VFSHealthCheck {
33
+ private vfs;
34
+ private brain;
35
+ private logger;
36
+ constructor(vfs: VirtualFileSystem, brain: Brainy);
37
+ /**
38
+ * Perform comprehensive health check
39
+ */
40
+ checkHealth(): Promise<VFSHealthReport>;
41
+ /**
42
+ * Check root directory health
43
+ */
44
+ private checkRootDirectory;
45
+ /**
46
+ * Check Contains relationships integrity
47
+ */
48
+ private checkContainsRelationships;
49
+ /**
50
+ * Check metadata integrity
51
+ */
52
+ private checkMetadataIntegrity;
53
+ /**
54
+ * Check performance metrics
55
+ */
56
+ private checkPerformance;
57
+ /**
58
+ * Generate recommendations based on health report
59
+ */
60
+ private generateRecommendations;
61
+ /**
62
+ * Attempt to recover VFS to healthy state
63
+ */
64
+ recover(): Promise<void>;
65
+ /**
66
+ * Recover root directory
67
+ */
68
+ private recoverRootDirectory;
69
+ /**
70
+ * Repair orphaned files by creating missing Contains relationships
71
+ */
72
+ private repairOrphanedFiles;
73
+ /**
74
+ * Fix malformed metadata
75
+ */
76
+ private fixMalformedMetadata;
77
+ private getParentPath;
78
+ }
@@ -0,0 +1,299 @@
1
+ import { VFSError, VFSErrorCode } from './types.js';
2
+ import { Logger } from '../utils/logger.js';
3
+ /**
4
+ * VFS Health Check and Recovery System
5
+ *
6
+ * Provides automated health checks, diagnostics, and recovery
7
+ * procedures for the Virtual File System.
8
+ */
9
+ export class VFSHealthCheck {
10
+ constructor(vfs, brain) {
11
+ this.vfs = vfs;
12
+ this.brain = brain;
13
+ this.logger = new Logger('VFSHealthCheck');
14
+ }
15
+ /**
16
+ * Perform comprehensive health check
17
+ */
18
+ async checkHealth() {
19
+ const report = {
20
+ healthy: true,
21
+ rootDirectory: {
22
+ exists: false,
23
+ isDirectory: false,
24
+ accessible: false
25
+ },
26
+ containsRelationships: {
27
+ intact: true,
28
+ orphanedFiles: [],
29
+ missingRelationships: 0
30
+ },
31
+ metadataIntegrity: {
32
+ valid: true,
33
+ malformedEntities: []
34
+ },
35
+ performance: {
36
+ readLatency: 0,
37
+ writeLatency: 0,
38
+ cacheHitRate: 0
39
+ },
40
+ recommendations: []
41
+ };
42
+ // Check root directory
43
+ await this.checkRootDirectory(report);
44
+ // Check Contains relationships
45
+ await this.checkContainsRelationships(report);
46
+ // Check metadata integrity
47
+ await this.checkMetadataIntegrity(report);
48
+ // Check performance metrics
49
+ await this.checkPerformance(report);
50
+ // Generate recommendations
51
+ this.generateRecommendations(report);
52
+ report.healthy =
53
+ report.rootDirectory.exists &&
54
+ report.rootDirectory.isDirectory &&
55
+ report.rootDirectory.accessible &&
56
+ report.containsRelationships.intact &&
57
+ report.metadataIntegrity.valid;
58
+ return report;
59
+ }
60
+ /**
61
+ * Check root directory health
62
+ */
63
+ async checkRootDirectory(report) {
64
+ try {
65
+ // Check if root exists
66
+ const rootId = await this.vfs.resolvePath('/');
67
+ report.rootDirectory.exists = !!rootId;
68
+ if (rootId) {
69
+ // Get root entity
70
+ const rootEntity = await this.brain.get(rootId);
71
+ // Check if it's a directory
72
+ report.rootDirectory.isDirectory =
73
+ rootEntity?.metadata?.vfsType === 'directory' ||
74
+ rootEntity?.vfsType === 'directory';
75
+ // Check if it's accessible
76
+ try {
77
+ await this.vfs.readdir('/');
78
+ report.rootDirectory.accessible = true;
79
+ }
80
+ catch {
81
+ report.rootDirectory.accessible = false;
82
+ }
83
+ }
84
+ }
85
+ catch (error) {
86
+ this.logger.error('Root directory check failed:', error);
87
+ report.rootDirectory.exists = false;
88
+ }
89
+ }
90
+ /**
91
+ * Check Contains relationships integrity
92
+ */
93
+ async checkContainsRelationships(report) {
94
+ try {
95
+ // Get all file entities
96
+ const files = await this.brain.find({
97
+ metadata: { vfsType: 'file' },
98
+ limit: 1000
99
+ });
100
+ for (const file of files) {
101
+ // Check if file has a Contains relationship with its parent
102
+ const parentPath = this.getParentPath(file.metadata.path);
103
+ if (parentPath) {
104
+ try {
105
+ const parentId = await this.vfs.resolvePath(parentPath);
106
+ const relations = await this.brain.getRelations({
107
+ from: parentId,
108
+ to: file.id,
109
+ type: 'Contains'
110
+ });
111
+ if (relations.length === 0) {
112
+ report.containsRelationships.orphanedFiles.push(file.metadata.path);
113
+ report.containsRelationships.missingRelationships++;
114
+ report.containsRelationships.intact = false;
115
+ }
116
+ }
117
+ catch {
118
+ // Parent doesn't exist
119
+ report.containsRelationships.orphanedFiles.push(file.metadata.path);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ catch (error) {
125
+ this.logger.error('Contains relationships check failed:', error);
126
+ report.containsRelationships.intact = false;
127
+ }
128
+ }
129
+ /**
130
+ * Check metadata integrity
131
+ */
132
+ async checkMetadataIntegrity(report) {
133
+ try {
134
+ // Get all VFS entities
135
+ const entities = await this.brain.find({
136
+ metadata: { path: { $exists: true } },
137
+ limit: 1000
138
+ });
139
+ for (const entity of entities) {
140
+ // Check required metadata fields
141
+ if (!entity.metadata?.vfsType ||
142
+ !entity.metadata?.path ||
143
+ entity.metadata.vfsType !== 'file' && entity.metadata.vfsType !== 'directory') {
144
+ report.metadataIntegrity.malformedEntities.push(entity.id);
145
+ report.metadataIntegrity.valid = false;
146
+ }
147
+ }
148
+ }
149
+ catch (error) {
150
+ this.logger.error('Metadata integrity check failed:', error);
151
+ report.metadataIntegrity.valid = false;
152
+ }
153
+ }
154
+ /**
155
+ * Check performance metrics
156
+ */
157
+ async checkPerformance(report) {
158
+ try {
159
+ // Test read performance
160
+ const readStart = Date.now();
161
+ await this.vfs.readdir('/');
162
+ report.performance.readLatency = Date.now() - readStart;
163
+ // Test write performance
164
+ const writeStart = Date.now();
165
+ const testPath = '/.health-check-' + Date.now();
166
+ await this.vfs.writeFile(testPath, 'test');
167
+ report.performance.writeLatency = Date.now() - writeStart;
168
+ // Clean up test file
169
+ await this.vfs.unlink(testPath);
170
+ // Calculate cache hit rate (if available)
171
+ const stats = await this.brain.getStatistics();
172
+ if (stats?.cache) {
173
+ report.performance.cacheHitRate = stats.cache.hitRate || 0;
174
+ }
175
+ }
176
+ catch (error) {
177
+ this.logger.error('Performance check failed:', error);
178
+ }
179
+ }
180
+ /**
181
+ * Generate recommendations based on health report
182
+ */
183
+ generateRecommendations(report) {
184
+ if (!report.rootDirectory.exists || !report.rootDirectory.isDirectory) {
185
+ report.recommendations.push('Run VFS recovery to rebuild root directory');
186
+ }
187
+ if (report.containsRelationships.orphanedFiles.length > 0) {
188
+ report.recommendations.push(`Repair ${report.containsRelationships.orphanedFiles.length} orphaned files`);
189
+ }
190
+ if (report.metadataIntegrity.malformedEntities.length > 0) {
191
+ report.recommendations.push(`Fix metadata for ${report.metadataIntegrity.malformedEntities.length} malformed entities`);
192
+ }
193
+ if (report.performance.readLatency > 100) {
194
+ report.recommendations.push('Consider enabling caching or optimizing indexes');
195
+ }
196
+ if (report.performance.cacheHitRate < 0.5) {
197
+ report.recommendations.push('Increase cache size or TTL for better performance');
198
+ }
199
+ }
200
+ /**
201
+ * Attempt to recover VFS to healthy state
202
+ */
203
+ async recover() {
204
+ this.logger.info('Starting VFS recovery...');
205
+ // 1. Ensure root directory exists and is properly configured
206
+ await this.recoverRootDirectory();
207
+ // 2. Repair orphaned files
208
+ await this.repairOrphanedFiles();
209
+ // 3. Fix malformed metadata
210
+ await this.fixMalformedMetadata();
211
+ this.logger.info('VFS recovery completed');
212
+ }
213
+ /**
214
+ * Recover root directory
215
+ */
216
+ async recoverRootDirectory() {
217
+ try {
218
+ // Force recreation of root directory with proper metadata
219
+ const rootId = await this.vfs['initializeRoot']();
220
+ // Verify root is now accessible
221
+ await this.vfs.readdir('/');
222
+ this.logger.info('Root directory recovered successfully');
223
+ }
224
+ catch (error) {
225
+ this.logger.error('Failed to recover root directory:', error);
226
+ throw new VFSError(VFSErrorCode.EIO, 'Failed to recover root directory', '/', 'recover');
227
+ }
228
+ }
229
+ /**
230
+ * Repair orphaned files by creating missing Contains relationships
231
+ */
232
+ async repairOrphanedFiles() {
233
+ const report = await this.checkHealth();
234
+ for (const filePath of report.containsRelationships.orphanedFiles) {
235
+ try {
236
+ const fileId = await this.vfs.resolvePath(filePath);
237
+ const parentPath = this.getParentPath(filePath);
238
+ if (parentPath) {
239
+ // Ensure parent directory exists
240
+ await this.vfs.mkdir(parentPath, { recursive: true });
241
+ const parentId = await this.vfs.resolvePath(parentPath);
242
+ // Create missing Contains relationship
243
+ await this.brain.relate({
244
+ from: parentId,
245
+ to: fileId,
246
+ type: 'Contains'
247
+ });
248
+ this.logger.info(`Repaired orphaned file: ${filePath}`);
249
+ }
250
+ }
251
+ catch (error) {
252
+ this.logger.error(`Failed to repair orphaned file ${filePath}:`, error);
253
+ }
254
+ }
255
+ }
256
+ /**
257
+ * Fix malformed metadata
258
+ */
259
+ async fixMalformedMetadata() {
260
+ const report = await this.checkHealth();
261
+ for (const entityId of report.metadataIntegrity.malformedEntities) {
262
+ try {
263
+ const entity = await this.brain.get(entityId);
264
+ if (entity) {
265
+ // Reconstruct metadata
266
+ const fixedMetadata = {
267
+ path: entity.metadata?.path || entity.path || '/',
268
+ name: entity.metadata?.name || entity.name || '',
269
+ vfsType: entity.metadata?.vfsType || entity.vfsType || 'file',
270
+ size: entity.metadata?.size || entity.size || 0,
271
+ permissions: entity.metadata?.permissions || 0o644,
272
+ owner: entity.metadata?.owner || 'user',
273
+ group: entity.metadata?.group || 'users',
274
+ accessed: entity.metadata?.accessed || Date.now(),
275
+ modified: entity.metadata?.modified || Date.now(),
276
+ ...entity.metadata
277
+ };
278
+ // Update entity with fixed metadata
279
+ await this.brain.update({
280
+ id: entityId,
281
+ metadata: fixedMetadata
282
+ });
283
+ this.logger.info(`Fixed metadata for entity: ${entityId}`);
284
+ }
285
+ }
286
+ catch (error) {
287
+ this.logger.error(`Failed to fix metadata for entity ${entityId}:`, error);
288
+ }
289
+ }
290
+ }
291
+ getParentPath(path) {
292
+ const normalized = path.replace(/\/+/g, '/').replace(/\/$/, '');
293
+ const lastSlash = normalized.lastIndexOf('/');
294
+ if (lastSlash <= 0)
295
+ return '/';
296
+ return normalized.substring(0, lastSlash);
297
+ }
298
+ }
299
+ //# sourceMappingURL=VFSHealthCheck.js.map
@@ -62,16 +62,37 @@ export class VirtualFileSystem {
62
62
  * Create or find the root directory entity
63
63
  */
64
64
  async initializeRoot() {
65
- // Check if root already exists
65
+ // Check if root already exists - search using where clause
66
66
  const existing = await this.brain.find({
67
67
  where: {
68
- path: '/',
69
- vfsType: 'directory'
68
+ 'metadata.path': '/',
69
+ 'metadata.vfsType': 'directory'
70
70
  },
71
71
  limit: 1
72
72
  });
73
73
  if (existing.length > 0) {
74
- return existing[0].entity.id;
74
+ const rootEntity = existing[0];
75
+ // Ensure the root entity has proper metadata structure
76
+ const entityMetadata = rootEntity.metadata || rootEntity;
77
+ if (!entityMetadata.vfsType) {
78
+ // Update the root entity with proper metadata
79
+ await this.brain.update({
80
+ id: rootEntity.id,
81
+ metadata: {
82
+ path: '/',
83
+ name: '',
84
+ vfsType: 'directory',
85
+ size: 0,
86
+ permissions: 0o755,
87
+ owner: 'root',
88
+ group: 'root',
89
+ accessed: Date.now(),
90
+ modified: Date.now(),
91
+ ...entityMetadata // Preserve any existing metadata
92
+ }
93
+ });
94
+ }
95
+ return rootEntity.id;
75
96
  }
76
97
  // Create root directory
77
98
  const root = await this.brain.add({
@@ -247,6 +268,20 @@ export class VirtualFileSystem {
247
268
  data: entityData,
248
269
  metadata
249
270
  });
271
+ // Ensure Contains relationship exists (fix for missing relationships)
272
+ const existingRelations = await this.brain.getRelations({
273
+ from: parentId,
274
+ to: existingId,
275
+ type: VerbType.Contains
276
+ });
277
+ // Create relationship if it doesn't exist
278
+ if (existingRelations.length === 0) {
279
+ await this.brain.relate({
280
+ from: parentId,
281
+ to: existingId,
282
+ type: VerbType.Contains
283
+ });
284
+ }
250
285
  }
251
286
  else {
252
287
  // Create new file entity
@@ -736,9 +771,13 @@ export class VirtualFileSystem {
736
771
  return entityId;
737
772
  }
738
773
  catch (err) {
739
- // Directory doesn't exist, create it recursively
740
- await this.mkdir(path, { recursive: true });
741
- return await this.pathResolver.resolve(path);
774
+ // Only create directory if it doesn't exist (ENOENT error)
775
+ if (err instanceof VFSError && err.code === VFSErrorCode.ENOENT) {
776
+ await this.mkdir(path, { recursive: true });
777
+ return await this.pathResolver.resolve(path);
778
+ }
779
+ // Re-throw other errors (like ENOTDIR)
780
+ throw err;
742
781
  }
743
782
  }
744
783
  async getEntityById(id) {
@@ -746,6 +785,41 @@ export class VirtualFileSystem {
746
785
  if (!entity) {
747
786
  throw new VFSError(VFSErrorCode.ENOENT, `Entity not found: ${id}`);
748
787
  }
788
+ // Ensure entity has proper VFS metadata structure
789
+ // Handle both nested and flat metadata structures for compatibility
790
+ if (!entity.metadata || !entity.metadata.vfsType) {
791
+ // Check if metadata is at top level (legacy structure)
792
+ const anyEntity = entity;
793
+ if (anyEntity.vfsType || anyEntity.path) {
794
+ entity.metadata = {
795
+ path: anyEntity.path || '/',
796
+ name: anyEntity.name || '',
797
+ vfsType: anyEntity.vfsType || (anyEntity.path === '/' ? 'directory' : 'file'),
798
+ size: anyEntity.size || 0,
799
+ permissions: anyEntity.permissions || (anyEntity.vfsType === 'directory' ? 0o755 : 0o644),
800
+ owner: anyEntity.owner || 'user',
801
+ group: anyEntity.group || 'users',
802
+ accessed: anyEntity.accessed || Date.now(),
803
+ modified: anyEntity.modified || Date.now(),
804
+ ...entity.metadata // Preserve any existing nested metadata
805
+ };
806
+ }
807
+ else if (entity.id === this.rootEntityId) {
808
+ // Special case: ensure root directory always has proper metadata
809
+ entity.metadata = {
810
+ path: '/',
811
+ name: '',
812
+ vfsType: 'directory',
813
+ size: 0,
814
+ permissions: 0o755,
815
+ owner: 'root',
816
+ group: 'root',
817
+ accessed: Date.now(),
818
+ modified: Date.now(),
819
+ ...entity.metadata
820
+ };
821
+ }
822
+ }
749
823
  return entity;
750
824
  }
751
825
  getParentPath(path) {
@@ -2016,7 +2090,13 @@ export class VirtualFileSystem {
2016
2090
  path = `${from}/${path}`;
2017
2091
  }
2018
2092
  // Normalize path
2019
- return path.replace(/\/+/g, '/').replace(/\/$/, '') || '/';
2093
+ const normalizedPath = path.replace(/\/+/g, '/').replace(/\/$/, '') || '/';
2094
+ // Special case for root
2095
+ if (normalizedPath === '/') {
2096
+ return this.rootEntityId;
2097
+ }
2098
+ // Resolve the path to an entity ID
2099
+ return await this.pathResolver.resolve(normalizedPath);
2020
2100
  }
2021
2101
  }
2022
2102
  //# sourceMappingURL=VirtualFileSystem.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "3.14.2",
3
+ "version": "3.16.0",
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",