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,232 @@
1
+ import { Router } from "express";
2
+ import { getStores } from "../storage/store-registry.js";
3
+ import { analyzeSemanticRisk } from "../intelligence/action-risk.js";
4
+ import { postmortemStore } from "../intelligence/postmortem-store.js";
5
+ import { evaluateEnterprisePolicy, isAiActor } from "../intelligence/enterprise-policy-engine.js";
6
+ import { evaluateDiffSize } from "../intelligence/diff-size.js";
7
+ import { serviceGraph } from "../sensor/service-graph.js";
8
+ import logger from "../sensor/logger.js";
9
+ function createCIGateRouter() {
10
+ const router = Router();
11
+ router.post("/ci/gate", async (req, res) => {
12
+ const body = req.body;
13
+ const files = Array.isArray(body.files) ? body.files : [];
14
+ const prTitle = typeof body.prTitle === "string" ? body.prTitle.slice(0, 300) : "";
15
+ const diff = typeof body.diff === "string" ? body.diff.slice(0, 2e4) : "";
16
+ const actor = typeof body.actor === "string" ? body.actor.slice(0, 100) : "unknown";
17
+ const diffStats = body.diffStats && typeof body.diffStats === "object" ? {
18
+ filesChanged: Number(body.diffStats.filesChanged) || 0,
19
+ additions: Number(body.diffStats.additions) || 0,
20
+ deletions: Number(body.diffStats.deletions) || 0
21
+ } : null;
22
+ const prNumber = typeof body.prNumber === "number" && Number.isFinite(body.prNumber) ? body.prNumber : null;
23
+ const repo = typeof body.repo === "string" ? body.repo.slice(0, 200) : null;
24
+ const sha = typeof body.sha === "string" ? body.sha.slice(0, 64) : null;
25
+ const service = inferService(body.service, files);
26
+ const inferredTags = inferTagsFromContext(files, prTitle);
27
+ const now = /* @__PURE__ */ new Date();
28
+ const dayOfWeek = now.getUTCDay();
29
+ const hourOfDay = now.getUTCHours();
30
+ const matchedRules = [];
31
+ const reasons = [];
32
+ let verdict = "pass";
33
+ for (const tag of inferredTags) {
34
+ const rules = await getStores().overrides.getRulesForTag(tag, service, req.tenantId);
35
+ for (const rule of rules) {
36
+ matchedRules.push(rule);
37
+ const timeMatch = await getStores().overrides.hasRecentOverride(tag, service, dayOfWeek, hourOfDay, req.tenantId);
38
+ const dayName = rule.dayOfWeek != null ? ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][rule.dayOfWeek] : null;
39
+ const hourDesc = rule.hourWindow ? `${rule.hourWindow[0]}:00\u2013${rule.hourWindow[1]}:00 UTC` : null;
40
+ if (rule.occurrences >= 3 && timeMatch) {
41
+ verdict = "block";
42
+ reasons.push(
43
+ `Corpus block: \`${tag}\` has been overridden ${rule.occurrences}\xD7 (reason: ${rule.overrideReason})` + (dayName ? ` \u2014 typically on ${dayName}` : "") + (hourDesc ? ` ${hourDesc}` : "") + ". This matches your current time window."
44
+ );
45
+ } else if (rule.occurrences >= 1) {
46
+ if (verdict === "pass") verdict = "warn";
47
+ reasons.push(
48
+ `Corpus warning: \`${tag}\` has been overridden ${rule.occurrences}\xD7 (reason: ${rule.overrideReason})` + (dayName ? ` \u2014 pattern clusters on ${dayName}` : "") + "."
49
+ );
50
+ }
51
+ }
52
+ }
53
+ if (diff) {
54
+ const semantic = analyzeSemanticRisk(diff);
55
+ if (semantic.risk === "high") {
56
+ verdict = "block";
57
+ reasons.push(semantic.reason ?? "Blocked: destructive operation detected in diff. Review and replace with a reversible alternative before merging.");
58
+ } else if (semantic.risk === "medium" && verdict === "pass") {
59
+ verdict = "warn";
60
+ reasons.push(`Semantic risk MEDIUM: ${semantic.reason ?? "elevated-risk operation in diff"}`);
61
+ }
62
+ }
63
+ const enterpriseResult = evaluateEnterprisePolicy({
64
+ files,
65
+ actor,
66
+ service,
67
+ timestamp: Date.now()
68
+ });
69
+ if (enterpriseResult.triggeredRules.length > 0) {
70
+ if (enterpriseResult.verdict === "block") {
71
+ verdict = "block";
72
+ } else if (enterpriseResult.verdict === "warn" && verdict !== "block") {
73
+ verdict = "warn";
74
+ }
75
+ reasons.push(...enterpriseResult.reasons);
76
+ }
77
+ let diffSizeReport = null;
78
+ if (diffStats) {
79
+ diffSizeReport = evaluateDiffSize(diffStats, { actorIsAi: isAiActor(actor) });
80
+ if (diffSizeReport.requiresApproval) {
81
+ if (verdict !== "block") verdict = "warn";
82
+ reasons.push(`Diff size HIGH (${diffSizeReport.score}/100): ${diffSizeReport.recommendation}`);
83
+ } else if (diffSizeReport.level === "MEDIUM" && verdict === "pass") {
84
+ verdict = "warn";
85
+ reasons.push(`Diff size MEDIUM (${diffSizeReport.score}/100): ${diffSizeReport.recommendation}`);
86
+ }
87
+ }
88
+ const BLAST_RADIUS_THRESHOLD = 3;
89
+ const downstreamCount = service !== "unknown" ? serviceGraph.getUpstreamImpact(service).length : 0;
90
+ let blastRadiusEscalated = false;
91
+ if (verdict === "warn" && downstreamCount > BLAST_RADIUS_THRESHOLD && isAiActor(actor)) {
92
+ verdict = "block";
93
+ blastRadiusEscalated = true;
94
+ reasons.push(
95
+ `Blast radius: ${downstreamCount} downstream service(s) depend on \`${service}\` and the change was made by an AI actor \u2014 escalated from WARN to BLOCK.`
96
+ );
97
+ }
98
+ if (prTitle) {
99
+ const titleLower = prTitle.toLowerCase();
100
+ const summary = await getStores().overrides.getOverrideSummary(req.tenantId);
101
+ for (const entry of summary) {
102
+ if (titleLower.includes(entry.tag.replace(/_/g, " ")) || titleLower.includes(entry.tag.replace(/_/g, "-"))) {
103
+ if (verdict === "pass") verdict = "warn";
104
+ if (!reasons.some((r) => r.includes(entry.tag))) {
105
+ reasons.push(`PR title matches known override pattern: \`${entry.tag}\` (${entry.total} historical overrides)`);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ const riskScore = computeRiskScore(verdict, matchedRules);
111
+ const recommendation = verdict === "block" ? "Review the override corpus before merging. This change touches areas your team has explicitly blocked in the past." : verdict === "warn" ? "Proceed with caution \u2014 this change touches areas with a history of operational overrides." : "No corpus conflicts found. Safe to merge based on execution history.";
112
+ logger.info({ verdict, riskScore, tags: inferredTags, service, actor, files: files.length }, "ci-gate: evaluated PR");
113
+ const lineHits = diff ? _linkDiffToDecisions(diff, matchedRules, reasons) : [];
114
+ await getStores().gateHistory.record(
115
+ { prNumber, repo, service, actor, verdict, riskScore, reasons, sha },
116
+ req.tenantId
117
+ );
118
+ res.json({
119
+ ok: true,
120
+ verdict,
121
+ riskScore,
122
+ reasons,
123
+ recommendation,
124
+ corpusMatches: matchedRules,
125
+ lineHits,
126
+ diffSize: diffSizeReport,
127
+ meta: {
128
+ service,
129
+ inferredTags,
130
+ filesChecked: files.length,
131
+ actor,
132
+ blastRadius: { downstreamCount, threshold: BLAST_RADIUS_THRESHOLD, escalated: blastRadiusEscalated }
133
+ }
134
+ });
135
+ });
136
+ return router;
137
+ }
138
+ function inferService(explicit, files) {
139
+ if (explicit && explicit.trim()) return explicit.trim();
140
+ for (const file of files) {
141
+ const match = file.match(/^(?:apps|services|packages|src)\/([^/]+)\//);
142
+ if (match) return match[1];
143
+ }
144
+ return "unknown";
145
+ }
146
+ function inferTagsFromContext(files, prTitle) {
147
+ const tags = /* @__PURE__ */ new Set();
148
+ const dbFiles = files.some((f) => /db|database|migration|pool|sequelize|prisma|knex|typeorm/i.test(f));
149
+ if (dbFiles) {
150
+ tags.add("infra_db_connection_pool");
151
+ tags.add("n_plus_one_query");
152
+ }
153
+ if (files.some((f) => /auth|jwt|session|token|oauth|passport/i.test(f))) {
154
+ tags.add("auth_token_not_persisted");
155
+ }
156
+ if (files.some((f) => /cache|redis|memcache|memory|heap/i.test(f))) {
157
+ tags.add("oom_kill");
158
+ }
159
+ if (files.some((f) => /rate.?limit|throttl/i.test(f))) {
160
+ tags.add("rate_limit_cascade");
161
+ }
162
+ if (files.some((f) => /deploy|dockerfile|k8s|helm|\.yml|\.yaml|\.tf/i.test(f))) {
163
+ tags.add("deploy_config_drift");
164
+ }
165
+ const titleLower = prTitle.toLowerCase();
166
+ if (/pool|connection/.test(titleLower)) tags.add("infra_db_connection_pool");
167
+ if (/auth|token|session/.test(titleLower)) tags.add("auth_token_not_persisted");
168
+ if (/memory|oom|heap/.test(titleLower)) tags.add("oom_kill");
169
+ if (/rate.?limit|throttl/.test(titleLower)) tags.add("rate_limit_cascade");
170
+ if (/deploy|rollout|release/.test(titleLower)) tags.add("deploy_config_drift");
171
+ try {
172
+ const tagStats = postmortemStore.tagStats();
173
+ for (const { tag } of tagStats.slice(0, 20)) {
174
+ const tagSlug = tag.replace(/^infra_/, "").replace(/_/g, "");
175
+ if (files.some((f) => f.toLowerCase().replace(/[^a-z]/g, "").includes(tagSlug))) {
176
+ tags.add(tag);
177
+ }
178
+ }
179
+ } catch {
180
+ }
181
+ return [...tags];
182
+ }
183
+ function computeRiskScore(verdict, rules) {
184
+ if (verdict === "block") return Math.min(100, 70 + rules.length * 5);
185
+ if (verdict === "warn") return Math.min(69, 30 + rules.length * 10);
186
+ return Math.max(0, 10 - rules.length * 2);
187
+ }
188
+ function _linkDiffToDecisions(diff, matchedRules, reasons) {
189
+ const lines = diff.split("\n");
190
+ const hits = [];
191
+ const patterns = [];
192
+ for (const rule of matchedRules) {
193
+ const tag = rule.incidentTag ?? "";
194
+ const slug = tag.replace(/_/g, "[-_\\s]").replace(/\//g, "\\/");
195
+ if (slug) patterns.push({ pattern: new RegExp(slug, "i"), reason: `Corpus rule: ${tag} (${rule.overrideReason})` });
196
+ }
197
+ const semanticKeywords = [
198
+ { re: /DROP\s+TABLE|TRUNCATE|DELETE\s+FROM(?!\s+\w+\s+WHERE)/i, reason: "Destructive SQL operation" },
199
+ { re: /terraform\s+destroy|aws\s+s3\s+rm/i, reason: "Destructive infrastructure command" },
200
+ { re: /rm\s+-rf/i, reason: "Destructive filesystem command" },
201
+ { re: /ALTER\s+TABLE/i, reason: "Schema mutation" },
202
+ { re: /process\.env\.\w*(SECRET|KEY|TOKEN|PASS|PWD)/i, reason: "Potential secret exposure" },
203
+ { re: /kubectl\s+delete\s+namespace/i, reason: "Cluster-scope kubectl delete" }
204
+ ];
205
+ for (const kw of semanticKeywords) {
206
+ patterns.push({ pattern: kw.re, reason: kw.reason });
207
+ }
208
+ let lineNumber = 0;
209
+ for (const line of lines) {
210
+ lineNumber++;
211
+ if (!line.startsWith("+") || line.startsWith("+++")) continue;
212
+ const content = line.slice(1);
213
+ for (const { pattern, reason } of patterns) {
214
+ if (pattern.test(content)) {
215
+ if (!hits.some((h) => h.lineNumber === lineNumber)) {
216
+ hits.push({
217
+ lineNumber,
218
+ lineContent: content.slice(0, 200).trim(),
219
+ matchedPattern: pattern.source.slice(0, 80),
220
+ reason
221
+ });
222
+ }
223
+ break;
224
+ }
225
+ }
226
+ if (hits.length >= 20) break;
227
+ }
228
+ return hits;
229
+ }
230
+ export {
231
+ createCIGateRouter
232
+ };
@@ -0,0 +1,293 @@
1
+ import { Router } from "express";
2
+ import { store } from "../sensor/buffer.js";
3
+ import logger from "../sensor/logger.js";
4
+ function createCIRouter() {
5
+ const router = Router();
6
+ router.post("/ci/github", (req, res) => {
7
+ try {
8
+ const body = req.body;
9
+ const eventType = req.headers["x-github-event"];
10
+ let event = null;
11
+ if (eventType === "workflow_run" || body.workflow_run) {
12
+ const wr = body.workflow_run ?? body;
13
+ const sha = String(wr.head_sha ?? wr.sha ?? "");
14
+ event = {
15
+ type: "ci",
16
+ provider: "github_actions",
17
+ sha,
18
+ shortSha: sha.slice(0, 7),
19
+ branch: String(wr.head_branch ?? wr.branch ?? ""),
20
+ workflow: String(wr.name ?? wr.workflow ?? "workflow"),
21
+ job: String(wr.name ?? "run"),
22
+ status: normalizeGitHubStatus(String(wr.conclusion ?? wr.status ?? "unknown")),
23
+ durationMs: computeGitHubDuration(wr),
24
+ url: String(wr.html_url ?? wr.url ?? ""),
25
+ failedTests: [],
26
+ timestamp: parseGitHubTs(wr.updated_at ?? wr.created_at) ?? Date.now()
27
+ };
28
+ }
29
+ if (eventType === "check_run" || body.check_run) {
30
+ const cr = body.check_run ?? body;
31
+ const sha = String(cr.head_sha ?? "");
32
+ event = {
33
+ type: "ci",
34
+ provider: "github_actions",
35
+ sha,
36
+ shortSha: sha.slice(0, 7),
37
+ branch: "",
38
+ workflow: String(cr.app?.name ?? "GitHub"),
39
+ job: String(cr.name ?? "check"),
40
+ status: normalizeGitHubStatus(String(cr.conclusion ?? cr.status ?? "unknown")),
41
+ durationMs: computeGitHubDuration(cr),
42
+ url: String(cr.html_url ?? ""),
43
+ failedTests: [],
44
+ timestamp: parseGitHubTs(cr.completed_at ?? cr.started_at) ?? Date.now()
45
+ };
46
+ }
47
+ if (!event && body.sha) {
48
+ const sha = String(body.sha);
49
+ event = {
50
+ type: "ci",
51
+ provider: "github_actions",
52
+ sha,
53
+ shortSha: sha.slice(0, 7),
54
+ branch: String(body.branch ?? ""),
55
+ workflow: String(body.workflow ?? "workflow"),
56
+ job: String(body.job ?? "job"),
57
+ status: normalizeGitHubStatus(String(body.status ?? "unknown")),
58
+ durationMs: typeof body.duration_ms === "number" ? body.duration_ms : void 0,
59
+ url: String(body.url ?? body.run_url ?? ""),
60
+ failedTests: parseFailedTests(body.failed_tests),
61
+ timestamp: Date.now()
62
+ };
63
+ }
64
+ if (!event) {
65
+ res.status(400).json({ error: "unrecognised GitHub event shape" });
66
+ return;
67
+ }
68
+ store.push(event);
69
+ logger.info({ sha: event.sha.slice(0, 7), status: event.status, job: event.job }, "ci: github event ingested");
70
+ res.json({ ok: true, sha: event.sha, status: event.status });
71
+ } catch (err) {
72
+ logger.warn({ err }, "ci/github: parse error");
73
+ res.status(400).json({ error: "parse error" });
74
+ }
75
+ });
76
+ router.post("/ci/gitlab", (req, res) => {
77
+ try {
78
+ const body = req.body;
79
+ const sha = String(body.sha ?? body.checkout_sha ?? "");
80
+ const statusRaw = String(body.status ?? body.build_status ?? "unknown");
81
+ const event = {
82
+ type: "ci",
83
+ provider: "gitlab_ci",
84
+ sha,
85
+ shortSha: sha.slice(0, 7),
86
+ branch: String(body.ref ?? body.branch ?? "").replace("refs/heads/", ""),
87
+ workflow: String(body.pipeline?.name ?? body.project?.name ?? "pipeline"),
88
+ job: String(body.build_name ?? body.name ?? "job"),
89
+ status: normalizeGitLabStatus(statusRaw),
90
+ url: String(body.build_url ?? body.pipeline?.url ?? ""),
91
+ failedTests: parseFailedTests(body.failed_tests),
92
+ timestamp: Date.now()
93
+ };
94
+ store.push(event);
95
+ res.json({ ok: true, sha: event.sha, status: event.status });
96
+ } catch (err) {
97
+ logger.warn({ err }, "ci/gitlab: parse error");
98
+ res.status(400).json({ error: "parse error" });
99
+ }
100
+ });
101
+ router.post("/ci/azure-devops", (req, res) => {
102
+ try {
103
+ const body = req.body;
104
+ const resource = body.resource ?? body;
105
+ const eventType = String(body.eventType ?? body.event ?? "");
106
+ if (eventType && eventType !== "build.complete" && eventType !== "ms.vss-build.build-completed-event") {
107
+ res.json({ ok: true, skipped: true, reason: `eventType ${eventType} not processed` });
108
+ return;
109
+ }
110
+ const sha = String(resource.sourceVersion ?? resource.sourceVersionId ?? resource.commitId ?? "");
111
+ const definition = resource.definition ?? resource.plan ?? {};
112
+ const project = resource.project ?? body.resourceContainers?.project ?? {};
113
+ const links = resource._links ?? {};
114
+ const resultRaw = String(resource.result ?? resource.buildResult ?? "unknown").toLowerCase();
115
+ const status = resultRaw === "succeeded" ? "success" : resultRaw === "failed" || resultRaw === "partiallySucceeded" ? "failure" : resultRaw === "canceled" || resultRaw === "cancelled" ? "cancelled" : "failure";
116
+ const startMs = resource.startTime ? new Date(String(resource.startTime)).getTime() : null;
117
+ const finishMs = resource.finishTime ? new Date(String(resource.finishTime)).getTime() : null;
118
+ const durationMs = startMs && finishMs && finishMs > startMs ? finishMs - startMs : void 0;
119
+ const event = {
120
+ type: "ci",
121
+ provider: "azure_devops",
122
+ sha,
123
+ shortSha: sha.slice(0, 7),
124
+ branch: String(resource.sourceBranch ?? resource.branch ?? "").replace("refs/heads/", ""),
125
+ workflow: String(project.name ?? definition.name ?? "pipeline"),
126
+ job: String(resource.buildNumber ?? definition.name ?? "build"),
127
+ status,
128
+ durationMs,
129
+ url: links.web?.href ?? String(resource.url ?? resource.buildUrl ?? ""),
130
+ failedTests: [],
131
+ timestamp: finishMs ?? Date.now()
132
+ };
133
+ store.push(event);
134
+ logger.info({ sha: event.sha.slice(0, 7), status: event.status }, "ci/azure-devops: event ingested");
135
+ res.json({ ok: true, sha: event.sha, status: event.status });
136
+ } catch (err) {
137
+ logger.warn({ err }, "ci/azure-devops: parse error");
138
+ res.status(400).json({ error: "parse error" });
139
+ }
140
+ });
141
+ router.post("/ci/jenkins", (req, res) => {
142
+ try {
143
+ const body = req.body;
144
+ const build = body.build ?? body;
145
+ const scm = build.scm ?? {};
146
+ const phase = String(build.phase ?? "").toUpperCase();
147
+ if (phase && phase !== "COMPLETED" && phase !== "FINISHED") {
148
+ res.json({ ok: true, skipped: true, reason: `phase ${phase} not processed` });
149
+ return;
150
+ }
151
+ const sha = String(scm.commit ?? scm.sha ?? build.sha ?? "");
152
+ const statusRaw = String(build.status ?? build.result ?? "UNKNOWN").toUpperCase();
153
+ const status = statusRaw === "SUCCESS" ? "success" : statusRaw === "FAILURE" || statusRaw === "FAILED" ? "failure" : statusRaw === "ABORTED" ? "cancelled" : statusRaw === "UNSTABLE" ? "failure" : "failure";
154
+ const event = {
155
+ type: "ci",
156
+ provider: "jenkins",
157
+ sha,
158
+ shortSha: sha.slice(0, 7),
159
+ branch: String(scm.branch ?? build.branch ?? "").replace("refs/heads/", ""),
160
+ workflow: String(body.name ?? body.job ?? "pipeline"),
161
+ job: `${body.name ?? "build"} #${build.number ?? "?"}`,
162
+ status,
163
+ durationMs: typeof build.duration === "number" ? build.duration : void 0,
164
+ url: String(build.full_url ?? build.url ?? ""),
165
+ failedTests: parseFailedTests(build.test_summary ?? build.failedTests),
166
+ timestamp: Date.now()
167
+ };
168
+ store.push(event);
169
+ logger.info({ sha: event.sha.slice(0, 7), status: event.status }, "ci/jenkins: event ingested");
170
+ res.json({ ok: true, sha: event.sha, status: event.status });
171
+ } catch (err) {
172
+ logger.warn({ err }, "ci/jenkins: parse error");
173
+ res.status(400).json({ error: "parse error" });
174
+ }
175
+ });
176
+ router.post("/ci/generic", (req, res) => {
177
+ try {
178
+ const body = req.body;
179
+ if (!body.sha || !body.status) {
180
+ res.status(400).json({ error: "sha and status are required" });
181
+ return;
182
+ }
183
+ const sha = String(body.sha);
184
+ const event = {
185
+ type: "ci",
186
+ provider: body.provider ?? "unknown",
187
+ sha,
188
+ shortSha: sha.slice(0, 7),
189
+ branch: String(body.branch ?? ""),
190
+ workflow: String(body.workflow ?? ""),
191
+ job: String(body.job ?? "job"),
192
+ status: normalizeGenericStatus(String(body.status)),
193
+ durationMs: typeof body.duration_ms === "number" ? body.duration_ms : void 0,
194
+ url: String(body.url ?? ""),
195
+ failedTests: parseFailedTests(body.failed_tests),
196
+ timestamp: Date.now()
197
+ };
198
+ store.push(event);
199
+ res.json({ ok: true, sha: event.sha, status: event.status });
200
+ } catch (err) {
201
+ logger.warn({ err }, "ci/generic: parse error");
202
+ res.status(400).json({ error: "parse error" });
203
+ }
204
+ });
205
+ router.post("/deployments", (req, res) => {
206
+ try {
207
+ const body = req.body;
208
+ if (!body.sha || !body.environment || !body.status) {
209
+ res.status(400).json({ error: "sha, environment, and status are required" });
210
+ return;
211
+ }
212
+ const sha = String(body.sha);
213
+ const event = {
214
+ type: "deployment",
215
+ environment: String(body.environment),
216
+ sha,
217
+ shortSha: sha.slice(0, 7),
218
+ version: String(body.version ?? body.tag ?? ""),
219
+ service: String(body.service ?? body.app ?? ""),
220
+ status: normalizeDeployStatus(String(body.status)),
221
+ url: String(body.url ?? ""),
222
+ actor: String(body.actor ?? body.deployer ?? ""),
223
+ timestamp: Date.now()
224
+ };
225
+ store.push(event);
226
+ logger.info(
227
+ { sha: event.sha.slice(0, 7), env: event.environment, status: event.status },
228
+ "ci: deployment event ingested"
229
+ );
230
+ res.json({ ok: true, sha: event.sha, environment: event.environment, status: event.status });
231
+ } catch (err) {
232
+ logger.warn({ err }, "deployments: parse error");
233
+ res.status(400).json({ error: "parse error" });
234
+ }
235
+ });
236
+ return router;
237
+ }
238
+ function normalizeGitHubStatus(s) {
239
+ if (s === "success") return "success";
240
+ if (s === "failure" || s === "timed_out" || s === "startup_failure") return "failure";
241
+ if (s === "cancelled") return "cancelled";
242
+ if (s === "skipped" || s === "neutral") return "skipped";
243
+ return "failure";
244
+ }
245
+ function normalizeGitLabStatus(s) {
246
+ if (s === "success" || s === "passed") return "success";
247
+ if (s === "failed") return "failure";
248
+ if (s === "canceled") return "cancelled";
249
+ if (s === "skipped") return "skipped";
250
+ return "failure";
251
+ }
252
+ function normalizeGenericStatus(s) {
253
+ const lower = s.toLowerCase();
254
+ if (lower === "success" || lower === "pass" || lower === "passed") return "success";
255
+ if (lower === "failure" || lower === "fail" || lower === "failed") return "failure";
256
+ if (lower === "cancelled" || lower === "canceled") return "cancelled";
257
+ if (lower === "skipped") return "skipped";
258
+ return "failure";
259
+ }
260
+ function normalizeDeployStatus(s) {
261
+ const lower = s.toLowerCase();
262
+ if (lower === "started" || lower === "in_progress") return "started";
263
+ if (lower === "success" || lower === "deployed") return "success";
264
+ if (lower === "failure" || lower === "failed") return "failure";
265
+ if (lower === "rollback") return "rollback";
266
+ return "started";
267
+ }
268
+ function computeGitHubDuration(obj) {
269
+ const start = parseGitHubTs(obj.run_started_at ?? obj.started_at ?? obj.created_at);
270
+ const end = parseGitHubTs(obj.updated_at ?? obj.completed_at);
271
+ if (start && end && end > start) return end - start;
272
+ return void 0;
273
+ }
274
+ function parseGitHubTs(val) {
275
+ if (!val) return null;
276
+ const d = new Date(String(val));
277
+ return isNaN(d.getTime()) ? null : d.getTime();
278
+ }
279
+ function parseFailedTests(val) {
280
+ if (!Array.isArray(val)) return [];
281
+ return val.slice(0, 50).map((t) => {
282
+ if (typeof t === "string") return { name: t };
283
+ const obj = t;
284
+ return {
285
+ name: String(obj.name ?? obj.title ?? "unknown"),
286
+ error: obj.error ? String(obj.error) : void 0,
287
+ file: obj.file ? String(obj.file) : void 0
288
+ };
289
+ });
290
+ }
291
+ export {
292
+ createCIRouter
293
+ };
@@ -0,0 +1,24 @@
1
+ import { Router } from "express";
2
+ import { buildComplianceReport, renderComplianceHtml } from "../intelligence/compliance-report.js";
3
+ function createComplianceReportRouter() {
4
+ const router = Router();
5
+ router.get("/compliance/report", async (req, res) => {
6
+ const now = Date.now();
7
+ const format = (req.query.format ?? "json").toLowerCase();
8
+ const from = Number(req.query.from ?? now - 30 * 24 * 60 * 60 * 1e3);
9
+ const to = Number(req.query.to ?? now);
10
+ const report = await buildComplianceReport(from, to);
11
+ if (format === "html") {
12
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
13
+ res.setHeader("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'");
14
+ res.setHeader("Cache-Control", "no-store");
15
+ res.send(renderComplianceHtml(report));
16
+ return;
17
+ }
18
+ res.json({ ok: true, report });
19
+ });
20
+ return router;
21
+ }
22
+ export {
23
+ createComplianceReportRouter
24
+ };
@@ -0,0 +1,30 @@
1
+ import { Router } from "express";
2
+ import { confidenceStore, ConfidenceReportSchema } from "../intelligence/confidence-report.js";
3
+ function createConfidenceRouter() {
4
+ const router = Router();
5
+ router.post("/confidence-reports", (req, res) => {
6
+ const parsed = ConfidenceReportSchema.safeParse(req.body);
7
+ if (!parsed.success) {
8
+ res.status(400).json({ error: "validation failed", details: parsed.error.issues });
9
+ return;
10
+ }
11
+ const report = confidenceStore.add(parsed.data);
12
+ res.status(201).json({ ok: true, report });
13
+ });
14
+ router.get("/confidence-reports", (req, res) => {
15
+ const limit = Math.min(100, Math.max(1, Number(req.query.limit ?? 20)));
16
+ res.json({ ok: true, reports: confidenceStore.list(limit) });
17
+ });
18
+ router.get("/confidence-reports/:id", (req, res) => {
19
+ const report = confidenceStore.get(req.params.id);
20
+ if (!report) {
21
+ res.status(404).json({ error: "report not found" });
22
+ return;
23
+ }
24
+ res.json({ ok: true, report });
25
+ });
26
+ return router;
27
+ }
28
+ export {
29
+ createConfidenceRouter
30
+ };