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.
Files changed (308) hide show
  1. package/LICENSE +57 -0
  2. package/README.md +96 -0
  3. package/dist/__stubs__/calibration.js +471 -0
  4. package/dist/__stubs__/causal.js +601 -0
  5. package/dist/__stubs__/closed-source.js +150 -0
  6. package/dist/__stubs__/license.js +35 -0
  7. package/dist/__stubs__/plans.js +197 -0
  8. package/dist/app.js +467 -0
  9. package/dist/cli.js +250 -0
  10. package/dist/commands/agent-identity.js +79 -0
  11. package/dist/commands/gate.js +107 -0
  12. package/dist/commands/github.js +632 -0
  13. package/dist/commands/incident.js +644 -0
  14. package/dist/commands/policy.js +710 -0
  15. package/dist/commands/scan.js +243 -0
  16. package/dist/commands/setup.js +1218 -0
  17. package/dist/commands/shared.js +109 -0
  18. package/dist/commands/team.js +809 -0
  19. package/dist/datadog/blame-attribution.js +228 -0
  20. package/dist/datadog/client.js +159 -0
  21. package/dist/datadog/compactor.js +169 -0
  22. package/dist/datadog/fingerprinter.js +34 -0
  23. package/dist/datadog/incident-state.js +21 -0
  24. package/dist/datadog/line-matcher.js +56 -0
  25. package/dist/datadog/memory-store.js +399 -0
  26. package/dist/datadog/otel-trace.js +67 -0
  27. package/dist/index.js +505 -0
  28. package/dist/intelligence/action-risk.js +132 -0
  29. package/dist/intelligence/activity-feed.js +40 -0
  30. package/dist/intelligence/agent-identity.js +115 -0
  31. package/dist/intelligence/agent-pipeline.js +329 -0
  32. package/dist/intelligence/agent-profiles.js +81 -0
  33. package/dist/intelligence/ai-commit.js +16 -0
  34. package/dist/intelligence/approval-analytics.js +42 -0
  35. package/dist/intelligence/approval-events.js +5 -0
  36. package/dist/intelligence/arch-boundaries.js +94 -0
  37. package/dist/intelligence/arch-graph.js +117 -0
  38. package/dist/intelligence/autonomy.js +397 -0
  39. package/dist/intelligence/azure-broker.js +57 -0
  40. package/dist/intelligence/azure-proxy-sessions.js +49 -0
  41. package/dist/intelligence/baseline.js +1 -0
  42. package/dist/intelligence/behavior-baseline.js +134 -0
  43. package/dist/intelligence/billing.js +1 -0
  44. package/dist/intelligence/blast-radius.js +224 -0
  45. package/dist/intelligence/bypass.js +157 -0
  46. package/dist/intelligence/calibration-classifier.js +77 -0
  47. package/dist/intelligence/calibration-git-sync.js +74 -0
  48. package/dist/intelligence/calibration.js +1 -0
  49. package/dist/intelligence/cascade-detector.js +58 -0
  50. package/dist/intelligence/case-study-generator.js +126 -0
  51. package/dist/intelligence/causal-graph.js +126 -0
  52. package/dist/intelligence/causal.js +1 -0
  53. package/dist/intelligence/change-risk.js +102 -0
  54. package/dist/intelligence/compliance-report.js +134 -0
  55. package/dist/intelligence/confidence-report.js +85 -0
  56. package/dist/intelligence/corpus-to-policy.js +140 -0
  57. package/dist/intelligence/credential-broker.js +112 -0
  58. package/dist/intelligence/credential-misuse.js +39 -0
  59. package/dist/intelligence/debug-sessions.js +108 -0
  60. package/dist/intelligence/decision-escalation.js +36 -0
  61. package/dist/intelligence/decision-memory.js +47 -0
  62. package/dist/intelligence/decision-similarity.js +85 -0
  63. package/dist/intelligence/default-safety-tests.js +53 -0
  64. package/dist/intelligence/degradation-watcher.js +108 -0
  65. package/dist/intelligence/detector-plugins.js +70 -0
  66. package/dist/intelligence/detectors.js +1 -0
  67. package/dist/intelligence/device-auth.js +138 -0
  68. package/dist/intelligence/diff-size.js +42 -0
  69. package/dist/intelligence/ebpf-verify.js +45 -0
  70. package/dist/intelligence/enterprise-policy-engine.js +1122 -0
  71. package/dist/intelligence/error-fingerprint.js +1 -0
  72. package/dist/intelligence/execution-gate.js +100 -0
  73. package/dist/intelligence/execution-mode.js +22 -0
  74. package/dist/intelligence/gate-analytics.js +256 -0
  75. package/dist/intelligence/gate-decision.js +64 -0
  76. package/dist/intelligence/gcp-broker.js +50 -0
  77. package/dist/intelligence/git-adr-sync.js +207 -0
  78. package/dist/intelligence/hitl-hold.js +460 -0
  79. package/dist/intelligence/hypothesis-history.js +93 -0
  80. package/dist/intelligence/idp-identity.js +83 -0
  81. package/dist/intelligence/impl-critic.js +216 -0
  82. package/dist/intelligence/incident-autopilot.js +622 -0
  83. package/dist/intelligence/incident-replay.js +162 -0
  84. package/dist/intelligence/incident-result-cache.js +33 -0
  85. package/dist/intelligence/incident-similarity.js +58 -0
  86. package/dist/intelligence/infra-detectors.js +312 -0
  87. package/dist/intelligence/license.js +185 -0
  88. package/dist/intelligence/llm-spokesperson.js +62 -0
  89. package/dist/intelligence/mcp-prompts.js +320 -0
  90. package/dist/intelligence/mcp-resources.js +171 -0
  91. package/dist/intelligence/normalize.js +108 -0
  92. package/dist/intelligence/notifications.js +83 -0
  93. package/dist/intelligence/oidc-issuer.js +87 -0
  94. package/dist/intelligence/override-corpus.js +597 -0
  95. package/dist/intelligence/planning-gate.js +84 -0
  96. package/dist/intelligence/plans.js +223 -0
  97. package/dist/intelligence/platt-scaling.js +164 -0
  98. package/dist/intelligence/policy-proposals.js +84 -0
  99. package/dist/intelligence/policy-suggester.js +161 -0
  100. package/dist/intelligence/policy-sync.js +103 -0
  101. package/dist/intelligence/policy-test-runner.js +35 -0
  102. package/dist/intelligence/postmortem-parser.js +181 -0
  103. package/dist/intelligence/postmortem-retrieval.js +224 -0
  104. package/dist/intelligence/postmortem-store.js +596 -0
  105. package/dist/intelligence/pr-commenter.js +81 -0
  106. package/dist/intelligence/pr-policy-gate.js +38 -0
  107. package/dist/intelligence/pr-shadow-analyzer.js +229 -0
  108. package/dist/intelligence/process-tree.js +108 -0
  109. package/dist/intelligence/prompts.js +1 -0
  110. package/dist/intelligence/replay.js +38 -0
  111. package/dist/intelligence/repro-steps.js +1 -0
  112. package/dist/intelligence/resource-extractors.js +0 -0
  113. package/dist/intelligence/resource-file-context.js +138 -0
  114. package/dist/intelligence/risk-score.js +22 -0
  115. package/dist/intelligence/rollback.js +137 -0
  116. package/dist/intelligence/runbook-updater.js +143 -0
  117. package/dist/intelligence/sandbox.js +194 -0
  118. package/dist/intelligence/script-trust.js +104 -0
  119. package/dist/intelligence/session-metrics.js +67 -0
  120. package/dist/intelligence/session-threat-tracker.js +231 -0
  121. package/dist/intelligence/shadow-digest-cron.js +81 -0
  122. package/dist/intelligence/shadow-log.js +284 -0
  123. package/dist/intelligence/shell-ast.js +145 -0
  124. package/dist/intelligence/siem-forward.js +104 -0
  125. package/dist/intelligence/slack-digest.js +151 -0
  126. package/dist/intelligence/slack-override-loop.js +217 -0
  127. package/dist/intelligence/slack-routing.js +67 -0
  128. package/dist/intelligence/slack.js +900 -0
  129. package/dist/intelligence/sql-ast.js +31 -0
  130. package/dist/intelligence/team.js +1 -0
  131. package/dist/intelligence/telemetry.js +76 -0
  132. package/dist/intelligence/threshold-optimizer.js +75 -0
  133. package/dist/intelligence/token-budget.js +66 -0
  134. package/dist/intelligence/tool-guard.js +708 -0
  135. package/dist/intelligence/tool-manifest 2.js +74 -0
  136. package/dist/intelligence/tool-manifest.js +125 -0
  137. package/dist/intelligence/tools-analysis.js +857 -0
  138. package/dist/intelligence/tools-arch.js +152 -0
  139. package/dist/intelligence/tools-autonomy.js +495 -0
  140. package/dist/intelligence/tools-blast-radius.js +140 -0
  141. package/dist/intelligence/tools-browser.js +431 -0
  142. package/dist/intelligence/tools-change-timeline.js +154 -0
  143. package/dist/intelligence/tools-credentials.js +117 -0
  144. package/dist/intelligence/tools-datadog.js +233 -0
  145. package/dist/intelligence/tools-debug-sessions.js +209 -0
  146. package/dist/intelligence/tools-discovery.js +284 -0
  147. package/dist/intelligence/tools-infra.js +612 -0
  148. package/dist/intelligence/tools-intent.js +128 -0
  149. package/dist/intelligence/tools-memory.js +514 -0
  150. package/dist/intelligence/tools-runbook.js +951 -0
  151. package/dist/intelligence/tools-sessions.js +107 -0
  152. package/dist/intelligence/tools-state.js +232 -0
  153. package/dist/intelligence/tools-utility.js +430 -0
  154. package/dist/intelligence/tools-validate.js +215 -0
  155. package/dist/intelligence/tools.js +1 -0
  156. package/dist/intelligence/trace-context.js +11 -0
  157. package/dist/intelligence/unclassified-clusters.js +87 -0
  158. package/dist/intelligence/usage.js +195 -0
  159. package/dist/routes/active-authors.js +63 -0
  160. package/dist/routes/activity-feed.js +21 -0
  161. package/dist/routes/adr.js +117 -0
  162. package/dist/routes/agent-activity.js +184 -0
  163. package/dist/routes/agent-blunders.js +163 -0
  164. package/dist/routes/agents.js +175 -0
  165. package/dist/routes/api-keys.js +65 -0
  166. package/dist/routes/arch.js +75 -0
  167. package/dist/routes/audit-export.js +93 -0
  168. package/dist/routes/billing-dashboard.js +158 -0
  169. package/dist/routes/billing-outcome.js +77 -0
  170. package/dist/routes/calibration.js +305 -0
  171. package/dist/routes/ci-gate-history.js +20 -0
  172. package/dist/routes/ci-gate.js +232 -0
  173. package/dist/routes/ci.js +293 -0
  174. package/dist/routes/compliance-report.js +24 -0
  175. package/dist/routes/confidence.js +30 -0
  176. package/dist/routes/credentials.js +119 -0
  177. package/dist/routes/dashboard.js +1407 -0
  178. package/dist/routes/decision-memory.js +42 -0
  179. package/dist/routes/demo.js +844 -0
  180. package/dist/routes/device-auth-stub.js +173 -0
  181. package/dist/routes/explain-why.js +39 -0
  182. package/dist/routes/gate-analytics.js +213 -0
  183. package/dist/routes/gate.js +28 -0
  184. package/dist/routes/github-webhook.js +364 -0
  185. package/dist/routes/habituation.js +30 -0
  186. package/dist/routes/health-integrations.js +243 -0
  187. package/dist/routes/heartbeats.js +45 -0
  188. package/dist/routes/hitl.js +364 -0
  189. package/dist/routes/impact-report.js +645 -0
  190. package/dist/routes/incident-webhook.js +63 -0
  191. package/dist/routes/incidents.js +366 -0
  192. package/dist/routes/layers.js +62 -0
  193. package/dist/routes/license.js +187 -0
  194. package/dist/routes/oidc-issuer.js +30 -0
  195. package/dist/routes/onboarding.js +170 -0
  196. package/dist/routes/otel.js +67 -0
  197. package/dist/routes/otlp-receiver.js +179 -0
  198. package/dist/routes/overrides.js +241 -0
  199. package/dist/routes/pagerduty.js +258 -0
  200. package/dist/routes/policies.js +809 -0
  201. package/dist/routes/policy-nl.js +169 -0
  202. package/dist/routes/postmortem.js +102 -0
  203. package/dist/routes/pr-shadow.js +47 -0
  204. package/dist/routes/rbac.js +61 -0
  205. package/dist/routes/replay.js +17 -0
  206. package/dist/routes/risk-report.js +164 -0
  207. package/dist/routes/runbooks.js +125 -0
  208. package/dist/routes/safety-test.js +174 -0
  209. package/dist/routes/sdk.js +222 -0
  210. package/dist/routes/sensor.js +819 -0
  211. package/dist/routes/sentry.js +140 -0
  212. package/dist/routes/sessions.js +43 -0
  213. package/dist/routes/setup-ui.js +594 -0
  214. package/dist/routes/shadow-report.js +107 -0
  215. package/dist/routes/slack-routing.js +171 -0
  216. package/dist/routes/team-usage.js +71 -0
  217. package/dist/routes/telemetry.js +28 -0
  218. package/dist/routes/tenants.js +199 -0
  219. package/dist/routes/tickets.js +167 -0
  220. package/dist/routes/validate.js +13 -0
  221. package/dist/routes/war-room.js +113 -0
  222. package/dist/scripts/check-arch.js +22 -0
  223. package/dist/seeds/community-corpus.js +176 -0
  224. package/dist/seeds/corpus.js +1224 -0
  225. package/dist/sensor/action-ledger.js +327 -0
  226. package/dist/sensor/adr-store.js +140 -0
  227. package/dist/sensor/agent-blunder-store.js +370 -0
  228. package/dist/sensor/agent-context-store.js +225 -0
  229. package/dist/sensor/agent-memory-store.js +231 -0
  230. package/dist/sensor/audit-fetch.js +23 -0
  231. package/dist/sensor/audit-log.js +273 -0
  232. package/dist/sensor/buffer-schemas.js +225 -0
  233. package/dist/sensor/buffer.js +599 -0
  234. package/dist/sensor/bypass-tracker.js +93 -0
  235. package/dist/sensor/ci-gate-history.js +70 -0
  236. package/dist/sensor/cloud-auth.js +183 -0
  237. package/dist/sensor/commit-context-store.js +235 -0
  238. package/dist/sensor/docker-log-stream.js +164 -0
  239. package/dist/sensor/docker-monitor.js +122 -0
  240. package/dist/sensor/extended-buffer.js +46 -0
  241. package/dist/sensor/feedback-token.js +41 -0
  242. package/dist/sensor/file-lock.js +6 -0
  243. package/dist/sensor/fs-watcher.js +106 -0
  244. package/dist/sensor/gate-heartbeat.js +122 -0
  245. package/dist/sensor/git-suspect.js +136 -0
  246. package/dist/sensor/habituation-store.js +77 -0
  247. package/dist/sensor/heartbeat-monitor.js +133 -0
  248. package/dist/sensor/incident-store.js +340 -0
  249. package/dist/sensor/infra-normalizer.js +513 -0
  250. package/dist/sensor/ingest.js +339 -0
  251. package/dist/sensor/jest-reporter.js +52 -0
  252. package/dist/sensor/k8s-events.js +132 -0
  253. package/dist/sensor/layer2-store.js +111 -0
  254. package/dist/sensor/layer3-store.js +230 -0
  255. package/dist/sensor/layer4-store.js +147 -0
  256. package/dist/sensor/logger.js +13 -0
  257. package/dist/sensor/otel-exporter.js +161 -0
  258. package/dist/sensor/paths.js +71 -0
  259. package/dist/sensor/policy-history.js +147 -0
  260. package/dist/sensor/pr-shadow-store.js +83 -0
  261. package/dist/sensor/process-watcher.js +162 -0
  262. package/dist/sensor/rbac.js +92 -0
  263. package/dist/sensor/redact.js +114 -0
  264. package/dist/sensor/redis-store.js +191 -0
  265. package/dist/sensor/route-reachability.js +64 -0
  266. package/dist/sensor/security-utils.js +18 -0
  267. package/dist/sensor/service-graph.js +170 -0
  268. package/dist/sensor/service-topology.js +227 -0
  269. package/dist/sensor/session-history.js +74 -0
  270. package/dist/sensor/session-persist.js +38 -0
  271. package/dist/sensor/shadow-promote.js +122 -0
  272. package/dist/sensor/sourcemap.js +281 -0
  273. package/dist/sensor/sqlite-store.js +205 -0
  274. package/dist/sensor/sso.js +164 -0
  275. package/dist/sensor/vitest-reporter.js +65 -0
  276. package/dist/sensor/watcher.js +48 -0
  277. package/dist/storage/interfaces.js +0 -0
  278. package/dist/storage/pg/pg-action-ledger.js +138 -0
  279. package/dist/storage/pg/pg-approval-store.js +107 -0
  280. package/dist/storage/pg/pg-blunder-store.js +193 -0
  281. package/dist/storage/pg/pg-ci-gate-history.js +83 -0
  282. package/dist/storage/pg/pg-client.js +24 -0
  283. package/dist/storage/pg/pg-event-store.js +63 -0
  284. package/dist/storage/pg/pg-incident-store.js +190 -0
  285. package/dist/storage/pg/pg-migrations.js +26 -0
  286. package/dist/storage/pg/pg-override-corpus.js +447 -0
  287. package/dist/storage/pg/pg-shadow-log.js +129 -0
  288. package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
  289. package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
  290. package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
  291. package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
  292. package/dist/storage/sqlite/sqlite-event-store.js +29 -0
  293. package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
  294. package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
  295. package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
  296. package/dist/storage/store-factory.js +55 -0
  297. package/dist/storage/store-registry.js +31 -0
  298. package/dist/update-checker.js +107 -0
  299. package/dist/workers/autopilot-worker.js +28 -0
  300. package/dist/workers/notification-worker.js +28 -0
  301. package/dist/workers/queues.js +58 -0
  302. package/dist/workers/validation-worker.js +24 -0
  303. package/dist/workers/worker-registry.js +18 -0
  304. package/package.json +123 -0
  305. package/sdk/mergen-inject.js +260 -0
  306. package/sdk/node.js +313 -0
  307. package/sdk/vite-plugin.ts +57 -0
  308. package/sdk/webpack-plugin.js +106 -0
