@junando/worker 0.12.3 → 0.13.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.
Files changed (2) hide show
  1. package/dist/handler.cjs +113 -86
  2. package/package.json +2 -2
package/dist/handler.cjs CHANGED
@@ -24309,6 +24309,59 @@ var WideEventBuilder = class {
24309
24309
  }
24310
24310
  };
24311
24311
 
24312
+ //#endregion
24313
+ //#region ../core/src/shared/logger/enums.ts
24314
+ /**
24315
+ * Pipeline component taxonomy.
24316
+ *
24317
+ * `component` (not `service`) distinguishes pipeline stages in wide events.
24318
+ * `service` stays constant ("junando"); `component` tells you WHERE the event
24319
+ * was emitted from.
24320
+ */
24321
+ const Component = {
24322
+ Webhook: "webhook",
24323
+ Worker: "worker",
24324
+ UseCase: "useCase",
24325
+ Llm: "llm",
24326
+ Notifier: "notifier",
24327
+ Dedup: "dedup",
24328
+ Traces: "traces",
24329
+ Ingest: "ingest",
24330
+ Rollback: "rollback"
24331
+ };
24332
+ /**
24333
+ * Pipeline stages that write their results into the WideEventBuilder.
24334
+ */
24335
+ const Stage = {
24336
+ Dedup: "dedup",
24337
+ RulesPre: "rulesPre",
24338
+ Traces: "traces",
24339
+ Llm: "llm",
24340
+ RulesPost: "rulesPost",
24341
+ Notify: "notify"
24342
+ };
24343
+ /**
24344
+ * Terminal outcomes for a wide event across all entry points.
24345
+ */
24346
+ const Outcome = {
24347
+ Success: "success",
24348
+ Suppressed: "suppressed",
24349
+ Degraded: "degraded",
24350
+ Error: "error",
24351
+ Accepted: "accepted",
24352
+ Empty: "empty",
24353
+ ParseError: "parse_error"
24354
+ };
24355
+ /**
24356
+ * Reason recorded for a tail-sampling decision.
24357
+ */
24358
+ const SamplingDecision$1 = {
24359
+ Error: "error",
24360
+ Slow: "slow",
24361
+ Random: "random",
24362
+ Skipped: "skipped"
24363
+ };
24364
+
24312
24365
  //#endregion
24313
24366
  //#region ../core/src/shared/logger/sampling.ts
24314
24367
  /** Events slower than this (ms) are always sampled. */
@@ -24328,6 +24381,9 @@ function shouldSample(event) {
24328
24381
  if (event.durationMs !== undefined && event.durationMs > 1e4) {
24329
24382
  return true;
24330
24383
  }
24384
+ if (event.outcome === Outcome.Degraded) {
24385
+ return true;
24386
+ }
24331
24387
  return Math.random() < NORMAL_SAMPLE_RATE;
24332
24388
  }
24333
24389
 
@@ -24416,59 +24472,6 @@ function redact(obj) {
24416
24472
  }));
24417
24473
  }
24418
24474
 
24419
- //#endregion
24420
- //#region ../core/src/shared/logger/enums.ts
24421
- /**
24422
- * Pipeline component taxonomy.
24423
- *
24424
- * `component` (not `service`) distinguishes pipeline stages in wide events.
24425
- * `service` stays constant ("junando"); `component` tells you WHERE the event
24426
- * was emitted from.
24427
- */
24428
- const Component = {
24429
- Webhook: "webhook",
24430
- Worker: "worker",
24431
- UseCase: "useCase",
24432
- Llm: "llm",
24433
- Notifier: "notifier",
24434
- Dedup: "dedup",
24435
- Traces: "traces",
24436
- Ingest: "ingest",
24437
- Rollback: "rollback"
24438
- };
24439
- /**
24440
- * Pipeline stages that write their results into the WideEventBuilder.
24441
- */
24442
- const Stage = {
24443
- Dedup: "dedup",
24444
- RulesPre: "rulesPre",
24445
- Traces: "traces",
24446
- Llm: "llm",
24447
- RulesPost: "rulesPost",
24448
- Notify: "notify"
24449
- };
24450
- /**
24451
- * Terminal outcomes for a wide event across all entry points.
24452
- */
24453
- const Outcome = {
24454
- Success: "success",
24455
- Suppressed: "suppressed",
24456
- Degraded: "degraded",
24457
- Error: "error",
24458
- Accepted: "accepted",
24459
- Empty: "empty",
24460
- ParseError: "parse_error"
24461
- };
24462
- /**
24463
- * Reason recorded for a tail-sampling decision.
24464
- */
24465
- const SamplingDecision$1 = {
24466
- Error: "error",
24467
- Slow: "slow",
24468
- Random: "random",
24469
- Skipped: "skipped"
24470
- };
24471
-
24472
24475
  //#endregion
