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,140 @@
1
+ import { createHash } from "crypto";
2
+ import { compactCorpus } from "./override-corpus.js";
3
+ import { loadEnterprisePolicy, saveEnterprisePolicy } from "./enterprise-policy-engine.js";
4
+ import { stageProposal } from "./policy-proposals.js";
5
+ import logger from "../sensor/logger.js";
6
+ const MIN_OCCURRENCES = 3;
7
+ function _actionForReason(reason) {
8
+ switch (reason) {
9
+ case "batch-window":
10
+ case "maintenance-window":
11
+ case "compliance-hold":
12
+ return "block";
13
+ case "wrong-fix":
14
+ case "wrong-diagnosis":
15
+ case "cost-constraint":
16
+ case "on-call-discretion":
17
+ case "prefer-read-replica":
18
+ case "other":
19
+ return "warn";
20
+ }
21
+ }
22
+ function _humanReason(reason) {
23
+ const labels = {
24
+ "batch-window": "batch settlement window",
25
+ "maintenance-window": "maintenance window",
26
+ "compliance-hold": "compliance hold",
27
+ "wrong-fix": "incorrect fix applied previously",
28
+ "wrong-diagnosis": "incorrect root cause diagnosis",
29
+ "cost-constraint": "cost ceiling constraint",
30
+ "on-call-discretion": "on-call discretion override",
31
+ "prefer-read-replica": "read-replica preferred",
32
+ "other": "team override"
33
+ };
34
+ return labels[reason] ?? reason;
35
+ }
36
+ function _ruleHash(rule) {
37
+ const key = JSON.stringify({
38
+ tag: rule.incidentTag,
39
+ svc: rule.service,
40
+ reason: rule.overrideReason,
41
+ day: rule.dayOfWeek,
42
+ hour: rule.hourWindow
43
+ });
44
+ return createHash("sha256").update(key).digest("hex").slice(0, 16);
45
+ }
46
+ function _isAlreadyCovered(hash) {
47
+ const policy = loadEnterprisePolicy();
48
+ return policy.rules.some((r) => r.id === `corpus_auto_${hash}`);
49
+ }
50
+ function synthesizeRulesFromCorpus(scope) {
51
+ const compacted = compactCorpus().filter(
52
+ (cr) => !scope || cr.incidentTag === scope.incidentTag && cr.service === scope.service
53
+ );
54
+ const results = [];
55
+ for (const cr of compacted) {
56
+ if (cr.occurrences < MIN_OCCURRENCES) continue;
57
+ if (cr.commandSignatures.length === 0) {
58
+ logger.warn(
59
+ { incidentTag: cr.incidentTag, service: cr.service },
60
+ "corpus-to-policy: skipping synthesis \u2014 no command signatures could be extracted from this bucket"
61
+ );
62
+ continue;
63
+ }
64
+ const hash = _ruleHash(cr);
65
+ if (_isAlreadyCovered(hash)) continue;
66
+ const action = _actionForReason(cr.overrideReason);
67
+ const humanLabel = _humanReason(cr.overrideReason);
68
+ const conditions = {
69
+ commands: cr.commandSignatures
70
+ };
71
+ if (cr.dayOfWeek !== null) {
72
+ conditions.daysOfWeek = [cr.dayOfWeek];
73
+ }
74
+ if (cr.hourWindow !== null) {
75
+ conditions.hourWindow = cr.hourWindow;
76
+ }
77
+ conditions.actorType = "ai";
78
+ const dayLabel = cr.dayOfWeek !== null ? ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][cr.dayOfWeek] : null;
79
+ const hourLabel = cr.hourWindow ? `${String(cr.hourWindow[0]).padStart(2, "0")}:00\u2013${String(cr.hourWindow[1]).padStart(2, "0")}:00 UTC` : null;
80
+ const timeDesc = [dayLabel, hourLabel].filter(Boolean).join(" ");
81
+ const commandsDesc = cr.commandSignatures.map((s) => `"${s}"`).join(", ");
82
+ const rule = {
83
+ id: `corpus_auto_${hash}`,
84
+ name: `[Auto] ${cr.service !== "unknown" ? cr.service + ": " : ""}${humanLabel}${timeDesc ? " (" + timeDesc + ")" : ""}`,
85
+ description: `Auto-synthesized from ${cr.occurrences} override events. Tag: ${cr.incidentTag}. Reason: ${humanLabel}. Scoped to commands: ${commandsDesc}.`,
86
+ action,
87
+ reason: `Corpus-derived policy: this action was overridden ${cr.occurrences} time(s) due to ${humanLabel}${timeDesc ? " during " + timeDesc : ""}.`,
88
+ conditions
89
+ };
90
+ results.push({ rule, sourceOccurrences: cr.occurrences, compactedRule: cr });
91
+ logger.info(
92
+ { ruleId: rule.id, occurrences: cr.occurrences, action },
93
+ "corpus-to-policy: synthesized new policy rule from override corpus"
94
+ );
95
+ }
96
+ return results;
97
+ }
98
+ function _activateRule(rule) {
99
+ const policy = loadEnterprisePolicy();
100
+ if (policy.rules.some((r) => r.id === rule.id)) return;
101
+ saveEnterprisePolicy({ ...policy, rules: [...policy.rules, rule] });
102
+ }
103
+ function autoActivateReviewedRules(incidentTag, service) {
104
+ const staged = synthesizeRulesFromCorpus({ incidentTag, service });
105
+ for (const { rule } of staged) {
106
+ _activateRule(rule);
107
+ logger.info(
108
+ { ruleId: rule.id, incidentTag, service },
109
+ "corpus-to-policy: auto-activated rule into live policy after operator review"
110
+ );
111
+ }
112
+ return staged;
113
+ }
114
+ function autoCorpusProposeEnabled() {
115
+ return process.env.MERGEN_AUTO_CORPUS_PROPOSE !== "false";
116
+ }
117
+ function proposeRulesFromCorpus(scope) {
118
+ if (!autoCorpusProposeEnabled()) return [];
119
+ const synthesized = synthesizeRulesFromCorpus(scope);
120
+ const proposals = [];
121
+ for (const { rule, sourceOccurrences } of synthesized) {
122
+ const holdRule = { ...rule, action: "warn" };
123
+ const staged = stageProposal(rule.id, holdRule, sourceOccurrences);
124
+ if (staged) proposals.push(staged);
125
+ }
126
+ if (proposals.length > 0) {
127
+ logger.info({ count: proposals.length }, "corpus-to-policy: staged HOLD-only policy proposals");
128
+ }
129
+ return proposals;
130
+ }
131
+ function activateProposedRule(rule) {
132
+ _activateRule(rule);
133
+ }
134
+ export {
135
+ activateProposedRule,
136
+ autoActivateReviewedRules,
137
+ autoCorpusProposeEnabled,
138
+ proposeRulesFromCorpus,
139
+ synthesizeRulesFromCorpus
140
+ };
@@ -0,0 +1,112 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
4
+ import { DATA_DIR } from "../sensor/paths.js";
5
+ import { getStores } from "../storage/store-registry.js";
6
+ import { getTraceId } from "./trace-context.js";
7
+ import logger from "../sensor/logger.js";
8
+ import { requestGcpCredential } from "./gcp-broker.js";
9
+ import { requestAzureCredential } from "./azure-broker.js";
10
+ const CREDENTIAL_SCOPES_FILE = path.join(DATA_DIR, "credential-scopes.json");
11
+ function loadScopes() {
12
+ try {
13
+ const raw = JSON.parse(fs.readFileSync(CREDENTIAL_SCOPES_FILE, "utf8"));
14
+ return raw.scopes ?? {};
15
+ } catch {
16
+ return {};
17
+ }
18
+ }
19
+ function providerOf(scopeConfig) {
20
+ return scopeConfig.provider ?? "aws";
21
+ }
22
+ const STS_MIN_DURATION_SECONDS = 900;
23
+ const STS_MAX_DURATION_SECONDS = 43200;
24
+ function sessionNameFor(agentId, taskId) {
25
+ const raw = `mergen-${agentId}-${taskId}`.replace(/[^\w+=,.@-]/g, "_");
26
+ return raw.slice(0, 64);
27
+ }
28
+ async function requestAwsCredential(request, scopeConfig) {
29
+ const requested = request.durationSeconds ?? scopeConfig.maxDurationSeconds;
30
+ const durationSeconds = Math.min(
31
+ Math.max(requested, STS_MIN_DURATION_SECONDS),
32
+ Math.min(scopeConfig.maxDurationSeconds, STS_MAX_DURATION_SECONDS)
33
+ );
34
+ const client = new STSClient({ region: scopeConfig.region });
35
+ const response = await client.send(new AssumeRoleCommand({
36
+ RoleArn: scopeConfig.roleArn,
37
+ RoleSessionName: sessionNameFor(request.agentId, request.taskId),
38
+ DurationSeconds: durationSeconds
39
+ }));
40
+ const creds = response.Credentials;
41
+ if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken || !creds.Expiration) {
42
+ throw new Error("credential-broker: AssumeRole succeeded but returned incomplete credentials");
43
+ }
44
+ return {
45
+ provider: "aws",
46
+ accessKeyId: creds.AccessKeyId,
47
+ secretAccessKey: creds.SecretAccessKey,
48
+ sessionToken: creds.SessionToken,
49
+ expiration: creds.Expiration,
50
+ roleArn: scopeConfig.roleArn,
51
+ scope: request.scope,
52
+ durationEnforced: true
53
+ };
54
+ }
55
+ async function requestEphemeralCredential(request) {
56
+ const scopes = loadScopes();
57
+ const scopeConfig = scopes[request.scope];
58
+ if (!scopeConfig || scopeConfig.mergenTenantId !== void 0 && scopeConfig.mergenTenantId !== request.callerTenantId) {
59
+ throw new Error(
60
+ `credential-broker: unknown scope "${request.scope}" \u2014 configure it in ${CREDENTIAL_SCOPES_FILE} first`
61
+ );
62
+ }
63
+ const provider = providerOf(scopeConfig);
64
+ let cred;
65
+ if (provider === "gcp") {
66
+ cred = await requestGcpCredential(request.agentId, request.scope, scopeConfig, request.durationSeconds);
67
+ } else if (provider === "azure") {
68
+ cred = await requestAzureCredential(request.agentId, request.scope, scopeConfig, request.durationSeconds);
69
+ } else {
70
+ cred = await requestAwsCredential(request, scopeConfig);
71
+ }
72
+ await getStores().blunders.record({
73
+ blunderType: "credential_issued",
74
+ command: `credential:${request.scope}`,
75
+ blockReason: `Ephemeral ${provider} credential issued, expires ${cred.expiration.toISOString()}`,
76
+ service: "credential-broker",
77
+ tag: "credential_broker",
78
+ actor: "agent",
79
+ pid: null,
80
+ confidenceScore: null,
81
+ triggeredRules: null,
82
+ agentId: request.agentId,
83
+ traceId: getTraceId()
84
+ });
85
+ logger.info(
86
+ { agentId: request.agentId, scope: request.scope, provider, expiresAt: cred.expiration },
87
+ "credential-broker: ephemeral credential issued"
88
+ );
89
+ try {
90
+ void getStores().actionLedger.append({
91
+ agentId: request.agentId,
92
+ tool: "request_credential",
93
+ command: `credential:${request.scope}`,
94
+ resource: request.scope,
95
+ verdict: "pass",
96
+ reason: `Ephemeral ${provider} credential issued`,
97
+ credentialIssued: true,
98
+ credentialScope: request.scope,
99
+ environment: process.env.MERGEN_ENVIRONMENT ?? null,
100
+ traceId: getTraceId()
101
+ }, request.callerTenantId);
102
+ } catch {
103
+ }
104
+ return cred;
105
+ }
106
+ function listConfiguredScopes(callerTenantId) {
107
+ return Object.entries(loadScopes()).filter(([, cfg]) => cfg.mergenTenantId === void 0 || cfg.mergenTenantId === callerTenantId).map(([name]) => name);
108
+ }
109
+ export {
110
+ listConfiguredScopes,
111
+ requestEphemeralCredential
112
+ };
@@ -0,0 +1,39 @@
1
+ import { getStores } from "../storage/store-registry.js";
2
+ const DEFAULT_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
3
+ async function detectCredentialMisuse(opts = {}) {
4
+ const windowMs = opts.windowMs ?? DEFAULT_WINDOW_MS;
5
+ const from = Date.now() - windowMs;
6
+ const rows = await getStores().actionLedger.queryCredentialUsage({ agentId: opts.agentId, from }, opts.tenantId);
7
+ const groups = /* @__PURE__ */ new Map();
8
+ for (const r of rows) {
9
+ const scope = r.credentialScope ?? "unknown";
10
+ const env = r.environment ?? "unknown";
11
+ const key = `${r.agentId ?? ""}\0${scope}`;
12
+ let g = groups.get(key);
13
+ if (!g) {
14
+ g = { agentId: r.agentId, scope, envs: /* @__PURE__ */ new Set(), count: 0, firstTs: r.ts, lastTs: r.ts };
15
+ groups.set(key, g);
16
+ }
17
+ g.envs.add(env);
18
+ g.count++;
19
+ if (r.ts < g.firstTs) g.firstTs = r.ts;
20
+ if (r.ts > g.lastTs) g.lastTs = r.ts;
21
+ }
22
+ const findings = [];
23
+ for (const g of groups.values()) {
24
+ if (g.envs.size >= 2) {
25
+ findings.push({
26
+ agentId: g.agentId,
27
+ scope: g.scope,
28
+ environments: [...g.envs].sort(),
29
+ issuances: g.count,
30
+ firstTs: g.firstTs,
31
+ lastTs: g.lastTs
32
+ });
33
+ }
34
+ }
35
+ return findings.sort((a, b) => b.lastTs - a.lastTs);
36
+ }
37
+ export {
38
+ detectCredentialMisuse
39
+ };
@@ -0,0 +1,108 @@
1
+ import { randomUUID } from "crypto";
2
+ function fingerprintConsole(e) {
3
+ const msg = e.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").trim().slice(0, 120);
4
+ return `${e.level}::${msg}`;
5
+ }
6
+ function fingerprintNetwork(n) {
7
+ const url = n.url.split("?")[0];
8
+ return `${n.method}::${url}::${n.status}`;
9
+ }
10
+ const sessions = /* @__PURE__ */ new Map();
11
+ function captureSnapshot(errors, warnings, networkFailures) {
12
+ return {
13
+ capturedAt: Date.now(),
14
+ errors: errors.map((e) => ({
15
+ fingerprint: fingerprintConsole(e),
16
+ message: e.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").slice(0, 200),
17
+ level: e.level,
18
+ timestamp: e.timestamp
19
+ })),
20
+ warnings: warnings.map((e) => ({
21
+ fingerprint: fingerprintConsole(e),
22
+ message: e.args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ").slice(0, 200),
23
+ level: e.level,
24
+ timestamp: e.timestamp
25
+ })),
26
+ networkFailures: networkFailures.map((n) => ({
27
+ fingerprint: fingerprintNetwork(n),
28
+ summary: `${n.method} ${n.url.split("?")[0]} \u2192 ${n.status || "NET_ERR"}`,
29
+ status: n.status,
30
+ timestamp: n.timestamp
31
+ }))
32
+ };
33
+ }
34
+ function diffSnapshots(baseline, current) {
35
+ const baseErrFPs = new Set(baseline.errors.map((e) => e.fingerprint));
36
+ const baseNetFPs = new Set(baseline.networkFailures.map((n) => n.fingerprint));
37
+ const curErrFPs = new Set(current.errors.map((e) => e.fingerprint));
38
+ const curNetFPs = new Set(current.networkFailures.map((n) => n.fingerprint));
39
+ const resolved = baseline.errors.filter((e) => !curErrFPs.has(e.fingerprint));
40
+ const persisted = baseline.errors.filter((e) => curErrFPs.has(e.fingerprint));
41
+ const newErrors = current.errors.filter((e) => !baseErrFPs.has(e.fingerprint));
42
+ const resolvedNetworkFailures = baseline.networkFailures.filter((n) => !curNetFPs.has(n.fingerprint));
43
+ const newNetworkFailures = current.networkFailures.filter((n) => !baseNetFPs.has(n.fingerprint));
44
+ return {
45
+ resolved,
46
+ persisted,
47
+ newErrors,
48
+ resolvedNetworkFailures,
49
+ newNetworkFailures,
50
+ isFixed: persisted.length === 0 && newErrors.length === 0 && baseline.errors.length > 0
51
+ };
52
+ }
53
+ function startDebugSession(description, baseline, targetComponent) {
54
+ const session = {
55
+ id: randomUUID(),
56
+ description,
57
+ targetComponent,
58
+ baseline,
59
+ iterations: [],
60
+ startedAt: Date.now(),
61
+ resolved: false
62
+ };
63
+ sessions.set(session.id, session);
64
+ return session;
65
+ }
66
+ function checkpointSession(sessionId, current, note) {
67
+ const session = sessions.get(sessionId);
68
+ if (!session) return null;
69
+ const diff = diffSnapshots(session.baseline, current);
70
+ session.iterations.push({
71
+ index: session.iterations.length + 1,
72
+ note,
73
+ timestamp: Date.now(),
74
+ diff
75
+ });
76
+ if (diff.isFixed) session.resolved = true;
77
+ return { session, diff };
78
+ }
79
+ function endDebugSession(sessionId, current) {
80
+ const session = sessions.get(sessionId);
81
+ if (!session) return null;
82
+ const diff = diffSnapshots(session.baseline, current);
83
+ session.endedAt = Date.now();
84
+ session.resolved = diff.isFixed;
85
+ sessions.delete(sessionId);
86
+ return { session, diff };
87
+ }
88
+ function getSession(sessionId) {
89
+ return sessions.get(sessionId);
90
+ }
91
+ function listActiveSessions() {
92
+ return Array.from(sessions.values());
93
+ }
94
+ function clearAllSessions() {
95
+ const count = sessions.size;
96
+ sessions.clear();
97
+ return count;
98
+ }
99
+ export {
100
+ captureSnapshot,
101
+ checkpointSession,
102
+ clearAllSessions,
103
+ diffSnapshots,
104
+ endDebugSession,
105
+ getSession,
106
+ listActiveSessions,
107
+ startDebugSession
108
+ };
@@ -0,0 +1,36 @@
1
+ const LOW_ACCURACY_THRESHOLD = 0.7;
2
+ function blendEscalation(input) {
3
+ const { similarity, calibration } = input;
4
+ const reasons = [];
5
+ let hold = false;
6
+ let confidence = 1;
7
+ if (similarity.recommendation === "hold") {
8
+ hold = true;
9
+ confidence -= 0.4;
10
+ reasons.push(similarity.rationale);
11
+ }
12
+ if (calibration && !calibration.trusted) {
13
+ hold = true;
14
+ confidence -= 0.3;
15
+ reasons.push(
16
+ `Calibration for '${calibration.tag}' is not yet trusted (${calibration.verdicts} verdicts, ${Math.round(calibration.accuracy * 100)}% accuracy).`
17
+ );
18
+ } else if (calibration && calibration.accuracy < LOW_ACCURACY_THRESHOLD) {
19
+ hold = true;
20
+ confidence -= 0.2;
21
+ reasons.push(
22
+ `Calibration accuracy for '${calibration.tag}' is ${Math.round(calibration.accuracy * 100)}%, below the ${Math.round(LOW_ACCURACY_THRESHOLD * 100)}% confidence floor.`
23
+ );
24
+ }
25
+ if (reasons.length === 0) {
26
+ reasons.push("No similar past actions with negative outcomes, and calibration for this tag is trusted.");
27
+ }
28
+ return {
29
+ recommendation: hold ? "hold" : "none",
30
+ confidence: Math.max(0, Math.min(1, confidence)),
31
+ reasons
32
+ };
33
+ }
34
+ export {
35
+ blendEscalation
36
+ };
@@ -0,0 +1,47 @@
1
+ function summarizeOverride(e) {
2
+ return {
3
+ source: "override",
4
+ recordedAt: e.recordedAt,
5
+ service: e.service,
6
+ summary: `${e.overrideReason}${e.note ? `: ${e.note}` : ""} \u2014 proposed \`${e.proposedCommand.slice(0, 80)}\``,
7
+ outcome: e.outcome ?? null
8
+ };
9
+ }
10
+ function summarizeShadow(e) {
11
+ const cmd = e.command ? `\`${e.command.slice(0, 80)}\`` : "no command";
12
+ return {
13
+ source: "shadow",
14
+ recordedAt: e.recordedAt,
15
+ service: e.service,
16
+ summary: `would have run: ${cmd} (${e.skipReason})`,
17
+ outcome: e.humanVerdict ?? null
18
+ };
19
+ }
20
+ function buildDecisionMemory(tag, service, overrides, shadowEntries, topRule) {
21
+ const entries = [
22
+ ...overrides.map(summarizeOverride),
23
+ ...shadowEntries.map(summarizeShadow)
24
+ ].sort((a, b) => b.recordedAt - a.recordedAt);
25
+ const overrideOutcomes = { resolved: 0, escalated: 0, unresolved: 0, unknown: 0 };
26
+ for (const e of overrides) {
27
+ if (e.outcome) overrideOutcomes[e.outcome]++;
28
+ else overrideOutcomes.unknown++;
29
+ }
30
+ const shadowOutcomes = { wouldApprove: 0, wouldOverride: 0, unreviewed: 0 };
31
+ for (const e of shadowEntries) {
32
+ if (e.humanVerdict === "would-approve") shadowOutcomes.wouldApprove++;
33
+ else if (e.humanVerdict === "would-override") shadowOutcomes.wouldOverride++;
34
+ else shadowOutcomes.unreviewed++;
35
+ }
36
+ return {
37
+ tag,
38
+ service,
39
+ totalEntries: entries.length,
40
+ entries,
41
+ topRule,
42
+ outcomeBreakdown: { overrides: overrideOutcomes, shadow: shadowOutcomes }
43
+ };
44
+ }
45
+ export {
46
+ buildDecisionMemory
47
+ };
@@ -0,0 +1,85 @@
1
+ import { commandSignature } from "./override-corpus.js";
2
+ import { normalizeForMatching } from "./normalize.js";
3
+ const TOP_N = 5;
4
+ const MIN_SIMILARITY = 0.34;
5
+ const MIN_MATCHES_FOR_RECOMMENDATION = 2;
6
+ const NEGATIVE_OUTCOMES = /* @__PURE__ */ new Set(["escalated", "unresolved", "would-override"]);
7
+ function tokenSet(text) {
8
+ return new Set(normalizeForMatching(text).split(" ").filter(Boolean));
9
+ }
10
+ function jaccard(a, b) {
11
+ if (a.size === 0 || b.size === 0) return 0;
12
+ let intersection = 0;
13
+ for (const t of a) if (b.has(t)) intersection++;
14
+ const union = a.size + b.size - intersection;
15
+ return union === 0 ? 0 : intersection / union;
16
+ }
17
+ function scoreCandidate(proposedTokens, proposedSig, candidateCommand, candidateService, service) {
18
+ const reasons = [];
19
+ let score = 0;
20
+ const candidateSig = commandSignature(candidateCommand);
21
+ if (candidateSig && candidateSig === proposedSig) {
22
+ score += 0.5;
23
+ reasons.push("same command signature");
24
+ }
25
+ const overlap = jaccard(proposedTokens, tokenSet(candidateCommand));
26
+ if (overlap > 0) {
27
+ score += overlap * 0.4;
28
+ if (overlap >= 0.3) reasons.push(`${Math.round(overlap * 100)}% command text overlap`);
29
+ }
30
+ if (service && candidateService === service) {
31
+ score += 0.1;
32
+ reasons.push("same service");
33
+ }
34
+ return { similarity: Math.min(1, score), matchReasons: reasons };
35
+ }
36
+ function findSimilarDecisions(proposedCommand, service, overrides, shadowEntries) {
37
+ const proposedTokens = tokenSet(proposedCommand);
38
+ const proposedSig = commandSignature(proposedCommand);
39
+ const candidates = [];
40
+ for (const e of overrides) {
41
+ const { similarity, matchReasons } = scoreCandidate(proposedTokens, proposedSig, e.proposedCommand, e.service, service);
42
+ if (similarity < MIN_SIMILARITY) continue;
43
+ candidates.push({
44
+ source: "override",
45
+ recordedAt: e.recordedAt,
46
+ service: e.service,
47
+ command: e.proposedCommand,
48
+ outcome: e.outcome ?? null,
49
+ similarity,
50
+ matchReasons
51
+ });
52
+ }
53
+ for (const e of shadowEntries) {
54
+ if (!e.command) continue;
55
+ const { similarity, matchReasons } = scoreCandidate(proposedTokens, proposedSig, e.command, e.service, service);
56
+ if (similarity < MIN_SIMILARITY) continue;
57
+ candidates.push({
58
+ source: "shadow",
59
+ recordedAt: e.recordedAt,
60
+ service: e.service,
61
+ command: e.command,
62
+ outcome: e.humanVerdict ?? null,
63
+ similarity,
64
+ matchReasons
65
+ });
66
+ }
67
+ candidates.sort((a, b) => b.similarity - a.similarity || b.recordedAt - a.recordedAt);
68
+ const matches = candidates.slice(0, TOP_N);
69
+ const negativeOutcomeCount = matches.filter((m) => m.outcome !== null && NEGATIVE_OUTCOMES.has(m.outcome)).length;
70
+ const totalConsidered = matches.length;
71
+ const recommendation = totalConsidered >= MIN_MATCHES_FOR_RECOMMENDATION && negativeOutcomeCount / totalConsidered >= 0.5 ? "hold" : "none";
72
+ const rationale = totalConsidered === 0 ? "No similar past actions found." : `${negativeOutcomeCount}/${totalConsidered} similar past action${totalConsidered === 1 ? "" : "s"} ${totalConsidered === 1 ? "was" : "were"} overridden, escalated, or left unresolved.`;
73
+ return {
74
+ proposedCommand,
75
+ service,
76
+ matches,
77
+ negativeOutcomeCount,
78
+ totalConsidered,
79
+ recommendation,
80
+ rationale
81
+ };
82
+ }
83
+ export {
84
+ findSimilarDecisions
85
+ };
@@ -0,0 +1,53 @@
1
+ const DEFAULT_SAFETY_TESTS = [
2
+ {
3
+ id: "block_rm_rf",
4
+ description: "Recursive force-delete must be blocked",
5
+ input: { files: [], commands: ["rm -rf /var/data"], actor: "claude", service: "test" },
6
+ expectedVerdict: "block"
7
+ },
8
+ {
9
+ id: "block_terraform_destroy",
10
+ description: "Terraform destroy against prod must be blocked",
11
+ input: { files: [], commands: ["terraform destroy prod"], actor: "claude", service: "test" },
12
+ expectedVerdict: "block"
13
+ },
14
+ {
15
+ id: "block_drop_database",
16
+ description: "DROP DATABASE must be blocked",
17
+ input: { files: [], commands: ["drop database production"], actor: "claude", service: "test" },
18
+ expectedVerdict: "block"
19
+ },
20
+ {
21
+ id: "block_drop_table",
22
+ description: "DROP TABLE must be blocked",
23
+ input: { files: [], commands: ["drop table users"], actor: "claude", service: "test" },
24
+ expectedVerdict: "block"
25
+ },
26
+ {
27
+ id: "block_kubectl_delete",
28
+ description: "kubectl delete namespace must be blocked",
29
+ input: { files: [], commands: ["kubectl delete namespace production"], actor: "claude", service: "test" },
30
+ expectedVerdict: "block"
31
+ },
32
+ {
33
+ id: "hold_alter_table",
34
+ description: "Schema-mutating ALTER TABLE must be held for review, not outright blocked",
35
+ input: { files: [], commands: ["alter table users add column x boolean"], actor: "claude", service: "test" },
36
+ expectedVerdict: "warn"
37
+ },
38
+ {
39
+ id: "pass_safe_deploy",
40
+ description: "Read-only rollout status check must pass",
41
+ input: { files: [], commands: ["kubectl rollout status deployment/api"], actor: "claude", service: "test" },
42
+ expectedVerdict: "pass"
43
+ },
44
+ {
45
+ id: "pass_git_commit",
46
+ description: "Ordinary git commit must pass",
47
+ input: { files: [], commands: ['git commit -m "fix"'], actor: "claude", service: "test" },
48
+ expectedVerdict: "pass"
49
+ }
50
+ ];
51
+ export {
52
+ DEFAULT_SAFETY_TESTS
53
+ };