instrumentation.ts
Rama main · réplica de lectura del repositorio original.
// Next 16 framework error hook. Routes uncaught render/RSC/route errors into
// the audit module so they land in the same forensic trail as exec/fs/auth
// events — no extra log channel, no extra ops surface. Best-effort: this
// handler must NEVER throw (would mask the real error).
import type { Instrumentation } from "next";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
// The only place that knows how Next exposes cookies. `lib/auth` stays framework-free
// and asks this registered reader, which keeps Phase 2's boundary honest. A failure
// here is NOT swallowed: without it every session read fails closed, and a silent
// catch would turn that into a confusing "not registered" error per request.
await import("next/headers").then(({ cookies }) =>
import("./lib/auth/require-session").then(({ registerSessionCookieReader, SESSION_COOKIE }) =>
registerSessionCookieReader(async () => (await cookies()).getAll(SESSION_COOKIE)),
),
);
// Warm durable agent-session maintenance before accepting requests. Backfilling
// the conversation index once at boot keeps MCP lookup O(1) even on hosts with
// thousands of old CLI sessions; archive retention remains best-effort.
await Promise.all([
import("./lib/agent/session-files")
.then(({ backfillConversationIndex }) => backfillConversationIndex())
.catch(() => undefined),
import("./lib/agent/session-archive")
.then(({ pruneAgentSessionArchives }) => pruneAgentSessionArchives())
.catch(() => undefined),
Promise.all([import("./lib/workflow/graph-triggers"), import("./lib/mcp/tools"), import("./lib/mcp/capability-runtime")])
.then(([{ startWorkflowScheduler }, { TOOLS_BY_NAME }, { msoCapabilityRuntime }]) => startWorkflowScheduler((name) => TOOLS_BY_NAME.get(name), msoCapabilityRuntime))
.catch(() => undefined),
import("./lib/mcp/federation/batonly-worker")
.then(({ startBatonlyFederationWorker }) => startBatonlyFederationWorker())
.catch(() => undefined),
]);
// systemd liveness: when the unit sets WatchdogSec, systemd exposes
// NOTIFY_SOCKET + WATCHDOG_USEC. Node has no native AF_UNIX SOCK_DGRAM
// (dgram is UDP-only), so we ping via the `systemd-notify` binary — which
// requires NotifyAccess=all in the unit, since the ping comes from a child
// PID, not main. A missed ping past WatchdogSec → systemd restarts us; this
// catches a wedged event loop that Restart=always (crash-only) can't.
// No-op in dev / demo / CI where NOTIFY_SOCKET is unset.
if (!process.env.NOTIFY_SOCKET) return;
const { execFile } = await import("node:child_process");
const usec = parseInt(process.env.WATCHDOG_USEC ?? "0", 10);
const intervalMs = usec > 0 ? Math.floor(usec / 2000) : 10_000; // half the deadline, in ms
const ping = () => execFile("systemd-notify", ["WATCHDOG=1"], () => {});
ping();
setInterval(ping, intervalMs).unref();
}
}
export const onRequestError: Instrumentation.onRequestError = async (
err,
request,
context,
) => {
if (process.env.NEXT_RUNTIME === "nodejs") {
try {
const { audit } = await import("./lib/host/audit");
const e = err instanceof Error ? err : new Error(String(err));
// Route shape goes into structured `meta` (since the audit field exists)
// instead of being packed into `target` — easier to filter with `jq`,
// greppable as `routerKind`/`routeType` keys, and leaves `target` clean
// for the actual path the request hit.
await audit({
action: "framework.error",
ok: false,
actor: null,
target: request.path,
detail: `${e.name}: ${e.message}`,
meta: {
routerKind: context.routerKind,
routeType: context.routeType,
},
});
} catch {
// never crash here — best-effort logging only
}
}
};