UNPKG

mcard-js

Version:

MCard - Content-addressable storage with cryptographic hashing, handle resolution, and vector search for Node.js and browsers

431 lines 19.1 kB
/** * CLMRunner - Execute JavaScript logic from CLM specifications * * Node.js PTR runtime for interpreting Cubical Logic Models. * * Refactored to use modular components from ./clm/ */ import * as path from 'path'; import * as yaml from 'yaml'; import { RuntimeFactory } from '../runtimes/index.js'; import { PCard } from '../../../model/PCard.js'; import { VCard } from '../../../model/VCard.js'; // Utilities import { asObject, buildBaseContext, buildExecutionResult, isRecursiveCLM, isMultiRuntime, } from './utils.js'; // Builtins import { isNetworkBuiltin, isLoaderBuiltin, getHandleBuiltinType, isStaticServerBuiltin, isWebSocketServerBuiltin } from '../core/operations/index.js'; import { executeHandleVersion, executeHandlePrune } from '../core/operations/handle.js'; import { executeStaticServer } from '../core/operations/static-server.js'; import { executeWebSocketServer } from '../core/operations/websocket-server.js'; // Other modules import { CLMLoader } from './loader.js'; import { executeMultiRuntime } from './multiruntime.js'; export class CLMRunner { loader; timeout; collection; constructor(basePath = process.cwd(), timeout = 5000, collection) { this.loader = new CLMLoader(basePath); this.timeout = timeout; this.collection = collection; } /** * Run a CLM directly from a file path. */ async runFile(clmPath, input) { const fullPath = path.isAbsolute(clmPath) ? clmPath : path.resolve(this.loader.basePath, clmPath); const clm = this.loader.load(fullPath); const chapterDir = path.dirname(fullPath); return this.executeCLM(clm, chapterDir, input || {}); } /** * Execute a CLM specification with given input. * * Implements Petri Net Transition Semantics: * 1. Constructs PCard (Transition) * 2. Checks Pre-conditions (Firing Rule) * 3. Executes Logic * 4. Produces VerificationVCard (Token) * 5. Persists to Collection (Place) */ async executeCLM(clm, chapterDir, input) { // 1. Construct PCard (Transition) // We recreate the YAML content to ensure content-addressability const clmContent = yaml.stringify(clm); const pcard = await PCard.create(clmContent); // Persist PCard if collection available if (this.collection) { await this.collection.add(pcard); } // 0. GATEKEEPER CHECK (VCard Enforcement) const inputObj = asObject(input); const vcardManifest = inputObj.vcard_manifest || {}; // Convert plain object to Map for PCard.canFire const manifestMap = new Map(Object.entries(vcardManifest)); const gatekeeperStatus = pcard.canFire(manifestMap); if (!gatekeeperStatus.canFire) { const errorMsg = `SecurityError: Gatekeeper Rejection. Missing required VCards: ${gatekeeperStatus.missing.join(', ')}`; // Log error regardless of enforcement console.error(`[CLMRunner] ${errorMsg}`); if (inputObj.enforce_gatekeeper) { throw new Error(errorMsg); } else { console.warn("[CLMRunner] Gatekeeper check failed but enforcement is currently optional (input.enforce_gatekeeper = false). Proceeding with caution."); } } const startTime = Date.now(); const config = clm.clm.concrete || { manifestation: 'Unknown', description: 'Unknown' }; const runtimeName = config.runtime || 'lambda'; // Handle recursive CLM if (isRecursiveCLM(runtimeName)) { return this.executeRecursive(runtimeName, config, chapterDir, input, clm); } let result; try { const builtin = config.builtin; const operation = config.process || config.action || config.operation; // Route to appropriate handler // NOTE: Check specific builtins BEFORE isNetworkBuiltin (which uses prefix matching) if (isLoaderBuiltin(builtin, operation) || runtimeName === 'loader' || runtimeName === 'collection_loader') { result = await this.executeLoader(runtimeName, config, clm, chapterDir, input, startTime); } else if (isStaticServerBuiltin(builtin, operation)) { // Static server builtin - extract inner config object const serverConfig = config.config || {}; const serverResult = await executeStaticServer(serverConfig, asObject(input), chapterDir); result = buildExecutionResult(serverResult.success, serverResult, startTime, clm); } else if (isWebSocketServerBuiltin(builtin, operation)) { // WebSocket server builtin - extract inner config object const wsConfig = config.config || {}; const wsResult = await executeWebSocketServer(wsConfig, asObject(input), chapterDir); result = buildExecutionResult(wsResult.success, wsResult, startTime, clm); } else if (isNetworkBuiltin(builtin, operation)) { result = await this.executeNetwork(config, clm, chapterDir, input, startTime); } else { const handleType = getHandleBuiltinType(builtin, operation); if (handleType) { const handleResult = handleType === 'version' ? await executeHandleVersion(input) : await executeHandlePrune(input); result = buildExecutionResult(true, handleResult, startTime, clm); } else { // Standard runtime execution result = await this.executeStandard(runtimeName, config, clm, chapterDir, input, startTime); } } } catch (err) { result = buildExecutionResult(false, undefined, startTime, clm, err instanceof Error ? err.message : String(err)); } // 4. Produce VerificationVCard (Token) // Only if we have a collection to store it in if (this.collection) { try { // Determine previous hash from input if available (provenance chain) const inputObj = asObject(input); let previousVCard; if (inputObj.previous_hash && typeof inputObj.previous_hash === 'string') { const prevCard = await this.collection.get(inputObj.previous_hash); if (prevCard && await VCard.fromMCard(prevCard)) { previousVCard = await VCard.fromMCard(prevCard); } } const vcard = await VCard.createVerificationVCard(pcard, { success: result.success, result: result.result, error: result.error, executionTime: result.executionTime }, previousVCard, result.success); await this.collection.add(vcard); // Attach the VCard info to the result for visibility result.petriNet = { pcardHash: pcard.hash, vcardHash: vcard.hash, handle: vcard.getTokenHandle() }; } catch (e) { // Don't fail the execution if token creation fails, but log it console.warn(`[CLMRunner] Failed to create VerificationVCard: ${e}`); } } return result; } /** * Execute a CLM across multiple runtimes and verify consensus. */ async executeMultiRuntime(clm, chapterDir, input) { return executeMultiRuntime(clm, chapterDir, input); } /** * Check if a CLM is a multi-runtime CLM. */ isMultiRuntime(clm) { return isMultiRuntime(clm); } /** * Verify CLM output against expected result. */ async verifyCLM(clm, chapterDir, input, expected) { const executionResult = await this.executeCLM(clm, chapterDir, input); return { verified: executionResult.success && JSON.stringify(executionResult.result) === JSON.stringify(expected), expected, actual: executionResult.result, executionResult, }; } /** * Run all examples from a CLM specification. */ async runExamples(clm, chapterDir) { const examples = clm.examples || []; const results = []; for (const example of examples) { const result = await this.executeCLM(clm, chapterDir, example.input); results.push({ name: example.name, result }); } return results; } /** * Summarize example runs. */ summarizeExampleRuns(clm, results) { let passed = 0; const summaryResults = results.map((res, index) => { const example = clm.examples?.[index]; const expected = example?.expected_output; const resultContains = example?.result_contains; let match = false; if (res.result.success) { if (resultContains !== undefined) { const resultStr = typeof res.result.result === 'string' ? res.result.result : JSON.stringify(res.result.result); match = resultStr.includes(String(resultContains)); } else if (expected === undefined) { match = true; } else { match = JSON.stringify(res.result.result) === JSON.stringify(expected); } } if (match) passed += 1; return { case: index + 1, name: res.name, input: example?.input, result: res.result.result, error: res.result.error, expected: resultContains !== undefined ? `contains: ${resultContains}` : expected, match, }; }); return { total: summaryResults.length, passed, results: summaryResults }; } /** * Build CLM banner for display. */ buildCLMBanner(clm) { return { header: [ `--- Executing ${clm.chapter.title} ---`, `🧊 CLM Cube:`, ` - Abstract (Why): ${clm.clm.abstract?.concept || 'Unknown'}`, ` - Concrete (How): ${clm.clm.concrete?.manifestation || 'Unknown'}`, ` - Balanced (What): ${clm.clm.balanced?.expectation || 'Unknown'}`, ], }; } /** * Build execution report. */ buildExecutionReport(clm, execution) { return { status: execution.success ? 'success' : 'failure', result: execution.result, error: execution.error, chapter_id: clm.chapter.id, chapter_title: clm.chapter.title, }; } /** * Build summary report. */ buildSummaryReport(clm, summary) { return { status: summary.passed === summary.total ? 'success' : 'failure', result: { success: summary.passed === summary.total, total: summary.total, results: summary.results.map((r) => ({ case: r.case, name: r.name, result: r.result, error: r.error, })), }, chapter_id: clm.chapter.id, chapter_title: clm.chapter.title, }; } // ─── Private Execution Methods ─────────────────────────────────────────── async executeLoader(runtimeName, config, clm, chapterDir, input, startTime) { const targetRuntime = (runtimeName === 'collection_loader') ? 'collection_loader' : 'loader'; const loaderRuntime = RuntimeFactory.getRuntime(targetRuntime); const loaderContext = buildBaseContext(clm, input); const result = await loaderRuntime.execute('', loaderContext, config, chapterDir); return buildExecutionResult(true, result, startTime, clm); } async executeNetwork(config, clm, chapterDir, input, startTime) { const networkRuntime = RuntimeFactory.getRuntime('network', { collection: this.collection }); const networkContext = this.buildNetworkContext(clm, input, chapterDir); const result = await networkRuntime.execute('', networkContext, config, chapterDir); return buildExecutionResult(true, result, startTime, clm); } async executeStandard(runtimeName, config, clm, chapterDir, input, startTime) { const runtime = RuntimeFactory.getRuntime(runtimeName, { collection: this.collection }); const codeOrPath = this.resolveCodeOrPath(runtimeName, config, clm, chapterDir, input); if (!codeOrPath) { if (runtimeName === 'lambda') throw new Error(`Lambda runtime requires input expression or term`); throw new Error(`Execution source not found for runtime ${runtimeName}`); } let executionContext = input; if (runtimeName === 'javascript') { executionContext = this.buildJavaScriptContext(input, chapterDir); } // Lambda runtime needs longer timeout due to async overhead vs Python's sync implementation // Allow CLM config to override with explicit maxTimeMs, otherwise use 30s for lambda const lambdaTimeout = config.maxTimeMs || config.max_time_ms || 30000; // Pass io_effects from CLM config to lambda runtime const ioEffects = config.io_effects || clm.clm?.concrete?.io_effects; const effectiveConfig = runtimeName === 'lambda' ? { ...config, maxTimeMs: lambdaTimeout, io_effects: ioEffects } : config; let result = await runtime.execute(codeOrPath, executionContext, effectiveConfig, chapterDir); // Unwrap LambdaRuntimeResult to get the actual value for test comparisons if (runtimeName === 'lambda' && result && typeof result === 'object') { const lambdaRes = result; if (lambdaRes.success !== undefined) { if (!lambdaRes.success) { return buildExecutionResult(false, null, startTime, clm, lambdaRes.error); } // Prefer prettyPrint for term-returning operations, otherwise use result result = lambdaRes.prettyPrint || lambdaRes.result; } } return buildExecutionResult(true, result, startTime, clm); } async executeRecursive(runtimePath, config, chapterDir, input, originalClm) { const startTime = Date.now(); const boundary = config.boundary || 'intrinsic'; try { const metaClmPath = path.resolve(chapterDir, runtimePath); const metaClm = this.loader.load(metaClmPath); const metaDir = path.dirname(metaClmPath); const metaContext = { ...asObject(input), source_pcard_title: originalClm.chapter.title, concrete: originalClm.clm.concrete, abstract: originalClm.clm.abstract, __input_content__: input }; let metaResult; if (boundary === 'extrinsic') { const isolatedRunner = new CLMRunner(this.loader.basePath, this.timeout, this.collection); metaResult = await isolatedRunner.executeCLM(metaClm, metaDir, metaContext); } else { metaResult = await this.executeCLM(metaClm, metaDir, metaContext); } return { success: metaResult.success, result: metaResult.result, error: metaResult.error, executionTime: Date.now() - startTime + metaResult.executionTime, clm: { chapter: originalClm.chapter.title, concept: originalClm.clm.abstract?.concept || 'Unknown', manifestation: originalClm.clm.concrete?.manifestation || 'Unknown', boundary } }; } catch (e) { return buildExecutionResult(false, undefined, startTime, originalClm, e instanceof Error ? e.message : String(e), boundary); } } // ─── Private Helpers ───────────────────────────────────────────────────── resolveCodeOrPath(runtimeName, config, clm, chapterDir, input) { if (['rust', 'c'].includes(runtimeName)) { let binaryPath = config.binary_path; if (!binaryPath && config.runtimes_config) { const rc = config.runtimes_config.find((r) => r.name === runtimeName); binaryPath = rc?.binary; } return binaryPath || null; } if (runtimeName === 'wasm') { let mod = config.wasm_module; if (!mod && config.runtimes_config) { const rc = config.runtimes_config.find((r) => r.name === runtimeName); mod = rc?.module; } return mod || null; } if (runtimeName === 'llm') return "llm-prompt"; if (runtimeName === 'lambda') { const inputObj = asObject(input); return inputObj.expression || inputObj.term || "lambda-op"; } return this.loader.loadLogicFile(clm, chapterDir); } buildNetworkContext(clm, input, chapterDir) { const baseContext = buildBaseContext(clm, input); return { ...baseContext, input, user_input: input, secrets: process.env, process, runCLM: this.buildRunCLM(chapterDir), }; } buildJavaScriptContext(input, chapterDir) { return { ...asObject(input), console, setTimeout, clearTimeout, setInterval, clearInterval, process, runCLM: async (clmFile, clmInput) => { const fullPath = path.resolve(chapterDir, clmFile); const res = await this.runFile(fullPath, clmInput); if (!res.success) throw new Error(res.error || 'CLM execution failed'); return res.result; } }; } buildRunCLM(chapterDir) { return async (clmFile, clmInput) => { const fullPath = path.resolve(chapterDir, clmFile); return this.runFile(fullPath, clmInput); }; } } //# sourceMappingURL=runner.js.map