From cd95f5b3da3346e95ef7bc589f5bc2267edd544b Mon Sep 17 00:00:00 2001 From: ruv Date: Sun, 2 Aug 2026 14:51:50 -0400 Subject: [PATCH] release(npm): publish ruvector 0.2.41 Brings the ruvector npm package on main up to the 0.2.40 release content (published from an unmerged branch: metaharness SDK/CLI/MCP surface, ONNX embedder improvements, embedding provenance) and bumps to 0.2.41, published post-PR-#752 with rebuilt NAPI binaries on main. Co-Authored-By: claude-flow --- npm/package-lock.json | 154 ++++++++++++- npm/packages/ruvector/README.md | 28 ++- npm/packages/ruvector/bin/cli.js | 116 +++++++++- npm/packages/ruvector/bin/mcp-policy.js | 6 + npm/packages/ruvector/bin/mcp-server.js | 154 +++++++++++++ npm/packages/ruvector/package.json | 17 +- .../ruvector/src/core/embedding-provenance.ts | 168 +++++++++++++- .../ruvector/src/core/intelligence-engine.ts | 10 +- .../ruvector/src/core/onnx-embedder.ts | 118 +++++++++- .../ruvector/src/core/onnx-optimized.ts | 20 +- npm/packages/ruvector/src/index.ts | 1 + .../ruvector/src/metaharness/index.ts | 208 ++++++++++++++++++ npm/packages/ruvector/test/metaharness-cli.js | 65 ++++++ npm/packages/ruvector/test/metaharness-mcp.js | 103 +++++++++ npm/packages/ruvector/test/metaharness-sdk.js | 53 +++++ .../tests/embedding-space-identity.test.mjs | 72 ++++++ 16 files changed, 1257 insertions(+), 36 deletions(-) create mode 100644 npm/packages/ruvector/src/metaharness/index.ts create mode 100644 npm/packages/ruvector/test/metaharness-cli.js create mode 100644 npm/packages/ruvector/test/metaharness-mcp.js create mode 100644 npm/packages/ruvector/test/metaharness-sdk.js create mode 100644 npm/packages/ruvector/tests/embedding-space-identity.test.mjs diff --git a/npm/package-lock.json b/npm/package-lock.json index 6b115aa4d..d68483973 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -2085,6 +2085,93 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@metaharness/darwin": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@metaharness/darwin/-/darwin-0.8.0.tgz", + "integrity": "sha512-Pgefr/es0Btofh7GxQrOAg/i43ZKcLUfeD9rndOAkpA8s3ZYohSmfLerJLNsGOOKc2eTvmmauljl8QEVmKC2dw==", + "license": "MIT", + "bin": { + "metaharness-darwin": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@metaharness/flywheel": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@metaharness/flywheel/-/flywheel-0.1.7.tgz", + "integrity": "sha512-am7dROkjyS1Zkms3TOcn2LVHjwMLQXPJ6Pu1aP55q40vWJLRONGdGvnrcBL/VhMFqjQrVB57lmSn2E+s5CSZwA==", + "license": "MIT" + }, + "node_modules/@metaharness/harness": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@metaharness/harness/-/harness-0.1.0.tgz", + "integrity": "sha512-KegYx/q8qXlnaSYP0l5hgNPn8iNMl6a2eONqPi68FXd1w1jt7NswJJlt2/Ftl3Sum8qAEchxH1ZJOYFWTvJxTA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@metaharness/redblue": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@metaharness/redblue/-/redblue-0.1.4.tgz", + "integrity": "sha512-JaAk6bs3xA7Ks5RnAcZoxI3WfzpYL+Bk262SCI07w82BDOA7C6VxwGM63F7b86lRTKUVjTEnSqf7QZ3uyElT/g==", + "license": "MIT", + "bin": { + "metaharness-redblue": "dist/cli/index.js", + "redblue": "dist/cli/index.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@metaharness/router": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@metaharness/router/-/router-0.3.2.tgz", + "integrity": "sha512-LQElU6mUrWd3ffJ5bwoonEIgw7oFQZpwZaehffyl/Pg+iozpRjIBPEXdb4uTOBnKH+yPiTEWKc+h9AiZr9Os9w==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@ruvector/tiny-dancer": "^0.1.21" + }, + "peerDependenciesMeta": { + "@ruvector/tiny-dancer": { + "optional": true + } + } + }, + "node_modules/@metaharness/weight-eft": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@metaharness/weight-eft/-/weight-eft-0.1.1.tgz", + "integrity": "sha512-GSg0APPAbRK93OzrzlE+R8hfEK+I5+Zhmh0Z28RC9Mk5/MjhPo3shqINO7ye8VPGYHIO4rars9FwCWbe/V4cEQ==", + "license": "MIT", + "bin": { + "weight-eft": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@metaharness/workspace-lens": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@metaharness/workspace-lens/-/workspace-lens-0.1.1.tgz", + "integrity": "sha512-BkEdTvjD2PEhkGTCmtTIPnoeaOsxvzFtsFEAurwVKlVOu4hQetHeXDfiyAYheWg8Cp4fcGcr7E5MO1ptQveQ2g==", + "license": "MIT" + }, + "node_modules/@metaharness/workspace-probe": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@metaharness/workspace-probe/-/workspace-probe-0.1.1.tgz", + "integrity": "sha512-kHL7T16476a5obVunbZ2NYqK7YY/1qtZTaQC1KyXmFh/7dRNselqGAilawspuD86F9U1H/LMCDvchDj3Vh5duw==", + "license": "MIT", + "dependencies": { + "@metaharness/workspace-lens": "^0.1.0" + }, + "bin": { + "workspace-probe": "bin/workspace-probe.mjs" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", @@ -10780,12 +10867,17 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -12888,7 +12980,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, "license": "MIT", "dependencies": { "kleur": "^3.0.3", @@ -13880,7 +13971,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, "license": "MIT" }, "node_modules/slash": { @@ -18370,9 +18460,17 @@ } }, "packages/ruvector": { - "version": "0.2.39", + "version": "0.2.41", "license": "MIT", "dependencies": { + "@metaharness/darwin": "0.8.0", + "@metaharness/flywheel": "0.1.7", + "@metaharness/harness": "0.1.0", + "@metaharness/redblue": "0.1.4", + "@metaharness/router": "0.3.2", + "@metaharness/weight-eft": "0.1.1", + "@metaharness/workspace-lens": "0.1.1", + "@metaharness/workspace-probe": "0.1.1", "@modelcontextprotocol/sdk": "^1.30.0", "@ruvector/attention": "^0.1.3", "@ruvector/core": "^0.1.25", @@ -18380,6 +18478,7 @@ "@ruvector/sona": "^0.1.4", "chalk": "^4.1.2", "commander": "^11.1.0", + "metaharness": "0.4.2", "ora": "^5.4.1" }, "bin": { @@ -18390,7 +18489,7 @@ "typescript": "^5.9.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" }, "optionalDependencies": { "@ruvector/rvf": "^0.1.0", @@ -18550,6 +18649,50 @@ "@ruvector/rvf-wasm": "^0.1.5" } }, + "packages/ruvector/node_modules/metaharness": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/metaharness/-/metaharness-0.4.2.tgz", + "integrity": "sha512-h94B47zBEuNEfKLFMZMV/fRnXBrkwV6L8kMukgioB/9621k6Lm6s2lR2gBKl3BAT1sfN/DYqhsqAJc+6H5PTGQ==", + "license": "MIT", + "dependencies": { + "@metaharness/darwin": "^0.2.2", + "@metaharness/flywheel": "^0.1.1", + "@metaharness/redblue": "^0.1.1", + "@metaharness/weight-eft": "^0.1.0", + "kolorist": "^1.8.0", + "prompts": "^2.4.2" + }, + "bin": { + "harness": "dist/harness-bin.js", + "metaharness": "dist/bin.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "@ruvector/ruvllm": "^2.5.6" + }, + "peerDependencies": { + "@metaharness/kernel": "^0.1.0" + }, + "peerDependenciesMeta": { + "@metaharness/kernel": { + "optional": true + } + } + }, + "packages/ruvector/node_modules/metaharness/node_modules/@metaharness/darwin": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@metaharness/darwin/-/darwin-0.2.8.tgz", + "integrity": "sha512-B8tF7IrrSxwKS6fEPEL6N2Juth9WWn+hppLUtUYPTJ2vcHzzZPIg2cS5T9qTyNNuANlTSWnQHnvzlfvYdGNfeQ==", + "license": "MIT", + "bin": { + "metaharness-darwin": "dist/cli.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, "packages/ruvllm": { "name": "@ruvector/ruvllm", "version": "2.6.1", @@ -19193,6 +19336,7 @@ "packages/spiking-neural": { "name": "@ruvector/spiking-neural", "version": "1.0.3", + "hasInstallScript": true, "license": "MIT", "bin": { "snn": "bin/cli.js", diff --git a/npm/packages/ruvector/README.md b/npm/packages/ruvector/README.md index 6ce8f138f..54d4dc29a 100644 --- a/npm/packages/ruvector/README.md +++ b/npm/packages/ruvector/README.md @@ -110,19 +110,25 @@ Precedence is **DENY > ALLOW/PROFILE > allow-all**. With no policy set, all tool are available (backward compatible). Inspect the active posture with `npx ruvector harness status --json` (see `mcp.accessControl`). -### Harness Router (ADR-256) +### MetaHarness, Darwin, and Flywheel -`ruvector harness` surfaces the unified routing/agentic primitives ruvector ships — -cost-optimal model routing (Tiny Dancer), semantic routing, hooks routing, the MCP -server, witness-signed provenance, and SONA memory — in one place: +`ruvector` ships the pinned MetaHarness capability set as direct dependencies. +They load lazily, so ordinary vector operations do not pay their startup cost. +Darwin candidate execution is not exposed through MCP and requires an explicit +`--execute` flag in the CLI. ```bash -npx ruvector harness status # human-readable surface + availability -npx ruvector harness status --json # structured, for tooling/CI +npx ruvector harness doctor --json +npx ruvector harness route --examples examples.json --prices prices.json --query query.json +npx ruvector harness flywheel verify replay-bundle.json +npx ruvector harness flywheel gate promotion-evidence.json +npx ruvector harness darwin evolution-config.json --execute ``` -Memory + learning loops use a stable namespace (default `ruvector`), overridable per -deployment with `RUVECTOR_MEMORY_NAMESPACE` and reported under `memory.namespace`. +The SDK exports capability discovery, routing, promotion and replay verification, +Flywheel and Darwin runners, the algorithmic harness kernel, workspace scoring, +reward-hack scanning, and credential guards. The read-only MCP profile exposes only +non-executing inspection and evaluation tools. ### Brain AGI Commands @@ -1976,7 +1982,7 @@ Performance: **<0.5ms latency**, **50K+ ops/sec** Performance: **10-50ms latency**, **~1K ops/sec** -**Node.js 18+ required** for all platforms. +**Node.js 20+ required** for all platforms. ## 🔧 Building from Source @@ -2218,7 +2224,7 @@ npm install --include=optional ruvector # Verify platform npx ruvector info -# Check Node.js version (18+ required) +# Check Node.js version (20+ required) node --version ``` @@ -2234,7 +2240,7 @@ If you're using WASM fallback and need better performance: - **Alpine Linux**: Uses WASM fallback (musl not supported) - **Windows ARM**: Not yet supported, uses WASM fallback -- **Node.js < 18**: Not supported, upgrade to Node.js 18+ +- **Node.js < 20**: Not supported, upgrade to Node.js 20+ ## 📚 Documentation diff --git a/npm/packages/ruvector/bin/cli.js b/npm/packages/ruvector/bin/cli.js index f24227807..4a739d98e 100755 --- a/npm/packages/ruvector/bin/cli.js +++ b/npm/packages/ruvector/bin/cli.js @@ -10220,13 +10220,25 @@ const optimizeCmd = program.command('optimize') }); // ============================================================================= -// Harness Commands - unified "harness router" surface (ADR-256) -// Borrows metaharness concepts using primitives ruvector already ships: -// cost router (tiny-dancer) + semantic router + hooks routing + MCP + witness -// Read-only status surface; degrades gracefully when optional deps are absent. +// Harness Commands - pinned MetaHarness dependencies plus RuVector primitives. +// Dependencies are loaded lazily so unrelated CLI commands keep their startup +// profile. Darwin remains behind an explicit execution flag. // ============================================================================= -function buildHarnessSurface() { +function loadMetaHarnessSdk() { + return require('../dist/metaharness/index.js'); +} + +function readHarnessJson(file, label) { + const resolved = path.resolve(file); + try { + return JSON.parse(fs.readFileSync(resolved, 'utf8')); + } catch (error) { + throw new Error(`Unable to read ${label} JSON at ${resolved}: ${error.message}`); + } +} + +async function buildHarnessSurface() { const primitives = {}; // Cost-optimal model router — Tiny Dancer FastGRNN (ADR-252) @@ -10303,10 +10315,22 @@ function buildHarnessSurface() { namespace: (process.env.RUVECTOR_MEMORY_NAMESPACE || 'ruvector').trim() || 'ruvector', }; + const capabilities = await loadMetaHarnessSdk().getMetaHarnessCapabilities(); + for (const capability of capabilities) { + primitives[`metaharness.${capability.name}`] = { + name: capability.package, + role: capability.detail, + available: capability.available, + version: capability.version, + directDependency: true, + }; + } + const values = Object.values(primitives); return { adr: 'ADR-256', - decision: 'borrow metaharness concepts using primitives ruvector already ships', + adrs: ['ADR-256', 'ADR-275'], + decision: 'ship pinned MetaHarness, Darwin, Flywheel, routing, safety, and workspace capabilities as direct npm dependencies', primitives, summary: { available: values.filter((p) => p.available).length, @@ -10317,10 +10341,10 @@ function buildHarnessSurface() { const harnessCmd = program .command('harness') - .description('Unified "harness router" surface — cost router + semantic router + hooks routing + MCP + witness (ADR-256)'); + .description('MetaHarness, Darwin, Flywheel, routing, safety, and workspace capabilities'); -function printHarnessStatus(opts) { - const surface = buildHarnessSurface(); +async function printHarnessStatus(opts) { + const surface = await buildHarnessSurface(); if (opts && opts.json) { console.log(JSON.stringify(surface, null, 2)); return; @@ -10343,12 +10367,80 @@ function printHarnessStatus(opts) { harnessCmd .command('status') .alias('info') - .description('Show the unified harness routing surface and primitive availability') + .description('Load and verify every pinned harness capability') .option('--json', 'Output as JSON') - .action((opts) => printHarnessStatus(opts)); + .action(async (opts) => printHarnessStatus(opts)); + +harnessCmd + .command('doctor') + .description('Alias for harness status with dependency load checks') + .option('--json', 'Output as JSON') + .action(async (opts) => printHarnessStatus(opts)); + +harnessCmd + .command('route') + .description('Choose the cheapest model predicted to clear a quality bar') + .requiredOption('--examples ', 'JSON array of {embedding,scores} examples') + .requiredOption('--prices ', 'JSON object mapping model IDs to cost per million tokens') + .requiredOption('--query ', 'JSON query embedding') + .option('--quality-bar ', 'Minimum predicted quality', Number, 0.9) + .option('-k ', 'Nearest neighbours used for prediction', Number) + .action(async (opts) => { + const result = await loadMetaHarnessSdk().routeWithMetaHarness({ + rows: readHarnessJson(opts.examples, 'routing examples'), + prices: readHarnessJson(opts.prices, 'routing prices'), + queryEmbedding: readHarnessJson(opts.query, 'query embedding'), + qualityBar: opts.qualityBar, + ...(opts.k === undefined ? {} : { k: opts.k }), + }); + console.log(JSON.stringify(result, null, 2)); + }); + +const flywheelCmd = harnessCmd + .command('flywheel') + .description('Inspect signed Flywheel evidence without executing candidate code'); + +flywheelCmd + .command('verify ') + .description('Verify a replay bundle and its signed lineage') + .option('--gate-fingerprint ', 'Require a pinned promotion-gate fingerprint') + .action(async (bundle, opts) => { + const result = await loadMetaHarnessSdk().verifyMetaHarnessReplay( + readHarnessJson(bundle, 'replay bundle'), + opts.gateFingerprint ? { pinnedGateFingerprint: opts.gateFingerprint } : {}, + ); + console.log(JSON.stringify(result, null, 2)); + if (!result.pass) process.exitCode = 1; + }); + +flywheelCmd + .command('gate ') + .description('Evaluate the frozen conjunctive promotion rule') + .action(async (evidence) => { + const result = await loadMetaHarnessSdk().evaluateMetaHarnessPromotion( + readHarnessJson(evidence, 'promotion evidence'), + ); + console.log(JSON.stringify(result, null, 2)); + if (!result.promote) process.exitCode = 1; + }); + +harnessCmd + .command('darwin ') + .description('Run Darwin evolution from a JSON config (requires explicit --execute)') + .option('--execute', 'Authorize candidate execution for this invocation') + .action(async (config, opts) => { + if (!opts.execute) { + throw new Error('Refusing to run Darwin without --execute'); + } + const result = await loadMetaHarnessSdk().runMetaHarnessDarwin( + readHarnessJson(config, 'Darwin config'), + { execute: true }, + ); + console.log(JSON.stringify(result, null, 2)); + }); // Bare `ruvector harness` defaults to status -harnessCmd.action(() => printHarnessStatus({})); +harnessCmd.action(async () => printHarnessStatus({})); // Only drive the CLI when executed directly; when required (e.g. by tests) // expose the store-durability internals instead of parsing argv. diff --git a/npm/packages/ruvector/bin/mcp-policy.js b/npm/packages/ruvector/bin/mcp-policy.js index a1d282aef..f061e54c3 100644 --- a/npm/packages/ruvector/bin/mcp-policy.js +++ b/npm/packages/ruvector/bin/mcp-policy.js @@ -23,6 +23,12 @@ const MCP_PROFILES = { readonly: [ 'ruvector', + 'metaharness_status', + 'metaharness_route', + 'metaharness_replay_verify', + 'metaharness_flywheel_gate', + 'metaharness_workspace_probe', + 'metaharness_reward_hack_scan', 'hooks_stats', 'hooks_recall', 'hooks_route', diff --git a/npm/packages/ruvector/bin/mcp-server.js b/npm/packages/ruvector/bin/mcp-server.js index cbc02fc7d..423c7432a 100644 --- a/npm/packages/ruvector/bin/mcp-server.js +++ b/npm/packages/ruvector/bin/mcp-server.js @@ -30,6 +30,12 @@ const { execFileSync } = require('child_process'); const { buildToolPolicy, isToolAllowed, filterAllowedTools } = require('./mcp-policy.js'); const MCP_TOOL_POLICY = buildToolPolicy(process.env); +// MetaHarness packages are ESM-only; the compiled SDK adapter preserves native +// import() and loads them only when one of these tools is called. +function loadMetaHarnessSdk() { + return require('../dist/metaharness/index.js'); +} + // ── Security Helpers ──────────────────────────────────────────────────────── /** @@ -598,6 +604,78 @@ const intel = new Intelligence(); // Define tools const TOOLS = [ + { + name: 'metaharness_status', + description: 'Load and report the pinned MetaHarness, Darwin, Flywheel, router, safety, and workspace capabilities', + inputSchema: { type: 'object', properties: {}, required: [] } + }, + { + name: 'metaharness_route', + description: 'Choose the cheapest model predicted to meet a quality bar from labelled embedding examples', + inputSchema: { + type: 'object', + properties: { + rows: { + type: 'array', + items: { + type: 'object', + properties: { + embedding: { type: 'array', items: { type: 'number' } }, + scores: { type: 'object', additionalProperties: { type: 'number' } } + }, + required: ['embedding', 'scores'] + } + }, + prices: { type: 'object', additionalProperties: { type: 'number' } }, + query_embedding: { type: 'array', items: { type: 'number' } }, + quality_bar: { type: 'number', default: 0.9 }, + k: { type: 'number' } + }, + required: ['rows', 'prices', 'query_embedding'] + } + }, + { + name: 'metaharness_replay_verify', + description: 'Verify a Flywheel replay bundle, signed lineage, parent continuity, and optional pinned gate fingerprint', + inputSchema: { + type: 'object', + properties: { + bundle: { type: 'object' }, + gate_fingerprint: { type: 'string' } + }, + required: ['bundle'] + } + }, + { + name: 'metaharness_flywheel_gate', + description: 'Evaluate promotion evidence with the frozen conjunctive Flywheel gate', + inputSchema: { + type: 'object', + properties: { evidence: { type: 'object' } }, + required: ['evidence'] + } + }, + { + name: 'metaharness_workspace_probe', + description: 'Score workspace receipts for drift, safety flags, critical triggers, and clean fraction', + inputSchema: { + type: 'object', + properties: { + receipts: { type: 'array', items: { type: 'object' } }, + options: { type: 'object' } + }, + required: ['receipts'] + } + }, + { + name: 'metaharness_reward_hack_scan', + description: 'Scan an archived Darwin trajectory for gold reads, verification tampering, and sandbox escape', + inputSchema: { + type: 'object', + properties: { trajectory: { type: 'object' } }, + required: ['trajectory'] + } + }, { name: 'hooks_stats', description: 'Get RuVector intelligence statistics including learned patterns, memories, and trajectories', @@ -1816,6 +1894,82 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { try { switch (name) { + case 'metaharness_status': { + const capabilities = await loadMetaHarnessSdk().getMetaHarnessCapabilities(); + return { + content: [{ + type: 'text', + text: JSON.stringify({ + success: capabilities.every((capability) => capability.available), + capabilities + }, null, 2) + }] + }; + } + + case 'metaharness_route': { + const result = await loadMetaHarnessSdk().routeWithMetaHarness({ + rows: args.rows, + prices: args.prices, + queryEmbedding: args.query_embedding, + qualityBar: args.quality_bar ?? 0.9, + ...(args.k === undefined ? {} : { k: args.k }) + }); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, route: result }, null, 2) + }] + }; + } + + case 'metaharness_replay_verify': { + const result = await loadMetaHarnessSdk().verifyMetaHarnessReplay( + args.bundle, + args.gate_fingerprint ? { pinnedGateFingerprint: args.gate_fingerprint } : {} + ); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: result.pass, verification: result }, null, 2) + }], + isError: !result.pass + }; + } + + case 'metaharness_flywheel_gate': { + const result = await loadMetaHarnessSdk().evaluateMetaHarnessPromotion(args.evidence); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, decision: result }, null, 2) + }] + }; + } + + case 'metaharness_workspace_probe': { + const result = await loadMetaHarnessSdk().scoreMetaHarnessWorkspace( + args.receipts, + args.options || {} + ); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, score: result }, null, 2) + }] + }; + } + + case 'metaharness_reward_hack_scan': { + const result = await loadMetaHarnessSdk().scanMetaHarnessRewardHacks(args.trajectory); + return { + content: [{ + type: 'text', + text: JSON.stringify({ success: true, ...result }, null, 2) + }] + }; + } + case 'hooks_stats': { const stats = intel.stats(); return { diff --git a/npm/packages/ruvector/package.json b/npm/packages/ruvector/package.json index a032656ed..1fc15dfb7 100644 --- a/npm/packages/ruvector/package.json +++ b/npm/packages/ruvector/package.json @@ -1,7 +1,7 @@ { "name": "ruvector", - "version": "0.2.39", - "description": "Self-learning vector database for Node.js \u2014 hybrid search, Graph RAG, FlashAttention-3, HNSW, 50+ attention mechanisms", + "version": "0.2.41", + "description": "Self-learning vector database for Node.js — hybrid search, Graph RAG, FlashAttention-3, HNSW, 50+ attention mechanisms", "main": "dist/index.js", "types": "dist/index.d.ts", "bin": { @@ -12,7 +12,7 @@ "verify-dist": "node scripts/verify-dist.js", "prepack": "npm run build && npm run verify-dist", "prepublishOnly": "npm run build && npm run verify-dist", - "test": "node test/integration.js && node test/cli-commands.js && node test/db-workflow.js && node test/mcp-stdio.js && node test/sigterm-cleanup.js && node test/mcp-policy.js && node test/mcp-command-security.js && node test/mcp-handshake.js && node test/startup-budget.js" + "test": "node test/integration.js && node test/metaharness-sdk.js && node test/cli-commands.js && node test/metaharness-cli.js && node test/db-workflow.js && node test/mcp-stdio.js && node test/metaharness-mcp.js && node test/sigterm-cleanup.js && node test/mcp-policy.js && node test/mcp-command-security.js && node test/mcp-handshake.js && node test/startup-budget.js" }, "keywords": [ "vector", @@ -71,6 +71,14 @@ "directory": "npm/packages/ruvector" }, "dependencies": { + "@metaharness/darwin": "0.8.0", + "@metaharness/flywheel": "0.1.7", + "@metaharness/harness": "0.1.0", + "@metaharness/redblue": "0.1.4", + "@metaharness/router": "0.3.2", + "@metaharness/weight-eft": "0.1.1", + "@metaharness/workspace-lens": "0.1.1", + "@metaharness/workspace-probe": "0.1.1", "@modelcontextprotocol/sdk": "^1.30.0", "@ruvector/attention": "^0.1.3", "@ruvector/core": "^0.1.25", @@ -78,6 +86,7 @@ "@ruvector/sona": "^0.1.4", "chalk": "^4.1.2", "commander": "^11.1.0", + "metaharness": "0.4.2", "ora": "^5.4.1" }, "optionalDependencies": { @@ -118,6 +127,6 @@ } }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } } diff --git a/npm/packages/ruvector/src/core/embedding-provenance.ts b/npm/packages/ruvector/src/core/embedding-provenance.ts index d21bdfc48..f2eb1a052 100644 --- a/npm/packages/ruvector/src/core/embedding-provenance.ts +++ b/npm/packages/ruvector/src/core/embedding-provenance.ts @@ -21,8 +21,146 @@ // Types // ============================================================================ -export type PrefixPolicy = 'none' | 'required' | 'query-recommended'; +import { createHash } from 'crypto'; + +export type PrefixPolicy = 'none' | 'required' | 'query-recommended' | 'custom'; export type EmbedTextKind = 'query' | 'passage'; +export type EmbeddingRole = EmbedTextKind; +export type EmbeddingRolePolicy = 'symmetric' | 'asymmetric'; +export type OutputDtype = 'f32' | 'f16' | 'bf16' | 'i8' | 'u8'; +export type EmbeddingDistanceMetric = 'cosine' | 'dot' | 'euclidean' | 'manhattan'; +export type PoolingStrategy = + | 'mean' + | 'cls' + | 'last-token' + | 'weighted-mean' + | { custom: string; implementation_revision: string }; + +/** Canonical cross-runtime provenance contract from ADR-274. */ +export interface EmbeddingSpaceIdentity { + schema_version: 1; + provider: string; + model_id: string; + model_artifact_sha256: string; + model_graph_sha256: string; + tokenizer_sha256: string; + prompt_template_sha256: string; + pooling_strategy: PoolingStrategy; + normalize: boolean; + truncation_tokens: number; + output_dimension: number; + output_dtype: OutputDtype; + runtime_revision: string; + distance_metric: EmbeddingDistanceMetric; + role_policy: EmbeddingRolePolicy; + prefix_policy: PrefixPolicy; + prefix_policy_version: number; +} + +const SHA256_RE = /^[0-9a-f]{64}$/; +const EMBEDDING_SPACE_DOMAIN = 'ruvector.embedding-space.v1\0'; + +function canonicalize(value: unknown): string { + if (value === null || typeof value === 'boolean' || typeof value === 'number') { + return JSON.stringify(value); + } + if (typeof value === 'string') return JSON.stringify(value); + if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]`; + if (typeof value === 'object') { + const object = value as Record; + return `{${Object.keys(object) + .sort() + .map(key => `${JSON.stringify(key)}:${canonicalize(object[key])}`) + .join(',')}}`; + } + throw new TypeError(`unsupported canonical JSON value: ${typeof value}`); +} + +/** Validate the complete identity before it is used as a cache/store key. */ +export function assertEmbeddingSpaceIdentity(identity: EmbeddingSpaceIdentity): void { + const requiredStrings: Array = [ + 'provider', 'model_id', 'runtime_revision', + ]; + for (const key of requiredStrings) { + if ( + typeof identity[key] !== 'string' || + !(identity[key] as string) || + (identity[key] as string) !== (identity[key] as string).normalize('NFC') + ) { + throw new TypeError(`invalid embedding identity field: ${key}`); + } + } + for (const key of [ + 'model_artifact_sha256', 'model_graph_sha256', 'tokenizer_sha256', 'prompt_template_sha256', + ] as const) { + if (!SHA256_RE.test(identity[key])) throw new TypeError(`invalid SHA-256 field: ${key}`); + } + if (identity.schema_version !== 1) throw new TypeError('unsupported embedding identity schema_version'); + if (!Number.isInteger(identity.truncation_tokens) || identity.truncation_tokens < 1) { + throw new TypeError('truncation_tokens must be a positive integer'); + } + if (!Number.isInteger(identity.output_dimension) || identity.output_dimension < 1) { + throw new TypeError('output_dimension must be a positive integer'); + } + if (!Number.isInteger(identity.prefix_policy_version) || identity.prefix_policy_version < 1) { + throw new TypeError('prefix_policy_version must be a positive integer'); + } +} + +/** RFC-8785-compatible for this integer/string/boolean identity schema. */ +export function canonicalEmbeddingSpaceJson(identity: EmbeddingSpaceIdentity): string { + assertEmbeddingSpaceIdentity(identity); + return canonicalize(identity); +} + +export function embeddingSpaceId(identity: EmbeddingSpaceIdentity): string { + return createHash('sha256') + .update(EMBEDDING_SPACE_DOMAIN, 'utf8') + .update(canonicalEmbeddingSpaceJson(identity), 'utf8') + .digest('hex'); +} + +/** Cache keys are separated by complete space identity and operation role. */ +export function embeddingCacheKey( + identity: EmbeddingSpaceIdentity, + role: EmbeddingRole, + text: string +): string { + const textHash = createHash('sha256').update(text, 'utf8').digest('hex'); + return `${embeddingSpaceId(identity)}:${role}:${textHash}`; +} + +export class EmbeddingSpaceMismatchError extends Error { + readonly code = 'ERR_EMBEDDING_SPACE_MISMATCH'; + constructor(readonly storedSpaceId: string, readonly activeSpaceId: string) { + super(`Embedding-space mismatch: store=${storedSpaceId}, active=${activeSpaceId}; text embedding and corpus mutation are disabled`); + this.name = 'EmbeddingSpaceMismatchError'; + } +} + +export interface EmbeddingSpaceAccess { + textEmbedding: boolean; + corpusMutation: boolean; + vectorRead: true; + inspect: true; + verify: true; + export: true; +} + +export function embeddingSpaceAccess( + stored: EmbeddingSpaceIdentity, + active: EmbeddingSpaceIdentity +): EmbeddingSpaceAccess { + const compatible = embeddingSpaceId(stored) === embeddingSpaceId(active); + return { + textEmbedding: compatible, + corpusMutation: compatible, + vectorRead: true, + inspect: true, + verify: true, + export: true, + }; +} /** Embedder identity classes. `modelId` carries the exact model. */ export type EmbedderKind = 'onnx-minilm' | 'onnx' | 'hash'; @@ -74,6 +212,16 @@ export const MODEL_PREFIXES: Record = { 'bge-small-en-v1.5': { prefixPolicy: 'query-recommended', queryPrefix: BGE_QUERY_INSTRUCTION, passagePrefix: '' }, 'bge-base-en-v1.5': { prefixPolicy: 'query-recommended', queryPrefix: BGE_QUERY_INSTRUCTION, passagePrefix: '' }, 'gte-small': { ...NO_PREFIX }, + 'Qwen/Qwen3-Embedding-0.6B': { + prefixPolicy: 'required', + queryPrefix: 'Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:', + passagePrefix: '', + }, + 'Qwen/Qwen3-Embedding-4B': { + prefixPolicy: 'required', + queryPrefix: 'Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery:', + passagePrefix: '', + }, }; /** @@ -89,6 +237,16 @@ export function getModelPrefixSpec(modelId: string | null | undefined): ModelPre return NO_PREFIX; } +/** Authoritative registry lookup for constructing a real embedding provider. */ +export function requireModelPrefixSpec(modelId: string): ModelPrefixSpec { + if (!Object.prototype.hasOwnProperty.call(MODEL_PREFIXES, modelId)) { + throw new Error( + `unregistered embedding model '${modelId}'; supply an exact registry fixture before loading`, + ); + } + return MODEL_PREFIXES[modelId]; +} + /** * Pure prefix application (D4): the exact text handed to the tokenizer for a * query/passage embed of `text` under `modelId`'s registered policy. @@ -100,6 +258,14 @@ export function prefixText(modelId: string | null | undefined, kind: EmbedTextKi return prefix ? prefix + text : text; } +/** Hash of the exact registered query/passage template bundle. */ +export function promptTemplateHash(spec: ModelPrefixSpec): string { + return createHash('sha256').update(canonicalize({ + passage_template: `${spec.passagePrefix}{text}`, + query_template: `${spec.queryPrefix}{text}`, + }), 'utf8').digest('hex'); +} + /** Embedder family for an ONNX model id. */ export function embedderKindForModel(modelId: string | null | undefined): EmbedderKind { return modelId && modelId.startsWith('all-MiniLM') ? 'onnx-minilm' : 'onnx'; diff --git a/npm/packages/ruvector/src/core/intelligence-engine.ts b/npm/packages/ruvector/src/core/intelligence-engine.ts index ce195e876..0cf7cdb20 100644 --- a/npm/packages/ruvector/src/core/intelligence-engine.ts +++ b/npm/packages/ruvector/src/core/intelligence-engine.ts @@ -13,9 +13,10 @@ import { FastAgentDB, Episode, Trajectory, EpisodeSearchResult } from './agentdb-fast'; import { SonaEngine, SonaConfig, LearnedPattern, SonaStats, isSonaAvailable } from './sona-wrapper'; -import { OnnxEmbedder, OnnxEmbedderConfig, isOnnxAvailable, initOnnxEmbedder, getEmbedderProvenance, embedBulk, shutdownParallelEmbedder } from './onnx-embedder'; +import { OnnxEmbedder, OnnxEmbedderConfig, isOnnxAvailable, initOnnxEmbedder, getEmbedderProvenance, getEmbeddingSpaceIdentity, getEmbeddingSpaceId, embedBulk, shutdownParallelEmbedder } from './onnx-embedder'; import { EmbeddingProvenance, + EmbeddingSpaceIdentity, resolveEmbedderSelection, warnHashFallbackOnce, } from './embedding-provenance'; @@ -1212,6 +1213,11 @@ export class IntelligenceEngine { }; } + /** Full ADR-274 identity for the active real embedder. */ + getActiveEmbeddingSpaceIdentity(): EmbeddingSpaceIdentity | null { + return this.onnxReady ? getEmbeddingSpaceIdentity() : null; + } + // ========================================================================= // Persistence // ========================================================================= @@ -1225,6 +1231,8 @@ export class IntelligenceEngine { exported: new Date().toISOString(), config: this.config, embeddingProvenance: this.getActiveProvenance(), + embeddingSpaceIdentity: this.getActiveEmbeddingSpaceIdentity(), + embeddingSpaceId: this.onnxReady ? getEmbeddingSpaceId() : null, memories: Array.from(this.memories.values()), diff --git a/npm/packages/ruvector/src/core/onnx-embedder.ts b/npm/packages/ruvector/src/core/onnx-embedder.ts index b5dcb1559..3398e39fa 100644 --- a/npm/packages/ruvector/src/core/onnx-embedder.ts +++ b/npm/packages/ruvector/src/core/onnx-embedder.ts @@ -20,11 +20,17 @@ import { pathToFileURL } from 'url'; import { createRequire } from 'module'; import { EmbeddingProvenance, + EmbeddingSpaceIdentity, EmbedTextKind, + assertEmbeddingSpaceIdentity, embedderKindForModel, + embeddingSpaceId, getModelPrefixSpec, prefixText, + promptTemplateHash, + requireModelPrefixSpec, } from './embedding-provenance'; +import { createHash } from 'crypto'; // Extend globalThis type for ESM require compatibility declare global { @@ -69,6 +75,8 @@ export interface OnnxEmbedderConfig { numWorkers?: number; /** Minimum batch size to use parallel processing (default: 4) */ parallelThreshold?: number; + /** Required override for a custom pinned artifact revision. */ + embeddingSpaceIdentity?: EmbeddingSpaceIdentity; } // Capability detection @@ -107,10 +115,45 @@ let bundledPool: any = null; // embedding-provenance record (D0). let loadedModelId: string | null = null; let loadedNormalize = true; +let loadedEmbeddingSpaceIdentity: EmbeddingSpaceIdentity | null = null; // Default model const DEFAULT_MODEL = 'all-MiniLM-L6-v2'; +export function deriveOnnxEmbeddingSpaceIdentity( + modelId: string, + modelBytes: Uint8Array, + tokenizerJson: string, + maxLength: number, + normalize: boolean, + dimension: number, +): EmbeddingSpaceIdentity { + const modelSha = createHash('sha256').update(modelBytes).digest('hex'); + const tokenizerSha = createHash('sha256').update(tokenizerJson, 'utf8').digest('hex'); + const prefixSpec = requireModelPrefixSpec(modelId); + const identity: EmbeddingSpaceIdentity = { + schema_version: 1, + provider: 'ruvector-onnx-wasm', + model_id: modelId, + model_artifact_sha256: modelSha, + model_graph_sha256: modelSha, + tokenizer_sha256: tokenizerSha, + prompt_template_sha256: promptTemplateHash(prefixSpec), + pooling_strategy: 'mean', + normalize, + truncation_tokens: maxLength, + output_dimension: dimension, + output_dtype: 'f32', + runtime_revision: 'ruvector-onnx-wasm-v1', + distance_metric: 'cosine', + role_policy: prefixSpec.queryPrefix === prefixSpec.passagePrefix ? 'symmetric' : 'asymmetric', + prefix_policy: prefixSpec.prefixPolicy, + prefix_policy_version: 1, + }; + assertEmbeddingSpaceIdentity(identity); + return identity; +} + /** * Check if the ONNX embedder is *available* — i.e. the bundled WASM files are * present and the embedder can be initialized. @@ -278,6 +321,32 @@ export async function initOnnxEmbedder(config: OnnxEmbedderConfig = {}): Promise embedder = wasmModule.WasmEmbedder.withConfig(modelBytes, tokenizerJson, embedderConfig); + const derivedIdentity = deriveOnnxEmbeddingSpaceIdentity( + modelId, + modelBytes, + tokenizerJson, + loadedMaxLength, + config.normalize !== false, + embedder.dimension(), + ); + const supplied = config.embeddingSpaceIdentity; + if (supplied) { + assertEmbeddingSpaceIdentity(supplied); + for (const key of [ + 'model_id', 'model_artifact_sha256', 'model_graph_sha256', 'tokenizer_sha256', + 'prompt_template_sha256', 'normalize', 'truncation_tokens', 'output_dimension', + 'pooling_strategy', 'role_policy', 'prefix_policy', + ] as const) { + if (JSON.stringify(supplied[key]) !== JSON.stringify(derivedIdentity[key])) { + throw new Error(`embeddingSpaceIdentity disagrees with loaded ONNX field: ${key}`); + } + } + loadedEmbeddingSpaceIdentity = supplied; + } else { + assertEmbeddingSpaceIdentity(derivedIdentity); + loadedEmbeddingSpaceIdentity = derivedIdentity; + } + // Detect SIMD capability detectSimd(); console.error(`ONNX embedder ready: ${embedder.dimension()}d, SIMD: ${simdAvailable}`); @@ -360,11 +429,29 @@ export async function embedPassage(text: string): Promise { return embedKind('passage', text); } +export async function embedFor(role: EmbedTextKind, text: string): Promise { + if (role !== 'query' && role !== 'passage') throw new TypeError(`unknown embedding role: ${role}`); + return embedKind(role, text); +} + /** * Generate embeddings for multiple texts * Uses parallel workers automatically for batches >= parallelThreshold */ export async function embedBatch(texts: string[]): Promise { + return embedBatchFor('passage', texts); +} + +export async function embedQueryBatch(texts: string[]): Promise { + return embedBatchFor('query', texts); +} + +export async function embedPassageBatch(texts: string[]): Promise { + return embedBatchFor('passage', texts); +} + +export async function embedBatchFor(kind: EmbedTextKind, texts: string[]): Promise { + if (kind !== 'query' && kind !== 'passage') throw new TypeError(`unknown embedding role: ${kind}`); if (!isInitialized) { await initOnnxEmbedder(); } @@ -373,7 +460,7 @@ export async function embedBatch(texts: string[]): Promise { } // ADR-210 D4: batch embedding is the passage path (embed() === embedPassage()). - const prepared = texts.map(t => prefixText(loadedModelId ?? DEFAULT_MODEL, 'passage', t)); + const prepared = texts.map(t => prefixText(loadedModelId ?? DEFAULT_MODEL, kind, t)); const start = performance.now(); @@ -500,6 +587,17 @@ export function getEmbedderProvenance(): EmbeddingProvenance | null { }; } +/** Complete immutable ADR-274 identity, or null before model initialization. */ +export function getEmbeddingSpaceIdentity(): EmbeddingSpaceIdentity | null { + return loadedEmbeddingSpaceIdentity ? { ...loadedEmbeddingSpaceIdentity } : null; +} + +export function getEmbeddingSpaceId(): string | null { + return loadedEmbeddingSpaceIdentity + ? embeddingSpaceId(loadedEmbeddingSpaceIdentity) + : null; +} + /** * Get embedder stats including SIMD and parallel capabilities */ @@ -666,11 +764,29 @@ export class OnnxEmbedder { return result.embedding; } + async embedFor(role: EmbedTextKind, text: string): Promise { + const result = await embedFor(role, text); + return result.embedding; + } + async embedBatch(texts: string[]): Promise { const results = await embedBatch(texts); return results.map(r => r.embedding); } + async embedBatchFor(role: EmbedTextKind, texts: string[]): Promise { + const results = await embedBatchFor(role, texts); + return results.map(r => r.embedding); + } + + async embedQueryBatch(texts: string[]): Promise { + return this.embedBatchFor('query', texts); + } + + async embedPassageBatch(texts: string[]): Promise { + return this.embedBatchFor('passage', texts); + } + async similarity(text1: string, text2: string): Promise { const result = await similarity(text1, text2); return result.similarity; diff --git a/npm/packages/ruvector/src/core/onnx-optimized.ts b/npm/packages/ruvector/src/core/onnx-optimized.ts index 51148f34b..ae021d582 100644 --- a/npm/packages/ruvector/src/core/onnx-optimized.ts +++ b/npm/packages/ruvector/src/core/onnx-optimized.ts @@ -316,6 +316,11 @@ export class OptimizedOnnxEmbedder { return this.embedKind('passage', text); } + async embedFor(role: EmbedTextKind, text: string): Promise { + if (role !== 'query' && role !== 'passage') throw new TypeError(`unknown embedding role: ${role}`); + return this.embedKind(role, text); + } + private async embedKind(kind: EmbedTextKind, text: string): Promise { // ADR-210 D4: prefix before tokenization (and before the cache key, so // query and passage embeds of the same text never collide for E5/BGE). @@ -359,6 +364,19 @@ export class OptimizedOnnxEmbedder { * Embed multiple texts with batching and caching */ async embedBatch(texts: string[]): Promise { + return this.embedBatchFor('passage', texts); + } + + async embedQueryBatch(texts: string[]): Promise { + return this.embedBatchFor('query', texts); + } + + async embedPassageBatch(texts: string[]): Promise { + return this.embedBatchFor('passage', texts); + } + + async embedBatchFor(kind: EmbedTextKind, texts: string[]): Promise { + if (kind !== 'query' && kind !== 'passage') throw new TypeError(`unknown embedding role: ${kind}`); if (this.config.lazyInit && !this.initialized) { await this.init(); } @@ -367,7 +385,7 @@ export class OptimizedOnnxEmbedder { } // ADR-210 D4: batch embedding is the passage path (embed() === embedPassage()). - const prepared = texts.map(t => prefixText(this.config.modelId, 'passage', t)); + const prepared = texts.map(t => prefixText(this.config.modelId, kind, t)); const results: Float32Array[] = new Array(prepared.length); const uncached: { index: number; text: string }[] = []; diff --git a/npm/packages/ruvector/src/index.ts b/npm/packages/ruvector/src/index.ts index 4c6811de5..f893076a3 100644 --- a/npm/packages/ruvector/src/index.ts +++ b/npm/packages/ruvector/src/index.ts @@ -15,6 +15,7 @@ export * from './types'; // Export core wrappers (safe interfaces with automatic type conversion) export * from './core'; export * from './services'; +export * from './metaharness'; let implementation: any; let implementationType: 'native' | 'rvf' | 'wasm' = 'wasm'; diff --git a/npm/packages/ruvector/src/metaharness/index.ts b/npm/packages/ruvector/src/metaharness/index.ts new file mode 100644 index 000000000..1541a55f2 --- /dev/null +++ b/npm/packages/ruvector/src/metaharness/index.ts @@ -0,0 +1,208 @@ +/** + * Lazy CommonJS-to-ESM bridge for the pinned MetaHarness capability set. + * + * `ruvector` remains CommonJS for compatibility, while MetaHarness is ESM-only. + * Keeping native import() intact also means importing `ruvector` does not pay the + * startup or side-effect cost of loading research infrastructure. + */ +const nativeImport = new Function( + 'specifier', + 'return import(specifier)', +) as (specifier: string) => Promise>; + +export const METAHARNESS_VERSIONS = Object.freeze({ + metaharness: '0.4.2', + darwin: '0.8.0', + flywheel: '0.1.7', + harness: '0.1.0', + router: '0.3.2', + redblue: '0.1.4', + weightEft: '0.1.1', + workspaceLens: '0.1.1', + workspaceProbe: '0.1.1', +}); + +const PACKAGE_NAMES = Object.freeze({ + metaharness: 'metaharness', + darwin: '@metaharness/darwin', + flywheel: '@metaharness/flywheel', + harness: '@metaharness/harness', + router: '@metaharness/router', + redblue: '@metaharness/redblue', + weightEft: '@metaharness/weight-eft', + workspaceLens: '@metaharness/workspace-lens', + workspaceProbe: '@metaharness/workspace-probe', +}); + +export type MetaHarnessCapabilityName = keyof typeof METAHARNESS_VERSIONS; + +export interface MetaHarnessCapabilityStatus { + name: MetaHarnessCapabilityName; + package: string; + version: string; + available: boolean; + detail: string; +} + +export interface MetaHarnessRouteInput { + rows: Array<{ embedding: number[]; scores: Record }>; + prices: Record; + queryEmbedding: number[]; + qualityBar?: number; + k?: number; +} + +export interface MetaHarnessPromotionEvidence { + baseline: { + primary: number; + noopRate: number; + costPerWin: number; + regressed?: boolean; + }; + candidate: { + primary: number; + noopRate: number; + costPerWin: number; + regressed?: boolean; + }; + anchor?: { baseline: number; candidate: number }; +} + +const moduleCache = new Map>>(); + +function loadPackage(name: string): Promise> { + let pending = moduleCache.get(name); + if (!pending) { + pending = nativeImport(name); + moduleCache.set(name, pending); + } + return pending; +} + +function assertFiniteVector(name: string, vector: number[]): void { + if (!Array.isArray(vector) || vector.length === 0 || vector.some((v) => !Number.isFinite(v))) { + throw new TypeError(`${name} must be a non-empty array of finite numbers`); + } +} + +export async function getMetaHarnessCapabilities(): Promise { + const checks: Record = { + metaharness: ['scaffold', 'core scaffold and orchestration exports loaded'], + darwin: ['evolve', 'statistical evolution engine loaded'], + flywheel: ['runFlywheelGenerations', 'signed replay and promotion gate loaded'], + harness: ['HarnessKernel', 'algorithmic four-gate control plane loaded'], + router: ['Router', 'cost-aware embedding router loaded'], + redblue: ['enforceSafetyLimits', 'capability-contained Red/Blue suite loaded'], + weightEft: ['detectRewardHack', 'reward-hack detector loaded'], + workspaceLens: ['', 'workspace receipt contract loaded'], + workspaceProbe: ['workspaceProbeScore', 'workspace mutation evidence loaded'], + }; + return Promise.all((Object.keys(METAHARNESS_VERSIONS) as MetaHarnessCapabilityName[]).map(async (name) => { + const packageName = PACKAGE_NAMES[name]; + const [symbol, detail] = checks[name]; + try { + const loaded = await loadPackage(packageName); + return { + name, + package: packageName, + version: METAHARNESS_VERSIONS[name], + available: symbol ? typeof loaded[symbol] !== 'undefined' : true, + detail, + }; + } catch (error: any) { + return { + name, + package: packageName, + version: METAHARNESS_VERSIONS[name], + available: false, + detail: error?.message || String(error), + }; + } + })); +} + +/** Route to the cheapest model predicted to meet the requested quality bar. */ +export async function routeWithMetaHarness(input: MetaHarnessRouteInput): Promise<{ + id: string; + predictedQuality: number; + costPerMTok: number; + metBar: boolean; +}> { + assertFiniteVector('queryEmbedding', input.queryEmbedding); + if (!Array.isArray(input.rows) || input.rows.length === 0) { + throw new TypeError('rows must contain at least one labelled routing example'); + } + for (const [index, row] of input.rows.entries()) { + assertFiniteVector(`rows[${index}].embedding`, row.embedding); + if (row.embedding.length !== input.queryEmbedding.length) { + throw new RangeError(`rows[${index}].embedding dimension does not match queryEmbedding`); + } + } + const { Router } = await loadPackage(PACKAGE_NAMES.router); + return Router.fromExamples(input.rows, input.prices, { + qualityBar: input.qualityBar ?? 0.9, + ...(input.k === undefined ? {} : { k: input.k }), + }).route(input.queryEmbedding); +} + +/** Evaluate the frozen upstream conjunctive promotion rule without executing a candidate. */ +export async function evaluateMetaHarnessPromotion( + evidence: MetaHarnessPromotionEvidence, +): Promise<{ promote: boolean; reasons: string[] }> { + const { meetsPromotionRule } = await loadPackage(PACKAGE_NAMES.flywheel); + return meetsPromotionRule(evidence); +} + +/** Verify signed lineage, parent continuity, and the pinned promotion gate. */ +export async function verifyMetaHarnessReplay( + bundle: unknown, + options: { pinnedGateFingerprint?: string } = {}, +): Promise> { + const { verifyReplayBundle } = await loadPackage(PACKAGE_NAMES.flywheel); + return verifyReplayBundle(bundle, options); +} + +/** Run a caller-supplied Flywheel configuration. This is intentionally SDK-only. */ +export async function runMetaHarnessFlywheel(config: Record): Promise { + const { runFlywheelGenerations } = await loadPackage(PACKAGE_NAMES.flywheel); + return runFlywheelGenerations(config); +} + +/** Run Darwin only after the caller explicitly opts into code execution. */ +export async function runMetaHarnessDarwin( + config: Record, + options: { execute?: boolean } = {}, +): Promise { + if (options.execute !== true) { + throw new Error('Darwin execution requires { execute: true }'); + } + const { evolve } = await loadPackage(PACKAGE_NAMES.darwin); + return evolve(config); +} + +export async function createMetaHarnessKernel(options: Record): Promise { + const { HarnessKernel } = await loadPackage(PACKAGE_NAMES.harness); + return new HarnessKernel(options); +} + +export async function scoreMetaHarnessWorkspace( + receipts: unknown[], + options: Record = {}, +): Promise> { + const { workspaceProbeScore } = await loadPackage(PACKAGE_NAMES.workspaceProbe); + return workspaceProbeScore(receipts, options); +} + +export async function scanMetaHarnessRewardHacks( + trajectory: Record, +): Promise<{ clean: boolean; findings: unknown[] }> { + const { detectRewardHack } = await loadPackage(PACKAGE_NAMES.weightEft); + const findings = detectRewardHack(trajectory); + return { clean: findings.length === 0, findings }; +} + +export async function assertMetaHarnessSafePayload(payload: unknown): Promise { + const { assertNoLiveCredential } = await loadPackage(PACKAGE_NAMES.redblue); + const serialized = typeof payload === 'string' ? payload : JSON.stringify(payload); + assertNoLiveCredential(serialized); +} diff --git a/npm/packages/ruvector/test/metaharness-cli.js b/npm/packages/ruvector/test/metaharness-cli.js new file mode 100644 index 000000000..a275766fd --- /dev/null +++ b/npm/packages/ruvector/test/metaharness-cli.js @@ -0,0 +1,65 @@ +#!/usr/bin/env node + +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { spawnSync } = require('child_process'); + +const ROOT = path.join(__dirname, '..'); +const CLI = path.join(ROOT, 'bin', 'cli.js'); + +function run(args) { + const result = spawnSync(process.execPath, [CLI, ...args], { + cwd: ROOT, + encoding: 'utf8', + env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' }, + }); + assert.strictEqual(result.status, 0, result.stderr); + return result.stdout; +} + +const status = JSON.parse(run(['harness', 'doctor', '--json'])); +assert.deepStrictEqual( + Object.entries(status.primitives) + .filter(([name]) => name.startsWith('metaharness.')) + .filter(([, capability]) => !capability.available), + [], +); +assert.strictEqual(status.primitives['metaharness.darwin'].directDependency, true); + +const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvector-metaharness-cli-')); +const examples = path.join(temporary, 'examples.json'); +const prices = path.join(temporary, 'prices.json'); +const query = path.join(temporary, 'query.json'); +const evidence = path.join(temporary, 'evidence.json'); +fs.writeFileSync(examples, JSON.stringify([ + { embedding: [1, 0], scores: { cheap: 0.91, frontier: 0.99 } }, +])); +fs.writeFileSync(prices, JSON.stringify({ cheap: 1, frontier: 20 })); +fs.writeFileSync(query, JSON.stringify([1, 0])); +fs.writeFileSync(evidence, JSON.stringify({ + baseline: { primary: 0.8, noopRate: 0.2, costPerWin: 2, regressed: false }, + candidate: { primary: 0.81, noopRate: 0.1, costPerWin: 1.9, regressed: false }, + anchor: { baseline: 0.7, candidate: 0.7 }, +})); + +const route = JSON.parse(run([ + 'harness', 'route', + '--examples', examples, + '--prices', prices, + '--query', query, +])); +assert.strictEqual(route.id, 'cheap'); + +const gate = JSON.parse(run(['harness', 'flywheel', 'gate', evidence])); +assert.strictEqual(gate.promote, true); + +const denied = spawnSync(process.execPath, [CLI, 'harness', 'darwin', evidence], { + cwd: ROOT, + encoding: 'utf8', +}); +assert.notStrictEqual(denied.status, 0); +assert.match(denied.stderr, /without --execute/); + +console.log('MetaHarness CLI integration checks passed'); diff --git a/npm/packages/ruvector/test/metaharness-mcp.js b/npm/packages/ruvector/test/metaharness-mcp.js new file mode 100644 index 000000000..cce3fc73c --- /dev/null +++ b/npm/packages/ruvector/test/metaharness-mcp.js @@ -0,0 +1,103 @@ +#!/usr/bin/env node + +const assert = require('assert'); +const path = require('path'); +const { spawn } = require('child_process'); + +const ROOT = path.join(__dirname, '..'); +const CLI = path.join(ROOT, 'bin', 'cli.js'); +const child = spawn(process.execPath, [CLI, 'mcp', 'start'], { + cwd: ROOT, + env: { ...process.env, RUVECTOR_MCP_PROFILE: 'readonly', NO_COLOR: '1' }, + stdio: ['pipe', 'pipe', 'pipe'], +}); + +let nextId = 1; +let buffer = ''; +const pending = new Map(); + +child.stdout.on('data', (chunk) => { + buffer += chunk.toString(); + const lines = buffer.split(/\r?\n/); + buffer = lines.pop() || ''; + for (const line of lines) { + if (!line) continue; + const message = JSON.parse(line); + const waiter = pending.get(message.id); + if (waiter) { + pending.delete(message.id); + waiter.resolve(message); + } + } +}); + +function request(method, params) { + const id = nextId++; + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + pending.delete(id); + reject(new Error(`Timed out waiting for MCP ${method}`)); + }, 15_000); + pending.set(id, { + resolve: (message) => { + clearTimeout(timer); + resolve(message); + }, + }); + child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`); + }); +} + +async function main() { + const initialized = await request('initialize', { + protocolVersion: '2024-11-05', + capabilities: {}, + clientInfo: { name: 'metaharness-integration-test', version: '1.0.0' }, + }); + assert.ok(initialized.result); + child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' })}\n`); + + const listed = await request('tools/list', {}); + const names = listed.result.tools.map((tool) => tool.name); + for (const name of [ + 'metaharness_status', + 'metaharness_route', + 'metaharness_replay_verify', + 'metaharness_flywheel_gate', + 'metaharness_workspace_probe', + 'metaharness_reward_hack_scan', + ]) { + assert.ok(names.includes(name), `${name} must be available in the readonly profile`); + } + + const status = await request('tools/call', { + name: 'metaharness_status', + arguments: {}, + }); + const statusPayload = JSON.parse(status.result.content[0].text); + assert.strictEqual(statusPayload.success, true); + + const route = await request('tools/call', { + name: 'metaharness_route', + arguments: { + rows: [{ embedding: [1, 0], scores: { cheap: 0.91, frontier: 0.99 } }], + prices: { cheap: 1, frontier: 20 }, + query_embedding: [1, 0], + quality_bar: 0.9, + }, + }); + const routePayload = JSON.parse(route.result.content[0].text); + assert.strictEqual(routePayload.route.id, 'cheap'); + + console.log('MetaHarness MCP integration checks passed'); +} + +main() + .catch((error) => { + console.error(error); + process.exitCode = 1; + }) + .finally(() => { + child.stdin.end(); + child.kill('SIGTERM'); + }); diff --git a/npm/packages/ruvector/test/metaharness-sdk.js b/npm/packages/ruvector/test/metaharness-sdk.js new file mode 100644 index 000000000..9607650b2 --- /dev/null +++ b/npm/packages/ruvector/test/metaharness-sdk.js @@ -0,0 +1,53 @@ +#!/usr/bin/env node + +const assert = require('assert'); +const meta = require('../dist'); + +async function main() { + const capabilities = await meta.getMetaHarnessCapabilities(); + assert.strictEqual(capabilities.length, 9); + assert.deepStrictEqual( + capabilities.filter((capability) => !capability.available), + [], + 'all pinned MetaHarness dependencies must load from the published SDK surface', + ); + + const route = await meta.routeWithMetaHarness({ + rows: [{ embedding: [1, 0], scores: { cheap: 0.91, frontier: 0.99 } }], + prices: { cheap: 1, frontier: 20 }, + queryEmbedding: [1, 0], + qualityBar: 0.9, + }); + assert.strictEqual(route.id, 'cheap'); + assert.strictEqual(route.metBar, true); + + const decision = await meta.evaluateMetaHarnessPromotion({ + baseline: { primary: 0.8, noopRate: 0.2, costPerWin: 2, regressed: false }, + candidate: { primary: 0.81, noopRate: 0.1, costPerWin: 1.9, regressed: false }, + anchor: { baseline: 0.7, candidate: 0.7 }, + }); + assert.deepStrictEqual(decision, { promote: true, reasons: [] }); + + await assert.rejects( + () => meta.runMetaHarnessDarwin({}), + /requires \{ execute: true \}/, + 'Darwin must never execute through an accidental SDK call', + ); + + const scan = await meta.scanMetaHarnessRewardHacks({ + instance_id: 'fixture-1', + model: 'fixture', + tier: 'cheap', + resolved: false, + messages: [], + model_patch: '', + }); + assert.deepStrictEqual(scan, { clean: true, findings: [] }); + + console.log('MetaHarness SDK dependency integration checks passed'); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/npm/packages/ruvector/tests/embedding-space-identity.test.mjs b/npm/packages/ruvector/tests/embedding-space-identity.test.mjs new file mode 100644 index 000000000..88fcb2f1a --- /dev/null +++ b/npm/packages/ruvector/tests/embedding-space-identity.test.mjs @@ -0,0 +1,72 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const pkg = path.resolve(here, '..'); +const fixtures = JSON.parse(fs.readFileSync( + path.resolve(pkg, '../../../schemas/fixtures/embedding-space-identity-v1.json'), + 'utf8', +)); +const identity = await import(pathToFileURL( + path.join(pkg, 'dist/core/embedding-provenance.js'), +).href); +const onnx = await import(pathToFileURL( + path.join(pkg, 'dist/core/onnx-embedder.js'), +).href); + +assert.equal( + identity.embeddingSpaceId(fixtures.base.identity), + fixtures.base.embedding_space_id, +); +assert.equal( + identity.embeddingSpaceId(fixtures.changed_query_template.identity), + fixtures.changed_query_template.embedding_space_id, +); +assert.notEqual( + fixtures.base.embedding_space_id, + fixtures.changed_query_template.embedding_space_id, +); + +const access = identity.embeddingSpaceAccess( + fixtures.base.identity, + fixtures.changed_query_template.identity, +); +assert.equal(access.textEmbedding, false); +assert.equal(access.corpusMutation, false); +assert.equal(access.vectorRead, true); +assert.notEqual( + identity.embeddingCacheKey(fixtures.base.identity, 'query', 'same text'), + identity.embeddingCacheKey(fixtures.changed_query_template.identity, 'query', 'same text'), +); +assert.notEqual( + identity.embeddingCacheKey(fixtures.base.identity, 'query', 'same text'), + identity.embeddingCacheKey(fixtures.base.identity, 'passage', 'same text'), +); + +const loadedA = onnx.deriveOnnxEmbeddingSpaceIdentity( + 'bge-small-en-v1.5', + new Uint8Array([1, 2, 3]), + '{"tokenizer":"a"}', + 512, + true, + 384, +); +const loadedB = onnx.deriveOnnxEmbeddingSpaceIdentity( + 'bge-small-en-v1.5', + new Uint8Array([1, 2, 4]), + '{"tokenizer":"a"}', + 512, + true, + 384, +); +assert.equal(loadedA.role_policy, 'asymmetric'); +assert.equal(loadedA.prefix_policy, 'query-recommended'); +assert.notEqual(identity.embeddingSpaceId(loadedA), identity.embeddingSpaceId(loadedB)); +assert.notEqual( + identity.embeddingCacheKey(loadedA, 'query', 'same text'), + identity.embeddingCacheKey(loadedB, 'query', 'same text'), +); + +console.log('embedding-space identity fixture: ok');