/** * Regression: focused deconstruct targetNodeId identity boundary. * * Verifies the deterministic enforcement invariant: * request.targetNodeId (original graph node ID) must be the final * API response targetNodeId regardless of what the model returns. */ import { describe, it, expect, vi } from "vitest"; // ── helpers ────────────────────────────────────────────────────────────── function makeMockProvider(inventedTargetNodeId) { return { generateReconstruction: vi.fn().mockResolvedValue({ targetNodeId: inventedTargetNodeId, observations: ["doc is minimal", "processes in founder's head"], uncertainties: ["whether formal docs can capture tacit knowledge"], assumptions: ["documentation is primary mechanism for knowledge transfer"], relationships: [ { from: "founder", to: "processes", type: "holds", rationale: "tacit" }, { from: "ops-context", to: "docs-infra", type: "depends_on", rationale: "formal docs required" }, ], possibleFollowUpQuestions: [ "What processes does the founder hold tacitly?", "How is knowledge transferred when founder is unavailable?", ], }), }; } // ── Boundary test ──────────────────────────────────────────────────────── describe("focused-deconstruct targetNodeId identity boundary", () => { it("request targetNodeId overrides model-invented targetNodeId", async () => { const requestTargetNodeId = "nk04xvk"; // original graph node ID const inventedModelId = "invented-model-id"; vi.doMock("@/lib/llm/provider", () => ({ getProvider: () => makeMockProvider(inventedModelId), })); const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js"); const requestBody = { targetNodeId: requestTargetNodeId, targetLabel: "Whether unclear or uneven distribution of responsibilities is preventing autonomy in key areas.", targetDescription: "Original open question node label", centralStatement: "Current operational context and documentation state", question: "What was the comparable state before whether unclear or uneven distribution of responsibilities is preventing autonomy in key areas?", answer: "Documentation is minimal, most processes are in the head of the founder.", }; const request = new Request("http://localhost/api/focused-investigation/deconstruct", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody), }); const response = await POST(request); expect(response.status).toBe(200); const json = await response.json(); expect(json.success).toBe(true); // THE INVARIANT: final API targetNodeId = request targetNodeId (authoritative) expect(json.targetNodeId).toBe(requestTargetNodeId); expect(json.targetNodeId).not.toBe(inventedModelId); }); it("semantic fields pass through unchanged from model", async () => { const mockObs = ["doc is minimal", "processes in founder's head"]; const mockUnc = ["whether formal docs can capture tacit knowledge"]; const mockAssm = ["documentation is primary mechanism for knowledge transfer"]; const mockRel = [ { from: "founder", to: "processes", type: "holds", rationale: "tacit" }, { from: "ops-context", to: "docs-infra", type: "depends_on", rationale: "formal docs required" }, ]; const mockFuq = [ "What processes does the founder hold tacitly?", "How is knowledge transferred when founder is unavailable?", ]; vi.doMock("@/lib/llm/provider", () => ({ getProvider: () => ({ generateReconstruction: vi.fn().mockResolvedValue({ targetNodeId: "some-invented-id", observations: mockObs, uncertainties: mockUnc, assumptions: mockAssm, relationships: mockRel, possibleFollowUpQuestions: mockFuq, }), }), })); const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js"); const request = new Request("http://localhost/api/focused-investigation/deconstruct", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ targetNodeId: "nk04xvk", targetLabel: "label", targetDescription: "desc", centralStatement: "central", question: "question?", answer: "answer.", }), }); const response = await POST(request); const json = await response.json(); // Semantic fields unchanged expect(json.observations).toEqual(mockObs); expect(json.uncertainties).toEqual(mockUnc); expect(json.assumptions).toEqual(mockAssm); expect(json.relationships).toEqual(mockRel); expect(json.possibleFollowUpQuestions).toEqual(mockFuq); }); it("contribution append preserves authoritative targetNodeId", () => { // Simulates reasoning-workspace.jsx:1178-1189 after the fix: // onFocusedContribution calls with body.targetNodeId (the original graph node) const requestTargetNodeId = "nk04xvk"; const inventedModelId = "invented-model-id"; const contribution = { targetNodeId: requestTargetNodeId, targetLabel: "label", targetDescription: "desc", question: "question?", answer: "answer.", observations: ["obs1"], uncertainties: ["unc1"], assumptions: ["asm1"], relationships: [{ from: "a", to: "b", type: "depends_on" }], possibleFollowUpQuestions: ["fuq1"], }; expect(contribution.targetNodeId).toBe(requestTargetNodeId); expect(contribution.targetNodeId).not.toBe(inventedModelId); // Simulates ThreadContributionsBadge filter: contributions.filter(c => c.targetNodeId === nodeId) const threadContribs = [contribution].filter((c) => c.targetNodeId === requestTargetNodeId); expect(threadContribs.length).toBe(1); }); it("full identity path: request → response → contribution", async () => { // Reset modules to avoid mock leakage from earlier tests vi.resetModules(); const originalNodeId = "nk04xvk"; const modelInventedId = "investigation_node_responsibility_distribution_autonomy"; vi.doMock("@/lib/llm/provider", () => ({ getProvider: () => ({ generateReconstruction: vi.fn().mockResolvedValue({ targetNodeId: modelInventedId, observations: ["Documentation is minimal."], uncertainties: [], assumptions: [ "That formal documentation is the primary mechanism for capturing or transferring the founder's tacit knowledge of processes.", ], relationships: [ { from: "Founder", to: "Processes", type: "holds" }, { from: "Operational Context", to: "Documentation Infrastructure", type: "affects" }, ], possibleFollowUpQuestions: [], }), }), })); const { POST } = await import("../app/api/focused-investigation/deconstruct/route.js"); const request = new Request("http://localhost/api/focused-investigation/deconstruct", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ targetNodeId: originalNodeId, targetLabel: "Whether unclear or uneven distribution of responsibilities is preventing autonomy in key areas.", targetDescription: "Original open question node description", centralStatement: "Central statement", question: "What was the comparable state before whether unclear or uneven distribution of responsibilities is preventing autonomy in key areas?", answer: "Documentation is minimal, most processes are in the head of the founder.", }), }); const response = await POST(request); expect(response.status).toBe(200); const json = await response.json(); // Identity path verification: // 1. Request targetNodeId expect(json.targetNodeId).toBe(originalNodeId); // 2. Response carries authoritative identity (not model-invented) expect(json.targetNodeId).not.toBe(modelInventedId); // 3. Semantic fields from the model remain unchanged expect(json.observations).toEqual(["Documentation is minimal."]); expect(json.uncertainties).toEqual([]); expect(json.assumptions).toEqual([ "That formal documentation is the primary mechanism for capturing or transferring the founder's tacit knowledge of processes.", ]); expect(json.relationships).toEqual([ { from: "Founder", to: "Processes", type: "holds" }, { from: "Operational Context", to: "Documentation Infrastructure", type: "affects" }, ]); expect(json.possibleFollowUpQuestions).toEqual([]); // 4. Stored contribution would use originalNodeId (not modelInventedId) const stored = { ...json }; expect(stored.targetNodeId).toBe(originalNodeId); }); });