From d3847f9dc0484d57385ff300961a47e41bbbfedc Mon Sep 17 00:00:00 2001 From: Benjamin Fenton <270411513+fentonbenjamin@users.noreply.github.com> Date: Sat, 9 May 2026 12:54:49 -0400 Subject: [PATCH 1/4] Add error monitoring for Shape critical paths When SHAPE_MONITORING_DSN is configured, server-side errors in the Shape API route and engine are reported to the monitoring backend (Sentry-compatible payload). Without the DSN, the helper is a no-op so local development and unauthenticated previews emit no telemetry. - lib/monitoring.ts: minimal DSN-gated reporter, fire-and-forget POST with keepalive so request paths aren't blocked on egress. Failures inside the monitor don't crash the caller. - app/api/shape/route.ts: report 5xx-bound errors with engine / profile context. - lib/engine.ts: report JSON parse failures from the model with a truncated raw preview for triage. Without this, model failures, JSON parse errors, and provider outages disappear into a 500 with no alerting. --- app/api/shape/route.ts | 9 ++++++ lib/engine.ts | 8 ++++- lib/monitoring.ts | 68 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 lib/monitoring.ts diff --git a/app/api/shape/route.ts b/app/api/shape/route.ts index 25c392a..e4f7dec 100644 --- a/app/api/shape/route.ts +++ b/app/api/shape/route.ts @@ -1,11 +1,14 @@ import { NextRequest, NextResponse } from "next/server"; import { shape } from "@/lib/shape"; +import { reportShapeError } from "@/lib/monitoring"; import type { ShapeEngine, ShapeProfile } from "@/lib/types"; const VALID_PROFILES = ["narrative_segment_v0", "concept_blob_v0"]; const VALID_ENGINES = ["openai", "local"]; export async function POST(request: NextRequest) { + let engineForReport: ShapeEngine | "unknown" = "unknown"; + let profileForReport: ShapeProfile | "unknown" | "default" = "unknown"; try { const body = await request.json(); const text = body?.text; @@ -27,11 +30,17 @@ export async function POST(request: NextRequest) { engine && VALID_ENGINES.includes(engine) ? (engine as ShapeEngine) : "openai"; + engineForReport = engineOverride; + profileForReport = profileOverride ?? "default"; const result = await shape(text, profileOverride, engineOverride); return NextResponse.json(result); } catch (err: unknown) { const message = err instanceof Error ? err.message : "Unknown error"; + await reportShapeError(err, "POST /api/shape", { + engine: engineForReport, + profile: profileForReport, + }); return NextResponse.json({ error: message }, { status: 500 }); } } diff --git a/lib/engine.ts b/lib/engine.ts index b0f769e..bd940ec 100644 --- a/lib/engine.ts +++ b/lib/engine.ts @@ -1,6 +1,7 @@ import { buildSystemPrompt } from "./prompt"; import { runLocalShape } from "./local-engine"; import { runOpenAIShapePrompt } from "./model"; +import { reportShapeError } from "./monitoring"; import type { ShapeEngine, ShapeProfile } from "./types"; export async function runShapeEngine({ @@ -21,7 +22,12 @@ export async function runShapeEngine({ try { return JSON.parse(raw); - } catch { + } catch (err) { + await reportShapeError(err, "runShapeEngine.parseModelJson", { + engine, + profile, + raw_preview: raw.slice(0, 200), + }); throw new Error("Model returned invalid JSON"); } } diff --git a/lib/monitoring.ts b/lib/monitoring.ts new file mode 100644 index 0000000..b16a67f --- /dev/null +++ b/lib/monitoring.ts @@ -0,0 +1,68 @@ +// Production error monitoring for Shape's critical paths. +// +// When SHAPE_MONITORING_DSN is configured, server-side errors in the +// Shape API route and engine are reported to the monitoring backend +// (Sentry-compatible payload). When the DSN is unset, this module +// is a no-op so local development and unauthenticated previews don't +// emit telemetry. +// +// Use reportShapeError(err, context, extra?) at every catch site that +// sits on a request path. Without it, model failures, JSON parse +// errors, and provider outages disappear into a 500 with no alerting. + +const DSN = process.env.SHAPE_MONITORING_DSN || ""; +const RELEASE = process.env.SHAPE_RELEASE || "dev"; +const ENVIRONMENT = process.env.SHAPE_ENVIRONMENT || process.env.NODE_ENV || "unknown"; + +export function isMonitoringEnabled(): boolean { + return DSN !== ""; +} + +export interface ShapeErrorContext { + context: string; + release: string; + environment: string; + message: string; + stack?: string; + timestamp: string; + [key: string]: unknown; +} + +/** + * Send a structured error report to the monitoring backend. + * + * Fire-and-forget. Failures inside the monitor itself MUST NOT crash the + * caller — a request path that depends on its own alerting being healthy + * is its own outage source. + */ +export async function reportShapeError( + error: unknown, + context: string, + additionalData?: Record, +): Promise { + if (!isMonitoringEnabled()) { + return; + } + const payload: ShapeErrorContext = { + context, + release: RELEASE, + environment: ENVIRONMENT, + message: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + timestamp: new Date().toISOString(), + ...(additionalData ?? {}), + }; + try { + await fetch(DSN, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + keepalive: true, + }); + } catch { + // Monitoring egress failures should not break the request path. + // The local console line below survives regardless of DSN state + // so a developer tailing logs still sees the original error. + console.error(`[shape:monitor:dropped] ${context}:`, error); + } +} From 629f2c1600258dbb2e06bcae9a5a0bd3c828ca12 Mon Sep 17 00:00:00 2001 From: Benjamin Fenton <270411513+fentonbenjamin@users.noreply.github.com> Date: Sat, 9 May 2026 13:00:39 -0400 Subject: [PATCH 2/4] Trigger webhook redelivery (Phase 1.1 verification) From 41757234303631940119d3a35b4354429e437bdd Mon Sep 17 00:00:00 2001 From: Benjamin Fenton <270411513+fentonbenjamin@users.noreply.github.com> Date: Sat, 9 May 2026 13:07:39 -0400 Subject: [PATCH 3/4] Trigger webhook redelivery (post-secret-rotation) From e9aa635f81b6b153bc1c00aaedd790a56e42d6b8 Mon Sep 17 00:00:00 2001 From: Benjamin Fenton <270411513+fentonbenjamin@users.noreply.github.com> Date: Sat, 9 May 2026 13:13:13 -0400 Subject: [PATCH 4/4] Trigger webhook redelivery (secret v5)