refactor(gateway): split bootstrap + aiAnalyzer, add module barrels
app/: - bootstrap.ts 277 -> 145 lines: config guard, DB connect, client debug logging and startup order are now named steps with a comment header - lifecycle.ts (new): everything wired on the Discord 'ready' hook, in explicit order (inject broadcaster -> register listeners -> start workers) - process-guards.ts (new): SIGINT/SIGTERM/uncaughtException/unhandledRejection in ONE place, using isTransientStreamError() instead of two duplicated inline code lists - metrics-collector.ts (new): AI pipeline Prometheus gauges modules/: - ai-moderation/index.ts + message-capture/index.ts (new): public facades so app/ never reaches into internal files - aiAnalyzer.ts 317 -> 146 lines: pure entry API; skip-verdict recording extracted into recordSkip() - recovery-worker.ts (new): stranded-message recovery + stale lane/CB pruning - cache-prune.ts (new): 6h expired-verdict sweep, throttled + resettable - drop 3 dead re-exports (pickBatchWithinBudget/onCircuitBreakerAlert/ getConversationKey) whose consumers import the origin files directly No behavior change. typecheck + lint + 138 tests green; nix build OK.
This commit is contained in:
1 parent
6bf3b40cc7
commit
494e16b3b3
9 files changed
+597
-400
No files matched your search
@@ -0,0 +1,61 @@
|
||||
import { errorMessage, isTransientStreamError } from "@/shared/errors/index.js";
|
||||
import type { Logger } from "@/shared/logger/index.js";
|
||||
import type { GracefulShutdown } from "./shutdown.js";
|
||||
|
||||
/**
|
||||
* Process-level signal + error guards.
|
||||
*
|
||||
* Extracted from bootstrap so the "what keeps the gateway alive vs what
|
||||
* shuts it down" policy lives in exactly one place.
|
||||
*
|
||||
* Policy: transient stream-teardown failures (EPIPE / ERR_STREAM_DESTROYED /
|
||||
* ERR_STREAM_WRITE_AFTER_END / ECONNRESET) are logged and IGNORED — crashing
|
||||
* the gateway on them (voice stop/disconnect races, a child process stdin
|
||||
* closed while we still write) takes the whole bot offline mid-operation.
|
||||
* Anything else is a real bug: log with stack and shut down cleanly.
|
||||
*/
|
||||
export function registerProcessGuards(
|
||||
logger: Logger,
|
||||
gracefulShutdown: GracefulShutdown,
|
||||
): void {
|
||||
process.on("SIGINT", () => {
|
||||
gracefulShutdown("SIGINT");
|
||||
});
|
||||
|
||||
process.on("SIGTERM", () => {
|
||||
gracefulShutdown("SIGTERM");
|
||||
});
|
||||
|
||||
process.on("uncaughtException", (err) => {
|
||||
if (isTransientStreamError(err)) {
|
||||
logger.warn(
|
||||
{ error: err },
|
||||
"Uncaught transient stream error — continuing",
|
||||
);
|
||||
return;
|
||||
}
|
||||
logger.error(
|
||||
{
|
||||
err,
|
||||
errorMsg: errorMessage(err),
|
||||
stack: err?.stack,
|
||||
},
|
||||
"Uncaught exception",
|
||||
);
|
||||
gracefulShutdown("uncaughtException");
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (reason) => {
|
||||
const err =
|
||||
reason instanceof Error ? reason : new Error(String(reason ?? "unknown"));
|
||||
if (isTransientStreamError(err)) {
|
||||
logger.warn(
|
||||
{ error: err },
|
||||
"Unhandled rejection transient stream error — continuing",
|
||||
);
|
||||
return;
|
||||
}
|
||||
logger.error({ error: err, reason: String(reason) }, "Unhandled rejection");
|
||||
gracefulShutdown("unhandledRejection");
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user