fix(confidence-engine): permit empty plausibleInterpretations for honest zero state
- Zod validator: plausibleInterpretations accepts empty string (zero interpretations) while remaining required and string-typed - Prompt instruction: SECTION B now explicitly requests "" instead of absence prose when no plausible inputs exist - Report generation/update paths derive hasPlausibleInterpretations from actual synthesized data rather than hard-coding true - Tests: updated validation contract, added hasPlausibleInterpretations derivation assertions, updated seam tests to use derived values
This commit is contained in:
@@ -235,9 +235,18 @@ describe("investigation overview — output validation contract", () => {
|
||||
expect(result.valid).toBe(false);
|
||||
});
|
||||
|
||||
it("empty string plausibleInterpretations rejected", () => {
|
||||
it("empty string plausibleInterpretations accepted (zero interpretations)", () => {
|
||||
const result = validateOverviewResponse({ understanding: "something", plausibleInterpretations: "" });
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.data.plausibleInterpretations).toBe("");
|
||||
});
|
||||
|
||||
it("valid empty-string plausibleInterpretations with non-empty understanding is canonical zero", () => {
|
||||
const result = validateOverviewResponse({
|
||||
understanding: "Supported understanding.",
|
||||
plausibleInterpretations: "",
|
||||
});
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it("malformed JSON rejected", () => {
|
||||
@@ -245,17 +254,61 @@ describe("investigation overview — output validation contract", () => {
|
||||
expect(result.valid).toBe(false);
|
||||
});
|
||||
|
||||
it("null input rejected", () => {
|
||||
expect(validateOverviewResponse(null).valid).toBe(false);
|
||||
});
|
||||
|
||||
it("undefined input rejected", () => {
|
||||
expect(validateOverviewResponse(undefined).valid).toBe(false);
|
||||
it("non-empty plausible synthesis remains valid", () => {
|
||||
const result = validateOverviewResponse({
|
||||
understanding: "Supported understanding.",
|
||||
plausibleInterpretations: "Y is still a candidate.",
|
||||
});
|
||||
expect(result.valid).toBe(true);
|
||||
});
|
||||
|
||||
it("non-object input rejected", () => {
|
||||
expect(validateOverviewResponse("just a string").valid).toBe(false);
|
||||
});
|
||||
|
||||
// ── hasPlausibleInterpretations derivation from validated data ──
|
||||
|
||||
describe("hasPlausibleInterpretations derivation", () => {
|
||||
it("zero plausible output derives hasPlausibleInterpretations === false", () => {
|
||||
const result = validateOverviewResponse({
|
||||
understanding: "Supported understanding.",
|
||||
plausibleInterpretations: "",
|
||||
});
|
||||
expect(result.valid).toBe(true);
|
||||
const hasPlausibleInterpretations = result.data.plausibleInterpretations.trim().length > 0;
|
||||
expect(hasPlausibleInterpretations).toBe(false);
|
||||
});
|
||||
|
||||
it("non-zero plausible output derives hasPlausibleInterpretations === true", () => {
|
||||
const result = validateOverviewResponse({
|
||||
understanding: "Supported understanding.",
|
||||
plausibleInterpretations: "Some interpretation.",
|
||||
});
|
||||
expect(result.valid).toBe(true);
|
||||
const hasPlausibleInterpretations = result.data.plausibleInterpretations.trim().length > 0;
|
||||
expect(hasPlausibleInterpretations).toBe(true);
|
||||
});
|
||||
|
||||
it("whitespace-only plausibleInterpretations derives false", () => {
|
||||
const result = validateOverviewResponse({
|
||||
understanding: "Supported.",
|
||||
plausibleInterpretations: " ",
|
||||
});
|
||||
expect(result.valid).toBe(true);
|
||||
const hasPlausibleInterpretations = result.data.plausibleInterpretations.trim().length > 0;
|
||||
expect(hasPlausibleInterpretations).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ── SECTION B empty-input prompt contract ──
|
||||
|
||||
describe("SECTION B empty-input prompt instruction", () => {
|
||||
it("prompt explicitly permits and requests empty-string output when no plausible inputs", () => {
|
||||
const prompt = buildOverviewSynthesisPrompt(graph, eligibleFindings, []);
|
||||
expect(prompt).toContain('plausibleInterpretations');
|
||||
expect(prompt).toMatch(/empty string/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── 6. No decision/recommendation/readiness fields allowed ───────────────
|
||||
|
||||
@@ -145,7 +145,7 @@ describe("Report page identity and lifecycle — v0.60h-a (seam-level)", () => {
|
||||
const resp = await fetch("/api/cases/overview", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ situationGraph: loaded?.situationGraph, findings: loaded?.findings || [] }) });
|
||||
expect(resp.ok).toBe(true);
|
||||
|
||||
await (await import("@/lib/storage/investigation-storage")).saveInvestigation({ ...loaded, investigationReport: { understanding: "Generated.", hasPlausibleInterpretations: false, generatedFromRevision: 4 } });
|
||||
await (await import("@/lib/storage/investigation-storage")).saveInvestigation({ ...loaded, investigationReport: { understanding: "Generated.", plausibleInterpretations: null, hasPlausibleInterpretations: (null ?? "").trim().length > 0, generatedFromRevision: 4 } });
|
||||
expect(mockLastSavedSnapshot.id).toBe("inv-report-a");
|
||||
expect(mockLastSavedSnapshot.investigationReport.generatedFromRevision).toBe(4);
|
||||
});
|
||||
@@ -189,7 +189,7 @@ describe("Report page identity and lifecycle — v0.60h-a (seam-level)", () => {
|
||||
const resp = await fetch("/api/cases/overview", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ situationGraph: loaded?.situationGraph, findings: loaded?.findings }) });
|
||||
expect(resp.ok).toBe(true);
|
||||
|
||||
await (await import("@/lib/storage/investigation-storage")).saveInvestigation({ ...loaded, investigationReport: { understanding: "R2.", hasPlausibleInterpretations: false, generatedFromRevision: 5 } });
|
||||
await (await import("@/lib/storage/investigation-storage")).saveInvestigation({ ...loaded, investigationReport: { understanding: "R2.", plausibleInterpretations: null, hasPlausibleInterpretations: (null ?? "").trim().length > 0, generatedFromRevision: 5 } });
|
||||
expect(mockLastSavedSnapshot.id).toBe("inv-report-a");
|
||||
expect(mockLastSavedSnapshot.investigationReport.generatedFromRevision).toBe(5);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user