ruvector 0.2.31 → 0.2.32

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/README.md CHANGED
@@ -91,6 +91,38 @@ claude mcp add ruvector -- npx ruvector mcp start
91
91
  - `midstream_status`, `midstream_attractor`, `midstream_scheduler` — Streaming analysis
92
92
  - `midstream_benchmark`, `midstream_search`, `midstream_health` — Latency benchmarks + health
93
93
 
94
+ **MCP tool-access policy (default-deny, ADR-256):** restrict the exposed/callable
95
+ tool surface with environment variables — useful for least-privilege deployments.
96
+
97
+ ```bash
98
+ # Only expose specific tools (everything else is denied)
99
+ RUVECTOR_MCP_ALLOW="hooks_route,hooks_recall" npx ruvector mcp start
100
+
101
+ # Block specific tools (deny wins over allow)
102
+ RUVECTOR_MCP_DENY="hooks_force_learn" npx ruvector mcp start
103
+
104
+ # Apply a curated read-only profile (safe, non-mutating subset)
105
+ RUVECTOR_MCP_PROFILE=readonly npx ruvector mcp start
106
+ ```
107
+
108
+ Precedence is **DENY > ALLOW/PROFILE > allow-all**. With no policy set, all tools
109
+ are available (backward compatible). Inspect the active posture with
110
+ `npx ruvector harness status --json` (see `mcp.accessControl`).
111
+
112
+ ### Harness Router (ADR-256)
113
+
114
+ `ruvector harness` surfaces the unified routing/agentic primitives ruvector ships —
115
+ cost-optimal model routing (Tiny Dancer), semantic routing, hooks routing, the MCP
116
+ server, witness-signed provenance, and SONA memory — in one place:
117
+
118
+ ```bash
119
+ npx ruvector harness status # human-readable surface + availability
120
+ npx ruvector harness status --json # structured, for tooling/CI
121
+ ```
122
+
123
+ Memory + learning loops use a stable namespace (default `ruvector`), overridable per
124
+ deployment with `RUVECTOR_MEMORY_NAMESPACE` and reported under `memory.namespace`.
125
+
94
126
  ### Brain AGI Commands
95
127
 
96
128
  Access all 8 AGI subsystems deployed at π.ruv.io:
package/bin/cli.js CHANGED
@@ -10101,6 +10101,137 @@ const optimizeCmd = program.command('optimize')
10101
10101
  console.log('');
10102
10102
  });
10103
10103
 
