From 8c3edfec7d0c7bfb0ef1718037c7ac39a9585e1c Mon Sep 17 00:00:00 2001 From: robbond Date: Sat, 5 Sep 2026 14:37:29 +0100 Subject: [PATCH] fix(confidence-engine): log case-start failures --- app/api/cases/start/route.js | 24 +++++++++++++-- docs/current-handoff.md | 6 ++++ tests/app/api/cases-start-route.test.js | 39 ++++++++++++++++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/app/api/cases/start/route.js b/app/api/cases/start/route.js index eccc915..1a025f6 100644 --- a/app/api/cases/start/route.js +++ b/app/api/cases/start/route.js @@ -16,10 +16,25 @@ export async function POST(request) { ? result.statusCode : 500; + const diagnostics = { + status, + error: result.error ?? "Start case failed", + validationErrors: result.validationErrors, + analysisErrors: result.analysisErrors, + validationIssues: result.validationIssues, + rawResponse: result.rawResponse ?? undefined, + }; + + if (status >= 500) { + console.error("[api/cases/start] error response", diagnostics); + } else { + console.warn("[api/cases/start] error response", diagnostics); + } + return Response.json( { success: false, - error: result.error ?? "Start case failed", + error: diagnostics.error, validationErrors: result.validationErrors, diagnostics: result.diagnostics, analysisErrors: result.analysisErrors, @@ -28,7 +43,12 @@ export async function POST(request) { }, { status }, ); - } catch { + } catch (error) { + console.error("[api/cases/start] unhandled exception", { + message: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + error, + }); return Response.json( { success: false, diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 2fa0706..7885666 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -57,6 +57,12 @@ - This is a generation-time relationship-reference integrity defect. v0.5 now requires semantic units before relationships and exact reuse of emitted endpoint IDs; legitimate missing endpoint concepts must be materialised first under existing provenance rules. - The builder remains unchanged. Live compliance remains untested; next restart point is one production-default relationship-reference-integrity validation. +## Case-start error logging + +- The latest live validation was blocked by an HTTP 500 route-level exception with generic client output. `/api/cases/start` now writes structured Next.js server-console diagnostics for every route-owned error response: 4xx at warning level and 5xx/502 at error level. +- Caught exceptions log their actual exception, message, and stack while the client response remains generic; successful responses are not logged. No reasoning, status, or response semantics changed. +- Next restart point: one production-default manufacturing call while observing the Next.js dev-server terminal. + ## Current product architecture Three distinct routes, not a single page: diff --git a/tests/app/api/cases-start-route.test.js b/tests/app/api/cases-start-route.test.js index 76d8651..4cfcd86 100644 --- a/tests/app/api/cases-start-route.test.js +++ b/tests/app/api/cases-start-route.test.js @@ -1,6 +1,8 @@ -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mockStartCase = vi.fn(); +let warnSpy; +let errorSpy; vi.mock("@/lib/graph/orchestrator.js", () => ({ startCase: (...args) => mockStartCase(...args), @@ -10,6 +12,13 @@ describe("app/api/cases/start route", () => { beforeEach(() => { vi.resetModules(); vi.clearAllMocks(); + warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + errorSpy.mockRestore(); }); it("delegates request body to the orchestrator", async () => { @@ -78,6 +87,8 @@ describe("app/api/cases/start route", () => { success: true, reconstruction, }); + expect(warnSpy).not.toHaveBeenCalled(); + expect(errorSpy).not.toHaveBeenCalled(); }); it("returns 400 for invalid request input", async () => { @@ -102,6 +113,12 @@ describe("app/api/cases/start route", () => { success: false, error: "Invalid start-case request", }); + expect(warnSpy).toHaveBeenCalledTimes(1); + expect(warnSpy).toHaveBeenCalledWith( + "[api/cases/start] error response", + expect.objectContaining({ status: 400, error: "Invalid start-case request" }), + ); + expect(errorSpy).not.toHaveBeenCalled(); }); it("returns provider/internal failures as 5xx without stack traces", async () => { @@ -148,6 +165,18 @@ describe("app/api/cases/start route", () => { received: "undefined", }), ]); + expect(errorSpy).toHaveBeenCalledTimes(1); + expect(errorSpy).toHaveBeenCalledWith( + "[api/cases/start] error response", + expect.objectContaining({ + status: 502, + error: "Provider unavailable", + analysisErrors: ["reconstruction: Required"], + validationIssues: expect.any(Array), + rawResponse, + }), + ); + expect(warnSpy).not.toHaveBeenCalled(); }); it("returns structured 500 on malformed JSON", async () => { @@ -163,5 +192,13 @@ describe("app/api/cases/start route", () => { success: false, error: "Internal server error", }); + expect(errorSpy).toHaveBeenCalledWith( + "[api/cases/start] unhandled exception", + expect.objectContaining({ + message: "Unexpected token", + stack: expect.any(String), + error: expect.any(Error), + }), + ); }); });