@junando/webhook 0.15.1 → 0.16.1
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/dist/handler.cjs +73 -11
- package/package.json +2 -2
package/dist/handler.cjs
CHANGED
|
@@ -19666,6 +19666,12 @@ let RuleEvaluationPhase = /* @__PURE__ */ function(RuleEvaluationPhase) {
|
|
|
19666
19666
|
RuleEvaluationPhase["PostLlm"] = "post-llm";
|
|
19667
19667
|
return RuleEvaluationPhase;
|
|
19668
19668
|
}({});
|
|
19669
|
+
/** Notifier backend a logical channel resolves to */
|
|
19670
|
+
let ChannelType = /* @__PURE__ */ function(ChannelType) {
|
|
19671
|
+
ChannelType["Slack"] = "slack";
|
|
19672
|
+
ChannelType["Teams"] = "teams";
|
|
19673
|
+
return ChannelType;
|
|
19674
|
+
}({});
|
|
19669
19675
|
const AlertCountSchema = object({
|
|
19670
19676
|
min: number$1().optional(),
|
|
19671
19677
|
max: number$1().optional()
|
|
@@ -19715,9 +19721,19 @@ const RuleSchema = object({
|
|
|
19715
19721
|
requiresRollback: boolean$1().optional()
|
|
19716
19722
|
});
|
|
19717
19723
|
const RuleSectionSchema = object({ rules: array(RuleSchema).default([]) });
|
|
19724
|
+
const SLACK_CHANNEL_SCHEMA = object({
|
|
19725
|
+
type: literal("slack"),
|
|
19726
|
+
channel: string$1().startsWith("#")
|
|
19727
|
+
});
|
|
19728
|
+
const TEAMS_CHANNEL_SCHEMA = object({
|
|
19729
|
+
type: literal("teams"),
|
|
19730
|
+
webhookUrlEnv: string$1().min(1)
|
|
19731
|
+
});
|
|
19732
|
+
const ChannelConfigSchema = discriminatedUnion("type", [SLACK_CHANNEL_SCHEMA, TEAMS_CHANNEL_SCHEMA]);
|
|
19718
19733
|
const RuleConfigurationSchema = object({
|
|
19719
19734
|
["pre-llm"]: RuleSectionSchema,
|
|
19720
|
-
["post-llm"]: RuleSectionSchema
|
|
19735
|
+
["post-llm"]: RuleSectionSchema,
|
|
19736
|
+
channels: record(string$1(), ChannelConfigSchema).default({})
|
|
19721
19737
|
});
|
|
19722
19738
|
|
|
19723
19739
|
//#endregion
|
|
@@ -65714,6 +65730,7 @@ function sanitizeEndpointPath(endpointPath) {
|
|
|
65714
65730
|
if (!endpointPath) return "unknown";
|
|
65715
65731
|
return endpointPath.replaceAll("`", "").slice(0, 200);
|
|
65716
65732
|
}
|
|
65733
|
+
const ROLLBACK_PROBABLE_CAUSE_MAX_LEN = 500;
|
|
65717
65734
|
var SlackNotifier = class {
|
|
65718
65735
|
botToken;
|
|
65719
65736
|
channel;
|
|
@@ -65829,7 +65846,8 @@ var SlackNotifier = class {
|
|
|
65829
65846
|
serviceName: cluster.serviceName,
|
|
65830
65847
|
endpointPath: cluster.endpointPath,
|
|
65831
65848
|
alertType: cluster.alertType,
|
|
65832
|
-
urgencyLevel: analysis.urgency_level
|
|
65849
|
+
urgencyLevel: analysis.urgency_level,
|
|
65850
|
+
probableCause: analysis.probable_cause.slice(0, ROLLBACK_PROBABLE_CAUSE_MAX_LEN)
|
|
65833
65851
|
}),
|
|
65834
65852
|
confirm: {
|
|
65835
65853
|
title: {
|
|
@@ -73526,15 +73544,52 @@ function buildNotifierRegistry(config) {
|
|
|
73526
73544
|
return registry;
|
|
73527
73545
|
}
|
|
73528
73546
|
/**
|
|
73547
|
+
* Builds the notifier for a single named channel entry from the rules YAML's
|
|
73548
|
+
* `channels:` section.
|
|
73549
|
+
*
|
|
73550
|
+
* @throws {Error} if the channel's backend cannot actually be reached (e.g.
|
|
73551
|
+
* `type: slack` with no SLACK_BOT_TOKEN configured, or `type: teams` whose
|
|
73552
|
+
* `webhookUrlEnv` is unset) — fails fast at startup rather than at delivery
|
|
73553
|
+
* time, mid-incident.
|
|
73554
|
+
*/
|
|
73555
|
+
function buildChannelNotifier(name, channelConfig, config) {
|
|
73556
|
+
if (channelConfig.type === "slack") {
|
|
73557
|
+
if (!config.slackBotToken) {
|
|
73558
|
+
throw new Error(`Channel "${name}" (type: slack) requires SLACK_BOT_TOKEN to be set`);
|
|
73559
|
+
}
|
|
73560
|
+
return new SlackNotifier(config.slackBotToken, channelConfig.channel);
|
|
73561
|
+
}
|
|
73562
|
+
const webhookUrl = process.env[channelConfig.webhookUrlEnv];
|
|
73563
|
+
if (!webhookUrl) {
|
|
73564
|
+
throw new Error(`Channel "${name}" (type: teams) references env var "${channelConfig.webhookUrlEnv}", which is not set`);
|
|
73565
|
+
}
|
|
73566
|
+
let parsedUrl;
|
|
73567
|
+
try {
|
|
73568
|
+
parsedUrl = new URL(webhookUrl);
|
|
73569
|
+
} catch {
|
|
73570
|
+
throw new Error(`Channel "${name}": ${channelConfig.webhookUrlEnv} is not a valid URL`);
|
|
73571
|
+
}
|
|
73572
|
+
if (!parsedUrl.searchParams.has("api-version")) {
|
|
73573
|
+
throw new Error(`Channel "${name}": ${channelConfig.webhookUrlEnv} must include api-version= as a query parameter`);
|
|
73574
|
+
}
|
|
73575
|
+
return new TeamsNotifier(webhookUrl);
|
|
73576
|
+
}
|
|
73577
|
+
/**
|
|
73529
73578
|
* Creates the notifier for the application.
|
|
73530
73579
|
*
|
|
73531
73580
|
* When `config.rulesConfigPath` is set:
|
|
73532
73581
|
* - Reads and validates the rules YAML config
|
|
73533
|
-
* -
|
|
73582
|
+
* - Builds a ChannelRegistry from its `channels:` section, with the default
|
|
73583
|
+
* notifier as fallback
|
|
73534
73584
|
* - Wraps the default notifier with a RoutingNotifier for multi-channel dispatch
|
|
73535
73585
|
*
|
|
73536
73586
|
* When `config.rulesConfigPath` is NOT set:
|
|
73537
73587
|
* - Returns the default notifier directly (backward-compatible)
|
|
73588
|
+
*
|
|
73589
|
+
* @throws {Error} at startup if a rule's route/escalate action references a
|
|
73590
|
+
* channel with no matching entry under `channels:` — an operator must fix
|
|
73591
|
+
* the rules config rather than have the alert silently fall back to the
|
|
73592
|
+
* default channel mid-incident.
|
|
73538
73593
|
*/
|
|
73539
73594
|
function createNotifier(config) {
|
|
73540
73595
|
const registry = buildNotifierRegistry(config);
|
|
@@ -73543,21 +73598,24 @@ function createNotifier(config) {
|
|
|
73543
73598
|
logger$3.debug("RULES_CONFIG_PATH not set — rule engine disabled, using default notifier");
|
|
73544
73599
|
return defaultNotifier;
|
|
73545
73600
|
}
|
|
73601
|
+
const yamlContent = (0, node_fs.readFileSync)(config.rulesConfigPath, "utf-8");
|
|
73602
|
+
const ruleConfig = parseRuleConfig(yamlContent);
|
|
73546
73603
|
const channelRegistry = new ChannelRegistry();
|
|
73547
73604
|
channelRegistry.setDefault(defaultNotifier);
|
|
73605
|
+
for (const [name, channelConfig] of Object.entries(ruleConfig.channels)) {
|
|
73606
|
+
channelRegistry.register(name, buildChannelNotifier(name, channelConfig, config));
|
|
73607
|
+
}
|
|
73548
73608
|
const unresolved = collectUnresolvedChannels(config, channelRegistry);
|
|
73549
73609
|
if (unresolved.length > 0) {
|
|
73550
|
-
|
|
73551
|
-
channels: unresolved,
|
|
73552
|
-
rulesConfigPath: config.rulesConfigPath
|
|
73553
|
-
}, "Rules reference channels with no registered notifier — these will be delivered to the default channel");
|
|
73610
|
+
throw new Error(`Rules reference undefined channels: ${unresolved.join(", ")}. Define them under ` + `"channels:" in ${config.rulesConfigPath}, or remove the reference.`);
|
|
73554
73611
|
}
|
|
73555
73612
|
return new RoutingNotifier(channelRegistry, defaultNotifier);
|
|
73556
73613
|
}
|
|
73557
73614
|
/**
|
|
73558
73615
|
* Collect the channel names referenced by Route/Escalate actions in the rules
|
|
73559
|
-
* config that have no notifier registered
|
|
73560
|
-
*
|
|
73616
|
+
* config that have no notifier registered against `registry` — i.e. no
|
|
73617
|
+
* matching entry under the rules YAML's `channels:` section. `createNotifier`
|
|
73618
|
+
* treats a non-empty result as a fail-fast startup error.
|
|
73561
73619
|
*
|
|
73562
73620
|
* Returns an empty list when no rules config is set.
|
|
73563
73621
|
*/
|
|
@@ -73568,7 +73626,7 @@ function collectUnresolvedChannels(config, registry) {
|
|
|
73568
73626
|
const yamlContent = (0, node_fs.readFileSync)(config.rulesConfigPath, "utf-8");
|
|
73569
73627
|
const ruleConfig = parseRuleConfig(yamlContent);
|
|
73570
73628
|
const referenced = new Set();
|
|
73571
|
-
for (const section of
|
|
73629
|
+
for (const section of [ruleConfig["pre-llm"], ruleConfig["post-llm"]]) {
|
|
73572
73630
|
for (const rule of section.rules) {
|
|
73573
73631
|
for (const action of rule.actions) {
|
|
73574
73632
|
if ("channel" in action) {
|
|
@@ -73612,6 +73670,7 @@ var NoopRollbackActionHandler = class {
|
|
|
73612
73670
|
endpointPath: request.endpointPath,
|
|
73613
73671
|
alertType: request.alertType,
|
|
73614
73672
|
urgencyLevel: request.urgencyLevel,
|
|
73673
|
+
probableCause: request.probableCause,
|
|
73615
73674
|
triggeredBy: request.triggeredBy,
|
|
73616
73675
|
correlationId: request.correlationId,
|
|
73617
73676
|
messageTs: request.messageTs
|
|
@@ -95154,6 +95213,7 @@ var src_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
|
95154
95213
|
AlertType: () => AlertType,
|
|
95155
95214
|
AlertmanagerPayloadSchema: () => AlertmanagerPayloadSchema,
|
|
95156
95215
|
CIRCUIT_BREAKER: () => CIRCUIT_BREAKER,
|
|
95216
|
+
ChannelConfigSchema: () => ChannelConfigSchema,
|
|
95157
95217
|
DEDUP_TTL_MS_MULTIPLIER: () => DEDUP_TTL_MS_MULTIPLIER,
|
|
95158
95218
|
DEV_SERVER_PORT: () => DEV_SERVER_PORT,
|
|
95159
95219
|
HOUR_MS: () => HOUR_MS,
|
|
@@ -95250,7 +95310,8 @@ const RollbackButtonValueSchema = object({
|
|
|
95250
95310
|
serviceName: string$1().min(1),
|
|
95251
95311
|
endpointPath: string$1().min(1),
|
|
95252
95312
|
alertType: nativeEnum(AlertType),
|
|
95253
|
-
urgencyLevel: _enum(URGENCY_LEVELS)
|
|
95313
|
+
urgencyLevel: _enum(URGENCY_LEVELS),
|
|
95314
|
+
probableCause: string$1().optional()
|
|
95254
95315
|
});
|
|
95255
95316
|
/**
|
|
95256
95317
|
* Parses and validates the JSON value encoded in the Slack rollback button.
|
|
@@ -95279,6 +95340,7 @@ function buildRollbackActionRequest(parsed, payload, correlationId) {
|
|
|
95279
95340
|
endpointPath: parsed.endpointPath,
|
|
95280
95341
|
alertType: parsed.alertType,
|
|
95281
95342
|
urgencyLevel: parsed.urgencyLevel,
|
|
95343
|
+
...parsed.probableCause !== undefined && { probableCause: parsed.probableCause },
|
|
95282
95344
|
triggeredBy: {
|
|
95283
95345
|
...payload.user?.id !== undefined && { id: payload.user.id },
|
|
95284
95346
|
...payload.user?.username !== undefined && { username: payload.user.username },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junando/webhook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "AWS Lambda webhook handler — receives and enqueues external alert payloads for Junando",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@aws-sdk/client-sqs": "^3.1127.0",
|
|
22
22
|
"zod": "^4.5.4",
|
|
23
|
-
"@junando/core": "0.
|
|
23
|
+
"@junando/core": "0.16.1"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/aws-lambda": "^8.10.163",
|