fix(confidence-engine): restore finding controls on reopen

This commit is contained in:
2026-08-28 13:25:24 +01:00
parent 06f3f501d2
commit a8539e2494
3 changed files with 182 additions and 11 deletions
@@ -989,3 +989,154 @@ describe("Focused investigation history cue", () => {
// some_other_node would show INVESTIGATING, but it's NOT oq-originating
});
});
// ── Reopened Finding resolution (v0.49 repair) ─────────────────────
describe("Reopened Finding resolution via Contribution.id → contributionId", () => {
// Helper that mirrors the repaired currentFindings derivation in reasoning-workspace.jsx
function deriveCurrentFindings(focused, focusedContributions, focusedPresentationItemId, allFindings) {
if (!allFindings?.length) return [];
const hasCorrelationId = !!focused?.result?.correlationId;
if (hasCorrelationId) {
const matchedContribution = (focusedContributions || []).find(
(c) => c.correlationId === focused.result.correlationId,
);
if (matchedContribution) {
return allFindings.filter((f) => f.contributionId === matchedContribution.id);
}
return [];
}
// REOPEN PATH
const target = focusedPresentationItemId;
if (!target || !focusedContributions?.length) return [];
const threadContribs = focusedContributions.filter(
(c) => c.targetNodeId === target || c.originatingTargetNodeId === target,
);
if (!threadContribs.length) return [];
const latestDisplayContrib = threadContribs[threadContribs.length - 1];
return allFindings.filter((f) => f.contributionId === latestDisplayContrib.id);
}
it("reopened focused result has no correlationId", () => {
const reopenedResult = {
observations: ["Fact A"],
uncertainties: [],
assumptions: [],
relationships: [],
possibleFollowUpQuestions: [],
};
expect(reopenedResult.correlationId).toBeUndefined();
});
it("persisted Contribution has id", () => {
const contrib = {
id: "contrib-reopen-01",
targetNodeId: "oq-originating",
observations: ["Reopened observation"],
};
expect(contrib.id).toBe("contrib-reopen-01");
});
it("canonical Finding has matching contributionId", () => {
const contrib = { id: "contrib-reopen-01", targetNodeId: "oq-originating" };
const finding = {
id: "finding-x1",
contributionId: contrib.id,
proposition: "The system scales horizontally.",
userDisposition: "agreed",
};
expect(finding.contributionId).toBe(contrib.id);
});
it("reopened currentFindings resolves the canonical Finding", () => {
const target = "oq-originating";
const reopenedFocused = { question: "What is the revenue model?", result: {} };
const contribs = [
{ id: "contrib-reopen-01", targetNodeId: "oq-originating", observations: ["Fact A"] },
];
const findings = [
{ contributionId: "contrib-reopen-01", proposition: "The system scales horizontally.", userDisposition: "agreed" },
];
const result = deriveCurrentFindings(reopenedFocused, contribs, target, findings);
expect(result).toHaveLength(1);
expect(result[0].proposition).toBe("The system scales horizontally.");
});
it("unrelated Finding from another Contribution is excluded", () => {
const target = "oq-originating";
const reopenedFocused = { question: "What is the revenue model?", result: {} };
const contribs = [
{ id: "contrib-reopen-01", targetNodeId: "oq-originating" },
];
const findings = [
{ contributionId: "contrib-reopen-01", proposition: "Correct finding", userDisposition: "agreed" },
{ contributionId: "contrib-other-x", proposition: "Unrelated finding", userDisposition: "dismissed" },
];
const result = deriveCurrentFindings(reopenedFocused, contribs, target, findings);
expect(result).toHaveLength(1);
expect(result[0].proposition).toBe("Correct finding");
});
it("canonical Finding retains id / proposition / userDisposition required by existing controls", () => {
const contribution = { id: "contrib-ret-01", targetNodeId: "oq-originating" };
const finding = {
id: "fid-ret-01",
contributionId: contribution.id,
proposition: "Revenue via subscription.",
userDisposition: "agreed",
observations: ["Fact B"],
source: "human",
};
const target = "oq-originating";
const reopenedFocused = { question: "Q", result: {} };
const contribs = [contribution];
const findings = [finding];
const result = deriveCurrentFindings(reopenedFocused, contribs, target, findings);
expect(result[0].id).toBe("fid-ret-01");
expect(result[0].proposition).toBe("Revenue via subscription.");
expect(result[0].userDisposition).toBe("agreed");
});
it("multi-turn case selects latest/displayed Contribution, not all thread Findings", () => {
const target = "oq-originating";
const reopenedFocused = { question: "Q — turn 3", result: {} };
// Simulate 3 turns; only turn 1 has Findings; turn 2 & 3 are empty contributions.
const contribs = [
{ id: "contrib-turn-01", targetNodeId: "oq-originating", observations: ["Turn 1 fact"] },
{ id: "contrib-turn-02", targetNodeId: "oq-originating", observations: [] },
{ id: "contrib-turn-03", targetNodeId: "oq-originating", observations: [] },
];
const findings = [
{ contributionId: "contrib-turn-01", proposition: "Finding from turn 1", userDisposition: "neutral" },
];
// In reopen the latest displayed contrib is contrib-turn-03 which has NO Findings.
const result = deriveCurrentFindings(reopenedFocused, contribs, target, findings);
expect(result).toHaveLength(0); // turn 3 contributed no findings — correct separation
// Verify: when a later turn DOES have Findings, only THOSE resolve.
const contribsWithTurn2Findings = [
...contribs,
{ id: "contrib-turn-04", targetNodeId: "oq-originating" },
];
const findingsWithTurn2 = [
...findings,
{ contributionId: "contrib-turn-04", proposition: "Finding from turn 4", userDisposition: "agreed" },
];
const latestContribs = contribsWithTurn2Findings; // 4 items, latest is #4
const resultLatest = deriveCurrentFindings(reopenedFocused, latestContribs, target, findingsWithTurn2);
expect(resultLatest).toHaveLength(1);
expect(resultLatest[0].proposition).toBe("Finding from turn 4");
// Turn 1 Finding NOT merged into latest view.
});
});