@@ -0,0 +1,622 @@
1
+ import { randomUUID } from "crypto";
2
+ import { store } from "../sensor/buffer.js";
3
+ import { getRunbookForTag } from "../routes/runbooks.js";
4
+ import { buildCausalChain, fixActionToCommand } from "./causal.js";
5
+ import { getRecords, recordVerdict, classifyVerdict, recordRemediationVerdict, isCorpusSeeded, getRealVerdictCount } from "./calibration.js";
6
+ import { executeRemediation, extractCommand } from "./autonomy.js";
7
+ import { postThreadReply, postApprovalRequest, fetchIncidentChannelContext, postSimpleWebhookNotification } from "./slack.js";
8
+ import { requestApproval } from "./execution-gate.js";
9
+ import { approvalEvents } from "./approval-events.js";
10
+ import { deriveRollback, executeRollback } from "./rollback.js";
11
+ import { captureSnapshot } from "./incident-replay.js";
12
+ import { computeBlastRadius } from "./blast-radius.js";
13
+ import { getExecutionThreshold } from "./threshold-optimizer.js";
14
+ import { isAutopilotEnabled, isShadowMode } from "./execution-mode.js";
15
+ import { incidentStore } from "../sensor/incident-store.js";
16
+ import { getActiveIncident } from "../datadog/incident-state.js";
17
+ import { fetchErrorCountSince, isConfigured as isDatadogConfigured } from "../datadog/client.js";
18
+ import { normalizeRuntimeFactMarkdown, normalizeProcessExits, normalizeSlackContext } from "../sensor/infra-normalizer.js";
19
+ import { getK8sEvents } from "../sensor/k8s-events.js";
20
+ import { dominantOverrideReason } from "./override-corpus.js";
21
+ import { findSimilarDecisions } from "./decision-similarity.js";
22
+ import { blendEscalation } from "./decision-escalation.js";
23
+ import { cacheIncidentResult, getCachedIncidentResult } from "./incident-result-cache.js";
24
+ import { getStores } from "../storage/store-registry.js";
25
+ import { getAutopilotLevel, classifyCommandRisk, autopilotLevelDescription } from "./action-risk.js";
26
+ import { evaluateEnterprisePolicy, loadEnterprisePolicy } from "./enterprise-policy-engine.js";
27
+ import { getStatsForTag } from "./calibration.js";
28
+ import { runAgentPipeline } from "./agent-pipeline.js";
29
+ import { detectCascade } from "./cascade-detector.js";
30
+ import { planningGate } from "./planning-gate.js";
31
+ import { plattScale } from "./platt-scaling.js";
32
+ import { formatValidatedFactsForLLM } from "./llm-spokesperson.js";
33
+ import { serviceGraph } from "../sensor/service-graph.js";
34
+ import { routeReachability } from "../sensor/route-reachability.js";
35
+ import { updateRunbookFromPostmortem } from "./runbook-updater.js";
36
+ import { generatePostmortem } from "./postmortem-store.js";
37
+ import logger from "../sensor/logger.js";
38
+ approvalEvents.on("approval:expired", (pid, text) => {
39
+ postThreadReply(pid, text).catch((err) => logger.error({ err, pid }, "approval:expired: slack reply failed"));
40
+ });
41
+ async function replyToThread(pid, text) {
42
+ const ok = await postThreadReply(pid, text);
43
+ if (!ok) logger.error({ pid }, "incident-autopilot: Slack reply failed \u2014 engineer may not see this update");
44
+ }
45
+ const ANALYSIS_TIMEOUT_MS = 3e4;
46
+ const DEDUP_TTL_MS = 30 * 60 * 1e3;
47
+ const _recentlyTriaged = /* @__PURE__ */ new Map();
48
+ const MAX_CONCURRENT_AUTOPILOT = 3;
49
+ let _activeCalls = 0;
50
+ setInterval(() => {
51
+ const cutoff = Date.now() - DEDUP_TTL_MS;
52
+ for (const [k, v] of _recentlyTriaged) if (v < cutoff) _recentlyTriaged.delete(k);
53
+ }, DEDUP_TTL_MS).unref();
54
+ const _stats = {
55
+ dedupHits: 0,
56
+ corpusFastPathHits: 0,
57
+ executedFastPathHits: 0,
58
+ concurrencyBlocked: 0,
59
+ tokenBudgetTruncations: 0,
60
+ concurrentPeak: 0
61
+ };
62
+ function getCostGuardStats() {
63
+ return { ..._stats, concurrentActive: _activeCalls };
64
+ }
65
+ function _resetTriagedForTesting() {
66
+ _recentlyTriaged.clear();
67
+ }
68
+ const getAutoExecuteThreshold = () => {
69
+ const base = getExecutionThreshold();
70
+ const provisional = isCorpusSeeded() || getRealVerdictCount() < 10;
71
+ return provisional ? Math.min(0.95, base + 0.05) : base;
72
+ };
73
+ const getBaseUrl = () => (process.env.MERGEN_PUBLIC_URL ?? process.env.MERGEN_BASE_URL ?? "http://127.0.0.1:3000").replace(/\/$/, "");
74
+ function buildVerdictLinks(entry) {
75
+ const base = getBaseUrl();
76
+ const approve = `${base}/shadow-report/${entry.id}/verdict?v=approve`;
77
+ const override = `${base}/shadow-report/${entry.id}/verdict?v=override&reason=on-call-discretion`;
78
+ return `
79
+ <${approve}|\u2705 Approve> \xB7 <${override}|\u270B Override>`;
80
+ }
81
+ const TELEMETRY_WAIT_MAX_MS = 1e4;
82
+ const TELEMETRY_POLL_INTERVAL = 250;
83
+ const TELEMETRY_MIN_EVENTS = 3;
84
+ async function waitForTelemetry(firedAt, tenantId) {
85
+ const deadline = Date.now() + TELEMETRY_WAIT_MAX_MS;
86
+ while (Date.now() < deadline) {
87
+ const logs = store.getLogs(TELEMETRY_MIN_EVENTS, void 0, firedAt, tenantId);
88
+ const net = store.getNetwork(TELEMETRY_MIN_EVENTS, void 0, firedAt, tenantId);
89
+ if (logs.length + net.length >= TELEMETRY_MIN_EVENTS) return;
90
+ await new Promise((r) => setTimeout(r, TELEMETRY_POLL_INTERVAL));
91
+ }
92
+ }
93
+ async function runIncidentAutopilot(opts) {
94
+ if (!isAutopilotEnabled() && !isShadowMode()) {
95
+ logger.debug({ service: opts.service }, "incident-autopilot: disabled (set MERGEN_AUTOPILOT=true or MERGEN_SHADOW_MODE=true)");
96
+ return;
97
+ }
98
+ const { service, pid, firedAt, cwd } = opts;
99
+ logger.info({ service, pid }, "incident-autopilot: starting");
100
+ if (!isShadowMode()) {
101
+ const lastTriaged = _recentlyTriaged.get(pid);
102
+ if (lastTriaged && Date.now() - lastTriaged < DEDUP_TTL_MS) {
103
+ _stats.dedupHits++;
104
+ logger.info({ service, pid }, "incident-autopilot: duplicate fingerprint within 30min \u2014 skipping re-analysis");
105
+ await replyToThread(pid, "_Mergen: same incident fingerprint already triaged within 30 min \u2014 skipping re-analysis._");
106
+ return;
107
+ }
108
+ _recentlyTriaged.set(pid, Date.now());
109
+ if (_recentlyTriaged.size > 200) {
110
+ const cutoff = Date.now() - DEDUP_TTL_MS;
111
+ for (const [k, v] of _recentlyTriaged) if (v < cutoff) _recentlyTriaged.delete(k);
112
+ }
113
+ }
114
+ if (!isShadowMode()) {
115
+ const cached = getCachedIncidentResult(pid);
116
+ if (cached?.corpusBlocked) {
117
+ _stats.corpusFastPathHits++;
118
+ logger.info({ service, pid, tag: cached.incidentTag }, "incident-autopilot: corpus fast-path \u2014 skipping LLM inference");
119
+ await replyToThread(
120
+ pid,
121
+ `\u26A0\uFE0F _Autopilot: corpus fast-path \u2014 incident pattern \`${cached.incidentTag}\` was previously blocked by the override corpus for \`${service}\`. Awaiting manual action._`
122
+ );
123
+ return;
124
+ }
125
+ if (cached?.executedCommand) {
126
+ _stats.executedFastPathHits++;
127
+ const agoMin = Math.round((Date.now() - cached.cachedAt) / 6e4);
128
+ logger.info({ service, pid, cmd: cached.executedCommand }, "incident-autopilot: executed fast-path \u2014 fix already applied");
129
+ await replyToThread(
130
+ pid,
131
+ `_Mergen: fix \`${cached.executedCommand}\` was applied for this incident ${agoMin} min ago \u2014 skipping re-analysis. If the issue persists, override the cache via POST /overrides._`
132
+ );
133
+ return;
134
+ }
135
+ }
136
+ if (_activeCalls >= MAX_CONCURRENT_AUTOPILOT) {
137
+ const deadline = Date.now() + 2 * 6e4;
138
+ while (_activeCalls >= MAX_CONCURRENT_AUTOPILOT && Date.now() < deadline) {
139
+ await new Promise((r) => setTimeout(r, 1e3));
140
+ }
141
+ if (_activeCalls >= MAX_CONCURRENT_AUTOPILOT) {
142
+ _stats.concurrencyBlocked++;
143
+ logger.warn({ service, pid, active: _activeCalls }, "incident-autopilot: concurrency cap reached \u2014 aborting");
144
+ await replyToThread(pid, "_Mergen: too many concurrent analyses in progress \u2014 retry in a few minutes or investigate manually._");
145
+ return;
146
+ }
147
+ }
148
+ _activeCalls++;
149
+ if (_activeCalls > _stats.concurrentPeak) _stats.concurrentPeak = _activeCalls;
150
+ const cascade = detectCascade(service, pid);
151
+ if (cascade.isCascade) {
152
+ await replyToThread(pid, cascade.summary);
153
+ }
154
+ return _runAutopilotCore(service, pid, firedAt, cwd, opts.tenantId).finally(() => {
155
+ _activeCalls--;
156
+ });
157
+ }
158
+ async function _runAutopilotCore(service, pid, firedAt, cwd, tenantId) {
159
+ await waitForTelemetry(firedAt, tenantId);
160
+ const logs = store.getLogs(200, void 0, firedAt, tenantId);
161
+ const network = store.getNetwork(200, void 0, firedAt, tenantId);
162
+ const contexts = store.getContext(20, firedAt, tenantId);
163
+ const terminal = store.getTerminalOutput(100, void 0, firedAt, tenantId);
164
+ const processExits = store.getProcessExits(20, void 0, firedAt, tenantId);
165
+ const ciEvents = store.getCIEvents(20, void 0, firedAt, tenantId);
166
+ const deployments = store.getDeployments(10, void 0, firedAt, tenantId);
167
+ const MAX_BUDGET_LOGS = 60;
168
+ const MAX_BUDGET_NETWORK = 60;
169
+ const errorLogs = logs.filter((e) => e.level === "error").slice(-MAX_BUDGET_LOGS);
170
+ const fillerLogs = logs.filter((e) => e.level !== "error").slice(-(MAX_BUDGET_LOGS - errorLogs.length));
171
+ const netFails = network.filter((n) => n.status >= 400 || !!n.error).slice(-MAX_BUDGET_NETWORK);
172
+ const netOk = network.filter((n) => n.status < 400 && !n.error).slice(-(MAX_BUDGET_NETWORK - netFails.length));
173
+ const budgetedLogs = [...fillerLogs, ...errorLogs];
174
+ const budgetedNetwork = [...netOk, ...netFails];
175
+ if (logs.length > MAX_BUDGET_LOGS || network.length > MAX_BUDGET_NETWORK) {
176
+ _stats.tokenBudgetTruncations++;
177
+ logger.info(
178
+ { service, pid, originalLogs: logs.length, budgetedLogs: budgetedLogs.length, originalNetwork: network.length, budgetedNetwork: budgetedNetwork.length },
179
+ "incident-autopilot: token budget truncated telemetry"
180
+ );
181
+ }
182
+ const errorCount = logs.filter((e) => e.level === "error").length;
183
+ const netErrors = network.filter((n) => n.status >= 400 || !!n.error).length;
184
+ const slackContextText = await fetchIncidentChannelContext(firedAt).catch(() => null);
185
+ const activeIncident = getActiveIncident();
186
+ const runtimeFactMarkdown = activeIncident?.runtimeFact ?? null;
187
+ const infraEvents = [
188
+ ...runtimeFactMarkdown ? normalizeRuntimeFactMarkdown(runtimeFactMarkdown, service, firedAt) : [],
189
+ ...normalizeProcessExits(processExits),
190
+ ...getK8sEvents(firedAt),
191
+ ...slackContextText ? normalizeSlackContext(slackContextText, service, firedAt) : []
192
+ ];
193
+ if (slackContextText) {
194
+ logger.info({ service, pid, chars: slackContextText.length }, "incident-autopilot: slack channel context added to evidence");
195
+ }
196
+ const hasAnySignal = errorCount > 0 || netErrors > 0 || infraEvents.length > 0;
197
+ if (!hasAnySignal) {
198
+ logger.info({ service, pid }, "incident-autopilot: no signals found \u2014 skipping");
199
+ await replyToThread(pid, "_Mergen autopilot: no errors or infra signals found in buffer \u2014 manual investigation required._");
200
+ return;
201
+ }
202
+ if (runtimeFactMarkdown) {
203
+ const ageMin = Math.round((Date.now() - firedAt) / 6e4);
204
+ await replyToThread(
205
+ pid,
206
+ [
207
+ `\u{1F4E1} *Mergen Autopilot \u2014 Incident Context* (${ageMin}m after alert)`,
208
+ "",
209
+ runtimeFactMarkdown
210
+ ].join("\n")
211
+ );
212
+ }
213
+ let causal = null;
214
+ try {
215
+ causal = await Promise.race([
216
+ buildCausalChain(budgetedLogs, budgetedNetwork, contexts, firedAt, terminal, processExits, ciEvents, deployments, infraEvents),
217
+ new Promise(
218
+ (_, reject) => setTimeout(() => reject(new Error("analysis timeout")), ANALYSIS_TIMEOUT_MS)
219
+ )
220
+ ]);
221
+ } catch (err) {
222
+ logger.warn({ err, service, pid }, "incident-autopilot: causal analysis failed \u2014 posting raw telemetry");
223
+ const topErrors = logs.filter((e) => e.level === "error").slice(0, 5);
224
+ const topNetFails = network.filter((n) => n.status >= 400 || !!n.error).slice(0, 5);
225
+ await replyToThread(pid, [
226
+ `\u26A1 *Mergen \u2014 Raw Telemetry Snapshot* (analysis unavailable)`,
227
+ `*${errorCount} console errors, ${netErrors} network failures* in window`,
228
+ topErrors.length > 0 ? `*Top errors:*
229
+ ${topErrors.map((e) => `\u2022 ${String(e.args?.[0] ?? "").slice(0, 120)}`).join("\n")}` : "",
230
+ topNetFails.length > 0 ? `*Network failures:*
231
+ ${topNetFails.map((n) => `\u2022 ${n.method} ${n.url} \u2192 ${n.status}`).join("\n")}` : "",
232
+ `_Manual investigation required \u2014 AI analysis unavailable._`
233
+ ].filter(Boolean).join("\n"));
234
+ return;
235
+ }
236
+ if (serviceGraph.size > 0) {
237
+ const callers = new Set(serviceGraph.getCallers(service));
238
+ const callees = new Set(serviceGraph.getCallees(service));
239
+ const graphNeighbours = /* @__PURE__ */ new Set([...callers, ...callees, service]);
240
+ for (const hyp of causal.hypotheses) {
241
+ const isUpstream = /upstream|cascade|spike|flood|overload/i.test(hyp.summary);
242
+ if (isUpstream && callers.size === 0) {
243
+ hyp.confidenceScore = Math.max(0, hyp.confidenceScore * 0.7);
244
+ } else if (isUpstream && callers.size > 0) {
245
+ hyp.confidenceScore = Math.min(1, hyp.confidenceScore * 1.1);
246
+ }
247
+ const isExternal = /database|db|cache|redis|postgres|mysql|mongo|timeout/i.test(hyp.summary);
248
+ if (isExternal && callees.size === 0) {
249
+ hyp.confidenceScore = Math.max(0, hyp.confidenceScore * 0.75);
250
+ }
251
+ const routeMatch = hyp.summary.match(/\/(api|v\d|webhooks|auth|admin|internal)[^\s"')>]*/i);
252
+ if (routeMatch && routeReachability.size > 0 && !routeReachability.isReachable(routeMatch[0])) {
253
+ hyp.confidenceScore = Math.max(0, hyp.confidenceScore * 0.7);
254
+ }
255
+ }
256
+ logger.debug(
257
+ { service, callers: callers.size, callees: callees.size, routes: routeReachability.size, hypotheses: causal.hypotheses.length },
258
+ "incident-autopilot: topology filter applied"
259
+ );
260
+ }
261
+ const plattAdjusted = /* @__PURE__ */ new Map();
262
+ for (const hyp of causal.hypotheses) {
263
+ const { calibrated, source } = plattScale(hyp.confidenceScore, hyp.tag);
264
+ if (source !== "raw") plattAdjusted.set(hyp, calibrated);
265
+ }
266
+ causal.hypotheses.sort((a, b) => {
267
+ const aScore = plattAdjusted.get(a) ?? a.confidenceScore;
268
+ const bScore = plattAdjusted.get(b) ?? b.confidenceScore;
269
+ return bScore - aScore;
270
+ });
271
+ const topHyp = causal.hypotheses[0];
272
+ captureSnapshot({
273
+ pid,
274
+ capturedAt: Date.now(),
275
+ firedAt,
276
+ logs,
277
+ network,
278
+ contexts,
279
+ terminal,
280
+ processExits,
281
+ ciEvents,
282
+ deployments,
283
+ infraEvents,
284
+ originalTag: topHyp?.tag ?? null,
285
+ originalConfidenceScore: topHyp?.confidenceScore ?? null,
286
+ originalFixHint: topHyp?.fixHint ?? null
287
+ });
288
+ if (topHyp && activeIncident?.implicatedFile && topHyp.fixHint) {
289
+ const loc = activeIncident.implicatedLine ? `${activeIncident.implicatedFile}:${activeIncident.implicatedLine}` : activeIncident.implicatedFile;
290
+ if (!topHyp.fixHint.includes(activeIncident.implicatedFile)) {
291
+ topHyp.fixHint = `${topHyp.fixHint}
292
+ Failing location: \`${loc}\``;
293
+ }
294
+ }
295
+ if (!topHyp) {
296
+ logger.info({ service, pid }, "incident-autopilot: no hypothesis generated");
297
+ await replyToThread(pid, `_Mergen autopilot: analyzed ${errorCount} console errors and ${netErrors} network errors \u2014 no actionable root cause identified._`);
298
+ return;
299
+ }
300
+ const rawScore = topHyp.confidenceScore ?? 0;
301
+ const calibratedScore = plattAdjusted.get(topHyp) ?? rawScore;
302
+ const pct = Math.round(calibratedScore * 100);
303
+ const calStats = getStatsForTag(topHyp.tag);
304
+ const calibrationLabel = calStats?.isEmpirical ? `calibrated \u2014 ${calStats.verdicts} verdicts` : "estimated \u2014 self-calibrates with use";
305
+ const now = /* @__PURE__ */ new Date();
306
+ const pipeline = runAgentPipeline(causal, {
307
+ service,
308
+ executionThreshold: getAutoExecuteThreshold(),
309
+ service_time: { dayOfWeek: now.getUTCDay(), hourOfDay: now.getUTCHours() }
310
+ });
311
+ const pipelineLine = isAutopilotEnabled() || isShadowMode() ? `*Pipeline:* ${pipeline.stages.map((s) => {
312
+ const icon = s.status === "pass" ? "\u2705" : s.status === "warn" ? "\u26A0\uFE0F" : "\u{1F6AB}";
313
+ return `${icon} ${s.name}`;
314
+ }).join(" \xB7 ")}` : "";
315
+ const preShadowId = isShadowMode() && topHyp.pid ? randomUUID() : void 0;
316
+ const preShadowVerdictLinks = preShadowId ? `
317
+ ${buildVerdictLinks({ id: preShadowId })}` : "";
318
+ const diagMsg = [
319
+ `\u{1F50D} *Mergen Autopilot \u2014 Root Cause Analysis*`,
320
+ `*Hypothesis:* ${topHyp.summary}`,
321
+ `*Confidence:* ${topHyp.confidence} (${pct}%) [${calibrationLabel}]`,
322
+ topHyp.causalPath.length > 0 ? `*Causal path:*
323
+ ${topHyp.causalPath.map((s, i) => `${i + 1}. ${s}`).join("\n")}` : "",
324
+ topHyp.evidence.length > 0 ? `*Evidence:*
325
+ ${topHyp.evidence.map((e) => `\u2022 ${e}`).join("\n")}` : "",
326
+ topHyp.fixHint ? `*Fix:* ${topHyp.fixHint}` : "",
327
+ pipelineLine
328
+ ].filter(Boolean).join("\n") + preShadowVerdictLinks;
329
+ await replyToThread(pid, diagMsg);
330
+ logger.info({ service, pid, confidence: pct, hypothesis: topHyp.tag }, "incident-autopilot: diagnosis posted");
331
+ const webhookMsg = `\u{1F4E2} *Triage complete* for *${service}*. Hypothesis: *${topHyp.summary}* (${pct}% confidence). View unified timeline in Cursor/Claude Code.`;
332
+ void postSimpleWebhookNotification(service, webhookMsg);
333
+ const approvedRunbook = getRunbookForTag(topHyp.tag);
334
+ const runbookCommand = approvedRunbook ? approvedRunbook.steps.join(" && ") : null;
335
+ if (approvedRunbook) {
336
+ logger.info({ id: approvedRunbook.id, name: approvedRunbook.name, tag: topHyp.tag }, "incident-autopilot: using pre-approved runbook");
337
+ }
338
+ const command = runbookCommand ?? pipeline.plan?.command ?? (topHyp.fixAction ? fixActionToCommand(topHyp.fixAction) : null) ?? (topHyp.fixHint ? extractCommand(topHyp.fixHint) : null);
339
+ const execConfidence = topHyp.remediationConfidence ?? topHyp.confidenceScore ?? 0;
340
+ const execPct = Math.round(execConfidence * 100);
341
+ const gate = planningGate(topHyp, service, getAutoExecuteThreshold());
342
+ const gateDenied = !gate.execute;
343
+ const runtimeFact = activeIncident?.runtimeFact ?? null;
344
+ const brief = formatValidatedFactsForLLM(topHyp, service, gate, errorCount, runtimeFact, calibratedScore);
345
+ logger.debug({ service, pid, tokens: brief.estimatedTokens }, "incident-autopilot: LLM brief assembled");
346
+ if (gate.adjustedConfidence !== execConfidence) {
347
+ logger.info(
348
+ { service, pid, raw: execPct, adjusted: Math.round(gate.adjustedConfidence * 100), blastRisk: gate.signals.blastRisk, classifier: gate.signals.classifierScore },
349
+ "incident-autopilot: planning gate adjusted confidence"
350
+ );
351
+ }
352
+ const corpusBlocked = pipeline.critique?.corpusConflict ?? false;
353
+ const levelBlocked = pipeline.critique?.levelConflict ?? false;
354
+ const pipelineBlocked = pipeline.verdict === "block";
355
+ const pipelineReview = pipeline.verdict === "review";
356
+ let escalationBlocked = false;
357
+ let escalation = null;
358
+ if (command) {
359
+ try {
360
+ const [historicalOverrides, historicalShadowEntries] = await Promise.all([
361
+ getStores().overrides.getAllOverrides(tenantId),
362
+ getStores().shadowLog.getShadowEntries(tenantId)
363
+ ]);
364
+ const similarity = findSimilarDecisions(command, service, historicalOverrides, historicalShadowEntries);
365
+ escalation = blendEscalation({ similarity, calibration: getStatsForTag(topHyp.tag) ?? null });
366
+ escalationBlocked = escalation.recommendation === "hold";
367
+ } catch (err) {
368
+ logger.warn({ err, service, pid }, "incident-autopilot: decision-escalation lookup failed");
369
+ }
370
+ }
371
+ if (!command || pipelineBlocked || corpusBlocked || escalationBlocked || isShadowMode() || gateDenied || !pipelineReview && execConfidence < getAutoExecuteThreshold()) {
372
+ let skipReason;
373
+ let slackReason;
374
+ if (isShadowMode()) {
375
+ skipReason = "autopilot-disabled";
376
+ slackReason = `shadow mode \u2014 would execute \`${command ?? "no command"}\` (remediation: ${execPct}%)`;
377
+ } else if (!command) {
378
+ skipReason = "no-command";
379
+ slackReason = "no executable command in fixHint";
380
+ } else if (pipelineBlocked) {
381
+ skipReason = pipeline.critique?.levelConflict ? "level-restricted" : pipeline.critique?.corpusConflict ? "override-corpus" : "pipeline-block";
382
+ slackReason = pipeline.blockReason ?? "Governance pipeline blocked execution";
383
+ } else if (corpusBlocked) {
384
+ skipReason = "override-corpus";
385
+ const corpusReason = dominantOverrideReason(topHyp.tag, service);
386
+ slackReason = `override corpus: this action has been overridden before for \`${service}\` (reason: ${corpusReason ?? "unknown"})`;
387
+ } else if (escalationBlocked) {
388
+ skipReason = "similar-incident-history";
389
+ slackReason = `historical decision memory: ${escalation?.reasons.join(" ") ?? "similar past actions and calibration confidence flagged this for review"}`;
390
+ } else if (gateDenied) {
391
+ skipReason = "planning-gate";
392
+ slackReason = `planning gate: ${gate.reason}`;
393
+ } else if (levelBlocked) {
394
+ skipReason = "level-restricted";
395
+ const commandTier2 = classifyCommandRisk(command);
396
+ const autopilotLevel = getAutopilotLevel();
397
+ slackReason = `autopilot level \`${autopilotLevel}\` permits ${autopilotLevelDescription(autopilotLevel)} \u2014 this command is \`${commandTier2}\` tier. Set MERGEN_AUTOPILOT_LEVEL=full to enable.`;
398
+ } else if (topHyp.remediationConfidence !== void 0 && topHyp.remediationConfidence < getAutoExecuteThreshold()) {
399
+ skipReason = "remediation-below-threshold";
400
+ slackReason = `remediation confidence ${execPct}% below 85% threshold (diagnosis: ${pct}%)`;
401
+ } else {
402
+ skipReason = "confidence-below-threshold";
403
+ slackReason = `confidence ${execPct}% below 85% threshold`;
404
+ }
405
+ let shadowEntry;
406
+ if (topHyp.pid) {
407
+ shadowEntry = await getStores().shadowLog.recordShadow({
408
+ ...preShadowId ? { id: preShadowId } : {},
409
+ pid: topHyp.pid,
410
+ incidentTag: topHyp.tag,
411
+ service,
412
+ command,
413
+ diagnosisConfidence: topHyp.confidenceScore ?? 0,
414
+ remediationConfidence: execConfidence,
415
+ wouldHaveExecuted: !!command && execConfidence >= getAutoExecuteThreshold() && !corpusBlocked && !escalationBlocked,
416
+ skipReason: skipReason === "pipeline-block" ? "confidence-below-threshold" : skipReason,
417
+ firedAt
418
+ }, tenantId);
419
+ }
420
+ logger.info({ service, pid, skipReason, diagPct: pct, execPct, pipelineVerdict: pipeline.verdict }, "incident-autopilot: skipping auto-execute");
421
+ if (skipReason === "override-corpus") {
422
+ await getStores().blunders.record({ blunderType: "override_corpus_block", command, blockReason: slackReason, service, tag: topHyp.tag, actor: "autopilot", pid: topHyp.pid ?? pid, confidenceScore: execConfidence });
423
+ cacheIncidentResult({ fingerprint: pid, service, incidentTag: topHyp.tag, corpusBlocked: true, blockReason: slackReason, executedCommand: null });
424
+ } else if (skipReason === "pipeline-block" || skipReason === "level-restricted") {
425
+ await getStores().blunders.record({ blunderType: "pipeline_block", command, blockReason: slackReason, service, tag: topHyp.tag, actor: "autopilot", pid: topHyp.pid ?? pid, confidenceScore: execConfidence });
426
+ } else if (skipReason === "planning-gate") {
427
+ await getStores().blunders.record({ blunderType: "planning_gate_block", command, blockReason: slackReason, service, tag: topHyp.tag, actor: "autopilot", pid: topHyp.pid ?? pid, confidenceScore: execConfidence });
428
+ }
429
+ const icon = isShadowMode() ? "\u{1F441}\uFE0F" : "\u26A0\uFE0F";
430
+ const verdictLinks = isShadowMode() && shadowEntry ? buildVerdictLinks(shadowEntry) : "";
431
+ await replyToThread(pid, `${icon} _Autopilot: ${slackReason}. Awaiting manual action._${verdictLinks}`);
432
+ return;
433
+ }
434
+ let hasHighRiskPolicy = false;
435
+ try {
436
+ const evalRes = evaluateEnterprisePolicy({
437
+ files: [],
438
+ commands: [command],
439
+ actor: "autopilot",
440
+ service
441
+ });
442
+ if (evalRes.triggeredRules.length > 0) {
443
+ const policyConfig = loadEnterprisePolicy();
444
+ const triggeredRulesWithRisk = policyConfig.rules.filter(
445
+ (r) => evalRes.triggeredRules.includes(r.id) && (r.riskTier === "medium" || r.riskTier === "high")
446
+ );
447
+ if (triggeredRulesWithRisk.length > 0) {
448
+ hasHighRiskPolicy = true;
449
+ logger.info({ triggeredRules: triggeredRulesWithRisk.map((r) => r.id) }, "incident-autopilot: command matched a medium/high risk policy rule");
450
+ }
451
+ }
452
+ } catch (err) {
453
+ logger.error({ err }, "incident-autopilot: failed to evaluate enterprise policy risk rules");
454
+ }
455
+ const commandTier = classifyCommandRisk(command);
456
+ const blastRadius = computeBlastRadius(command, { service });
457
+ if (hasHighRiskPolicy || commandTier !== "restart" && getAutopilotLevel() !== "full") {
458
+ logger.info({ service, pid, command, commandTier, blastScope: blastRadius.scope, hasHighRiskPolicy }, "incident-autopilot: routing through approval gate");
459
+ const approvalPosted = await postApprovalRequest(pid, command, commandTier, execConfidence, blastRadius);
460
+ if (!approvalPosted) {
461
+ logger.error({ service, pid, command }, "incident-autopilot: aborting approval gate \u2014 Slack notification failed, engineer cannot approve");
462
+ await getStores().blunders.record({ blunderType: "pipeline_block", command, blockReason: "Slack unavailable \u2014 approval block could not be delivered", service, tag: topHyp.tag, actor: "autopilot", pid: topHyp.pid ?? pid, confidenceScore: execConfidence });
463
+ return;
464
+ }
465
+ requestApproval({ pid, command, tier: commandTier, service, remediationConfidence: execConfidence, cwd, blastRadius });
466
+ void getStores().shadowLog.recordShadow({
467
+ pid: topHyp.pid ?? pid,
468
+ incidentTag: topHyp.tag,
469
+ service,
470
+ command,
471
+ diagnosisConfidence: topHyp.confidenceScore ?? 0,
472
+ remediationConfidence: execConfidence,
473
+ wouldHaveExecuted: true,
474
+ skipReason: "level-restricted",
475
+ firedAt
476
+ }, tenantId);
477
+ return;
478
+ }
479
+ logger.info({ service, pid, command }, "incident-autopilot: executing fix");
480
+ const execResult = await executeRemediation(command, { cwd, actor: "autopilot" });
481
+ if (execResult.blocked) {
482
+ logger.warn({ service, pid, reason: execResult.blockReason }, "incident-autopilot: fix blocked by safety filter");
483
+ await replyToThread(pid, `\u{1F6AB} *Fix blocked by safety filter*: ${execResult.blockReason}
484
+ Apply manually.`);
485
+ const execBlunderType = typeof execResult.blockReason === "string" && /inject/i.test(execResult.blockReason) ? "injection_attempt" : "allowlist_block";
486
+ await getStores().blunders.record({ blunderType: execBlunderType, command, blockReason: execResult.blockReason ?? "", service, tag: topHyp.tag, actor: "autopilot", pid: topHyp.pid ?? pid, confidenceScore: execConfidence });
487
+ void getStores().shadowLog.recordShadow({
488
+ pid: topHyp.pid ?? pid,
489
+ incidentTag: topHyp.tag,
490
+ service,
491
+ command,
492
+ diagnosisConfidence: topHyp.confidenceScore ?? 0,
493
+ remediationConfidence: execConfidence,
494
+ wouldHaveExecuted: true,
495
+ skipReason: "blocked-by-safety-filter",
496
+ firedAt
497
+ }, tenantId);
498
+ return;
499
+ }
500
+ if (!execResult.ok) {
501
+ logger.warn({ service, pid, exitCode: execResult.exitCode }, "incident-autopilot: fix command failed");
502
+ await replyToThread(pid, `\u274C *Fix command failed* (exit ${execResult.exitCode})
503
+ ${execResult.stderr.slice(0, 500)}`);
504
+ void getStores().shadowLog.recordShadow({
505
+ pid: topHyp.pid ?? pid,
506
+ incidentTag: topHyp.tag,
507
+ service,
508
+ command,
509
+ diagnosisConfidence: topHyp.confidenceScore ?? 0,
510
+ remediationConfidence: execConfidence,
511
+ wouldHaveExecuted: true,
512
+ skipReason: "executed-failure",
513
+ firedAt
514
+ }, tenantId);
515
+ return;
516
+ }
517
+ const fixAppliedAt = Date.now();
518
+ await replyToThread(pid, `\u2699\uFE0F \`${command}\` executed (${execResult.durationMs}ms) \u2014 validating\u2026`);
519
+ void getStores().shadowLog.recordShadow({
520
+ pid: topHyp.pid ?? pid,
521
+ incidentTag: topHyp.tag,
522
+ service,
523
+ command,
524
+ diagnosisConfidence: topHyp.confidenceScore ?? 0,
525
+ remediationConfidence: execConfidence,
526
+ wouldHaveExecuted: true,
527
+ skipReason: "executed",
528
+ firedAt,
529
+ ...approvedRunbook ? { runbookId: approvedRunbook.id } : {}
530
+ }, tenantId);
531
+ cacheIncidentResult({ fingerprint: pid, service, incidentTag: topHyp.tag, corpusBlocked: false, blockReason: null, executedCommand: command });
532
+ await new Promise((r) => setTimeout(r, 5e3));
533
+ let afterCount;
534
+ const beforeCount = errorCount + netErrors;
535
+ const ddErrorCount = isDatadogConfigured() ? await fetchErrorCountSince(service, 2) : null;
536
+ if (ddErrorCount !== null) {
537
+ afterCount = ddErrorCount;
538
+ logger.info({ service, pid, ddErrorCount }, "incident-autopilot: validation via Datadog");
539
+ } else {
540
+ const logsAfter = store.getLogs(200, "error", fixAppliedAt, tenantId);
541
+ const netAfter = store.getNetwork(200, void 0, fixAppliedAt, tenantId).filter((n) => n.status >= 400 || !!n.error);
542
+ afterCount = logsAfter.length + netAfter.length;
543
+ }
544
+ const verdict = classifyVerdict(beforeCount, afterCount);
545
+ if (topHyp.pid) {
546
+ const existing = getRecords().find((r) => r.pid === topHyp.pid);
547
+ if (existing && !existing.verdict) recordVerdict(topHyp.pid, verdict);
548
+ recordRemediationVerdict(topHyp.pid, verdict);
549
+ const resolvedAt = Date.now();
550
+ if (verdict === "correct") {
551
+ incidentStore.upsert(topHyp.pid, {
552
+ status: "resolved",
553
+ resolvedAt,
554
+ resolvedAutonomously: true,
555
+ causallyCorrect: true
556
+ // error rate dropped AND diagnosis confirmed
557
+ });
558
+ } else {
559
+ incidentStore.upsert(topHyp.pid, {
560
+ status: "resolved",
561
+ resolvedAt,
562
+ resolvedAutonomously: true,
563
+ causallyCorrect: false
564
+ });
565
+ }
566
+ try {
567
+ const mttrMs = resolvedAt - firedAt;
568
+ const pm = generatePostmortem({
569
+ pid: topHyp.pid,
570
+ tag: topHyp.tag,
571
+ service,
572
+ rootCause: topHyp.summary,
573
+ fixCommand: command,
574
+ confidence: topHyp.confidenceScore ?? 0,
575
+ mttrMs,
576
+ resolvedAutonomously: true,
577
+ causallyCorrect: verdict === "correct",
578
+ evidence: topHyp.evidence,
579
+ fixHint: topHyp.fixHint ?? null
580
+ });
581
+ setImmediate(() => updateRunbookFromPostmortem(pm));
582
+ } catch (err) {
583
+ logger.warn({ err, pid: topHyp.pid }, "incident-autopilot: postmortem generation failed");
584
+ }
585
+ }
586
+ const statusLabel = afterCount === 0 ? "RESOLVED" : afterCount < beforeCount ? "PARTIAL" : afterCount > beforeCount ? "REGRESSED" : "UNRESOLVED";
587
+ const statusIcon = afterCount === 0 ? "\u2705" : afterCount < beforeCount ? "\u26A0\uFE0F" : "\u274C";
588
+ await replyToThread(
589
+ pid,
590
+ `${statusIcon} *${statusLabel}* \u2014 ${afterCount} errors after fix (was ${beforeCount})`
591
+ );
592
+ if (statusLabel === "REGRESSED") {
593
+ const rollback = deriveRollback(command, execResult.stdout);
594
+ if (rollback.type === "command") {
595
+ await replyToThread(pid, `\u21A9\uFE0F _REGRESSED detected \u2014 attempting auto-rollback\u2026_`);
596
+ const rb = await executeRollback(rollback, { cwd, actor: "autopilot-rollback" });
597
+ await replyToThread(
598
+ pid,
599
+ rb.ok ? `\u21A9\uFE0F *Rollback succeeded:* \`${rb.message}\`` : `\u{1F534} *Rollback failed:* ${rb.message} \u2014 manual intervention required`
600
+ );
601
+ } else {
602
+ await replyToThread(pid, `\u26A0\uFE0F _Auto-rollback not available: ${rollback.reason}. Manual revert required._`);
603
+ }
604
+ }
605
+ logger.info({ service, pid, statusLabel, afterCount, beforeCount }, "incident-autopilot: complete");
606
+ }
607
+ async function enqueueAutopilot(opts) {
608
+ const useBullMQ = (process.env.MERGEN_CLOUD_MODE === "true" || process.env.MERGEN_BULLMQ === "true") && process.env.MERGEN_REDIS_URL;
609
+ if (useBullMQ) {
610
+ const { getAutopilotQueue } = await import("../workers/queues.js");
611
+ await getAutopilotQueue().add("triage-incident", opts, { jobId: opts.pid });
612
+ } else {
613
+ void runIncidentAutopilot(opts);
614
+ }
615
+ }
616
+ export {
617
+ _resetTriagedForTesting,
618
+ enqueueAutopilot,
619
+ getCostGuardStats,
620
+ runIncidentAutopilot,
621
+ runIncidentAutopilot as runIncidentAutopilotLocal
622
+ };