10104
+ // =============================================================================
10105
+ // Harness Commands - unified "harness router" surface (ADR-256)
10106
+ // Borrows metaharness concepts using primitives ruvector already ships:
10107
+ // cost router (tiny-dancer) + semantic router + hooks routing + MCP + witness
10108
+ // Read-only status surface; degrades gracefully when optional deps are absent.
10109
+ // =============================================================================
10110
+
10111
+ function buildHarnessSurface() {
10112
+ const primitives = {};
10113
+
10114
+ // Cost-optimal model router — Tiny Dancer FastGRNN (ADR-252)
10115
+ try {
10116
+ const td = require('@ruvector/tiny-dancer');
10117
+ primitives.costRouter = {
10118
+ name: '@ruvector/tiny-dancer',
10119
+ role: 'cost-optimal model routing (cheap vs strong)',
10120
+ available: true,
10121
+ version: typeof td.version === 'function' ? td.version() : null,
10122
+ usage: 'npx ruvector tiny-dancer score <model> --query <embedding>',
10123
+ };
10124
+ } catch {
10125
+ primitives.costRouter = {
10126
+ name: '@ruvector/tiny-dancer',
10127
+ role: 'cost-optimal model routing (cheap vs strong)',
10128
+ available: false,
10129
+ install: 'npm install @ruvector/tiny-dancer',
10130
+ };
10131
+ }
10132
+
10133
+ // Semantic intent router — @ruvector/router / ruvector-router-core
10134
+ let semanticAvailable = false;
10135
+ try { require.resolve('@ruvector/router'); semanticAvailable = true; } catch { semanticAvailable = false; }
10136
+ primitives.semanticRouter = {
10137
+ name: '@ruvector/router',
10138
+ role: 'semantic intent routing',
10139
+ available: semanticAvailable,
10140
+ ...(semanticAvailable ? { usage: 'npx ruvector router --route "<text>"' } : { install: 'npm install @ruvector/router' }),
10141
+ };
10142
+
10143
+ // Multi-tier intelligence routing — bundled (ADR-026)
10144
+ primitives.hooksRouting = {
10145
+ name: 'hooks route',
10146
+ role: '3-tier task→agent/model routing (ADR-026)',
10147
+ available: true,
10148
+ usage: 'npx ruvector hooks route "<task>"',
10149
+ };
10150
+
10151
+ // Agentic tool surface — bundled MCP server (with ADR-256 default-deny policy)
10152
+ const mcpPath = path.join(__dirname, 'mcp-server.js');
10153
+ let mcpPolicy = { configured: false };
10154
+ try {
10155
+ const { buildToolPolicy } = require('./mcp-policy.js');
10156
+ const p = buildToolPolicy(process.env);
10157
+ mcpPolicy = {
10158
+ configured: p.configured,
10159
+ profile: p.profile || null,
10160
+ allow: p.allowSet ? p.allowSet.size : 0,
10161
+ deny: p.deny.size,
10162
+ };
10163
+ } catch { /* policy module optional */ }
10164
+ primitives.mcp = {
10165
+ name: 'mcp-server',
10166
+ role: 'agentic tool surface (Model Context Protocol)',
10167
+ available: fs.existsSync(mcpPath),
10168
+ usage: 'npx ruvector mcp start',
10169
+ policy: mcpPolicy,
10170
+ accessControl: mcpPolicy.configured ? 'default-deny (configured)' : 'allow-all (set RUVECTOR_MCP_ALLOW/PROFILE)',
10171
+ };
10172
+
10173
+ // Signed provenance — witness chain (ADR-103 / ADR-134)
10174
+ primitives.witness = {
10175
+ name: 'witness-chain',
10176
+ role: 'signed provenance / release signing (ADR-103, ADR-134)',
10177
+ available: true,
10178
+ };
10179
+
10180
+ // Memory + learning loops — SONA / ReasoningBank (stable namespace, ADR-256 step 3)
10181
+ primitives.memory = {
10182
+ name: 'sona+reasoningbank',
10183
+ role: 'persistent memory + self-learning loops',
10184
+ available: true,
10185
+ namespace: (process.env.RUVECTOR_MEMORY_NAMESPACE || 'ruvector').trim() || 'ruvector',
10186
+ };
10187
+
10188
+ const values = Object.values(primitives);
10189
+ return {
10190
+ adr: 'ADR-256',
10191
+ decision: 'borrow metaharness concepts using primitives ruvector already ships',
10192
+ primitives,
10193
+ summary: {
10194
+ available: values.filter((p) => p.available).length,
10195
+ total: values.length,
10196
+ },
10197
+ };
10198
+ }
10199
+
10200
+ const harnessCmd = program
10201
+ .command('harness')
10202
+ .description('Unified "harness router" surface — cost router + semantic router + hooks routing + MCP + witness (ADR-256)');
10203
+
10204
+ function printHarnessStatus(opts) {
10205
+ const surface = buildHarnessSurface();
10206
+ if (opts && opts.json) {
10207
+ console.log(JSON.stringify(surface, null, 2));
10208
+ return;
10209
+ }
10210
+ console.log(chalk.cyan('\n═══════════════════════════════════════════════════════════════'));
10211
+ console.log(chalk.cyan(' RuVector Harness Router (ADR-256)'));
10212
+ console.log(chalk.cyan('═══════════════════════════════════════════════════════════════\n'));
10213
+ console.log(chalk.gray(' ' + surface.decision + '\n'));
10214
+ for (const p of Object.values(surface.primitives)) {
10215
+ const badge = p.available ? chalk.green('● available') : chalk.yellow('○ optional ');
10216
+ console.log(` ${badge} ${chalk.white(p.name)}${p.version ? chalk.dim(' v' + p.version) : ''}`);
10217
+ console.log(` ${chalk.dim(p.role)}`);
10218
+ if (p.available && p.usage) console.log(` ${chalk.dim(p.usage)}`);
10219
+ if (!p.available && p.install) console.log(` ${chalk.dim('install: ' + p.install)}`);
10220
+ }
10221
+ console.log('');
10222
+ console.log(chalk.cyan(` ${surface.summary.available}/${surface.summary.total} primitives available\n`));
10223
+ }
10224
+
10225
+ harnessCmd
10226
+ .command('status')
10227
+ .alias('info')
10228
+ .description('Show the unified harness routing surface and primitive availability')
10229
+ .option('--json', 'Output as JSON')
10230
+ .action((opts) => printHarnessStatus(opts));
10231
+
10232
+ // Bare `ruvector harness` defaults to status
10233
+ harnessCmd.action(() => printHarnessStatus({}));
10234
+
10104
10235
  program.parse();
