wip(confidence-engine): checkpoint open-question presentation work

This commit is contained in:
2026-10-09 07:46:19 +01:00
parent f74fe6de10
commit 6bc8be6edb
19 changed files with 812 additions and 22 deletions
@@ -0,0 +1,142 @@
import { describe, expect, it } from "vitest";
import { promises as fs } from "node:fs";
import { join } from "node:path";
import {
reconstructionGenerationJsonSchema,
reconstructionGenerationSchema,
reconstructionV2Schema,
} from "@/lib/reconstruction/schema.js";
import {
validateOpenQuestionCandidate,
validateReconstructionOpenQuestions,
} from "@/lib/reconstruction/open-question-presentation.js";
function makeAnalysisResponse(questionValue, includeQuestion = true) {
const unknown = {
id: "unknown-workforce",
description:
"Whether necessary skilled production workers can be recruited within the required project timeframe.",
confidence: "high",
};
if (includeQuestion) unknown.question = questionValue;
return {
inputClassification: {
primaryType: "other",
secondaryTypes: [],
reasoningModes: [],
classificationReason: "A delivery uncertainty is central to the situation.",
confidence: "high",
},
reconstruction: {
summary: "Delivery depends on recruiting skilled production workers.",
actors: [],
systemsOrObjects: [],
expectedStates: [],
observedStates: [],
differences: [],
knownTransitions: [],
unexplainedTransitions: [],
contradictions: [],
importantUnknowns: [unknown],
plausibleInterpretations: [],
relationships: [],
},
evidence: [],
nextQuestion: {
id: "next-1",
question: "What recruitment evidence is available?",
targets: ["unknown-workforce"],
reason: "Recruitment feasibility is unresolved.",
expectedInformationValue: "high",
reasoningMode: "other",
},
};
}
describe("new reconstruction generation contract", () => {
it("requires a non-empty question for every generated important unknown", () => {
expect(
reconstructionGenerationSchema.safeParse(
makeAnalysisResponse(undefined, false),
).success,
).toBe(false);
expect(
reconstructionGenerationSchema.safeParse(makeAnalysisResponse(" ")).success,
).toBe(false);
expect(
reconstructionGenerationSchema.safeParse(
makeAnalysisResponse(
"Can the necessary skilled production workers be recruited within the required project timeframe?",
),
).success,
).toBe(true);
const unknownSchema =
reconstructionGenerationJsonSchema.properties.reconstruction.properties
.importantUnknowns.items;
expect(unknownSchema.required).toContain("description");
expect(unknownSchema.required).toContain("question");
expect(unknownSchema.properties.question.minLength).toBe(1);
});
it("keeps the durable compatibility schema tolerant of a missing question", () => {
expect(
reconstructionV2Schema.safeParse(makeAnalysisResponse(undefined, false)).success,
).toBe(true);
});
});
describe("Open Question presentation validation", () => {
it("accepts a trimmed direct interrogative", () => {
expect(
validateOpenQuestionCandidate(
" Can the necessary workers be recruited in time? ",
),
).toEqual({
valid: true,
question: "Can the necessary workers be recruited in time?",
});
});
it("uses canonical UI fallback without a technical explanation", async () => {
const source = await fs.readFile(
join(process.cwd(), "components/reasoning-workspace.jsx"),
"utf8",
);
expect(source).toContain("const openQuestion = node.openQuestion || node.label;");
expect(source).not.toContain(
"Canonical uncertainty — direct question wording unavailable.",
);
expect(source).toContain("startFocused(n.id);");
});
it.each([
["question_missing", undefined, false],
["question_empty", " ", true],
["question_indirect_proposition", "Whether workers can be recruited in time", true],
["question_indirect_proposition", "Whether workers can be recruited in time?", true],
["question_not_interrogative", "Workers can be recruited in time.", true],
])("classifies %s nonfatally", (category, candidate, includeQuestion) => {
const input = makeAnalysisResponse(candidate, includeQuestion).reconstruction;
const canonicalBefore = input.importantUnknowns[0].description;
const idBefore = input.importantUnknowns[0].id;
const result = validateReconstructionOpenQuestions(input);
expect(result.reconstruction.importantUnknowns[0]).not.toHaveProperty("question");
expect(result.reconstruction.importantUnknowns[0].description).toBe(
canonicalBefore,
);
expect(result.reconstruction.importantUnknowns[0].id).toBe(idBefore);
expect(result.issues).toEqual([
expect.objectContaining({
category,
unknownId: idBefore,
severity: "nonfatal",
path: ["reconstruction", "importantUnknowns", 0, "question"],
}),
]);
});
});