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.
- package/LICENSE +57 -0
- package/README.md +96 -0
- package/dist/__stubs__/calibration.js +471 -0
- package/dist/__stubs__/causal.js +601 -0
- package/dist/__stubs__/closed-source.js +150 -0
- package/dist/__stubs__/license.js +35 -0
- package/dist/__stubs__/plans.js +197 -0
- package/dist/app.js +467 -0
- package/dist/cli.js +250 -0
- package/dist/commands/agent-identity.js +79 -0
- package/dist/commands/gate.js +107 -0
- package/dist/commands/github.js +632 -0
- package/dist/commands/incident.js +644 -0
- package/dist/commands/policy.js +710 -0
- package/dist/commands/scan.js +243 -0
- package/dist/commands/setup.js +1218 -0
- package/dist/commands/shared.js +109 -0
- package/dist/commands/team.js +809 -0
- package/dist/datadog/blame-attribution.js +228 -0
- package/dist/datadog/client.js +159 -0
- package/dist/datadog/compactor.js +169 -0
- package/dist/datadog/fingerprinter.js +34 -0
- package/dist/datadog/incident-state.js +21 -0
- package/dist/datadog/line-matcher.js +56 -0
- package/dist/datadog/memory-store.js +399 -0
- package/dist/datadog/otel-trace.js +67 -0
- package/dist/index.js +505 -0
- package/dist/intelligence/action-risk.js +132 -0
- package/dist/intelligence/activity-feed.js +40 -0
- package/dist/intelligence/agent-identity.js +115 -0
- package/dist/intelligence/agent-pipeline.js +329 -0
- package/dist/intelligence/agent-profiles.js +81 -0
- package/dist/intelligence/ai-commit.js +16 -0
- package/dist/intelligence/approval-analytics.js +42 -0
- package/dist/intelligence/approval-events.js +5 -0
- package/dist/intelligence/arch-boundaries.js +94 -0
- package/dist/intelligence/arch-graph.js +117 -0
- package/dist/intelligence/autonomy.js +397 -0
- package/dist/intelligence/azure-broker.js +57 -0
- package/dist/intelligence/azure-proxy-sessions.js +49 -0
- package/dist/intelligence/baseline.js +1 -0
- package/dist/intelligence/behavior-baseline.js +134 -0
- package/dist/intelligence/billing.js +1 -0
- package/dist/intelligence/blast-radius.js +224 -0
- package/dist/intelligence/bypass.js +157 -0
- package/dist/intelligence/calibration-classifier.js +77 -0
- package/dist/intelligence/calibration-git-sync.js +74 -0
- package/dist/intelligence/calibration.js +1 -0
- package/dist/intelligence/cascade-detector.js +58 -0
- package/dist/intelligence/case-study-generator.js +126 -0
- package/dist/intelligence/causal-graph.js +126 -0
- package/dist/intelligence/causal.js +1 -0
- package/dist/intelligence/change-risk.js +102 -0
- package/dist/intelligence/compliance-report.js +134 -0
- package/dist/intelligence/confidence-report.js +85 -0
- package/dist/intelligence/corpus-to-policy.js +140 -0
- package/dist/intelligence/credential-broker.js +112 -0
- package/dist/intelligence/credential-misuse.js +39 -0
- package/dist/intelligence/debug-sessions.js +108 -0
- package/dist/intelligence/decision-escalation.js +36 -0
- package/dist/intelligence/decision-memory.js +47 -0
- package/dist/intelligence/decision-similarity.js +85 -0
- package/dist/intelligence/default-safety-tests.js +53 -0
- package/dist/intelligence/degradation-watcher.js +108 -0
- package/dist/intelligence/detector-plugins.js +70 -0
- package/dist/intelligence/detectors.js +1 -0
- package/dist/intelligence/device-auth.js +138 -0
- package/dist/intelligence/diff-size.js +42 -0
- package/dist/intelligence/ebpf-verify.js +45 -0
- package/dist/intelligence/enterprise-policy-engine.js +1122 -0
- package/dist/intelligence/error-fingerprint.js +1 -0
- package/dist/intelligence/execution-gate.js +100 -0
- package/dist/intelligence/execution-mode.js +22 -0
- package/dist/intelligence/gate-analytics.js +256 -0
- package/dist/intelligence/gate-decision.js +64 -0
- package/dist/intelligence/gcp-broker.js +50 -0
- package/dist/intelligence/git-adr-sync.js +207 -0
- package/dist/intelligence/hitl-hold.js +460 -0
- package/dist/intelligence/hypothesis-history.js +93 -0
- package/dist/intelligence/idp-identity.js +83 -0
- package/dist/intelligence/impl-critic.js +216 -0
- package/dist/intelligence/incident-autopilot.js +622 -0
- package/dist/intelligence/incident-replay.js +162 -0
- package/dist/intelligence/incident-result-cache.js +33 -0
- package/dist/intelligence/incident-similarity.js +58 -0
- package/dist/intelligence/infra-detectors.js +312 -0
- package/dist/intelligence/license.js +185 -0
- package/dist/intelligence/llm-spokesperson.js +62 -0
- package/dist/intelligence/mcp-prompts.js +320 -0
- package/dist/intelligence/mcp-resources.js +171 -0
- package/dist/intelligence/normalize.js +108 -0
- package/dist/intelligence/notifications.js +83 -0
- package/dist/intelligence/oidc-issuer.js +87 -0
- package/dist/intelligence/override-corpus.js +597 -0
- package/dist/intelligence/planning-gate.js +84 -0
- package/dist/intelligence/plans.js +223 -0
- package/dist/intelligence/platt-scaling.js +164 -0
- package/dist/intelligence/policy-proposals.js +84 -0
- package/dist/intelligence/policy-suggester.js +161 -0
- package/dist/intelligence/policy-sync.js +103 -0
- package/dist/intelligence/policy-test-runner.js +35 -0
- package/dist/intelligence/postmortem-parser.js +181 -0
- package/dist/intelligence/postmortem-retrieval.js +224 -0
- package/dist/intelligence/postmortem-store.js +596 -0
- package/dist/intelligence/pr-commenter.js +81 -0
- package/dist/intelligence/pr-policy-gate.js +38 -0
- package/dist/intelligence/pr-shadow-analyzer.js +229 -0
- package/dist/intelligence/process-tree.js +108 -0
- package/dist/intelligence/prompts.js +1 -0
- package/dist/intelligence/replay.js +38 -0
- package/dist/intelligence/repro-steps.js +1 -0
- package/dist/intelligence/resource-extractors.js +0 -0
- package/dist/intelligence/resource-file-context.js +138 -0
- package/dist/intelligence/risk-score.js +22 -0
- package/dist/intelligence/rollback.js +137 -0
- package/dist/intelligence/runbook-updater.js +143 -0
- package/dist/intelligence/sandbox.js +194 -0
- package/dist/intelligence/script-trust.js +104 -0
- package/dist/intelligence/session-metrics.js +67 -0
- package/dist/intelligence/session-threat-tracker.js +231 -0
- package/dist/intelligence/shadow-digest-cron.js +81 -0
- package/dist/intelligence/shadow-log.js +284 -0
- package/dist/intelligence/shell-ast.js +145 -0
- package/dist/intelligence/siem-forward.js +104 -0
- package/dist/intelligence/slack-digest.js +151 -0
- package/dist/intelligence/slack-override-loop.js +217 -0
- package/dist/intelligence/slack-routing.js +67 -0
- package/dist/intelligence/slack.js +900 -0
- package/dist/intelligence/sql-ast.js +31 -0
- package/dist/intelligence/team.js +1 -0
- package/dist/intelligence/telemetry.js +76 -0
- package/dist/intelligence/threshold-optimizer.js +75 -0
- package/dist/intelligence/token-budget.js +66 -0
- package/dist/intelligence/tool-guard.js +708 -0
- package/dist/intelligence/tool-manifest 2.js +74 -0
- package/dist/intelligence/tool-manifest.js +125 -0
- package/dist/intelligence/tools-analysis.js +857 -0
- package/dist/intelligence/tools-arch.js +152 -0
- package/dist/intelligence/tools-autonomy.js +495 -0
- package/dist/intelligence/tools-blast-radius.js +140 -0
- package/dist/intelligence/tools-browser.js +431 -0
- package/dist/intelligence/tools-change-timeline.js +154 -0
- package/dist/intelligence/tools-credentials.js +117 -0
- package/dist/intelligence/tools-datadog.js +233 -0
- package/dist/intelligence/tools-debug-sessions.js +209 -0
- package/dist/intelligence/tools-discovery.js +284 -0
- package/dist/intelligence/tools-infra.js +612 -0
- package/dist/intelligence/tools-intent.js +128 -0
- package/dist/intelligence/tools-memory.js +514 -0
- package/dist/intelligence/tools-runbook.js +951 -0
- package/dist/intelligence/tools-sessions.js +107 -0
- package/dist/intelligence/tools-state.js +232 -0
- package/dist/intelligence/tools-utility.js +430 -0
- package/dist/intelligence/tools-validate.js +215 -0
- package/dist/intelligence/tools.js +1 -0
- package/dist/intelligence/trace-context.js +11 -0
- package/dist/intelligence/unclassified-clusters.js +87 -0
- package/dist/intelligence/usage.js +195 -0
- package/dist/routes/active-authors.js +63 -0
- package/dist/routes/activity-feed.js +21 -0
- package/dist/routes/adr.js +117 -0
- package/dist/routes/agent-activity.js +184 -0
- package/dist/routes/agent-blunders.js +163 -0
- package/dist/routes/agents.js +175 -0
- package/dist/routes/api-keys.js +65 -0
- package/dist/routes/arch.js +75 -0
- package/dist/routes/audit-export.js +93 -0
- package/dist/routes/billing-dashboard.js +158 -0
- package/dist/routes/billing-outcome.js +77 -0
- package/dist/routes/calibration.js +305 -0
- package/dist/routes/ci-gate-history.js +20 -0
- package/dist/routes/ci-gate.js +232 -0
- package/dist/routes/ci.js +293 -0
- package/dist/routes/compliance-report.js +24 -0
- package/dist/routes/confidence.js +30 -0
- package/dist/routes/credentials.js +119 -0
- package/dist/routes/dashboard.js +1407 -0
- package/dist/routes/decision-memory.js +42 -0
- package/dist/routes/demo.js +844 -0
- package/dist/routes/device-auth-stub.js +173 -0
- package/dist/routes/explain-why.js +39 -0
- package/dist/routes/gate-analytics.js +213 -0
- package/dist/routes/gate.js +28 -0
- package/dist/routes/github-webhook.js +364 -0
- package/dist/routes/habituation.js +30 -0
- package/dist/routes/health-integrations.js +243 -0
- package/dist/routes/heartbeats.js +45 -0
- package/dist/routes/hitl.js +364 -0
- package/dist/routes/impact-report.js +645 -0
- package/dist/routes/incident-webhook.js +63 -0
- package/dist/routes/incidents.js +366 -0
- package/dist/routes/layers.js +62 -0
- package/dist/routes/license.js +187 -0
- package/dist/routes/oidc-issuer.js +30 -0
- package/dist/routes/onboarding.js +170 -0
- package/dist/routes/otel.js +67 -0
- package/dist/routes/otlp-receiver.js +179 -0
- package/dist/routes/overrides.js +241 -0
- package/dist/routes/pagerduty.js +258 -0
- package/dist/routes/policies.js +809 -0
- package/dist/routes/policy-nl.js +169 -0
- package/dist/routes/postmortem.js +102 -0
- package/dist/routes/pr-shadow.js +47 -0
- package/dist/routes/rbac.js +61 -0
- package/dist/routes/replay.js +17 -0
- package/dist/routes/risk-report.js +164 -0
- package/dist/routes/runbooks.js +125 -0
- package/dist/routes/safety-test.js +174 -0
- package/dist/routes/sdk.js +222 -0
- package/dist/routes/sensor.js +819 -0
- package/dist/routes/sentry.js +140 -0
- package/dist/routes/sessions.js +43 -0
- package/dist/routes/setup-ui.js +594 -0
- package/dist/routes/shadow-report.js +107 -0
- package/dist/routes/slack-routing.js +171 -0
- package/dist/routes/team-usage.js +71 -0
- package/dist/routes/telemetry.js +28 -0
- package/dist/routes/tenants.js +199 -0
- package/dist/routes/tickets.js +167 -0
- package/dist/routes/validate.js +13 -0
- package/dist/routes/war-room.js +113 -0
- package/dist/scripts/check-arch.js +22 -0
- package/dist/seeds/community-corpus.js +176 -0
- package/dist/seeds/corpus.js +1224 -0
- package/dist/sensor/action-ledger.js +327 -0
- package/dist/sensor/adr-store.js +140 -0
- package/dist/sensor/agent-blunder-store.js +370 -0
- package/dist/sensor/agent-context-store.js +225 -0
- package/dist/sensor/agent-memory-store.js +231 -0
- package/dist/sensor/audit-fetch.js +23 -0
- package/dist/sensor/audit-log.js +273 -0
- package/dist/sensor/buffer-schemas.js +225 -0
- package/dist/sensor/buffer.js +599 -0
- package/dist/sensor/bypass-tracker.js +93 -0
- package/dist/sensor/ci-gate-history.js +70 -0
- package/dist/sensor/cloud-auth.js +183 -0
- package/dist/sensor/commit-context-store.js +235 -0
- package/dist/sensor/docker-log-stream.js +164 -0
- package/dist/sensor/docker-monitor.js +122 -0
- package/dist/sensor/extended-buffer.js +46 -0
- package/dist/sensor/feedback-token.js +41 -0
- package/dist/sensor/file-lock.js +6 -0
- package/dist/sensor/fs-watcher.js +106 -0
- package/dist/sensor/gate-heartbeat.js +122 -0
- package/dist/sensor/git-suspect.js +136 -0
- package/dist/sensor/habituation-store.js +77 -0
- package/dist/sensor/heartbeat-monitor.js +133 -0
- package/dist/sensor/incident-store.js +340 -0
- package/dist/sensor/infra-normalizer.js +513 -0
- package/dist/sensor/ingest.js +339 -0
- package/dist/sensor/jest-reporter.js +52 -0
- package/dist/sensor/k8s-events.js +132 -0
- package/dist/sensor/layer2-store.js +111 -0
- package/dist/sensor/layer3-store.js +230 -0
- package/dist/sensor/layer4-store.js +147 -0
- package/dist/sensor/logger.js +13 -0
- package/dist/sensor/otel-exporter.js +161 -0
- package/dist/sensor/paths.js +71 -0
- package/dist/sensor/policy-history.js +147 -0
- package/dist/sensor/pr-shadow-store.js +83 -0
- package/dist/sensor/process-watcher.js +162 -0
- package/dist/sensor/rbac.js +92 -0
- package/dist/sensor/redact.js +114 -0
- package/dist/sensor/redis-store.js +191 -0
- package/dist/sensor/route-reachability.js +64 -0
- package/dist/sensor/security-utils.js +18 -0
- package/dist/sensor/service-graph.js +170 -0
- package/dist/sensor/service-topology.js +227 -0
- package/dist/sensor/session-history.js +74 -0
- package/dist/sensor/session-persist.js +38 -0
- package/dist/sensor/shadow-promote.js +122 -0
- package/dist/sensor/sourcemap.js +281 -0
- package/dist/sensor/sqlite-store.js +205 -0
- package/dist/sensor/sso.js +164 -0
- package/dist/sensor/vitest-reporter.js +65 -0
- package/dist/sensor/watcher.js +48 -0
- package/dist/storage/interfaces.js +0 -0
- package/dist/storage/pg/pg-action-ledger.js +138 -0
- package/dist/storage/pg/pg-approval-store.js +107 -0
- package/dist/storage/pg/pg-blunder-store.js +193 -0
- package/dist/storage/pg/pg-ci-gate-history.js +83 -0
- package/dist/storage/pg/pg-client.js +24 -0
- package/dist/storage/pg/pg-event-store.js +63 -0
- package/dist/storage/pg/pg-incident-store.js +190 -0
- package/dist/storage/pg/pg-migrations.js +26 -0
- package/dist/storage/pg/pg-override-corpus.js +447 -0
- package/dist/storage/pg/pg-shadow-log.js +129 -0
- package/dist/storage/sqlite/sqlite-action-ledger.js +18 -0
- package/dist/storage/sqlite/sqlite-approval-store.js +37 -0
- package/dist/storage/sqlite/sqlite-blunder-store.js +27 -0
- package/dist/storage/sqlite/sqlite-ci-gate-history.js +19 -0
- package/dist/storage/sqlite/sqlite-event-store.js +29 -0
- package/dist/storage/sqlite/sqlite-incident-store.js +34 -0
- package/dist/storage/sqlite/sqlite-override-corpus.js +75 -0
- package/dist/storage/sqlite/sqlite-shadow-log.js +36 -0
- package/dist/storage/store-factory.js +55 -0
- package/dist/storage/store-registry.js +31 -0
- package/dist/update-checker.js +107 -0
- package/dist/workers/autopilot-worker.js +28 -0
- package/dist/workers/notification-worker.js +28 -0
- package/dist/workers/queues.js +58 -0
- package/dist/workers/validation-worker.js +24 -0
- package/dist/workers/worker-registry.js +18 -0
- package/package.json +123 -0
- package/sdk/mergen-inject.js +260 -0
- package/sdk/node.js +313 -0
- package/sdk/vite-plugin.ts +57 -0
- package/sdk/webpack-plugin.js +106 -0
|
@@ -0,0 +1,399 @@
|
|
|
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 "../sensor/paths.js";
|
|
7
|
+
import logger from "../sensor/logger.js";
|
|
8
|
+
const MEMORY_DB = path.join(DATA_DIR, "incident-memory.db");
|
|
9
|
+
const DEFAULT_CAUSALITY_WINDOW_MS = (() => {
|
|
10
|
+
const minutes = parseInt(process.env.MERGEN_CAUSALITY_WINDOW_MIN ?? "30", 10);
|
|
11
|
+
return (Number.isFinite(minutes) ? Math.min(Math.max(minutes, 5), 120) : 30) * 6e4;
|
|
12
|
+
})();
|
|
13
|
+
class IncidentMemoryStore {
|
|
14
|
+
db = null;
|
|
15
|
+
isWriting = false;
|
|
16
|
+
pendingWrite = false;
|
|
17
|
+
nextBuffer = null;
|
|
18
|
+
resolveWasmPath() {
|
|
19
|
+
if (process.env.MERGEN_WASM_PATH) return process.env.MERGEN_WASM_PATH;
|
|
20
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
const fromModule = path.resolve(moduleDir, "../../../node_modules/sql.js/dist/sql-wasm.wasm");
|
|
22
|
+
if (fs.existsSync(fromModule)) return fromModule;
|
|
23
|
+
try {
|
|
24
|
+
const req = createRequire(import.meta.url);
|
|
25
|
+
const resolved = path.join(path.dirname(req.resolve("sql.js")), "sql-wasm.wasm");
|
|
26
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return fromModule;
|
|
30
|
+
}
|
|
31
|
+
async init() {
|
|
32
|
+
try {
|
|
33
|
+
const wasmBinary = fs.readFileSync(this.resolveWasmPath());
|
|
34
|
+
const SQL = await initSqlJs({ wasmBinary });
|
|
35
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
36
|
+
let buf;
|
|
37
|
+
if (fs.existsSync(MEMORY_DB)) {
|
|
38
|
+
try {
|
|
39
|
+
buf = fs.readFileSync(MEMORY_DB);
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
this.db = buf ? new SQL.Database(buf) : new SQL.Database();
|
|
44
|
+
this.db.run(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS incident_memory (
|
|
46
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
+
fingerprint TEXT NOT NULL,
|
|
48
|
+
service TEXT NOT NULL,
|
|
49
|
+
endpoint TEXT NOT NULL,
|
|
50
|
+
error_type TEXT NOT NULL DEFAULT '',
|
|
51
|
+
error_message TEXT NOT NULL DEFAULT '',
|
|
52
|
+
implicated_file TEXT,
|
|
53
|
+
implicated_line INTEGER,
|
|
54
|
+
deployed_sha TEXT,
|
|
55
|
+
fired_at INTEGER NOT NULL,
|
|
56
|
+
resolved_at INTEGER,
|
|
57
|
+
mttr_ms INTEGER,
|
|
58
|
+
pd_incident_id TEXT,
|
|
59
|
+
pd_alert_title TEXT NOT NULL DEFAULT '',
|
|
60
|
+
pd_alert_url TEXT,
|
|
61
|
+
trace_id TEXT NOT NULL DEFAULT '',
|
|
62
|
+
fix_pr_url TEXT,
|
|
63
|
+
fix_pr_title TEXT,
|
|
64
|
+
fix_pr_sha TEXT,
|
|
65
|
+
fix_summary TEXT,
|
|
66
|
+
resolution_type TEXT NOT NULL DEFAULT 'unknown',
|
|
67
|
+
raw_fact TEXT
|
|
68
|
+
);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_mem_fingerprint ON incident_memory(fingerprint);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_mem_fired_at ON incident_memory(fired_at);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_mem_service ON incident_memory(service);
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS incident_benchmarks (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
75
|
+
fingerprint TEXT NOT NULL,
|
|
76
|
+
mttr_ms INTEGER,
|
|
77
|
+
resolution_type TEXT NOT NULL DEFAULT 'unknown',
|
|
78
|
+
recorded_at INTEGER NOT NULL
|
|
79
|
+
);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_bench_fingerprint ON incident_benchmarks(fingerprint);
|
|
81
|
+
`);
|
|
82
|
+
for (const col of [
|
|
83
|
+
"ALTER TABLE incident_memory ADD COLUMN attribution_confidence REAL",
|
|
84
|
+
"ALTER TABLE incident_memory ADD COLUMN attribution_sha TEXT",
|
|
85
|
+
"ALTER TABLE incident_memory ADD COLUMN attribution_validated INTEGER"
|
|
86
|
+
]) {
|
|
87
|
+
try {
|
|
88
|
+
this.db.run(col);
|
|
89
|
+
} catch {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
this._flush();
|
|
93
|
+
logger.info({ path: MEMORY_DB }, "incident memory store initialised");
|
|
94
|
+
} catch (err) {
|
|
95
|
+
logger.warn({ err }, "incident memory store failed to init");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ── Write ──────────────────────────────────────────────────────────────────
|
|
99
|
+
openIncident(fields) {
|
|
100
|
+
if (!this.db) return -1;
|
|
101
|
+
this.db.run(
|
|
102
|
+
`INSERT INTO incident_memory
|
|
103
|
+
(fingerprint, service, endpoint, error_type, error_message,
|
|
104
|
+
implicated_file, implicated_line, deployed_sha,
|
|
105
|
+
fired_at, pd_incident_id, pd_alert_title, pd_alert_url,
|
|
106
|
+
trace_id, raw_fact, attribution_confidence, attribution_sha)
|
|
107
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
108
|
+
[
|
|
109
|
+
fields.fingerprint,
|
|
110
|
+
fields.service,
|
|
111
|
+
fields.endpoint,
|
|
112
|
+
fields.errorType,
|
|
113
|
+
fields.errorMessage,
|
|
114
|
+
fields.implicatedFile ?? null,
|
|
115
|
+
fields.implicatedLine ?? null,
|
|
116
|
+
fields.deployedSha ?? null,
|
|
117
|
+
fields.firedAt ?? Date.now(),
|
|
118
|
+
fields.pdIncidentId ?? null,
|
|
119
|
+
fields.pdAlertTitle,
|
|
120
|
+
fields.pdAlertUrl ?? null,
|
|
121
|
+
fields.traceId,
|
|
122
|
+
fields.rawFact ?? null,
|
|
123
|
+
fields.attributionConfidence ?? null,
|
|
124
|
+
fields.attributionSha ?? null
|
|
125
|
+
]
|
|
126
|
+
);
|
|
127
|
+
const idRes = this.db.exec("SELECT last_insert_rowid()");
|
|
128
|
+
const id = idRes[0]?.values[0]?.[0] ?? -1;
|
|
129
|
+
this._flush();
|
|
130
|
+
return id;
|
|
131
|
+
}
|
|
132
|
+
closeIncident(opts) {
|
|
133
|
+
if (!this.db) return;
|
|
134
|
+
const resolvedAt = opts.resolvedAt ?? Date.now();
|
|
135
|
+
const resolutionType = opts.resolutionType ?? "unknown";
|
|
136
|
+
const openRec = opts.id ? this.db.exec(
|
|
137
|
+
"SELECT id, fired_at, fingerprint FROM incident_memory WHERE id = ? AND resolved_at IS NULL LIMIT 1",
|
|
138
|
+
[opts.id]
|
|
139
|
+
) : this.db.exec(
|
|
140
|
+
"SELECT id, fired_at, fingerprint FROM incident_memory WHERE pd_incident_id = ? AND resolved_at IS NULL LIMIT 1",
|
|
141
|
+
[opts.pdIncidentId ?? null]
|
|
142
|
+
);
|
|
143
|
+
if (!openRec[0]?.values?.length) return;
|
|
144
|
+
const [recId, firedAt, fingerprint] = openRec[0].values[0];
|
|
145
|
+
const mttrMs = resolvedAt - firedAt;
|
|
146
|
+
let attributionValidated = null;
|
|
147
|
+
if (opts.fixPrSha) {
|
|
148
|
+
const attrRes = this.db.exec(
|
|
149
|
+
"SELECT attribution_sha FROM incident_memory WHERE id = ?",
|
|
150
|
+
[recId]
|
|
151
|
+
);
|
|
152
|
+
const attrSha = attrRes[0]?.values[0]?.[0];
|
|
153
|
+
if (attrSha && opts.fixPrSha) {
|
|
154
|
+
attributionValidated = opts.fixPrSha.startsWith(attrSha) || attrSha.startsWith(opts.fixPrSha) ? 1 : 0;
|
|
155
|
+
logger.info({ recId, attributionValidated }, "attribution accuracy recorded");
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
this.db.run(
|
|
159
|
+
`UPDATE incident_memory
|
|
160
|
+
SET resolved_at=?, mttr_ms=?, fix_pr_url=?, fix_pr_title=?, fix_pr_sha=?,
|
|
161
|
+
fix_summary=?, resolution_type=?, attribution_validated=?
|
|
162
|
+
WHERE id=?`,
|
|
163
|
+
[
|
|
164
|
+
resolvedAt,
|
|
165
|
+
mttrMs,
|
|
166
|
+
opts.fixPrUrl ?? null,
|
|
167
|
+
opts.fixPrTitle ?? null,
|
|
168
|
+
opts.fixPrSha ?? null,
|
|
169
|
+
opts.fixSummary ?? null,
|
|
170
|
+
resolutionType,
|
|
171
|
+
attributionValidated,
|
|
172
|
+
recId
|
|
173
|
+
]
|
|
174
|
+
);
|
|
175
|
+
this.db.run(
|
|
176
|
+
"INSERT INTO incident_benchmarks (fingerprint, mttr_ms, resolution_type, recorded_at) VALUES (?,?,?,?)",
|
|
177
|
+
[fingerprint, mttrMs, resolutionType, resolvedAt]
|
|
178
|
+
);
|
|
179
|
+
this._flush();
|
|
180
|
+
}
|
|
181
|
+
// Called by GitHub webhook: find the most recent open incident and correlate
|
|
182
|
+
correlateGitHubPR(opts) {
|
|
183
|
+
if (!this.db) return;
|
|
184
|
+
const windowStart = opts.mergedAt - DEFAULT_CAUSALITY_WINDOW_MS;
|
|
185
|
+
const res = this.db.exec(
|
|
186
|
+
`SELECT id FROM incident_memory
|
|
187
|
+
WHERE resolved_at IS NULL AND fired_at >= ?
|
|
188
|
+
ORDER BY fired_at DESC LIMIT 1`,
|
|
189
|
+
[windowStart]
|
|
190
|
+
);
|
|
191
|
+
if (!res[0]?.values?.length) return;
|
|
192
|
+
const [id] = res[0].values[0];
|
|
193
|
+
const type = inferResolutionType(opts.prTitle);
|
|
194
|
+
this.db.run(
|
|
195
|
+
`UPDATE incident_memory
|
|
196
|
+
SET fix_pr_url=?, fix_pr_title=?, fix_pr_sha=?, resolution_type=?
|
|
197
|
+
WHERE id=?`,
|
|
198
|
+
[opts.prUrl, opts.prTitle, opts.prSha, type, id]
|
|
199
|
+
);
|
|
200
|
+
this._flush();
|
|
201
|
+
logger.info({ id, prUrl: opts.prUrl, type }, "github PR correlated to open incident");
|
|
202
|
+
}
|
|
203
|
+
// ── Read ───────────────────────────────────────────────────────────────────
|
|
204
|
+
findSimilar(fingerprint, limit = 5) {
|
|
205
|
+
if (!this.db) return [];
|
|
206
|
+
const res = this.db.exec(
|
|
207
|
+
`SELECT * FROM incident_memory WHERE fingerprint=? ORDER BY fired_at DESC LIMIT ?`,
|
|
208
|
+
[fingerprint, limit]
|
|
209
|
+
);
|
|
210
|
+
return this._rows(res);
|
|
211
|
+
}
|
|
212
|
+
benchmarkStats(fingerprint) {
|
|
213
|
+
if (!this.db) return null;
|
|
214
|
+
const res = this.db.exec(
|
|
215
|
+
`SELECT mttr_ms, resolution_type FROM incident_benchmarks WHERE fingerprint=? ORDER BY recorded_at DESC LIMIT 100`,
|
|
216
|
+
[fingerprint]
|
|
217
|
+
);
|
|
218
|
+
if (!res[0]?.values?.length) return null;
|
|
219
|
+
const rows = res[0].values;
|
|
220
|
+
const mttrValues = rows.map((r) => r[0]).filter((v) => v !== null).sort((a, b) => a - b);
|
|
221
|
+
const typeCounts = {};
|
|
222
|
+
for (const [, t] of rows) typeCounts[t] = (typeCounts[t] ?? 0) + 1;
|
|
223
|
+
const topEntry = Object.entries(typeCounts).sort((a, b) => b[1] - a[1])[0];
|
|
224
|
+
const lastRes = this.db.exec(
|
|
225
|
+
`SELECT MAX(recorded_at) FROM incident_benchmarks WHERE fingerprint=?`,
|
|
226
|
+
[fingerprint]
|
|
227
|
+
);
|
|
228
|
+
const lastSeenAt = lastRes[0]?.values[0]?.[0] ?? null;
|
|
229
|
+
return {
|
|
230
|
+
fingerprint,
|
|
231
|
+
occurrences: rows.length,
|
|
232
|
+
p50MttrMs: mttrValues.length ? mttrValues[Math.floor(mttrValues.length * 0.5)] : null,
|
|
233
|
+
p90MttrMs: mttrValues.length ? mttrValues[Math.floor(mttrValues.length * 0.9)] : null,
|
|
234
|
+
topResolutionType: topEntry?.[0] ?? "unknown",
|
|
235
|
+
topResolutionCount: topEntry?.[1] ?? 0,
|
|
236
|
+
lastSeenAt
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
listOpen() {
|
|
240
|
+
if (!this.db) return [];
|
|
241
|
+
const res = this.db.exec(
|
|
242
|
+
"SELECT * FROM incident_memory WHERE resolved_at IS NULL ORDER BY fired_at DESC LIMIT 20"
|
|
243
|
+
);
|
|
244
|
+
return this._rows(res);
|
|
245
|
+
}
|
|
246
|
+
listAll(limit = 100) {
|
|
247
|
+
if (!this.db) return [];
|
|
248
|
+
const res = this.db.exec(
|
|
249
|
+
"SELECT * FROM incident_memory ORDER BY fired_at DESC LIMIT ?",
|
|
250
|
+
[limit]
|
|
251
|
+
);
|
|
252
|
+
return this._rows(res);
|
|
253
|
+
}
|
|
254
|
+
/** Store explicit attribution feedback from `mergen-server resolved` prompt. */
|
|
255
|
+
recordAttributionFeedback(id, validated) {
|
|
256
|
+
if (!this.db) return;
|
|
257
|
+
this.db.run(
|
|
258
|
+
"UPDATE incident_memory SET attribution_validated = ? WHERE id = ?",
|
|
259
|
+
[validated, id]
|
|
260
|
+
);
|
|
261
|
+
this._flush();
|
|
262
|
+
logger.info({ id, validated }, "attribution feedback stored");
|
|
263
|
+
}
|
|
264
|
+
/** Find recent incidents (open or resolved) that implicate a specific file path. */
|
|
265
|
+
findByFile(filePath, limit = 10) {
|
|
266
|
+
if (!this.db) return [];
|
|
267
|
+
const basename = filePath.split("/").pop() ?? filePath;
|
|
268
|
+
const res = this.db.exec(
|
|
269
|
+
`SELECT * FROM incident_memory
|
|
270
|
+
WHERE implicated_file LIKE ? OR implicated_file LIKE ?
|
|
271
|
+
ORDER BY fired_at DESC LIMIT ?`,
|
|
272
|
+
[`%${basename}`, `%${filePath}%`, limit]
|
|
273
|
+
);
|
|
274
|
+
return this._rows(res);
|
|
275
|
+
}
|
|
276
|
+
/** Returns per-service stats: incident count, avg MTTR, most recent. */
|
|
277
|
+
serviceStats(service) {
|
|
278
|
+
if (!this.db) return { count: 0, avgMttrMs: null, lastFiredAt: null };
|
|
279
|
+
const res = this.db.exec(
|
|
280
|
+
`SELECT COUNT(*) as cnt, AVG(mttr_ms) as avg_mttr, MAX(fired_at) as last
|
|
281
|
+
FROM incident_memory WHERE service = ?`,
|
|
282
|
+
[service]
|
|
283
|
+
);
|
|
284
|
+
const row = res[0]?.values[0];
|
|
285
|
+
if (!row) return { count: 0, avgMttrMs: null, lastFiredAt: null };
|
|
286
|
+
return {
|
|
287
|
+
count: Number(row[0] ?? 0),
|
|
288
|
+
avgMttrMs: row[1] ? Number(row[1]) : null,
|
|
289
|
+
lastFiredAt: row[2] ? Number(row[2]) : null
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
getById(id) {
|
|
293
|
+
if (!this.db) return null;
|
|
294
|
+
const res = this.db.exec("SELECT * FROM incident_memory WHERE id=?", [id]);
|
|
295
|
+
const rows = this._rows(res);
|
|
296
|
+
return rows[0] ?? null;
|
|
297
|
+
}
|
|
298
|
+
// ── Internal ───────────────────────────────────────────────────────────────
|
|
299
|
+
_rows(res) {
|
|
300
|
+
if (!res[0]?.values) return [];
|
|
301
|
+
const cols = res[0].columns;
|
|
302
|
+
return res[0].values.map((vals) => {
|
|
303
|
+
const r = {};
|
|
304
|
+
cols.forEach((c, i) => {
|
|
305
|
+
r[c] = vals[i];
|
|
306
|
+
});
|
|
307
|
+
return {
|
|
308
|
+
id: r.id,
|
|
309
|
+
fingerprint: String(r.fingerprint ?? ""),
|
|
310
|
+
service: String(r.service ?? ""),
|
|
311
|
+
endpoint: String(r.endpoint ?? ""),
|
|
312
|
+
errorType: String(r.error_type ?? ""),
|
|
313
|
+
errorMessage: String(r.error_message ?? ""),
|
|
314
|
+
implicatedFile: r.implicated_file ? String(r.implicated_file) : null,
|
|
315
|
+
implicatedLine: r.implicated_line ? Number(r.implicated_line) : null,
|
|
316
|
+
deployedSha: r.deployed_sha ? String(r.deployed_sha) : null,
|
|
317
|
+
firedAt: Number(r.fired_at ?? 0),
|
|
318
|
+
resolvedAt: r.resolved_at ? Number(r.resolved_at) : null,
|
|
319
|
+
mttrMs: r.mttr_ms ? Number(r.mttr_ms) : null,
|
|
320
|
+
pdIncidentId: r.pd_incident_id ? String(r.pd_incident_id) : null,
|
|
321
|
+
pdAlertTitle: String(r.pd_alert_title ?? ""),
|
|
322
|
+
pdAlertUrl: r.pd_alert_url ? String(r.pd_alert_url) : null,
|
|
323
|
+
traceId: String(r.trace_id ?? ""),
|
|
324
|
+
fixPrUrl: r.fix_pr_url ? String(r.fix_pr_url) : null,
|
|
325
|
+
fixPrTitle: r.fix_pr_title ? String(r.fix_pr_title) : null,
|
|
326
|
+
fixPrSha: r.fix_pr_sha ? String(r.fix_pr_sha) : null,
|
|
327
|
+
fixSummary: r.fix_summary ? String(r.fix_summary) : null,
|
|
328
|
+
resolutionType: r.resolution_type ?? "unknown",
|
|
329
|
+
rawFact: r.raw_fact ? String(r.raw_fact) : null,
|
|
330
|
+
attributionConfidence: r.attribution_confidence ? Number(r.attribution_confidence) : null,
|
|
331
|
+
attributionSha: r.attribution_sha ? String(r.attribution_sha) : null,
|
|
332
|
+
attributionValidated: r.attribution_validated != null ? Number(r.attribution_validated) : null
|
|
333
|
+
};
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
/** Returns true when the SQLite database initialised successfully. */
|
|
337
|
+
isHealthy() {
|
|
338
|
+
return this.db !== null;
|
|
339
|
+
}
|
|
340
|
+
_flush() {
|
|
341
|
+
if (!this.db) return;
|
|
342
|
+
try {
|
|
343
|
+
const data = this.db.export();
|
|
344
|
+
this._writeBufferAsync(Buffer.from(data.buffer, data.byteOffset, data.byteLength));
|
|
345
|
+
} catch (err) {
|
|
346
|
+
logger.warn({ err }, "memory store flush failed");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
_writeBufferAsync(buf) {
|
|
350
|
+
if (this.isWriting) {
|
|
351
|
+
this.pendingWrite = true;
|
|
352
|
+
this.nextBuffer = buf;
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
this.isWriting = true;
|
|
356
|
+
const tmp = `${MEMORY_DB}.tmp.${process.pid}`;
|
|
357
|
+
fs.writeFile(tmp, buf, (err) => {
|
|
358
|
+
if (err) {
|
|
359
|
+
logger.warn({ err }, "memory store async write failed");
|
|
360
|
+
this.isWriting = false;
|
|
361
|
+
this._processPendingWrite();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
fs.rename(tmp, MEMORY_DB, (renameErr) => {
|
|
365
|
+
if (renameErr) logger.warn({ err: renameErr }, "memory store async rename failed");
|
|
366
|
+
this.isWriting = false;
|
|
367
|
+
this._processPendingWrite();
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
_processPendingWrite() {
|
|
372
|
+
if (this.pendingWrite && this.nextBuffer) {
|
|
373
|
+
this.pendingWrite = false;
|
|
374
|
+
const buf = this.nextBuffer;
|
|
375
|
+
this.nextBuffer = null;
|
|
376
|
+
this._writeBufferAsync(buf);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function inferResolutionType(prTitle) {
|
|
381
|
+
const t = prTitle.toLowerCase();
|
|
382
|
+
if (/revert|rollback|undo/.test(t)) return "rollback_deploy";
|
|
383
|
+
if (/flag|feature.flag|toggle|launch.darkly|launchdarkly/.test(t)) return "flag_rollback";
|
|
384
|
+
if (/config|env|setting|secret|credential/.test(t)) return "config_change";
|
|
385
|
+
if (/fix|patch|hotfix|bug|crash|error|incident/.test(t)) return "hotfix_deploy";
|
|
386
|
+
return "unknown";
|
|
387
|
+
}
|
|
388
|
+
function formatMttr(ms) {
|
|
389
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
390
|
+
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
391
|
+
return `${(ms / 36e5).toFixed(1)}h`;
|
|
392
|
+
}
|
|
393
|
+
const memoryStore = new IncidentMemoryStore();
|
|
394
|
+
export {
|
|
395
|
+
DEFAULT_CAUSALITY_WINDOW_MS,
|
|
396
|
+
formatMttr,
|
|
397
|
+
inferResolutionType,
|
|
398
|
+
memoryStore
|
|
399
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
const SESSION_TRACE_ID = crypto.randomBytes(16).toString("hex");
|
|
3
|
+
let _traceId = SESSION_TRACE_ID;
|
|
4
|
+
let _spanId = crypto.randomBytes(8).toString("hex");
|
|
5
|
+
function startServerSpan(incomingTraceparent) {
|
|
6
|
+
if (incomingTraceparent) {
|
|
7
|
+
const parsed = parseTraceparent(incomingTraceparent);
|
|
8
|
+
if (parsed) {
|
|
9
|
+
_traceId = parsed.traceId;
|
|
10
|
+
_spanId = crypto.randomBytes(8).toString("hex");
|
|
11
|
+
return build();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
_spanId = crypto.randomBytes(8).toString("hex");
|
|
15
|
+
return build();
|
|
16
|
+
}
|
|
17
|
+
function startClientSpan() {
|
|
18
|
+
const childSpan = crypto.randomBytes(8).toString("hex");
|
|
19
|
+
return {
|
|
20
|
+
traceId: _traceId,
|
|
21
|
+
spanId: childSpan,
|
|
22
|
+
traceparent: `00-${_traceId}-${childSpan}-01`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function getCurrentTraceContext() {
|
|
26
|
+
return build();
|
|
27
|
+
}
|
|
28
|
+
function extractTraceparentFromMeta(meta) {
|
|
29
|
+
if (!meta) return null;
|
|
30
|
+
if (typeof meta["traceparent"] === "string") return meta["traceparent"];
|
|
31
|
+
const tc = meta["traceContext"];
|
|
32
|
+
if (typeof tc?.["traceparent"] === "string") return tc["traceparent"];
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function parseTraceparent(tp) {
|
|
36
|
+
const parts = tp.split("-");
|
|
37
|
+
if (parts.length < 4) return null;
|
|
38
|
+
const [version, traceId, spanId, flags] = parts;
|
|
39
|
+
if (version !== "00") return null;
|
|
40
|
+
if (!/^[0-9a-f]{32}$/.test(traceId)) return null;
|
|
41
|
+
if (!/^[0-9a-f]{16}$/.test(spanId)) return null;
|
|
42
|
+
return { traceId, spanId, flags: flags ?? "01" };
|
|
43
|
+
}
|
|
44
|
+
function buildMetaTraceContext() {
|
|
45
|
+
const ctx = build();
|
|
46
|
+
return {
|
|
47
|
+
traceContext: {
|
|
48
|
+
traceparent: ctx.traceparent
|
|
49
|
+
// tracestate intentionally omitted unless we have vendor-specific state
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function build() {
|
|
54
|
+
return {
|
|
55
|
+
traceId: _traceId,
|
|
56
|
+
spanId: _spanId,
|
|
57
|
+
traceparent: `00-${_traceId}-${_spanId}-01`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
buildMetaTraceContext,
|
|
62
|
+
extractTraceparentFromMeta,
|
|
63
|
+
getCurrentTraceContext,
|
|
64
|
+
parseTraceparent,
|
|
65
|
+
startClientSpan,
|
|
66
|
+
startServerSpan
|
|
67
|
+
};
|