10105
10236
 
10106
10237
 
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * MCP tool-access policy (ADR-256 — default-deny posture).
5
+ *
6
+ * Borrows metaharness's "default-deny allowlist" security concept using a small,
7
+ * pure, testable module. Operators restrict the exposed/callable MCP tool surface
8
+ * via environment variables:
9
+ *
10
+ * RUVECTOR_MCP_ALLOW="hooks_route,hooks_recall" → only these are exposed/callable
11
+ * RUVECTOR_MCP_DENY="hooks_security_scan" → these are blocked (wins over allow)
12
+ * RUVECTOR_MCP_PROFILE=readonly → a curated safe subset
13
+ *
14
+ * Precedence: DENY > ALLOW/PROFILE > (default allow-all).
15
+ * With no policy configured, all registered tools are allowed (backward compatible);
16
+ * `policy.configured` is false so the server can nudge toward an allowlist.
17
+ *
18
+ * This module is dependency-free and side-effect-free so it can be unit-tested
19
+ * without spawning an MCP stdio server.
20
+ */
21
+
22
+ /** Curated read-only / low-risk tool subsets. */
23
+ const MCP_PROFILES = {
24
+ readonly: [
25
+ 'ruvector',
26
+ 'hooks_stats',
27
+ 'hooks_recall',
28
+ 'hooks_route',
29
+ 'hooks_route_enhanced',
30
+ 'hooks_suggest_context',
31
+ 'hooks_capabilities',
32
+ 'hooks_export',
33
+ 'hooks_doctor',
34
+ 'hooks_attention_info',
35
+ 'hooks_gnn_info',
36
+ ],
37
+ };
38
+
39
+ /** Split a comma/space separated env list into a clean array. */
40
+ function parseList(value) {
41
+ if (!value || typeof value !== 'string') return [];
42
+ return value
43
+ .split(/[,\s]+/)
44
+ .map((s) => s.trim())
45
+ .filter(Boolean);
46
+ }
47
+
48
+ /**
49
+ * Build a tool-access policy from an env-like object (defaults to process.env).
50
+ * Returns { allowSet: Set|null, deny: Set, profile: string|null, configured: bool }.
51
+ */
52
+ function buildToolPolicy(env) {
53
+ const e = env || process.env;
54
+ const allow = parseList(e.RUVECTOR_MCP_ALLOW);
55
+ const deny = new Set(parseList(e.RUVECTOR_MCP_DENY));
56
+ const profileName = e.RUVECTOR_MCP_PROFILE && e.RUVECTOR_MCP_PROFILE.trim();
57
+
58
+ let allowSet = null;
59
+ if (profileName && MCP_PROFILES[profileName]) {
60
+ allowSet = new Set(MCP_PROFILES[profileName]);
61
+ }
62
+ if (allow.length) {
63
+ allowSet = new Set([...(allowSet || []), ...allow]);
64
+ }
65
+
66
+ return {
67
+ allowSet,
68
+ deny,
69
+ profile: profileName && MCP_PROFILES[profileName] ? profileName : null,
70
+ // An unknown profile name with no allow/deny is treated as "not configured".
71
+ configured: Boolean(allowSet || deny.size),
72
+ };
73
+ }
74
+
75
+ /** Is a tool name permitted under the given policy? DENY always wins. */
76
+ function isToolAllowed(name, policy) {
77
+ if (!policy) return true;
78
+ if (policy.deny.has(name)) return false;
79
+ if (policy.allowSet) return policy.allowSet.has(name);
80
+ return true;
81
+ }
82
+
83
+ /** Filter a TOOLS array (objects with a `.name`) down to the allowed subset. */
84
+ function filterAllowedTools(tools, policy) {
85
+ if (!Array.isArray(tools)) return [];
86
+ return tools.filter((t) => t && isToolAllowed(t.name, policy));
87
+ }
88
+
89
+ module.exports = {
90
+ MCP_PROFILES,
91
+ parseList,
92
+ buildToolPolicy,
93
+ isToolAllowed,
94
+ filterAllowedTools,
95
+ };
package/bin/mcp-server.js CHANGED
@@ -26,6 +26,10 @@ const path = require('path');
26
26
  const fs = require('fs');
