From 932fb9520bb0f43f50d2b1844ab945abed8ce97a Mon Sep 17 00:00:00 2001 From: David Snelling Date: Mon, 2 Feb 2026 09:20:38 -0800 Subject: [PATCH] fix: set verb.source/target to entity UUID instead of NounType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit relate() was setting verb.source = fromEntity.type (a NounType like "concept") instead of the entity UUID. Cortex's graph index indexes by verb.source, so lookups by UUID found nothing — causing in-session reads to return 0 results. Also fixes: - PathResolver calling private getIdsFromChunks() → public getIds() - Plugin auto-detection removed; cortex loads only via explicit config - GraphVerb types accept number timestamps and sourceId/targetId aliases - Dead autoDetect() method removed from PluginRegistry - In-session regression tests added for getRelations after relate() --- src/brainy.ts | 26 +++++++++++--------------- src/coreTypes.ts | 8 ++++---- src/plugin.ts | 23 ----------------------- src/types/graphTypes.ts | 10 ++++++---- src/vfs/PathResolver.ts | 2 +- tests/unit/vfs-restart-fix.test.ts | 10 +++++++++- 6 files changed, 31 insertions(+), 48 deletions(-) diff --git a/src/brainy.ts b/src/brainy.ts index 7a51043e..abd414c5 100644 --- a/src/brainy.ts +++ b/src/brainy.ts @@ -1488,14 +1488,14 @@ export class Brainy implements BrainyInterface { vector: relationVector, sourceId: params.from, targetId: params.to, - source: fromEntity.type, - target: toEntity.type, + source: params.from, + target: params.to, verb: params.type, type: params.type, weight: params.weight ?? 1.0, - metadata: params.metadata as any, + metadata: params.metadata, createdAt: Date.now() - } as any + } // Execute atomically with transaction system await this.transactionManager.executeTransaction(async (tx) => { @@ -6694,15 +6694,14 @@ export class Brainy implements BrainyInterface { */ private async loadPlugins(): Promise { // plugins config: - // undefined (default) → auto-detect installed plugins - // false → no plugins, skip auto-detection - // [] → no plugins, skip auto-detection - // ['@soulcraft/cortex'] → load only these, no auto-detection + // undefined (default) → no auto-detection (safe default) + // false → no auto-detection + // [] → no auto-detection + // ['@soulcraft/cortex'] → load only these explicitly listed packages + // Note: plugins registered via brain.use() are always activated regardless of config const pluginConfig = this.config.plugins - if (pluginConfig === false) { - // Explicitly disabled — no plugins - } else if (Array.isArray(pluginConfig)) { - // Explicit list: only register the specified packages, no auto-detection + if (Array.isArray(pluginConfig) && pluginConfig.length > 0) { + // Explicit list: import and register the specified packages for (const pkg of pluginConfig) { try { const mod = await import(pkg) @@ -6714,9 +6713,6 @@ export class Brainy implements BrainyInterface { // Package not found — skip } } - } else { - // Default: auto-detect known plugins - await this.pluginRegistry.autoDetect() } // Create plugin context diff --git a/src/coreTypes.ts b/src/coreTypes.ts index b8c99c32..f730b6fc 100644 --- a/src/coreTypes.ts +++ b/src/coreTypes.ts @@ -291,15 +291,15 @@ export interface GraphVerb { service?: string // Multi-tenancy support - which service created this verb // Additional properties used in the codebase - source?: string // Alias for sourceId - target?: string // Alias for targetId + source?: string // Entity UUID (same as sourceId, for graphTypes compatibility) + target?: string // Entity UUID (same as targetId, for graphTypes compatibility) verb?: string // Alias for type data?: Record // Additional flexible data storage embedding?: Vector // Alias for vector // Timestamp and creator properties - createdAt?: { seconds: number; nanoseconds: number } // When the verb was created - updatedAt?: { seconds: number; nanoseconds: number } // When the verb was last updated + createdAt?: number | { seconds: number; nanoseconds: number } // When the verb was created + updatedAt?: number | { seconds: number; nanoseconds: number } // When the verb was last updated createdBy?: { augmentation: string; version: string } // Information about what created this verb } diff --git a/src/plugin.ts b/src/plugin.ts index 4ed44443..ec149aa0 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -84,29 +84,6 @@ export class PluginRegistry { this.plugins.set(plugin.name, plugin) } - /** - * Auto-detect known plugins by attempting dynamic import. - * Additional package names can be passed for third-party plugins. - */ - async autoDetect(additionalPackages: string[] = []): Promise { - const packages = [ - '@soulcraft/cortex', - '@soulcraft/brainy-cortex', // deprecated — backward compat - ...additionalPackages - ] - - for (const pkg of packages) { - try { - const mod = await import(pkg) - const plugin: BrainyPlugin = mod.default || mod - if (plugin && typeof plugin.activate === 'function' && plugin.name) { - this.plugins.set(plugin.name, plugin) - } - } catch { - // Package not installed — skip silently - } - } - } /** * Activate all registered plugins. diff --git a/src/types/graphTypes.ts b/src/types/graphTypes.ts index a5ee2622..559b6565 100644 --- a/src/types/graphTypes.ts +++ b/src/types/graphTypes.ts @@ -369,12 +369,14 @@ export interface GraphNoun { */ export interface GraphVerb { id: string // Unique identifier for the verb - source: string // ID of the source noun - target: string // ID of the target noun + source: string // Entity UUID of the source noun + target: string // Entity UUID of the target noun + sourceId?: string // Alias for source (coreTypes compatibility) + targetId?: string // Alias for target (coreTypes compatibility) label?: string // Optional descriptive label verb: VerbType // Type of relationship - createdAt: Timestamp // When the verb was created - updatedAt: Timestamp // When the verb was last updated + createdAt: Timestamp | number // When the verb was created + updatedAt: Timestamp | number // When the verb was last updated createdBy: CreatorMetadata // Information about what created this verb service?: string // Multi-tenancy support - which service created this verb data?: Record // Additional flexible data storage diff --git a/src/vfs/PathResolver.ts b/src/vfs/PathResolver.ts index 9d3ae742..7d0999ba 100644 --- a/src/vfs/PathResolver.ts +++ b/src/vfs/PathResolver.ts @@ -214,7 +214,7 @@ export class PathResolver { try { // Direct O(log n) query to roaring bitmap index // This queries the 'path' field in VFS entity metadata - const ids = await metadataIndex.getIdsFromChunks('path', path) + const ids = await metadataIndex.getIds('path', path) if (ids.length === 0) { this.metadataIndexMisses++ diff --git a/tests/unit/vfs-restart-fix.test.ts b/tests/unit/vfs-restart-fix.test.ts index 83f1fae3..67cb48b8 100644 --- a/tests/unit/vfs-restart-fix.test.ts +++ b/tests/unit/vfs-restart-fix.test.ts @@ -33,13 +33,17 @@ describe('VFS restart persistence', () => { entityId = await brain.vfs.resolvePathToId('/chapter-1.txt') expect(entityId).toBeTruthy() - // Verify within same session + // Verify within same session — entity, readdir, and relations const entity1 = await brain.get(entityId!) expect(entity1).not.toBeNull() const entries1 = await brain.vfs.readdir('/') expect(entries1.length).toBeGreaterThan(0) + // In-session getRelations: root should have Contains relationship to the file + const relations1 = await brain.getRelations({ from: rootId, type: VerbType.Contains }) + expect(relations1.length).toBeGreaterThan(0) + await brain.close() // === SESSION 2: Read data after restart === @@ -100,6 +104,10 @@ describe('VFS restart persistence', () => { const docEntries1 = await brain.vfs.readdir('/docs') expect(docEntries1.length).toBe(2) // guide.txt + api.txt + // In-session getRelations: root should have Contains relationships + const rootRelations1 = await brain.getRelations({ from: rootId, type: VerbType.Contains }) + expect(rootRelations1.length).toBeGreaterThan(0) + await brain.close() // === SESSION 2: Verify all data persisted ===