import fs from "fs"; import os from "os"; import path from "path"; import { afterEach, describe, expect, it, vi } from "vitest"; const GRAPH_RUNTIME_FLAG = "FEATURE_ASSISTANT_GRAPH_RUNTIME_V1"; const ORIGINAL_GRAPH_RUNTIME_FLAG = process.env[GRAPH_RUNTIME_FLAG]; const TEMP_DIRS: string[] = []; function restoreGraphFlag(): void { if (ORIGINAL_GRAPH_RUNTIME_FLAG === undefined) { delete process.env[GRAPH_RUNTIME_FLAG]; return; } process.env[GRAPH_RUNTIME_FLAG] = ORIGINAL_GRAPH_RUNTIME_FLAG; } function cleanupTempDirs(): void { for (const dir of TEMP_DIRS.splice(0)) { fs.rmSync(dir, { recursive: true, force: true }); } } function createSnapshotRoot(records: Array>): string { const root = fs.mkdtempSync(path.join(os.tmpdir(), "assistant-datalayer-graph-")); TEMP_DIRS.push(root); const payload = JSON.stringify({ records }, null, 2); fs.writeFileSync( path.resolve(root, "09_samples_key_fields_Recorder_Ref_Supplier_Buyer_Responsible.json"), payload, "utf-8" ); return root; } async function executeHybrid(input: { flag: "0" | "1"; query: string; records: Array>; }) { process.env[GRAPH_RUNTIME_FLAG] = input.flag; vi.resetModules(); const { AssistantDataLayer } = await import("../src/services/assistantDataLayer"); const rootDir = createSnapshotRoot(input.records); const dataLayer = new AssistantDataLayer(rootDir); return dataLayer.executeRoute("hybrid_store_plus_live", input.query); } function buildDeferredRecord(): Record { return { source_entity: "Document", source_id: "DOC-97-1", display_name: "Deferred expense lifecycle node", unknown_link_count: 1, attributes: { Recorder: "DOC-97-REC", Period: "2020-06-30T00:00:00", Description: "deferred expense 97 writeoff lifecycle expected transition" }, links: [ { relation: "document_has_counterparty", target_entity: "Counterparty", target_id: "CP-97-1", source_field: "Counterparty" }, { relation: "document_refers_to_document", target_entity: "Document", target_id: "DOC-97-LINK", source_field: "Recorder" } ] }; } describe.sequential("assistant data layer graph traversal integration", () => { afterEach(() => { cleanupTempDirs(); restoreGraphFlag(); vi.resetModules(); }); it("applies typed graph traversal for 97 lifecycle query when graph runtime is enabled", async () => { const result = await executeHybrid({ flag: "1", query: "Check account 97 for 2020-06 and show where expected writeoff transition is missing.", records: [buildDeferredRecord()] }); expect(result.status).toBe("ok"); const summary = result.summary as Record; expect(summary.graph_runtime_enabled).toBe(true); expect(summary.graph_eligible).toBe(true); expect(summary.graph_traversal_applied).toBe(true); const traversal = summary.graph_traversal as Record; expect(traversal.planner_mode).toBe("typed_domain_path"); expect((traversal.target_domains as string[]).includes("deferred_expense")).toBe(true); const signalCounts = traversal.signal_counts as Record; expect(Number(signalCounts.missing_transition ?? 0)).toBeGreaterThan(0); }); it("maps VAT and period close prompts to graph target domains without changing prompt set", async () => { const result = await executeHybrid({ flag: "1", query: "For VAT in 2020-06 show document/register conflict and closure risk for period close.", records: [buildDeferredRecord()] }); const summary = result.summary as Record; const semanticProfile = summary.semantic_profile as Record; const graphProfile = semanticProfile.graph_traversal as Record; const targetDomains = Array.isArray(graphProfile.target_domains) ? (graphProfile.target_domains as string[]) : []; expect(targetDomains.includes("vat_flow")).toBe(true); expect(targetDomains.includes("period_close")).toBe(true); }); it("keeps graph traversal disabled when feature flag is off", async () => { const result = await executeHybrid({ flag: "0", query: "Check account 97 for 2020-06 and show where expected writeoff transition is missing.", records: [buildDeferredRecord()] }); const summary = result.summary as Record; expect(summary.graph_runtime_enabled).toBe(false); expect(summary.graph_traversal_applied).toBe(false); const traversal = summary.graph_traversal as Record; expect(traversal.planner_mode).toBe("semantic_only"); }); });