27
27
  const { execSync, execFileSync } = require('child_process');
28
28
 
29
+ // ADR-256: default-deny MCP tool-access policy (RUVECTOR_MCP_ALLOW/DENY/PROFILE)
30
+ const { buildToolPolicy, isToolAllowed, filterAllowedTools } = require('./mcp-policy.js');
31
+ const MCP_TOOL_POLICY = buildToolPolicy(process.env);
32
+
29
33
  // ── Security Helpers ────────────────────────────────────────────────────────
30
34
 
31
35
  /**
@@ -1692,15 +1696,30 @@ const TOOLS = [
1692
1696
  }
1693
1697
  ];
1694
1698
 
1695
- // List tools handler
1699
+ // List tools handler — only expose tools permitted by the access policy (ADR-256)
1696
1700
  server.setRequestHandler(ListToolsRequestSchema, async () => {
1697
- return { tools: TOOLS };
1701
+ return { tools: filterAllowedTools(TOOLS, MCP_TOOL_POLICY) };
1698
1702
  });
1699
1703
 
1700
1704
  // Call tool handler
1701
1705
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
1702
1706
  const { name, arguments: args } = request.params;
1703
1707
 
1708
+ // ADR-256 default-deny gate: refuse tools excluded by RUVECTOR_MCP_ALLOW/DENY/PROFILE
1709
+ if (!isToolAllowed(name, MCP_TOOL_POLICY)) {
1710
+ return {
1711
+ content: [{
1712
+ type: 'text',
1713
+ text: JSON.stringify({
1714
+ success: false,
1715
+ error: `Tool '${name}' is denied by the MCP access policy (ADR-256). ` +
1716
+ `Adjust RUVECTOR_MCP_ALLOW / RUVECTOR_MCP_DENY / RUVECTOR_MCP_PROFILE to permit it.`,
1717
+ }, null, 2),
1718
+ }],
1719
+ isError: true,
1720
+ };
1721
+ }
1722
+
1704
1723
  try {
1705
1724
  switch (name) {
1706
1725
  case 'hooks_stats': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvector",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
4
4
  "description": "Self-learning vector database for Node.js \u2014 hybrid search, Graph RAG, FlashAttention-3, HNSW, 50+ attention mechanisms",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,11 +8,11 @@
8
8
  "ruvector": "./bin/cli.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "tsc && mkdir -p dist/core/onnx && cp -r src/core/onnx/. dist/core/onnx/",
11
+ "build": "tsc && node -e \"require('fs').cpSync('src/core/onnx','dist/core/onnx',{recursive:true})\"",
12
12
  "verify-dist": "node scripts/verify-dist.js",
13
13
  "prepack": "npm run build && npm run verify-dist",
14
14
  "prepublishOnly": "npm run build && npm run verify-dist",
15
- "test": "node test/integration.js && node test/cli-commands.js && node test/db-workflow.js && node test/sigterm-cleanup.js"
15
+ "test": "node test/integration.js && node test/cli-commands.js && node test/db-workflow.js && node test/sigterm-cleanup.js && node test/mcp-policy.js && node test/startup-budget.js"
16
16
  },
17
17
  "keywords": [
18
18
  "vector",