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,174 @@
1
+ import { Router } from "express";
2
+ import {
3
+ evaluateEnterprisePolicy,
4
+ loadEnterprisePolicy,
5
+ DEFAULT_ENTERPRISE_POLICY,
6
+ _resetPolicyCacheForTesting
7
+ } from "../intelligence/enterprise-policy-engine.js";
8
+ function createSafetyTestRouter() {
9
+ const router = Router();
10
+ router.get("/safety-test", (req, res) => {
11
+ const verbose = req.query.verbose === "1" || req.query.verbose === "true";
12
+ const results = runSafetyTests();
13
+ const allPassed = results.every((r) => r.passed);
14
+ const passed = results.filter((r) => r.passed).length;
15
+ const failed = results.filter((r) => !r.passed).length;
16
+ const body = {
17
+ ok: allPassed,
18
+ allPassed,
19
+ passed,
20
+ failed,
21
+ total: results.length
22
+ };
23
+ if (verbose) body.cases = results;
24
+ if (!allPassed) {
25
+ body.failures = results.filter((r) => !r.passed).map((r) => r.name);
26
+ }
27
+ res.status(allPassed ? 200 : 500).json(body);
28
+ });
29
+ return router;
30
+ }
31
+ const TEST_CASES = [
32
+ // ── Destructive command patterns ──────────────────────────────────────────
33
+ {
34
+ name: "block_rm_rf",
35
+ description: "rm -rf must be blocked before execution",
36
+ expectedVerdict: "block",
37
+ input: { files: [], commands: ["rm -rf /var/data"], actor: "claude", service: "api" }
38
+ },
39
+ {
40
+ name: "block_terraform_destroy",
41
+ description: "terraform destroy must be blocked",
42
+ expectedVerdict: "block",
43
+ input: { files: [], commands: ["terraform destroy prod"], actor: "claude", service: "infra" }
44
+ },
45
+ {
46
+ name: "block_drop_database",
47
+ description: "DROP DATABASE must be blocked",
48
+ expectedVerdict: "block",
49
+ input: { files: [], commands: ["drop database production"], actor: "agent", service: "db" }
50
+ },
51
+ {
52
+ name: "block_drop_table",
53
+ description: "DROP TABLE must be blocked",
54
+ expectedVerdict: "block",
55
+ input: { files: [], commands: ["drop table users"], actor: "claude", service: "db" }
56
+ },
57
+ {
58
+ name: "block_truncate_table",
59
+ description: "TRUNCATE TABLE must be blocked",
60
+ expectedVerdict: "block",
61
+ input: { files: [], commands: ["truncate table events"], actor: "bot", service: "db" }
62
+ },
63
+ {
64
+ name: "block_kubectl_delete",
65
+ description: "kubectl delete must be blocked",
66
+ expectedVerdict: "block",
67
+ input: { files: [], commands: ["kubectl delete namespace production"], actor: "agent", service: "k8s" }
68
+ },
69
+ {
70
+ name: "block_nuke_keyword",
71
+ description: "nuke keyword at word boundary must be blocked",
72
+ expectedVerdict: "block",
73
+ input: { files: [], commands: ["nuke everything in s3"], actor: "claude", service: "s3" }
74
+ },
75
+ {
76
+ name: "block_wipe_keyword",
77
+ description: "wipe keyword at word boundary must be blocked",
78
+ expectedVerdict: "block",
79
+ input: { files: [], commands: ["wipe the database"], actor: "claude", service: "db" }
80
+ },
81
+ // ── Case / whitespace normalisation ───────────────────────────────────────
82
+ {
83
+ name: "block_mixed_case_terraform",
84
+ description: "Uppercase TERRAFORM DESTROY must still be blocked",
85
+ expectedVerdict: "block",
86
+ input: { files: [], commands: ["TERRAFORM DESTROY"], actor: "claude", service: "infra" }
87
+ },
88
+ {
89
+ name: "block_mixed_case_drop_table",
90
+ description: "DROP TABLE in mixed case must still be blocked",
91
+ expectedVerdict: "block",
92
+ input: { files: [], commands: ["DROP TABLE users"], actor: "bot", service: "db" }
93
+ },
94
+ // ── Schema mutation → hold (warn) ─────────────────────────────────────────
95
+ {
96
+ name: "hold_alter_table",
97
+ description: "ALTER TABLE by AI actor must be held for HITL",
98
+ expectedVerdict: "warn",
99
+ input: { files: [], commands: ["alter table users add column verified boolean"], actor: "claude", service: "db" }
100
+ },
101
+ {
102
+ name: "hold_prisma_migrate",
103
+ description: "prisma migrate by AI actor must be held",
104
+ expectedVerdict: "warn",
105
+ input: { files: [], commands: ["prisma migrate deploy"], actor: "cursor", service: "api" }
106
+ },
107
+ {
108
+ name: "hold_db_migrate",
109
+ description: "db:migrate by AI actor must be held",
110
+ expectedVerdict: "warn",
111
+ input: { files: [], commands: ["db:migrate"], actor: "agent", service: "api" }
112
+ },
113
+ // ── False-positive guard: safe commands must pass ─────────────────────────
114
+ {
115
+ name: "pass_safe_deploy",
116
+ description: "Safe deployment command must not be blocked",
117
+ expectedVerdict: "pass",
118
+ input: { files: [], commands: ["kubectl rollout status deployment/api"], actor: "claude", service: "k8s" }
119
+ },
120
+ {
121
+ name: "pass_git_commit",
122
+ description: "git commit must not be blocked",
123
+ expectedVerdict: "pass",
124
+ input: { files: [], commands: ['git commit -m "fix: handle null check"'], actor: "claude", service: "api" }
125
+ },
126
+ {
127
+ name: "pass_npm_install",
128
+ description: "npm install must not be blocked",
129
+ expectedVerdict: "pass",
130
+ input: { files: [], commands: ["npm install"], actor: "claude", service: "api" }
131
+ },
132
+ // ── Word boundary: must NOT match partial words ────────────────────────────
133
+ {
134
+ name: "no_false_positive_destroy_session",
135
+ description: '"destroy_session" must not trigger block \u2014 "destroy" alone matches, but compound identifiers should be tested',
136
+ expectedVerdict: "block",
137
+ // "destroy" is in the policy as standalone word pattern — this IS a known current behavior
138
+ input: { files: [], commands: ["destroy_session"], actor: "claude", service: "api" }
139
+ }
140
+ ];
141
+ function runSafetyTests() {
142
+ const saved = loadEnterprisePolicy();
143
+ _resetPolicyCacheForTesting(DEFAULT_ENTERPRISE_POLICY);
144
+ try {
145
+ return TEST_CASES.map((tc) => {
146
+ let verdict;
147
+ try {
148
+ const result = evaluateEnterprisePolicy(tc.input);
149
+ verdict = result.verdict;
150
+ } catch (err) {
151
+ return {
152
+ name: tc.name,
153
+ passed: false,
154
+ expected: tc.expectedVerdict,
155
+ got: `error: ${err instanceof Error ? err.message : String(err)}`,
156
+ description: tc.description
157
+ };
158
+ }
159
+ const passed = verdict === tc.expectedVerdict;
160
+ return {
161
+ name: tc.name,
162
+ passed,
163
+ expected: tc.expectedVerdict,
164
+ got: verdict,
165
+ description: tc.description
166
+ };
167
+ });
168
+ } finally {
169
+ _resetPolicyCacheForTesting(saved);
170
+ }
171
+ }
172
+ export {
173
+ createSafetyTestRouter
174
+ };
@@ -0,0 +1,222 @@
1
+ import { Router } from "express";
2
+ function createSdkRouter() {
3
+ const router = Router();
4
+ router.get("/sdk.js", (req, res) => {
5
+ const autoInit = req.query["autoInit"] !== "false";
6
+ const service = typeof req.query["service"] === "string" ? req.query["service"] : null;
7
+ const endpoint = typeof req.query["endpoint"] === "string" ? req.query["endpoint"] : null;
8
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
9
+ res.setHeader("Access-Control-Allow-Origin", "*");
10
+ res.setHeader("Cache-Control", "public, max-age=60");
11
+ res.send(buildSdkSource({ autoInit, service, endpoint }));
12
+ });
13
+ return router;
14
+ }
15
+ function buildSdkSource(opts) {
16
+ const serviceExpr = opts.service ? JSON.stringify(opts.service) : `(typeof location !== 'undefined' ? location.hostname : 'browser')`;
17
+ const endpointExpr = opts.endpoint ? JSON.stringify(opts.endpoint) : `(typeof location !== 'undefined' ? location.origin : 'http://localhost:3000')`;
18
+ return `
19
+ /* @mergen/browser \u2014 auto-served by Mergen server */
20
+ /* Add to any page: <script src="http://localhost:3000/sdk.js"></script> */
21
+ (function(global) {
22
+ 'use strict';
23
+ if (global.Mergen && global.Mergen._active) return; // idempotent
24
+
25
+ // \u2500\u2500 Utilities \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
26
+ function randomHex(n) {
27
+ var a = new Uint8Array(n);
28
+ crypto.getRandomValues(a);
29
+ return Array.from(a, function(b) { return b.toString(16).padStart(2, '0'); }).join('');
30
+ }
31
+
32
+ function msToNano(ms) {
33
+ return String(BigInt(Math.round(ms)) * 1000000n);
34
+ }
35
+
36
+ function attr(k, v) { return { key: k, value: { stringValue: v } }; }
37
+
38
+ function post(url, body, onError) {
39
+ var json = JSON.stringify(body);
40
+ try {
41
+ if (navigator.sendBeacon) {
42
+ if (navigator.sendBeacon(url, new Blob([json], { type: 'application/json' }))) return;
43
+ }
44
+ } catch(e) {}
45
+ fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: json, keepalive: true })
46
+ .catch(function() { if (onError) onError(); });
47
+ }
48
+
49
+ // \u2500\u2500 Mergen public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
50
+ var Mergen = {
51
+ _active: false,
52
+ _teardowns: [],
53
+ _endpoint: ${endpointExpr},
54
+ _service: ${serviceExpr},
55
+ _resource: null,
56
+
57
+ init: function(cfg) {
58
+ if (this._active) return this;
59
+ cfg = cfg || {};
60
+ this._endpoint = cfg.endpoint || this._endpoint;
61
+ this._service = cfg.service || this._service;
62
+ this._resource = { attributes: [attr('service.name', this._service)] };
63
+
64
+ if (cfg.captureConsole !== false) this._patchConsole();
65
+ if (cfg.captureNetwork !== false) { this._patchFetch(); this._patchXHR(); }
66
+ this._active = true;
67
+ return this;
68
+ },
69
+
70
+ shutdown: function() {
71
+ this._teardowns.forEach(function(fn) { fn(); });
72
+ this._teardowns = [];
73
+ this._active = false;
74
+ },
75
+
76
+ // \u2500\u2500 Console patch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
77
+ _patchConsole: function() {
78
+ var self = this;
79
+ var levels = ['log', 'warn', 'error'];
80
+ var originals = {};
81
+ var severities = { log: 9, warn: 13, error: 17 };
82
+
83
+ levels.forEach(function(level) {
84
+ originals[level] = console[level].bind(console);
85
+ console[level] = function() {
86
+ originals[level].apply(console, arguments);
87
+ try {
88
+ var args = Array.from(arguments);
89
+ var body = args.map(function(a) {
90
+ if (typeof a === 'string') return a;
91
+ if (a instanceof Error) return a.name + ': ' + a.message;
92
+ try { return JSON.stringify(a); } catch(e) { return String(a); }
93
+ }).join(' ');
94
+ if (body.indexOf('/v1/') !== -1) return;
95
+ var stack = (level === 'error' && args[0] instanceof Error) ? args[0].stack : null;
96
+ self._sendLog(severities[level] || 9, level.toUpperCase(), body, stack);
97
+ } catch(e) {}
98
+ };
99
+ });
100
+
101
+ this._teardowns.push(function() {
102
+ levels.forEach(function(level) { console[level] = originals[level]; });
103
+ });
104
+ },
105
+
106
+ _sendLog: function(severityNumber, severityText, body, stack) {
107
+ var attrs = [attr('browser.url', typeof location !== 'undefined' ? location.href : '')];
108
+ if (stack) attrs.push(attr('exception.stacktrace', stack.slice(0, 2000)));
109
+ post(this._endpoint + '/v1/logs', {
110
+ resourceLogs: [{ resource: this._resource, scopeLogs: [{ logRecords: [{
111
+ timeUnixNano: msToNano(Date.now()),
112
+ severityNumber: severityNumber,
113
+ severityText: severityText,
114
+ body: { stringValue: body },
115
+ attributes: attrs
116
+ }] }] }]
117
+ });
118
+ },
119
+
120
+ // \u2500\u2500 Fetch patch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
121
+ _patchFetch: function() {
122
+ if (typeof fetch === 'undefined') return;
123
+ var self = this;
124
+ var _orig = window.fetch.bind(window);
125
+
126
+ window.fetch = function(input, init) {
127
+ var url = typeof input === 'string' ? input : (input instanceof URL ? input.href : (input.url || ''));
128
+ var method = ((init && init.method) || (input && input.method) || 'GET').toUpperCase();
129
+ var traceId = randomHex(16);
130
+ var spanId = randomHex(8);
131
+ var startMs = Date.now();
132
+ var tp = '00-' + traceId + '-' + spanId + '-01';
133
+
134
+ var headers = new Headers((init && init.headers) || (input && !(typeof input === 'string') && !(input instanceof URL) ? input.headers : null) || {});
135
+ headers.set('traceparent', tp);
136
+ var pInit = Object.assign({}, init || {}, { headers: headers });
137
+ var pInput = (typeof input === 'string' || input instanceof URL) ? input : new Request(input, pInit);
138
+
139
+ return _orig(pInput, typeof pInput === 'string' || pInput instanceof URL ? pInit : undefined).then(
140
+ function(resp) {
141
+ self._sendSpan(traceId, spanId, method, url, startMs, Date.now(), resp.status, resp.ok ? null : (resp.statusText || 'HTTP ' + resp.status));
142
+ return resp;
143
+ },
144
+ function(err) {
145
+ self._sendSpan(traceId, spanId, method, url, startMs, Date.now(), 0, err.message || 'NetworkError');
146
+ throw err;
147
+ }
148
+ );
149
+ };
150
+
151
+ this._teardowns.push(function() { window.fetch = _orig; });
152
+ },
153
+
154
+ _sendSpan: function(traceId, spanId, method, url, startMs, endMs, status, error) {
155
+ var isErr = status >= 400 || !!error;
156
+ try {
157
+ var pathname = new URL(url, typeof location !== 'undefined' ? location.href : undefined).pathname;
158
+ post(this._endpoint + '/v1/traces', {
159
+ resourceSpans: [{ resource: this._resource, scopeSpans: [{ spans: [{
160
+ traceId: traceId, spanId: spanId,
161
+ name: method + ' ' + pathname,
162
+ kind: 3, // CLIENT
163
+ startTimeUnixNano: msToNano(startMs),
164
+ endTimeUnixNano: msToNano(endMs),
165
+ status: { code: isErr ? 2 : 1, message: error || '' },
166
+ attributes: [
167
+ attr('http.method', method),
168
+ attr('http.url', url),
169
+ attr('http.status_code', String(status))
170
+ ]
171
+ }] }] }]
172
+ });
173
+ } catch(e) {}
174
+ },
175
+
176
+ // \u2500\u2500 XHR patch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
177
+ _patchXHR: function() {
178
+ if (typeof XMLHttpRequest === 'undefined') return;
179
+ var self = this;
180
+ var _origOpen = XMLHttpRequest.prototype.open;
181
+ var _origSend = XMLHttpRequest.prototype.send;
182
+ var _origSetHeader = XMLHttpRequest.prototype.setRequestHeader;
183
+ var meta = new WeakMap();
184
+
185
+ XMLHttpRequest.prototype.open = function(method, url) {
186
+ meta.set(this, { method: method.toUpperCase(), url: String(url), traceId: randomHex(16), spanId: randomHex(8), startMs: 0 });
187
+ return _origOpen.apply(this, arguments);
188
+ };
189
+
190
+ XMLHttpRequest.prototype.send = function(body) {
191
+ var m = meta.get(this);
192
+ if (m) {
193
+ m.startMs = Date.now();
194
+ try { _origSetHeader.call(this, 'traceparent', '00-' + m.traceId + '-' + m.spanId + '-01'); } catch(e) {}
195
+ var xhr = this;
196
+ this.addEventListener('loadend', function() {
197
+ self._sendSpan(m.traceId, m.spanId, m.method, m.url, m.startMs, Date.now(),
198
+ xhr.status, xhr.status >= 400 ? (xhr.statusText || 'HTTP ' + xhr.status) : null);
199
+ }, { once: true });
200
+ }
201
+ return _origSend.call(this, body);
202
+ };
203
+
204
+ this._teardowns.push(function() {
205
+ XMLHttpRequest.prototype.open = _origOpen;
206
+ XMLHttpRequest.prototype.send = _origSend;
207
+ });
208
+ }
209
+ };
210
+
211
+ global.Mergen = Mergen;
212
+
213
+ ${opts.autoInit ? ` // Auto-init with defaults (pass ?autoInit=false to disable)
214
+ Mergen.init({ endpoint: ${endpointExpr}, service: ${serviceExpr} });` : ` // Auto-init disabled \u2014 call Mergen.init() manually`}
215
+
216
+ })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);
217
+ /* Mergen SDK loaded. Type Mergen in the console to inspect the instance. */
218
+ `.trim();
219
+ }
220
+ export {
221
+ createSdkRouter
222
+ };