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,327 @@
1
+ import initSqlJs from "sql.js";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { createRequire } from "module";
6
+ import { DATA_DIR } from "./paths.js";
7
+ import logger from "./logger.js";
8
+ const LEDGER_DB = path.join(DATA_DIR, "action-ledger.db");
9
+ const RETENTION_MS = (() => {
10
+ const d = parseFloat(process.env.MERGEN_LEDGER_RETENTION_DAYS ?? "90");
11
+ return (Number.isFinite(d) && d > 0 ? d : 90) * 24 * 60 * 60 * 1e3;
12
+ })();
13
+ const MAX_ROWS = 1e6;
14
+ const FLUSH_DEBOUNCE_MS = 2e3;
15
+ class ActionLedger {
16
+ db = null;
17
+ wasmPath;
18
+ writeCount = 0;
19
+ flushTimer = null;
20
+ // Rows appended before init() finishes are held here and drained on init.
21
+ preInitQueue = [];
22
+ constructor() {
23
+ this.wasmPath = ActionLedger.resolveWasmPath();
24
+ }
25
+ static resolveWasmPath() {
26
+ if (process.env.MERGEN_WASM_PATH) return process.env.MERGEN_WASM_PATH;
27
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
28
+ const fromModule = path.resolve(moduleDir, "../../node_modules/sql.js/dist/sql-wasm.wasm");
29
+ if (fs.existsSync(fromModule)) return fromModule;
30
+ try {
31
+ const req = createRequire(import.meta.url);
32
+ const resolved = path.join(path.dirname(req.resolve("sql.js")), "sql-wasm.wasm");
33
+ if (fs.existsSync(resolved)) return resolved;
34
+ } catch {
35
+ }
36
+ return fromModule;
37
+ }
38
+ async init() {
39
+ try {
40
+ const wasmBinary = fs.readFileSync(this.wasmPath);
41
+ const SQL = await initSqlJs({ wasmBinary });
42
+ fs.mkdirSync(DATA_DIR, { recursive: true });
43
+ let fileBuffer;
44
+ if (fs.existsSync(LEDGER_DB)) {
45
+ try {
46
+ fileBuffer = fs.readFileSync(LEDGER_DB);
47
+ } catch {
48
+ logger.warn("action-ledger.db unreadable, starting fresh");
49
+ }
50
+ }
51
+ this.db = fileBuffer ? new SQL.Database(fileBuffer) : new SQL.Database();
52
+ this.db.run(`
53
+ CREATE TABLE IF NOT EXISTS action_ledger (
54
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
55
+ ts INTEGER NOT NULL,
56
+ agent_id TEXT,
57
+ user_id TEXT,
58
+ tool TEXT NOT NULL,
59
+ command TEXT,
60
+ resource TEXT,
61
+ verdict TEXT NOT NULL,
62
+ policy_matched TEXT NOT NULL DEFAULT '[]',
63
+ reason TEXT,
64
+ credential_issued INTEGER NOT NULL DEFAULT 0,
65
+ credential_scope TEXT,
66
+ environment TEXT,
67
+ trace_id TEXT
68
+ );
69
+ CREATE INDEX IF NOT EXISTS idx_ledger_agent_ts ON action_ledger (agent_id, ts);
70
+ CREATE INDEX IF NOT EXISTS idx_ledger_ts ON action_ledger (ts);
71
+ CREATE INDEX IF NOT EXISTS idx_ledger_cred ON action_ledger (credential_scope, environment, ts);
72
+ `);
73
+ this.flush();
74
+ const pending = this.preInitQueue;
75
+ this.preInitQueue = [];
76
+ for (const rec of pending) this.appendActionRecord(rec);
77
+ logger.info({ path: LEDGER_DB, retentionDays: RETENTION_MS / 864e5 }, "action-ledger: initialised");
78
+ this.scheduleRetention();
79
+ } catch (err) {
80
+ logger.warn(
81
+ { err, wasmPath: this.wasmPath },
82
+ "action-ledger: init failed \u2014 durable action history unavailable. Set MERGEN_WASM_PATH to override the wasm location."
83
+ );
84
+ this.db = null;
85
+ }
86
+ }
87
+ /** Append a decision. Fire-and-forget from the gate path: never throws to the
88
+ * caller, never blocks on disk (the fsync is debounced + async). */
89
+ appendActionRecord(rec) {
90
+ try {
91
+ if (!this.db) {
92
+ if (this.preInitQueue.length < 1e4) this.preInitQueue.push(rec);
93
+ return;
94
+ }
95
+ this.db.run(
96
+ `INSERT INTO action_ledger
97
+ (ts, agent_id, user_id, tool, command, resource, verdict,
98
+ policy_matched, reason, credential_issued, credential_scope, environment, trace_id)
99
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`,
100
+ [
101
+ rec.ts ?? Date.now(),
102
+ rec.agentId ?? null,
103
+ rec.userId ?? null,
104
+ rec.tool,
105
+ rec.command ?? null,
106
+ rec.resource ?? null,
107
+ rec.verdict,
108
+ JSON.stringify(rec.policyMatched ?? []),
109
+ rec.reason ?? null,
110
+ rec.credentialIssued ? 1 : 0,
111
+ rec.credentialScope ?? null,
112
+ rec.environment ?? null,
113
+ rec.traceId ?? null
114
+ ]
115
+ );
116
+ this.writeCount++;
117
+ this.scheduleFlush();
118
+ this.forwardSiem(rec);
119
+ } catch (err) {
120
+ logger.warn({ err }, "action-ledger: append failed");
121
+ }
122
+ }
123
+ forwardSiem(rec) {
124
+ let id = 0;
125
+ try {
126
+ id = this.db.exec("SELECT last_insert_rowid()")[0]?.values[0]?.[0] ?? 0;
127
+ } catch {
128
+ }
129
+ void import("../intelligence/siem-forward.js").then(({ forwardLedgerToSiem }) => {
130
+ forwardLedgerToSiem({
131
+ id,
132
+ ts: rec.ts ?? Date.now(),
133
+ agentId: rec.agentId ?? null,
134
+ userId: rec.userId ?? null,
135
+ tool: rec.tool,
136
+ command: rec.command ?? null,
137
+ resource: rec.resource ?? null,
138
+ verdict: rec.verdict,
139
+ policyMatched: rec.policyMatched ?? [],
140
+ reason: rec.reason ?? null,
141
+ credentialIssued: rec.credentialIssued ?? false,
142
+ credentialScope: rec.credentialScope ?? null,
143
+ environment: rec.environment ?? null,
144
+ traceId: rec.traceId ?? null
145
+ });
146
+ }).catch(() => {
147
+ });
148
+ }
149
+ queryLedger(opts = {}) {
150
+ if (!this.db) return [];
151
+ try {
152
+ const { agentId, from, to, verdict, tool, limit = 100 } = opts;
153
+ const clauses = ["1=1"];
154
+ const params = [];
155
+ if (agentId) {
156
+ clauses.push("agent_id = ?");
157
+ params.push(agentId);
158
+ }
159
+ if (from !== void 0) {
160
+ clauses.push("ts >= ?");
161
+ params.push(from);
162
+ }
163
+ if (to !== void 0) {
164
+ clauses.push("ts <= ?");
165
+ params.push(to);
166
+ }
167
+ if (verdict) {
168
+ clauses.push("verdict = ?");
169
+ params.push(verdict);
170
+ }
171
+ if (tool) {
172
+ clauses.push("tool = ?");
173
+ params.push(tool);
174
+ }
175
+ const sql = `SELECT * FROM action_ledger WHERE ${clauses.join(" AND ")} ORDER BY ts DESC LIMIT ?`;
176
+ params.push(Math.min(Math.max(1, limit), 5e3));
177
+ return this.readRows(sql, params);
178
+ } catch (err) {
179
+ logger.warn({ err }, "action-ledger: query failed");
180
+ return [];
181
+ }
182
+ }
183
+ /** Credential-issuance rows only — feeds cross-environment misuse detection. */
184
+ queryCredentialUsage(opts = {}) {
185
+ if (!this.db) return [];
186
+ try {
187
+ const { agentId, scope, from, to, limit = 1e3 } = opts;
188
+ const clauses = ["credential_issued = 1"];
189
+ const params = [];
190
+ if (agentId) {
191
+ clauses.push("agent_id = ?");
192
+ params.push(agentId);
193
+ }
194
+ if (scope) {
195
+ clauses.push("credential_scope = ?");
196
+ params.push(scope);
197
+ }
198
+ if (from !== void 0) {
199
+ clauses.push("ts >= ?");
200
+ params.push(from);
201
+ }
202
+ if (to !== void 0) {
203
+ clauses.push("ts <= ?");
204
+ params.push(to);
205
+ }
206
+ const sql = `SELECT * FROM action_ledger WHERE ${clauses.join(" AND ")} ORDER BY ts DESC LIMIT ?`;
207
+ params.push(Math.min(Math.max(1, limit), 1e4));
208
+ return this.readRows(sql, params);
209
+ } catch (err) {
210
+ logger.warn({ err }, "action-ledger: credential query failed");
211
+ return [];
212
+ }
213
+ }
214
+ readRows(sql, params) {
215
+ const stmt = this.db.prepare(sql);
216
+ stmt.bind(params);
217
+ const rows = [];
218
+ while (stmt.step()) {
219
+ const r = stmt.getAsObject();
220
+ let policyMatched = [];
221
+ try {
222
+ policyMatched = JSON.parse(r["policy_matched"] ?? "[]");
223
+ } catch {
224
+ }
225
+ rows.push({
226
+ id: r["id"],
227
+ ts: r["ts"],
228
+ agentId: r["agent_id"] ?? null,
229
+ userId: r["user_id"] ?? null,
230
+ tool: r["tool"],
231
+ command: r["command"] ?? null,
232
+ resource: r["resource"] ?? null,
233
+ verdict: r["verdict"],
234
+ policyMatched,
235
+ reason: r["reason"] ?? null,
236
+ credentialIssued: r["credential_issued"] === 1,
237
+ credentialScope: r["credential_scope"] ?? null,
238
+ environment: r["environment"] ?? null,
239
+ traceId: r["trace_id"] ?? null
240
+ });
241
+ }
242
+ stmt.free();
243
+ return rows;
244
+ }
245
+ isHealthy() {
246
+ return this.db !== null;
247
+ }
248
+ /** Delete rows past the retention window, then enforce the hard row cap.
249
+ * Called on init, hourly, and (in tests) directly. */
250
+ prune() {
251
+ if (!this.db) return;
252
+ try {
253
+ this.db.run("DELETE FROM action_ledger WHERE ts < ?", [Date.now() - RETENTION_MS]);
254
+ const total = this.db.exec("SELECT COUNT(*) FROM action_ledger")[0]?.values[0]?.[0] ?? 0;
255
+ if (total > MAX_ROWS) {
256
+ const excess = total - MAX_ROWS;
257
+ this.db.run("DELETE FROM action_ledger WHERE id IN (SELECT id FROM action_ledger ORDER BY ts ASC LIMIT ?)", [excess]);
258
+ logger.warn({ excess }, "action-ledger: row cap exceeded \u2014 pruned oldest rows");
259
+ }
260
+ this.flush();
261
+ } catch (err) {
262
+ logger.warn({ err }, "action-ledger: retention prune failed");
263
+ }
264
+ }
265
+ scheduleRetention() {
266
+ this.prune();
267
+ setInterval(() => this.prune(), 60 * 6e4).unref();
268
+ }
269
+ // ── Disk persistence (async tmp+rename, coalesced) ──────────────────────────
270
+ scheduleFlush() {
271
+ if (this.flushTimer) return;
272
+ this.flushTimer = setTimeout(() => {
273
+ this.flushTimer = null;
274
+ this.flush();
275
+ }, FLUSH_DEBOUNCE_MS);
276
+ if (this.flushTimer && typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
277
+ this.flushTimer.unref();
278
+ }
279
+ }
280
+ isWriting = false;
281
+ pendingWrite = false;
282
+ nextBuffer = null;
283
+ /** Force a synchronous-enough flush; also used at graceful shutdown. */
284
+ flush() {
285
+ if (!this.db) return;
286
+ try {
287
+ const data = this.db.export();
288
+ this.writeBufferAsync(Buffer.from(data.buffer, data.byteOffset, data.byteLength));
289
+ } catch (err) {
290
+ logger.warn({ err }, "action-ledger: flush failed");
291
+ }
292
+ }
293
+ writeBufferAsync(buf) {
294
+ if (this.isWriting) {
295
+ this.pendingWrite = true;
296
+ this.nextBuffer = buf;
297
+ return;
298
+ }
299
+ this.isWriting = true;
300
+ const tmp = `${LEDGER_DB}.tmp.${process.pid}`;
301
+ fs.writeFile(tmp, buf, (err) => {
302
+ if (err) {
303
+ logger.warn({ err }, "action-ledger: async write failed");
304
+ this.isWriting = false;
305
+ this.processPendingWrite();
306
+ return;
307
+ }
308
+ fs.rename(tmp, LEDGER_DB, (renameErr) => {
309
+ if (renameErr) logger.warn({ err: renameErr }, "action-ledger: async rename failed");
310
+ this.isWriting = false;
311
+ this.processPendingWrite();
312
+ });
313
+ });
314
+ }
315
+ processPendingWrite() {
316
+ if (this.pendingWrite && this.nextBuffer) {
317
+ this.pendingWrite = false;
318
+ const buf = this.nextBuffer;
319
+ this.nextBuffer = null;
320
+ this.writeBufferAsync(buf);
321
+ }
322
+ }
323
+ }
324
+ const actionLedger = new ActionLedger();
325
+ export {
326
+ actionLedger
327
+ };
@@ -0,0 +1,140 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { DATA_DIR, zeroRetentionMode } from "./paths.js";
4
+ import logger from "./logger.js";
5
+ const ADR_FILE = path.join(DATA_DIR, "adrs.json");
6
+ const SEED_ADRS = [
7
+ {
8
+ id: "ADR-001",
9
+ title: "Ring buffer as primary event store",
10
+ status: "accepted",
11
+ date: "2024-01-15",
12
+ decision: "Use a fixed-capacity (2 000-event) in-memory ring buffer as the primary store for inbound telemetry events, with O(1) eviction of the oldest event when the cap is reached.",
13
+ alternatives: [
14
+ "Unlimited in-memory list \u2014 rejected: unbounded memory growth would cause OOM crashes",
15
+ "Write-through to SQLite on every event \u2014 rejected: synchronous disk I/O bottlenecks throughput during incident spikes",
16
+ "Redis as primary store \u2014 rejected: adds an external dependency that breaks the zero-infrastructure install story"
17
+ ],
18
+ rationale: "Incident triage tools need the most recent events, not a complete historical ledger. A ring buffer keeps the hot path allocation-free, provides constant-time reads and writes, and caps memory at a predictable ceiling.",
19
+ consequences: "Reading more than 2 000 events requires querying the SQLite history store. The cap is tunable via MERGEN_BUFFER_SIZE env var."
20
+ },
21
+ {
22
+ id: "ADR-002",
23
+ title: "MCP protocol over custom REST for AI IDE integration",
24
+ status: "accepted",
25
+ date: "2024-02-01",
26
+ decision: "Expose Mergen's analysis and triage capabilities as Model Context Protocol (MCP) tools over stdio transport, rather than a proprietary REST API.",
27
+ alternatives: [
28
+ "Custom REST API with IDE plugins \u2014 rejected: every IDE needs a bespoke plugin; integration surface multiplies with each new IDE",
29
+ "Language Server Protocol (LSP) \u2014 rejected: LSP is designed for code intelligence, not arbitrary tool invocation",
30
+ "OpenAI function-calling format \u2014 rejected: not IDE-agnostic; couples Mergen to a single model vendor"
31
+ ],
32
+ rationale: "MCP is the emerging standard for AI tool exposure. A single MCP server declaration in a project config file immediately surfaces all Mergen tools in every compatible IDE.",
33
+ consequences: "The HTTP server (:3000) and MCP server (stdio) are separate processes sharing state via the in-memory buffer. MCP version upgrades may require updating tool registration patterns."
34
+ },
35
+ {
36
+ id: "ADR-003",
37
+ title: "SQLite for persistent event history",
38
+ status: "accepted",
39
+ date: "2024-02-15",
40
+ decision: "Use SQLite (via sql.js) for the 1-hour persistent event history layer, rather than a networked database or plain JSON files.",
41
+ alternatives: [
42
+ "Plain JSON files \u2014 rejected: not atomic; crash mid-write corrupts the file; range queries require full deserialisation",
43
+ "PostgreSQL / MySQL \u2014 rejected: requires a running database server, breaking the zero-infrastructure install story",
44
+ "Redis \u2014 rejected: persistence config complexity; not enabled by default",
45
+ "LevelDB / RocksDB \u2014 rejected: native bindings fail on ARM Macs and Alpine Linux"
46
+ ],
47
+ rationale: "sql.js compiles SQLite to WebAssembly \u2014 zero native bindings, consistent binary on every platform. SQLite provides ACID guarantees and efficient timestamp range scans.",
48
+ consequences: "The sql.js WASM binary adds ~1.2 MB to the installed package. Write throughput is single-threaded. Schema migrations must be handled manually in sqlite-store.ts."
49
+ },
50
+ {
51
+ id: "ADR-004",
52
+ title: "Ingest server binds to 127.0.0.1 by default",
53
+ status: "accepted",
54
+ date: "2024-03-01",
55
+ decision: "The Express HTTP server listens on 127.0.0.1:3000 (loopback) by default, not 0.0.0.0.",
56
+ alternatives: [
57
+ "Bind to 0.0.0.0 by default \u2014 rejected: any process on the local network could read the event buffer or trigger autonomous fix execution",
58
+ "Bind to 0.0.0.0 with mandatory API key \u2014 rejected: key management burden defeats the zero-config local install goal",
59
+ "Unix domain socket \u2014 rejected: not supported on Windows"
60
+ ],
61
+ rationale: "Developer tools should be safe by default. Loopback binding limits the attack surface to processes already on the same machine. Teams needing network access can set MERGEN_HOST=0.0.0.0.",
62
+ consequences: "Browser extensions and SDKs must use 127.0.0.1, not localhost. Docker deployments require --network=host or explicit port mapping. Cloud mode overrides this to 0.0.0.0 with TLS."
63
+ },
64
+ {
65
+ id: "ADR-005",
66
+ title: "Three-tier tool access model (free / pro / all)",
67
+ status: "accepted",
68
+ date: "2024-04-01",
69
+ decision: "Classify every MCP tool into free, pro, or all tiers enforced via withTierGate(). The canonical tier for each tool is declared in tool-manifest.ts.",
70
+ alternatives: [
71
+ "Binary free / paid split \u2014 rejected: too coarse; some read-only tools are safe to offer free while execution tools carry risk",
72
+ "Per-tool pricing \u2014 rejected: metering complexity and unpredictable bills",
73
+ "No gating at all \u2014 rejected: unrestricted execution tools represent liability without a paid support contract"
74
+ ],
75
+ rationale: "Three tiers maps onto what engineers actually need: free for read-only analysis, pro for execution and third-party API integration, all for tools with no restriction.",
76
+ consequences: "New tools must be added to tool-manifest.ts with a tier before registration. The manifest consistency test will fail otherwise. withTierGate() returns an upgrade prompt rather than a 403."
77
+ }
78
+ ];
79
+ class AdrStore {
80
+ records = /* @__PURE__ */ new Map();
81
+ constructor() {
82
+ for (const adr of SEED_ADRS) {
83
+ this.records.set(adr.id, adr);
84
+ }
85
+ this._loadFromDisk();
86
+ }
87
+ _loadFromDisk() {
88
+ try {
89
+ if (!fs.existsSync(ADR_FILE)) return;
90
+ const raw = fs.readFileSync(ADR_FILE, "utf8");
91
+ const extra = JSON.parse(raw);
92
+ for (const adr of extra) {
93
+ if (adr.id && !this.records.has(adr.id)) {
94
+ this.records.set(adr.id, adr);
95
+ }
96
+ }
97
+ } catch {
98
+ }
99
+ }
100
+ _saveToDisk() {
101
+ if (zeroRetentionMode()) return;
102
+ try {
103
+ if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
104
+ const userAdrs = [...this.records.values()].filter(
105
+ (r) => !SEED_ADRS.some((s) => s.id === r.id)
106
+ );
107
+ fs.writeFileSync(ADR_FILE, JSON.stringify(userAdrs, null, 2), "utf8");
108
+ } catch (err) {
109
+ logger.warn({ err }, "adr-store: failed to persist to disk");
110
+ }
111
+ }
112
+ list(query) {
113
+ const all = [...this.records.values()];
114
+ if (!query) return all;
115
+ const q = query.toLowerCase();
116
+ return all.filter(
117
+ (r) => r.id.toLowerCase().includes(q) || r.title.toLowerCase().includes(q) || r.decision.toLowerCase().includes(q) || r.rationale.toLowerCase().includes(q) || r.alternatives.some((a) => a.toLowerCase().includes(q))
118
+ );
119
+ }
120
+ get(id) {
121
+ return this.records.get(id.toUpperCase());
122
+ }
123
+ add(record) {
124
+ const nextNum = this.records.size + 1;
125
+ const id = `ADR-${String(nextNum).padStart(3, "0")}`;
126
+ const adr = { id, ...record };
127
+ this.records.set(id, adr);
128
+ this._saveToDisk();
129
+ logger.info({ id }, "adr-store: new ADR recorded");
130
+ return adr;
131
+ }
132
+ upsert(record) {
133
+ this.records.set(record.id.toUpperCase(), record);
134
+ this._saveToDisk();
135
+ }
136
+ }
137
+ const adrStore = new AdrStore();
138
+ export {
139
+ adrStore
140
+ };