24473
24476
  //#region ../core/src/shared/logger/index.ts
24474
24477
  let _root = buildLogger({});
@@ -29393,12 +29396,14 @@ function toErrorSection(err) {
29393
29396
  }
29394
29397
  /**
29395
29398
  * Terminal outcome for a processed cluster. Early returns — no switch/case.
29396
- * Notify failure is fatal (the batch is retried via SQS); LLM failure is
29397
- * degraded (notification still went out without a diagnosis).
29399
+ * Notify failure is fatal (the batch is retried via SQS); LLM transport
29400
+ * failure and a parse-degraded diagnosis both leave the cluster degraded
29401
+ * (notification still went out, with or without a diagnosis).
29398
29402
  */
29399
- function resolveOutcome({ llmError, notifyError }) {
29403
+ function resolveOutcome({ llmError, notifyError, llmDegradedReason }) {
29400
29404
  if (notifyError != null) return Outcome.Error;
29401
29405
  if (llmError != null) return Outcome.Degraded;
29406
+ if (llmDegradedReason != null) return Outcome.Degraded;
29402
29407
  return Outcome.Success;
29403
29408
  }
29404
29409
  var ProcessIncidentUseCase = class {
@@ -29473,14 +29478,17 @@ var ProcessIncidentUseCase = class {
29473
29478
  });
29474
29479
  let analysis = null;
29475
29480
  let llmError = null;
29481
+ let llmDegradedReason = null;
29476
29482
  try {
29477
29483
  const llmResult = await llm.analyze(cluster, allSpans);
29478
29484
  analysis = llmResult.analysis;
29485
+ llmDegradedReason = llmResult.degradedReason ?? null;
29479
29486
  builder.set("llm", {
29480
29487
  provider: llmResult.provider,
29481
29488
  model: llmResult.model,
29482
29489
  latencyMs: llmResult.latencyMs,
29483
- urgency: llmResult.analysis.urgency_level,
29490
+ ...llmResult.analysis && { urgency: llmResult.analysis.urgency_level },
29491
+ ...llmResult.degradedReason && { degradedReason: llmResult.degradedReason },
29484
29492
  tokens: llmResult.promptTokens + llmResult.completionTokens
29485
29493
  });
29486
29494
  } catch (err) {
@@ -29529,7 +29537,8 @@ var ProcessIncidentUseCase = class {
29529
29537
  }
29530
29538
  this.emit(builder, resolveOutcome({
29531
29539
  llmError,
29532
- notifyError: null
29540
+ notifyError: null,
29541
+ llmDegradedReason
29533
29542
  }), clusterStartMs);
29534
29543
  }
29535
29544
  }
@@ -30893,21 +30902,25 @@ function buildUserPrompt(cluster, traces) {
30893
30902
  }
30894
30903
  /**
30895
30904
  * Extracts LLMAnalysis from raw LLM response text.
30896
- * Uses multi-stage parsing: JSON → regex fallback heuristics.
30897
- * Returns validated LLMAnalysis or falls back to default values.
30905
+ * Two-stage parsing: JSON → regex fallback. Never fabricates a diagnosis —
30906
+ * returns null when neither stage produces a usable analysis.
30898
30907
  */
30899
30908
  function parseAnalysis(raw, correlationId) {
30900
30909
  const startIdx = raw.indexOf("{");
30901
30910
  const endIdx = raw.lastIndexOf("}");
30911
+ let stage1Succeeded = false;
30902
30912
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
30903
30913
  try {
30904
- return LLMAnalysisSchema.parse(JSON.parse(raw.slice(startIdx, endIdx + 1)));
30905
- } catch {
30906
- logger$6.warn({
30907
- rawResponse: raw.slice(0, 500),
30908
- correlationId
30909
- }, "llm:parse:failed");
30910
- }
30914
+ const analysis = LLMAnalysisSchema.parse(JSON.parse(raw.slice(startIdx, endIdx + 1)));
30915
+ stage1Succeeded = true;
30916
+ return analysis;
30917
+ } catch {}
30918
+ }
30919
+ if (!stage1Succeeded) {
30920
+ logger$6.warn({
30921
+ rawResponse: raw.slice(0, 500),
30922
+ correlationId
30923
+ }, "llm:parse:failed");
30911
30924
  }
30912
30925
  const probableCauseMatch = RE_PROBABLE_CAUSE.exec(raw);
30913
30926
  const urgencyMatch = RE_URGENCY_LEVEL.exec(raw);
@@ -30926,20 +30939,34 @@ function parseAnalysis(raw, correlationId) {
30926
30939
  urgency_level: urgency,
30927
30940
  requires_rollback: rollbackMatch?.[1] === "true"
30928
30941
  };
