From eb12a9ce49e19228585c3a59c6797cc960679fe1 Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 6 Aug 2026 12:40:07 +0100 Subject: [PATCH] experiment: qualify condition status by evidence scope --- tests/graph/decision-condition-status.test.js | 110 +++++++++++++++--- 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/tests/graph/decision-condition-status.test.js b/tests/graph/decision-condition-status.test.js index c73d394..5df6fc8 100644 --- a/tests/graph/decision-condition-status.test.js +++ b/tests/graph/decision-condition-status.test.js @@ -84,7 +84,9 @@ describe("assessDecisionConditionStatus — established", () => { expect(result.evidenceNodeIds).toHaveLength(0); }); - it("returns established when resolved unique feature evidence supports differentiation", () => { + it("returns unresolved when evidence mentions a unique feature but scope is not direct_match", () => { + // Evidence contains support keywords but does not share the core "differentiat" keyword + // required by CONCEPT_FAMILIES for differentiation category. Scope = unrelated → unresolved. const graph = makeGraph( [makeNode("d-1", "Our platform offers unique real-time collaboration with no European equivalent", { status: "resolved" })], ["d-1"], @@ -95,8 +97,24 @@ describe("assessDecisionConditionStatus — established", () => { graph, }); + expect(result.status).toBe("unresolved"); + expect(result.evidenceNodeIds).toHaveLength(0); + }); + + it("returns established when evidence has direct_match scope and supports differentiation", () => { + // Evidence contains core keyword "differentiat" → direct_match + supports → established + const graph = makeGraph( + [makeNode("d-3", "Our product offers a clear competitive differentiation against existing players", { status: "resolved" })], + ["d-3"], + ); + + const result = assessDecisionConditionStatus({ + condition: CONDITIONS[3], + graph, + }); + expect(result.status).toBe("established"); - expect(result.evidenceNodeIds).toContain("d-1"); + expect(result.evidenceNodeIds).toContain("d-3"); }); it("returns established for compliance with GDPR match", () => { @@ -148,7 +166,10 @@ describe("assessDecisionConditionStatus — contradicted", () => { expect(result.evidenceNodeIds).toContain("x-1"); }); - it("contradiction takes precedence over support when both exist", () => { + it("contradiction takes precedence over support only within direct scope", () => { + // s-1 gets direct_match scope (present-state evidence about present compliance) + // c-1 gets unrelated scope (evidence text does not match compliance category) + // With scope-aware logic: only s-1 contributes → established, contradicted status = unresolved const graph = makeGraph( [ makeNode("s-1", "Our platform meets GDPR requirements for compliance", { status: "resolved" }), @@ -162,6 +183,25 @@ describe("assessDecisionConditionStatus — contradicted", () => { graph, }); + // Only direct-scope evidence counts; s-1 supports, c-1 is unrelated → established + expect(result.status).toBe("established"); + }); + + it("contradiction takes precedence over support within the same direct scope", () => { + // Both evidence nodes have direct_match scope for compliance + const graph = makeGraph( + [ + makeNode("p-1", "Our platform meets compliance requirements", { status: "resolved" }), + makeNode("p-2", "But it does not support EU data residency mandates", { status: "resolved" }), + ], + ["p-1", "p-2"], + ); + + const result = assessDecisionConditionStatus({ + condition: CONDITIONS[1], + graph, + }); + expect(result.status).toBe("contradicted"); }); @@ -196,14 +236,51 @@ describe("assessDecisionConditionStatus — contradicted", () => { expect(result.status).toBe("established"); }); - it("returns contradicted when node text contains 'not achievable' phrase", () => { + it("returns unresolved when evidence contains 'not achievable' but scope is unrelated", () => { + // Evidence mentions "not achievable" (a contradiction phrase) but is about market share, + // not value_cost. Scope check correctly classifies as unrelated → contradiction ignored. const graph = makeGraph( [makeNode("na-1", "Target market share is not achievable given incumbent dominance", { status: "resolved" })], ["na-1"], ); const result = assessDecisionConditionStatus({ - condition: CONDITIONS[2], // value_cost — "achievable" matches CONTRADICTION_PHRASES via substring + condition: CONDITIONS[2], // value_cost — unrelated subject → scope=unrelated + graph, + }); + + expect(result.status).toBe("unresolved"); + }); + + it("returns unresolved when evidence scope is different_timeframe for future-feasibility condition", () => { + // A condition using "can be achieved" triggers the different_timeframe scope check + // because the scope module recognises this as a future-feasibility phrase. + const graph = makeGraph( + [makeNode("c-3", "Our platform does not currently support EU data residency requirements", { status: "resolved" })], + ["c-3"], + ); + + // This condition text triggers different_timeframe scope detection (contains "can be achieved"), + // so present-state evidence cannot establish or contradict it. + const result = assessDecisionConditionStatus({ + condition: "European compliance can be achieved", + graph, + }); + + expect(result.status).toBe("unresolved"); + }); + + it("present-state compliance condition with contradictory evidence returns contradicted", () => { + // Focused test: a genuinely present-state compliance condition should be contradicted + // when evidence directly negates the claim. Requires a separate unknown node for category + // matching and an observation-type node as contradicting evidence. + const cs1 = makeNode("cs-1", "Our platform does not support EU data residency requirements", { kind: "observation", status: "resolved" }); + const ux = makeNode("u-x", "Whether our product meets EU compliance requirements", { kind: "unknown", status: "resolved" }); + + const graph = makeGraph([cs1, ux], ["cs-1", "u-x"]); + + const result = assessDecisionConditionStatus({ + condition: "The platform currently supports EU data residency requirements", graph, }); @@ -442,23 +519,26 @@ describe("assessDecisionConditionStatus — long investigation sequence", () => expect(valueCostResult.status).toBe("unresolved"); }); - it("turn 4 has two established and one contradicted", () => { + it("turn 4 has demand established, compliance contradicted, value_cost unresolved, differentiation unresolved", () => { // Turn 4 resolved=[u-1,u-2,u-3,u-4] - // demand=established, compliance=contradicted, value_cost=unresolved, differentiation=established + // demand=established (obs-2 direct_match + supports) + // compliance=contradicted (obs-3 direct_match + contradicts) + // value_cost=unresolved (obs-4 informs — no directional signal) + // differentiation=unresolved (obs-5 unrelated scope for CONDITIONS[3] keywords) const turn4 = allResults.filter((r) => r.turn === 4); - const establishedCount = turn4.filter((r) => r.status === "established").length; - expect(establishedCount).toBeGreaterThanOrEqual(2); - const contradictedCount = turn4.filter((r) => r.status === "contradicted").length; - expect(contradictedCount).toBeGreaterThanOrEqual(1); - - // Demand is established const demandResult = turn4.find((r) => r.condition.includes("demand")); expect(demandResult.status).toBe("established"); - // Differentiation is established (no direct European equivalent) + const complianceResult = turn4.find((r) => r.condition.includes("compliance")); + expect(complianceResult.status).toBe("contradicted"); + + const valueCostResult = turn4.find((r) => r.condition.includes("value")); + expect(valueCostResult.status).toBe("unresolved"); + const diffResult = turn4.find((r) => r.condition.includes("competitive")); - expect(diffResult.status).toBe("established"); + // obs-5 "unrelated" scope for CONDITIONS[3] → unresolved + expect(diffResult.status).toBe("unresolved"); }); it("status transitions make sense: unresolved → determined over turns", () => {