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,601 @@
1
+ import { recordPrediction, applyCalibration } from "./calibration.js";
2
+ const SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
3
+ "authorization",
4
+ "cookie",
5
+ "set-cookie",
6
+ "x-auth-token",
7
+ "x-api-key"
8
+ ]);
9
+ const NETWORK_WINDOW_MS = 3e4;
10
+ const STATE_WINDOW_MS = 5e3;
11
+ const SLOW_THRESHOLD_MS = 1e3;
12
+ function parseFrame(stack) {
13
+ const m = stack.match(/at\s+\S+\s+\((.+):(\d+):(\d+)\)/);
14
+ return m ? { file: m[1], line: Number(m[2]), col: Number(m[3]) } : null;
15
+ }
16
+ function redactHeaders(h) {
17
+ const out = {};
18
+ for (const [k, v] of Object.entries(h)) {
19
+ out[k] = SENSITIVE_HEADERS.has(k.toLowerCase()) ? "[REDACTED]" : v;
20
+ }
21
+ return out;
22
+ }
23
+ function isEmptyBody(body) {
24
+ if (body == null) return false;
25
+ if (Array.isArray(body)) return body.length === 0;
26
+ if (typeof body === "object") return Object.keys(body).length === 0;
27
+ if (typeof body === "string") return body === "" || body === "[]" || body === "{}";
28
+ return false;
29
+ }
30
+ function buildContextPack(errors, correlated, state) {
31
+ const lines = [];
32
+ if (errors.length === 0) {
33
+ lines.push("No console errors");
34
+ } else {
35
+ lines.push("## Console Errors");
36
+ for (const e of errors) lines.push(`- ${e.message}`);
37
+ }
38
+ const failed = correlated.filter((n) => n.status >= 400 || n.status === 0 || n.error);
39
+ if (failed.length > 0) {
40
+ lines.push("\n## Network Failures");
41
+ for (const n of failed) {
42
+ let errPart = "";
43
+ if (n.error) {
44
+ const isNet = n.error.includes("NET_ERR") || n.error.includes("net::");
45
+ errPart = ` (${isNet ? "NET_ERR: " + n.error : n.error})`;
46
+ }
47
+ lines.push(`- ${n.method} ${n.url} \u2192 ${n.status}${errPart}`);
48
+ if (n.requestBody != null) lines.push(` Request: ${JSON.stringify(n.requestBody)}`);
49
+ if (n.responseBody != null) {
50
+ const rb = typeof n.responseBody === "string" ? n.responseBody : JSON.stringify(n.responseBody);
51
+ lines.push(` Response: ${rb}`);
52
+ }
53
+ if (n.requestHeaders && Object.keys(n.requestHeaders).length > 0) {
54
+ lines.push(` Request-Headers: ${JSON.stringify(redactHeaders(n.requestHeaders))}`);
55
+ }
56
+ if (n.responseHeaders && Object.keys(n.responseHeaders).length > 0) {
57
+ lines.push(` Response-Headers: ${JSON.stringify(n.responseHeaders)}`);
58
+ }
59
+ }
60
+ }
61
+ lines.push("\n## Invisible State");
62
+ if (!state) {
63
+ lines.push("No storage snapshot");
64
+ } else {
65
+ const ls = state.localStorage ?? {};
66
+ if (Object.keys(ls).length === 0) {
67
+ lines.push("localStorage: (empty)");
68
+ } else {
69
+ lines.push("localStorage:");
70
+ for (const [k, v] of Object.entries(ls)) {
71
+ const flag = v === "null" || v === "" ? " *NULL/EMPTY*" : "";
72
+ lines.push(` ${k}: ${v}${flag}`);
73
+ }
74
+ }
75
+ }
76
+ return lines.join("\n");
77
+ }
78
+ function detectHypotheses(errors, allNetwork, correlated, state, terminal, processExits, ciEvents, deployments) {
79
+ const hyps = [];
80
+ const ls = state?.localStorage ?? {};
81
+ const errorText = errors.map((e) => e.message).join(" ").toLowerCase();
82
+ if (errors.length > 0) {
83
+ const hasNullToken = Object.entries(ls).some(
84
+ ([k, v]) => /token|session/i.test(k) && (v === "null" || v === "")
85
+ );
86
+ const hasLoginSuccess = correlated.some(
87
+ (n) => /login|auth/i.test(n.url) && n.status >= 200 && n.status < 300
88
+ );
89
+ if (hasNullToken && hasLoginSuccess && /token|null|read|undefined/i.test(errorText)) {
90
+ hyps.push({
91
+ tag: "auth_token_not_persisted",
92
+ summary: "Token not persisted to localStorage after successful login \u2014 reads null on next access",
93
+ confidence: "HIGH",
94
+ confidenceScore: 0.88,
95
+ causalPath: ["login request succeeded", "token not written to localStorage", "next read returns null"],
96
+ evidence: ["token: null in localStorage", "successful login request", errorText.slice(0, 80)],
97
+ fixHint: "Ensure the token is written to localStorage immediately after the login response.",
98
+ fixAction: null,
99
+ remediationConfidence: 0.7
100
+ });
101
+ }
102
+ }
103
+ for (const n of allNetwork) {
104
+ if (n.status >= 200 && n.status < 300 && n.duration > SLOW_THRESHOLD_MS && !hyps.find((h) => h.tag === "slow_api_silent")) {
105
+ hyps.push({
106
+ tag: "slow_api_silent",
107
+ summary: `${n.url} took ${n.duration}ms with no error \u2014 silent performance regression`,
108
+ confidence: "MEDIUM",
109
+ confidenceScore: 0.65,
110
+ causalPath: ["slow 2xx", "no error thrown", "UI blocked silently"],
111
+ evidence: [`${n.method} ${n.url} duration=${n.duration}ms status=${n.status}`],
112
+ fixHint: "Check server-side query performance or add a timeout with user feedback.",
113
+ fixAction: null,
114
+ remediationConfidence: 0.5
115
+ });
116
+ }
117
+ if (n.status >= 200 && n.status < 300 && isEmptyBody(n.responseBody) && !hyps.find((h) => h.tag === "empty_response_silent")) {
118
+ hyps.push({
119
+ tag: "empty_response_silent",
120
+ summary: `${n.url} returned 200 with empty body \u2014 possible silent data loss`,
121
+ confidence: "MEDIUM",
122
+ confidenceScore: 0.6,
123
+ causalPath: ["200 with empty body", "component renders nothing", "blank UI"],
124
+ evidence: [`${n.method} ${n.url} status=200 responseBody=empty`],
125
+ fixHint: "Verify the API query conditions are not overly restrictive.",
126
+ fixAction: null,
127
+ remediationConfidence: 0.4
128
+ });
129
+ }
130
+ }
131
+ if (!hyps.find((h) => h.tag === "disk_full")) {
132
+ const diskFullMsg = errors.find(
133
+ (e) => /ENOSPC|No space left on device/i.test(e.message)
134
+ );
135
+ if (diskFullMsg) {
136
+ hyps.push({
137
+ tag: "disk_full",
138
+ summary: "Disk full (ENOSPC) \u2014 process cannot write; likely log or temp file accumulation",
139
+ confidence: "HIGH",
140
+ confidenceScore: 0.94,
141
+ causalPath: ["disk write attempted", "ENOSPC returned", "process cannot continue"],
142
+ evidence: [diskFullMsg.message.slice(0, 120)],
143
+ fixHint: "Free disk space \u2014 check for log accumulation or leaked temp files: `df -h && du -sh /var/log/*`",
144
+ fixAction: null,
145
+ remediationConfidence: 0.8
146
+ });
147
+ }
148
+ }
149
+ if (!hyps.find((h) => h.tag === "missing_env_var")) {
150
+ const envMsg = errors.find((e) => {
151
+ const m = e.message;
152
+ return /(process\.env|undefined.*env|env.*undefined)/i.test(m) || m.includes("Cannot read properties of undefined") && !/auth|token/i.test(m);
153
+ });
154
+ if (envMsg) {
155
+ hyps.push({
156
+ tag: "missing_env_var",
157
+ summary: "Missing environment variable \u2014 accessing undefined config at runtime",
158
+ confidence: "HIGH",
159
+ confidenceScore: 0.89,
160
+ causalPath: ["env var not set", "process.env lookup returns undefined", "downstream code throws"],
161
+ evidence: [envMsg.message.slice(0, 120)],
162
+ fixHint: "Check for missing environment variables: `printenv | sort` and verify all required vars are set.",
163
+ fixAction: null,
164
+ remediationConfidence: 0.75
165
+ });
166
+ }
167
+ }
168
+ if (!hyps.find((h) => h.tag === "unhandled_promise_rejection")) {
169
+ const promiseMsg = errors.find(
170
+ (e) => /UnhandledPromiseRejection|Unhandled promise rejection/i.test(e.message)
171
+ );
172
+ if (promiseMsg) {
173
+ hyps.push({
174
+ tag: "unhandled_promise_rejection",
175
+ summary: "Unhandled promise rejection \u2014 async error escaped without a .catch() handler",
176
+ confidence: "HIGH",
177
+ confidenceScore: 0.91,
178
+ causalPath: ["async operation threw", "no .catch() handler", "process emits UnhandledPromiseRejection"],
179
+ evidence: [promiseMsg.message.slice(0, 120)],
180
+ fixHint: "Add a .catch() handler or try/catch to the async operation flagged in the stack trace.",
181
+ fixAction: null,
182
+ remediationConfidence: 0.8
183
+ });
184
+ }
185
+ }
186
+ if (!hyps.find((h) => h.tag === "connection_refused")) {
187
+ const connMsg = errors.find(
188
+ (e) => /ECONNREFUSED|connection refused/i.test(e.message)
189
+ );
190
+ if (connMsg) {
191
+ hyps.push({
192
+ tag: "connection_refused",
193
+ summary: "Connection refused \u2014 target service is not running or not accepting connections",
194
+ confidence: "HIGH",
195
+ confidenceScore: 0.87,
196
+ causalPath: ["client attempts TCP connect", "kernel returns ECONNREFUSED", "request fails immediately"],
197
+ evidence: [connMsg.message.slice(0, 120)],
198
+ fixHint: "Target service is not running or not accepting connections. Check `lsof -i :<port>` or restart the service.",
199
+ fixAction: null,
200
+ remediationConfidence: 0.75
201
+ });
202
+ }
203
+ }
204
+ if (!hyps.find((h) => h.tag === "rate_limit_silent")) {
205
+ const rateLimitNet = allNetwork.find((n) => n.status === 429);
206
+ if (rateLimitNet) {
207
+ const hasNearbyError = errors.some(
208
+ (e) => Math.abs(e.ts - rateLimitNet.timestamp) <= 5e3
209
+ );
210
+ if (!hasNearbyError) {
211
+ hyps.push({
212
+ tag: "rate_limit_silent",
213
+ summary: `${rateLimitNet.url} returned 429 with no console error \u2014 rate limit hit silently`,
214
+ confidence: "MEDIUM",
215
+ confidenceScore: 0.82,
216
+ causalPath: ["request sent", "server returns 429", "client swallows error silently"],
217
+ evidence: [`${rateLimitNet.method} ${rateLimitNet.url} status=429`],
218
+ fixHint: "Implement exponential backoff on 429 responses and surface the rate limit to the user.",
219
+ fixAction: null,
220
+ remediationConfidence: 0.65
221
+ });
222
+ }
223
+ }
224
+ }
225
+ if (!hyps.find((h) => h.tag === "cors_preflight_failure")) {
226
+ const corsFail = allNetwork.find(
227
+ (n) => n.method === "OPTIONS" && n.status >= 400 || n.status === 0 && /CORS|blocked by CORS/i.test(n.error ?? "")
228
+ );
229
+ if (corsFail) {
230
+ hyps.push({
231
+ tag: "cors_preflight_failure",
232
+ summary: "CORS preflight failed \u2014 browser blocked the cross-origin request",
233
+ confidence: "HIGH",
234
+ confidenceScore: 0.86,
235
+ causalPath: ["browser sends OPTIONS preflight", "server rejects or missing CORS headers", "actual request blocked"],
236
+ evidence: [`${corsFail.method} ${corsFail.url} status=${corsFail.status}${corsFail.error ? " error=" + corsFail.error : ""}`],
237
+ fixHint: "Add CORS headers for the preflight origin: `Access-Control-Allow-Origin` and `Access-Control-Allow-Methods`.",
238
+ fixAction: null,
239
+ remediationConfidence: 0.8
240
+ });
241
+ }
242
+ }
243
+ if (!hyps.find((h) => h.tag === "jwt_expiry")) {
244
+ const unauthorizedRequests = allNetwork.filter((n) => n.status === 401);
245
+ if (unauthorizedRequests.length > 0) {
246
+ const failedHosts = new Set(
247
+ unauthorizedRequests.map((n) => {
248
+ try {
249
+ return new URL(n.url).host;
250
+ } catch {
251
+ return n.url;
252
+ }
253
+ })
254
+ );
255
+ const hadPriorSuccess = allNetwork.some((n) => {
256
+ if (n.status < 200 || n.status >= 300) return false;
257
+ try {
258
+ return failedHosts.has(new URL(n.url).host) && unauthorizedRequests.some((u) => n.timestamp < u.timestamp);
259
+ } catch {
260
+ return false;
261
+ }
262
+ });
263
+ if (hadPriorSuccess) {
264
+ const sample = unauthorizedRequests[0];
265
+ hyps.push({
266
+ tag: "jwt_expiry",
267
+ summary: "Session token expired mid-use \u2014 prior requests succeeded but now returning 401",
268
+ confidence: "HIGH",
269
+ confidenceScore: 0.88,
270
+ causalPath: ["requests succeeded with valid token", "token TTL elapsed", "subsequent requests return 401"],
271
+ evidence: [`${sample.method} ${sample.url} status=401`, "prior successful requests to same host"],
272
+ fixHint: "Token has expired. Implement refresh-token rotation or extend the JWT TTL.",
273
+ fixAction: null,
274
+ remediationConfidence: 0.75
275
+ });
276
+ }
277
+ }
278
+ }
279
+ if (!hyps.find((h) => h.tag === "n_plus_one_query")) {
280
+ if (allNetwork.length > 8) {
281
+ const sorted = [...allNetwork].sort((a, b) => a.timestamp - b.timestamp);
282
+ for (let i = 0; i < sorted.length; i++) {
283
+ const windowStart = sorted[i].timestamp;
284
+ const windowEvents = sorted.filter(
285
+ (n) => n.timestamp >= windowStart && n.timestamp <= windowStart + 3e3
286
+ );
287
+ if (windowEvents.length > 8) {
288
+ const pathPrefixes = windowEvents.map((n) => {
289
+ try {
290
+ const u = new URL(n.url);
291
+ return u.pathname.replace(/\/[^/]+$/, "");
292
+ } catch {
293
+ return n.url.replace(/\/[^/]+$/, "");
294
+ }
295
+ });
296
+ const prefixCounts = pathPrefixes.reduce((acc, p) => {
297
+ acc[p] = (acc[p] ?? 0) + 1;
298
+ return acc;
299
+ }, {});
300
+ const topPrefix = Object.entries(prefixCounts).sort((a, b) => b[1] - a[1])[0];
301
+ if (topPrefix && topPrefix[1] > 8) {
302
+ hyps.push({
303
+ tag: "n_plus_one_query",
304
+ summary: `N+1 query pattern: ${topPrefix[1]} requests to ${topPrefix[0]}/... within 3 seconds`,
305
+ confidence: "MEDIUM",
306
+ confidenceScore: 0.72,
307
+ causalPath: ["per-item fetch in loop", "N separate HTTP requests fired", "latency multiplies with list size"],
308
+ evidence: [`${topPrefix[1]} requests matching ${topPrefix[0]}/* within 3s`],
309
+ fixHint: "Replace per-item fetches with a batch endpoint or join at the data layer.",
310
+ fixAction: null,
311
+ remediationConfidence: 0.6
312
+ });
313
+ break;
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ if (!hyps.find((h) => h.tag === "health_check_degraded")) {
320
+ const healthFail = allNetwork.find(
321
+ (n) => /\/health|\/ping|\/ready|\/live/i.test(n.url) && (n.status >= 400 || n.duration > 2e3)
322
+ );
323
+ if (healthFail) {
324
+ hyps.push({
325
+ tag: "health_check_degraded",
326
+ summary: `Health endpoint degraded \u2014 ${healthFail.url} returned ${healthFail.status} or took ${healthFail.duration}ms`,
327
+ confidence: "MEDIUM",
328
+ confidenceScore: 0.79,
329
+ causalPath: ["health check called", "dependent service (DB/cache) slow or down", "health endpoint returns non-2xx or times out"],
330
+ evidence: [`${healthFail.method} ${healthFail.url} status=${healthFail.status} duration=${healthFail.duration}ms`],
331
+ fixHint: "Health endpoint is degraded. Check dependent services (DB, cache) and resource limits.",
332
+ fixAction: null,
333
+ remediationConfidence: 0.6
334
+ });
335
+ }
336
+ }
337
+ if (!hyps.find((h) => h.tag === "memory_leak_oom")) {
338
+ const oomExit = processExits.find((p) => p.reason === "oom");
339
+ if (oomExit) {
340
+ hyps.push({
341
+ tag: "memory_leak_oom",
342
+ summary: `Process killed by OOM killer (${oomExit.process}) \u2014 heap exceeded available memory`,
343
+ confidence: "HIGH",
344
+ confidenceScore: 0.93,
345
+ causalPath: ["heap grows unbounded", "OS OOM killer fires", "process exits with reason=oom"],
346
+ evidence: [`process exit reason=oom process=${oomExit.process}`],
347
+ fixHint: "Process killed by OOM. Profile heap with `node --inspect` or add `--max-old-space-size` as a stopgap.",
348
+ fixAction: null,
349
+ remediationConfidence: 0.7
350
+ });
351
+ }
352
+ }
353
+ if (!hyps.find((h) => h.tag === "deployment_induced_regression")) {
354
+ const TEN_MINUTES = 10 * 60 * 1e3;
355
+ const recentDeploy = deployments.find((d) => {
356
+ if (d.status !== "success") return false;
357
+ const deployTs = d.timestamp ?? 0;
358
+ return errors.some((e) => e.ts >= deployTs && e.ts <= deployTs + TEN_MINUTES);
359
+ });
360
+ if (recentDeploy) {
361
+ hyps.push({
362
+ tag: "deployment_induced_regression",
363
+ summary: `Errors appeared within 10 minutes of a successful deployment \u2014 likely regression introduced by the deploy`,
364
+ confidence: "HIGH",
365
+ confidenceScore: 0.88,
366
+ causalPath: ["deployment succeeded", "new code activated", "errors emerge within 10 minutes"],
367
+ evidence: [
368
+ `deployment status=success at ${new Date(recentDeploy.timestamp ?? 0).toISOString()}`,
369
+ `first error at ${new Date(errors[0]?.ts ?? 0).toISOString()}`
370
+ ],
371
+ fixHint: "Rollback to the previous deployment: `kubectl rollout undo deployment/<name>` or `git revert HEAD && deploy`.",
372
+ fixAction: null,
373
+ remediationConfidence: 0.75
374
+ });
375
+ }
376
+ }
377
+ if (!hyps.find((h) => h.tag === "db_migration_lock")) {
378
+ const THIRTY_MINUTES = 30 * 60 * 1e3;
379
+ const now = Date.now();
380
+ const recentDeploy = deployments.find(
381
+ (d) => now - (d.timestamp ?? 0) <= THIRTY_MINUTES
382
+ );
383
+ const dbTimeouts = allNetwork.filter(
384
+ (n) => n.duration > 5e3 && /db|database|postgres|mysql|mongo|redis/i.test(n.url)
385
+ );
386
+ if (recentDeploy && dbTimeouts.length > 0) {
387
+ hyps.push({
388
+ tag: "db_migration_lock",
389
+ summary: "DB timeouts after recent deployment \u2014 migration may have locked a table",
390
+ confidence: "MEDIUM",
391
+ confidenceScore: 0.74,
392
+ causalPath: ["deployment ran migrations", "migration holds table lock", "subsequent DB queries timeout"],
393
+ evidence: [
394
+ `${dbTimeouts.length} DB request(s) with duration >5s`,
395
+ `recent deployment within 30 minutes`
396
+ ],
397
+ fixHint: "Check for locked tables: `SHOW PROCESSLIST` (MySQL) or `SELECT * FROM pg_stat_activity` (Postgres). Kill blocking queries.",
398
+ fixAction: null,
399
+ remediationConfidence: 0.6
400
+ });
401
+ }
402
+ }
403
+ if (!hyps.find((h) => h.tag === "stale_cache")) {
404
+ const has304 = allNetwork.some((n) => n.status === 304);
405
+ if (has304 && errors.length > 0) {
406
+ hyps.push({
407
+ tag: "stale_cache",
408
+ summary: "Cached resource (304 Not Modified) served while console errors present \u2014 possible stale cache",
409
+ confidence: "MEDIUM",
410
+ confidenceScore: 0.69,
411
+ causalPath: ["browser uses cached resource (304)", "cached version is stale or broken", "page errors on stale asset"],
412
+ evidence: ["network status=304 observed", `${errors.length} console error(s) present`],
413
+ fixHint: "Force cache bypass with `Cache-Control: no-cache` on the erroring request or add a cache-busting query param.",
414
+ fixAction: null,
415
+ remediationConfidence: 0.5
416
+ });
417
+ }
418
+ }
419
+ if (!hyps.find((h) => h.tag === "cascading_timeout")) {
420
+ const SIXTY_SECONDS = 6e4;
421
+ const timeoutEvents = allNetwork.filter(
422
+ (n) => n.status === 0 || n.status === 504 || n.status === 503
423
+ );
424
+ if (timeoutEvents.length >= 3) {
425
+ const hosts = new Set(
426
+ timeoutEvents.map((n) => {
427
+ try {
428
+ return new URL(n.url).host;
429
+ } catch {
430
+ return n.url;
431
+ }
432
+ })
433
+ );
434
+ const sorted = [...timeoutEvents].sort((a, b) => a.timestamp - b.timestamp);
435
+ const windowMs = sorted[sorted.length - 1].timestamp - sorted[0].timestamp;
436
+ if (hosts.size >= 2 && windowMs <= SIXTY_SECONDS) {
437
+ hyps.push({
438
+ tag: "cascading_timeout",
439
+ summary: `Cascading timeouts: ${timeoutEvents.length} failures across ${hosts.size} services within ${Math.round(windowMs / 1e3)}s`,
440
+ confidence: "MEDIUM",
441
+ confidenceScore: 0.76,
442
+ causalPath: ["upstream service fails", "dependent services timeout waiting", "cascade spreads across services"],
443
+ evidence: [
444
+ `${timeoutEvents.length} timeout/503/504 events across ${hosts.size} hosts`,
445
+ `window: ${Math.round(windowMs / 1e3)}s`
446
+ ],
447
+ fixHint: "Multiple upstream services are failing. Start with the service that failed first and check its resource limits.",
448
+ fixAction: null,
449
+ remediationConfidence: 0.55
450
+ });
451
+ }
452
+ }
453
+ }
454
+ if (!hyps.find((h) => h.tag === "connection_pool_exhausted")) {
455
+ const poolFails = allNetwork.filter((n) => {
456
+ const errStr = (n.error ?? "").toLowerCase();
457
+ return n.status === 0 || errStr.includes("etimedout") || errStr.includes("econnreset");
458
+ });
459
+ if (poolFails.length >= 5) {
460
+ const hosts = new Set(
461
+ poolFails.map((n) => {
462
+ try {
463
+ return new URL(n.url).host;
464
+ } catch {
465
+ return n.url;
466
+ }
467
+ })
468
+ );
469
+ if (hosts.size === 1) {
470
+ const host = [...hosts][0];
471
+ hyps.push({
472
+ tag: "connection_pool_exhausted",
473
+ summary: `Connection pool exhausted \u2014 ${poolFails.length} consecutive timeouts/resets to ${host}`,
474
+ confidence: "HIGH",
475
+ confidenceScore: 0.81,
476
+ causalPath: ["all pool connections in use", "new requests cannot acquire connection", "ETIMEDOUT / ECONNRESET returned"],
477
+ evidence: [`${poolFails.length} failed connections to ${host}`],
478
+ fixHint: "Connection pool exhausted. Increase pool size or check for connection leaks: `SHOW STATUS LIKE 'Threads_connected'`.",
479
+ fixAction: null,
480
+ remediationConfidence: 0.65
481
+ });
482
+ }
483
+ }
484
+ }
485
+ if (!hyps.find((h) => h.tag === "session_fixation")) {
486
+ const sessionKey = Object.keys(ls).find((k) => /sessionId|session_id/i.test(k));
487
+ if (sessionKey) {
488
+ const preLoginSessionId = ls[sessionKey];
489
+ const loginSuccess = correlated.find(
490
+ (n) => /login|auth/i.test(n.url) && n.status >= 200 && n.status < 300
491
+ );
492
+ if (loginSuccess && preLoginSessionId && preLoginSessionId !== "null" && preLoginSessionId !== "") {
493
+ hyps.push({
494
+ tag: "session_fixation",
495
+ summary: "Session ID unchanged after successful login \u2014 potential session fixation vulnerability",
496
+ confidence: "MEDIUM",
497
+ confidenceScore: 0.73,
498
+ causalPath: ["pre-login session ID set", "login succeeds", "session ID not rotated", "attacker can reuse pre-auth session"],
499
+ evidence: [`${sessionKey} present before and after login`, `login to ${loginSuccess.url} returned ${loginSuccess.status}`],
500
+ fixHint: "Regenerate session ID immediately after successful login to prevent session fixation attacks.",
501
+ fixAction: null,
502
+ remediationConfidence: 0.7
503
+ });
504
+ }
505
+ }
506
+ }
507
+ if (!hyps.find((h) => h.tag === "failed_migration")) {
508
+ const failedCIWithMigration = ciEvents.find(
509
+ (ci) => ci.status === "failure" && /migrat/i.test(JSON.stringify(ci))
510
+ );
511
+ const terminalMigrationFail = terminal.find(
512
+ (t) => /migration failed/i.test(t.data)
513
+ );
514
+ if (failedCIWithMigration || terminalMigrationFail) {
515
+ const evidence = [];
516
+ if (failedCIWithMigration) evidence.push(`CI event status=failure (migration keyword matched)`);
517
+ if (terminalMigrationFail) evidence.push('terminal output: "migration failed"');
518
+ hyps.push({
519
+ tag: "failed_migration",
520
+ summary: "Database migration failed \u2014 schema may be inconsistent between app and DB",
521
+ confidence: "HIGH",
522
+ confidenceScore: 0.85,
523
+ causalPath: ["migration script executed", "migration step errored", "schema left in partial state"],
524
+ evidence,
525
+ fixHint: "Migration failed in CI. Check the migration SQL for syntax errors or version conflicts.",
526
+ fixAction: null,
527
+ remediationConfidence: 0.7
528
+ });
529
+ }
530
+ }
531
+ return hyps;
532
+ }
533
+ async function buildCausalChain(logs, network, contexts, _firedAt, terminal = [], processExits = [], ciEvents = [], deployments = [], _infraEvents) {
534
+ const errors = logs.filter((e) => e.level === "error");
535
+ const warnings = logs.filter((e) => e.level === "warn");
536
+ const firstErrorTs = errors[0]?.timestamp ?? null;
537
+ const errorBlocks = errors.map((e) => ({
538
+ message: e.args?.[0] ?? "",
539
+ stack: e.stack,
540
+ primaryFrame: e.stack ? parseFrame(e.stack) : null,
541
+ ts: e.timestamp
542
+ }));
543
+ const correlated = network.map((n) => ({
544
+ url: n.url,
545
+ method: n.method,
546
+ status: n.status,
547
+ duration: n.duration,
548
+ error: n.error ?? null,
549
+ requestBody: n.requestBody,
550
+ responseBody: n.responseBody,
551
+ requestHeaders: n.requestHeaders,
552
+ responseHeaders: n.responseHeaders,
553
+ msBeforeError: firstErrorTs !== null ? firstErrorTs - n.timestamp : null,
554
+ ts: n.timestamp
555
+ })).filter((n) => {
556
+ if (firstErrorTs === null) return true;
557
+ const diff = firstErrorTs - n.ts;
558
+ return diff >= 0 && diff <= NETWORK_WINDOW_MS;
559
+ });
560
+ let stateAtError = null;
561
+ if (firstErrorTs !== null) {
562
+ const eligible = contexts.filter((c) => {
563
+ const diff = firstErrorTs - c.timestamp;
564
+ return diff >= 0 && diff <= STATE_WINDOW_MS;
565
+ });
566
+ if (eligible.length > 0) {
567
+ stateAtError = eligible.reduce((a, b) => a.timestamp > b.timestamp ? a : b);
568
+ }
569
+ }
570
+ const chain = [
571
+ ...contexts.map((c) => ({ kind: "state", ts: c.timestamp, message: `${c.component ?? "unknown"} state captured` })),
572
+ ...errors.map((e) => ({ kind: "error", ts: e.timestamp, message: e.args?.[0] ?? "" })),
573
+ ...warnings.map((w) => ({ kind: "warn", ts: w.timestamp, message: w.args?.[0] ?? "" })),
574
+ ...network.map((n) => ({
575
+ kind: n.status >= 400 || n.status === 0 || !!n.error ? "network_fail" : "network_ok",
576
+ ts: n.timestamp,
577
+ message: `${n.method} ${n.url} \u2192 ${n.status}`
578
+ }))
579
+ ].sort((a, b) => a.ts - b.ts);
580
+ const contextPack = buildContextPack(errorBlocks, correlated, stateAtError);
581
+ const rawHyps = detectHypotheses(
582
+ errorBlocks,
583
+ network,
584
+ correlated,
585
+ stateAtError,
586
+ terminal,
587
+ processExits,
588
+ ciEvents,
589
+ deployments
590
+ );
591
+ const tagged = recordPrediction(rawHyps);
592
+ const { active, suppressed } = applyCalibration(tagged);
593
+ return { errors: errorBlocks, chain, contextPack, correlatedNetwork: correlated, correlatedBackend: [], stateAtError, hypotheses: active, suppressedHypotheses: suppressed };
594
+ }
595
+ function fixActionToCommand(_action) {
596
+ return null;
597
+ }
598
+ export {
599
+ buildCausalChain,
600
+ fixActionToCommand
601
+ };