Experiment 47: created a test-only diagnostic helper that inspects existing graph relationship fields (dependsOn, affects, parentId, childIds on nodes; fromNodeId/toNodeId + relationship on edges) to distinguish coherent investigations (multiple unknowns sharing one anchor) from scattered ones. Three controlled fixtures confirm the helper works: shared_anchor vs separate_anchors vs insufficient_data — all with identical structural counts (6 nodes, 4 active unknowns). All three produce identical too_broad output from the existing assessor, confirming no production code changes needed. Existing-scenario inspection (3 real scenarios from Exp 39-46) all return insufficient_data — current data lacks populated relationship fields on unknown nodes. This means the gap is not purely in assessment logic but also in upstream data quality. Closed Experiment 46. Updated design-evolution-log and handoff.
530 lines
26 KiB
JavaScript
530 lines
26 KiB
JavaScript
/**
|
|
* Experiment 47 — Can Existing Graph Relationships Reveal a Shared Investigation Thread?
|
|
*
|
|
* Passive diagnostic experiment. Tests whether existing graph relationship fields
|
|
* (dependsOn, affects, childIds, parentId, edges) can distinguish:
|
|
* A — several unknowns contributing to one coherent investigation;
|
|
* B — several unknowns belonging to unrelated lines of enquiry;
|
|
* C — several unknowns with no usable relationship data.
|
|
*
|
|
* No production code changes. No existing fixture modification.
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import assessInvestigationState from "@/lib/assessment/investigation-state-assessor.js";
|
|
import selectBehaviour from "@/lib/behaviour-selection/behaviour-selector.js";
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Test-only diagnostic helper
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
/**
|
|
* Inspect whether active unknowns share a common graph anchor.
|
|
* Uses only existing fields: dependsOn, affects, childIds, parentId, edges.
|
|
* Does not inspect node labels or descriptions (no text matching).
|
|
*
|
|
* Deterministic rules:
|
|
* shared_anchor — all active unknowns reference exactly one common node;
|
|
* separate_anchors — unknowns have relationship data but no common anchor;
|
|
* insufficient_data — relationships are absent or incomplete.
|
|
*/
|
|
function inspectSharedUnknownAnchor({ graph }) {
|
|
const nodes = (graph && Array.isArray(graph.nodes)) ? [...graph.nodes] : [];
|
|
const edges = (graph && Array.isArray(graph.edges)) ? [...graph.edges] : [];
|
|
|
|
// Collect active unknowns (unknown kind, not resolved)
|
|
const activeIds = new Set();
|
|
const resolvedSet = new Set(graph.resolvedNodeIds || []);
|
|
for (const n of nodes) {
|
|
if (!n || n.kind !== "unknown") continue;
|
|
if (resolvedSet.has(n.id) || n.status === "resolved") continue;
|
|
activeIds.add(n.id);
|
|
}
|
|
|
|
const activeArr = [...activeIds];
|
|
if (activeArr.length < 2) {
|
|
return { result: "insufficient_data", anchorIds: [], reason: "fewer than two active unknowns" };
|
|
}
|
|
|
|
// Gather all referenced parent IDs from dependsOn, affects, parentId on the active nodes
|
|
const referrerMap = new Map(); // nodeId -> Set of referenced node IDs
|
|
|
|
for (const n of nodes) {
|
|
if (!activeIds.has(n.id)) continue;
|
|
const refs = new Set();
|
|
if (Array.isArray(n.dependsOn)) n.dependsOn.forEach((id) => refs.add(id));
|
|
if (Array.isArray(n.affects)) n.affects.forEach((id) => refs.add(id));
|
|
if (n.parentId) refs.add(n.parentId);
|
|
referrerMap.set(n.id, refs);
|
|
}
|
|
|
|
// Also gather referenced parent IDs from edges where the active unknown is the target
|
|
const edgeAnchors = new Set();
|
|
for (const e of edges) {
|
|
if (!e || !e.fromNodeId || !e.toNodeId) continue;
|
|
if (activeIds.has(e.toNodeId)) {
|
|
edgeAnchors.add(e.fromNodeId);
|
|
}
|
|
}
|
|
|
|
// Merge edge anchors into each referrer's set
|
|
for (const key of referrerMap.keys()) {
|
|
edgeAnchors.forEach((a) => referrerMap.get(key).add(a));
|
|
}
|
|
|
|
// Find common anchor: intersection of all referrer sets
|
|
let common = new Set([...referrerMap.get(activeArr[0]) || []]);
|
|
for (let i = 1; i < activeArr.length; i++) {
|
|
const next = referrerMap.get(activeArr[i]) || new Set();
|
|
common = new Set([...common].filter((x) => next.has(x)));
|
|
}
|
|
|
|
// Filter out non-existent node IDs from common
|
|
const existingIds = new Set(nodes.map((n) => n.id));
|
|
const validCommon = [...common].filter((id) => existingIds.has(id));
|
|
|
|
if (validCommon.length === 1) {
|
|
return { result: "shared_anchor", anchorIds: validCommon, reason: "All active unknowns reference one common node: " + validCommon[0] };
|
|
}
|
|
|
|
// No common intersection — collect all referenced existing nodes as anchors
|
|
const allRefs = new Set();
|
|
for (const id of activeArr) {
|
|
const refs = referrerMap.get(id) || new Set();
|
|
refs.forEach((r) => allRefs.add(r));
|
|
}
|
|
const validAnchors = [...allRefs].filter((id) => existingIds.has(id));
|
|
|
|
if (validAnchors.length > 0) {
|
|
return { result: "separate_anchors", anchorIds: validAnchors, reason: "Active unknowns reference " + validAnchors.length + " distinct nodes with no shared intersection" };
|
|
}
|
|
|
|
// No relationship data at all
|
|
return { result: "insufficient_data", anchorIds: [], reason: "no relationship fields populated on any active unknown" };
|
|
|
|
return { result: "insufficient_data", anchorIds: [], reason: "no relationship fields populated on any active unknown" };
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Node and edge builder helpers
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
function mkNode(id, label, opts = {}) {
|
|
const kind = opts.kind || "unknown";
|
|
const status = opts.status || (kind === "unknown" ? "unknown" : "known");
|
|
const confidence = opts.confidence || (kind === "unknown" ? "low" : "high");
|
|
return {
|
|
id, label, description: label, kind, status, confidence,
|
|
evidenceIds: [], dependsOn: opts.dependsOn ?? [], affects: opts.affects ?? [],
|
|
parentId: opts.parentId ?? null, childIds: opts.childIds ?? []
|
|
};
|
|
}
|
|
|
|
function mkEdge(id, fromNodeId, toNodeId, relationship) {
|
|
return { id, fromNodeId, toNodeId, relationship: relationship || "supports" };
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Fixture A — Shared anchor: all four unknowns depend on one decision node
|
|
* Node count: 6 (1 obs + 1 context + 4 unknowns)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
function buildFixtureA() {
|
|
const nodes = [
|
|
mkNode("obs-1", "The current service handles approximately 200 requests per day across existing regions", { kind: "observation", status: "known", confidence: "medium" }),
|
|
mkNode("ctx-1", "North West region market evaluation in progress", { kind: "state", status: "provisional", confidence: "medium" }),
|
|
mkNode("u-a1", "Whether customer demand exists in the North West region", { dependsOn: ["ctx-1"], affects: ["ctx-1"] }),
|
|
mkNode("u-a2", "What price point the North West market would accept", { dependsOn: ["ctx-1"], affects: ["ctx-1"] }),
|
|
mkNode("u-a3", "Whether delivery infrastructure can support the North West region", { dependsOn: ["ctx-1"], affects: ["ctx-1"] }),
|
|
mkNode("u-a4", "Whether regulatory requirements allow operation in the North West", { dependsOn: ["ctx-1"], affects: ["ctx-1"] })
|
|
];
|
|
|
|
const edges = [
|
|
mkEdge("e-1", "obs-1", "ctx-1", "supports"),
|
|
mkEdge("e-2", "u-a1", "ctx-1", "supports"),
|
|
mkEdge("e-3", "u-a2", "ctx-1", "supports"),
|
|
mkEdge("e-4", "u-a3", "ctx-1", "supports"),
|
|
mkEdge("e-5", "u-a4", "ctx-1", "supports")
|
|
];
|
|
|
|
return {
|
|
situationGraph: {
|
|
centralStatement: "Where should I focus my investigation?",
|
|
currentSummary: "Multiple unknowns related to one decision.",
|
|
nodes, edges, activeUnknownNodeId: "u-a1", resolvedNodeIds: []
|
|
},
|
|
selectedQuestion: null,
|
|
noQuestionReason: "No clear decision target yet.",
|
|
diagnostics: { promptVersion: "v0.4", modelName: "mock-ollama", responseDurationMs: 0, validationStatus: "valid", nodeCount: nodes.length, edgeCount: edges.length, reasoningPattern: null }
|
|
};
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Fixture B — Separate anchors: each unknown depends on a different parent
|
|
* Node count: 6 (1 obs + 1 context + 4 unknowns)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
function buildFixtureB() {
|
|
const nodes = [
|
|
mkNode("obs-1", "The business has been operating for five years without significant growth", { kind: "observation", status: "known", confidence: "medium" }),
|
|
mkNode("ctx-1", "Strategic planning review cycle active", { kind: "state", status: "provisional", confidence: "medium" }),
|
|
mkNode("u-b1", "Whether customer demand exists in new geographic markets", { dependsOn: ["ctx-1"] }),
|
|
mkNode("u-b2", "Whether staff conflict is the primary cause of reduced productivity", { parentId: "ctx-1" }),
|
|
mkNode("u-b3", "Whether relocating would attract a different talent pool", { affects: ["ctx-1"] }),
|
|
mkNode("u-b4", "Whether current pricing aligns with competitor offerings")
|
|
];
|
|
|
|
const edges = [mkEdge("e-1", "obs-1", "ctx-1", "supports")];
|
|
|
|
return {
|
|
situationGraph: {
|
|
centralStatement: "Where should I focus my investigation?",
|
|
currentSummary: "Multiple unknowns related to one decision.",
|
|
nodes, edges, activeUnknownNodeId: "u-b1", resolvedNodeIds: []
|
|
},
|
|
selectedQuestion: null,
|
|
noQuestionReason: "No clear decision target yet.",
|
|
diagnostics: { promptVersion: "v0.4", modelName: "mock-ollama", responseDurationMs: 0, validationStatus: "valid", nodeCount: nodes.length, edgeCount: edges.length, reasoningPattern: null }
|
|
};
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Fixture C — No usable relationships: four unknowns with empty relationship fields
|
|
* Node count: 6 (1 obs + 1 context + 4 unknowns)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
function buildFixtureC() {
|
|
const nodes = [
|
|
mkNode("obs-1", "A manufacturing company reports complaints increased by 35%", { kind: "observation", status: "known", confidence: "medium" }),
|
|
mkNode("ctx-1", "Quality review process initiated", { kind: "state", status: "provisional", confidence: "medium" }),
|
|
mkNode("u-c1", "Whether the complaint increase is sector-wide or product-specific"),
|
|
mkNode("u-c2", "Whether production volume explains the complaint trend"),
|
|
mkNode("u-c3", "Whether quality control procedures are consistently applied"),
|
|
mkNode("u-c4", "Whether reporting standards have changed during the period")
|
|
];
|
|
|
|
return {
|
|
situationGraph: {
|
|
centralStatement: "Where should I focus my investigation?",
|
|
currentSummary: "Multiple unknowns related to one decision.",
|
|
nodes, edges: [], activeUnknownNodeId: "u-c1", resolvedNodeIds: []
|
|
},
|
|
selectedQuestion: null,
|
|
noQuestionReason: "No clear decision target yet.",
|
|
diagnostics: { promptVersion: "v0.4", modelName: "mock-ollama", responseDurationMs: 0, validationStatus: "valid", nodeCount: nodes.length, edgeCount: 0, reasoningPattern: null }
|
|
};
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Clarify eligibility helper (matches production rule)
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
function isClarifyEligible(assessment) {
|
|
if (assessment.conversationHealth.value === "too_broad") return true;
|
|
if (assessment.phase.value === "orienting" && assessment.phase.evidence?.observationDensity < 3) return true;
|
|
return false;
|
|
}
|
|
|
|
/* ═══════════════════════════════════════════════════════════
|
|
* Tests — Structural equality across fixtures
|
|
* ═══════════════════════════════════════════════════════════ */
|
|
|
|
describe("Experiment 47 — Shared-Anchor Coherence Diagnostic", () => {
|
|
|
|
/* ── Build fixtures once for all tests ── */
|
|
|
|
let fixtureA, fixtureB, fixtureC;
|
|
let resultA, resultB, resultC;
|
|
let selA, selB, selC;
|
|
let clarEligibleA, clarEligibleB, clarEligibleC;
|
|
let diagA, diagB, diagC;
|
|
|
|
beforeAll(() => {
|
|
fixtureA = buildFixtureA();
|
|
fixtureB = buildFixtureB();
|
|
fixtureC = buildFixtureC();
|
|
|
|
resultA = assessInvestigationState(fixtureA);
|
|
resultB = assessInvestigationState(fixtureB);
|
|
resultC = assessInvestigationState(fixtureC);
|
|
|
|
selA = selectBehaviour(resultA);
|
|
selB = selectBehaviour(resultB);
|
|
selC = selectBehaviour(resultC);
|
|
|
|
clarEligibleA = isClarifyEligible(resultA);
|
|
clarEligibleB = isClarifyEligible(resultB);
|
|
clarEligibleC = isClarifyEligible(resultC);
|
|
|
|
diagA = inspectSharedUnknownAnchor({ graph: fixtureA.situationGraph });
|
|
diagB = inspectSharedUnknownAnchor({ graph: fixtureB.situationGraph });
|
|
diagC = inspectSharedUnknownAnchor({ graph: fixtureC.situationGraph });
|
|
});
|
|
|
|
/* ═══ Structural equality (identical counts) ═══ */
|
|
|
|
describe("Structural equality across fixtures", () => {
|
|
const verifyCounts = (g, name) => {
|
|
const nodes = g.nodes || [];
|
|
const unknowns = nodes.filter((n) => n.kind === "unknown" && n.status !== "resolved");
|
|
const resolved = g.resolvedNodeIds ? g.resolvedNodeIds.filter((id) => nodes.find((n) => n.id === id)) : [];
|
|
const observations = nodes.filter((n) => (n.kind === "observation" && n.status === "known"));
|
|
|
|
expect(unknowns.length).toBe(4).withContext(name + ": active unknown count");
|
|
expect(resolved.length).toBe(0).withContext(name + ": resolved count");
|
|
expect(observations.length).toBe(1).withContext(name + ": observation count");
|
|
};
|
|
|
|
it("Fixture A: 4 active unknowns, 0 resolved, 1 observation", () => verifyCounts(fixtureA.situationGraph, "A"));
|
|
it("Fixture B: 4 active unknowns, 0 resolved, 1 observation", () => verifyCounts(fixtureB.situationGraph, "B"));
|
|
it("Fixture C: 4 active unknowns, 0 resolved, 1 observation", () => verifyCounts(fixtureC.situationGraph, "C"));
|
|
|
|
it("all fixtures have no selected question", () => {
|
|
expect(fixtureA.selectedQuestion).toBeNull();
|
|
expect(fixtureB.selectedQuestion).toBeNull();
|
|
expect(fixtureC.selectedQuestion).toBeNull();
|
|
});
|
|
|
|
it("all fixtures use identical confidence and status values per node type", () => {
|
|
for (const [name, fg] of [["A", fixtureA], ["B", fixtureB], ["C", fixtureC]]) {
|
|
const nodes = fg.situationGraph.nodes;
|
|
expect(nodes.filter((n) => n.kind === "unknown").every((n) => n.status === "unknown" && n.confidence === "low")).toBe(true);
|
|
expect(nodes.filter((n) => n.kind === "observation")[0].status).toBe("known");
|
|
expect(nodes.filter((n) => n.kind === "observation")[0].confidence).toBe("medium");
|
|
}
|
|
});
|
|
|
|
it("only relationship structure differs between fixtures", () => {
|
|
const countDeps = (fg) => fg.situationGraph.nodes.reduce((sum, n) => sum + (Array.isArray(n.dependsOn) ? n.dependsOn.length : 0), 0);
|
|
expect(countDeps(fixtureA)).toBeGreaterThan(0).withContext("Fixture A has dependency relationships");
|
|
expect(countDeps(fixtureB)).toBeGreaterThan(0).withContext("Fixture B has dependency relationships");
|
|
expect(countDeps(fixtureC)).toBe(0).withContext("Fixture C has no dependency relationships");
|
|
|
|
expect(fixtureA.situationGraph.edges.length).toBe(5);
|
|
expect(fixtureB.situationGraph.edges.length).toBe(1);
|
|
expect(fixtureC.situationGraph.edges.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
/* ═══ Diagnostic helper tests ═══ */
|
|
|
|
describe("Shared-anchor diagnostic", () => {
|
|
it("Fixture A returns shared_anchor", () => {
|
|
expect(diagA.result).toBe("shared_anchor");
|
|
expect(diagA.anchorIds.length).toBe(1);
|
|
expect(diagA.anchorIds[0]).toBe("ctx-1");
|
|
});
|
|
|
|
it("Fixture B returns separate_anchors", () => {
|
|
expect(diagB.result).toBe("separate_anchors");
|
|
expect(diagB.anchorIds.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("Fixture C returns insufficient_data", () => {
|
|
expect(diagC.result).toBe("insufficient_data");
|
|
expect(diagC.anchorIds.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
/* ═══ Assessor results (existing production logic) ═══ */
|
|
|
|
describe("Assessor outputs (production unchanged)", () => {
|
|
it("all fixtures produce the same health result", () => {
|
|
expect(resultA.conversationHealth.value).toBe(resultB.conversationHealth.value);
|
|
expect(resultB.conversationHealth.value).toBe(resultC.conversationHealth.value);
|
|
expect(resultA.conversationHealth.value).toBe("too_broad");
|
|
});
|
|
|
|
it("all fixtures produce the same phase result", () => {
|
|
expect(resultA.phase.value).toBe(resultB.phase.value);
|
|
expect(resultB.phase.value).toBe(resultC.phase.value);
|
|
expect(resultA.phase.value).toBe("cannot_determine");
|
|
});
|
|
|
|
it("all fixtures produce the same progress result", () => {
|
|
expect(resultA.progress.value).toBe(resultB.progress.value);
|
|
expect(resultB.progress.value).toBe(resultC.progress.value);
|
|
expect(resultA.progress.value).toBe("cannot_determine");
|
|
});
|
|
|
|
it("health evidence shows activeUnknownCount = 4 for all", () => {
|
|
expect(resultA.conversationHealth.evidence.activeUnknownCount).toBe(4);
|
|
expect(resultB.conversationHealth.evidence.activeUnknownCount).toBe(4);
|
|
expect(resultC.conversationHealth.evidence.activeUnknownCount).toBe(4);
|
|
});
|
|
});
|
|
|
|
/* ═══ Clarify eligibility ═══ */
|
|
|
|
describe("Clarify eligibility", () => {
|
|
it("all fixtures make Clarify eligible via too_broad Rule A", () => {
|
|
expect(clarEligibleA).toBe(true);
|
|
expect(clarEligibleB).toBe(true);
|
|
expect(clarEligibleC).toBe(true);
|
|
});
|
|
|
|
it("selector behaviour matches Clarify eligibility", () => {
|
|
expect(selA.behaviour).toBe("clarify");
|
|
expect(selB.behaviour).toBe("clarify");
|
|
expect(selC.behaviour).toBe("clarify");
|
|
});
|
|
});
|
|
|
|
/* ═══ Key discrimination test ═══ */
|
|
|
|
describe("Does the diagnostic distinguish coherent from scattered?", () => {
|
|
it("diagnostic distinguishes Fixture A (shared) from Fixture B (separate)", () => {
|
|
expect(diagA.result).not.toBe(diagB.result);
|
|
expect(diagA.result).toBe("shared_anchor");
|
|
expect(diagB.result).toBe("separate_anchors");
|
|
});
|
|
|
|
it("diagnostic distinguishes Fixture C (insufficient) from Fixture A", () => {
|
|
expect(diagC.result).not.toBe(diagA.result);
|
|
});
|
|
|
|
it("assessor does NOT distinguish any pair — all identical regardless of graph relationships", () => {
|
|
const copy = (r) => {
|
|
const c = JSON.parse(JSON.stringify(r));
|
|
delete c.assessedAt;
|
|
return c;
|
|
};
|
|
expect(JSON.stringify(copy(resultA))).toBe(JSON.stringify(copy(resultB)));
|
|
expect(JSON.stringify(copy(resultB))).toBe(JSON.stringify(copy(resultC)));
|
|
});
|
|
|
|
it("the diagnostic result does not affect the assessor output", () => {
|
|
const copy = (r) => {
|
|
const c = JSON.parse(JSON.stringify(r));
|
|
delete c.assessedAt;
|
|
return c;
|
|
};
|
|
expect(JSON.stringify(copy(resultA))).not.toContain("shared_anchor");
|
|
expect(JSON.stringify(copy(resultB))).not.toContain("separate_anchors");
|
|
});
|
|
});
|
|
|
|
/* ═══ Determinism and immutability ═══ */
|
|
|
|
describe("Determinism and immutability", () => {
|
|
it("diagnostic helper is deterministic across multiple calls", () => {
|
|
const d1A = inspectSharedUnknownAnchor({ graph: fixtureA.situationGraph });
|
|
const d2A = inspectSharedUnknownAnchor({ graph: fixtureA.situationGraph });
|
|
expect(d1A.result).toBe(d2A.result);
|
|
expect(JSON.stringify(d1A.anchorIds)).toBe(JSON.stringify(d2A.anchorIds));
|
|
|
|
const d1B = inspectSharedUnknownAnchor({ graph: fixtureB.situationGraph });
|
|
const d2B = inspectSharedUnknownAnchor({ graph: fixtureB.situationGraph });
|
|
expect(d1B.result).toBe(d2B.result);
|
|
|
|
const d1C = inspectSharedUnknownAnchor({ graph: fixtureC.situationGraph });
|
|
const d2C = inspectSharedUnknownAnchor({ graph: fixtureC.situationGraph });
|
|
expect(d1C.result).toBe(d2C.result);
|
|
});
|
|
|
|
it("inputs are not mutated by the assessor", () => {
|
|
const aSnap = JSON.stringify(fixtureA);
|
|
const bSnap = JSON.stringify(fixtureB);
|
|
const cSnap = JSON.stringify(fixtureC);
|
|
assessInvestigationState(fixtureA);
|
|
assessInvestigationState(fixtureB);
|
|
assessInvestigationState(fixtureC);
|
|
expect(JSON.stringify(fixtureA)).toBe(aSnap);
|
|
expect(JSON.stringify(fixtureB)).toBe(bSnap);
|
|
expect(JSON.stringify(fixtureC)).toBe(cSnap);
|
|
});
|
|
|
|
it("inputs are not mutated by the diagnostic helper", () => {
|
|
const g = JSON.parse(JSON.stringify(fixtureA.situationGraph));
|
|
inspectSharedUnknownAnchor({ graph: g });
|
|
expect(JSON.stringify(g)).toEqual(JSON.stringify(fixtureA.situationGraph));
|
|
});
|
|
|
|
it("fixtures and helper remain test-only", () => { expect(true).toBe(true); });
|
|
});
|
|
|
|
/* ═══ Existing scenario inspection ═══ */
|
|
|
|
describe("Existing scenarios from Experiments 39-46", () => {
|
|
// Real shapes from existing tests — not rewritten, just inspected
|
|
const existingScenarios = [
|
|
{
|
|
name: "comparison-turn-2 (Exp 39/41/45 real data path)",
|
|
graph: {
|
|
nodes: [
|
|
{ id: "obs-1", kind: "observation", status: "known", confidence: "high", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "obs-2", kind: "observation", status: "known", confidence: "high", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "obs-3", kind: "observation", status: "known", confidence: "high", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "obs-4", kind: "observation", status: "known", confidence: "high", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "obs-5", kind: "observation", status: "known", confidence: "medium", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "state-1", kind: "state", status: "provisional", confidence: "medium", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "u-1", kind: "unknown", status: "resolved", confidence: "high", dependsOn: ["obs-1"], affects: [], parentId: null, childIds: [] },
|
|
{ id: "u-2", kind: "unknown", status: "resolved", confidence: "medium", dependsOn: ["obs-3"], affects: [], parentId: null, childIds: [] },
|
|
{ id: "u-3", kind: "unknown", status: "unknown", confidence: "low", dependsOn: [], affects: [], parentId: null, childIds: [] }
|
|
],
|
|
edges: [],
|
|
resolvedNodeIds: ["u-1", "u-2"],
|
|
}
|
|
},
|
|
{
|
|
name: "long-turn-3 (Exp 45 real data path)",
|
|
graph: {
|
|
nodes: [
|
|
{ id: "obs-1", kind: "observation", status: "known", confidence: "high", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "obs-2", kind: "observation", status: "known", confidence: "medium", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "obs-3", kind: "observation", status: "known", confidence: "high", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "obs-4", kind: "observation", status: "known", confidence: "medium", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "state-1", kind: "state", status: "provisional", confidence: "medium", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "u-1", kind: "unknown", status: "resolved", confidence: "medium", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "u-2", kind: "unknown", status: "resolved", confidence: "high", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "u-3", kind: "unknown", status: "resolved", confidence: "medium", dependsOn: [], affects: [], parentId: null, childIds: [] },
|
|
{ id: "u-4", kind: "unknown", status: "unknown", confidence: "low", dependsOn: [], affects: [], parentId: null, childIds: [] }
|
|
],
|
|
edges: [],
|
|
resolvedNodeIds: ["u-1", "u-2", "u-3"],
|
|
}
|
|
},
|
|
{
|
|
name: "live-ollama-state (Exp 46 test shape)",
|
|
graph: {
|
|
nodes: [
|
|
{ id: "obs-1", kind: "observation", status: "known", confidence: "high", dependsOn: [], affects: [] },
|
|
{ id: "obs-2", kind: "observation", status: "known", confidence: "medium", dependsOn: [], affects: [] },
|
|
{ id: "u-1", kind: "unknown", status: "unknown", confidence: "low", dependsOn: ["obs-1", "obs-2"], affects: [] }
|
|
],
|
|
edges: [
|
|
{ id: "e-1", fromNodeId: "obs-1", toNodeId: "u-1", relationship: "supports" },
|
|
{ id: "e-2", fromNodeId: "obs-2", toNodeId: "u-1", relationship: "supports" }
|
|
],
|
|
resolvedNodeIds: [],
|
|
}
|
|
}
|
|
];
|
|
|
|
let existingResults;
|
|
beforeAll(() => {
|
|
existingResults = existingScenarios.map((s) => inspectSharedUnknownAnchor({ graph: s.graph }));
|
|
});
|
|
|
|
it("all existing scenarios return insufficient_data", () => {
|
|
for (const r of existingResults) {
|
|
expect(r.result).toBe("insufficient_data");
|
|
}
|
|
});
|
|
|
|
it("diagnostic results for each existing scenario", () => {
|
|
for (const [i, s] of existingScenarios.entries()) {
|
|
console.log("\n=== Existing Scenario: " + s.name + " ===");
|
|
console.log(" result: " + existingResults[i].result);
|
|
console.log(" anchorIds: " + JSON.stringify(existingResults[i].anchorIds));
|
|
console.log(" reason: " + existingResults[i].reason);
|
|
}
|
|
});
|
|
|
|
it("none contain a usable shared-anchor signal", () => {
|
|
const hasShared = existingResults.some((r) => r.result === "shared_anchor");
|
|
expect(hasShared).toBe(false);
|
|
});
|
|
});
|
|
});
|