From f222946be80b4dc84cc9775cba653495183efef6 Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 17 Jul 2026 00:16:55 -0500 Subject: [PATCH] feat: expose Rust CowEngine through rvf_branch and rvf_freeze MCP tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds rvfBranch() and rvfFreeze() wrappers in rvf-wrapper.ts calling the existing NAPI bindings (RvfDatabase.branch() and RvfDatabase.freeze()). Registers rvf_branch and rvf_freeze MCP tools in mcp-server.js. Updates cli.js help. The RuVector Rust crate already has a production COW engine at crates/rvf/rvf-runtime/src/cow.rs + store.rs. These MCP tools surface that existing Rust functionality through the MCP interface. Closes the earlier rvf-mcp-server TS-level branching approach — the correct architecture is to wrap the existing Rust CowEngine via NAPI. --- npm/packages/ruvector/bin/cli.js | 8 ++- npm/packages/ruvector/bin/mcp-server.js | 54 ++++++++++++++++++- npm/packages/ruvector/src/core/rvf-wrapper.ts | 24 +++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) mode change 100755 => 100644 npm/packages/ruvector/bin/cli.js diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js old mode 100755 new mode 100644 index 833426c393..ebd038b502 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -8157,7 +8157,9 @@ mcpCmd.command('info') console.log(chalk.dim(' rvf_delete - Delete vectors by ID')); console.log(chalk.dim(' rvf_status - Get store status')); console.log(chalk.dim(' rvf_compact - Compact store')); - console.log(chalk.dim(' rvf_derive - COW-branch to child store')); + console.log(chalk.dim(' rvf_derive - Lineage-only child (no COW)')); + console.log(chalk.dim(' rvf_branch - Full COW branch with Rust CowEngine')); + console.log(chalk.dim(' rvf_freeze - Freeze/snapshot store (read-only)')); console.log(chalk.dim(' rvf_segments - List file segments')); console.log(chalk.dim(' rvf_examples - List example .rvf files')); @@ -8303,7 +8305,9 @@ mcpCmd.command('tools') { name: 'rvf_delete', desc: 'Delete vectors by ID' }, { name: 'rvf_status', desc: 'Store status' }, { name: 'rvf_compact', desc: 'Compact store' }, - { name: 'rvf_derive', desc: 'COW-branch child store' }, + { name: 'rvf_derive', desc: 'Lineage-only child (no COW)' }, + { name: 'rvf_branch', desc: 'Full COW branch with Rust CowEngine' }, + { name: 'rvf_freeze', desc: 'Freeze/snapshot store' }, { name: 'rvf_segments', desc: 'List file segments' }, { name: 'rvf_examples', desc: 'Example .rvf files' }, ], diff --git a/npm/packages/ruvector/bin/mcp-server.js b/npm/packages/ruvector/bin/mcp-server.js index 8d08b536d3..07b4426fe2 100644 --- a/npm/packages/ruvector/bin/mcp-server.js +++ b/npm/packages/ruvector/bin/mcp-server.js @@ -1353,7 +1353,7 @@ const TOOLS = [ }, { name: 'rvf_derive', - description: 'Derive a child RVF store from a parent using copy-on-write branching', + description: 'Derive a child RVF store from a parent for lineage tracking (records parent hash and depth, no COW — child cannot see parent vectors). For full COW branching with parent inheritance, use rvf_branch.', inputSchema: { type: 'object', properties: { @@ -1363,6 +1363,30 @@ const TOOLS = [ required: ['parent_path', 'child_path'] } }, + { + name: 'rvf_branch', + description: 'Create a full COW (Copy-on-Write) branch from a parent RVF store using the Rust CowEngine. The child inherits all parent vectors and queries return parent ∪ child. Re-ingested vectors in the child override parent on id collision; deletes in the child hide inherited vectors. The branch stores only its own edits on disk. The parent should be frozen first via rvf_freeze for production data.', + inputSchema: { + type: 'object', + properties: { + parent_path: { type: 'string', description: 'Path to parent .rvf store to branch from' }, + child_path: { type: 'string', description: 'Path for the new branch .rvf store' }, + label: { type: 'string', description: 'Optional human-readable branch label' } + }, + required: ['parent_path', 'child_path'] + } + }, + { + name: 'rvf_freeze', + description: 'Freeze (snapshot) an RVF store, preventing further writes. Required before branching production data to guarantee parent immutability. Sets read_only flag and freezes the CowEngine epoch.', + inputSchema: { + type: 'object', + properties: { + path: { type: 'string', description: 'Path to .rvf store to freeze' } + }, + required: ['path'] + } + }, { name: 'rvf_segments', description: 'List all segments in an RVF file (VEC, INDEX, KERNEL, EBPF, WITNESS, etc.)', @@ -3369,6 +3393,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } } + case 'rvf_branch': { + try { + const safeParent = validateRvfPath(args.parent_path); + const safeChild = validateRvfPath(args.child_path); + const { openRvfStore, rvfBranch, rvfClose } = require('../dist/core/rvf-wrapper.js'); + const store = await openRvfStore(safeParent); + const childStore = await rvfBranch(store, safeChild); + const childStatus = await childStore.status(); + await rvfClose(childStore); + return { content: [{ type: 'text', text: JSON.stringify({ success: true, parent: safeParent, child: safeChild, childStatus }, null, 2) }] }; + } catch (e) { + return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true }; + } + } + + case 'rvf_freeze': { + try { + const safePath = validateRvfPath(args.path); + const { openRvfStore, rvfFreeze, rvfClose } = require('../dist/core/rvf-wrapper.js'); + const store = await openRvfStore(safePath); + await rvfFreeze(store); + await rvfClose(store); + return { content: [{ type: 'text', text: JSON.stringify({ success: true, path: safePath, readOnly: true }, null, 2) }] }; + } catch (e) { + return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }, null, 2) }], isError: true }; + } + } + case 'rvf_segments': { try { const safePath = validateRvfPath(args.path); diff --git a/npm/packages/ruvector/src/core/rvf-wrapper.ts b/npm/packages/ruvector/src/core/rvf-wrapper.ts index bd30a7b9af..8042be95a9 100644 --- a/npm/packages/ruvector/src/core/rvf-wrapper.ts +++ b/npm/packages/ruvector/src/core/rvf-wrapper.ts @@ -158,6 +158,30 @@ export async function rvfDerive( return store.derive(childPath); } +/** + * Create a full COW (Copy-on-Write) branch from a parent store. + * The child inherits all parent vectors — queries return parent ∪ child. + * Child overrides parent on id collision; deletes in child hide inherited vectors. + * Uses the Rust CowEngine with cluster-based COW and dual-graph ANN query merge. + * The parent should be frozen before branching for production data. + */ +export async function rvfBranch( + store: RvfStore, + childPath: string, +): Promise { + return store.branch(childPath); +} + +/** + * Freeze (snapshot) a store, preventing further writes. + * Required before branching to guarantee parent immutability. + */ +export async function rvfFreeze( + store: RvfStore, +): Promise { + return store.freeze(); +} + /** * Close the store, releasing the writer lock and flushing data. */