tasq/node_modules/@claude-flow/guidance/dist/ruvbot-integration.js

738 lines
27 KiB
JavaScript

/**
* RuvBot Integration Bridge
*
* Bridges ruvbot (npm: ruvbot@0.1.8) with the @claude-flow/guidance control
* plane. Wires ruvbot events to guidance hooks, wraps AIDefence as an
* enforcement gate, governs memory operations, and feeds trust accumulation.
*
* ruvbot is an optional peer dependency. All types and classes are exported
* regardless of whether ruvbot is installed. Runtime calls that require the
* ruvbot package will throw a clear error if the package is missing.
*
* Components:
* 1. RuvBotGuidanceBridge - Event wiring, gate delegation, trust tracking
* 2. AIDefenceGate - Prompt injection, jailbreak, PII detection gate
* 3. RuvBotMemoryAdapter - Governed memory read/write with proof logging
*
* @module @claude-flow/guidance/ruvbot-integration
*/
// ============================================================================
// Severity Mapping
// ============================================================================
/** Maps blockThreshold to a minimum severity that triggers a block. */
const BLOCK_SEVERITY_THRESHOLDS = {
low: new Set(['low', 'medium', 'high', 'critical']),
medium: new Set(['medium', 'high', 'critical']),
high: new Set(['high', 'critical']),
};
/** Maps ruvbot threat type strings to our typed threat type. */
const THREAT_TYPE_MAP = {
'prompt-injection': 'prompt-injection',
'prompt_injection': 'prompt-injection',
'promptInjection': 'prompt-injection',
'jailbreak': 'jailbreak',
'pii': 'pii',
'control-chars': 'control-chars',
'control_chars': 'control-chars',
'controlChars': 'control-chars',
'homoglyph': 'homoglyph',
};
/** Maps ruvbot severity strings to our typed severity. */
const SEVERITY_MAP = {
low: 'low',
medium: 'medium',
high: 'high',
critical: 'critical',
};
// ============================================================================
// Dynamic Import Helper
// ============================================================================
/** Resolved ruvbot module cache (null = not attempted, undefined = failed). */
let ruvbotModuleCache = null;
/**
* Module specifiers kept in variables so TypeScript does not attempt
* compile-time resolution of this optional peer dependency.
*/
const RUVBOT_MODULE = 'ruvbot';
const RUVBOT_CORE_MODULE = 'ruvbot/core';
/**
* Attempt to dynamically import the ruvbot package.
* Throws a descriptive error if the package is not installed.
*/
async function requireRuvBot() {
if (ruvbotModuleCache)
return ruvbotModuleCache;
try {
const mod = await import(RUVBOT_MODULE);
ruvbotModuleCache = mod;
return mod;
}
catch {
throw new Error('ruvbot is not installed. Install it with: npm install ruvbot@0.1.8\n' +
'ruvbot is an optional peer dependency of @claude-flow/guidance.');
}
}
/**
* Attempt to dynamically import ruvbot/core sub-export.
*/
async function requireRuvBotCore() {
try {
return await import(RUVBOT_CORE_MODULE);
}
catch {
// Fall back to the main export
return requireRuvBot();
}
}
// ============================================================================
// AIDefenceGate
// ============================================================================
/**
* Wraps ruvbot's 6-layer AIDefence as an enforcement gate compatible with the
* guidance control plane's GateResult / GateDecision interface.
*
* Supports:
* - Prompt injection detection
* - Jailbreak detection
* - PII detection
* - Control character and homoglyph detection (via ruvbot internals)
* - Configurable sensitivity / block threshold
*
* Evaluates both input (pre-processing) and output (post-processing) text.
*/
export class AIDefenceGate {
config;
guard = null;
guardInitPromise = null;
constructor(config = {}) {
this.config = {
detectPromptInjection: config.detectPromptInjection ?? true,
detectJailbreak: config.detectJailbreak ?? true,
detectPII: config.detectPII ?? true,
blockThreshold: config.blockThreshold ?? 'medium',
};
}
/**
* Lazily initialize the underlying ruvbot AIDefence guard.
* Safe to call multiple times; only the first call creates the guard.
*/
async ensureGuard() {
if (this.guard)
return this.guard;
if (!this.guardInitPromise) {
this.guardInitPromise = (async () => {
const mod = await requireRuvBot();
const createGuard = mod['createAIDefenceGuard'];
if (typeof createGuard !== 'function') {
throw new Error('ruvbot does not export createAIDefenceGuard. ' +
'Ensure ruvbot@0.1.8 or later is installed.');
}
this.guard = createGuard({
detectPromptInjection: this.config.detectPromptInjection,
detectJailbreak: this.config.detectJailbreak,
detectPII: this.config.detectPII,
});
})();
}
await this.guardInitPromise;
return this.guard;
}
/**
* Evaluate input text for threats (pre-processing gate).
*
* Checks for prompt injection, jailbreak attempts, and PII based
* on the configured sensitivity.
*/
async evaluateInput(input) {
const start = performance.now();
const guard = await this.ensureGuard();
const raw = await guard.check(input);
const latencyMs = performance.now() - start;
const threats = this.normalizeThreats(raw.threats);
return {
safe: raw.safe,
threats,
sanitizedInput: raw.sanitizedInput,
latencyMs,
};
}
/**
* Evaluate output text for threats (post-processing gate).
*
* Primarily checks for PII leakage and secret exposure in responses.
*/
async evaluateOutput(output) {
const start = performance.now();
const guard = await this.ensureGuard();
const raw = await guard.check(output);
const latencyMs = performance.now() - start;
// For output evaluation, focus on PII / data leakage threats
const threats = this.normalizeThreats(raw.threats).filter(t => t.type === 'pii' || t.type === 'control-chars');
return {
safe: threats.length === 0,
threats,
sanitizedInput: raw.sanitizedInput,
latencyMs,
};
}
/**
* Convert an AIDefenceResult into a GateResult compatible with the
* guidance enforcement pipeline.
*
* Decision logic:
* - If no threats: 'allow'
* - If threats above block threshold: 'block'
* - Otherwise: 'warn'
*/
toGateResult(result, context) {
if (result.safe && result.threats.length === 0) {
return {
decision: 'allow',
gateName: 'ai-defence',
reason: 'AIDefence check passed with no threats detected.',
triggeredRules: [],
metadata: { latencyMs: result.latencyMs },
};
}
const blockingSeverities = BLOCK_SEVERITY_THRESHOLDS[this.config.blockThreshold];
const blockingThreats = result.threats.filter(t => blockingSeverities.has(t.severity));
const decision = blockingThreats.length > 0 ? 'block' : 'warn';
const threatSummary = result.threats
.map(t => `${t.type} (${t.severity}): ${t.detail}`)
.join('; ');
return {
decision,
gateName: 'ai-defence',
reason: `AIDefence detected ${result.threats.length} threat(s): ${threatSummary}`,
triggeredRules: [],
remediation: this.buildRemediation(result.threats),
metadata: {
threats: result.threats,
blockThreshold: this.config.blockThreshold,
latencyMs: result.latencyMs,
context,
},
};
}
/**
* Get the current configuration.
*/
getConfig() {
return { ...this.config };
}
/**
* Update configuration. Resets the guard so the next evaluation
* re-initializes with the new settings.
*/
updateConfig(config) {
this.config = { ...this.config, ...config };
this.guard = null;
this.guardInitPromise = null;
}
// ===== Private Helpers =====
normalizeThreats(raw) {
return raw.map(t => ({
type: THREAT_TYPE_MAP[t.type] ?? 'prompt-injection',
severity: SEVERITY_MAP[t.severity] ?? 'medium',
detail: t.detail,
}));
}
buildRemediation(threats) {
const parts = [];
const hasInjection = threats.some(t => t.type === 'prompt-injection');
const hasJailbreak = threats.some(t => t.type === 'jailbreak');
const hasPII = threats.some(t => t.type === 'pii');
if (hasInjection) {
parts.push('1. Review input for prompt injection patterns and remove adversarial content.');
}
if (hasJailbreak) {
parts.push('2. Input contains jailbreak attempt. Reject and log the attempt.');
}
if (hasPII) {
parts.push('3. Redact or mask personally identifiable information before processing.');
}
if (parts.length === 0) {
parts.push('Review flagged content and apply appropriate sanitization.');
}
return parts.join('\n');
}
}
// ============================================================================
// RuvBotMemoryAdapter
// ============================================================================
/**
* Wraps ruvbot's memory read/write operations with guidance control plane
* governance. Every write passes through the MemoryWriteGate for authority
* and coherence checks. All operations are logged to a proof chain.
*/
export class RuvBotMemoryAdapter {
memoryGate;
coherenceScheduler;
proofChain = null;
ruvbotMemory = null;
operationLog = [];
constructor(memoryGate, coherenceScheduler) {
this.memoryGate = memoryGate;
this.coherenceScheduler = coherenceScheduler;
}
/**
* Attach a ruvbot memory instance for proxied operations.
*/
attachMemory(memory) {
this.ruvbotMemory = memory;
}
/**
* Attach a proof chain for operation logging.
*/
attachProofChain(proofChain) {
this.proofChain = proofChain;
}
/**
* Governed read: reads through ruvbot memory, logs to proof chain.
*/
async read(key, namespace = 'default') {
this.ensureMemoryAttached();
const value = await this.ruvbotMemory.read(key, namespace);
this.operationLog.push({
operation: 'read',
key,
namespace,
timestamp: Date.now(),
});
return value;
}
/**
* Governed write: runs through MemoryWriteGate, checks coherence,
* logs to proof chain, then delegates to ruvbot memory.
*
* Returns the WriteDecision. If denied, the write is not performed.
*/
async write(key, namespace, value, authority, existingEntries) {
this.ensureMemoryAttached();
// Step 1: Evaluate through MemoryWriteGate
const decision = this.memoryGate.evaluateWrite(authority, key, namespace, value, existingEntries);
// Step 2: Log the operation
this.operationLog.push({
operation: 'write',
key,
namespace,
timestamp: Date.now(),
decision,
});
// Step 3: If denied, do not write
if (!decision.allowed) {
return decision;
}
// Step 4: Perform the write through ruvbot
await this.ruvbotMemory.write(key, value, namespace);
return decision;
}
/**
* Governed delete: checks authority, logs, then delegates.
*/
async delete(key, namespace, authority) {
this.ensureMemoryAttached();
// Authority must have delete permission
if (!authority.canDelete) {
const result = {
allowed: false,
reason: `Agent "${authority.agentId}" does not have delete permission.`,
};
this.operationLog.push({
operation: 'delete',
key,
namespace,
timestamp: Date.now(),
});
return result;
}
// Perform the delete if the underlying memory supports it
if (typeof this.ruvbotMemory.delete === 'function') {
await this.ruvbotMemory.delete(key, namespace);
}
this.operationLog.push({
operation: 'delete',
key,
namespace,
timestamp: Date.now(),
});
return { allowed: true, reason: 'Delete permitted and executed.' };
}
/**
* Get the operation log for audit/proof purposes.
*/
getOperationLog() {
return this.operationLog;
}
/**
* Get the count of governed operations.
*/
get operationCount() {
return this.operationLog.length;
}
/**
* Clear the operation log.
*/
clearLog() {
this.operationLog = [];
}
// ===== Private Helpers =====
ensureMemoryAttached() {
if (!this.ruvbotMemory) {
throw new Error('No ruvbot memory instance attached. Call attachMemory() before ' +
'performing memory operations.');
}
}
}
// ============================================================================
// RuvBotGuidanceBridge
// ============================================================================
/**
* Bridges a ruvbot instance with the @claude-flow/guidance control plane.
*
* Wires ruvbot event hooks to guidance enforcement and trust systems:
*
* - `message` -> EnforcementGates (secrets, destructive ops) + AIDefence
* - `agent:spawn` -> ManifestValidator
* - `session:create` -> ProofChain initialization
* - `session:end` -> ProofChain finalization and ledger persistence
* - `ready` -> Trust accumulator initialization
* - `error` -> Trust 'deny' outcome recording
*
* All gate outcomes are fed into the TrustAccumulator so that ruvbot agents
* build (or lose) trust over time.
*/
export class RuvBotGuidanceBridge {
config;
ruvbot = null;
// Guidance components (injected)
gates = null;
manifestValidator = null;
trustSystem = null;
aiDefenceGate = null;
memoryAdapter = null;
// Session proof chains keyed by sessionId
sessionChains = new Map();
// Bound event handlers for cleanup
boundHandlers = new Map();
// Event log for diagnostics
eventLog = [];
static MAX_EVENT_LOG = 1000;
constructor(config = {}) {
this.config = {
enableAIDefence: config.enableAIDefence ?? true,
enableMemoryGovernance: config.enableMemoryGovernance ?? true,
enableTrustTracking: config.enableTrustTracking ?? true,
enableProofChain: config.enableProofChain ?? true,
};
}
/**
* Attach guidance control plane components.
*
* Accepts either a full GuidanceControlPlane instance (from which
* sub-components are extracted) or individual components.
*/
attachGuidance(components) {
if (components.gates)
this.gates = components.gates;
if (components.manifestValidator)
this.manifestValidator = components.manifestValidator;
if (components.trustSystem)
this.trustSystem = components.trustSystem;
if (components.aiDefenceGate)
this.aiDefenceGate = components.aiDefenceGate;
if (components.memoryAdapter)
this.memoryAdapter = components.memoryAdapter;
}
/**
* Connect to a ruvbot instance and wire all event handlers.
*
* This is the primary entry point. Once called, the bridge will
* intercept ruvbot events and route them through guidance gates.
*/
connect(ruvbot) {
if (this.ruvbot) {
this.disconnect();
}
this.ruvbot = ruvbot;
// Wire event handlers
this.wireEvent('message', this.handleMessage.bind(this));
this.wireEvent('agent:spawn', this.handleAgentSpawn.bind(this));
this.wireEvent('session:create', this.handleSessionCreate.bind(this));
this.wireEvent('session:end', this.handleSessionEnd.bind(this));
this.wireEvent('ready', this.handleReady.bind(this));
this.wireEvent('shutdown', this.handleShutdown.bind(this));
this.wireEvent('error', this.handleError.bind(this));
this.wireEvent('agent:stop', this.handleAgentStop.bind(this));
}
/**
* Disconnect from the ruvbot instance, removing all event handlers.
*/
disconnect() {
if (!this.ruvbot)
return;
for (const [event, handler] of this.boundHandlers) {
this.ruvbot.off(event, handler);
}
this.boundHandlers.clear();
this.ruvbot = null;
}
/**
* Evaluate a ruvbot AIDefence result and return a GateResult-compatible
* decision. Can be called independently of event wiring.
*/
async evaluateAIDefence(input) {
if (!this.aiDefenceGate) {
throw new Error('AIDefenceGate not attached. Call attachGuidance({ aiDefenceGate }) first.');
}
const result = await this.aiDefenceGate.evaluateInput(input);
return this.aiDefenceGate.toGateResult(result, 'manual-evaluation');
}
/**
* Get the proof chain for a specific session.
*/
getSessionProofChain(sessionId) {
return this.sessionChains.get(sessionId);
}
/**
* Get all active session IDs.
*/
getActiveSessionIds() {
return [...this.sessionChains.keys()];
}
/**
* Get the event log for diagnostics.
*/
getEventLog() {
return this.eventLog;
}
/**
* Get the current bridge configuration.
*/
getConfig() {
return { ...this.config };
}
/**
* Whether the bridge is currently connected to a ruvbot instance.
*/
get connected() {
return this.ruvbot !== null;
}
// ===== Event Handlers =====
/**
* Handle `message` events: run content through enforcement gates
* and optionally through AIDefence.
*/
async handleMessage(...args) {
const data = (args[0] ?? {});
const content = String(data['content'] ?? data['text'] ?? '');
const sessionId = String(data['sessionId'] ?? 'unknown');
const agentId = String(data['agentId'] ?? 'unknown');
this.logEvent('message', { sessionId, agentId, contentLength: content.length });
const gateResults = [];
// Step 1: Run through EnforcementGates (secrets, destructive ops)
if (this.gates) {
const commandResults = this.gates.evaluateCommand(content);
gateResults.push(...commandResults);
}
// Step 2: Run through AIDefence gate
if (this.config.enableAIDefence && this.aiDefenceGate) {
try {
const defenceResult = await this.aiDefenceGate.evaluateInput(content);
const gateResult = this.aiDefenceGate.toGateResult(defenceResult, `message:${sessionId}`);
if (gateResult.decision !== 'allow') {
gateResults.push(gateResult);
}
}
catch {
// AIDefence unavailable; log but do not block
}
}
// Step 3: Feed outcomes into trust accumulator
if (this.config.enableTrustTracking && this.trustSystem) {
if (gateResults.length === 0) {
this.trustSystem.recordOutcome(agentId, 'allow', `Message passed all gates (session: ${sessionId})`);
}
else {
for (const result of gateResults) {
const outcome = gateDecisionToTrustOutcome(result.decision);
this.trustSystem.recordOutcome(agentId, outcome, `Gate "${result.gateName}" ${result.decision}: ${result.reason}`);
}
}
}
}
/**
* Handle `agent:spawn` events: validate agent manifest.
*/
async handleAgentSpawn(...args) {
const data = (args[0] ?? {});
const agentId = String(data['agentId'] ?? data['id'] ?? 'unknown');
const manifest = data['manifest'];
this.logEvent('agent:spawn', { agentId, hasManifest: !!manifest });
if (this.manifestValidator && manifest) {
const validation = this.manifestValidator.validate(manifest);
if (this.config.enableTrustTracking && this.trustSystem) {
if (validation.admissionDecision === 'admit') {
this.trustSystem.recordOutcome(agentId, 'allow', `Agent manifest validated: admission=${validation.admissionDecision}, risk=${validation.riskScore}`);
}
else {
const outcome = validation.admissionDecision === 'reject' ? 'deny' : 'warn';
this.trustSystem.recordOutcome(agentId, outcome, `Agent manifest ${validation.admissionDecision}: risk=${validation.riskScore}, errors=${validation.errors.length}`);
}
}
}
}
/**
* Handle `session:create` events: initialize a proof chain for the session.
*/
async handleSessionCreate(...args) {
const data = (args[0] ?? {});
const sessionId = String(data['sessionId'] ?? data['id'] ?? `session-${Date.now()}`);
this.logEvent('session:create', { sessionId });
if (this.config.enableProofChain) {
if (!this.config.proofSigningKey) {
throw new Error('RuvBotBridgeConfig.proofSigningKey is required when enableProofChain is true');
}
const { createProofChain } = await import('./proof.js');
const chain = createProofChain({ signingKey: this.config.proofSigningKey });
this.sessionChains.set(sessionId, chain);
}
}
/**
* Handle `session:end` events: finalize the proof chain and persist.
*/
async handleSessionEnd(...args) {
const data = (args[0] ?? {});
const sessionId = String(data['sessionId'] ?? data['id'] ?? 'unknown');
this.logEvent('session:end', { sessionId });
if (this.config.enableProofChain) {
const chain = this.sessionChains.get(sessionId);
if (chain) {
// Export the finalized chain for external persistence
const _exported = chain.export();
// The caller can retrieve this via getSessionProofChain() before
// the session is cleaned up, or listen for an event.
// Clean up
this.sessionChains.delete(sessionId);
}
}
}
/**
* Handle `ready` events: log bridge activation.
*/
async handleReady(...args) {
this.logEvent('ready', {});
}
/**
* Handle `shutdown` events: clean up all session proof chains.
*/
async handleShutdown(...args) {
this.logEvent('shutdown', {});
this.sessionChains.clear();
}
/**
* Handle `error` events: record a deny outcome in trust tracking.
*/
async handleError(...args) {
const data = (args[0] ?? {});
const agentId = String(data['agentId'] ?? 'unknown');
const errorMessage = String(data['message'] ?? data['error'] ?? 'unknown error');
this.logEvent('error', { agentId, error: errorMessage });
if (this.config.enableTrustTracking && this.trustSystem) {
this.trustSystem.recordOutcome(agentId, 'deny', `Error event: ${errorMessage}`);
}
}
/**
* Handle `agent:stop` events: record final trust snapshot.
*/
async handleAgentStop(...args) {
const data = (args[0] ?? {});
const agentId = String(data['agentId'] ?? data['id'] ?? 'unknown');
this.logEvent('agent:stop', { agentId });
}
// ===== Private Helpers =====
wireEvent(event, handler) {
this.boundHandlers.set(event, handler);
this.ruvbot.on(event, handler);
}
logEvent(type, data) {
const event = {
type,
timestamp: Date.now(),
sessionId: data['sessionId'],
agentId: data['agentId'],
data,
};
this.eventLog.push(event);
if (this.eventLog.length > RuvBotGuidanceBridge.MAX_EVENT_LOG) {
this.eventLog = this.eventLog.slice(-RuvBotGuidanceBridge.MAX_EVENT_LOG);
}
}
}
// ============================================================================
// Helper Functions
// ============================================================================
/**
* Map a GateDecision to a GateOutcome for trust accumulation.
*
* - 'allow' -> 'allow'
* - 'block' -> 'deny'
* - 'require-confirmation' -> 'warn'
* - 'warn' -> 'warn'
*/
function gateDecisionToTrustOutcome(decision) {
switch (decision) {
case 'allow': return 'allow';
case 'block': return 'deny';
case 'warn': return 'warn';
case 'require-confirmation': return 'warn';
default: return 'warn';
}
}
// ============================================================================
// Factory Functions
// ============================================================================
/**
* Create a fully wired RuvBotGuidanceBridge.
*
* Connects the bridge to a ruvbot instance and attaches the guidance
* control plane components. The bridge immediately begins intercepting
* ruvbot events.
*
* @param ruvbotInstance - A ruvbot instance (from createRuvBot())
* @param guidancePlane - A GuidanceControlPlane or individual components
* @param config - Optional bridge configuration
* @returns The connected RuvBotGuidanceBridge
*/
export function createRuvBotBridge(ruvbotInstance, guidancePlane, config) {
const bridge = new RuvBotGuidanceBridge(config);
bridge.attachGuidance(guidancePlane);
bridge.connect(ruvbotInstance);
return bridge;
}
/**
* Create an AIDefenceGate with optional configuration.
*
* The gate lazily initializes the underlying ruvbot AIDefence guard
* on the first evaluation call.
*
* @param config - Optional gate configuration
* @returns A new AIDefenceGate instance
*/
export function createAIDefenceGate(config) {
return new AIDefenceGate(config);
}
/**
* Create a RuvBotMemoryAdapter with governance components.
*
* The adapter wraps ruvbot memory operations with MemoryWriteGate authority
* checks and CoherenceScheduler tracking.
*
* @param memoryGate - The MemoryWriteGate for authority/rate/contradiction checks
* @param coherenceScheduler - The CoherenceScheduler for drift tracking
* @returns A new RuvBotMemoryAdapter instance
*/
export function createRuvBotMemoryAdapter(memoryGate, coherenceScheduler) {
return new RuvBotMemoryAdapter(memoryGate, coherenceScheduler);
}
//# sourceMappingURL=ruvbot-integration.js.map