mergen-server 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +57 -0
- package/README.md +96 -0
- package/dist/__stubs__/calibration.js +471 -0
- package/dist/__stubs__/causal.js +601 -0
- package/dist/__stubs__/closed-source.js +150 -0
- package/dist/__stubs__/license.js +35 -0
- package/dist/__stubs__/plans.js +197 -0
- package/dist/app.js +467 -0
- package/dist/cli.js +250 -0
- package/dist/commands/agent-identity.js +79 -0
- package/dist/commands/gate.js +107 -0
- package/dist/commands/github.js +632 -0
- package/dist/commands/incident.js +644 -0
- package/dist/commands/policy.js +710 -0
- package/dist/commands/scan.js +243 -0
- package/dist/commands/setup.js +1218 -0
- package/dist/commands/shared.js +109 -0
- package/dist/commands/team.js +809 -0
- package/dist/datadog/blame-attribution.js +228 -0
- package/dist/datadog/client.js +159 -0
- package/dist/datadog/compactor.js +169 -0
- package/dist/datadog/fingerprinter.js +34 -0
- package/dist/datadog/incident-state.js +21 -0
- package/dist/datadog/line-matcher.js +56 -0
- package/dist/datadog/memory-store.js +399 -0
- package/dist/datadog/otel-trace.js +67 -0
- package/dist/index.js +505 -0
- package/dist/intelligence/action-risk.js +132 -0
- package/dist/intelligence/activity-feed.js +40 -0
- package/dist/intelligence/agent-identity.js +115 -0
- package/dist/intelligence/agent-pipeline.js +329 -0
- package/dist/intelligence/agent-profiles.js +81 -0
- package/dist/intelligence/ai-commit.js +16 -0
- package/dist/intelligence/approval-analytics.js +42 -0
- package/dist/intelligence/approval-events.js +5 -0
- package/dist/intelligence/arch-boundaries.js +94 -0
- package/dist/intelligence/arch-graph.js +117 -0
- package/dist/intelligence/autonomy.js +397 -0
- package/dist/intelligence/azure-broker.js +57 -0
- package/dist/intelligence/azure-proxy-sessions.js +49 -0
- package/dist/intelligence/baseline.js +1 -0
- package/dist/intelligence/behavior-baseline.js +134 -0
- package/dist/intelligence/billing.js +1 -0
- package/dist/intelligence/blast-radius.js +224 -0
- package/dist/intelligence/bypass.js +157 -0
- package/dist/intelligence/calibration-classifier.js +77 -0
- package/dist/intelligence/calibration-git-sync.js +74 -0
- package/dist/intelligence/calibration.js +1 -0
- package/dist/intelligence/cascade-detector.js +58 -0
- package/dist/intelligence/case-study-generator.js +126 -0
- package/dist/intelligence/causal-graph.js +126 -0
- package/dist/intelligence/causal.js +1 -0
- package/dist/intelligence/change-risk.js +102 -0
- package/dist/intelligence/compliance-report.js +134 -0
- package/dist/intelligence/confidence-report.js +85 -0
- package/dist/intelligence/corpus-to-policy.js +140 -0
- package/dist/intelligence/credential-broker.js +112 -0
- package/dist/intelligence/credential-misuse.js +39 -0
- package/dist/intelligence/debug-sessions.js +108 -0
- package/dist/intelligence/decision-escalation.js +36 -0
- package/dist/intelligence/decision-memory.js +47 -0
- package/dist/intelligence/decision-similarity.js +85 -0
- package/dist/intelligence/default-safety-tests.js +53 -0
- package/dist/intelligence/degradation-watcher.js +108 -0
- package/dist/intelligence/detector-plugins.js +70 -0
- package/dist/intelligence/detectors.js +1 -0
- package/dist/intelligence/device-auth.js +138 -0
- package/dist/intelligence/diff-size.js +42 -0
- package/dist/intelligence/ebpf-verify.js +45 -0
- package/dist/intelligence/enterprise-policy-engine.js +1122 -0
- package/dist/intelligence/error-fingerprint.js +1 -0
- package/dist/intelligence/execution-gate.js +100 -0
- package/dist/intelligence/execution-mode.js +22 -0
- package/dist/intelligence/gate-analytics.js +256 -0
- package/dist/intelligence/gate-decision.js +64 -0
- package/dist/intelligence/gcp-broker.js +50 -0
- package/dist/intelligence/git-adr-sync.js +207 -0
- package/dist/intelligence/hitl-hold.js +460 -0
- package/dist/intelligence/hypothesis-history.js +93 -0
- package/dist/intelligence/idp-identity.js +83 -0
- package/dist/intelligence/impl-critic.js +216 -0
- package/dist/intelligence/incident-autopilot.js +622 -0
- package/dist/intelligence/incident-replay.js +162 -0
- package/dist/intelligence/incident-result-cache.js +33 -0
- package/dist/intelligence/incident-similarity.js +58 -0
- package/dist/intelligence/infra-detectors.js +312 -0
- package/dist/intelligence/license.js +185 -0
- package/dist/intelligence/llm-spokesperson.js +62 -0
- package/dist/intelligence/mcp-prompts.js +320 -0
- package/dist/intelligence/mcp-resources.js +171 -0
- package/dist/intelligence/normalize.js +108 -0
- package/dist/intelligence/notifications.js +83 -0
- package/dist/intelligence/oidc-issuer.js +87 -0
- package/dist/intelligence/override-corpus.js +597 -0
- package/dist/intelligence/planning-gate.js +84 -0
- package/dist/intelligence/plans.js +223 -0
- package/dist/intelligence/platt-scaling.js +164 -0
- package/dist/intelligence/policy-proposals.js +84 -0
- package/dist/intelligence/policy-suggester.js +161 -0
- package/dist/intelligence/policy-sync.js +103 -0
- package/dist/intelligence/policy-test-runner.js +35 -0
- package/dist/intelligence/postmortem-parser.js +181 -0
- package/dist/intelligence/postmortem-retrieval.js +224 -0
- package/dist/intelligence/postmortem-store.js +596 -0
- package/dist/intelligence/pr-commenter.js +81 -0
- package/dist/intelligence/pr-policy-gate.js +38 -0
- package/dist/intelligence/pr-shadow-analyzer.js +229 -0
- package/dist/intelligence/process-tree.js +108 -0
- package/dist/intelligence/prompts.js +1 -0
- package/dist/intelligence/replay.js +38 -0
- package/dist/intelligence/repro-steps.js +1 -0
- package/dist/intelligence/resource-extractors.js +0 -0
- package/dist/intelligence/resource-file-context.js +138 -0
- package/dist/intelligence/risk-score.js +22 -0
- package/dist/intelligence/rollback.js +137 -0
- package/dist/intelligence/runbook-updater.js +143 -0
- package/dist/intelligence/sandbox.js +194 -0
- package/dist/intelligence/script-trust.js +104 -0
- package/dist/intelligence/session-metrics.js +67 -0
- package/dist/intelligence/session-threat-tracker.js +231 -0
- package/dist/intelligence/shadow-digest-cron.js +81 -0
- package/dist/intelligence/shadow-log.js +284 -0
- package/dist/intelligence/shell-ast.js +145 -0
- package/dist/intelligence/siem-forward.js +104 -0
- package/dist/intelligence/slack-digest.js +151 -0
- package/dist/intelligence/slack-override-loop.js +217 -0
- package/dist/intelligence/slack-routing.js +67 -0
- package/dist/intelligence/slack.js +900 -0
- package/dist/intelligence/sql-ast.js +31 -0
- package/dist/intelligence/team.js +1 -0
- package/dist/intelligence/telemetry.js +76 -0
- package/dist/intelligence/threshold-optimizer.js +75 -0
- package/dist/intelligence/token-budget.js +66 -0
- package/dist/intelligence/tool-guard.js +708 -0
- package/dist/intelligence/tool-manifest 2.js +74 -0
- package/dist/intelligence/tool-manifest.js +125 -0
- package/dist/intelligence/tools-analysis.js +857 -0
- package/dist/intelligence/tools-arch.js +152 -0
- package/dist/intelligence/tools-autonomy.js +495 -0
- package/dist/intelligence/tools-blast-radius.js +140 -0
- package/dist/intelligence/tools-browser.js +431 -0
- package/dist/intelligence/tools-change-timeline.js +154 -0
- package/dist/intelligence/tools-credentials.js +117 -0
- package/dist/intelligence/tools-datadog.js +233 -0
- package/dist/intelligence/tools-debug-sessions.js +209 -0
- package/dist/intelligence/tools-discovery.js +284 -0
- package/dist/intelligence/tools-infra.js +612 -0
- package/dist/intelligence/tools-intent.js +128 -0
- package/dist/intelligence/tools-memory.js +514 -0
- package/dist/intelligence/tools-runbook.js +951 -0
- package/dist/intelligence/tools-sessions.js +107 -0
- package/dist/intelligence/tools-state.js +232 -0
- package/dist/intelligence/tools-utility.js +430 -0
- package/dist/intelligence/tools-validate.js +215 -0
- package/dist/intelligence/tools.js +1 -0
- package/dist/intelligence/trace-context.js +11 -0
- package/dist/intelligence/unclassified-clusters.js +87 -0
- package/dist/intelligence/usage.js +195 -0
- package/dist/routes/active-authors.js +63 -0
- package/dist/routes/activity-feed.js +21 -0
- package/dist/routes/adr.js +117 -0
- package/dist/routes/agent-activity.js +184 -0
- package/dist/routes/agent-blunders.js +163 -0
- package/dist/routes/agents.js +175 -0
- package/dist/routes/api-keys.js +65 -0
- package/dist/routes/arch.js +75 -0
- package/dist/routes/audit-export.js +93 -0
- package/dist/routes/billing-dashboard.js +158 -0
- package/dist/routes/billing-outcome.js +77 -0
- package/dist/routes/calibration.js +305 -0
- package/dist/routes/ci-gate-history.js +20 -0
- package/dist/routes/ci-gate.js +232 -0
- package/dist/routes/ci.js +293 -0
- package/dist/routes/compliance-report.js +24 -0
- package/dist/routes/confidence.js +30 -0
- package/dist/routes/credentials.js +119 -0
- package/dist/routes/dashboard.js +1407 -0
- package/dist/routes/decision-memory.js +42 -0
- package/dist/routes/demo.js +844 -0
- package/dist/routes/device-auth-stub.js +173 -0
- package/dist/routes/explain-why.js +39 -0
- package/dist/routes/gate-analytics.js +213 -0
- package/dist/routes/gate.js +28 -0
- package/dist/routes/github-webhook.js +364 -0
- package/dist/routes/habituation.js +30 -0
- package/dist/routes/health-integrations.js +243 -0
- package/dist/routes/heartbeats.js +45 -0
- package/dist/routes/hitl.js +364 -0
- package/dist/routes/impact-report.js +645 -0
- package/dist/routes/incident-webhook.js +63 -0
- package/dist/routes/incidents.js +366 -0
- package/dist/routes/layers.js +62 -0
- package/dist/routes/license.js +187 -0
- package/dist/routes/oidc-issuer.js +30 -0
- package/dist/routes/onboarding.js +170 -0
- package/dist/routes/otel.js +67 -0
- package/dist/routes/otlp-receiver.js +179 -0
- package/dist/routes/overrides.js +241 -0
- package/dist/routes/pagerduty.js +258 -0
- package/dist/routes/policies.js +809 -0
- package/dist/routes/policy-nl.js +169 -0
- package/dist/routes/postmortem.js +102 -0
- package/dist/routes/pr-shadow.js +47 -0
- package/dist/routes/rbac.js +61 -0
- package/dist/routes/replay.js +17 -0
- package/dist/routes/risk-report.js +164 -0
- package/dist/routes/runbooks.js +125 -0
- package/dist/routes/safety-test.js +174 -0
- package/dist/routes/sdk.js +222 -0
- package/dist/routes/sensor.js +819 -0
- package/dist/routes/sentry.js +140 -0
- package/dist/routes/sessions.js +43 -0
- package/dist/routes/setup-ui.js +594 -0
- package/dist/routes/shadow-report.js +107 -0
- package/dist/routes/slack-routing.js +171 -0
- package/dist/routes/team-usage.js +71 -0
- package/dist/routes/telemetry.js +28 -0
- package/dist/routes/tenants.js +199 -0
- package/dist/routes/tickets.js +167 -0
- package/dist/routes/validate.js +13 -0
- package/dist/routes/war-room.js +113 -0
- package/dist/scripts/check-arch.js +22 -0
- package/dist/seeds/community-corpus.js +176 -0
- package/dist/seeds/corpus.js +1224 -0
- package/dist/sensor/action-ledger.js +327 -0
- package/dist/sensor/adr-store.js +140 -0
- package/dist/sensor/agent-blunder-store.js +370 -0
- package/dist/sensor/agent-context-store.js +225 -0
- package/dist/sensor/agent-memory-store.js +231 -0
- package/dist/sensor/audit-fetch.js +23 -0
- package/dist/sensor/audit-log.js +273 -0
- package/dist/sensor/buffer-schemas.js +225 -0
- package/dist/sensor/buffer.js +599 -0
- package/dist/sensor/bypass-tracker.js +93 -0
- package/dist/sensor/ci-gate-history.js +70 -0
- package/dist/sensor/cloud-auth.js +183 -0
- package/dist/sensor/commit-context-store.js +235 -0
- package/dist/sensor/docker-log-stream.js +164 -0
- package/dist/sensor/docker-monitor.js +122 -0
- package/dist/sensor/extended-buffer.js +46 -0
- package/dist/sensor/feedback-token.js +41 -0
- package/dist/sensor/file-lock.js +6 -0
- package/dist/sensor/fs-watcher.js +106 -0
- package/dist/sensor/gate-heartbeat.js +122 -0
- package/dist/sensor/git-suspect.js +136 -0
- package/dist/sensor/habituation-store.js +77 -0
- package/dist/sensor/heartbeat-monitor.js +133 -0
- package/dist/sensor/incident-store.js +340 -0
- package/dist/sensor/infra-normalizer.js +513 -0
- package/dist/sensor/ingest.js +339 -0
- package/dist/sensor/jest-reporter.js +52 -0
- package/dist/sensor/k8s-events.js +132 -0
- package/dist/sensor/layer2-store.js +111 -0
- package/dist/sensor/layer3-store.js +230 -0
- package/dist/sensor/layer4-store.js +147 -0
- package/dist/sensor/logger.js +13 -0
- package/dist/sensor/otel-exporter.js +161 -0
- package/dist/sensor/paths.js +71 -0
- package/dist/sensor/policy-history.js +147 -0
- package/dist/sensor/pr-shadow-store.js +83 -0
- package/dist/sensor/process-watcher.js +162 -0
- package/dist/sensor/rbac.js +92 -0
- package/dist/sensor/redact.js +114 -0
- package/dist/sensor/redis-store.js +191 -0
- package/dist/sensor/route-reachability.js +64 -0
- package/dist/sensor/security-utils.js +18 -0
- package/dist/sensor/service-graph.js +170 -0
- package/dist/sensor/service-topology.js +227 -0
- package/dist/sensor/session-history.js +74 -0
- package/dist/sensor/session-persist.js +38 -0
- package/dist/sensor/shadow-promote.js +122 -0
- package/dist/sensor/sourcemap.js +281 -0
- package/dist/sensor/sqlite-store.js +205 -0
- package/dist/sensor/sso.js +164 -0
- package/dist/sensor/vitest-reporter.js +65 -0
- package/dist/sensor/watcher.js +48 -0
- package/dist/storage/interfaces.js +0 -0
- package/dist/storage/pg/pg-action-ledger.js +138 -0
- package/dist/storage/pg/pg-approval-store.js +107 -0
- package/dist/storage/pg/pg-blunder-store.js +193 -0
- package/dist/storage/pg/pg-ci-gate-history.js +83 -0
- package/dist/storage/pg/pg-client.js +24 -0
- package/dist/storage/pg/pg-event-store.js +63 -0
- package/dist/storage/pg/pg-incident-store.js +190 -0
- package/dist/storage/pg/pg-migrations.js +26 -0
- package/dist/storage/pg/pg-override-corpus.js +447 -0
- package/dist/storage/pg/pg-shadow-log.js +129 -0
- package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
- package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
- package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
- package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
- package/dist/storage/sqlite/sqlite-event-store.js +29 -0
- package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
- package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
- package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
- package/dist/storage/store-factory.js +55 -0
- package/dist/storage/store-registry.js +31 -0
- package/dist/update-checker.js +107 -0
- package/dist/workers/autopilot-worker.js +28 -0
- package/dist/workers/notification-worker.js +28 -0
- package/dist/workers/queues.js +58 -0
- package/dist/workers/validation-worker.js +24 -0
- package/dist/workers/worker-registry.js +18 -0
- package/package.json +123 -0
- package/sdk/mergen-inject.js +260 -0
- package/sdk/node.js +313 -0
- package/sdk/vite-plugin.ts +57 -0
- package/sdk/webpack-plugin.js +106 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { DATA_DIR } from "../sensor/paths.js";
|
|
4
|
+
import { buildCausalChain } from "./causal.js";
|
|
5
|
+
import logger from "../sensor/logger.js";
|
|
6
|
+
const REPLAY_DIR = path.join(DATA_DIR, "replay-snapshots");
|
|
7
|
+
const MAX_SNAPSHOTS = 500;
|
|
8
|
+
function captureSnapshot(snapshot) {
|
|
9
|
+
try {
|
|
10
|
+
fs.mkdirSync(REPLAY_DIR, { recursive: true });
|
|
11
|
+
fs.writeFileSync(
|
|
12
|
+
path.join(REPLAY_DIR, `${snapshot.pid}.json`),
|
|
13
|
+
JSON.stringify(snapshot),
|
|
14
|
+
"utf8"
|
|
15
|
+
);
|
|
16
|
+
const files = fs.readdirSync(REPLAY_DIR).filter((f) => f.endsWith(".json"));
|
|
17
|
+
if (files.length > MAX_SNAPSHOTS) {
|
|
18
|
+
const withMtime = files.map((f) => {
|
|
19
|
+
try {
|
|
20
|
+
return { f, mtime: fs.statSync(path.join(REPLAY_DIR, f)).mtimeMs };
|
|
21
|
+
} catch {
|
|
22
|
+
return { f, mtime: 0 };
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
withMtime.sort((a, b) => a.mtime - b.mtime);
|
|
26
|
+
for (const { f } of withMtime.slice(0, files.length - MAX_SNAPSHOTS)) {
|
|
27
|
+
try {
|
|
28
|
+
fs.unlinkSync(path.join(REPLAY_DIR, f));
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger.warn({ err, pid: snapshot.pid }, "incident-replay: failed to capture snapshot");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function replayIncident(pid) {
|
|
38
|
+
const file = path.join(REPLAY_DIR, `${pid}.json`);
|
|
39
|
+
if (!fs.existsSync(file)) return null;
|
|
40
|
+
let snapshot;
|
|
41
|
+
try {
|
|
42
|
+
snapshot = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
logger.warn({ err, pid }, "incident-replay: corrupt snapshot file");
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
let replayedTag = null;
|
|
48
|
+
let replayedScore = null;
|
|
49
|
+
let replayedHint = null;
|
|
50
|
+
try {
|
|
51
|
+
const causal = await buildCausalChain(
|
|
52
|
+
snapshot.logs,
|
|
53
|
+
snapshot.network,
|
|
54
|
+
snapshot.contexts,
|
|
55
|
+
snapshot.firedAt,
|
|
56
|
+
snapshot.terminal,
|
|
57
|
+
snapshot.processExits,
|
|
58
|
+
snapshot.ciEvents,
|
|
59
|
+
snapshot.deployments,
|
|
60
|
+
snapshot.infraEvents
|
|
61
|
+
);
|
|
62
|
+
const top = causal.hypotheses[0] ?? null;
|
|
63
|
+
replayedTag = top?.tag ?? null;
|
|
64
|
+
replayedScore = top?.confidenceScore ?? null;
|
|
65
|
+
replayedHint = top?.fixHint ?? null;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
logger.warn({ err, pid }, "incident-replay: replay analysis threw \u2014 returning partial result");
|
|
68
|
+
}
|
|
69
|
+
const topTagChanged = replayedTag !== snapshot.originalTag;
|
|
70
|
+
const fixHintChanged = replayedHint !== snapshot.originalFixHint;
|
|
71
|
+
const confidenceDelta = replayedScore !== null && snapshot.originalConfidenceScore !== null ? Math.round((replayedScore - snapshot.originalConfidenceScore) * 1e3) / 1e3 : null;
|
|
72
|
+
const parts = [];
|
|
73
|
+
if (topTagChanged) {
|
|
74
|
+
parts.push(`Diagnosis changed: ${snapshot.originalTag ?? "none"} \u2192 ${replayedTag ?? "none"}`);
|
|
75
|
+
} else {
|
|
76
|
+
parts.push("Same diagnosis");
|
|
77
|
+
}
|
|
78
|
+
if (confidenceDelta !== null && Math.abs(confidenceDelta) >= 0.01) {
|
|
79
|
+
parts.push(`Confidence ${confidenceDelta >= 0 ? "+" : ""}${(confidenceDelta * 100).toFixed(1)}pp`);
|
|
80
|
+
}
|
|
81
|
+
if (fixHintChanged) parts.push("Fix hint changed");
|
|
82
|
+
return {
|
|
83
|
+
pid,
|
|
84
|
+
originalHypothesis: {
|
|
85
|
+
tag: snapshot.originalTag,
|
|
86
|
+
confidenceScore: snapshot.originalConfidenceScore,
|
|
87
|
+
fixHint: snapshot.originalFixHint
|
|
88
|
+
},
|
|
89
|
+
replayedHypothesis: {
|
|
90
|
+
tag: replayedTag,
|
|
91
|
+
confidenceScore: replayedScore,
|
|
92
|
+
fixHint: replayedHint
|
|
93
|
+
},
|
|
94
|
+
drift: {
|
|
95
|
+
topTagChanged,
|
|
96
|
+
confidenceDelta,
|
|
97
|
+
fixHintChanged,
|
|
98
|
+
summary: parts.join(". ") + "."
|
|
99
|
+
},
|
|
100
|
+
replayedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function replayAllSnapshots(opts = {}) {
|
|
104
|
+
const confidenceThreshold = opts.confidenceThreshold ?? 0.05;
|
|
105
|
+
let pids = listSnapshotPids();
|
|
106
|
+
if (opts.excludePrefix) pids = pids.filter((p) => !p.startsWith(opts.excludePrefix));
|
|
107
|
+
const total = pids.length;
|
|
108
|
+
if (opts.limit !== void 0) pids = pids.slice(0, Math.max(0, opts.limit));
|
|
109
|
+
let replayed = 0, skipped = 0, unchanged = 0, tagChanged = 0, fixHintChanged = 0, confidenceDrifted = 0;
|
|
110
|
+
const drifted = [];
|
|
111
|
+
for (const pid of pids) {
|
|
112
|
+
const result = await replayIncident(pid);
|
|
113
|
+
if (!result) {
|
|
114
|
+
skipped++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
replayed++;
|
|
118
|
+
const confDrift = result.drift.confidenceDelta !== null && Math.abs(result.drift.confidenceDelta) >= confidenceThreshold;
|
|
119
|
+
if (result.drift.topTagChanged) tagChanged++;
|
|
120
|
+
if (result.drift.fixHintChanged) fixHintChanged++;
|
|
121
|
+
if (confDrift) confidenceDrifted++;
|
|
122
|
+
if (result.drift.topTagChanged || result.drift.fixHintChanged || confDrift) {
|
|
123
|
+
drifted.push({
|
|
124
|
+
pid,
|
|
125
|
+
summary: result.drift.summary,
|
|
126
|
+
originalTag: result.originalHypothesis.tag,
|
|
127
|
+
replayedTag: result.replayedHypothesis.tag,
|
|
128
|
+
confidenceDelta: result.drift.confidenceDelta
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
unchanged++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
drifted.sort((a, b) => Number(b.originalTag !== b.replayedTag) - Number(a.originalTag !== a.replayedTag));
|
|
135
|
+
return {
|
|
136
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
137
|
+
confidenceThreshold,
|
|
138
|
+
total,
|
|
139
|
+
replayed,
|
|
140
|
+
skipped,
|
|
141
|
+
unchanged,
|
|
142
|
+
tagChanged,
|
|
143
|
+
fixHintChanged,
|
|
144
|
+
confidenceDrifted,
|
|
145
|
+
driftRate: replayed > 0 ? Math.round((replayed - unchanged) / replayed * 1e3) / 1e3 : 0,
|
|
146
|
+
drifted
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function listSnapshotPids() {
|
|
150
|
+
try {
|
|
151
|
+
if (!fs.existsSync(REPLAY_DIR)) return [];
|
|
152
|
+
return fs.readdirSync(REPLAY_DIR).filter((f) => f.endsWith(".json")).map((f) => f.slice(0, -5)).sort();
|
|
153
|
+
} catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
captureSnapshot,
|
|
159
|
+
listSnapshotPids,
|
|
160
|
+
replayAllSnapshots,
|
|
161
|
+
replayIncident
|
|
162
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const TTL_MS = 60 * 60 * 1e3;
|
|
2
|
+
const MAX_ENTRIES = 500;
|
|
3
|
+
const _cache = /* @__PURE__ */ new Map();
|
|
4
|
+
function cacheIncidentResult(result) {
|
|
5
|
+
_cache.set(result.fingerprint, { ...result, cachedAt: Date.now() });
|
|
6
|
+
if (_cache.size > MAX_ENTRIES) {
|
|
7
|
+
const cutoff = Date.now() - TTL_MS;
|
|
8
|
+
for (const [k, v] of _cache) {
|
|
9
|
+
if (v.cachedAt < cutoff) _cache.delete(k);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function getCachedIncidentResult(fingerprint) {
|
|
14
|
+
const entry = _cache.get(fingerprint);
|
|
15
|
+
if (!entry) return null;
|
|
16
|
+
if (Date.now() - entry.cachedAt > TTL_MS) {
|
|
17
|
+
_cache.delete(fingerprint);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return entry;
|
|
21
|
+
}
|
|
22
|
+
function invalidateIncidentResult(fingerprint) {
|
|
23
|
+
_cache.delete(fingerprint);
|
|
24
|
+
}
|
|
25
|
+
function incidentCacheSize() {
|
|
26
|
+
return _cache.size;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
cacheIncidentResult,
|
|
30
|
+
getCachedIncidentResult,
|
|
31
|
+
incidentCacheSize,
|
|
32
|
+
invalidateIncidentResult
|
|
33
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { incidentStore } from "../sensor/incident-store.js";
|
|
2
|
+
import { memoryStore } from "../datadog/memory-store.js";
|
|
3
|
+
import { hybridSearch } from "./postmortem-retrieval.js";
|
|
4
|
+
const DAY_MS = 864e5;
|
|
5
|
+
function findSimilarIncidents(ctx, limit = 3) {
|
|
6
|
+
const results = [];
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
if (ctx.service) {
|
|
9
|
+
try {
|
|
10
|
+
const serviceLower = ctx.service.toLowerCase();
|
|
11
|
+
for (const inc of incidentStore.list(void 0, 100)) {
|
|
12
|
+
const match = (inc.service ?? "").toLowerCase() === serviceLower || (inc.hypothesis ?? "").toLowerCase().includes(serviceLower);
|
|
13
|
+
if (match) {
|
|
14
|
+
results.push({
|
|
15
|
+
hypothesis: inc.hypothesis,
|
|
16
|
+
daysAgo: Math.floor((now - inc.createdAt) / DAY_MS),
|
|
17
|
+
confidence: inc.confidence,
|
|
18
|
+
source: "service_history"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const file of (ctx.files ?? []).slice(0, 10)) {
|
|
26
|
+
try {
|
|
27
|
+
for (const mem of memoryStore.findByFile(file, 2)) {
|
|
28
|
+
results.push({
|
|
29
|
+
hypothesis: `${file}: ${mem.pdAlertTitle || mem.errorType || mem.errorMessage}`,
|
|
30
|
+
daysAgo: Math.floor((now - mem.firedAt) / DAY_MS),
|
|
31
|
+
confidence: 0.7,
|
|
32
|
+
// memory-store hits are exact filename matches — treated as high-confidence
|
|
33
|
+
source: "file_history"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (ctx.commandText && ctx.commandText.trim().length >= 3) {
|
|
40
|
+
try {
|
|
41
|
+
const hits = hybridSearch(ctx.commandText, { topK: 2, maxCorpus: 200 });
|
|
42
|
+
for (const hit of hits) {
|
|
43
|
+
results.push({
|
|
44
|
+
hypothesis: hit.postmortem.rootCause,
|
|
45
|
+
daysAgo: 0,
|
|
46
|
+
confidence: hit.postmortem.confidence,
|
|
47
|
+
source: "text_match"
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
results.sort((a, b) => b.confidence - a.confidence || a.daysAgo - b.daysAgo);
|
|
54
|
+
return results.slice(0, limit);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
findSimilarIncidents
|
|
58
|
+
};
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { scoreToConfidence } from "./detectors.js";
|
|
2
|
+
const DATADOG_BOOST = 0.15;
|
|
3
|
+
function withDatadogBoost(base, matched) {
|
|
4
|
+
return matched.some((e) => e.source === "datadog") ? Math.min(base + DATADOG_BOOST, 0.95) : base;
|
|
5
|
+
}
|
|
6
|
+
function detectDbConnectionPool(events) {
|
|
7
|
+
const matches = events.filter((e) => e.kind === "db_connection_pool_exhausted");
|
|
8
|
+
if (matches.length === 0) return null;
|
|
9
|
+
const p = matches[0];
|
|
10
|
+
const score = withDatadogBoost(0.8, matches);
|
|
11
|
+
return {
|
|
12
|
+
tag: "infra_db_connection_pool",
|
|
13
|
+
summary: `Database connection pool exhausted on \`${p.service}\` \u2014 ${p.message}`,
|
|
14
|
+
confidence: scoreToConfidence(score),
|
|
15
|
+
confidenceScore: score,
|
|
16
|
+
// Fix reliability is lower than diagnostic confidence: resize vs restart vs leak hunt
|
|
17
|
+
// depends on the specific failure mode and cannot be determined from telemetry alone.
|
|
18
|
+
remediationConfidence: 0.6,
|
|
19
|
+
evidence: [
|
|
20
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
21
|
+
`Service: \`${p.service}\``,
|
|
22
|
+
`Endpoint: \`${p.attributes.endpoint || "unknown"}\``,
|
|
23
|
+
`Trace: \`${p.attributes.traceId || "none"}\``
|
|
24
|
+
],
|
|
25
|
+
causalPath: [
|
|
26
|
+
"Request volume exceeded connection pool capacity",
|
|
27
|
+
"New connections queued waiting for a free slot",
|
|
28
|
+
`\`${p.service}\` \u2192 timeout / connection refused on \`${p.attributes.endpoint || "DB"}\``
|
|
29
|
+
],
|
|
30
|
+
fixHint: [
|
|
31
|
+
"Increase pool size: set `DB_POOL_MAX` (or `pool.max` in your ORM config).",
|
|
32
|
+
"Check for connection leaks \u2014 unclosed transactions hold pool slots indefinitely.",
|
|
33
|
+
"Add a connection pool metrics query to validate utilisation."
|
|
34
|
+
].join(" "),
|
|
35
|
+
fixAction: { type: "service_restart", target: p.service, method: "kubectl" }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function detectOomKill(events) {
|
|
39
|
+
const matches = events.filter((e) => e.kind === "oom_kill" || e.kind === "memory_pressure");
|
|
40
|
+
if (matches.length === 0) return null;
|
|
41
|
+
const p = matches[0];
|
|
42
|
+
const isHard = p.kind === "oom_kill";
|
|
43
|
+
const score = withDatadogBoost(isHard ? 0.9 : 0.65, matches);
|
|
44
|
+
const exitCode = p.attributes.exitCode;
|
|
45
|
+
const memMb = p.attributes.memoryLimitMb;
|
|
46
|
+
return {
|
|
47
|
+
tag: "infra_oom_kill",
|
|
48
|
+
summary: `${isHard ? "OOM kill" : "Memory pressure"} on \`${p.service}\` \u2014 ${p.message}`,
|
|
49
|
+
confidence: scoreToConfidence(score),
|
|
50
|
+
confidenceScore: score,
|
|
51
|
+
// Raising the memory limit restarts the service but does not fix the underlying leak.
|
|
52
|
+
// Remediation confidence is lower: the hint is directionally correct but rarely complete.
|
|
53
|
+
remediationConfidence: isHard ? 0.55 : 0.5,
|
|
54
|
+
evidence: [
|
|
55
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
56
|
+
`Service: \`${p.service}\``,
|
|
57
|
+
...isHard ? [`Exit code ${exitCode} (SIGKILL \u2014 kernel OOM killer fired)`] : [],
|
|
58
|
+
...memMb ? [`Memory limit: ${memMb} MB`] : []
|
|
59
|
+
],
|
|
60
|
+
causalPath: [
|
|
61
|
+
"RSS / heap grew past the container memory limit",
|
|
62
|
+
"Kernel OOM killer selected and killed the process (exit 137)",
|
|
63
|
+
isHard ? `\`${p.service}\` is now restarting \u2014 possible CrashLoopBackOff` : `\`${p.service}\` experiencing memory pressure \u2014 degraded performance`
|
|
64
|
+
],
|
|
65
|
+
fixHint: isHard ? [
|
|
66
|
+
"Increase container memory limit in k8s manifest / docker-compose.",
|
|
67
|
+
"Heap-profile with `node --inspect` or `py-spy top` to find the leak.",
|
|
68
|
+
"Check for unbounded caches or retained buffers in recent deploys."
|
|
69
|
+
].join(" ") : [
|
|
70
|
+
"Profile heap allocation. Check for unbounded in-memory caches or event-listener leaks.",
|
|
71
|
+
"Review recent deploys for large data structure growth."
|
|
72
|
+
].join(" "),
|
|
73
|
+
fixAction: isHard ? { type: "service_restart", target: p.service, method: "kubectl" } : void 0
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function detectRateLimitCascade(events) {
|
|
77
|
+
const matches = events.filter((e) => e.kind === "rate_limit_cascade");
|
|
78
|
+
if (matches.length === 0) return null;
|
|
79
|
+
const p = matches[0];
|
|
80
|
+
const score = withDatadogBoost(0.75, matches);
|
|
81
|
+
return {
|
|
82
|
+
tag: "infra_rate_limit_cascade",
|
|
83
|
+
summary: `Rate-limit cascade on \`${p.service}\` \u2014 ${p.message}`,
|
|
84
|
+
confidence: scoreToConfidence(score),
|
|
85
|
+
confidenceScore: score,
|
|
86
|
+
// The fix (add backoff + honour Retry-After) is correct but requires a code change
|
|
87
|
+
// and deploy — not an immediately executable command. Remediation confidence reflects
|
|
88
|
+
// that the hint cannot be auto-applied without human involvement.
|
|
89
|
+
remediationConfidence: 0.65,
|
|
90
|
+
evidence: [
|
|
91
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
92
|
+
`Service: \`${p.service}\``,
|
|
93
|
+
`Endpoint: \`${p.attributes.endpoint || "unknown"}\``
|
|
94
|
+
],
|
|
95
|
+
causalPath: [
|
|
96
|
+
"Upstream API / gateway returned 429 Too Many Requests",
|
|
97
|
+
"Service retried without exponential backoff, amplifying the traffic spike",
|
|
98
|
+
`\`${p.service}\` \u2192 retry storm \u2192 all requests throttled`
|
|
99
|
+
],
|
|
100
|
+
fixHint: [
|
|
101
|
+
"Add exponential backoff with jitter to the retry loop.",
|
|
102
|
+
"Honour the `Retry-After` response header.",
|
|
103
|
+
"Consider a token-bucket circuit breaker to shed load before hitting the upstream limit."
|
|
104
|
+
].join(" ")
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function detectDownstreamLatency(events) {
|
|
108
|
+
const matches = events.filter(
|
|
109
|
+
(e) => e.kind === "downstream_latency_spike" || e.kind === "slow_query"
|
|
110
|
+
);
|
|
111
|
+
if (matches.length === 0) return null;
|
|
112
|
+
const p = matches[0];
|
|
113
|
+
const isQuery = p.kind === "slow_query";
|
|
114
|
+
const score = withDatadogBoost(isQuery ? 0.7 : 0.6, matches);
|
|
115
|
+
return {
|
|
116
|
+
tag: isQuery ? "infra_slow_query" : "infra_downstream_latency",
|
|
117
|
+
summary: `${isQuery ? "Slow DB query" : "Downstream latency spike"} on \`${p.service}\` \u2014 ${p.message}`,
|
|
118
|
+
confidence: scoreToConfidence(score),
|
|
119
|
+
confidenceScore: score,
|
|
120
|
+
evidence: [
|
|
121
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
122
|
+
`Service: \`${p.service}\``,
|
|
123
|
+
`Endpoint: \`${p.attributes.endpoint || "unknown"}\``
|
|
124
|
+
],
|
|
125
|
+
causalPath: isQuery ? [
|
|
126
|
+
"Query plan degraded (missing index, table scan, lock contention)",
|
|
127
|
+
`Database query exceeded statement_timeout on \`${p.service}\``,
|
|
128
|
+
"Upstream requests queued waiting \u2014 goroutines / workers exhausted"
|
|
129
|
+
] : [
|
|
130
|
+
"Downstream service p99 latency exceeded caller timeout",
|
|
131
|
+
"Threads / goroutines blocked on slow dependency",
|
|
132
|
+
`\`${p.service}\` \u2192 cascading timeouts to callers`
|
|
133
|
+
],
|
|
134
|
+
fixHint: isQuery ? "Run EXPLAIN ANALYZE on the slow query. Add indexes on WHERE / JOIN columns. Check recent schema migrations for unindexed columns." : "Add circuit breaker around the slow downstream. Increase timeout if the dependency is legitimately slow, or reduce p99 of the dependency."
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function detectCertificateExpiry(events) {
|
|
138
|
+
const matches = events.filter((e) => e.kind === "certificate_expiry");
|
|
139
|
+
if (matches.length === 0) return null;
|
|
140
|
+
const p = matches[0];
|
|
141
|
+
const score = 0.85;
|
|
142
|
+
return {
|
|
143
|
+
tag: "infra_certificate_expiry",
|
|
144
|
+
summary: `TLS / certificate error on \`${p.service}\` \u2014 ${p.message}`,
|
|
145
|
+
confidence: scoreToConfidence(score),
|
|
146
|
+
confidenceScore: score,
|
|
147
|
+
// certbot renew is deterministic when the cert is genuinely expired — high remediation
|
|
148
|
+
// confidence. Mismatch cases (wrong hostname, CA trust) require manual intervention,
|
|
149
|
+
// but certificate expiry is the dominant failure mode by volume.
|
|
150
|
+
remediationConfidence: 0.9,
|
|
151
|
+
evidence: [
|
|
152
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
153
|
+
`Service: \`${p.service}\``,
|
|
154
|
+
"Error pattern: TLS handshake failure / certificate validation error"
|
|
155
|
+
],
|
|
156
|
+
causalPath: [
|
|
157
|
+
"TLS handshake rejected \u2014 certificate expired, untrusted, or hostname mismatch",
|
|
158
|
+
`\`${p.service}\` cannot establish HTTPS connection to endpoint`,
|
|
159
|
+
"All HTTPS requests to this endpoint failing"
|
|
160
|
+
],
|
|
161
|
+
fixHint: [
|
|
162
|
+
"Check expiry: `openssl s_client -connect <host>:443 2>/dev/null | openssl x509 -noout -dates`",
|
|
163
|
+
"Renew via Let's Encrypt (`certbot renew`) or your CA.",
|
|
164
|
+
"Verify the cert covers the target hostname (SAN list)."
|
|
165
|
+
].join(" ")
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function detectDiskPressure(events) {
|
|
169
|
+
const matches = events.filter((e) => e.kind === "disk_pressure");
|
|
170
|
+
if (matches.length === 0) return null;
|
|
171
|
+
const p = matches[0];
|
|
172
|
+
const score = 0.85;
|
|
173
|
+
return {
|
|
174
|
+
tag: "infra_disk_pressure",
|
|
175
|
+
summary: `Disk pressure on \`${p.service}\` \u2014 ${p.message}`,
|
|
176
|
+
confidence: scoreToConfidence(score),
|
|
177
|
+
confidenceScore: score,
|
|
178
|
+
evidence: [
|
|
179
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
180
|
+
`Service: \`${p.service}\``,
|
|
181
|
+
"Error pattern: no space left / disk full"
|
|
182
|
+
],
|
|
183
|
+
causalPath: [
|
|
184
|
+
"Filesystem reached 100% capacity",
|
|
185
|
+
"Writes blocked \u2014 log rotation, WAL, or temp files cannot be created",
|
|
186
|
+
`\`${p.service}\` \u2192 I/O errors / service crash`
|
|
187
|
+
],
|
|
188
|
+
fixHint: [
|
|
189
|
+
"Check disk usage: `df -h` and `du -sh /var/log/* /tmp/*`.",
|
|
190
|
+
"Rotate logs immediately: `journalctl --vacuum-size=500M`.",
|
|
191
|
+
"Increase PV size or add log retention policy to prevent recurrence."
|
|
192
|
+
].join(" ")
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
function detectQueueBacklog(events) {
|
|
196
|
+
const matches = events.filter((e) => e.kind === "queue_backlog");
|
|
197
|
+
if (matches.length === 0) return null;
|
|
198
|
+
const p = matches[0];
|
|
199
|
+
const score = withDatadogBoost(0.65, matches);
|
|
200
|
+
return {
|
|
201
|
+
tag: "infra_queue_backlog",
|
|
202
|
+
summary: `Message queue backlog on \`${p.service}\` \u2014 ${p.message}`,
|
|
203
|
+
confidence: scoreToConfidence(score),
|
|
204
|
+
confidenceScore: score,
|
|
205
|
+
// Scaling consumers is directionally correct but may not be the fastest path —
|
|
206
|
+
// slow handler logic or a poison message may require different intervention.
|
|
207
|
+
remediationConfidence: 0.6,
|
|
208
|
+
evidence: [
|
|
209
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
210
|
+
`Service: \`${p.service}\``,
|
|
211
|
+
"Error pattern: consumer lag / queue depth / backlog"
|
|
212
|
+
],
|
|
213
|
+
causalPath: [
|
|
214
|
+
"Producer rate exceeded consumer throughput",
|
|
215
|
+
"Consumer group lagging \u2014 messages accumulating in queue",
|
|
216
|
+
`\`${p.service}\` \u2192 processing delay / stale data`
|
|
217
|
+
],
|
|
218
|
+
fixHint: [
|
|
219
|
+
"Scale up consumers (increase replica count or partition count).",
|
|
220
|
+
"Check for slow message processing \u2014 add timing metrics around handler logic.",
|
|
221
|
+
"Verify no consumer is in an error loop silently dropping messages."
|
|
222
|
+
].join(" ")
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function detectServiceUnavailable(events) {
|
|
226
|
+
const matches = events.filter((e) => e.kind === "service_unavailable");
|
|
227
|
+
if (matches.length === 0) return null;
|
|
228
|
+
const p = matches[0];
|
|
229
|
+
const score = withDatadogBoost(0.7, matches);
|
|
230
|
+
return {
|
|
231
|
+
tag: "infra_service_unavailable",
|
|
232
|
+
summary: `Service unavailable: \`${p.service}\` cannot reach upstream \u2014 ${p.message}`,
|
|
233
|
+
confidence: scoreToConfidence(score),
|
|
234
|
+
confidenceScore: score,
|
|
235
|
+
evidence: [
|
|
236
|
+
`Triggering signal: "${p.message.slice(0, 200)}"`,
|
|
237
|
+
`Service: \`${p.service}\``,
|
|
238
|
+
`Endpoint: \`${p.attributes.endpoint || "unknown"}\``,
|
|
239
|
+
"Error pattern: 503 / upstream connect error / no healthy upstream"
|
|
240
|
+
],
|
|
241
|
+
causalPath: [
|
|
242
|
+
"Upstream service is down or all instances are unhealthy",
|
|
243
|
+
"Load balancer / ingress cannot find a healthy backend",
|
|
244
|
+
`\`${p.service}\` receiving 503 on every request to the dependency`
|
|
245
|
+
],
|
|
246
|
+
fixHint: [
|
|
247
|
+
"Check k8s pod status: `kubectl get pods -n <namespace>`.",
|
|
248
|
+
"Look for CrashLoopBackOff or Pending pods.",
|
|
249
|
+
"Review readiness probe failures and recent deploy events."
|
|
250
|
+
].join(" ")
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const NOISE_ENDPOINT_PATTERNS = [
|
|
254
|
+
"/health",
|
|
255
|
+
"/metrics",
|
|
256
|
+
"/ready",
|
|
257
|
+
"/liveness",
|
|
258
|
+
"/readiness",
|
|
259
|
+
"/ping",
|
|
260
|
+
"/livez",
|
|
261
|
+
"/readyz"
|
|
262
|
+
];
|
|
263
|
+
function isMonitoringNoise(e) {
|
|
264
|
+
const endpoint = String(e.attributes.endpoint || "");
|
|
265
|
+
const msg = e.message || "";
|
|
266
|
+
return NOISE_ENDPOINT_PATTERNS.some((p) => endpoint.includes(p) || msg.includes(p));
|
|
267
|
+
}
|
|
268
|
+
function detectUpstreamError(events) {
|
|
269
|
+
const matches = events.filter((e) => e.kind === "upstream_error" && !isMonitoringNoise(e));
|
|
270
|
+
if (matches.length === 0) return null;
|
|
271
|
+
const p = matches[0];
|
|
272
|
+
const score = 0.4;
|
|
273
|
+
return {
|
|
274
|
+
tag: "infra_upstream_error",
|
|
275
|
+
summary: `Production error on \`${p.service}\` \u2014 ${p.message}`,
|
|
276
|
+
confidence: scoreToConfidence(score),
|
|
277
|
+
confidenceScore: score,
|
|
278
|
+
evidence: [
|
|
279
|
+
`Service: \`${p.service}\``,
|
|
280
|
+
`Endpoint: \`${p.attributes.endpoint || "unknown"}\``,
|
|
281
|
+
`Trace: \`${p.attributes.traceId || "none"}\``
|
|
282
|
+
],
|
|
283
|
+
causalPath: [
|
|
284
|
+
`\`${p.service}\` returned an error on \`${p.attributes.endpoint || "unknown"}\``
|
|
285
|
+
],
|
|
286
|
+
fixHint: p.attributes.traceId ? `Inspect trace \`${p.attributes.traceId}\` in Datadog APM for the full call stack. Check service logs around the incident time.` : "Check service logs and recent deploys. Run `get_incident_context` to fetch the full Datadog trace."
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const ALL_INFRA_DETECTORS = [
|
|
290
|
+
detectDbConnectionPool,
|
|
291
|
+
detectOomKill,
|
|
292
|
+
detectRateLimitCascade,
|
|
293
|
+
detectDownstreamLatency,
|
|
294
|
+
detectCertificateExpiry,
|
|
295
|
+
detectDiskPressure,
|
|
296
|
+
detectQueueBacklog,
|
|
297
|
+
detectServiceUnavailable,
|
|
298
|
+
detectUpstreamError
|
|
299
|
+
// catch-all — must be last
|
|
300
|
+
];
|
|
301
|
+
export {
|
|
302
|
+
ALL_INFRA_DETECTORS,
|
|
303
|
+
detectCertificateExpiry,
|
|
304
|
+
detectDbConnectionPool,
|
|
305
|
+
detectDiskPressure,
|
|
306
|
+
detectDownstreamLatency,
|
|
307
|
+
detectOomKill,
|
|
308
|
+
detectQueueBacklog,
|
|
309
|
+
detectRateLimitCascade,
|
|
310
|
+
detectServiceUnavailable,
|
|
311
|
+
detectUpstreamError
|
|
312
|
+
};
|