prismalens 0.0.2 → 0.1.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/README.md +12 -2
- package/dist/bin/prismalens.js +13 -0
- package/dist/bin/prismalens.js.map +1 -1
- package/dist/src/cli/doctor.d.ts +15 -1
- package/dist/src/cli/doctor.d.ts.map +1 -1
- package/dist/src/cli/doctor.js +107 -56
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/flags.d.ts +2 -0
- package/dist/src/cli/flags.d.ts.map +1 -0
- package/dist/src/cli/flags.js +25 -0
- package/dist/src/cli/flags.js.map +1 -0
- package/dist/src/cli/grouping.d.ts +18 -0
- package/dist/src/cli/grouping.d.ts.map +1 -0
- package/dist/src/cli/grouping.js +169 -0
- package/dist/src/cli/grouping.js.map +1 -0
- package/dist/src/cli/init.d.ts.map +1 -1
- package/dist/src/cli/init.js +26 -11
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/investigate.d.ts +12 -0
- package/dist/src/cli/investigate.d.ts.map +1 -1
- package/dist/src/cli/investigate.js +142 -140
- package/dist/src/cli/investigate.js.map +1 -1
- package/dist/src/cli/listen.d.ts +50 -0
- package/dist/src/cli/listen.d.ts.map +1 -0
- package/dist/src/cli/listen.js +249 -0
- package/dist/src/cli/listen.js.map +1 -0
- package/dist/src/cli/report.d.ts +19 -0
- package/dist/src/cli/report.d.ts.map +1 -0
- package/dist/src/cli/report.js +60 -0
- package/dist/src/cli/report.js.map +1 -0
- package/dist/src/cli/serve.d.ts.map +1 -1
- package/dist/src/cli/serve.js +11 -2
- package/dist/src/cli/serve.js.map +1 -1
- package/dist/src/cli/status.d.ts +14 -0
- package/dist/src/cli/status.d.ts.map +1 -0
- package/dist/src/cli/status.js +83 -0
- package/dist/src/cli/status.js.map +1 -0
- package/dist/src/config/loader.d.ts.map +1 -1
- package/dist/src/config/loader.js +28 -2
- package/dist/src/config/loader.js.map +1 -1
- package/dist/src/config/schema.d.ts +121 -0
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +47 -1
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/core/caps.d.ts +36 -0
- package/dist/src/core/caps.d.ts.map +1 -0
- package/dist/src/core/caps.js +36 -0
- package/dist/src/core/caps.js.map +1 -0
- package/dist/src/core/db.d.ts +3 -0
- package/dist/src/core/db.d.ts.map +1 -0
- package/dist/src/core/db.js +71 -0
- package/dist/src/core/db.js.map +1 -0
- package/dist/src/core/file-session-store.js +2 -2
- package/dist/src/core/file-session-store.js.map +1 -1
- package/dist/src/core/run-investigation.d.ts +10 -0
- package/dist/src/core/run-investigation.d.ts.map +1 -1
- package/dist/src/core/run-investigation.js +85 -30
- package/dist/src/core/run-investigation.js.map +1 -1
- package/dist/src/core/session.d.ts +22 -1
- package/dist/src/core/session.d.ts.map +1 -1
- package/dist/src/core/session.js +6 -129
- package/dist/src/core/session.js.map +1 -1
- package/dist/src/core/slack-notify.d.ts +9 -0
- package/dist/src/core/slack-notify.d.ts.map +1 -0
- package/dist/src/core/slack-notify.js +57 -0
- package/dist/src/core/slack-notify.js.map +1 -0
- package/dist/src/core/sqlite-session-store.d.ts +39 -0
- package/dist/src/core/sqlite-session-store.d.ts.map +1 -0
- package/dist/src/core/sqlite-session-store.js +239 -0
- package/dist/src/core/sqlite-session-store.js.map +1 -0
- package/dist/src/http/server.d.ts +26 -0
- package/dist/src/http/server.d.ts.map +1 -0
- package/dist/src/http/server.js +151 -0
- package/dist/src/http/server.js.map +1 -0
- package/package.json +6 -5
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Sumit Patel
|
|
3
|
+
function tx(db, fn) {
|
|
4
|
+
db.exec("BEGIN");
|
|
5
|
+
try {
|
|
6
|
+
const r = fn();
|
|
7
|
+
db.exec("COMMIT");
|
|
8
|
+
return r;
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
db.exec("ROLLBACK");
|
|
12
|
+
throw e;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function mapRun(row) {
|
|
16
|
+
const record = {
|
|
17
|
+
runId: row.run_id,
|
|
18
|
+
status: row.status,
|
|
19
|
+
workspacePath: row.workspace_path,
|
|
20
|
+
createdAt: row.created_at,
|
|
21
|
+
updatedAt: row.updated_at,
|
|
22
|
+
};
|
|
23
|
+
if (row.alertname)
|
|
24
|
+
record.alertname = row.alertname;
|
|
25
|
+
if (row.agent)
|
|
26
|
+
record.agent = row.agent;
|
|
27
|
+
if (row.repo)
|
|
28
|
+
record.repo = row.repo;
|
|
29
|
+
if (row.error)
|
|
30
|
+
record.error = row.error;
|
|
31
|
+
if (row.suppression_reason)
|
|
32
|
+
record.suppressionReason = row.suppression_reason;
|
|
33
|
+
if (row.completed_at)
|
|
34
|
+
record.completedAt = row.completed_at;
|
|
35
|
+
return record;
|
|
36
|
+
}
|
|
37
|
+
export class SqliteSessionManager {
|
|
38
|
+
baseDir;
|
|
39
|
+
db;
|
|
40
|
+
workspaceDir;
|
|
41
|
+
assertRunId;
|
|
42
|
+
constructor(baseDir, db, workspaceDir, assertRunId) {
|
|
43
|
+
this.baseDir = baseDir;
|
|
44
|
+
this.db = db;
|
|
45
|
+
this.workspaceDir = workspaceDir;
|
|
46
|
+
this.assertRunId = assertRunId;
|
|
47
|
+
}
|
|
48
|
+
async create(input) {
|
|
49
|
+
this.assertRunId(input.runId);
|
|
50
|
+
const dir = this.workspaceDir(input.runId);
|
|
51
|
+
const { mkdirSync } = await import("node:fs");
|
|
52
|
+
mkdirSync(dir, { recursive: true });
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
return tx(this.db, () => {
|
|
55
|
+
const insertGroup = this.db.prepare(`
|
|
56
|
+
INSERT INTO groups (id, formed_by, created_at)
|
|
57
|
+
VALUES (?, 'window', ?)
|
|
58
|
+
ON CONFLICT DO NOTHING
|
|
59
|
+
`);
|
|
60
|
+
insertGroup.run(input.runId, now);
|
|
61
|
+
const insertRun = this.db.prepare(`
|
|
62
|
+
INSERT INTO runs (
|
|
63
|
+
run_id, group_id, status, alertname, agent, repo,
|
|
64
|
+
workspace_path, created_at, updated_at
|
|
65
|
+
) VALUES (?, ?, 'running', ?, ?, ?, ?, ?, ?)
|
|
66
|
+
`);
|
|
67
|
+
insertRun.run(input.runId, input.runId, input.alertname ?? null, input.agent ?? null, input.repo ?? null, dir, now, now);
|
|
68
|
+
const getRun = this.db.prepare("SELECT * FROM runs WHERE run_id = ?");
|
|
69
|
+
const row = getRun.get(input.runId);
|
|
70
|
+
if (!row)
|
|
71
|
+
throw new Error("Failed to read back created session");
|
|
72
|
+
return mapRun(row);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async get(runId) {
|
|
76
|
+
this.assertRunId(runId);
|
|
77
|
+
const getRun = this.db.prepare("SELECT * FROM runs WHERE run_id = ?");
|
|
78
|
+
const row = getRun.get(runId);
|
|
79
|
+
return row ? mapRun(row) : null;
|
|
80
|
+
}
|
|
81
|
+
async update(runId, updates) {
|
|
82
|
+
this.assertRunId(runId);
|
|
83
|
+
const now = new Date().toISOString();
|
|
84
|
+
return tx(this.db, () => {
|
|
85
|
+
const existingStmt = this.db.prepare("SELECT * FROM runs WHERE run_id = ?");
|
|
86
|
+
const existing = existingStmt.get(runId);
|
|
87
|
+
if (!existing)
|
|
88
|
+
throw new Error(`Session "${runId}" not found`);
|
|
89
|
+
const status = updates.status ?? existing.status;
|
|
90
|
+
const alertname = updates.alertname !== undefined
|
|
91
|
+
? updates.alertname
|
|
92
|
+
: existing.alertname;
|
|
93
|
+
const agent = updates.agent !== undefined ? updates.agent : existing.agent;
|
|
94
|
+
const repo = updates.repo !== undefined ? updates.repo : existing.repo;
|
|
95
|
+
const error = updates.error !== undefined ? updates.error : existing.error;
|
|
96
|
+
const completedAt = updates.completedAt !== undefined
|
|
97
|
+
? updates.completedAt
|
|
98
|
+
: existing.completed_at;
|
|
99
|
+
const updateStmt = this.db.prepare(`
|
|
100
|
+
UPDATE runs SET
|
|
101
|
+
status = ?,
|
|
102
|
+
alertname = ?,
|
|
103
|
+
agent = ?,
|
|
104
|
+
repo = ?,
|
|
105
|
+
error = ?,
|
|
106
|
+
completed_at = ?,
|
|
107
|
+
updated_at = ?
|
|
108
|
+
WHERE run_id = ?
|
|
109
|
+
`);
|
|
110
|
+
const res = updateStmt.run(status, alertname ?? null, agent ?? null, repo ?? null, error ?? null, completedAt ?? null, now, runId);
|
|
111
|
+
if (res.changes === 0) {
|
|
112
|
+
throw new Error(`Session "${runId}" not found`);
|
|
113
|
+
}
|
|
114
|
+
const row = existingStmt.get(runId);
|
|
115
|
+
if (!row)
|
|
116
|
+
throw new Error("Failed to read back updated session");
|
|
117
|
+
return mapRun(row);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async list(filter) {
|
|
121
|
+
let query = "SELECT * FROM runs";
|
|
122
|
+
let params = [];
|
|
123
|
+
if (filter?.status && filter.status.length > 0) {
|
|
124
|
+
const placeholders = filter.status.map(() => "?").join(",");
|
|
125
|
+
query += ` WHERE status IN (${placeholders})`;
|
|
126
|
+
params = [...filter.status];
|
|
127
|
+
}
|
|
128
|
+
// rowid DESC is the deterministic tiebreaker: same-millisecond created_at
|
|
129
|
+
// rows would otherwise sort arbitrarily (CI flake). Newest-insert-first.
|
|
130
|
+
query += " ORDER BY created_at DESC, rowid DESC";
|
|
131
|
+
const stmt = this.db.prepare(query);
|
|
132
|
+
const rows = stmt.all(...params);
|
|
133
|
+
return rows.map(mapRun);
|
|
134
|
+
}
|
|
135
|
+
async appendEvent(runId, event) {
|
|
136
|
+
const stmt = this.db.prepare("INSERT INTO events(run_id, payload) VALUES(?, ?)");
|
|
137
|
+
stmt.run(runId, JSON.stringify(event));
|
|
138
|
+
}
|
|
139
|
+
async readEvents(runId) {
|
|
140
|
+
const stmt = this.db.prepare("SELECT payload FROM events WHERE run_id = ? ORDER BY id");
|
|
141
|
+
const rows = stmt.all(runId);
|
|
142
|
+
return rows.map((r) => JSON.parse(r.payload));
|
|
143
|
+
}
|
|
144
|
+
async writeReport(runId, report) {
|
|
145
|
+
const stmt = this.db.prepare(`
|
|
146
|
+
INSERT INTO reports (run_id, payload)
|
|
147
|
+
VALUES (?, ?)
|
|
148
|
+
ON CONFLICT(run_id) DO UPDATE SET payload=excluded.payload
|
|
149
|
+
`);
|
|
150
|
+
stmt.run(runId, JSON.stringify(report));
|
|
151
|
+
}
|
|
152
|
+
async readReport(runId) {
|
|
153
|
+
try {
|
|
154
|
+
const stmt = this.db.prepare("SELECT payload FROM reports WHERE run_id = ?");
|
|
155
|
+
const row = stmt.get(runId);
|
|
156
|
+
if (!row)
|
|
157
|
+
return null;
|
|
158
|
+
return JSON.parse(row.payload);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Persist a group's record (ADR-0012 grouping): upsert the `groups` row with
|
|
166
|
+
* the key + how it formed (taken from the record, never hardcoded), then
|
|
167
|
+
* replace its `group_alerts` with the formative alerts (late=0) followed by
|
|
168
|
+
* any already-attached late alerts (late=1), each in order. The group row may
|
|
169
|
+
* already exist (a plain `create()` seeds one) — we keep its created_at.
|
|
170
|
+
*/
|
|
171
|
+
async writeGroupRecord(runId, rec) {
|
|
172
|
+
this.assertRunId(runId);
|
|
173
|
+
const now = new Date().toISOString();
|
|
174
|
+
tx(this.db, () => {
|
|
175
|
+
this.db
|
|
176
|
+
.prepare(`
|
|
177
|
+
INSERT INTO groups (id, group_key, formed_by, created_at)
|
|
178
|
+
VALUES (?, ?, ?, ?)
|
|
179
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
180
|
+
group_key = excluded.group_key,
|
|
181
|
+
formed_by = excluded.formed_by
|
|
182
|
+
`)
|
|
183
|
+
.run(runId, rec.groupKey, rec.formedBy, now);
|
|
184
|
+
this.db.prepare("DELETE FROM group_alerts WHERE group_id = ?").run(runId);
|
|
185
|
+
const insertAlert = this.db.prepare("INSERT INTO group_alerts (group_id, late, payload) VALUES (?, ?, ?)");
|
|
186
|
+
for (const alert of rec.alerts) {
|
|
187
|
+
insertAlert.run(runId, 0, JSON.stringify(alert));
|
|
188
|
+
}
|
|
189
|
+
for (const alert of rec.lateAlerts) {
|
|
190
|
+
insertAlert.run(runId, 1, JSON.stringify(alert));
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Attach a late alert to an existing group, appended in arrival order. */
|
|
195
|
+
async appendGroupAlert(runId, alert) {
|
|
196
|
+
this.assertRunId(runId);
|
|
197
|
+
tx(this.db, () => {
|
|
198
|
+
const group = this.db
|
|
199
|
+
.prepare("SELECT id FROM groups WHERE id = ?")
|
|
200
|
+
.get(runId);
|
|
201
|
+
if (!group) {
|
|
202
|
+
throw new Error(`Cannot append alert to missing group for run ${runId}`);
|
|
203
|
+
}
|
|
204
|
+
this.db
|
|
205
|
+
.prepare("INSERT INTO group_alerts (group_id, late, payload) VALUES (?, 1, ?)")
|
|
206
|
+
.run(runId, JSON.stringify(alert));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async recordSuppressed(input) {
|
|
210
|
+
this.assertRunId(input.runId);
|
|
211
|
+
const now = new Date().toISOString();
|
|
212
|
+
tx(this.db, () => {
|
|
213
|
+
// The grouping layer already wrote this run's group row before dispatch;
|
|
214
|
+
// upsert-noop keeps the FK satisfied if it somehow did not.
|
|
215
|
+
this.db
|
|
216
|
+
.prepare(`
|
|
217
|
+
INSERT INTO groups (id, formed_by, created_at)
|
|
218
|
+
VALUES (?, 'window', ?)
|
|
219
|
+
ON CONFLICT DO NOTHING
|
|
220
|
+
`)
|
|
221
|
+
.run(input.runId, now);
|
|
222
|
+
this.db
|
|
223
|
+
.prepare(`
|
|
224
|
+
INSERT INTO runs (
|
|
225
|
+
run_id, group_id, status, alertname,
|
|
226
|
+
workspace_path, suppression_reason,
|
|
227
|
+
created_at, updated_at, completed_at
|
|
228
|
+
) VALUES (?, ?, 'suppressed', ?, '', ?, ?, ?, ?)
|
|
229
|
+
`)
|
|
230
|
+
.run(input.runId, input.runId, input.alertname ?? null, input.reason, now, now, now);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/** Release the underlying db handle. Callers that own a transient manager
|
|
234
|
+
* (e.g. one per `pl listen` run) must close it or leak a connection. */
|
|
235
|
+
close() {
|
|
236
|
+
this.db.close();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=sqlite-session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-session-store.js","sourceRoot":"","sources":["../../../src/core/sqlite-session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { GroupingPort } from "../cli/grouping.js";
|
|
2
|
+
export interface ListenServerOptions {
|
|
3
|
+
/** Port to bind (0 = ephemeral, for tests). */
|
|
4
|
+
port: number;
|
|
5
|
+
/** Shared bearer token; every request without it gets 401. */
|
|
6
|
+
token: string;
|
|
7
|
+
/** Grouping layer. */
|
|
8
|
+
grouping: GroupingPort;
|
|
9
|
+
/**
|
|
10
|
+
* Max alerts waiting or running at once. A payload that would overflow gets
|
|
11
|
+
* 503 so Alertmanager's durable retry absorbs the backpressure. To prevent
|
|
12
|
+
* permanent 503s for oversized groups, an empty queue always admits a payload
|
|
13
|
+
* regardless of size. Default 8.
|
|
14
|
+
*/
|
|
15
|
+
maxPending?: number;
|
|
16
|
+
}
|
|
17
|
+
/** The one route this receiver serves (Alertmanager webhook_config url path). */
|
|
18
|
+
export declare const WEBHOOK_PATH = "/webhooks/alertmanager";
|
|
19
|
+
export interface ListenServer {
|
|
20
|
+
/** The actually-bound port (resolves port 0). */
|
|
21
|
+
port: number;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** Start the intake server; resolves once the port is bound. */
|
|
25
|
+
export declare function startListenServer(options: ListenServerOptions): Promise<ListenServer>;
|
|
26
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/http/server.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IACnC,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,QAAQ,EAAE,YAAY,CAAC;IAEvB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,iFAAiF;AACjF,eAAO,MAAM,YAAY,2BAA2B,CAAC;AAErD,MAAM,WAAW,YAAY;IAC5B,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AA4BD,gEAAgE;AAChE,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,mBAAmB,GAC1B,OAAO,CAAC,YAAY,CAAC,CA0HvB"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Sumit Patel
|
|
3
|
+
/**
|
|
4
|
+
* `pl listen` HTTP intake (issue #58) — a token-authed local webhook receiver
|
|
5
|
+
* for Alertmanager. Plain `node:http`: one POST route, no framework. The
|
|
6
|
+
* investigation runner is INJECTED (the command wires the real seam chain;
|
|
7
|
+
* tests wire a fake) so the wire contract and the pipeline stay separately
|
|
8
|
+
* testable, mirroring how `jsonrpc/server.ts` takes injectable streams.
|
|
9
|
+
*/
|
|
10
|
+
import { timingSafeEqual } from "node:crypto";
|
|
11
|
+
import { createServer, } from "node:http";
|
|
12
|
+
import { PrometheusWebhookSchema } from "@prismalens/contracts";
|
|
13
|
+
const DEFAULT_MAX_PENDING = 8;
|
|
14
|
+
/** The one route this receiver serves (Alertmanager webhook_config url path). */
|
|
15
|
+
export const WEBHOOK_PATH = "/webhooks/alertmanager";
|
|
16
|
+
/** Constant-time bearer-token check — never leak a prefix match via timing. */
|
|
17
|
+
function isAuthorized(req, token) {
|
|
18
|
+
const header = req.headers.authorization;
|
|
19
|
+
if (!header?.startsWith("Bearer "))
|
|
20
|
+
return false;
|
|
21
|
+
const presented = Buffer.from(header.slice("Bearer ".length));
|
|
22
|
+
const expected = Buffer.from(token);
|
|
23
|
+
return (presented.length === expected.length && timingSafeEqual(presented, expected));
|
|
24
|
+
}
|
|
25
|
+
/** An Alertmanager payload is KBs; anything past this is not a webhook. */
|
|
26
|
+
const MAX_BODY_BYTES = 5 * 1024 * 1024;
|
|
27
|
+
/** Collect the request body as UTF-8, or null once it exceeds the cap. */
|
|
28
|
+
async function readBody(req) {
|
|
29
|
+
const chunks = [];
|
|
30
|
+
let size = 0;
|
|
31
|
+
for await (const chunk of req) {
|
|
32
|
+
size += chunk.length;
|
|
33
|
+
if (size > MAX_BODY_BYTES)
|
|
34
|
+
return null;
|
|
35
|
+
chunks.push(chunk);
|
|
36
|
+
}
|
|
37
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
38
|
+
}
|
|
39
|
+
/** Start the intake server; resolves once the port is bound. */
|
|
40
|
+
export async function startListenServer(options) {
|
|
41
|
+
// grouping owns dispatch; `server.ts` only gates admission via `pendingGroups()`/`newGroupCount()` against `maxPending`.
|
|
42
|
+
const maxPending = options.maxPending ?? DEFAULT_MAX_PENDING;
|
|
43
|
+
const reply = (res, status, body) => {
|
|
44
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
45
|
+
res.end(JSON.stringify(body));
|
|
46
|
+
};
|
|
47
|
+
const handle = async (req, res) => {
|
|
48
|
+
// Route gate BEFORE auth: a wrong path stays distinguishable from a bad
|
|
49
|
+
// token in the sender's logs (this is a local, single-route receiver).
|
|
50
|
+
const path = (req.url ?? "").split("?", 1)[0];
|
|
51
|
+
if (path !== WEBHOOK_PATH) {
|
|
52
|
+
reply(res, 404, { error: "not found" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (req.method !== "POST") {
|
|
56
|
+
res.setHeader("allow", "POST");
|
|
57
|
+
reply(res, 405, { error: "method not allowed" });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!isAuthorized(req, options.token)) {
|
|
61
|
+
reply(res, 401, { error: "unauthorized" });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Reject before reading the body: a slow in-flight client must not be
|
|
65
|
+
// able to delay server.close() during shutdown.
|
|
66
|
+
if (options.grouping.isShuttingDown()) {
|
|
67
|
+
reply(res, 503, { error: "server is shutting down" });
|
|
68
|
+
req.destroy();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const raw = await readBody(req);
|
|
72
|
+
if (raw === null) {
|
|
73
|
+
reply(res, 413, { error: "request body too large" });
|
|
74
|
+
req.destroy();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let payloadObj;
|
|
78
|
+
try {
|
|
79
|
+
payloadObj = JSON.parse(raw);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
reply(res, 400, { error: "request body is not valid JSON" });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const parsed = PrometheusWebhookSchema.safeParse(payloadObj);
|
|
86
|
+
if (!parsed.success) {
|
|
87
|
+
// Never silently swallow a bad payload (unlike parseStdin): surface the
|
|
88
|
+
// zod reason so a misconfigured Alertmanager is debuggable from its logs.
|
|
89
|
+
const reason = parsed.error.issues
|
|
90
|
+
.map((i) => `${i.path.join(".") || "payload"}: ${i.message}`)
|
|
91
|
+
.join("; ");
|
|
92
|
+
reply(res, 400, { error: `invalid Alertmanager payload — ${reason}` });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Answer BEFORE investigating: Alertmanager's webhook timeout is seconds,
|
|
96
|
+
// an investigation takes minutes. Firing alerts only (which are grouped and
|
|
97
|
+
// investigated via the grouping layer); resolved ones are just acknowledged,
|
|
98
|
+
// not fed into lifecycle tracking (future work).
|
|
99
|
+
const firing = parsed.data.alerts.filter((a) => a.status === "firing");
|
|
100
|
+
const payload = payloadObj;
|
|
101
|
+
const pendingGroups = options.grouping.pendingGroups();
|
|
102
|
+
const newGroupCount = options.grouping.newGroupCount(firing, payload);
|
|
103
|
+
if (newGroupCount > 0 &&
|
|
104
|
+
pendingGroups > 0 &&
|
|
105
|
+
pendingGroups + newGroupCount > maxPending) {
|
|
106
|
+
// Whole-payload rejection (no partial acceptance): Alertmanager retries
|
|
107
|
+
// the notification later; nothing 202'd is ever silently dropped.
|
|
108
|
+
// To avoid a permanent 503 loop for groups larger than the cap, we ALWAYS
|
|
109
|
+
// admit payloads when the queue is entirely empty (pending === 0).
|
|
110
|
+
reply(res, 503, {
|
|
111
|
+
error: `investigation queue full (${pendingGroups} pending, max ${maxPending}) — retry later`,
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
reply(res, 202, {
|
|
116
|
+
received: parsed.data.alerts.length,
|
|
117
|
+
accepted: firing.length,
|
|
118
|
+
});
|
|
119
|
+
options.grouping.admit(firing, payload);
|
|
120
|
+
};
|
|
121
|
+
const server = createServer((req, res) => {
|
|
122
|
+
// Last-resort guard: every expected failure is already replied to inside
|
|
123
|
+
// handle(); this only fires on e.g. a socket torn down mid-read.
|
|
124
|
+
/* v8 ignore next 4 */
|
|
125
|
+
handle(req, res).catch(() => {
|
|
126
|
+
if (!res.headersSent)
|
|
127
|
+
reply(res, 500, { error: "internal error" });
|
|
128
|
+
else
|
|
129
|
+
res.end();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
await new Promise((resolve, reject) => {
|
|
133
|
+
server.once("error", reject);
|
|
134
|
+
server.listen(options.port, "127.0.0.1", () => resolve());
|
|
135
|
+
});
|
|
136
|
+
const address = server.address();
|
|
137
|
+
/* v8 ignore next 3 — a TCP listen never yields a pipe/null address */
|
|
138
|
+
if (address === null || typeof address === "string") {
|
|
139
|
+
throw new Error("listen server bound to a non-TCP address");
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
port: address.port,
|
|
143
|
+
close: () => {
|
|
144
|
+
options.grouping.shutdown();
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/http/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prismalens",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "AI root-cause investigation from your terminal — point it at a repo, pipe it an alert, get an evidence-linked report",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -42,12 +42,13 @@
|
|
|
42
42
|
"json-rpc-2.0": "^1.7.1",
|
|
43
43
|
"yaml": "^2.9.0",
|
|
44
44
|
"zod": "^3.25.76",
|
|
45
|
-
"@prismalens/
|
|
46
|
-
"@prismalens/engine": "0.0
|
|
47
|
-
"@prismalens/
|
|
45
|
+
"@prismalens/config": "0.1.0",
|
|
46
|
+
"@prismalens/engine": "0.1.0",
|
|
47
|
+
"@prismalens/contracts": "0.0.2"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@types/node": "^24.13.2",
|
|
51
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
51
52
|
"tsx": "^4.22.5",
|
|
52
53
|
"typescript": "^5.9.3",
|
|
53
54
|
"vitest": "^4.1.9"
|
|
@@ -56,6 +57,6 @@
|
|
|
56
57
|
"build": "tsc",
|
|
57
58
|
"typecheck": "tsc --noEmit",
|
|
58
59
|
"dev": "tsx bin/prismalens.ts",
|
|
59
|
-
"test": "vitest run"
|
|
60
|
+
"test": "vitest run --coverage"
|
|
60
61
|
}
|
|
61
62
|
}
|