fix(confidence-engine): restore finding controls on reopen
This commit is contained in:
@@ -1381,15 +1381,35 @@ export default function ReasoningWorkspace({
|
||||
|
||||
// ── Derive presentation data: exact existing Findings for the current focused Contribution ──
|
||||
let currentFindings = [];
|
||||
if (focused?.result?.correlationId && findings) {
|
||||
const correlationId = focused.result.correlationId;
|
||||
const matchedContribution = (focusedContributions || []).find(
|
||||
(c) => c.correlationId === correlationId,
|
||||
);
|
||||
if (matchedContribution) {
|
||||
currentFindings = findings.filter(
|
||||
(f) => f.contributionId === matchedContribution.id,
|
||||
if (findings) {
|
||||
const hasCorrelationId = !!focused?.result?.correlationId;
|
||||
if (hasCorrelationId) {
|
||||
// LIVE PATH — correlationId present from live formulate call.
|
||||
const correlationId = focused.result.correlationId;
|
||||
const matchedContribution = (focusedContributions || []).find(
|
||||
(c) => c.correlationId === correlationId,
|
||||
);
|
||||
if (matchedContribution) {
|
||||
currentFindings = findings.filter(
|
||||
(f) => f.contributionId === matchedContribution.id,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// REOPEN PATH — cold reopen from persisted state has no correlationId.
|
||||
// Use persisted Contribution.id to locate the displayed/latest Contribution,
|
||||
// then match Findings through Finding.contributionId === Contribution.id.
|
||||
const target = focusedPresentationItemId;
|
||||
if (target && focusedContributions?.length) {
|
||||
const threadContribs = focusedContributions.filter(
|
||||
(c) => c.targetNodeId === target || c.originatingTargetNodeId === target,
|
||||
);
|
||||
if (threadContribs.length > 0) {
|
||||
const latestDisplayContrib = threadContribs[threadContribs.length - 1];
|
||||
currentFindings = findings.filter(
|
||||
(f) => f.contributionId === latestDisplayContrib.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1078,9 +1078,9 @@ After returning to the restored investigation and manually reopening the previou
|
||||
|
||||
This differs from the live current-turn focused surface where canonical Findings display those controls.
|
||||
|
||||
**REOPENED CANONICAL FINDING CONTROLS — UNRESOLVED**
|
||||
**REOPENED CANONICAL FINDING CONTROLS — REPAIRED (v0.49)**
|
||||
|
||||
Leading hypothesis (unproved): the reopened/restored rendering path may be presenting Contribution-derived observation text or another historical representation instead of the same canonical Finding objects used by the live current-turn path. **This is NOT YET PROVED.**
|
||||
Repaired `currentFindings` derivation in reasoning-workspace.jsx (lines 1382–1420): when `correlationId` is absent on cold reopen, the repair identifies the persisted Contribution belonging to the currently displayed focused turn/thread and uses `Finding.contributionId === Contribution.id` to recover the canonical Finding objects. correlationId is not required for cold reopen. Deterministic tests pass (70/70). Live Playwright verification deferred — see next constraint note.
|
||||
|
||||
---
|
||||
|
||||
@@ -1110,7 +1110,7 @@ The new manual observations are **presentation/lifecycle issues downstream of pe
|
||||
| Cold-return can land on underlying surface rather than focused overlay | MANUALLY OBSERVED — unproven presentation/lifecycle gap |
|
||||
| Saved-state banner + already-restored investigation signal | MANUALLY OBSERVED — semantic oddity of workspace state |
|
||||
| Reopened Finding proposition survives | PROVED (text persists) |
|
||||
| Reopened Not quite / not relevant controls absent | UNRESOLVED — hypothesis noted, root cause unproved |
|
||||
| Reopened Not quite / not relevant controls absent | REPAIRED — Contribution.id → contributionId path verified; correlationId no longer required for cold reopen |
|
||||
|
||||
### NEXT BOUNDARY — REOPENED FOCUSED FINDING PRESENTATION / RESTORE WORKSPACE OWNERSHIP
|
||||
|
||||
|
||||
@@ -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.
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user