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
+136 -1
View File
@@ -8,7 +8,11 @@ import {
normaliseOpenAITransportResponse,
reconstructionJsonSchema,
} from "@/lib/llm/provider.js";
import { reconstructionV2Schema } from "@/lib/reconstruction/schema.js";
import {
reconstructionGenerationJsonSchema,
reconstructionGenerationSchema,
reconstructionV2Schema,
} from "@/lib/reconstruction/schema.js";
import { focusedDeconstructJsonSchema } from "@/lib/graph/focused-investigation.js";
import { z } from "zod";
@@ -299,6 +303,137 @@ describe("OpenAI reconstruction provider experiment seam", () => {
expect(acceptsNull(transition.properties.id)).toBe(false);
});
it("preserves optional transport nullability for the generation-specific schema", async () => {
const fetchImpl = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ output_text: "{}" }),
});
const provider = createOpenAIReconstructionProvider({
apiKey: "test-key",
fetchImpl,
});
await provider.generateReconstruction(
"prompt",
"test-model",
reconstructionGenerationJsonSchema,
{ outputZodSchema: reconstructionGenerationSchema },
);
const request = JSON.parse(fetchImpl.mock.calls[0][1].body);
const reconstruction = request.text.format.schema.properties.reconstruction;
const unknown = reconstruction.properties.importantUnknowns.items;
const unexplained = reconstruction.properties.unexplainedTransitions.items;
expect(unknown.required).toContain("question");
expect(unknown.properties.question.minLength).toBe(1);
expect(unexplained.required).toContain("entity");
expect(unexplained.properties.entity.anyOf).toContainEqual({ type: "null" });
});
it("normalises generation-schema null placeholders before canonical validation", async () => {
const transportResponse = {
inputClassification: {
primaryType: "unexplained_change",
secondaryTypes: null,
reasoningModes: null,
classificationReason: "A reported change requires reconstruction.",
confidence: "high",
},
reconstruction: {
summary: "A change occurred and its cause remains uncertain.",
actors: [],
systemsOrObjects: [],
expectedStates: [],
observedStates: [],
differences: [],
knownTransitions: [],
unexplainedTransitions: [
{
id: "transition-1",
description: "The service level declined after the release.",
confidence: "medium",
entity: null,
previousState: null,
currentState: null,
},
],
contradictions: [],
importantUnknowns: [
{
id: "unknown-1",
description: "Whether the release caused the service-level decline",
question: "Did the release cause the service-level decline?",
confidence: "medium",
},
],
plausibleInterpretations: [
{
id: "interpretation-1",
description: "The release may have reduced service capacity.",
supportingEvidenceIds: ["evidence-1"],
assumptionsRequired: null,
confidence: "low",
},
],
relationships: null,
},
evidence: [
{
id: "evidence-1",
description: "The decline was reported after the release.",
evidenceType: "reported_statement",
source: null,
attribution: null,
confidence: "medium",
importance: "important",
},
],
nextQuestion: {
id: "next-1",
question: "What changed in service capacity after the release?",
targets: ["unknown-1"],
reason: "The cause of the decline remains unresolved.",
expectedInformationValue: "high",
reasoningMode: null,
},
};
const fetchImpl = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ output_text: JSON.stringify(transportResponse) }),
});
const provider = createOpenAIReconstructionProvider({
apiKey: "test-key",
fetchImpl,
});
const result = await provider.generateReconstruction(
"prompt",
"test-model",
reconstructionGenerationJsonSchema,
{ outputZodSchema: reconstructionGenerationSchema },
);
const request = JSON.parse(fetchImpl.mock.calls[0][1].body);
const unexplainedSchema =
request.text.format.schema.properties.reconstruction.properties
.unexplainedTransitions.items;
expect(unexplainedSchema.required).toContain("entity");
expect(unexplainedSchema.properties.entity.anyOf).toContainEqual({ type: "null" });
expect(result.response.inputClassification).not.toHaveProperty("secondaryTypes");
expect(result.response.inputClassification).not.toHaveProperty("reasoningModes");
expect(result.response.reconstruction.unexplainedTransitions[0]).not.toHaveProperty("entity");
expect(result.response.reconstruction.unexplainedTransitions[0]).not.toHaveProperty("previousState");
expect(result.response.reconstruction.unexplainedTransitions[0]).not.toHaveProperty("currentState");
expect(result.response.reconstruction.plausibleInterpretations[0]).not.toHaveProperty("assumptionsRequired");
expect(result.response.reconstruction).not.toHaveProperty("relationships");
expect(result.response.evidence[0]).not.toHaveProperty("source");
expect(result.response.evidence[0].attribution).toBeNull();
expect(result.response.nextQuestion).not.toHaveProperty("reasoningMode");
expect(reconstructionGenerationSchema.safeParse(result.response).success).toBe(true);
expect(reconstructionV2Schema.safeParse(result.response).success).toBe(true);
});
it("removes only optional transport null placeholders", () => {
const schema = z.object({
optionalText: z.string().optional(),