30929
- return LLMAnalysisSchema.parse(analysis);
30930
- }
30931
- const lowerRaw = raw.toLowerCase();
30932
- let urgencyLevel = "medium";
30933
- if (lowerRaw.includes("critical") || lowerRaw.includes("severity 1")) urgencyLevel = "critical";
30934
- else if (lowerRaw.includes("high") || lowerRaw.includes("severity 2")) urgencyLevel = "high";
30935
- else if (lowerRaw.includes("low")) urgencyLevel = "low";
30936
- return LLMAnalysisSchema.parse({
30937
- probable_cause: "Analysis in progress - check logs for details",
30938
- impacted_services: ["unknown-service"],
30939
- recommended_steps: ["Review incident details in logs"],
30940
- urgency_level: urgencyLevel,
30941
- requires_rollback: lowerRaw.includes("rollback") || lowerRaw.includes("revert")
30942
- });
30942
+ const parsed = LLMAnalysisSchema.parse(analysis);
30943
+ logger$6.warn({
30944
+ matchedFields: ["probable_cause", "urgency_level"],
30945
+ correlationId
30946
+ }, "llm:parse:partial");
30947
+ return parsed;
30948
+ }
30949
+ logger$6.warn({ correlationId }, "llm:parse:unusable");
30950
+ return null;
30951
+ }
30952
+ /**
30953
+ * Shared entry point for turning provider text into a diagnosis (or a
30954
+ * degradedReason explaining why not). Detects empty responses once, here,
30955
+ * before delegating to parseAnalysis.
30956
+ */
30957
+ function parseLlmText(raw, correlationId) {
30958
+ if (raw.trim() === "") {
30959
+ logger$6.warn({ correlationId }, "llm:parse:empty");
30960
+ return {
30961
+ analysis: null,
30962
+ degradedReason: "empty_response"
30963
+ };
30964
+ }
30965
+ const analysis = parseAnalysis(raw, correlationId);
30966
+ return analysis ? { analysis } : {
30967
+ analysis: null,
30968
+ degradedReason: "unparseable_response"
30969
+ };
30943
30970
  }
30944
30971
  /**
30945
30972
  * Gemini LLM provider using Google Generative AI SDK.
@@ -30981,7 +31008,7 @@ var GeminiProvider = class {
30981
31008
  const result = await gemini.generateContent(buildUserPrompt(cluster, traces));
30982
31009
  const usage = result.response.usageMetadata;
30983
31010
  return {
30984
- analysis: parseAnalysis(result.response.text()),
31011
+ ...parseLlmText(result.response.text()),
30985
31012
  promptTokens: usage?.promptTokenCount ?? 0,
30986
31013
  completionTokens: usage?.candidatesTokenCount ?? 0
30987
31014
  };
@@ -31013,7 +31040,7 @@ var ClaudeProvider = class {
31013
31040
  });
31014
31041
  const text = message.content.find((b) => b.type === "text")?.text ?? "";
31015
31042
  return {
31016
- analysis: parseAnalysis(text),
31043
+ ...parseLlmText(text),
31017
31044
  provider: "claude",
31018
31045
  model: this.model,
31019
31046
  latencyMs: Date.now() - startMs,
@@ -31133,7 +31160,7 @@ var OpenRouterProvider = class {
31133
31160
  }, "llm:validation:failed");
31134
31161
  }
31135
31162
  const text = parsed.success ? parsed.data.choices?.[0]?.message?.content ?? "" : "";
31136
- const analysis = parseAnalysis(text, correlationId);
31163
+ const parsedAnalysis = parseLlmText(text, correlationId);
31137
31164
  const usage = parsed.success ? parsed.data.usage : undefined;
31138
31165
  if (usage) {
31139
31166
  const { prompt_tokens, completion_tokens, total_tokens } = usage;
@@ -31151,7 +31178,7 @@ var OpenRouterProvider = class {
31151
31178
  llmInferenceTotal.inc({ status: "success" });
31152
31179
  llmInferenceDuration.observe({ model: this.model }, latencyMs / 1e3);
31153
31180
  return {
31154
- analysis,
31181
+ ...parsedAnalysis,
31155
31182
  provider: this.providerName,
31156
31183
  model: this.model,
31157
31184
  latencyMs,
@@ -31203,7 +31230,7 @@ var OpenRouterProvider = class {
31203
31230
  const text = parsed.success ? parsed.data.choices?.[0]?.message?.content ?? "" : "";
31204
31231
  const usage = parsed.success ? parsed.data.usage : undefined;
31205
31232
  return {
31206
- analysis: parseAnalysis(text, correlationId),
31233
+ ...parseLlmText(text, correlationId),
31207
31234
  provider: this.providerName,
31208
31235
  model: toModel,
31209
31236
  latencyMs: Date.now() - startMs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junando/worker",
3
- "version": "0.12.3",
3
+ "version": "0.13.0",
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.12.3"
25
+ "@junando/core": "0.13.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/aws-lambda": "^8.10.163",