diff --git a/scripts/check-kpi.mjs b/scripts/check-kpi.mjs index c9b480879..4cd980971 100755 --- a/scripts/check-kpi.mjs +++ b/scripts/check-kpi.mjs @@ -55,11 +55,15 @@ let minTimestampMs = Number.POSITIVE_INFINITY; let maxTimestampMs = Number.NEGATIVE_INFINITY; let exchangesWithTimestamp = 0; -for (const line of lines) { +for (const [index, line] of lines.entries()) { let record; try { record = JSON.parse(line); } catch { + if (/^\s*[\[{]/.test(line)) { + console.error(`Malformed JSON in KPI log line ${index + 1}.`); + process.exit(1); + } // Wrangler tail can include non-JSON diagnostic noise; preserve that tolerance. continue; } diff --git a/scripts/compute-kpi.mjs b/scripts/compute-kpi.mjs old mode 100755 new mode 100644 diff --git a/test/check-kpi-input-integrity.test.ts b/test/check-kpi-input-integrity.test.ts index facb6c2d9..9cbf0e5c6 100644 --- a/test/check-kpi-input-integrity.test.ts +++ b/test/check-kpi-input-integrity.test.ts @@ -59,6 +59,25 @@ describe("KPI threshold input integrity", () => { } }); + it("rejects malformed JSON-looking lines instead of silently dropping threshold evidence", () => { + const dir = mkdtempSync(join(tmpdir(), "noema-check-kpi-")); + try { + const logPath = join(dir, "exchange-30d.ndjson"); + writeFileSync( + logPath, + '{"event":"http_request","route":"/exchange","status_code":500,"latency_ms":120\n', + ); + + const result = runCheckKpi(logPath); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("Malformed JSON in KPI log line 1"); + expect(result.stdout).toBe(""); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + it("rejects an impossible calendar timestamp instead of normalizing it into window evidence", () => { const dir = mkdtempSync(join(tmpdir(), "noema-check-kpi-time-")); try {