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:
2026-10-02 06:48:24 +01:00
parent 8dc6e58d43
commit 0cc9d087c3
4 changed files with 67 additions and 14 deletions
@@ -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 ───────────────