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,339 @@
1
+ import { Router } from "express";
2
+ import { timingSafeSecretEqualAny } from "./security-utils.js";
3
+ import { store, BrowserEventSchema } from "./buffer.js";
4
+ import { resolveFrameAndStack } from "./sourcemap.js";
5
+ import { redact } from "./redact.js";
6
+ import logger from "./logger.js";
7
+ import { getStores } from "../storage/store-registry.js";
8
+ import { layer2Store } from "./layer2-store.js";
9
+ import { layer3Store } from "./layer3-store.js";
10
+ import { layer4Store } from "./layer4-store.js";
11
+ import { historyStore } from "./sqlite-store.js";
12
+ import { exportToOtel } from "./otel-exporter.js";
13
+ import { serviceTopology } from "./service-topology.js";
14
+ let _teamBroadcast = null;
15
+ function registerTeamBroadcaster(fn) {
16
+ _teamBroadcast = fn;
17
+ }
18
+ function maybeTeamBroadcast(event) {
19
+ if (_teamBroadcast) {
20
+ try {
21
+ _teamBroadcast([event], "self");
22
+ } catch {
23
+ }
24
+ }
25
+ }
26
+ let _notifier = null;
27
+ function registerActivityNotifier(n) {
28
+ _notifier = n;
29
+ }
30
+ class DedupWindow {
31
+ _seen = /* @__PURE__ */ new Map();
32
+ _windowMs;
33
+ constructor(windowMs = 5e3) {
34
+ this._windowMs = windowMs;
35
+ }
36
+ /** Returns true if this fingerprint was seen within the window (duplicate).
37
+ * Side effect: records the fingerprint and increments its count. */
38
+ isDuplicate(fp, ts) {
39
+ const entry = this._seen.get(fp);
40
+ if (entry && ts - entry.lastTs < this._windowMs) {
41
+ entry.count++;
42
+ entry.lastTs = ts;
43
+ return true;
44
+ }
45
+ this._seen.set(fp, { count: 1, lastTs: ts });
46
+ if (this._seen.size > 500) {
47
+ const cutoff = ts - this._windowMs * 2;
48
+ for (const [k, v] of this._seen) {
49
+ if (v.lastTs < cutoff) this._seen.delete(k);
50
+ }
51
+ }
52
+ return false;
53
+ }
54
+ reset() {
55
+ this._seen.clear();
56
+ }
57
+ }
58
+ const _dedup = new DedupWindow(5e3);
59
+ function eventFingerprint(event) {
60
+ if (event.type === "network") {
61
+ return `net:${event.method}:${event.url}:${event.status}`;
62
+ }
63
+ if (event.type === "console" && event.level === "error") {
64
+ const msg = typeof event.args[0] === "string" ? event.args[0].slice(0, 100) : "";
65
+ return `err:${msg}`;
66
+ }
67
+ return null;
68
+ }
69
+ const _tenantBuckets = /* @__PURE__ */ new Map();
70
+ function getTenantBucket(tenantId) {
71
+ let bucket = _tenantBuckets.get(tenantId);
72
+ if (!bucket) {
73
+ bucket = new TokenBucket(100, 1e3);
74
+ _tenantBuckets.set(tenantId, bucket);
75
+ }
76
+ return bucket;
77
+ }
78
+ function createIngestRouter(localSecret) {
79
+ const router = Router();
80
+ const effectiveSecret = process.env.MERGEN_SECRET ?? localSecret;
81
+ router.post("/ingest", async (req, res) => {
82
+ if (!timingSafeSecretEqualAny(req.headers["x-mergen-secret"], effectiveSecret)) {
83
+ res.status(401).json({ error: "unauthorized" });
84
+ return;
85
+ }
86
+ const tenantId = req.tenantId;
87
+ const limited = tenantId ? getTenantBucket(tenantId).isRateLimited() : _bucket.isRateLimited();
88
+ if (limited) {
89
+ res.status(429).json({ error: "rate limit exceeded" });
90
+ return;
91
+ }
92
+ const result = BrowserEventSchema.safeParse(req.body);
93
+ if (!result.success) {
94
+ if (req.body && req.body.type === "blunder") {
95
+ const { id, recordedAt, blunderType, command, blockReason, service, tag, actor, pid, confidenceScore, triggeredRules } = req.body;
96
+ if (typeof blunderType !== "string" || typeof blockReason !== "string" || typeof actor !== "string") {
97
+ res.status(400).json({ error: "invalid blunder event fields" });
98
+ return;
99
+ }
100
+ const rules = Array.isArray(triggeredRules) ? triggeredRules.filter((r) => typeof r === "string").slice(0, 32).map((r) => r.slice(0, 200)) : null;
101
+ try {
102
+ await getStores().blunders.record({
103
+ id: typeof id === "string" ? id : void 0,
104
+ recordedAt: typeof recordedAt === "number" ? recordedAt : void 0,
105
+ blunderType,
106
+ command: typeof command === "string" ? command : null,
107
+ blockReason,
108
+ service: typeof service === "string" ? service : null,
109
+ tag: typeof tag === "string" ? tag : null,
110
+ actor,
111
+ pid: typeof pid === "string" ? pid : null,
112
+ confidenceScore: typeof confidenceScore === "number" ? confidenceScore : null,
113
+ triggeredRules: rules
114
+ });
115
+ res.status(204).end();
116
+ return;
117
+ } catch (err) {
118
+ logger.warn({ err }, "ingest: failed to record blunder");
119
+ res.status(500).json({ error: "failed to record blunder" });
120
+ return;
121
+ }
122
+ }
123
+ res.status(400).json({ error: "invalid event", details: result.error.flatten() });
124
+ return;
125
+ }
126
+ const tsFutureMs = result.data.timestamp - Date.now();
127
+ if (tsFutureMs > 10 * 60 * 1e3) {
128
+ const eventUrl = "url" in result.data ? result.data.url : void 0;
129
+ logger.warn({ tsFutureMs, url: eventUrl }, "ingest: event timestamp is too far in the future \u2014 rejected");
130
+ res.status(400).json({ error: "event timestamp too far in the future" });
131
+ return;
132
+ }
133
+ const event = redactEvent(clampNetworkBodies(result.data));
134
+ const fp = eventFingerprint(event);
135
+ if (fp && _dedup.isDuplicate(fp, event.timestamp)) {
136
+ res.status(204).end();
137
+ return;
138
+ }
139
+ res.status(204).end();
140
+ _processEvent(event, req.tenantId);
141
+ });
142
+ return router;
143
+ }
144
+ const ingestRouter = Router();
145
+ class TokenBucket {
146
+ constructor(limit = 100, windowMs = 1e3) {
147
+ this.limit = limit;
148
+ this.windowMs = windowMs;
149
+ }
150
+ limit;
151
+ windowMs;
152
+ _count = 0;
153
+ _timer = null;
154
+ isRateLimited() {
155
+ if (this._count >= this.limit) return true;
156
+ this._count++;
157
+ if (!this._timer) {
158
+ this._timer = setTimeout(() => {
159
+ this._count = 0;
160
+ this._timer = null;
161
+ }, this.windowMs);
162
+ }
163
+ return false;
164
+ }
165
+ reset() {
166
+ if (this._timer) {
167
+ clearTimeout(this._timer);
168
+ this._timer = null;
169
+ }
170
+ this._count = 0;
171
+ }
172
+ }
173
+ const _bucket = new TokenBucket();
174
+ function resetForTesting() {
175
+ _bucket.reset();
176
+ _dedup.reset();
177
+ }
178
+ function isRateLimited() {
179
+ return _bucket.isRateLimited();
180
+ }
181
+ const SOURCEMAP_TIMEOUT_MS = 2e3;
182
+ const MAX_BODY_BYTES = 8 * 1024;
183
+ const TRUNC_MARKER = "[\u2026truncated by mergen]";
184
+ function clampBody(body) {
185
+ if (body == null) return body;
186
+ if (typeof body === "string") {
187
+ return body.length > MAX_BODY_BYTES ? body.slice(0, MAX_BODY_BYTES) + ` ${TRUNC_MARKER} (+${body.length - MAX_BODY_BYTES} bytes)` : body;
188
+ }
189
+ let s;
190
+ try {
191
+ s = JSON.stringify(body);
192
+ } catch {
193
+ return TRUNC_MARKER;
194
+ }
195
+ if (s.length <= MAX_BODY_BYTES) return body;
196
+ return s.slice(0, MAX_BODY_BYTES) + ` ${TRUNC_MARKER} (+${s.length - MAX_BODY_BYTES} bytes)`;
197
+ }
198
+ function clampNetworkBodies(event) {
199
+ if (event.type !== "network") return event;
200
+ return {
201
+ ...event,
202
+ requestBody: clampBody(event.requestBody),
203
+ responseBody: clampBody(event.responseBody)
204
+ };
205
+ }
206
+ function redactEvent(event) {
207
+ if (event.type === "network") {
208
+ return {
209
+ ...event,
210
+ url: typeof event.url === "string" ? redact(event.url) : event.url,
211
+ requestBody: redact(event.requestBody),
212
+ responseBody: redact(event.responseBody),
213
+ requestHeaders: event.requestHeaders ? redact(event.requestHeaders) : event.requestHeaders,
214
+ responseHeaders: event.responseHeaders ? redact(event.responseHeaders) : event.responseHeaders
215
+ };
216
+ }
217
+ if (event.type === "console") {
218
+ return { ...event, args: redact(event.args) };
219
+ }
220
+ if (event.type === "context") {
221
+ return {
222
+ ...event,
223
+ localStorage: redact(event.localStorage),
224
+ sessionStorage: redact(event.sessionStorage)
225
+ };
226
+ }
227
+ return event;
228
+ }
229
+ function withTimeout(promise, ms) {
230
+ return Promise.race([
231
+ promise,
232
+ new Promise(
233
+ (_, reject) => setTimeout(() => reject(new Error(`sourcemap resolution timed out after ${ms}ms`)), ms)
234
+ )
235
+ ]);
236
+ }
237
+ function _processEvent(event, tenantId) {
238
+ const isError = event.type === "console" && event.level === "error";
239
+ const isPageload = event.type === "context" && event.trigger === "pageload";
240
+ const isHmr = event.type === "context" && event.trigger === "hmr";
241
+ const isFailedNet = event.type === "network" && (event.status >= 400 || event.status === 0 || !!event.error);
242
+ const isSlowNet = event.type === "network" && event.duration > 500;
243
+ const burstReason = () => {
244
+ if (isFailedNet) {
245
+ const w = store.getNetwork(50, void 0, Date.now() - 1e4);
246
+ if (w.filter((n) => n.status >= 400 || n.status === 0 || !!n.error).length >= 3) return "net_burst";
247
+ }
248
+ if (isSlowNet) {
249
+ const w = store.getNetwork(50, void 0, Date.now() - 1e4);
250
+ if (w.filter((n) => n.duration > 500).length >= 3) return "slow_burst";
251
+ }
252
+ return null;
253
+ };
254
+ const triggerActivity = () => {
255
+ if (!_notifier) return;
256
+ if (isError) {
257
+ _notifier.notifyError();
258
+ return;
259
+ }
260
+ if (isPageload) {
261
+ _notifier.notifyActivity("pageload");
262
+ return;
263
+ }
264
+ if (isHmr) {
265
+ _notifier.notifyActivity("hmr");
266
+ return;
267
+ }
268
+ const burst = burstReason();
269
+ if (burst) {
270
+ _notifier.notifyActivity(burst);
271
+ return;
272
+ }
273
+ };
274
+ const eventId = layer2Store.indexEvent(event);
275
+ const breakpoint = layer3Store.checkBreakpoint(event);
276
+ if (breakpoint) logger.info({ breakpoint: breakpoint.id, eventId }, "Breakpoint hit");
277
+ if (event.type === "console" && event.level === "error") {
278
+ const message = event.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
279
+ layer4Store.recordError(message, event.stack);
280
+ }
281
+ if (event.type === "console" && typeof event.stack === "string") {
282
+ withTimeout(resolveFrameAndStack(event.stack), SOURCEMAP_TIMEOUT_MS).then(({ resolvedStack, primaryFrame }) => {
283
+ const resolved_event = {
284
+ ...event,
285
+ stack: resolvedStack,
286
+ ...primaryFrame?.gitSuspect ? { gitSuspect: primaryFrame.gitSuspect } : {}
287
+ };
288
+ store.push(resolved_event, tenantId);
289
+ historyStore.push(resolved_event);
290
+ maybeTeamBroadcast(resolved_event);
291
+ exportToOtel(resolved_event);
292
+ }).catch((err) => {
293
+ logger.warn({ err }, "sourcemap resolution failed or timed out, storing raw event");
294
+ store.push(event, tenantId);
295
+ historyStore.push(event);
296
+ maybeTeamBroadcast(event);
297
+ exportToOtel(event);
298
+ }).finally(triggerActivity);
299
+ } else {
300
+ store.push(event, tenantId);
301
+ historyStore.push(event);
302
+ maybeTeamBroadcast(event);
303
+ exportToOtel(event);
304
+ updateTopology(event);
305
+ triggerActivity();
306
+ }
307
+ }
308
+ function updateTopology(event) {
309
+ if (event.type === "backend_span") {
310
+ serviceTopology.updateFromSpan(event);
311
+ const siblings = store.getBackendSpans(50).filter((s) => s.traceId === event.traceId);
312
+ if (siblings.length > 1) serviceTopology.updateFromTraceGroup(siblings);
313
+ return;
314
+ }
315
+ if (event.type === "network" && event.traceId) {
316
+ const matchingSpan = store.getBackendSpans(50).find((s) => s.traceId === event.traceId);
317
+ if (matchingSpan) {
318
+ serviceTopology.updateFromTraceJoin(
319
+ event.url,
320
+ matchingSpan.service,
321
+ event.duration,
322
+ event.status >= 500 || !!event.error,
323
+ event.traceId,
324
+ event.timestamp
325
+ );
326
+ }
327
+ }
328
+ }
329
+ export {
330
+ MAX_BODY_BYTES,
331
+ TokenBucket,
332
+ clampBody,
333
+ clampNetworkBodies,
334
+ createIngestRouter,
335
+ ingestRouter,
336
+ registerActivityNotifier,
337
+ registerTeamBroadcaster,
338
+ resetForTesting
339
+ };
@@ -0,0 +1,52 @@
1
+ import http from "http";
2
+ const MERGEN_PORT = parseInt(process.env.MERGEN_PORT ?? "3000", 10);
3
+ function postEvent(payload) {
4
+ const body = JSON.stringify(payload);
5
+ const req = http.request(
6
+ {
7
+ hostname: "127.0.0.1",
8
+ port: MERGEN_PORT,
9
+ path: "/ingest",
10
+ method: "POST",
11
+ headers: {
12
+ "Content-Type": "application/json",
13
+ "Content-Length": Buffer.byteLength(body)
14
+ }
15
+ },
16
+ (res) => res.resume()
17
+ );
18
+ req.on("error", () => {
19
+ });
20
+ req.write(body);
21
+ req.end();
22
+ }
23
+ class MergenJestReporter {
24
+ constructor(_globalConfig, _options) {
25
+ }
26
+ onTestResult(_test, testResult) {
27
+ const file = testResult.testFilePath;
28
+ for (const t of testResult.testResults) {
29
+ const stateMap = {
30
+ passed: "pass",
31
+ failed: "fail",
32
+ skipped: "skip",
33
+ todo: "todo",
34
+ pending: "skip"
35
+ };
36
+ const error = t.failureMessages[0] ? { message: t.failureMessages[0].split("\n")[0], stack: t.failureMessages[0] } : void 0;
37
+ postEvent({
38
+ type: "test_result",
39
+ runner: "jest",
40
+ file,
41
+ name: t.fullName,
42
+ status: stateMap[t.status] ?? "skip",
43
+ duration: t.duration ?? void 0,
44
+ error,
45
+ timestamp: Date.now()
46
+ });
47
+ }
48
+ }
49
+ }
50
+ export {
51
+ MergenJestReporter as default
52
+ };
@@ -0,0 +1,132 @@
1
+ import { execFile } from "child_process";
2
+ import { promisify } from "util";
3
+ import logger from "./logger.js";
4
+ const execFileAsync = promisify(execFile);
5
+ const POLL_INTERVAL_MS = 3e4;
6
+ const MAX_BUFFERED = 500;
7
+ const EVENT_WINDOW_MS = 10 * 60 * 1e3;
8
+ const _buffer = [];
9
+ const _seenUids = /* @__PURE__ */ new Set();
10
+ function push(event) {
11
+ _buffer.push(event);
12
+ if (_buffer.length > MAX_BUFFERED) _buffer.shift();
13
+ }
14
+ function toTimestamp(item) {
15
+ const raw = item.lastTimestamp ?? item.firstTimestamp ?? item.eventTime ?? item.metadata.creationTimestamp;
16
+ return raw ? new Date(raw).getTime() : Date.now();
17
+ }
18
+ function normalizeEvent(item, namespace) {
19
+ if (item.type === "Normal") return null;
20
+ const reason = (item.reason ?? "").toLowerCase();
21
+ const message = item.message ?? "";
22
+ const msgLow = message.toLowerCase();
23
+ const service = item.involvedObject.name;
24
+ const ts = toTimestamp(item);
25
+ const attrs = {
26
+ namespace,
27
+ objectKind: item.involvedObject.kind,
28
+ k8sReason: item.reason,
29
+ count: item.count ?? 1
30
+ };
31
+ if (reason === "oomkilling" || reason === "oom" || msgLow.includes("oom")) {
32
+ return { kind: "oom_kill", timestamp: ts, service, severity: "critical", message, attributes: attrs, source: "k8s" };
33
+ }
34
+ if (reason === "backoff" || msgLow.includes("back-off") || msgLow.includes("crashloop")) {
35
+ return { kind: "pod_crash", timestamp: ts, service, severity: "critical", message, attributes: attrs, source: "k8s" };
36
+ }
37
+ if (reason === "evicting" || reason === "evicted") {
38
+ const isDisk = msgLow.includes("disk") || msgLow.includes("ephemeral") || msgLow.includes("storage");
39
+ return { kind: isDisk ? "disk_pressure" : "oom_kill", timestamp: ts, service, severity: "critical", message, attributes: attrs, source: "k8s" };
40
+ }
41
+ if ((reason === "failed" || reason === "failedsync") && (msgLow.includes("imagepull") || msgLow.includes("errimagepull") || msgLow.includes("image pull"))) {
42
+ return { kind: "pod_crash", timestamp: ts, service, severity: "critical", message, attributes: { ...attrs, k8sReason: "ImagePullError" }, source: "k8s" };
43
+ }
44
+ if (reason === "unhealthy") {
45
+ return { kind: "pod_crash", timestamp: ts, service, severity: "high", message, attributes: attrs, source: "k8s" };
46
+ }
47
+ if (reason === "failedmount" || reason === "failedattachvolume") {
48
+ return { kind: "pod_crash", timestamp: ts, service, severity: "critical", message, attributes: attrs, source: "k8s" };
49
+ }
50
+ if (reason === "nodehasdiskcapacity" || msgLow.includes("disk pressure")) {
51
+ return { kind: "disk_pressure", timestamp: ts, service, severity: "high", message, attributes: attrs, source: "k8s" };
52
+ }
53
+ if (reason === "nodenotready" || reason === "notready") {
54
+ return { kind: "service_unavailable", timestamp: ts, service, severity: "critical", message, attributes: attrs, source: "k8s" };
55
+ }
56
+ if (reason === "toomanyrequests" || msgLow.includes("rate limit") || msgLow.includes("429")) {
57
+ return { kind: "rate_limit_cascade", timestamp: ts, service, severity: "high", message, attributes: attrs, source: "k8s" };
58
+ }
59
+ return null;
60
+ }
61
+ async function pollNamespace(namespace) {
62
+ let stdout;
63
+ try {
64
+ const result = await execFileAsync("kubectl", ["get", "events", "-n", namespace, "-o", "json"], {
65
+ timeout: 1e4,
66
+ maxBuffer: 4 * 1024 * 1024
67
+ // 4 MB
68
+ });
69
+ stdout = result.stdout;
70
+ } catch {
71
+ return;
72
+ }
73
+ let list;
74
+ try {
75
+ list = JSON.parse(stdout);
76
+ } catch {
77
+ return;
78
+ }
79
+ if (!Array.isArray(list?.items)) return;
80
+ const cutoff = Date.now() - EVENT_WINDOW_MS;
81
+ for (const item of list.items) {
82
+ const uid = item.metadata?.uid;
83
+ if (!uid || _seenUids.has(uid)) continue;
84
+ const normalized = normalizeEvent(item, namespace);
85
+ if (!normalized) {
86
+ _seenUids.add(uid);
87
+ continue;
88
+ }
89
+ if (normalized.timestamp < cutoff) {
90
+ _seenUids.add(uid);
91
+ continue;
92
+ }
93
+ _seenUids.add(uid);
94
+ push(normalized);
95
+ logger.debug({ kind: normalized.kind, service: normalized.service, namespace }, "k8s-events: new event");
96
+ }
97
+ if (_seenUids.size > 1e4) {
98
+ const arr = [..._seenUids];
99
+ arr.slice(0, 5e3).forEach((uid) => _seenUids.delete(uid));
100
+ }
101
+ }
102
+ let _pollHandle = null;
103
+ let _namespaces = [];
104
+ function startK8sEventsPoller() {
105
+ const raw = process.env.MERGEN_K8S_NAMESPACE;
106
+ if (!raw) return () => {
107
+ };
108
+ _namespaces = raw.split(",").map((s) => s.trim()).filter(Boolean);
109
+ if (_namespaces.length === 0) return () => {
110
+ };
111
+ logger.info({ namespaces: _namespaces }, "k8s-events: polling started");
112
+ void Promise.all(_namespaces.map(pollNamespace));
113
+ _pollHandle = setInterval(() => {
114
+ void Promise.all(_namespaces.map(pollNamespace));
115
+ }, POLL_INTERVAL_MS);
116
+ _pollHandle.unref();
117
+ return () => {
118
+ if (_pollHandle) {
119
+ clearInterval(_pollHandle);
120
+ _pollHandle = null;
121
+ }
122
+ logger.info("k8s-events: polling stopped");
123
+ };
124
+ }
125
+ function getK8sEvents(since) {
126
+ if (since === void 0) return [..._buffer];
127
+ return _buffer.filter((e) => e.timestamp >= since);
128
+ }
129
+ export {
130
+ getK8sEvents,
131
+ startK8sEventsPoller
132
+ };
@@ -0,0 +1,111 @@
1
+ import { randomBytes } from "crypto";
2
+ class Layer2Store {
3
+ eventIndex = /* @__PURE__ */ new Map();
4
+ // id → full event
5
+ watchPatterns = /* @__PURE__ */ new Map();
6
+ // id → pattern
7
+ watchListeners = /* @__PURE__ */ new Map();
8
+ /** Store full event with ID for later replay */
9
+ indexEvent(event) {
10
+ const id = randomBytes(8).toString("hex");
11
+ this.eventIndex.set(id, event);
12
+ for (const [watchId, pattern] of this.watchPatterns) {
13
+ if (this.matchesPattern(event, pattern)) {
14
+ const listener = this.watchListeners.get(watchId);
15
+ if (listener) listener(event);
16
+ }
17
+ }
18
+ return id;
19
+ }
20
+ /** Retrieve full event by ID */
21
+ getEventById(id) {
22
+ return this.eventIndex.get(id);
23
+ }
24
+ /** Register a watch pattern */
25
+ registerWatch(pattern, type) {
26
+ const id = randomBytes(8).toString("hex");
27
+ this.watchPatterns.set(id, {
28
+ id,
29
+ pattern,
30
+ type,
31
+ createdAt: Date.now()
32
+ });
33
+ return id;
34
+ }
35
+ /** Remove a watch pattern */
36
+ removeWatch(id) {
37
+ this.watchListeners.delete(id);
38
+ return this.watchPatterns.delete(id);
39
+ }
40
+ /** List all active watch patterns */
41
+ listWatches() {
42
+ return Array.from(this.watchPatterns.values());
43
+ }
44
+ /** Set callback for watch pattern */
45
+ setWatchListener(watchId, callback) {
46
+ this.watchListeners.set(watchId, callback);
47
+ }
48
+ /** Build timeline from events */
49
+ buildTimeline(events, from, to) {
50
+ return events.filter((e) => {
51
+ if (from !== void 0 && e.timestamp < from) return false;
52
+ if (to !== void 0 && e.timestamp > to) return false;
53
+ return true;
54
+ }).map((e) => ({
55
+ timestamp: e.timestamp,
56
+ type: e.type,
57
+ summary: this.summarizeEvent(e),
58
+ fullEvent: e
59
+ })).sort((a, b) => a.timestamp - b.timestamp);
60
+ }
61
+ matchesPattern(event, pattern) {
62
+ if (pattern.type !== event.type) return false;
63
+ try {
64
+ const regex = new RegExp(pattern.pattern, "i");
65
+ if (event.type === "console") {
66
+ const message = event.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
67
+ return regex.test(message);
68
+ }
69
+ if (event.type === "network") {
70
+ return regex.test(event.url);
71
+ }
72
+ } catch {
73
+ if (event.type === "console") {
74
+ const message = event.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
75
+ return message.includes(pattern.pattern);
76
+ }
77
+ if (event.type === "network") {
78
+ return event.url.includes(pattern.pattern);
79
+ }
80
+ }
81
+ return false;
82
+ }
83
+ summarizeEvent(event) {
84
+ if (event.type === "console") {
85
+ const msg = event.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").slice(0, 100);
86
+ return `[${event.level.toUpperCase()}] ${msg}`;
87
+ }
88
+ if (event.type === "network") {
89
+ return `${event.method} ${event.url} \u2192 ${event.status} (${event.duration}ms)`;
90
+ }
91
+ if (event.type === "context") {
92
+ return `Context snapshot (${event.trigger}) at ${event.url}`;
93
+ }
94
+ return "Unknown event";
95
+ }
96
+ /** Clear old indexed events to prevent memory leak (keep last 500) */
97
+ pruneEventIndex() {
98
+ const MAX_INDEXED_EVENTS = 500;
99
+ if (this.eventIndex.size > MAX_INDEXED_EVENTS) {
100
+ const sorted = Array.from(this.eventIndex.entries()).sort((a, b) => b[1].timestamp - a[1].timestamp);
101
+ this.eventIndex.clear();
102
+ for (const [id, event] of sorted.slice(0, MAX_INDEXED_EVENTS)) {
103
+ this.eventIndex.set(id, event);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ const layer2Store = new Layer2Store();
109
+ export {
110
+ layer2Store
111
+ };