@junando/worker 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.
Files changed (2) hide show
  1. package/dist/handler.cjs +69 -10
  2. package/package.json +2 -2
package/dist/handler.cjs CHANGED
@@ -19669,6 +19669,12 @@ let RuleEvaluationPhase = /* @__PURE__ */ function(RuleEvaluationPhase) {
19669
19669
  RuleEvaluationPhase["PostLlm"] = "post-llm";
19670
19670
  return RuleEvaluationPhase;
19671
19671
  }({});
19672
+ /** Notifier backend a logical channel resolves to */
19673
+ let ChannelType = /* @__PURE__ */ function(ChannelType) {
19674
+ ChannelType["Slack"] = "slack";
19675
+ ChannelType["Teams"] = "teams";
19676
+ return ChannelType;
19677
+ }({});
19672
19678
  const AlertCountSchema = object({
19673
19679
  min: number$1().optional(),
19674
19680
  max: number$1().optional()
@@ -19718,9 +19724,19 @@ const RuleSchema = object({
19718
19724
  requiresRollback: boolean$1().optional()
19719
19725
  });
19720
19726
  const RuleSectionSchema = object({ rules: array(RuleSchema).default([]) });
19727
+ const SLACK_CHANNEL_SCHEMA = object({
19728
+ type: literal("slack"),
19729
+ channel: string$1().startsWith("#")
19730
+ });
19731
+ const TEAMS_CHANNEL_SCHEMA = object({
19732
+ type: literal("teams"),
19733
+ webhookUrlEnv: string$1().min(1)
19734
+ });
19735
+ const ChannelConfigSchema = discriminatedUnion("type", [SLACK_CHANNEL_SCHEMA, TEAMS_CHANNEL_SCHEMA]);
19721
19736
  const RuleConfigurationSchema = object({
19722
19737
  ["pre-llm"]: RuleSectionSchema,
19723
- ["post-llm"]: RuleSectionSchema
19738
+ ["post-llm"]: RuleSectionSchema,
19739
+ channels: record(string$1(), ChannelConfigSchema).default({})
19724
19740
  });
19725
19741
 
19726
19742
  //#endregion
@@ -74129,6 +74145,7 @@ function sanitizeEndpointPath(endpointPath) {
74129
74145
  if (!endpointPath) return "unknown";
74130
74146
  return endpointPath.replaceAll("`", "").slice(0, 200);
74131
74147
  }
74148
+ const ROLLBACK_PROBABLE_CAUSE_MAX_LEN = 500;
74132
74149
  var SlackNotifier = class {
74133
74150
  botToken;
74134
74151
  channel;
@@ -74244,7 +74261,8 @@ var SlackNotifier = class {
74244
74261
  serviceName: cluster.serviceName,
74245
74262
  endpointPath: cluster.endpointPath,
74246
74263
  alertType: cluster.alertType,
74247
- urgencyLevel: analysis.urgency_level
74264
+ urgencyLevel: analysis.urgency_level,
74265
+ probableCause: analysis.probable_cause.slice(0, ROLLBACK_PROBABLE_CAUSE_MAX_LEN)
74248
74266
  }),
74249
74267
  confirm: {
74250
74268
  title: {
@@ -81941,15 +81959,52 @@ function buildNotifierRegistry(config) {
81941
81959
  return registry;
81942
81960
  }
81943
81961
  /**
81962
+ * Builds the notifier for a single named channel entry from the rules YAML's
81963
+ * `channels:` section.
81964
+ *
81965
+ * @throws {Error} if the channel's backend cannot actually be reached (e.g.
81966
+ * `type: slack` with no SLACK_BOT_TOKEN configured, or `type: teams` whose
81967
+ * `webhookUrlEnv` is unset) — fails fast at startup rather than at delivery
81968
+ * time, mid-incident.
81969
+ */
81970
+ function buildChannelNotifier(name, channelConfig, config) {
81971
+ if (channelConfig.type === "slack") {
81972
+ if (!config.slackBotToken) {
81973
+ throw new Error(`Channel "${name}" (type: slack) requires SLACK_BOT_TOKEN to be set`);
81974
+ }
81975
+ return new SlackNotifier(config.slackBotToken, channelConfig.channel);
81976
+ }
81977
+ const webhookUrl = process.env[channelConfig.webhookUrlEnv];
81978
+ if (!webhookUrl) {
81979
+ throw new Error(`Channel "${name}" (type: teams) references env var "${channelConfig.webhookUrlEnv}", which is not set`);
81980
+ }
81981
+ let parsedUrl;
81982
+ try {
81983
+ parsedUrl = new URL(webhookUrl);
81984
+ } catch {
81985
+ throw new Error(`Channel "${name}": ${channelConfig.webhookUrlEnv} is not a valid URL`);
81986
+ }
81987
+ if (!parsedUrl.searchParams.has("api-version")) {
81988
+ throw new Error(`Channel "${name}": ${channelConfig.webhookUrlEnv} must include api-version= as a query parameter`);
81989
+ }
81990
+ return new TeamsNotifier(webhookUrl);
81991
+ }
81992
+ /**
81944
81993
  * Creates the notifier for the application.
81945
81994
  *
81946
81995
  * When `config.rulesConfigPath` is set:
81947
81996
  * - Reads and validates the rules YAML config
81948
- * - Creates a ChannelRegistry with the default notifier as fallback
81997
+ * - Builds a ChannelRegistry from its `channels:` section, with the default
81998
+ * notifier as fallback
81949
81999
  * - Wraps the default notifier with a RoutingNotifier for multi-channel dispatch
81950
82000
  *
81951
82001
  * When `config.rulesConfigPath` is NOT set:
81952
82002
  * - Returns the default notifier directly (backward-compatible)
82003
+ *
82004
+ * @throws {Error} at startup if a rule's route/escalate action references a
82005
+ * channel with no matching entry under `channels:` — an operator must fix
82006
+ * the rules config rather than have the alert silently fall back to the
82007
+ * default channel mid-incident.
81953
82008
  */
81954
82009
  function createNotifier(config) {
81955
82010
  const registry = buildNotifierRegistry(config);
@@ -81958,21 +82013,24 @@ function createNotifier(config) {
81958
82013
  logger$3.debug("RULES_CONFIG_PATH not set — rule engine disabled, using default notifier");
81959
82014
  return defaultNotifier;
81960
82015
  }
82016
+ const yamlContent = (0, node_fs.readFileSync)(config.rulesConfigPath, "utf-8");
82017
+ const ruleConfig = parseRuleConfig(yamlContent);
81961
82018
  const channelRegistry = new ChannelRegistry();
81962
82019
  channelRegistry.setDefault(defaultNotifier);
82020
+ for (const [name, channelConfig] of Object.entries(ruleConfig.channels)) {
82021
+ channelRegistry.register(name, buildChannelNotifier(name, channelConfig, config));
82022
+ }
81963
82023
  const unresolved = collectUnresolvedChannels(config, channelRegistry);
81964
82024
  if (unresolved.length > 0) {
81965
- logger$3.warn({
81966
- channels: unresolved,
81967
- rulesConfigPath: config.rulesConfigPath
81968
- }, "Rules reference channels with no registered notifier — these will be delivered to the default channel");
82025
+ throw new Error(`Rules reference undefined channels: ${unresolved.join(", ")}. Define them under ` + `"channels:" in ${config.rulesConfigPath}, or remove the reference.`);
81969
82026
  }
81970
82027
  return new RoutingNotifier(channelRegistry, defaultNotifier);
81971
82028
  }
81972
82029
  /**
81973
82030
  * Collect the channel names referenced by Route/Escalate actions in the rules
81974
- * config that have no notifier registered, and would therefore fall back to the
81975
- * default channel.
82031
+ * config that have no notifier registered against `registry` i.e. no
82032
+ * matching entry under the rules YAML's `channels:` section. `createNotifier`
82033
+ * treats a non-empty result as a fail-fast startup error.
81976
82034
  *
81977
82035
  * Returns an empty list when no rules config is set.
81978
82036
  */
@@ -81983,7 +82041,7 @@ function collectUnresolvedChannels(config, registry) {
81983
82041
  const yamlContent = (0, node_fs.readFileSync)(config.rulesConfigPath, "utf-8");
81984
82042
  const ruleConfig = parseRuleConfig(yamlContent);
81985
82043
  const referenced = new Set();
81986
- for (const section of Object.values(ruleConfig)) {
82044
+ for (const section of [ruleConfig["pre-llm"], ruleConfig["post-llm"]]) {
81987
82045
  for (const rule of section.rules) {
81988
82046
  for (const action of rule.actions) {
81989
82047
  if ("channel" in action) {
@@ -82027,6 +82085,7 @@ var NoopRollbackActionHandler = class {
82027
82085
  endpointPath: request.endpointPath,
82028
82086
  alertType: request.alertType,
82029
82087
  urgencyLevel: request.urgencyLevel,
82088
+ probableCause: request.probableCause,
82030
82089
  triggeredBy: request.triggeredBy,
82031
82090
  correlationId: request.correlationId,
82032
82091
  messageTs: request.messageTs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junando/worker",
3
- "version": "0.15.1",
3
+ "version": "0.16.1",
4
4
  "description": "AWS Lambda SQS worker — processes alert events and dispatches notifications for Junando",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "@aws-sdk/client-ssm": "^3.1127.0",
23
23
  "ioredis": "^6.0.0",
24
24
  "zod": "^4.5.4",
25
- "@junando/core": "0.15.1"
25
+ "@junando/core": "0.16.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/aws-lambda": "^8.10.163",