pipework 0.68.0 → 0.69.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/CHANGELOG.md +6 -0
- package/REFERENCE.md +58 -0
- package/dist/core/config/namespace.d.ts +1 -0
- package/dist/core/config/namespace.d.ts.map +1 -1
- package/dist/core/config/schema.d.ts +3 -0
- package/dist/core/config/schema.d.ts.map +1 -1
- package/dist/core/config/schema.js +2 -0
- package/dist/core/config/schema.js.map +1 -1
- package/dist/data/migrate/internal-definitions.d.ts.map +1 -1
- package/dist/data/migrate/internal-definitions.js +6 -0
- package/dist/data/migrate/internal-definitions.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/infra/alerts/audit.d.ts +3 -0
- package/dist/infra/alerts/audit.d.ts.map +1 -0
- package/dist/infra/alerts/audit.js +25 -0
- package/dist/infra/alerts/audit.js.map +1 -0
- package/dist/infra/alerts/handle.d.ts +95 -0
- package/dist/infra/alerts/handle.d.ts.map +1 -0
- package/dist/infra/alerts/handle.js +301 -0
- package/dist/infra/alerts/handle.js.map +1 -0
- package/dist/infra/alerts/index.d.ts +5 -0
- package/dist/infra/alerts/index.d.ts.map +1 -0
- package/dist/infra/alerts/index.js +2 -0
- package/dist/infra/alerts/index.js.map +1 -0
- package/dist/infra/alerts/namespace.d.ts +23 -0
- package/dist/infra/alerts/namespace.d.ts.map +1 -0
- package/dist/infra/alerts/namespace.js +23 -0
- package/dist/infra/alerts/namespace.js.map +1 -0
- package/dist/infra/alerts/schema.d.ts +123 -0
- package/dist/infra/alerts/schema.d.ts.map +1 -0
- package/dist/infra/alerts/schema.js +55 -0
- package/dist/infra/alerts/schema.js.map +1 -0
- package/dist/infra/alerts/sinks.d.ts +60 -0
- package/dist/infra/alerts/sinks.d.ts.map +1 -0
- package/dist/infra/alerts/sinks.js +50 -0
- package/dist/infra/alerts/sinks.js.map +1 -0
- package/dist/infra/alerts/sources.d.ts +55 -0
- package/dist/infra/alerts/sources.d.ts.map +1 -0
- package/dist/infra/alerts/sources.js +134 -0
- package/dist/infra/alerts/sources.js.map +1 -0
- package/dist/infra/email/sns.d.ts +1 -1
- package/dist/infra/email/sns.js +1 -1
- package/dist/infra/webhook/sign.d.ts +2 -0
- package/dist/infra/webhook/sign.d.ts.map +1 -1
- package/dist/infra/webhook/sign.js +1 -1
- package/dist/infra/webhook/sign.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { WebhookSignatureOptions } from '../webhook/sign.js';
|
|
2
|
+
/** What a sink receives, and what the webhook sink POSTs as its body. */
|
|
3
|
+
export interface AlertPayload {
|
|
4
|
+
readonly kind: 'raise' | 'repeat' | 'resolve';
|
|
5
|
+
readonly key: string;
|
|
6
|
+
readonly severity: 'info' | 'warning' | 'error' | 'critical';
|
|
7
|
+
readonly message: string;
|
|
8
|
+
readonly subject: {
|
|
9
|
+
readonly tenantId?: string;
|
|
10
|
+
readonly entity?: string;
|
|
11
|
+
readonly entityId?: string;
|
|
12
|
+
} | null;
|
|
13
|
+
readonly count: number;
|
|
14
|
+
readonly firstSeenAt: string;
|
|
15
|
+
readonly lastSeenAt: string;
|
|
16
|
+
readonly resolvedAt: string | null;
|
|
17
|
+
/** The latest traces that raised it, newest first. */
|
|
18
|
+
readonly traceIds: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface AlertDelivery {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly kind: AlertPayload['kind'];
|
|
23
|
+
readonly key: string;
|
|
24
|
+
readonly sink: string;
|
|
25
|
+
readonly payload: AlertPayload;
|
|
26
|
+
/** 1 on the first try. */
|
|
27
|
+
readonly attempt: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A place alerts are sent. `send` throwing is a failed attempt; so is running past 10 s, which is all the
|
|
31
|
+
* handle gives a send (the row is leased for 30 s). `delivery.id` is the same on every attempt, so a receiver can dedupe on it.
|
|
32
|
+
*/
|
|
33
|
+
export interface AlertSink {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
send(delivery: AlertDelivery): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
export interface AlertWebhookConfig {
|
|
38
|
+
/** Sink name; default `'webhook'`. Two sinks on one handle need distinct names. */
|
|
39
|
+
readonly name?: string;
|
|
40
|
+
readonly url: string;
|
|
41
|
+
readonly secret: string;
|
|
42
|
+
/** Request timeout; default 10 000, at most 20 000 (the handle gives a send 10 s and leases the row for 30 s). */
|
|
43
|
+
readonly timeoutMs?: number;
|
|
44
|
+
readonly signing?: WebhookSignatureOptions;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* POSTs the payload as JSON with `X-Webhook-Id`, `X-Webhook-Timestamp` and `X-Webhook-Signature`
|
|
48
|
+
* (`X-Webhook-Id` is the delivery id, the same on every attempt; HMAC over `<id>.<timestamp>.<body>`, as `webhook.sign` makes them). Any non-2xx status fails the attempt.
|
|
49
|
+
*/
|
|
50
|
+
export declare function webhookSink(config: AlertWebhookConfig): AlertSink;
|
|
51
|
+
export interface MemoryAlertSink extends AlertSink {
|
|
52
|
+
/** Every delivery received, in order. */
|
|
53
|
+
readonly delivered: AlertDelivery[];
|
|
54
|
+
clear(): void;
|
|
55
|
+
}
|
|
56
|
+
/** A sink that keeps what it receives, for tests. */
|
|
57
|
+
export declare function memorySink(options?: {
|
|
58
|
+
name?: string;
|
|
59
|
+
}): MemoryAlertSink;
|
|
60
|
+
//# sourceMappingURL=sinks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.d.ts","sourceRoot":"","sources":["../../../src/infra/alerts/sinks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA;AAEjE,yEAAyE;AACzE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAA;IAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAA;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC7G,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAA;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,0BAA0B;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,kBAAkB;IACjC,mFAAmF;IACnF,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,kHAAkH;IAClH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,uBAAuB,CAAA;CAC3C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,CA8BjE;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,yCAAyC;IACzC,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,CAAA;IACnC,KAAK,IAAI,IAAI,CAAA;CACd;AAED,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,eAAe,CAS3E"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { signPayload } from '../webhook/sign.js';
|
|
2
|
+
/**
|
|
3
|
+
* POSTs the payload as JSON with `X-Webhook-Id`, `X-Webhook-Timestamp` and `X-Webhook-Signature`
|
|
4
|
+
* (`X-Webhook-Id` is the delivery id, the same on every attempt; HMAC over `<id>.<timestamp>.<body>`, as `webhook.sign` makes them). Any non-2xx status fails the attempt.
|
|
5
|
+
*/
|
|
6
|
+
export function webhookSink(config) {
|
|
7
|
+
const timeoutMs = config.timeoutMs ?? 10_000;
|
|
8
|
+
if (timeoutMs > 20_000) {
|
|
9
|
+
throw new Error(`[pipework] alerts.webhook: timeoutMs ${String(timeoutMs)} is over 20000. A delivery is leased for 30 s while it is sent; a longer send could be picked up and sent twice.`);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
name: config.name ?? 'webhook',
|
|
13
|
+
async send(delivery) {
|
|
14
|
+
const signed = signPayload(delivery.payload, config.secret, { ...config.signing, messageId: delivery.id });
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(config.url, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
'X-Webhook-Id': signed.messageId,
|
|
23
|
+
'X-Webhook-Timestamp': signed.timestamp.toString(),
|
|
24
|
+
'X-Webhook-Signature': signed.signature,
|
|
25
|
+
},
|
|
26
|
+
body: signed.body,
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
});
|
|
29
|
+
await response.text().catch(() => '');
|
|
30
|
+
if (response.status < 200 || response.status >= 300)
|
|
31
|
+
throw new Error(`webhook answered HTTP ${String(response.status)}`);
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** A sink that keeps what it receives, for tests. */
|
|
40
|
+
export function memorySink(options = {}) {
|
|
41
|
+
const delivered = [];
|
|
42
|
+
return {
|
|
43
|
+
name: options.name ?? 'memory',
|
|
44
|
+
delivered,
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- the sink contract is async
|
|
46
|
+
async send(delivery) { delivered.push(delivery); },
|
|
47
|
+
clear() { delivered.length = 0; },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=sinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sinks.js","sourceRoot":"","sources":["../../../src/infra/alerts/sinks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AA+ChD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAA0B;IACpD,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,MAAM,CAAA;IAC5C,IAAI,SAAS,GAAG,MAAM,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,wCAAwC,MAAM,CAAC,SAAS,CAAC,kHAAkH,CAAC,CAAA;IAC9L,CAAC;IACD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,SAAS;QAC9B,KAAK,CAAC,IAAI,CAAC,QAAQ;YACjB,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;YAC1G,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;YACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAA;YAC7D,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE;oBACvC,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACP,cAAc,EAAE,kBAAkB;wBAClC,cAAc,EAAE,MAAM,CAAC,SAAS;wBAChC,qBAAqB,EAAE,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE;wBAClD,qBAAqB,EAAE,MAAM,CAAC,SAAS;qBACxC;oBACD,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,MAAM,EAAE,UAAU,CAAC,MAAM;iBAC1B,CAAC,CAAA;gBACF,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;gBACrC,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG;oBAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;YAC1H,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,KAAK,CAAC,CAAA;YACrB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAQD,qDAAqD;AACrD,MAAM,UAAU,UAAU,CAAC,UAA6B,EAAE;IACxD,MAAM,SAAS,GAAoB,EAAE,CAAA;IACrC,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,QAAQ;QAC9B,SAAS;QACT,0FAA0F;QAC1F,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA,CAAC,CAAC;QACjD,KAAK,KAAK,SAAS,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,CAAC;KACjC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { HealthCheck } from '../../core/lifecycle/types.js';
|
|
2
|
+
import type { AlertSeverity } from './handle.js';
|
|
3
|
+
/** A condition a source finds true right now. */
|
|
4
|
+
export interface AlertCondition {
|
|
5
|
+
readonly key: string;
|
|
6
|
+
readonly severity: AlertSeverity;
|
|
7
|
+
readonly message: string;
|
|
8
|
+
readonly subject?: {
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
entity?: string;
|
|
11
|
+
entityId?: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Evaluated on every tick. Each returned condition is raised (so an ongoing one groups). When
|
|
16
|
+
* `owns` is given, an open alert the source owns that it no longer returns is resolved.
|
|
17
|
+
*/
|
|
18
|
+
export interface AlertSource {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
owns?(key: string): boolean;
|
|
21
|
+
evaluate(): Promise<AlertCondition[]>;
|
|
22
|
+
}
|
|
23
|
+
interface TableOption {
|
|
24
|
+
readonly table?: string;
|
|
25
|
+
readonly database?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Raises `job-dead-letter:<jobType>` for jobs that exhausted their attempts since the last
|
|
29
|
+
* evaluation (any process: the position is kept in `pipework_alert_source_state`; the first
|
|
30
|
+
* evaluation starts from now, so history is not replayed). The condition clears only by the
|
|
31
|
+
* handle's quiet period, so set `resolveAfterQuietMs` on `alerts.create`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function deadLetterSource(options?: TableOption & {
|
|
34
|
+
severity?: AlertSeverity;
|
|
35
|
+
}): AlertSource;
|
|
36
|
+
/** Raises `job-long-running:<jobType>` while a job of that type has been claimed for longer than `olderThanMs`; resolves when none is. */
|
|
37
|
+
export declare function longRunningSource(options: TableOption & {
|
|
38
|
+
olderThanMs: number;
|
|
39
|
+
severity?: AlertSeverity;
|
|
40
|
+
}): AlertSource;
|
|
41
|
+
/**
|
|
42
|
+
* Raises `queue-backlog:<jobType>` while more than `maxPending` jobs of a type are ready to claim, or the
|
|
43
|
+
* oldest has waited more than `maxOldestAgeMs`; resolves when the type is under both.
|
|
44
|
+
*/
|
|
45
|
+
export declare function backlogSource(options: TableOption & {
|
|
46
|
+
maxPending?: number;
|
|
47
|
+
maxOldestAgeMs?: number;
|
|
48
|
+
severity?: AlertSeverity;
|
|
49
|
+
}): AlertSource;
|
|
50
|
+
/** Raises `health:<name>` while a check reports unhealthy or throws; resolves when it is healthy again. Degraded is not a fault. */
|
|
51
|
+
export declare function healthSource(checks: readonly HealthCheck[], options?: {
|
|
52
|
+
severity?: AlertSeverity;
|
|
53
|
+
}): AlertSource;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../../../src/infra/alerts/sources.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC7E;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,QAAQ,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;CACtC;AAED,UAAU,WAAW;IAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;AAQ7E;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,WAAW,GAAG;IAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAO,GAAG,WAAW,CAiCtG;AAED,0IAA0I;AAC1I,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG,WAAW,CAqBvH;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG,WAAW,CA8B5I;AAED,oIAAoI;AACpI,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,aAAa,CAAA;CAAO,GAAG,WAAW,CAqBpH"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { sql } from '../../data/db/sql.js';
|
|
2
|
+
import { pipeSystem } from '../../data/db/db.js';
|
|
3
|
+
import { assertValidIdentifier, quoteIdentifier } from '../../data/db/identifiers.js';
|
|
4
|
+
function jobsTable(table) {
|
|
5
|
+
const name = table ?? 'pipework_jobs';
|
|
6
|
+
assertValidIdentifier(name, 'queue table');
|
|
7
|
+
return quoteIdentifier(name, 'queue table');
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Raises `job-dead-letter:<jobType>` for jobs that exhausted their attempts since the last
|
|
11
|
+
* evaluation (any process: the position is kept in `pipework_alert_source_state`; the first
|
|
12
|
+
* evaluation starts from now, so history is not replayed). The condition clears only by the
|
|
13
|
+
* handle's quiet period, so set `resolveAfterQuietMs` on `alerts.create`.
|
|
14
|
+
*/
|
|
15
|
+
export function deadLetterSource(options = {}) {
|
|
16
|
+
const jobs = jobsTable(options.table);
|
|
17
|
+
const severity = options.severity ?? 'error';
|
|
18
|
+
const name = 'job-dead-letter';
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
async evaluate() {
|
|
22
|
+
const db = pipeSystem(options.database);
|
|
23
|
+
const first = await db.execute(sql `
|
|
24
|
+
INSERT INTO pipework_alert_source_state (name, cursor_at) VALUES (${name}, clock_timestamp())
|
|
25
|
+
ON CONFLICT (name) DO NOTHING RETURNING name`);
|
|
26
|
+
if (first.length > 0)
|
|
27
|
+
return [];
|
|
28
|
+
const rows = await db.execute(sql `
|
|
29
|
+
WITH cur AS (SELECT cursor_at FROM pipework_alert_source_state WHERE name = ${name} FOR UPDATE),
|
|
30
|
+
found AS (
|
|
31
|
+
SELECT j.job_type, count(*)::int AS n, max(j.completed_at) AS hi,
|
|
32
|
+
(array_agg(j.error_payload->>'message' ORDER BY j.completed_at DESC))[1] AS last_error
|
|
33
|
+
FROM ${sql.raw(jobs)} j, cur
|
|
34
|
+
WHERE j.status = 'dead_letter' AND j.completed_at > cur.cursor_at
|
|
35
|
+
GROUP BY j.job_type),
|
|
36
|
+
moved AS (
|
|
37
|
+
UPDATE pipework_alert_source_state s SET cursor_at = GREATEST(s.cursor_at, (SELECT max(hi) FROM found))
|
|
38
|
+
WHERE s.name = ${name} AND EXISTS (SELECT 1 FROM found) RETURNING 1)
|
|
39
|
+
SELECT job_type, n, last_error, (SELECT count(*) FROM moved) AS moved FROM found ORDER BY job_type`);
|
|
40
|
+
return rows.map(r => ({
|
|
41
|
+
key: `job-dead-letter:${String(r['job_type'])}`,
|
|
42
|
+
severity,
|
|
43
|
+
message: `${String(r['n'])} ${String(r['job_type'])} job(s) dead-lettered`
|
|
44
|
+
+ (r['last_error'] !== null && r['last_error'] !== undefined ? `; last error: ${String(r['last_error'])}` : ''),
|
|
45
|
+
subject: { entity: 'job', entityId: String(r['job_type']) },
|
|
46
|
+
}));
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Raises `job-long-running:<jobType>` while a job of that type has been claimed for longer than `olderThanMs`; resolves when none is. */
|
|
51
|
+
export function longRunningSource(options) {
|
|
52
|
+
const jobs = jobsTable(options.table);
|
|
53
|
+
const severity = options.severity ?? 'warning';
|
|
54
|
+
return {
|
|
55
|
+
name: 'job-long-running',
|
|
56
|
+
owns: key => key.startsWith('job-long-running:'),
|
|
57
|
+
async evaluate() {
|
|
58
|
+
const rows = await pipeSystem(options.database).execute(sql `
|
|
59
|
+
SELECT job_type, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(claimed_at)) * 1000)::bigint AS oldest_ms
|
|
60
|
+
FROM ${sql.raw(jobs)}
|
|
61
|
+
WHERE status IN ('claimed', 'running')
|
|
62
|
+
AND claimed_at < clock_timestamp() - ${options.olderThanMs} * interval '1 millisecond'
|
|
63
|
+
GROUP BY job_type ORDER BY job_type`);
|
|
64
|
+
return rows.map(r => ({
|
|
65
|
+
key: `job-long-running:${String(r['job_type'])}`,
|
|
66
|
+
severity,
|
|
67
|
+
message: `${String(r['n'])} ${String(r['job_type'])} job(s) running for more than ${String(options.olderThanMs)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
68
|
+
subject: { entity: 'job', entityId: String(r['job_type']) },
|
|
69
|
+
}));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Raises `queue-backlog:<jobType>` while more than `maxPending` jobs of a type are ready to claim, or the
|
|
75
|
+
* oldest has waited more than `maxOldestAgeMs`; resolves when the type is under both.
|
|
76
|
+
*/
|
|
77
|
+
export function backlogSource(options) {
|
|
78
|
+
if (options.maxPending === undefined && options.maxOldestAgeMs === undefined) {
|
|
79
|
+
throw new Error('[pipework] alerts.source.backlog needs maxPending, maxOldestAgeMs or both.');
|
|
80
|
+
}
|
|
81
|
+
const jobs = jobsTable(options.table);
|
|
82
|
+
const severity = options.severity ?? 'warning';
|
|
83
|
+
const maxPending = options.maxPending ?? null;
|
|
84
|
+
const maxAge = options.maxOldestAgeMs ?? null;
|
|
85
|
+
return {
|
|
86
|
+
name: 'queue-backlog',
|
|
87
|
+
owns: key => key.startsWith('queue-backlog:'),
|
|
88
|
+
async evaluate() {
|
|
89
|
+
const rows = await pipeSystem(options.database).execute(sql `
|
|
90
|
+
SELECT job_type, n, oldest_ms FROM (
|
|
91
|
+
SELECT job_type, count(*)::int AS n,
|
|
92
|
+
(extract(epoch FROM clock_timestamp() - min(queued_at)) * 1000)::bigint AS oldest_ms
|
|
93
|
+
FROM ${sql.raw(jobs)}
|
|
94
|
+
WHERE status = 'pending' AND (next_retry_at IS NULL OR next_retry_at <= clock_timestamp())
|
|
95
|
+
GROUP BY job_type) p
|
|
96
|
+
WHERE (${maxPending}::int IS NOT NULL AND n > ${maxPending}::int)
|
|
97
|
+
OR (${maxAge}::bigint IS NOT NULL AND oldest_ms > ${maxAge}::bigint)
|
|
98
|
+
ORDER BY job_type`);
|
|
99
|
+
return rows.map(r => ({
|
|
100
|
+
key: `queue-backlog:${String(r['job_type'])}`,
|
|
101
|
+
severity,
|
|
102
|
+
message: `${String(r['n'])} ${String(r['job_type'])} job(s) waiting, the oldest for ${String(r['oldest_ms'])} ms`,
|
|
103
|
+
subject: { entity: 'job', entityId: String(r['job_type']) },
|
|
104
|
+
}));
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Raises `health:<name>` while a check reports unhealthy or throws; resolves when it is healthy again. Degraded is not a fault. */
|
|
109
|
+
export function healthSource(checks, options = {}) {
|
|
110
|
+
const severity = options.severity ?? 'error';
|
|
111
|
+
const keys = new Set(checks.map(c => `health:${c.name}`));
|
|
112
|
+
return {
|
|
113
|
+
name: 'health',
|
|
114
|
+
owns: key => keys.has(key),
|
|
115
|
+
async evaluate() {
|
|
116
|
+
const found = [];
|
|
117
|
+
for (const check of checks) {
|
|
118
|
+
let message = null;
|
|
119
|
+
try {
|
|
120
|
+
const result = await check.check();
|
|
121
|
+
if (result.status === 'unhealthy')
|
|
122
|
+
message = `${check.name} is unhealthy${result.message !== undefined ? `: ${result.message}` : ''}`;
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
message = `${check.name} check threw: ${err instanceof Error ? err.message : String(err)}`;
|
|
126
|
+
}
|
|
127
|
+
if (message !== null)
|
|
128
|
+
found.push({ key: `health:${check.name}`, severity, message, subject: { entity: 'health', entityId: check.name } });
|
|
129
|
+
}
|
|
130
|
+
return found;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../../src/infra/alerts/sources.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ export declare function snsStringToSign(msg: SnsMessage): string;
|
|
|
18
18
|
* `pipework_email_suppressions`, which the send job checks; a transient or undetermined
|
|
19
19
|
* bounce is only logged. Each one is a trace step with an `Email:bounce` / `Email:complaint`
|
|
20
20
|
* audit record whose `after` carries `messageTraceId`, the trace of the original send.
|
|
21
|
-
* A suppression is logged at warn as `email suppressed`;
|
|
21
|
+
* A suppression is logged at warn as `email suppressed`; an app raises an alert for it with `alerts.create(...).raise`.
|
|
22
22
|
*
|
|
23
23
|
* Only messages from the configured `topicArns` are accepted (401 otherwise, before any subscription is
|
|
24
24
|
* confirmed or event processed): SNS signs every topic, including an attacker's own.
|
package/dist/infra/email/sns.js
CHANGED
|
@@ -86,7 +86,7 @@ const processEvent = __flowStep({ ref: 'pipework/email:notification' }, async (e
|
|
|
86
86
|
* `pipework_email_suppressions`, which the send job checks; a transient or undetermined
|
|
87
87
|
* bounce is only logged. Each one is a trace step with an `Email:bounce` / `Email:complaint`
|
|
88
88
|
* audit record whose `after` carries `messageTraceId`, the trace of the original send.
|
|
89
|
-
* A suppression is logged at warn as `email suppressed`;
|
|
89
|
+
* A suppression is logged at warn as `email suppressed`; an app raises an alert for it with `alerts.create(...).raise`.
|
|
90
90
|
*
|
|
91
91
|
* Only messages from the configured `topicArns` are accepted (401 otherwise, before any subscription is
|
|
92
92
|
* confirmed or event processed): SNS signs every topic, including an attacker's own.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface WebhookSignatureOptions {
|
|
2
2
|
algorithm?: string;
|
|
3
3
|
encoding?: 'hex' | 'base64';
|
|
4
|
+
/** Message id to sign and return; default a fresh UUID. Pass a stable id so a retry is recognisable as the same message. */
|
|
5
|
+
messageId?: string;
|
|
4
6
|
}
|
|
5
7
|
export interface SignedPayload {
|
|
6
8
|
body: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../../src/infra/webhook/sign.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../../src/infra/webhook/sign.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC3B,4HAA4H;IAC5H,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,uBAA4B,GACpC,aAAa,CAWf"}
|
|
@@ -2,7 +2,7 @@ import { createHmac, randomUUID } from 'node:crypto';
|
|
|
2
2
|
export function signPayload(payload, secret, options = {}) {
|
|
3
3
|
const algorithm = options.algorithm ?? 'sha256';
|
|
4
4
|
const encoding = options.encoding ?? 'hex';
|
|
5
|
-
const messageId = randomUUID();
|
|
5
|
+
const messageId = options.messageId ?? randomUUID();
|
|
6
6
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
7
7
|
const body = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
8
8
|
const toSign = `${messageId}.${timestamp}.${body}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../../../src/infra/webhook/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"sign.js","sourceRoot":"","sources":["../../../src/infra/webhook/sign.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAgBpD,MAAM,UAAU,WAAW,CACzB,OAAgB,EAChB,MAAc,EACd,UAAmC,EAAE;IAErC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,QAAQ,CAAA;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAA;IAE1C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,UAAU,EAAE,CAAA;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IAC/C,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IAC5E,MAAM,MAAM,GAAG,GAAG,SAAS,IAAI,SAAS,IAAI,IAAI,EAAE,CAAA;IAClD,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAE/E,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,CAAA;AAClD,CAAC"}
|