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,370 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { createHash, createHmac, randomUUID, randomBytes, timingSafeEqual } from "crypto";
4
+ import { DATA_DIR, zeroRetentionMode } from "./paths.js";
5
+ import logger from "./logger.js";
6
+ import { lockAndExecute } from "./file-lock.js";
7
+ const BLUNDER_FILE = path.join(DATA_DIR, "agent-blunders.json");
8
+ const BLUNDER_HMAC_FILE = BLUNDER_FILE + ".hmac";
9
+ const CHECKPOINT_FILE = path.join(DATA_DIR, "agent-blunders.checkpoints.jsonl");
10
+ const CHECKPOINT_INTERVAL = 50;
11
+ let _blunderHmacSecret = "";
12
+ let _blunderHmacSecretConfigured = false;
13
+ function setBlunderHmacSecret(secret) {
14
+ _blunderHmacSecret = secret;
15
+ _blunderHmacSecretConfigured = true;
16
+ }
17
+ function _resetBlunderHmacSecretForTesting() {
18
+ _blunderHmacSecret = "";
19
+ _blunderHmacSecretConfigured = false;
20
+ }
21
+ function _blunderHmacKey() {
22
+ return process.env.MERGEN_AUDIT_SECRET || _blunderHmacSecret;
23
+ }
24
+ function hmacKeyConfigured() {
25
+ return !!(process.env.MERGEN_AUDIT_SECRET || _blunderHmacSecretConfigured);
26
+ }
27
+ function _writeHmac(contents) {
28
+ if (!hmacKeyConfigured()) return;
29
+ try {
30
+ const hmac = createHmac("sha256", _blunderHmacKey()).update(contents).digest("hex");
31
+ fs.writeFileSync(BLUNDER_HMAC_FILE, hmac, "utf8");
32
+ } catch {
33
+ }
34
+ }
35
+ function _verifyHmac(contents) {
36
+ if (!hmacKeyConfigured()) return true;
37
+ try {
38
+ const stored = fs.readFileSync(BLUNDER_HMAC_FILE, "utf8").trim();
39
+ const expected = createHmac("sha256", _blunderHmacKey()).update(contents).digest("hex");
40
+ const storedBuf = Buffer.from(stored, "hex");
41
+ const expectedBuf = Buffer.from(expected, "hex");
42
+ if (storedBuf.length !== expectedBuf.length) return false;
43
+ return timingSafeEqual(storedBuf, expectedBuf);
44
+ } catch {
45
+ return true;
46
+ }
47
+ }
48
+ function emitCheckpoint(latest) {
49
+ const checkpoint = {
50
+ sequenceNumber: latest.sequenceNumber,
51
+ hash: latest.hash,
52
+ timestamp: Date.now()
53
+ };
54
+ logger.info({ checkpoint }, "agent-blunder-store: audit checkpoint");
55
+ if (zeroRetentionMode() || _testingMode || process.env.VITEST) return;
56
+ try {
57
+ fs.mkdirSync(DATA_DIR, { recursive: true, mode: 448 });
58
+ fs.appendFileSync(CHECKPOINT_FILE, JSON.stringify(checkpoint) + "\n", { encoding: "utf8", flag: "a" });
59
+ } catch (err) {
60
+ logger.warn({ err }, "agent-blunder-store: checkpoint sidecar write failed (non-fatal)");
61
+ }
62
+ }
63
+ function tamperEvidenceLevel(chainHasVerifiedEntries) {
64
+ if (!chainHasVerifiedEntries) return "none";
65
+ return hmacKeyConfigured() ? "hmac-sealed" : "hash-chain";
66
+ }
67
+ const MAX_BLUNDERS = (() => {
68
+ const v = parseInt(process.env.MERGEN_MAX_BLUNDERS ?? "5000", 10);
69
+ return Number.isFinite(v) && v > 0 ? Math.min(v, 1e5) : 5e3;
70
+ })();
71
+ const GENESIS_HASH = "0".repeat(64);
72
+ let _blunders = [];
73
+ let _loaded = false;
74
+ let _nextSequenceNumber = 0;
75
+ let _testingMode = false;
76
+ function _hashableContent(event, previousHash) {
77
+ const content = {
78
+ id: event.id,
79
+ recordedAt: event.recordedAt,
80
+ blunderType: event.blunderType,
81
+ command: event.command,
82
+ blockReason: event.blockReason,
83
+ service: event.service,
84
+ tag: event.tag,
85
+ actor: event.actor,
86
+ pid: event.pid,
87
+ confidenceScore: event.confidenceScore
88
+ };
89
+ if (event.triggeredRules !== void 0) content.triggeredRules = event.triggeredRules;
90
+ if (event.agentId !== void 0) content.agentId = event.agentId;
91
+ if (event.parentAgentId !== void 0) content.parentAgentId = event.parentAgentId;
92
+ if (event.humanOwner !== void 0) content.humanOwner = event.humanOwner;
93
+ if (event.sessionId !== void 0) content.sessionId = event.sessionId;
94
+ if (event.traceId !== void 0) content.traceId = event.traceId;
95
+ if (event.sequenceNumber !== void 0) content.sequenceNumber = event.sequenceNumber;
96
+ return previousHash + JSON.stringify(content);
97
+ }
98
+ function _computeHash(event, previousHash) {
99
+ return createHash("sha256").update(_hashableContent(event, previousHash)).digest("hex");
100
+ }
101
+ let _integrityViolated = false;
102
+ function isBlunderIntegrityViolated() {
103
+ return _integrityViolated;
104
+ }
105
+ function load(force = false) {
106
+ if (_testingMode) return;
107
+ if (_loaded && !force) return;
108
+ _loaded = true;
109
+ if (!fs.existsSync(BLUNDER_FILE)) {
110
+ _blunders = [];
111
+ return;
112
+ }
113
+ try {
114
+ const contents = fs.readFileSync(BLUNDER_FILE, "utf8");
115
+ if (!_verifyHmac(contents)) {
116
+ _integrityViolated = true;
117
+ logger.error({ path: BLUNDER_FILE }, "agent-blunder-store: HMAC mismatch \u2014 file may have been tampered with");
118
+ }
119
+ const raw = JSON.parse(contents);
120
+ if ((raw?.version === 3 || raw?.version === 2 || raw?.version === 1) && Array.isArray(raw.blunders)) {
121
+ _blunders = raw.blunders;
122
+ const maxSeen = _blunders.reduce((max, b) => typeof b.sequenceNumber === "number" ? Math.max(max, b.sequenceNumber) : max, -1);
123
+ _nextSequenceNumber = typeof raw.nextSequenceNumber === "number" ? raw.nextSequenceNumber : maxSeen + 1;
124
+ } else {
125
+ _blunders = [];
126
+ _nextSequenceNumber = 0;
127
+ }
128
+ } catch {
129
+ _blunders = [];
130
+ _nextSequenceNumber = 0;
131
+ }
132
+ }
133
+ function persist() {
134
+ if (zeroRetentionMode()) return;
135
+ try {
136
+ fs.mkdirSync(DATA_DIR, { recursive: true, mode: 448 });
137
+ const tmp = `${BLUNDER_FILE}.tmp.${process.pid}.${randomBytes(4).toString("hex")}`;
138
+ const contents = JSON.stringify({ version: 3, blunders: _blunders, nextSequenceNumber: _nextSequenceNumber });
139
+ fs.writeFileSync(tmp, contents, "utf8");
140
+ fs.renameSync(tmp, BLUNDER_FILE);
141
+ _writeHmac(contents);
142
+ } catch (err) {
143
+ logger.warn({ err }, "agent-blunder-store: persist failed");
144
+ }
145
+ }
146
+ function _resetForTesting() {
147
+ _blunders = [];
148
+ _loaded = true;
149
+ _testingMode = true;
150
+ _nextSequenceNumber = 0;
151
+ }
152
+ function _injectRawForTesting(entry) {
153
+ _blunders.push({
154
+ command: null,
155
+ service: null,
156
+ tag: null,
157
+ actor: null,
158
+ pid: null,
159
+ confidenceScore: null,
160
+ previousHash: "",
161
+ hash: "",
162
+ ...entry
163
+ });
164
+ }
165
+ function recordBlunder(event) {
166
+ return lockAndExecute(`${BLUNDER_FILE}.lock`, () => {
167
+ load(true);
168
+ const id = event.id ?? randomUUID();
169
+ if (_blunders.some((b) => b.id === id)) {
170
+ return;
171
+ }
172
+ const base = {
173
+ ...event,
174
+ id,
175
+ recordedAt: event.recordedAt ?? Date.now(),
176
+ // Normalize to null so every new entry carries the field explicitly —
177
+ // presence distinguishes v3 entries from pre-migration v2 ones.
178
+ triggeredRules: event.triggeredRules ?? null,
179
+ // Agent identity — normalize undefined → null so presence-aware hashing
180
+ // works correctly: new entries always carry these fields explicitly.
181
+ agentId: event.agentId ?? null,
182
+ parentAgentId: event.parentAgentId ?? null,
183
+ humanOwner: event.humanOwner ?? null,
184
+ sessionId: event.sessionId ?? null,
185
+ traceId: event.traceId ?? null,
186
+ sequenceNumber: _nextSequenceNumber
187
+ };
188
+ const previousHash = _blunders.length > 0 ? _blunders[_blunders.length - 1].hash || GENESIS_HASH : GENESIS_HASH;
189
+ const hash = _computeHash(base, previousHash);
190
+ const entry = { ...base, previousHash, hash };
191
+ _blunders.push(entry);
192
+ _nextSequenceNumber++;
193
+ if (_blunders.length > MAX_BLUNDERS) _blunders = _blunders.slice(-MAX_BLUNDERS);
194
+ persist();
195
+ if (entry.sequenceNumber % CHECKPOINT_INTERVAL === 0) emitCheckpoint(entry);
196
+ void import("../intelligence/siem-forward.js").then(({ forwardToSiem }) => forwardToSiem(entry)).catch(() => {
197
+ });
198
+ logger.info(
199
+ { blunderType: event.blunderType, cmd: event.command?.slice(0, 80), service: event.service, agentId: event.agentId },
200
+ "agent-blunder: intercepted"
201
+ );
202
+ });
203
+ }
204
+ function getBlunders(filter) {
205
+ load(true);
206
+ if (filter?.agentId) {
207
+ return _blunders.filter((b) => b.agentId === filter.agentId);
208
+ }
209
+ return [..._blunders];
210
+ }
211
+ const NON_BLOCKING_BLUNDER_TYPES = /* @__PURE__ */ new Set(["credential_issued", "policy_integrity_failure"]);
212
+ function isNonBlockingBlunderType(blunderType) {
213
+ return NON_BLOCKING_BLUNDER_TYPES.has(blunderType);
214
+ }
215
+ function getBlunderStats() {
216
+ load(true);
217
+ const now = Date.now();
218
+ const ms7 = 7 * 24 * 60 * 60 * 1e3;
219
+ const ms30 = 30 * 24 * 60 * 60 * 1e3;
220
+ const byType = {};
221
+ let total = 0, last7Days = 0, last30Days = 0;
222
+ for (const b of _blunders) {
223
+ byType[b.blunderType] = (byType[b.blunderType] ?? 0) + 1;
224
+ if (NON_BLOCKING_BLUNDER_TYPES.has(b.blunderType)) continue;
225
+ total++;
226
+ if (b.recordedAt >= now - ms7) last7Days++;
227
+ if (b.recordedAt >= now - ms30) last30Days++;
228
+ }
229
+ return { total, byType, last7Days, last30Days };
230
+ }
231
+ function verifyChain() {
232
+ load(true);
233
+ const hmacProtected = hmacKeyConfigured();
234
+ if (_blunders.length === 0) {
235
+ return { valid: true, verified: 0, note: "Log is empty \u2014 nothing to verify", tamperEvidenceLevel: "none", hmacProtected };
236
+ }
237
+ const firstV2Idx = _blunders.findIndex((b) => !!b.hash);
238
+ if (firstV2Idx === -1) {
239
+ return {
240
+ valid: true,
241
+ verified: 0,
242
+ truncated: false,
243
+ note: "No cryptographically-verified entries \u2014 all entries predate hash-chain migration. Chain integrity cannot be confirmed.",
244
+ tamperEvidenceLevel: "none",
245
+ hmacProtected
246
+ };
247
+ }
248
+ const anchor = _blunders[firstV2Idx];
249
+ const truncated = anchor.previousHash !== GENESIS_HASH;
250
+ let expectedPrev = anchor.previousHash;
251
+ let seenFirstV2 = false;
252
+ let expectedSeq = null;
253
+ for (let i = firstV2Idx; i < _blunders.length; i++) {
254
+ const b = _blunders[i];
255
+ if (!b.hash) {
256
+ return {
257
+ valid: false,
258
+ firstInvalidIdx: i,
259
+ reason: `entry at index ${i} (id=${b.id}) is missing hash field after v2 section began \u2014 possible injection`,
260
+ tamperEvidenceLevel: tamperEvidenceLevel(true),
261
+ hmacProtected
262
+ };
263
+ }
264
+ if (!seenFirstV2) {
265
+ seenFirstV2 = true;
266
+ } else {
267
+ if (b.previousHash !== expectedPrev) {
268
+ return {
269
+ valid: false,
270
+ firstInvalidIdx: i,
271
+ reason: `previousHash mismatch at index ${i}: expected ${expectedPrev.slice(0, 8)}\u2026 got ${b.previousHash.slice(0, 8)}\u2026`,
272
+ tamperEvidenceLevel: tamperEvidenceLevel(true),
273
+ hmacProtected
274
+ };
275
+ }
276
+ }
277
+ if (typeof b.sequenceNumber === "number") {
278
+ if (expectedSeq !== null && b.sequenceNumber !== expectedSeq) {
279
+ return {
280
+ valid: false,
281
+ firstInvalidIdx: i,
282
+ reason: `sequenceNumber gap at index ${i} (id=${b.id}): expected ${expectedSeq}, got ${b.sequenceNumber} \u2014 an entry may have been removed`,
283
+ tamperEvidenceLevel: tamperEvidenceLevel(true),
284
+ hmacProtected
285
+ };
286
+ }
287
+ expectedSeq = b.sequenceNumber + 1;
288
+ } else {
289
+ expectedSeq = null;
290
+ }
291
+ const { hash: _h, previousHash: _p, ...rest } = b;
292
+ const recomputed = _computeHash(rest, b.previousHash);
293
+ if (recomputed !== b.hash) {
294
+ return {
295
+ valid: false,
296
+ firstInvalidIdx: i,
297
+ reason: `hash mismatch at index ${i} (id=${b.id}): content was modified after recording`,
298
+ tamperEvidenceLevel: tamperEvidenceLevel(true),
299
+ hmacProtected
300
+ };
301
+ }
302
+ expectedPrev = b.hash;
303
+ }
304
+ const verifiedCount = _blunders.length - firstV2Idx;
305
+ return {
306
+ valid: true,
307
+ truncated,
308
+ verifiedFrom: anchor.id,
309
+ verified: verifiedCount,
310
+ tamperEvidenceLevel: tamperEvidenceLevel(true),
311
+ hmacProtected
312
+ };
313
+ }
314
+ function verifyCheckpoints() {
315
+ load(true);
316
+ const mismatches = [];
317
+ let raw;
318
+ try {
319
+ raw = fs.readFileSync(CHECKPOINT_FILE, "utf8");
320
+ } catch {
321
+ return { checked: 0, mismatches: [] };
322
+ }
323
+ const bySeq = /* @__PURE__ */ new Map();
324
+ for (const b of _blunders) {
325
+ if (typeof b.sequenceNumber === "number") bySeq.set(b.sequenceNumber, b);
326
+ }
327
+ let checked = 0;
328
+ for (const line of raw.split("\n")) {
329
+ if (!line.trim()) continue;
330
+ let checkpoint;
331
+ try {
332
+ checkpoint = JSON.parse(line);
333
+ } catch {
334
+ continue;
335
+ }
336
+ checked++;
337
+ const current = bySeq.get(checkpoint.sequenceNumber);
338
+ if (!current) {
339
+ mismatches.push({
340
+ sequenceNumber: checkpoint.sequenceNumber,
341
+ expectedHash: checkpoint.hash,
342
+ reason: "entry no longer exists in the live chain (evicted by normal ring-buffer rollover, or deleted)"
343
+ });
344
+ } else if (current.hash !== checkpoint.hash) {
345
+ mismatches.push({
346
+ sequenceNumber: checkpoint.sequenceNumber,
347
+ expectedHash: checkpoint.hash,
348
+ reason: `live entry's hash (${current.hash.slice(0, 8)}\u2026) does not match the checkpointed hash \u2014 content was altered after the checkpoint was recorded`
349
+ });
350
+ }
351
+ }
352
+ return { checked, mismatches };
353
+ }
354
+ export {
355
+ MAX_BLUNDERS,
356
+ NON_BLOCKING_BLUNDER_TYPES,
357
+ _injectRawForTesting,
358
+ _resetBlunderHmacSecretForTesting,
359
+ _resetForTesting,
360
+ getBlunderStats,
361
+ getBlunders,
362
+ hmacKeyConfigured,
363
+ isBlunderIntegrityViolated,
364
+ isNonBlockingBlunderType,
365
+ recordBlunder,
366
+ setBlunderHmacSecret,
367
+ tamperEvidenceLevel,
368
+ verifyChain,
369
+ verifyCheckpoints
370
+ };
@@ -0,0 +1,225 @@
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
+ import { randomUUID } from "crypto";
9
+ const LEGACY_CONTEXT_DB = path.join(DATA_DIR, "agent-memory.db");
10
+ const CONTEXT_DB = path.join(DATA_DIR, "agent-context.db");
11
+ class AgentContextStore {
12
+ db = null;
13
+ resolveWasmPath() {
14
+ if (process.env.MERGEN_WASM_PATH) return process.env.MERGEN_WASM_PATH;
15
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
16
+ const fromModule = path.resolve(moduleDir, "../../node_modules/sql.js/dist/sql-wasm.wasm");
17
+ if (fs.existsSync(fromModule)) return fromModule;
18
+ try {
19
+ const req = createRequire(import.meta.url);
20
+ const resolved = path.join(path.dirname(req.resolve("sql.js")), "sql-wasm.wasm");
21
+ if (fs.existsSync(resolved)) return resolved;
22
+ } catch {
23
+ }
24
+ return fromModule;
25
+ }
26
+ async init() {
27
+ try {
28
+ const wasmBinary = fs.readFileSync(this.resolveWasmPath());
29
+ const SQL = await initSqlJs({ wasmBinary });
30
+ fs.mkdirSync(DATA_DIR, { recursive: true });
31
+ if (fs.existsSync(LEGACY_CONTEXT_DB) && !fs.existsSync(CONTEXT_DB)) {
32
+ try {
33
+ fs.renameSync(LEGACY_CONTEXT_DB, CONTEXT_DB);
34
+ } catch {
35
+ }
36
+ }
37
+ let fileBuffer;
38
+ if (fs.existsSync(CONTEXT_DB)) {
39
+ try {
40
+ fileBuffer = fs.readFileSync(CONTEXT_DB);
41
+ } catch {
42
+ }
43
+ }
44
+ this.db = fileBuffer ? new SQL.Database(fileBuffer) : new SQL.Database();
45
+ this.db.run(`
46
+ CREATE TABLE IF NOT EXISTS agent_context (
47
+ id TEXT PRIMARY KEY,
48
+ agent_id TEXT NOT NULL DEFAULT '',
49
+ key TEXT NOT NULL,
50
+ value TEXT NOT NULL,
51
+ stored_at INTEGER NOT NULL,
52
+ ttl_ms INTEGER NOT NULL DEFAULT 0,
53
+ service TEXT NOT NULL DEFAULT '',
54
+ error_fingerprint TEXT NOT NULL DEFAULT ''
55
+ );
56
+ CREATE INDEX IF NOT EXISTS idx_agent_context_agent_key ON agent_context (agent_id, key);
57
+ CREATE INDEX IF NOT EXISTS idx_agent_context_episodic ON agent_context (service, error_fingerprint, stored_at);
58
+ `);
59
+ this.flush();
60
+ try {
61
+ this.db.run(`ALTER TABLE agent_memory RENAME TO agent_context`);
62
+ } catch {
63
+ }
64
+ try {
65
+ this.db.run(`ALTER TABLE agent_context ADD COLUMN service TEXT NOT NULL DEFAULT ''`);
66
+ } catch {
67
+ }
68
+ try {
69
+ this.db.run(`ALTER TABLE agent_context ADD COLUMN error_fingerprint TEXT NOT NULL DEFAULT ''`);
70
+ } catch {
71
+ }
72
+ try {
73
+ this.db.run(`CREATE INDEX IF NOT EXISTS idx_agent_context_episodic ON agent_context (service, error_fingerprint, stored_at)`);
74
+ } catch {
75
+ }
76
+ logger.debug({ path: CONTEXT_DB }, "agent-context-store: initialized");
77
+ this.scheduleCleanup();
78
+ } catch (err) {
79
+ logger.error({ err }, "agent-context-store: init failed");
80
+ }
81
+ }
82
+ scheduleCleanup() {
83
+ const run = () => {
84
+ if (!this.db) return;
85
+ try {
86
+ const now = Date.now();
87
+ this.db.run("DELETE FROM agent_context WHERE ttl_ms > 0 AND stored_at + ttl_ms < ?", [now]);
88
+ this.flush();
89
+ try {
90
+ const stat = fs.statSync(CONTEXT_DB);
91
+ if (stat.size > 100 * 1024 * 1024) {
92
+ this.db.run("DELETE FROM agent_context WHERE id IN (SELECT id FROM agent_context ORDER BY stored_at ASC LIMIT 1000)");
93
+ this.flush();
94
+ logger.warn("agent-context-store: DB exceeded 100 MB cap \u2014 pruned oldest 1000 entries");
95
+ }
96
+ } catch {
97
+ }
98
+ } catch (err) {
99
+ logger.warn({ err }, "agent-context-store: cleanup failed");
100
+ }
101
+ };
102
+ run();
103
+ setInterval(run, 60 * 6e4).unref();
104
+ }
105
+ flush() {
106
+ if (!this.db) return;
107
+ try {
108
+ const data = this.db.export();
109
+ fs.writeFileSync(CONTEXT_DB, Buffer.from(data));
110
+ } catch (err) {
111
+ logger.warn({ err }, "agent-context-store: flush failed");
112
+ }
113
+ }
114
+ store(agentId, key, value, ttlMs = 0, service = "", errorFingerprint = "") {
115
+ if (!this.db) throw new Error("agent-context-store: not initialized");
116
+ const now = Date.now();
117
+ const existing = this.recall(agentId, key, 1);
118
+ const id = existing[0]?.id ?? randomUUID();
119
+ this.db.run(
120
+ `INSERT OR REPLACE INTO agent_context
121
+ (id, agent_id, key, value, stored_at, ttl_ms, service, error_fingerprint)
122
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
123
+ [id, agentId, key, value, now, ttlMs, service, errorFingerprint]
124
+ );
125
+ this.flush();
126
+ return { id, agentId, key, value, storedAt: now, ttlMs, service, errorFingerprint };
127
+ }
128
+ recall(agentId, key, limit = 20, service, errorFingerprint) {
129
+ if (!this.db) return [];
130
+ const now = Date.now();
131
+ const clauses = ["(ttl_ms = 0 OR stored_at + ttl_ms > ?)"];
132
+ const params = [now];
133
+ if (agentId) {
134
+ clauses.push("agent_id = ?");
135
+ params.push(agentId);
136
+ }
137
+ if (key) {
138
+ clauses.push("key = ?");
139
+ params.push(key);
140
+ }
141
+ if (service) {
142
+ clauses.push("service = ?");
143
+ params.push(service);
144
+ }
145
+ if (errorFingerprint) {
146
+ clauses.push("error_fingerprint = ?");
147
+ params.push(errorFingerprint);
148
+ }
149
+ const sql = `SELECT * FROM agent_context WHERE ${clauses.join(" AND ")} ORDER BY stored_at DESC LIMIT ?`;
150
+ params.push(limit);
151
+ try {
152
+ const stmt = this.db.prepare(sql);
153
+ stmt.bind(params);
154
+ const rows = [];
155
+ while (stmt.step()) {
156
+ const r = stmt.getAsObject();
157
+ rows.push({
158
+ id: r["id"],
159
+ agentId: r["agent_id"],
160
+ key: r["key"],
161
+ value: r["value"],
162
+ storedAt: r["stored_at"],
163
+ ttlMs: r["ttl_ms"],
164
+ service: r["service"] ?? "",
165
+ errorFingerprint: r["error_fingerprint"] ?? ""
166
+ });
167
+ }
168
+ stmt.free();
169
+ return rows;
170
+ } catch (err) {
171
+ logger.warn({ err }, "agent-context-store: recall failed");
172
+ return [];
173
+ }
174
+ }
175
+ recallEpisodic(service, errorFingerprint, limit = 10) {
176
+ return this.recall(void 0, void 0, limit, service, errorFingerprint);
177
+ }
178
+ isHealthy() {
179
+ return this.db !== null;
180
+ }
181
+ listKeys(agentId) {
182
+ if (!this.db) return [];
183
+ const now = Date.now();
184
+ const clauses = ["(ttl_ms = 0 OR stored_at + ttl_ms > ?)"];
185
+ const params = [now];
186
+ if (agentId) {
187
+ clauses.push("agent_id = ?");
188
+ params.push(agentId);
189
+ }
190
+ const sql = `SELECT agent_id, key, MAX(stored_at) AS last_at
191
+ FROM agent_context
192
+ WHERE ${clauses.join(" AND ")}
193
+ GROUP BY agent_id, key
194
+ ORDER BY last_at DESC`;
195
+ try {
196
+ const stmt = this.db.prepare(sql);
197
+ stmt.bind(params);
198
+ const rows = [];
199
+ while (stmt.step()) {
200
+ const r = stmt.getAsObject();
201
+ rows.push({
202
+ agentId: r["agent_id"],
203
+ key: r["key"],
204
+ lastStoredAt: r["last_at"]
205
+ });
206
+ }
207
+ stmt.free();
208
+ return rows;
209
+ } catch (err) {
210
+ logger.warn({ err }, "agent-context-store: listKeys failed");
211
+ return [];
212
+ }
213
+ }
214
+ forget(agentId, key) {
215
+ if (!this.db) return;
216
+ this.db.run("DELETE FROM agent_context WHERE agent_id = ? AND key = ?", [agentId, key]);
217
+ this.flush();
218
+ }
219
+ }
220
+ const agentContextStore = new AgentContextStore();
221
+ const agentMemoryStore = agentContextStore;
222
+ export {
223
+ agentContextStore,
224
+ agentMemoryStore
225
+ };