/** * RTO.20A — Derived Focused Current-View Apparatus * * Purpose: Test whether an LLM can produce a concise, faithful "where are we * now?" view for one selected investigation using only authoritative granular * fragments and their known relationship, without whole-case reconstruction, * accumulated focused state, or introducing new evidential claims. * * Design boundary: * - Standalone experimental runner. * - Zero production code changes. * - Inspect-only by default (zero live model calls). * - --live flag enables exactly one live call producing a derived current view. * * Fixed record: * Fragment 2 — competitor hiring + conference signals * Fragment 3 — technical architecture + prototype evidence * One known relationship between them * * Output contract (presentation only, not new reasoning): * { currentView, whatChanged, remainingUncertainties } */ import fs from "fs/promises"; import path from "path"; import { fileURLToPath } from "url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const resultsDir = path.resolve(__dirname, "../../tests/experimental/results"); // ─── Fragment paths ────────────────────────────────────────────────────────── const FRAGMENT_2_PATH = path.resolve( __dirname, "../../tests/experimental/results/rto-granular-fragment-turn2.json" ); const FRAGMENT_3_PATH = path.resolve( __dirname, "../../tests/experimental/results/rto-granular-fragment-turn3.json" ); // ─── Known relationship (supplied, not discovered) ─────────────────────────── const KNOWN_RELATIONSHIP = "Fragment 3's technical evidence bears on Fragment 2's earlier uncertainty " + "by making active competing-product development more plausible, while " + "commercialisation and other uncertainties remain unresolved."; // ─── Load fragments ────────────────────────────────────────────────────────── async function loadFragments() { const frag2Raw = await fs.readFile(FRAGMENT_2_PATH, "utf-8"); const frag3Raw = await fs.readFile(FRAGMENT_3_PATH, "utf-8"); const frag2 = JSON.parse(frag2Raw); const frag3 = JSON.parse(frag3Raw); if (!frag2.structuredResult) { throw new Error("Fragment 2 missing structuredResult"); } if (!frag3.structuredResult) { throw new Error("Fragment 3 missing structuredResult"); } return { fragment2: frag2, structuredResult2: frag2.structuredResult, fragment3: frag3, structuredResult3: frag3.structuredResult, }; } // ─── Build the derived-current-view prompt ───────────────────────────────────── function buildCurrentViewPrompt(srs2, srs3) { const parts = [ "You are producing a FACILITATOR CURRENT VIEW — a concise summary of where one", "selected investigation stands right now. This is PRESENTATION ONLY.", "", "Return exactly one JSON object with these fields:", "- currentView (string): Where does this investigation stand? What do we understand?", "- whatChanged (string): How has the interpretation shifted from the earlier evidence?", "- remainingUncertainties (array of strings): What is still unresolved?", "", "CRITICAL BOUNDARIES:", "- Every claim must be traceable to Fragment 2, Fragment 3, or the known relationship.", "- Do NOT introduce new evidential claims.", "- Do NOT take action or advise on decisions.", "- Do NOT predict decisions or outcomes.", "- Do NOT select next questions or take ownership from the user.", "- This output is derived presentation, not new authoritative reasoning.", "", "Selected investigation identity:", ]; // Node label from Fragment 2 (they share the same target) parts.push(`- target node: "${srs2.targetNodeId}"`); parts.push(""); // ── Fragment 2 ──────────────────────────────────────────────── parts.push("=== Fragment 2 (earlier evidence) ==="); if (srs2.observations && srs2.observations.length) { parts.push("Observations:"); for (const o of srs2.observations) { parts.push(` - ${o}`); } } if (srs2.uncertainties && srs2.uncertainties.length) { parts.push("Uncertainties:"); for (const u of srs2.uncertainties) { parts.push(` - ${u}`); } } if (srs2.assumptions && srs2.assumptions.length) { parts.push("Assumptions:"); for (const a of srs2.assumptions) { parts.push(` - ${a}`); } } if (srs2.relationships && srs2.relationships.length) { parts.push("Relationships:"); for (const r of srs2.relationships) { parts.push(` - ${r.from} -> ${r.to} (${r.type})`); } } parts.push(""); // ── Fragment 3 ──────────────────────────────────────────────── parts.push("=== Fragment 3 (newer evidence) ==="); if (srs3.observations && srs3.observations.length) { parts.push("Observations:"); for (const o of srs3.observations) { parts.push(` - ${o}`); } } if (srs3.uncertainties && srs3.uncertainties.length) { parts.push("Uncertainties:"); for (const u of srs3.uncertainties) { parts.push(` - ${u}`); } } if (srs3.assumptions && srs3.assumptions.length) { parts.push("Assumptions:"); for (const a of srs3.assumptions) { parts.push(` - ${a}`); } } if (srs3.relationships && srs3.relationships.length) { parts.push("Relationships:"); for (const r of srs3.relationships) { parts.push(` - ${r.from} -> ${r.to} (${r.type})`); } } parts.push(""); // ── Known relationship ──────────────────────────────────────── parts.push("=== Known relationship (supplied, not discovered) ==="); parts.push(KNOWN_RELATIONSHIP); parts.push(""); // ── Instructions ────────────────────────────────────────────── parts.push("Produce a concise facilitator view of where this selected investigation stands now."); parts.push("Answer: What do we understand now? How has the interpretation shifted? What remains unresolved?"); parts.push("Do NOT take action or advise on decisions."); parts.push("Return JSON only. No additional commentary."); return parts.join("\n"); } // ─── Output schema for live mode ────────────────────────────────────────────── const CURRENT_VIEW_SCHEMA = { required: ["currentView", "whatChanged", "remainingUncertainties"], forbidden: [ "recommendation", "nextQuestion", "confidence", "materiality", "decision", "action", "status", "score", "addedNodes", "updatedNodes", "resolvedNodeIds", "selectedQuestion", ], }; function validateOutputSchema(result) { const errors = []; for (const field of CURRENT_VIEW_SCHEMA.required) { if (!(field in result)) { errors.push(`Missing required field: ${field}`); } } if (!Array.isArray(result.remainingUncertainties)) { errors.push("remainingUncertainties must be an array"); } else if (result.remainingUncertainties.some((s) => typeof s !== "string")) { errors.push("remainingUncertainties array items must all be strings"); } for (const field of CURRENT_VIEW_SCHEMA.forbidden) { if (field in result && typeof result[field] !== "undefined") { errors.push(`Forbidden field present: ${field}`); } } return errors; } // ─── Inspect mode (default: zero model calls) ───────────────────────────────── async function inspectApparatus() { console.log("=== RTO.20A Derived Focused Current-View Apparatus (inspect-only) ===\n"); // Load and verify fragments let fragment2, structuredResult2, fragment3, structuredResult3; try { const frags = await loadFragments(); fragment2 = frags.fragment2; structuredResult2 = frags.structuredResult2; fragment3 = frags.fragment3; structuredResult3 = frags.structuredResult3; } catch (e) { console.log("ERROR: Cannot load fragments:", e.message); process.exit(1); } // Fragment verification const f2Loaded = typeof structuredResult2 === "object" && structuredResult2 !== null; const f3Loaded = typeof structuredResult3 === "object" && structuredResult3 !== null; console.log("--- Fragment verification ---"); console.log(`Fragment 2 loaded: ${f2Loaded ? "YES" : "NO"}`); console.log(`Fragment 3 loaded: ${f3Loaded ? "YES" : "NO"}`); // Build prompt for inspection const prompt = buildCurrentViewPrompt(structuredResult2, structuredResult3); // Known relationship verification const relationshipIncluded = prompt.includes(KNOWN_RELATIONSHIP); console.log("\n--- Relationship ---"); console.log(`Known relationship included: ${relationshipIncluded ? "YES" : "NO"}`); // Context boundary checks console.log("\n--- Context boundaries ---"); const boundaryChecks = { "Whole SituationGraph": !prompt.includes("SituationGraph"), "Central case statement": !prompt.includes("central case") && !prompt.includes("centralStatement"), "Turn history": !prompt.includes("turn history"), "Other fragments": !prompt.includes("Fragment 1"), "Prior focusedUnderstanding": !prompt.includes("focusedUnderstanding"), "Prior decisionSignificance": !prompt.includes("decisionSignificance"), "Graph mutation instructions": !prompt.includes("addedNodes") && !prompt.includes("updatedNodes"), "Recommendation instructions": !prompt.includes("recommend what"), }; for (const [name, passed] of Object.entries(boundaryChecks)) { const status = passed ? "NO" : "VIOLATION"; console.log(` ${name}: ${status}`); } // Schema validation capability const mockOutput = { currentView: "test", whatChanged: "test", remainingUncertainties: ["test"], }; const schemaErrors = validateOutputSchema(mockOutput); console.log("\n--- Output schema ---"); console.log(`Required fields: ${CURRENT_VIEW_SCHEMA.required.join(", ")}`); console.log(`Validation capability: ${schemaErrors.length === 0 ? "valid" : "errors"}`); // Prompt size console.log("\n--- Prompt size ---"); console.log(`inputCharacterCount: ${prompt.length}`); // Live route console.log("\n--- Live route ---"); console.log("node scripts/experimental/rto-derived-focused-current-view.mjs --live"); // Inspect summary const allFragmentsOK = f2Loaded && f3Loaded; const relationshipOK = relationshipIncluded; const noViolations = Object.values(boundaryChecks).every((v) => v === true); console.log("\n=== SUMMARY ==="); console.log(`Inspect live calls: 0`); console.log(`Fragments valid: ${allFragmentsOK ? "YES" : "NO"}`); console.log(`Boundary clean: ${noViolations ? "YES" : "VIOLATION DETECTED"}`); const passed = allFragmentsOK && relationshipOK && noViolations; console.log(`\nInspect result: ${passed ? "PASS" : "FAIL"}`); if (!passed) { console.log("\nApparatus cannot satisfy boundary without production changes."); console.log("F — APPARATUS CANNOT ISOLATE DERIVED VIEW"); return null; } return { prompt, inputCharacterCount: prompt.length }; } // ─── Live execution (exactly one model call) ────────────────────────────────── async function executeLive() { const baseUrl = process.env.OLLAMA_BASE_URL; if (!baseUrl) { console.error("OLLAMA_BASE_URL not set. Cannot execute live mode."); process.exit(1); } if (baseUrl === "http://localhost:11434" || baseUrl === "http://127.0.0.1:11434") { console.error("Refuses localhost fallback. OLLAMA_BASE_URL=" + baseUrl); process.exit(1); } var z = (await import("zod")).z; const { structuredResult2, structuredResult3 } = await loadFragments(); const prompt = buildCurrentViewPrompt(structuredResult2, structuredResult3); // Schema for live output validation const currentViewSchema = z.object({ currentView: z.string().min(1), whatChanged: z.string().min(1), remainingUncertainties: z.array(z.string()), }).strict(); const startedAt = Date.now(); console.log("Live mode: sending to model..."); const provider = (await import(path.resolve(__dirname, "../../lib/llm/provider.js"))).getProvider(); const configEnv = await import(path.resolve(__dirname, "../../lib/config.js")); const modelName = configEnv.assertConfig().OLLAMA_MODEL; var raw = await provider.generateReconstruction(prompt, modelName); var elapsedMs = Date.now() - startedAt; console.log(`Model response received in ${elapsedMs}ms`); const parsedResult = currentViewSchema.parse(raw); // Validate no forbidden fields leaked in const schemaErrors = validateOutputSchema(parsedResult); if (schemaErrors.length > 0) { throw new Error(`Live output schema validation errors: ${schemaErrors.join("; ")}`); } await fs.mkdir(resultsDir, { recursive: true }); const artifactPath = path.resolve( resultsDir, "rto-derived-focused-current-view-live.json" ); const payload = { apparatus: "rto-derived-focused-current-view.mjs", experiment: "RTO.20A", artifactType: "LIVE RESULT — Derived focused current view", modelName: modelName, elapsedMs: elapsedMs, inputCharacterCount: prompt.length, fragment2Path: FRAGMENT_2_PATH, fragment3Path: FRAGMENT_3_PATH, knownRelationshipIncluded: true, wholeSituationGraphSupplied: false, centralCaseStatementSupplied: false, turnHistorySupplied: false, outputSchema: { currentView: typeof parsedResult.currentView === "string", whatChanged: typeof parsedResult.whatChanged === "string", remainingUncertainties: Array.isArray(parsedResult.remainingUncertainties), }, derivedCurrentView: parsedResult, }; await fs.writeFile(artifactPath, JSON.stringify(payload, null, 2)); console.log(`\nLive result written to: ${artifactPath}`); console.log("\n=== Derived Current View ==="); console.log(JSON.stringify(parsedResult, null, 2)); return payload; } // ─── CLI entry point ────────────────────────────────────────────────────────── async function main() { const args = process.argv.slice(2); // --live flag for exactly one live model call if (args.includes("--live")) { const result = await executeLive(); return; } // Default: inspect-only, zero model calls console.log("RTO.20A — Derived Focused Current View"); console.log("Run with --live for one live model call.\n"); const result = await inspectApparatus(); if (result === null) { process.exit(1); } } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });