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,708 @@
1
+ import { randomUUID, createHash } from "crypto";
2
+ import { hostname } from "os";
3
+ import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { evaluateEnterprisePolicy, loadEnterprisePolicy } from "./enterprise-policy-engine.js";
5
+ import { isPolicyDryRun } from "./execution-mode.js";
6
+ import { getTraceId } from "./trace-context.js";
7
+ import { computeRiskScore, getRiskHoldThreshold } from "./risk-score.js";
8
+ import { verifyNoExecve } from "./ebpf-verify.js";
9
+ import { findSimilarIncidents } from "./incident-similarity.js";
10
+ import { parseShellCommand } from "./shell-ast.js";
11
+ import { detectScriptInvocation, detectScriptInvocationFromRawText, evaluateScriptTrust } from "./script-trust.js";
12
+ import { computeBlastRadius, mostSevereBlast } from "./blast-radius.js";
13
+ import { getStores } from "../storage/store-registry.js";
14
+ import {
15
+ recordGateBlock,
16
+ recordGatePass,
17
+ recordGateCoverage,
18
+ recordGateEvent,
19
+ recordHitlHold
20
+ } from "./gate-analytics.js";
21
+ import { trackBlock, trackSuccessfulCall } from "../sensor/bypass-tracker.js";
22
+ import { recordActivity } from "./activity-feed.js";
23
+ import { checkAgentProfile } from "./agent-profiles.js";
24
+ import { resolveAgentIdentity } from "./agent-identity.js";
25
+ import {
26
+ detectSequenceThreat,
27
+ recordSessionCall,
28
+ markContaminated,
29
+ isSessionContaminated,
30
+ getContaminationSource,
31
+ updateAgentReputation,
32
+ getAgentScrutinyTier,
33
+ READ_ONLY_TOOLS
34
+ } from "./session-threat-tracker.js";
35
+ import logger from "../sensor/logger.js";
36
+ import { assertGateHeartbeatFresh } from "../sensor/gate-heartbeat.js";
37
+ import { registerBypassBlock, checkAndConsumeBypass } from "./bypass.js";
38
+ import { holdToolCall } from "./hitl-hold.js";
39
+ import { detectInjection, extractAllStrings, getSuggestedAlternative } from "./gate-decision.js";
40
+ import {
41
+ setBypassSecret,
42
+ registerBypassBlock as registerBypassBlock2,
43
+ approveBypass,
44
+ checkAndConsumeBypass as checkAndConsumeBypass2,
45
+ getPendingBypasses,
46
+ getPendingBypassDetail,
47
+ invalidateBypassToken,
48
+ persistBypasses,
49
+ loadBypasses
50
+ } from "./bypass.js";
51
+ import {
52
+ denyStaleHoldsOnStartup,
53
+ approveToolCall,
54
+ approveToolCallConstrained,
55
+ denyToolCall,
56
+ getPendingHolds
57
+ } from "./hitl-hold.js";
58
+ let _warnedNoAgentId = false;
59
+ function _resolveAgentId() {
60
+ const { agentId } = resolveAgentIdentity();
61
+ if (agentId && agentId !== "agent") return agentId;
62
+ const fp = createHash("sha256").update(`${hostname()}:${process.ppid ?? 0}`).digest("hex").slice(0, 8);
63
+ if (!_warnedNoAgentId) {
64
+ logger.info({ fingerprint: `env_${fp}` }, "tool-guard: no agent identity configured \u2014 using process fingerprint for reputation tracking");
65
+ _warnedNoAgentId = true;
66
+ }
67
+ return `env_${fp}`;
68
+ }
69
+ async function _checkScriptTrustForRaw(raw) {
70
+ const parsed = parseShellCommand(raw);
71
+ if (!parsed) {
72
+ const heuristic = detectScriptInvocationFromRawText(raw);
73
+ if (!heuristic) return { escalate: false };
74
+ const trust = await evaluateScriptTrust(heuristic.scriptPath, process.cwd());
75
+ if (!trust.trusted) {
76
+ return {
77
+ escalate: true,
78
+ reason: `Script invocation \`${heuristic.interpreter} ${heuristic.scriptPath}\` (matched via raw-text fallback \u2014 command could not be fully structurally parsed) is not trusted: ${trust.reasons.join("; ")}`
79
+ };
80
+ }
81
+ return { escalate: false };
82
+ }
83
+ for (const cmd of parsed) {
84
+ const invocation = detectScriptInvocation(cmd);
85
+ if (!invocation) continue;
86
+ const trust = await evaluateScriptTrust(invocation.scriptPath, process.cwd());
87
+ if (!trust.trusted) {
88
+ return {
89
+ escalate: true,
90
+ reason: `Script invocation \`${invocation.interpreter} ${invocation.scriptPath}\` is not trusted: ${trust.reasons.join("; ")}`
91
+ };
92
+ }
93
+ }
94
+ return { escalate: false };
95
+ }
96
+ async function _checkScriptTrustEscalation(argStrings, toolName) {
97
+ const candidates = argStrings.filter((s) => s && s.length > 0);
98
+ const toScan = candidates.length > 0 ? candidates : [toolName];
99
+ for (const raw of toScan) {
100
+ const res = await _checkScriptTrustForRaw(raw);
101
+ if (res.escalate) return res;
102
+ }
103
+ return { escalate: false };
104
+ }
105
+ async function applyGate(toolName, args, next, port, opts) {
106
+ try {
107
+ return await applyGateInner(toolName, args, next, port, opts);
108
+ } catch (err) {
109
+ logger.error({ err, toolName }, "tool-guard: applyGate threw an unexpected error \u2014 failing closed");
110
+ await getStores().blunders.record({
111
+ blunderType: "pipeline_block",
112
+ command: toolName,
113
+ blockReason: `Gate threw an unexpected error and failed closed: ${err instanceof Error ? err.message : String(err)}`,
114
+ service: "mcp",
115
+ tag: "gate_internal_error",
116
+ actor: "agent",
117
+ pid: null,
118
+ confidenceScore: null
119
+ });
120
+ return {
121
+ content: [{
122
+ type: "text",
123
+ text: [
124
+ `\u{1F6AB} **Mergen fail-closed: the local execution gate hit an internal error while evaluating this call.**`,
125
+ ``,
126
+ `**Tool:** \`${toolName}\``,
127
+ ``,
128
+ `_The tool call was not executed. This event has been logged to the Agent Blunder Log._`
129
+ ].join("\n")
130
+ }],
131
+ isError: true
132
+ };
133
+ }
134
+ }
135
+ async function applyGateInner(toolName, args, next, port, opts) {
136
+ const t0 = Date.now();
137
+ const heartbeat = assertGateHeartbeatFresh();
138
+ if (!heartbeat.ok) {
139
+ recordGateBlock(["gate_heartbeat_stale"]);
140
+ await getStores().blunders.record({
141
+ blunderType: "pipeline_block",
142
+ command: toolName,
143
+ blockReason: `Gate heartbeat fail-closed: ${heartbeat.reason}`,
144
+ service: "mcp",
145
+ tag: "gate_heartbeat",
146
+ actor: "agent",
147
+ pid: null,
148
+ confidenceScore: null,
149
+ triggeredRules: ["gate_heartbeat_stale"]
150
+ });
151
+ logger.error({ toolName, reason: heartbeat.reason }, "tool-guard: gate heartbeat stale \u2014 failing closed");
152
+ return {
153
+ content: [{
154
+ type: "text",
155
+ text: [
156
+ `\u{1F6AB} **Mergen fail-closed: local execution gate heartbeat is not fresh.**`,
157
+ ``,
158
+ `**Tool:** \`${toolName}\``,
159
+ `**Why:** ${heartbeat.reason}`,
160
+ ``,
161
+ `_Restart Mergen or restore the gate heartbeat before executing agent actions._`
162
+ ].join("\n")
163
+ }],
164
+ isError: true
165
+ };
166
+ }
167
+ const argsSnapshot = JSON.stringify(args ?? {});
168
+ const argsObj = args && typeof args === "object" ? args : {};
169
+ const commandArg = argsObj.command ?? argsObj.cmd ?? argsObj.fix ?? "";
170
+ const allArgStrings = extractAllStrings(args);
171
+ const agentId = _resolveAgentId();
172
+ const sessionId = agentId;
173
+ const identity = opts?.verifiedAgentId ? void 0 : resolveAgentIdentity();
174
+ const verifiedAgentId = opts?.verifiedAgentId ?? (identity?.verified ? identity.agentId : void 0);
175
+ const agentIdLabel = opts?.verifiedAgentId ?? identity?.agentId ?? void 0;
176
+ const parentAgentId = opts?.verifiedAgentId ? opts?.verifiedParentAgentId : identity?.parentAgentId;
177
+ const traceId = getTraceId();
178
+ const { threat: sequenceThreat, label: sequenceLabel } = detectSequenceThreat(sessionId, commandArg || argsSnapshot.slice(0, 200));
179
+ if (sequenceThreat) {
180
+ updateAgentReputation(agentId, "sequence");
181
+ recordGateBlock(["sequence_threat"]);
182
+ await getStores().blunders.record({
183
+ blunderType: "pipeline_block",
184
+ command: toolName,
185
+ blockReason: `Multi-turn threat sequence detected: ${sequenceLabel}`,
186
+ service: "mcp",
187
+ tag: "sequence_threat",
188
+ actor: agentId,
189
+ pid: null,
190
+ confidenceScore: null,
191
+ triggeredRules: ["sequence_threat"]
192
+ });
193
+ recordSessionCall(sessionId, toolName, commandArg, "BLOCK");
194
+ recordActivity({ toolName, commandArg, verdict: "BLOCK", triggeredRules: ["sequence_threat"], ruleNames: ["Multi-Turn Threat Sequence"] });
195
+ recordGateEvent({
196
+ ts: Date.now(),
197
+ toolName,
198
+ command: commandArg || null,
199
+ actor: "agent",
200
+ agentId: agentIdLabel ?? null,
201
+ service: "mcp",
202
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
203
+ verdict: "block",
204
+ triggeredRules: ["sequence_threat"],
205
+ guidedAlternative: null,
206
+ reason: `Multi-turn threat sequence detected: ${sequenceLabel}`,
207
+ traceId
208
+ });
209
+ return {
210
+ content: [{
211
+ type: "text",
212
+ text: [
213
+ `\u{1F6AB} **Mergen blocked this tool call: multi-turn threat sequence detected.**`,
214
+ ``,
215
+ `**Tool:** \`${toolName}\``,
216
+ `**Pattern:** \`${sequenceLabel}\``,
217
+ `**Why:** The sequence of recent tool calls matches a known multi-step attack chain.`,
218
+ ``,
219
+ `_This event has been logged to the Agent Blunder Log._`
220
+ ].join("\n")
221
+ }],
222
+ isError: true
223
+ };
224
+ }
225
+ const injectionMatch = detectInjection(argsSnapshot);
226
+ if (injectionMatch) {
227
+ updateAgentReputation(agentId, "injection");
228
+ markContaminated(sessionId, injectionMatch, 5);
229
+ recordGateBlock(["injection_attempt"]);
230
+ await getStores().blunders.record({
231
+ blunderType: "injection_attempt",
232
+ command: toolName,
233
+ blockReason: `Prompt injection pattern detected in tool arguments: ${injectionMatch}`,
234
+ service: "mcp",
235
+ tag: "injection",
236
+ actor: agentId,
237
+ pid: null,
238
+ confidenceScore: null,
239
+ triggeredRules: ["injection_attempt"]
240
+ });
241
+ logger.warn({ toolName, injectionMatch }, "tool-guard: prompt injection attempt detected in args");
242
+ recordSessionCall(sessionId, toolName, commandArg, "BLOCK");
243
+ recordActivity({ toolName, commandArg, verdict: "BLOCK", triggeredRules: ["injection_attempt"], ruleNames: ["Prompt Injection"] });
244
+ recordGateEvent({
245
+ ts: Date.now(),
246
+ toolName,
247
+ command: commandArg || null,
248
+ actor: "agent",
249
+ agentId: agentIdLabel ?? null,
250
+ service: "mcp",
251
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
252
+ verdict: "block",
253
+ triggeredRules: ["injection_attempt"],
254
+ guidedAlternative: null,
255
+ reason: `Prompt injection pattern detected in tool arguments: ${injectionMatch}`,
256
+ traceId
257
+ });
258
+ return {
259
+ content: [{
260
+ type: "text",
261
+ text: `\u{1F6AB} **Mergen blocked this tool call: prompt injection pattern detected.**
262
+
263
+ **Tool:** \`${toolName}\`
264
+
265
+ _This event has been logged to the Agent Blunder Log._`
266
+ }],
267
+ isError: true
268
+ };
269
+ }
270
+ if (checkAndConsumeBypass(toolName, commandArg)) {
271
+ const hardEval = evaluateEnterprisePolicy({
272
+ files: [toolName],
273
+ commands: [toolName, ...allArgStrings],
274
+ actor: "agent",
275
+ service: "mcp",
276
+ timestamp: Date.now(),
277
+ environment: process.env.MERGEN_ENVIRONMENT ?? void 0,
278
+ repo: process.env.MERGEN_REPO ?? void 0,
279
+ agentId: verifiedAgentId
280
+ });
281
+ if (hardEval.verdict === "block") {
282
+ const hardReason = hardEval.reasons.join("; ");
283
+ recordGateBlock(hardEval.triggeredRules);
284
+ await getStores().blunders.record({
285
+ blunderType: "pipeline_block",
286
+ command: toolName,
287
+ blockReason: `Hard policy block \u2014 bypass cannot override: ${hardReason}`,
288
+ service: "mcp",
289
+ tag: "tool_guard",
290
+ actor: "agent",
291
+ pid: null,
292
+ confidenceScore: null,
293
+ triggeredRules: hardEval.triggeredRules
294
+ });
295
+ logger.warn({ toolName, reason: hardReason }, "tool-guard: hard block rule rejected bypass approval");
296
+ recordGateEvent({
297
+ ts: Date.now(),
298
+ toolName,
299
+ command: commandArg || null,
300
+ actor: "agent",
301
+ agentId: agentIdLabel ?? null,
302
+ service: "mcp",
303
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
304
+ verdict: "block",
305
+ triggeredRules: hardEval.triggeredRules,
306
+ guidedAlternative: null,
307
+ reason: `Hard policy block \u2014 bypass cannot override: ${hardReason}`,
308
+ traceId
309
+ });
310
+ return {
311
+ content: [{
312
+ type: "text",
313
+ text: [
314
+ `\u{1F6AB} **Hard policy block \u2014 this action cannot be bypassed by operator approval.**`,
315
+ ``,
316
+ `**Tool:** \`${toolName}\``,
317
+ `**Why:** ${hardReason}`,
318
+ ``,
319
+ `_Hard blocks are immutable safety guardrails. Modify \`~/.mergen/enterprise-policy.json\` to change the rule itself._`
320
+ ].join("\n")
321
+ }],
322
+ isError: true
323
+ };
324
+ }
325
+ logger.info({ toolName, commandArg }, "tool-guard: approved bypass consumed, tool call passing");
326
+ recordGatePass();
327
+ trackSuccessfulCall(toolName);
328
+ recordGateEvent({
329
+ ts: Date.now(),
330
+ toolName,
331
+ command: commandArg || null,
332
+ actor: "agent",
333
+ agentId: agentIdLabel ?? null,
334
+ service: "mcp",
335
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
336
+ verdict: "pass",
337
+ triggeredRules: [],
338
+ guidedAlternative: null,
339
+ reason: "operator bypass approved",
340
+ traceId
341
+ });
342
+ return next();
343
+ }
344
+ const profileBlock = checkAgentProfile(toolName, void 0, verifiedAgentId);
345
+ if (profileBlock) {
346
+ recordGateBlock([]);
347
+ await getStores().blunders.record({
348
+ blunderType: "rbac_block",
349
+ command: toolName,
350
+ blockReason: profileBlock,
351
+ service: "mcp",
352
+ tag: "agent_profile",
353
+ actor: agentIdLabel ?? "agent",
354
+ pid: null,
355
+ confidenceScore: null,
356
+ triggeredRules: ["agent_profile"]
357
+ });
358
+ recordActivity({ toolName, commandArg, verdict: "BLOCK", triggeredRules: ["agent_profile"], ruleNames: ["Agent Profile Block"] });
359
+ recordGateEvent({
360
+ ts: Date.now(),
361
+ toolName,
362
+ command: commandArg || null,
363
+ actor: "agent",
364
+ agentId: agentIdLabel ?? null,
365
+ service: "mcp",
366
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
367
+ verdict: "block",
368
+ triggeredRules: ["agent_profile"],
369
+ guidedAlternative: null,
370
+ reason: profileBlock,
371
+ traceId
372
+ });
373
+ return {
374
+ content: [{
375
+ type: "text",
376
+ text: `\u{1F6AB} **Mergen agent profile gate blocked this tool call.**
377
+
378
+ **Tool:** \`${toolName}\`
379
+ **Why:** ${profileBlock}
380
+
381
+ _Adjust permissions at: mergen-server agent-update ${verifiedAgentId ?? ""}_`
382
+ }],
383
+ isError: true
384
+ };
385
+ }
386
+ const evaluation = evaluateEnterprisePolicy({
387
+ files: [toolName],
388
+ commands: [toolName, ...allArgStrings],
389
+ actor: "agent",
390
+ service: "mcp",
391
+ timestamp: Date.now(),
392
+ environment: process.env.MERGEN_ENVIRONMENT ?? void 0,
393
+ repo: process.env.MERGEN_REPO ?? void 0,
394
+ agentId: verifiedAgentId
395
+ });
396
+ const evalMs = Date.now() - t0;
397
+ if (evalMs > 10) {
398
+ logger.warn({ evalMs, toolName }, "tool-guard: policy evaluation exceeded 10ms target");
399
+ }
400
+ recordGateCoverage(toolName, evaluation.triggeredRules);
401
+ if (evaluation.verdict === "pass") {
402
+ const blastCandidates = (allArgStrings.length > 0 ? allArgStrings : [commandArg || toolName]).map((s) => computeBlastRadius(s));
403
+ const blast = mostSevereBlast(blastCandidates);
404
+ if (!blast.reversible && blast.dataAtRisk && blast.scope === "data-destructive") {
405
+ evaluation.verdict = "warn";
406
+ evaluation.triggeredRules.push("blast_radius_gate");
407
+ evaluation.reasons.push(`Auto-hold: irreversible data operation detected (scope: ${blast.scope})`);
408
+ logger.info({ toolName, scope: blast.scope }, "tool-guard: blast radius gate upgraded pass \u2192 hold");
409
+ } else {
410
+ const contaminated = isSessionContaminated(sessionId);
411
+ if (contaminated) {
412
+ const source = getContaminationSource(sessionId);
413
+ evaluation.verdict = "warn";
414
+ evaluation.triggeredRules.push("contamination_hold");
415
+ evaluation.reasons.push(`Session contaminated by prior injection attempt (pattern: ${source}). Human review required.`);
416
+ logger.info({ sessionId, toolName }, "tool-guard: contamination hold \u2014 upgrading PASS to HOLD");
417
+ } else {
418
+ const scriptTrust = await _checkScriptTrustEscalation(allArgStrings, toolName);
419
+ if (scriptTrust.escalate) {
420
+ evaluation.verdict = "warn";
421
+ evaluation.triggeredRules.push("script_trust_hold");
422
+ evaluation.reasons.push(scriptTrust.reason ?? "Untrusted script invocation requires human review.");
423
+ logger.info({ toolName, reason: scriptTrust.reason }, "tool-guard: script-trust check upgraded pass \u2192 hold");
424
+ } else {
425
+ const tier = getAgentScrutinyTier(agentId);
426
+ if (tier === "high" && !READ_ONLY_TOOLS.has(toolName)) {
427
+ evaluation.verdict = "warn";
428
+ evaluation.triggeredRules.push("high_scrutiny_hold");
429
+ evaluation.reasons.push(`Agent ${agentId} is under high scrutiny (repeated injection/sequence threats). Human review required.`);
430
+ logger.info({ agentId, toolName }, "tool-guard: high-scrutiny hold applied");
431
+ } else if (tier === "elevated" && !blast.reversible && blast.scope !== "unknown") {
432
+ evaluation.verdict = "warn";
433
+ evaluation.triggeredRules.push("elevated_scrutiny_hold");
434
+ evaluation.reasons.push(`Agent ${agentId} is under elevated scrutiny (repeated blocks). Non-reversible command requires human review.`);
435
+ logger.info({ agentId, toolName, scope: blast.scope }, "tool-guard: elevated-scrutiny hold applied");
436
+ } else {
437
+ const riskThreshold = getRiskHoldThreshold();
438
+ let riskEscalated = false;
439
+ if (riskThreshold !== null) {
440
+ const similarIncidentCount = findSimilarIncidents({ commandText: commandArg || toolName }).length;
441
+ const risk = computeRiskScore({
442
+ blastReversible: blast.reversible,
443
+ blastDataAtRisk: blast.dataAtRisk,
444
+ blastScope: blast.scope,
445
+ contaminated: false,
446
+ // already checked false to reach this branch
447
+ scrutinyTier: getAgentScrutinyTier(agentId),
448
+ similarIncidentCount
449
+ }, riskThreshold);
450
+ if (risk.escalateToHold) {
451
+ riskEscalated = true;
452
+ evaluation.verdict = "warn";
453
+ evaluation.triggeredRules.push("risk_score_hold");
454
+ evaluation.reasons.push(`Auto-hold: composite risk score ${risk.riskScore} met the configured threshold (${riskThreshold}). Human review required.`);
455
+ logger.info({ toolName, riskScore: risk.riskScore, threshold: riskThreshold }, "tool-guard: risk score upgraded pass \u2192 hold");
456
+ }
457
+ }
458
+ if (!riskEscalated) {
459
+ recordGatePass();
460
+ trackSuccessfulCall(toolName);
461
+ recordSessionCall(sessionId, toolName, commandArg, "PASS");
462
+ recordActivity({ toolName, commandArg, verdict: "PASS", triggeredRules: [], ruleNames: [] });
463
+ recordGateEvent({
464
+ ts: Date.now(),
465
+ toolName,
466
+ command: commandArg || null,
467
+ actor: "agent",
468
+ agentId: agentIdLabel ?? null,
469
+ service: "mcp",
470
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
471
+ verdict: "pass",
472
+ triggeredRules: [],
473
+ guidedAlternative: null,
474
+ traceId
475
+ });
476
+ return next();
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+ }
483
+ const reason = evaluation.reasons.join("; ");
484
+ const alternative = getSuggestedAlternative(evaluation.triggeredRules, commandArg);
485
+ if (isPolicyDryRun()) {
486
+ const gateEventVerdict = evaluation.verdict === "block" ? "block" : "hold";
487
+ logger.info(
488
+ { toolName, verdict: evaluation.verdict, reason, triggeredRules: evaluation.triggeredRules },
489
+ `tool-guard: [DRY RUN] would have ${evaluation.verdict} \u2014 passing through`
490
+ );
491
+ await getStores().blunders.record({
492
+ blunderType: "pipeline_block",
493
+ command: toolName,
494
+ blockReason: `[DRY RUN] ${reason}`,
495
+ service: "mcp",
496
+ tag: "policy_dry_run",
497
+ actor: "agent",
498
+ pid: null,
499
+ confidenceScore: null,
500
+ triggeredRules: evaluation.triggeredRules,
501
+ agentId: agentIdLabel ?? null,
502
+ parentAgentId: parentAgentId ?? null,
503
+ traceId
504
+ });
505
+ recordGateEvent({
506
+ ts: Date.now(),
507
+ toolName,
508
+ command: commandArg || null,
509
+ actor: "agent",
510
+ agentId: agentIdLabel ?? null,
511
+ service: "mcp",
512
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
513
+ verdict: gateEventVerdict,
514
+ triggeredRules: evaluation.triggeredRules,
515
+ guidedAlternative: alternative,
516
+ reason: reason || null,
517
+ traceId
518
+ });
519
+ return next();
520
+ }
521
+ if (evaluation.verdict === "block") {
522
+ updateAgentReputation(agentId, "block");
523
+ recordGateBlock(evaluation.triggeredRules);
524
+ trackBlock(toolName, evaluation.triggeredRules);
525
+ await getStores().blunders.record({
526
+ blunderType: "pipeline_block",
527
+ command: toolName,
528
+ blockReason: reason,
529
+ service: "mcp",
530
+ tag: "tool_guard",
531
+ actor: "agent",
532
+ pid: null,
533
+ confidenceScore: null,
534
+ triggeredRules: evaluation.triggeredRules,
535
+ agentId: agentIdLabel ?? null,
536
+ parentAgentId: parentAgentId ?? null,
537
+ traceId
538
+ });
539
+ logger.warn({ toolName, reason }, "tool-guard: tool call blocked by local policy");
540
+ const ebpfCommandHint = (commandArg || toolName).trim().split(/\s+/)[0] || toolName;
541
+ void verifyNoExecve(ebpfCommandHint).then((verified) => {
542
+ if (verified === "verified-executed") {
543
+ logger.error({ toolName, commandHint: ebpfCommandHint, reason }, "ebpf-verify: BLOCKED command executed anyway \u2014 gate bypass or race condition, investigate immediately");
544
+ } else if (verified === "verified-not-executed") {
545
+ logger.info({ toolName, commandHint: ebpfCommandHint }, "ebpf-verify: confirmed the blocked execve never occurred");
546
+ }
547
+ }).catch(() => {
548
+ });
549
+ recordSessionCall(sessionId, toolName, commandArg, "BLOCK");
550
+ recordGateEvent({
551
+ ts: Date.now(),
552
+ toolName,
553
+ command: commandArg || null,
554
+ actor: "agent",
555
+ agentId: agentIdLabel ?? null,
556
+ service: "mcp",
557
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
558
+ verdict: "block",
559
+ triggeredRules: evaluation.triggeredRules,
560
+ guidedAlternative: alternative,
561
+ reason: reason || null,
562
+ traceId
563
+ });
564
+ const bypassToken = registerBypassBlock(toolName, commandArg, evaluation.triggeredRules);
565
+ logger.info(
566
+ { bypassToken, toolName, commandArg },
567
+ `tool-guard: bypass approval required \u2014 operator can run: mergen approve ${bypassToken}`
568
+ );
569
+ const ruleNames = loadEnterprisePolicy().rules.filter((r) => evaluation.triggeredRules.includes(r.id)).map((r) => r.name);
570
+ recordActivity({ toolName, commandArg, verdict: "BLOCK", triggeredRules: evaluation.triggeredRules, ruleNames });
571
+ return {
572
+ content: [{
573
+ type: "text",
574
+ text: [
575
+ `\u{1F6AB} **Mergen policy gate blocked this tool call.**`,
576
+ ``,
577
+ `**Tool:** \`${toolName}\``,
578
+ `**Why:** ${reason}`,
579
+ ``,
580
+ `**What to do instead:** ${alternative}`,
581
+ ``,
582
+ `_Action logged to the Agent Blunder Log. An operator must approve this action via the Mergen terminal or Slack. To modify this policy: \`~/.mergen/enterprise-policy.json\`._`
583
+ ].join("\n")
584
+ }],
585
+ isError: true
586
+ };
587
+ }
588
+ recordSessionCall(sessionId, toolName, commandArg, "HOLD");
589
+ const token = randomUUID();
590
+ const heldAt = Date.now();
591
+ logger.info({ toolName, token, reason }, "tool-guard: tool call held for HITL approval");
592
+ recordGateEvent({
593
+ ts: heldAt,
594
+ toolName,
595
+ command: commandArg || null,
596
+ actor: "agent",
597
+ agentId: agentIdLabel ?? null,
598
+ service: "mcp",
599
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
600
+ verdict: "hold",
601
+ triggeredRules: evaluation.triggeredRules,
602
+ guidedAlternative: alternative,
603
+ reason: reason || null,
604
+ traceId
605
+ });
606
+ recordHitlHold();
607
+ const holdRuleNames = loadEnterprisePolicy().rules.filter((r) => evaluation.triggeredRules.includes(r.id)).map((r) => r.name);
608
+ recordActivity({ toolName, commandArg, verdict: "HOLD", triggeredRules: evaluation.triggeredRules, ruleNames: holdRuleNames });
609
+ return holdToolCall({
610
+ token,
611
+ toolName,
612
+ argsSnapshot,
613
+ reason,
614
+ triggeredRules: evaluation.triggeredRules,
615
+ heldAt,
616
+ commandArg,
617
+ suggestedAlt: alternative,
618
+ port
619
+ });
620
+ }
621
+ let _gateInstalled = false;
622
+ function isGateInstalled() {
623
+ return _gateInstalled;
624
+ }
625
+ function _resetGateInstalledForTesting() {
626
+ _gateInstalled = false;
627
+ }
628
+ const _installedGatedHandlers = /* @__PURE__ */ new WeakMap();
629
+ function createGuardedServer(server, port) {
630
+ if (server._toolHandlersInitialized) {
631
+ throw new Error(
632
+ "createGuardedServer: a tool was already registered on this McpServer before the gate was installed \u2014 the tools/call dispatch handler is already active and unpatched. Call createGuardedServer(mcp, port) before any registerTool/tool/registerToolTask call, never after."
633
+ );
634
+ }
635
+ const lowLevel = server.server;
636
+ const originalSetRequestHandler = lowLevel.setRequestHandler.bind(lowLevel);
637
+ lowLevel.setRequestHandler = (schema, handler) => {
638
+ if (schema !== CallToolRequestSchema) {
639
+ return originalSetRequestHandler(schema, handler);
640
+ }
641
+ const gated = (request, extra) => {
642
+ const r = request;
643
+ const toolName = r?.params?.name ?? "unknown_tool";
644
+ const args = r?.params?.arguments;
645
+ return applyGate(toolName, args, () => Promise.resolve(handler(request, extra)), port);
646
+ };
647
+ _installedGatedHandlers.set(server, gated);
648
+ return originalSetRequestHandler(schema, gated);
649
+ };
650
+ _gateInstalled = true;
651
+ return server;
652
+ }
653
+ async function assertGateCoversRegisteredTools(server) {
654
+ if (!isGateInstalled()) {
655
+ throw new Error("assertGateCoversRegisteredTools: createGuardedServer was never called on this server.");
656
+ }
657
+ const registeredTools = server._registeredTools ?? {};
658
+ const toolNames = Object.keys(registeredTools);
659
+ if (toolNames.length === 0) {
660
+ throw new Error(
661
+ `assertGateCoversRegisteredTools: no tools are registered on this server \u2014 registerTools() appears to have registered nothing. Refusing to start with an empty, unverifiable tool set. If you are building this repository from source without the closed-source intelligence/tools.ts (the proprietary MCP tool registry \u2014 see intelligence/LICENSE), this is expected: registerTools() resolves to a no-op stub (__stubs__/closed-source.ts) and the server correctly refuses to boot with zero verifiable tools rather than silently running ungoverned. See this repo's CONTRIBUTING.md, "Running the server locally", for what an external contributor can do without the private layer.`
662
+ );
663
+ }
664
+ const installedHandler = _installedGatedHandlers.get(server);
665
+ if (!installedHandler) {
666
+ throw new Error(
667
+ "assertGateCoversRegisteredTools: no tools/call dispatch handler is installed \u2014 setToolRequestHandlers() may never have run despite tools being present in _registeredTools."
668
+ );
669
+ }
670
+ const syntheticRequest = {
671
+ params: { name: toolNames[0], arguments: { command: "terraform destroy prod-mergen-selftest" } }
672
+ };
673
+ let result;
674
+ try {
675
+ result = await installedHandler(syntheticRequest, { signal: new AbortController().signal });
676
+ } catch (err) {
677
+ throw new Error(
678
+ `assertGateCoversRegisteredTools: self-test call raised instead of being cleanly blocked by the gate: ${err instanceof Error ? err.message : String(err)}`
679
+ );
680
+ }
681
+ const isBlocked = !!(result && typeof result === "object" && result.isError === true);
682
+ if (!isBlocked) {
683
+ throw new Error(
684
+ 'assertGateCoversRegisteredTools: a synthetic "terraform destroy" call was NOT blocked by the live tools/call dispatch handler \u2014 the gate is not enforcing on this server. Refusing to start.'
685
+ );
686
+ }
687
+ }
688
+ export {
689
+ _resetGateInstalledForTesting,
690
+ applyGate,
691
+ approveBypass,
692
+ approveToolCall,
693
+ approveToolCallConstrained,
694
+ assertGateCoversRegisteredTools,
695
+ checkAndConsumeBypass2 as checkAndConsumeBypass,
696
+ createGuardedServer,
697
+ denyStaleHoldsOnStartup,
698
+ denyToolCall,
699
+ getPendingBypassDetail,
700
+ getPendingBypasses,
701
+ getPendingHolds,
702
+ invalidateBypassToken,
703
+ isGateInstalled,
704
+ loadBypasses,
705
+ persistBypasses,
706
+ registerBypassBlock2 as registerBypassBlock,
707
+ setBypassSecret
708
+ };