feat(confidence-engine): accept episode evidence at graph application
This commit is contained in:
+27
-13
@@ -3971,9 +3971,11 @@ export async function applyValidatedProposal({
|
||||
proposal,
|
||||
previousQuestion = null,
|
||||
answer = null,
|
||||
evidenceContext = null,
|
||||
provider = null,
|
||||
modelName = null,
|
||||
}) {
|
||||
const isLegacySingleTurn = !evidenceContext;
|
||||
const graphValidation = situationGraphSchema.safeParse(situationGraph);
|
||||
const proposalValidation = graphUpdateSchema.safeParse(proposal);
|
||||
|
||||
@@ -4030,11 +4032,15 @@ export async function applyValidatedProposal({
|
||||
);
|
||||
|
||||
// ── 60B.80 — normalise terminal parent closure without explicit confirmation
|
||||
const ownershipChanges = reconcileDecisionClosureOwnership(
|
||||
situationGraph,
|
||||
reconciledProposal.proposal,
|
||||
answer,
|
||||
);
|
||||
// legacy only: episode evidence does not supply a single-answer confirmation string;
|
||||
// Done and Finding agree are NOT closure authority (settled architecture rule).
|
||||
const ownershipChanges = isLegacySingleTurn
|
||||
? reconcileDecisionClosureOwnership(
|
||||
situationGraph,
|
||||
reconciledProposal.proposal,
|
||||
answer,
|
||||
)
|
||||
: { strippedUpdates: [], strippedResolvedIds: [] };
|
||||
if (ownershipChanges.strippedResolvedIds.length > 0) {
|
||||
// Reconciler may have added selectedQuestion = null because the parent
|
||||
// appeared resolved during reconciliation. If we stripped it, restore
|
||||
@@ -4130,12 +4136,16 @@ export async function applyValidatedProposal({
|
||||
validatedProposal,
|
||||
);
|
||||
proposalCompatibilityErrors.push(...selectedQuestionValidation.errors);
|
||||
proposalCompatibilityErrors.push(
|
||||
...validateAnswerMeaningCompatibilityWithRawAnswer({
|
||||
answer,
|
||||
proposal: validatedProposal,
|
||||
}),
|
||||
);
|
||||
// legacy only: raw-answer fidelity is a single-turn safeguard.
|
||||
// Episode evidence carries structured turns — no synthetic answer is constructed.
|
||||
if (isLegacySingleTurn) {
|
||||
proposalCompatibilityErrors.push(
|
||||
...validateAnswerMeaningCompatibilityWithRawAnswer({
|
||||
answer,
|
||||
proposal: validatedProposal,
|
||||
}),
|
||||
);
|
||||
}
|
||||
proposalCompatibilityErrors.push(
|
||||
...validateAnswerMeaningAlignment(validatedProposal),
|
||||
);
|
||||
@@ -4165,10 +4175,14 @@ export async function applyValidatedProposal({
|
||||
? findNodeById(graphSnapshot, previousActiveUnknownNodeId)
|
||||
: null;
|
||||
const affectedNodeIds = buildAffectedNodeIds(graphSnapshot, proposalSnapshot);
|
||||
// For episode evidence: extract turn text from the ordered Q/A sequence.
|
||||
const _epTurns = evidenceContext?.isCompletedEpisode ? evidenceContext.episodeEvidence.turns : null;
|
||||
const _derivedQuestion = isLegacySingleTurn ? previousQuestion : (_epTurns?.[0]?.question ?? "");
|
||||
const _derivedAnswer = isLegacySingleTurn ? answer : (_epTurns?.[0]?.answer ?? "");
|
||||
const reasoningResolution = deriveReasoningStateOverride({
|
||||
graph: graphSnapshot,
|
||||
previousQuestion,
|
||||
answer,
|
||||
previousQuestion: _derivedQuestion,
|
||||
answer: _derivedAnswer,
|
||||
resolvedUnknownNodeIds: validatedProposal.resolvedUnknownNodeIds,
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
/**
|
||||
* Tests for episode evidence at the graph application boundary.
|
||||
*
|
||||
* Scope: applyValidatedProposal accepts structured completed-episode evidence
|
||||
* without collapsing it into a synthetic single answer, and preserves existing
|
||||
* single-turn behaviour unchanged.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
|
||||
|
||||
import { makeEdge, makeGraph, makeNode } from "@/lib/graph/schema.js";
|
||||
|
||||
// ── production-style helpers ───────────────────────────────────────
|
||||
|
||||
function makeSituationGraph() {
|
||||
const parent = makeNode({
|
||||
id: "n-parent",
|
||||
label: "Commercial justification for continuing development",
|
||||
description:
|
||||
"Need to know whether this solves a genuine problem and is commercially justified.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
});
|
||||
|
||||
const child1 = makeNode({
|
||||
id: "n-child-1",
|
||||
label: "Whether the idea solves a genuine problem",
|
||||
description: "Need evidence about whether there is real user demand.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
parentId: parent.id,
|
||||
});
|
||||
|
||||
const child2 = makeNode({
|
||||
id: "n-child-2",
|
||||
label: "Whether people would value it enough to pay",
|
||||
description: "Need evidence about willingness to pay.",
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
parentId: parent.id,
|
||||
});
|
||||
|
||||
const anchor = makeNode({
|
||||
id: "n-anchor",
|
||||
label: "Market observation anchor",
|
||||
description: "Anchor state introduced by the answer.",
|
||||
kind: "state",
|
||||
status: "known",
|
||||
confidence: "low",
|
||||
});
|
||||
|
||||
return makeGraph({
|
||||
centralStatement:
|
||||
"I have developed a new reasoning method and want to determine whether continuing development is commercially justified.",
|
||||
nodes: [parent, child1, child2, anchor],
|
||||
edges: [
|
||||
makeEdge({
|
||||
id: "e-p-c1",
|
||||
fromNodeId: child1.id,
|
||||
toNodeId: parent.id,
|
||||
relationship: "depends_on",
|
||||
description: "Genuine problem feeds the parent.",
|
||||
}),
|
||||
makeEdge({
|
||||
id: "e-p-c2",
|
||||
fromNodeId: child2.id,
|
||||
toNodeId: parent.id,
|
||||
relationship: "depends_on",
|
||||
description: "Willingness to pay feeds the parent.",
|
||||
}),
|
||||
],
|
||||
activeUnknownNodeId: "n-child-1",
|
||||
resolvedNodeIds: [],
|
||||
currentSummary: "Episode evidence boundary fixture",
|
||||
});
|
||||
}
|
||||
|
||||
function makeResolvedProposal(resolvedIds = ["n-child-1"]) {
|
||||
return {
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n-anchor2",
|
||||
label: "Update anchor 2",
|
||||
description: "Anchor state.",
|
||||
kind: "state",
|
||||
status: "known",
|
||||
confidence: "low",
|
||||
}),
|
||||
],
|
||||
updatedNodes: resolvedIds.map((id) => ({
|
||||
nodeId: id,
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: `answer:${id}`,
|
||||
reason: "resolved child",
|
||||
})),
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: resolvedIds,
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
};
|
||||
}
|
||||
|
||||
function makeLegacyAnswer() {
|
||||
return "Yes, I confirm there is no further material uncertainty.";
|
||||
}
|
||||
|
||||
// ── Legacy single-turn regression ──────────────────────────────────
|
||||
|
||||
describe("legacy single-turn path", () => {
|
||||
it("continues to work without evidenceContext (backward-compatible signature)", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const proposal = makeResolvedProposal(["n-child-1"]);
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
answer: makeLegacyAnswer(),
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("legacy raw-answer compatibility check still runs (Regression A)", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
|
||||
// Use unstructured answerMeaning (no structured supportCategory/resolutionGuidance)
|
||||
// so that validateAnswerMeaningCompatibilityWithRawAnswer actually executes.
|
||||
// Raw answer states a hard constraint but the proposal weakens it in newValue.
|
||||
const proposalWithWeakening = {
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n-anchor-lr",
|
||||
label: "Update anchor",
|
||||
description: "Anchor state.",
|
||||
kind: "state",
|
||||
status: "known",
|
||||
confidence: "low",
|
||||
}),
|
||||
],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: "n-child-1",
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "It would be good to proceed.", // weaker than raw constraint
|
||||
reason: "Evidence supports proceeding.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: ["n-child-1"],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
answerMeaning: {
|
||||
userSupportedMeaning: "It would be good to proceed.",
|
||||
supportCategory: null,
|
||||
resolutionGuidance: null,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: proposalWithWeakening,
|
||||
answer: "We MUST proceed — no exceptions.", // explicit hard constraint in raw answer
|
||||
});
|
||||
|
||||
// legacy path: raw-answer compatibility check rejects weakening
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.stage).toBe("proposal_compatibility");
|
||||
});
|
||||
});
|
||||
|
||||
// ── Episode evidence path ──────────────────────────────────────────
|
||||
|
||||
describe("episode evidence at application boundary", () => {
|
||||
it("accepts structured completed-episode evidence without collapsing to synthetic answer", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const proposal = makeResolvedProposal(["n-child-1"]);
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns: [
|
||||
{
|
||||
contributionId: "c-1",
|
||||
sequence: 1,
|
||||
question: "Should we proceed with the investment?",
|
||||
answer: "Yes, the risk assessment is complete.",
|
||||
},
|
||||
{
|
||||
contributionId: "c-2",
|
||||
sequence: 2,
|
||||
question: "Any remaining material uncertainties?",
|
||||
answer: "No, all findings are aligned.",
|
||||
},
|
||||
],
|
||||
eligibleCanonicalFindings: [
|
||||
{
|
||||
findingId: "f-1",
|
||||
contributionId: "c-1",
|
||||
proposition: "Risk assessment confirms go/no-go criteria met.",
|
||||
sourceObservation: "Risk analysis document v3.",
|
||||
endorsement: "agree",
|
||||
},
|
||||
{
|
||||
findingId: "f-2",
|
||||
contributionId: "c-2",
|
||||
proposition: "No remaining material uncertainties identified.",
|
||||
sourceObservation: "User confirmation response.",
|
||||
endorsement: null,
|
||||
},
|
||||
],
|
||||
excludedFindingProvenance: [
|
||||
{
|
||||
findingId: "f-3",
|
||||
contributionId: "c-1",
|
||||
proposition: "Minor budget variance noted.",
|
||||
sourceObservation: "Budget report.",
|
||||
disposition: "not_relevant",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("ordered turns remain distinct (no aggregation)", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const proposal = makeResolvedProposal(["n-child-1"]);
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns: [
|
||||
{
|
||||
contributionId: "c-10",
|
||||
sequence: 1,
|
||||
question: "Q1 distinct text?",
|
||||
answer: "A1 distinct text.",
|
||||
},
|
||||
{
|
||||
contributionId: "c-11",
|
||||
sequence: 2,
|
||||
question: "Q2 distinct text?",
|
||||
answer: "A2 distinct text.",
|
||||
},
|
||||
{
|
||||
contributionId: "c-12",
|
||||
sequence: 3,
|
||||
question: "Q3 distinct text?",
|
||||
answer: "A3 distinct text.",
|
||||
},
|
||||
],
|
||||
eligibleCanonicalFindings: [],
|
||||
excludedFindingProvenance: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("eligible Findings remain structured evidence", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const proposal = makeResolvedProposal(["n-child-1"]);
|
||||
|
||||
const eligibleFindings = [
|
||||
{
|
||||
findingId: "f-aggree",
|
||||
contributionId: "c-a",
|
||||
proposition: "Confirmed proposition A.",
|
||||
sourceObservation: "Source A.",
|
||||
endorsement: "agree",
|
||||
},
|
||||
{
|
||||
findingId: "f-agenull",
|
||||
contributionId: "c-b",
|
||||
proposition: "Working premise B.",
|
||||
sourceObservation: "Source B.",
|
||||
endorsement: null,
|
||||
},
|
||||
];
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns: [
|
||||
{ contributionId: "c-a", sequence: 1, question: "Q?", answer: "A." },
|
||||
{ contributionId: "c-b", sequence: 2, question: "Q?", answer: "A." },
|
||||
],
|
||||
eligibleCanonicalFindings: eligibleFindings,
|
||||
excludedFindingProvenance: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("no synthetic answer is constructed for episode path", async () => {
|
||||
// In episode mode, the `answer` parameter is omitted and structured turns
|
||||
// are provided via evidenceContext. Since no raw `answer` is passed,
|
||||
// validateAnswerMeaningCompatibilityWithRawAnswer returns [] immediately
|
||||
// (requires a truthy answer to execute), proving no synthetic/combined
|
||||
// answer is constructed from the episode turns.
|
||||
const graph = makeSituationGraph();
|
||||
|
||||
// Use structured meaning (bypasses raw-answer compatibility) with valid alignment
|
||||
const proposalWithMeaning = {
|
||||
addedNodes: [
|
||||
makeNode({
|
||||
id: "n-anchor-na",
|
||||
label: "Update anchor",
|
||||
description: "Anchor state.",
|
||||
kind: "state",
|
||||
status: "known",
|
||||
confidence: "low",
|
||||
}),
|
||||
],
|
||||
updatedNodes: [
|
||||
{
|
||||
nodeId: "n-child-1",
|
||||
previousStatus: "unknown",
|
||||
newStatus: "resolved",
|
||||
previousValue: null,
|
||||
newValue: "Proceed with full commitment.",
|
||||
reason: "Evidence supports it.",
|
||||
},
|
||||
],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: ["n-child-1"],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
structuralActionRequired: true,
|
||||
answerMeaning: {
|
||||
supportCategory: "explicit_hard_constraint",
|
||||
resolutionGuidance: "must_resolve",
|
||||
userSupportedMeaning: "Proceed with full commitment.",
|
||||
},
|
||||
};
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns: [
|
||||
{ contributionId: "c-1", sequence: 1, question: "Go?", answer: "Absolutely yes." },
|
||||
{ contributionId: "c-2", sequence: 2, question: "Any remaining material uncertainties?", answer: "No." },
|
||||
],
|
||||
eligibleCanonicalFindings: [],
|
||||
excludedFindingProvenance: [],
|
||||
},
|
||||
};
|
||||
|
||||
// No `answer` parameter — evidenceContext drives the episode branch.
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: proposalWithMeaning,
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("proposal-internal validation still runs in episode mode", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
|
||||
// Duplicate node ID — structural violation that proposal-internal validation catches.
|
||||
const proposalWithDuplicateNodes = {
|
||||
addedNodes: [
|
||||
{
|
||||
id: "n-child-1", // duplicates existing node ID
|
||||
kind: "unknown",
|
||||
status: "unknown",
|
||||
confidence: "medium",
|
||||
label: "Duplicate unknown",
|
||||
value: null,
|
||||
dependsOn: [],
|
||||
},
|
||||
],
|
||||
updatedNodes: [],
|
||||
addedEdges: [],
|
||||
removedEdgeIds: [],
|
||||
resolvedUnknownNodeIds: [],
|
||||
affectedNodeIds: [],
|
||||
selectedQuestion: null,
|
||||
};
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns: [
|
||||
{ contributionId: "c-1", sequence: 1, question: "Q?", answer: "A." },
|
||||
],
|
||||
eligibleCanonicalFindings: [],
|
||||
excludedFindingProvenance: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: proposalWithDuplicateNodes,
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
// Proposal-internal validation (structural) still runs and rejects.
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.stage).toBe("proposal_compatibility");
|
||||
});
|
||||
|
||||
it("structured episode is available for subsequent safeguard adaptation", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const proposal = makeResolvedProposal(["n-child-1"]);
|
||||
|
||||
const turns = [
|
||||
{ contributionId: "c-1", sequence: 1, question: "First turn?", answer: "First answer." },
|
||||
{ contributionId: "c-2", sequence: 2, question: "Second turn?", answer: "Second answer." },
|
||||
];
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns,
|
||||
eligibleCanonicalFindings: [],
|
||||
excludedFindingProvenance: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.updatedSituationGraph.nodes.find((n) => n.id === "n-child-1")?.status).toBe("resolved");
|
||||
});
|
||||
|
||||
it("preserves existing graph mutation behaviour in episode mode", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const proposal = makeResolvedProposal(["n-child-1"]);
|
||||
const originalParentStatus = graph.nodes.find((n) => n.id === "n-parent")?.status;
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns: [
|
||||
{ contributionId: "c-1", sequence: 1, question: "Q?", answer: "A." },
|
||||
],
|
||||
eligibleCanonicalFindings: [],
|
||||
excludedFindingProvenance: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
// Graph was mutated (expected)
|
||||
expect(result.updatedSituationGraph.nodes.find((n) => n.id === "n-child-1")?.status).toBe("resolved");
|
||||
// Original graph not mutated (atomicity test)
|
||||
expect(graph.nodes.find((n) => n.id === "n-parent")?.status).toBe(originalParentStatus);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ── Signature compatibility ────────────────────────────────────────
|
||||
|
||||
describe("applyValidatedProposal signature", () => {
|
||||
it("works with minimal legacy call (situationGraph + proposal only)", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: makeResolvedProposal(["n-child-1"]),
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("works with legacy answer parameter still passed", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal: makeResolvedProposal(["n-child-1"]),
|
||||
answer: "Yes.",
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it("works with both evidenceContext and legacy parameters (evidenceContext takes precedence)", async () => {
|
||||
const graph = makeSituationGraph();
|
||||
const proposal = makeResolvedProposal(["n-child-1"]);
|
||||
|
||||
const episodeEvidence = {
|
||||
isCompletedEpisode: true,
|
||||
episodeEvidence: {
|
||||
situationGraph: graph,
|
||||
targetNodeId: "n-parent",
|
||||
turns: [
|
||||
{ contributionId: "c-1", sequence: 1, question: "Q?", answer: "A." },
|
||||
],
|
||||
eligibleCanonicalFindings: [],
|
||||
excludedFindingProvenance: [],
|
||||
},
|
||||
};
|
||||
|
||||
const result = await applyValidatedProposal({
|
||||
situationGraph: graph,
|
||||
proposal,
|
||||
previousQuestion: "Legacy question?",
|
||||
answer: "Legacy answer.", // should be ignored — evidenceContext present
|
||||
evidenceContext: episodeEvidence,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user