@junando/core 0.11.1 → 0.12.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/dist/index.d.ts +65 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +435 -112
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["logger","logger","logger","parseYaml"],"sources":["../src/shared/constants.ts","../src/domain/entities/alert.ts","../src/domain/entities/cluster.ts","../src/domain/entities/incident.ts","../src/domain/entities/rule.ts","../src/domain/value-objects/fingerprint.ts","../src/domain/services/clustering.service.ts","../src/application/dtos/normalize-payload.ts","../src/application/use-cases/process-incident.use-case.ts","../src/shared/logger/loki-transport.ts","../src/shared/logger/index.ts","../src/infrastructure/dedup/redis-dedup.adapter.ts","../src/infrastructure/indexer/opensearch.adapter.ts","../src/infrastructure/llm/llm.adapter.ts","../src/shared/factory-registry.ts","../src/infrastructure/notifier/slack.adapter.ts","../src/infrastructure/notifier/teams.adapter.ts","../src/infrastructure/notifier/routing-notifier.ts","../src/infrastructure/rules/yaml-rule-loader.ts","../src/infrastructure/rules/channel-registry.ts","../src/infrastructure/rules/condition-evaluator.ts","../src/infrastructure/rules/action-dispatcher.ts","../src/infrastructure/rules/rule-engine.ts","../src/infrastructure/notifier/factory.ts","../src/infrastructure/queue/sqs.adapter.ts","../src/infrastructure/queue/sqs-lag-poller.ts","../src/infrastructure/traces/loki-trace.adapter.ts","../src/shared/config/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// constants.ts — Centralized constants and enums.\n// No magic numbers. No hardcoded strings. Everything typed and named.\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ── Alert Types (domain enum) ──────────────────────────────────────────────────\nexport enum AlertType {\n Error = 'http_500',\n Warning = 'latency_spike',\n Success = 'recovery',\n}\n\ninterface AlertTypeConfig {\n readonly alertName: string;\n readonly severity: string;\n readonly summary: (service: string, i: number, count: number) => string;\n}\n\nconst _alertTypeConfigs: Record<AlertType, AlertTypeConfig> = {\n [AlertType.Error]: {\n alertName: 'HighErrorRate',\n severity: 'critical',\n summary: (service, i, count) => `High error rate on ${service} — alert ${i + 1}/${count}`,\n },\n [AlertType.Warning]: {\n alertName: 'HighLatency',\n severity: 'warning',\n summary: (service, i, count) => `High latency detected on ${service} — alert ${i + 1}/${count}`,\n },\n [AlertType.Success]: {\n alertName: 'ServiceRecovered',\n severity: 'info',\n summary: (service, i, count) =>\n `Service ${service} has recovered and is operating normally — alert ${i + 1}/${count}`,\n },\n};\n\nexport const ALERT_TYPE_LABELS: Readonly<typeof _alertTypeConfigs> =\n Object.freeze(_alertTypeConfigs);\n\n// ── LLM Provider ───────────────────────────────────────────────────────────────\nexport enum LLMProviderType {\n Gemini = 'gemini',\n Claude = 'claude',\n OpenRouter = 'openrouter',\n Qwen = 'qwen',\n}\n\n// ── HTTP / Timeout Constants ───────────────────────────────────────────────────\nexport const HTTP_TIMEOUT_MS = Object.freeze({\n Default: 5_000,\n LLM: 30_000,\n});\n\nexport const CIRCUIT_BREAKER = Object.freeze({\n Timeout: 10_000,\n ErrorThresholdPercentage: 70,\n ResetTimeoutMs: 30_000,\n});\n\nexport const LLM_MAX_TOKENS = 1_024;\n\n// ── Rate Limiter Constants ─────────────────────────────────────────────────────\nexport const RATE_LIMITER = Object.freeze({\n MinTimeMs: 100,\n MaxConcurrent: 5,\n});\n\n// ── Dev Server ────────────────────────────────────────────────────────────────\nexport const DEV_SERVER_PORT = 4_000;\n\n// ── Deduplication ─────────────────────────────────────────────────────────────\nexport const DEDUP_TTL_MS_MULTIPLIER = 1_000;\n\n// ── Time Conversions ───────────────────────────────────────────────────────────\nexport const HOUR_MS = 3_600_000;\n\n// ── LLM Fallback Defaults ─────────────────────────────────────────────────\nexport const LLM_FALLBACK_DEFAULTS = Object.freeze({\n TimeoutMs: 60_000,\n Models: [\n 'google/gemma-4-31b-it:free',\n 'meta-llama/llama-3.3-70b-instruct:free',\n 'mistralai/mistral-7b-instruct:free',\n ] as string[],\n});\n\n// ── LLM Models ────────────────────────────────────────────────────────────────\nexport const LLM_MODELS = Object.freeze({\n Gemini: 'gemini-2.0-flash',\n Claude: 'claude-haiku-4-5',\n OpenRouter: 'qwen/qwen-2.5-72b-instruct',\n});\n\n// ── Slack ─────────────────────────────────────────────────────────────────────\nexport const SLACK_API_URL = 'https://slack.com/api/chat.postMessage';\n\n// ── Teams ─────────────────────────────────────────────────────────────────────\nexport const TEAMS_WEBHOOK_TIMEOUT_MS = 10_000;\n\nconst _urgencyEmoji: Record<string, string> = {\n critical: '🔴',\n high: '🟠',\n medium: '🟡',\n low: '🟢',\n};\nexport const URGENCY_EMOJI: Readonly<typeof _urgencyEmoji> = Object.freeze(_urgencyEmoji);\n\n// ── Redis Keys ────────────────────────────────────────────────────────────────\nexport const REDIS_KEY_PREFIX = 'junando:dedup:';\n\n// ── Webhook Defaults ──────────────────────────────────────────────────────────\nexport const WEBHOOK_DEFAULTS = Object.freeze({\n AlertmanagerUrl: 'http://localhost:9093',\n WebhookUrl: 'http://localhost:4000/webhook/alert',\n});\n\nexport const PAYLOAD_DEFAULTS = Object.freeze({\n Version: '4',\n TruncatedAlerts: 0,\n Receiver: 'junando',\n});\n","import { z } from 'zod';\nimport { AlertType } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Alert — the core domain entity.\n// Represents a single normalized alert from any source (Alertmanager, etc.)\n// The domain doesn't care where it came from — only what it means.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const AlertStatusSchema = z.enum(['firing', 'resolved']);\n\nexport const NormalizedAlertSchema = z.object({\n fingerprint: z.string(),\n alertName: z.string(),\n status: AlertStatusSchema,\n serviceName: z.string(),\n alertType: z.nativeEnum(AlertType),\n endpointPath: z.string(),\n traceId: z.string().optional(),\n startsAt: z.string().datetime(),\n latencyMs: z.number().optional(),\n labels: z.record(z.string(), z.string()),\n annotations: z.record(z.string(), z.string()),\n});\n\n// Raw Alertmanager webhook payload — validated at the boundary (Lambda A)\n// then normalized into NormalizedAlert before entering the domain.\nexport const AlertmanagerPayloadSchema = z.object({\n version: z.string().default('4'),\n groupKey: z.string(),\n truncatedAlerts: z.number().default(0),\n status: AlertStatusSchema,\n receiver: z.string(),\n groupLabels: z.record(z.string(), z.string()),\n commonLabels: z.record(z.string(), z.string()),\n commonAnnotations: z.record(z.string(), z.string()),\n externalURL: z.string().url(),\n alerts: z\n .array(\n z.object({\n status: AlertStatusSchema,\n labels: z.record(z.string(), z.string()),\n annotations: z.record(z.string(), z.string()).default({}),\n startsAt: z.string().datetime(),\n endsAt: z.string().datetime(),\n fingerprint: z.string().optional(),\n }),\n )\n .min(1),\n});\n\nexport type AlertStatus = z.infer<typeof AlertStatusSchema>;\nexport type NormalizedAlert = z.infer<typeof NormalizedAlertSchema>;\nexport type AlertmanagerPayload = z.infer<typeof AlertmanagerPayloadSchema>;\n\n// Backward-compat alias — errorType is now alertType\nexport type AlertErrorType = NormalizedAlert['alertType'];\n","import { z } from 'zod';\nimport { AlertType } from '../../shared/constants.js';\n\n/**\n * Severity level enum values — defined inline to avoid circular dependency\n * (cluster.ts → rule.ts → incident.ts → cluster.ts).\n * Must stay in sync with SeverityLevel in domain/entities/rule.ts.\n */\nconst SEVERITY_VALUES = ['critical', 'high', 'medium', 'low'] as const;\n\nexport const AlertClusterSchema = z.object({\n fingerprint: z.string(),\n serviceName: z.string(),\n alertType: z.nativeEnum(AlertType), // typed, not raw string\n endpointPath: z.string(),\n alertCount: z.number().int().positive(),\n representativeTraceIds: z.array(z.string()).max(2),\n firstSeenAt: z.string().datetime(),\n latencyP99Ms: z.number().optional(),\n /** Severity level — can be derived from ALERT_TYPE_LABELS[alertType].severity */\n severity: z.enum(SEVERITY_VALUES).optional(),\n /** Arbitrary key-value labels passed through from alerts */\n labels: z.record(z.string(), z.string()).optional(),\n});\n\nexport type AlertCluster = z.infer<typeof AlertClusterSchema>;\n","import { z } from 'zod';\nimport { AlertClusterSchema } from './cluster.js';\n\nexport const UrgencyLevelSchema = z.enum(['low', 'medium', 'high', 'critical']);\n\n// Strict JSON schema the LLM MUST return. Validated with Zod after parsing.\nexport const LLMAnalysisSchema = z.object({\n probable_cause: z.string().min(1),\n impacted_services: z.array(z.string()).min(1),\n recommended_steps: z.array(z.string()).min(1).max(5),\n urgency_level: UrgencyLevelSchema,\n requires_rollback: z.boolean(),\n});\n\nexport const IncidentSchema = z.object({\n cluster: AlertClusterSchema,\n traces: z.array(z.record(z.string(), z.unknown())).optional(),\n analysis: LLMAnalysisSchema.optional(), // absent if LLM failed gracefully\n processedAt: z.string().datetime(),\n});\n\nexport type UrgencyLevel = z.infer<typeof UrgencyLevelSchema>;\nexport type LLMAnalysis = z.infer<typeof LLMAnalysisSchema>;\nexport type Incident = z.infer<typeof IncidentSchema>;\n","import { z } from 'zod';\nimport { AlertType } from '../../shared/constants.js';\nimport { UrgencyLevelSchema } from './incident.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Enums — single source of truth for repeated values\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum RuleActionType {\n Suppress = 'suppress',\n Route = 'route',\n Escalate = 'escalate',\n Tag = 'tag',\n}\n\nexport enum SeverityLevel {\n Critical = 'critical',\n High = 'high',\n Medium = 'medium',\n Low = 'low',\n}\n\n/** Rule evaluation points in the pipeline */\nexport enum RuleEvaluationPhase {\n PreLlm = 'pre-llm',\n PostLlm = 'post-llm',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleCondition — what can be matched in a rule\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RuleCondition {\n serviceName?: string;\n alertType?: AlertType;\n severity?: SeverityLevel;\n labels?: Record<string, string>;\n endpointPath?: string;\n alertCount?: { min?: number; max?: number };\n latencyP99Ms?: { min?: number; max?: number };\n /** POST-LLM only (analysis must be present) */\n urgencyLevel?: z.infer<typeof UrgencyLevelSchema>;\n requiresRollback?: boolean;\n impactedServices?: string[];\n}\n\nconst AlertCountSchema = z.object({\n min: z.number().optional(),\n max: z.number().optional(),\n});\n\nconst LatencySchema = z.object({\n min: z.number().optional(),\n max: z.number().optional(),\n});\n\nexport const RuleConditionSchema = z.object({\n serviceName: z.string().optional(),\n alertType: z.nativeEnum(AlertType).optional(),\n severity: z.nativeEnum(SeverityLevel).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n endpointPath: z.string().optional(),\n alertCount: AlertCountSchema.optional(),\n latencyP99Ms: LatencySchema.optional(),\n urgencyLevel: UrgencyLevelSchema.optional(),\n requiresRollback: z.boolean().optional(),\n impactedServices: z.array(z.string()).optional(),\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleAction — discriminated union\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type RuleAction =\n | { type: RuleActionType.Suppress }\n | { type: RuleActionType.Route; channel: string }\n | { type: RuleActionType.Escalate; channel: string }\n | { type: RuleActionType.Tag; key: string; value: string };\n\nconst SUPPRESS_SCHEMA = z.object({ type: z.literal(RuleActionType.Suppress) });\nconst ROUTE_SCHEMA = z.object({ type: z.literal(RuleActionType.Route), channel: z.string().min(1) });\nconst ESCALATE_SCHEMA = z.object({ type: z.literal(RuleActionType.Escalate), channel: z.string().min(1) });\nconst TAG_SCHEMA = z.object({ type: z.literal(RuleActionType.Tag), key: z.string().min(1), value: z.string().min(1) });\n\nexport const RuleActionSchema = z.discriminatedUnion('type', [\n SUPPRESS_SCHEMA,\n ROUTE_SCHEMA,\n ESCALATE_SCHEMA,\n TAG_SCHEMA,\n]);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Rule — single rule definition\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Rule {\n id: string;\n name?: string;\n condition: RuleCondition;\n actions: RuleAction[];\n /** POST-LLM only (can only be used in post-llm section) */\n urgencyLevel?: z.infer<typeof UrgencyLevelSchema>;\n requiresRollback?: boolean;\n}\n\nexport const RuleSchema = z.object({\n id: z.string().min(1),\n name: z.string().optional(),\n condition: RuleConditionSchema,\n actions: z.array(RuleActionSchema).min(1),\n urgencyLevel: UrgencyLevelSchema.optional(),\n requiresRollback: z.boolean().optional(),\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleSection — pre-llm or post-llm rules\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RuleSection {\n rules: Rule[];\n}\n\nexport const RuleSectionSchema = z.object({\n rules: z.array(RuleSchema).default([]),\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleConfiguration — full YAML shape\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RuleConfiguration {\n [RuleEvaluationPhase.PreLlm]: RuleSection;\n [RuleEvaluationPhase.PostLlm]: RuleSection;\n}\n\nexport const RuleConfigurationSchema = z.object({\n [RuleEvaluationPhase.PreLlm]: RuleSectionSchema,\n [RuleEvaluationPhase.PostLlm]: RuleSectionSchema,\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Validated types (from Zod schemas)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ValidatedRuleCondition = z.infer<typeof RuleConditionSchema>;\nexport type ValidatedRuleAction = z.infer<typeof RuleActionSchema>;\nexport type ValidatedRule = z.infer<typeof RuleSchema>;\nexport type ValidatedRuleSection = z.infer<typeof RuleSectionSchema>;\nexport type ValidatedRuleConfiguration = z.infer<typeof RuleConfigurationSchema>;\n","import { createHash } from 'node:crypto';\nimport type { NormalizedAlert } from '../entities/alert.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fingerprint — a Value Object in DDD terms.\n// Immutable, identity based on value not reference.\n// Encapsulates the hashing algorithm so it's swappable in one place.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class Fingerprint {\n private constructor(readonly value: string) {}\n\n static fromAlert(alert: NormalizedAlert): Fingerprint {\n const input = [\n alert.serviceName.toLowerCase().trim(),\n alert.alertType.toLowerCase().trim(),\n alert.endpointPath.toLowerCase().trim(),\n ].join('|');\n\n const hash = createHash('sha256').update(input).digest('hex');\n return new Fingerprint(hash);\n }\n\n equals(other: Fingerprint): boolean {\n return this.value === other.value;\n }\n\n toString(): string {\n return this.value;\n }\n}\n","import type { NormalizedAlert } from '../entities/alert.js';\nimport type { AlertCluster } from '../entities/cluster.js';\nimport { Fingerprint } from '../value-objects/fingerprint.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ClusteringService — Domain Service.\n// Pure business logic. No I/O, no external deps.\n// Groups alerts and selects representative samples.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ClusteringService {\n /**\n * Groups alerts by fingerprint and builds AlertCluster objects.\n * 300 alerts with the same root cause → 1 cluster with 2 representative traces.\n */\n buildClusters(alerts: NormalizedAlert[]): AlertCluster[] {\n const groups = new Map<string, NormalizedAlert[]>();\n\n for (const alert of alerts) {\n const fp = Fingerprint.fromAlert(alert).toString();\n const group = groups.get(fp) ?? [];\n group.push(alert);\n groups.set(fp, group);\n }\n\n return Array.from(groups.entries()).map(([fp, group]) => this.buildCluster(fp, group));\n }\n\n private buildCluster(fingerprint: string, alerts: NormalizedAlert[]): AlertCluster {\n const sorted = [...alerts].sort(\n (a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),\n );\n\n const first = sorted[0];\n if (!first) throw new Error('Empty alert group — this should never happen');\n const traceIds = this.sampleTraceIds(alerts);\n const latencies = alerts.map((a) => a.latencyMs ?? 0);\n const sortedLatencies = [...latencies].sort((a, b) => a - b);\n const p99Index = Math.floor(sortedLatencies.length * 0.99);\n const p99 = sortedLatencies[Math.min(p99Index, sortedLatencies.length - 1)] ?? 0;\n\n return {\n fingerprint,\n serviceName: first.serviceName,\n alertType: first.alertType,\n endpointPath: first.endpointPath,\n alertCount: alerts.length,\n representativeTraceIds: traceIds,\n firstSeenAt: first.startsAt,\n latencyP99Ms: p99,\n };\n }\n\n private sampleTraceIds(alerts: NormalizedAlert[]): string[] {\n const withTraces = alerts.filter(\n (a): a is NormalizedAlert & { traceId: string } =>\n typeof a.traceId === 'string' && a.traceId.length > 0,\n );\n if (withTraces.length === 0) return [];\n\n const sorted = [...withTraces].sort(\n (a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),\n );\n const first = sorted[0];\n if (!first) throw new Error('Empty alert group with traces — this should never happen');\n const slowest = withTraces.reduce(\n (max, a) => ((a.latencyMs ?? 0) > (max.latencyMs ?? 0) ? a : max),\n first,\n );\n\n return slowest.traceId === first.traceId ? [first.traceId] : [first.traceId, slowest.traceId];\n }\n}\n","import type { AlertmanagerPayload, NormalizedAlert } from '../../domain/entities/alert.js';\nimport { AlertType } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// normalizePayload()\n// Maps the raw Alertmanager webhook payload → domain NormalizedAlert[].\n// This is the anti-corruption layer: external format never leaks into domain.\n// If Alertmanager changes its payload shape, only this file needs updating.\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst ERROR_TYPE_TO_ALERT_TYPE: Record<string, AlertType> = {\n http_500: AlertType.Error,\n latency_spike: AlertType.Warning,\n recovery: AlertType.Success,\n};\n\nfunction toAlertType(raw: string): AlertType {\n return ERROR_TYPE_TO_ALERT_TYPE[raw] ?? AlertType.Error;\n}\n\nexport function normalizePayload(payload: AlertmanagerPayload): NormalizedAlert[] {\n return payload.alerts\n .filter((a) => a.status === 'firing') // ignore resolved alerts in MVP\n .map(\n (a): NormalizedAlert => ({\n fingerprint:\n a.fingerprint ?? `${a.labels['alertname']}-${a.labels['service']}-${Date.now()}`,\n alertName: a.labels['alertname'] ?? 'unknown',\n status: a.status,\n serviceName: a.labels['service'] ?? a.labels['job'] ?? 'unknown-service',\n alertType: toAlertType(a.labels['error_type'] ?? a.labels['alertname'] ?? ''),\n endpointPath: a.labels['endpoint'] ?? a.annotations['endpoint'] ?? '/',\n traceId: a.labels['trace_id'] ?? a.annotations['trace_id'],\n startsAt: a.startsAt,\n latencyMs: a.labels['latency_ms'] ? Number(a.labels['latency_ms']) : undefined,\n labels: a.labels,\n annotations: a.annotations,\n }),\n );\n}\n","import type { NormalizedAlert } from '../../domain/entities/alert.js';\nimport type {\n IDeduplicationStore,\n ILLMProvider,\n INotifier,\n IRuleEngine,\n ITraceRepository,\n} from '../../domain/ports/index.js';\nimport { ClusteringService } from '../../domain/services/clustering.service.js';\nimport type { Logger } from '../../shared/logger/index.js';\nimport { dedupNew, dedupDuplicate, suppressedClusters } from '../../shared/metrics/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ProcessIncidentUseCase — Application layer.\n// Orchestrates the full pipeline using only domain interfaces (ports).\n// Never imports a concrete infrastructure class directly.\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface Dependencies {\n dedup: IDeduplicationStore;\n traces: ITraceRepository;\n llm: ILLMProvider;\n notifier: INotifier;\n logger: Logger;\n dedupTtlSeconds: number;\n clustering?: ClusteringService;\n onClustersBuilt?: (count: number) => void;\n ruleEngine?: IRuleEngine;\n}\n\nexport class ProcessIncidentUseCase {\n private readonly clustering: ClusteringService;\n\n constructor(private readonly deps: Dependencies) {\n this.clustering = deps.clustering ?? new ClusteringService();\n }\n\n async execute(alerts: NormalizedAlert[], correlationId: string): Promise<void> {\n const { dedup, traces, llm, notifier, logger, dedupTtlSeconds, ruleEngine } = this.deps;\n const log = logger.child({ correlationId, useCase: 'ProcessIncident' });\n\n log.info({ alertCount: alerts.length }, 'Processing alert batch');\n\n // 1. Cluster alerts by fingerprint\n const clusters = this.clustering.buildClusters(alerts);\n log.info({ clusterCount: clusters.length }, 'Clusters built');\n this.deps.onClustersBuilt?.(clusters.length);\n\n for (const cluster of clusters) {\n const log2 = log.child({ fingerprint: cluster.fingerprint, service: cluster.serviceName });\n\n // 2. Deduplicate — skip if seen recently\n const isNew = await dedup.isNew(cluster.fingerprint, dedupTtlSeconds);\n if (!isNew) {\n log2.debug('Duplicate cluster — skipping');\n dedupDuplicate.inc({ source: 'alertmanager' });\n continue;\n }\n dedupNew.inc({ source: 'alertmanager' });\n\n // 3. PRE-LLM rule engine hook — evaluate rules before LLM\n // ────────────────────────────────────────────────────────────────────\n let preLlmRouteChannels: string[] = [];\n let preLlmEscalateChannels: string[] = [];\n\n if (ruleEngine) {\n const preResult = ruleEngine.evaluatePreLlm(cluster);\n\n if (preResult.suppressed) {\n log2.info({ matchedRuleId: preResult.matchedRuleId }, 'Cluster suppressed by rule engine');\n if (preResult.matchedRuleId) {\n suppressedClusters.inc({ rule_id: preResult.matchedRuleId });\n }\n continue; // Skip LLM, traces, and notification entirely\n }\n\n // Collect route and escalate channels from PRE-LLM actions\n for (const action of preResult.actions) {\n if (action.type === 'route' && 'channel' in action) {\n preLlmRouteChannels.push(action.channel);\n }\n if (action.type === 'escalate' && 'channel' in action) {\n preLlmEscalateChannels.push(action.channel);\n }\n }\n }\n\n // 4. Extract representative traces from the trace repository\n const spanLists = await Promise.all(\n cluster.representativeTraceIds.map((id) =>\n traces.findByTraceId(id).catch((err) => {\n log2.warn({ err, traceId: id }, 'Trace fetch failed — continuing without it');\n return [];\n }),\n ),\n );\n const allSpans = spanLists.flat();\n log2.info({ spanCount: allSpans.length }, 'Traces extracted');\n\n // 5. LLM inference — fail gracefully, notify anyway with null analysis\n let analysis = null;\n try {\n analysis = await llm.analyze(cluster, allSpans);\n log2.info({ urgency: analysis.urgency_level }, 'LLM analysis complete');\n } catch (err) {\n log2.warn({ err }, 'LLM inference failed — notifying without diagnosis');\n }\n\n // 6. POST-LLM rule engine hook — evaluate rules after LLM analysis\n // ────────────────────────────────────────────────────────────────────\n let postLlmEscalateChannels: string[] = [];\n\n if (ruleEngine && analysis) {\n const postResult = ruleEngine.evaluatePostLlm(cluster, analysis);\n\n // Collect escalate channels from POST-LLM actions\n for (const action of postResult.actions) {\n if (action.type === 'escalate' && 'channel' in action) {\n postLlmEscalateChannels.push(action.channel);\n }\n // Tag actions: attach metadata to cluster for observability\n if (action.type === 'tag' && 'key' in action) {\n log2.info({ tagKey: action.key, tagValue: (action as { value: string }).value }, 'Tag attached to cluster');\n }\n }\n\n // Apply tags from postResult to cluster\n if (postResult.tags && Object.keys(postResult.tags).length > 0) {\n cluster.labels = { ...cluster.labels, ...postResult.tags };\n }\n }\n\n // 7. Notify via ChatOps — with rule-based routing\n // ────────────────────────────────────────────────────────────────────\n try {\n const primaryChannel = preLlmRouteChannels[0]; // First route wins\n const escalateChannels = [\n ...preLlmEscalateChannels,\n ...postLlmEscalateChannels,\n ];\n\n // Send primary notification (to route channel or default)\n await notifier.send(cluster, analysis, primaryChannel);\n\n // Send escalation notifications (in addition to primary)\n for (const channel of escalateChannels) {\n await notifier.send(cluster, analysis, channel);\n }\n\n log2.info('Notification sent');\n } catch (err) {\n log2.error({ err }, 'Notification failed');\n throw err; // let the worker retry via SQS\n }\n }\n }\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// LokiBuffer — synchronous in-process Loki transport for Lambda.\n//\n// pino-abstract-transport runs in a worker_thread that Lambda kills before\n// the fetch completes. This module buffers log entries in-process and flushes\n// them in a single HTTP request at the END of the handler, before Lambda exits.\n//\n// Usage:\n// 1. LokiBuffer captures pino log lines via a WritableStream destination.\n// 2. At the end of the handler, call `flushLoki()` to push all buffered logs.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport { Writable } from 'node:stream';\n\ninterface LokiConfig {\n host: string;\n username: string;\n password: string;\n labels: Record<string, string>;\n}\n\ninterface LokiStream {\n stream: Record<string, string>;\n values: [string, string][];\n}\n\n/**\n * Maximum number of buffered log entries before the oldest is dropped.\n *\n * Acts as a ring buffer to prevent memory leaks if `flushLoki()` is never called\n * (e.g. a new handler forgets to wire it in) or if Loki pushes fail repeatedly.\n * When the buffer is full, the oldest entry is dropped to keep memory bounded.\n *\n * 1000 lines × ~1KB/line ≈ 1MB worst case, well within Lambda memory limits.\n */\nconst MAX_BUFFER_ENTRIES = 1000;\n\nlet _config: LokiConfig | null = null;\nconst _buffer: [string, string][] = []; // [nanosTimestamp, line]\n\n/**\n * Initialize the Loki buffer with connection config.\n * Call this once after loadConfig() sets LOKI_URL.\n */\nexport function initLokiBuffer(config: LokiConfig): void {\n _config = config;\n _buffer.length = 0;\n}\n\n/**\n * Returns a pino-compatible Writable destination that buffers log lines.\n * Pass this as the second argument to pino().\n */\nexport function createLokiDestination(): Writable {\n return new Writable({\n write(chunk: Buffer, _encoding, callback) {\n try {\n const line = chunk.toString().trim();\n if (!line) {\n callback();\n return;\n }\n // Loki requires nanosecond timestamps as STRINGS.\n // pino can emit `time` as either ISO string (isoTime) or number (epochTime).\n // Handle both, and fall back to Date.now() if missing/unparseable.\n let tsNs: string;\n try {\n const parsed = JSON.parse(line) as { time?: number | string };\n let ms = Date.now();\n if (typeof parsed.time === 'number' && Number.isFinite(parsed.time)) {\n ms = parsed.time;\n } else if (typeof parsed.time === 'string') {\n const parsedMs = Date.parse(parsed.time);\n if (Number.isFinite(parsedMs)) ms = parsedMs;\n }\n tsNs = String(ms * 1_000_000);\n } catch {\n tsNs = String(Date.now() * 1_000_000);\n }\n // Ring buffer: drop oldest entry when full to prevent unbounded growth.\n if (_buffer.length >= MAX_BUFFER_ENTRIES) {\n _buffer.shift();\n }\n _buffer.push([tsNs, line]);\n } catch {\n // never fail the logger\n }\n callback();\n },\n objectMode: false,\n });\n}\n\n/**\n * Flush all buffered log entries to Loki in a single HTTP request.\n * Call this at the END of the Lambda handler, after all business logic completes.\n * Errors are swallowed — Loki is best-effort; CloudWatch is the primary sink.\n */\nexport async function flushLoki(): Promise<void> {\n if (!_config || _buffer.length === 0) return;\n\n const { host, username, password, labels } = _config;\n const values = [..._buffer];\n _buffer.length = 0;\n\n const stream: LokiStream = { stream: labels, values };\n\n try {\n const res = await fetch(`${host}/loki/api/v1/push`, {\n method: 'POST',\n signal: AbortSignal.timeout(5_000),\n headers: {\n 'Content-Type': 'application/json',\n Authorization: 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'),\n },\n body: JSON.stringify({ streams: [stream] }),\n });\n if (!res.ok) {\n process.stderr.write(`[junando] Loki flush failed: ${res.status} ${await res.text()}\\n`);\n }\n } catch (err) {\n process.stderr.write(`[junando] Loki flush error: ${(err as Error).message}\\n`);\n }\n}\n","import pino from 'pino';\nimport { createLokiDestination, initLokiBuffer } from './loki-transport.js';\n\nexport type Logger = pino.Logger;\n\nexport interface LoggerOptions {\n level?: string;\n name?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Proxy Logger — solves the Lambda cold-start problem.\n//\n// Module-level code runs BEFORE loadConfig() sets LOKI_URL. If we create pino\n// loggers at import time, they always get stdout (no Loki).\n//\n// Solution: every createLogger() call returns a Proxy that forwards all method\n// calls to the *current* root logger. When reinitLogger() is called after\n// loadConfig(), it swaps the root — and ALL existing proxy instances instantly\n// start writing to both stdout and Loki without needing to be recreated.\n// ─────────────────────────────────────────────────────────────────────────────\n\nlet _root: pino.Logger = buildLogger({});\n\nfunction buildLogger(opts: LoggerOptions): pino.Logger {\n const level = opts.level ?? 'info';\n const name = opts.name ?? 'junando';\n const lokiUrl = process.env['LOKI_URL'];\n\n if (lokiUrl) {\n const parsed = new URL(lokiUrl);\n\n // Initialize the in-process Loki buffer.\n // flushLoki() must be called at the end of every Lambda handler invocation.\n initLokiBuffer({\n host: `${parsed.protocol}//${parsed.host}`,\n username: parsed.username,\n password: parsed.password,\n labels: {\n service_name: name,\n environment: process.env['NODE_ENV'] ?? 'production',\n },\n });\n\n const lokiDest = createLokiDestination();\n\n // multistream: stdout (CloudWatch) always reliable; Loki via in-process buffer.\n return pino(\n {\n level,\n base: { service: name },\n timestamp: pino.stdTimeFunctions.isoTime,\n },\n pino.multistream([{ stream: process.stdout }, { stream: lokiDest }]),\n );\n }\n\n return pino({\n level,\n base: { service: name },\n timestamp: pino.stdTimeFunctions.isoTime,\n });\n}\n\n/**\n * Returns a Proxy logger that always delegates to the current root logger.\n * Module-level callers get a proxy that automatically starts writing to Loki\n * once reinitLogger() is called inside the handler after loadConfig().\n */\nexport function createLogger(levelOrOptions?: string | LoggerOptions): Logger {\n const opts: LoggerOptions =\n typeof levelOrOptions === 'string'\n ? { level: levelOrOptions }\n : (levelOrOptions ?? {});\n\n // Non-default options create a dedicated logger (not proxied to root)\n if (opts.level !== undefined || opts.name !== undefined) {\n return buildLogger(opts);\n }\n\n // Return a Proxy that always reads from the CURRENT _root at call time.\n // This means reinitLogger() affects all existing module-level loggers instantly.\n // Note: read-only by design. pino loggers must not be mutated externally;\n // a `set` trap here would silently propagate writes to the global root and\n // affect every other proxy instance.\n return new Proxy({} as pino.Logger, {\n get(_target, prop) {\n const value = (_root as unknown as Record<string | symbol, unknown>)[prop];\n if (typeof value === 'function') {\n return (value as Function).bind(_root);\n }\n return value;\n },\n });\n}\n\n/**\n * Re-creates the root logger with current env vars (including LOKI_URL).\n * Call this inside your Lambda handler immediately after loadConfig():\n *\n * @example\n * const config = await loadConfig();\n * reinitLogger(); // all module-level proxy loggers now write to Loki\n */\nexport function reinitLogger(opts?: LoggerOptions): void {\n _root = buildLogger(opts ?? {});\n}\n","import type { Redis } from 'ioredis';\nimport type { IDeduplicationStore } from '../../domain/ports/index.js';\nimport { dedupRedisFailoverTotal } from '../../shared/metrics/index.js';\nimport { createLogger } from '../../shared/logger/index.js';\n\nconst logger = createLogger();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RedisDeduplicationStore — Infrastructure adapter.\n// Implements IDeduplicationStore using Redis SET NX.\n// Swap this for DynamoDBDeduplicationStore or InMemoryDeduplicationStore\n// without touching a single line of domain or application code.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class RedisDeduplicationStore implements IDeduplicationStore {\n private readonly keyPrefix = 'junando:dedup:';\n\n constructor(private readonly redis: Redis) {}\n\n async isNew(fingerprint: string, ttlSeconds: number): Promise<boolean> {\n try {\n const result = await this.redis.set(\n `${this.keyPrefix}${fingerprint}`,\n '1',\n 'EX',\n ttlSeconds,\n 'NX',\n );\n return result === 'OK';\n } catch (err) {\n logger.warn({ err, fingerprint }, 'Redis dedup check failed, failing open');\n dedupRedisFailoverTotal.inc();\n // Fail open: Redis down → treat every alert as new (noisy but safe)\n return true;\n }\n }\n\n async reset(fingerprint: string): Promise<void> {\n await this.redis.del(`${this.keyPrefix}${fingerprint}`);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// InMemoryDeduplicationStore — Test adapter.\n// Zero dependencies. Use in unit tests and local dev without Redis.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class InMemoryDeduplicationStore implements IDeduplicationStore {\n private readonly store = new Map<string, number>(); // fingerprint → expiry timestamp\n\n async isNew(fingerprint: string, ttlSeconds: number): Promise<boolean> {\n const expiry = this.store.get(fingerprint);\n const now = Date.now();\n\n if (expiry !== undefined && expiry > now) return false;\n\n this.store.set(fingerprint, now + ttlSeconds * 1000);\n return true;\n }\n\n async reset(fingerprint: string): Promise<void> {\n this.store.delete(fingerprint);\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { IIndexer } from '../../domain/ports/index.js';\nimport type { TraceabilityDocument } from '../../domain/entities/traceability.js';\nexport type { TraceabilityDocument };\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Transport contract — an HTTP fetcher that performs the (already-prepared)\n// request and returns a minimal response shape. Default implementation signs\n// with SigV4; tests inject a stub.\n// ─────────────────────────────────────────────────────────────────────────────\nexport interface SignedHttpRequest {\n method: string;\n url: string;\n headers: Record<string, string>;\n body: string;\n}\n\nexport interface OpenSearchHttpResponse {\n status: number;\n body: string;\n}\n\nexport type OpenSearchHttpFetcher = (request: SignedHttpRequest) => Promise<OpenSearchHttpResponse>;\n\nexport interface OpenSearchIndexerDeps {\n endpoint: string;\n indexName: string;\n region: string;\n fetcher: OpenSearchHttpFetcher;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OpenSearchIndexer — Infrastructure adapter.\n// Indexes a TraceabilityDocument into an OpenSearch domain.\n// SigV4 signing is delegated to the injected fetcher so this class stays\n// transport-agnostic and trivially testable.\n// ─────────────────────────────────────────────────────────────────────────────\nexport class OpenSearchIndexer implements IIndexer<TraceabilityDocument> {\n private readonly endpoint: string;\n private readonly indexName: string;\n private readonly region: string;\n private readonly fetcher: OpenSearchHttpFetcher;\n\n constructor(deps: OpenSearchIndexerDeps) {\n this.endpoint = deps.endpoint.replace(/\\/+$/, '');\n this.indexName = deps.indexName;\n this.region = deps.region;\n this.fetcher = deps.fetcher;\n }\n\n async index(doc: TraceabilityDocument): Promise<void> {\n const url = `${this.endpoint}/${this.indexName}/_doc`;\n const body = JSON.stringify(doc);\n\n const response = await this.fetcher({\n method: 'POST',\n url,\n headers: {\n 'content-type': 'application/json',\n },\n body,\n });\n\n if (response.status < 200 || response.status >= 300) {\n throw new Error(\n `OpenSearch index failed: status=${response.status} region=${this.region} body=${response.body}`,\n );\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// InMemoryIndexer — Test/dev double.\n// ─────────────────────────────────────────────────────────────────────────────\nexport class InMemoryIndexer implements IIndexer<TraceabilityDocument> {\n readonly indexed: TraceabilityDocument[] = [];\n\n async index(doc: TraceabilityDocument): Promise<void> {\n this.indexed.push(doc);\n }\n}\n","import * as Breaker from 'opossum';\nimport { z } from 'zod';\nimport type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport { LLMAnalysisSchema } from '../../domain/entities/incident.js';\nimport type { ILLMProvider } from '../../domain/ports/index.js';\nimport {\n CIRCUIT_BREAKER,\n LLM_FALLBACK_DEFAULTS,\n LLM_MAX_TOKENS,\n LLM_MODELS,\n LLMProviderType,\n} from '../../shared/constants.js';\nimport { createLogger } from '../../shared/logger/index.js';\nimport { llmInferenceDuration, llmInferenceTotal } from '../../shared/metrics/index.js';\n\nconst logger = createLogger();\n\n/**\n * Schema for OpenRouter API response validation.\n * Ensures type safety at the external boundary.\n */\nexport const OpenRouterResponseSchema = z.object({\n id: z.string().optional(),\n choices: z.array(\n z.object({\n index: z.number(),\n message: z.object({\n role: z.string(),\n content: z.string().optional(),\n }),\n finish_reason: z.string().optional(),\n }),\n ),\n usage: z\n .object({\n prompt_tokens: z.number().optional(),\n completion_tokens: z.number().optional(),\n total_tokens: z.number().optional(),\n })\n .optional(),\n});\n\nexport type OpenRouterResponse = z.infer<typeof OpenRouterResponseSchema>;\n\nconst SYSTEM_PROMPT = `You are a senior Site Reliability Engineer.\nRespond ONLY with raw JSON, no markdown, no text before or after:\n{\"probable_cause\":\"string\",\"impacted_services\":[\"string\"],\"recommended_steps\":[\"string\"],\"urgency_level\":\"low|medium|high|critical\",\"requires_rollback\":true|false}`;\n\n/**\n * Pre-compiled regex patterns for parsing LLM responses.\n * Hoisted to module level to avoid recompilation on every parseAnalysis call.\n * Matches JSON field extraction from raw LLM output.\n */\nconst RE_PROBABLE_CAUSE = /\"probable_cause\"\\s*:\\s*\"([^\"]+)\"/;\nconst RE_URGENCY_LEVEL = /\"urgency_level\"\\s*:\\s*\"([^\"]+)\"/;\nconst RE_REQUIRES_ROLLBACK = /\"requires_rollback\"\\s*:\\s*(true|false)/;\nconst RE_RECOMMENDED_STEPS = /\"recommended_steps\"\\s*:\\s*\\[([^\\]]+)\\]/;\nconst RE_IMPACTED_SERVICES = /\"impacted_services\"\\s*:\\s*\\[([^\\]]+)\\]/;\n\nconst BREAKER_OPTIONS = {\n timeout: CIRCUIT_BREAKER.Timeout,\n errorThresholdPercentage: CIRCUIT_BREAKER.ErrorThresholdPercentage,\n resetTimeout: CIRCUIT_BREAKER.ResetTimeoutMs,\n};\n\n/**\n * Builds the user-facing prompt sent to the LLM for analysis.\n * Includes cluster summary and trace count for context.\n */\nfunction buildUserPrompt(cluster: AlertCluster, traces: Record<string, unknown>[]): string {\n return `Service:${cluster.serviceName} Error:${cluster.alertType} Alerts:${cluster.alertCount} Latency:${cluster.latencyP99Ms ?? 'N/A'} Traces:${traces.length}`;\n}\n\n/**\n * Extracts LLMAnalysis from raw LLM response text.\n * Uses multi-stage parsing: JSON → regex fallback → heuristics.\n * Returns validated LLMAnalysis or falls back to default values.\n */\nfunction parseAnalysis(raw: string, correlationId?: string): LLMAnalysis {\n const startIdx = raw.indexOf('{');\n const endIdx = raw.lastIndexOf('}');\n\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n try {\n return LLMAnalysisSchema.parse(JSON.parse(raw.slice(startIdx, endIdx + 1)));\n } catch {\n logger.warn(\n { rawResponse: raw.slice(0, 500), correlationId },\n 'llm:parse:failed',\n );\n }\n }\n\n const probableCauseMatch = RE_PROBABLE_CAUSE.exec(raw);\n const urgencyMatch = RE_URGENCY_LEVEL.exec(raw);\n const rollbackMatch = RE_REQUIRES_ROLLBACK.exec(raw);\n const stepsMatch = RE_RECOMMENDED_STEPS.exec(raw);\n const servicesMatch = RE_IMPACTED_SERVICES.exec(raw);\n\n const probableCause = probableCauseMatch?.[1];\n const urgency = urgencyMatch?.[1];\n\n if (probableCause && urgency) {\n const steps: string[] = stepsMatch?.[1] ? (JSON.parse(`[${stepsMatch[1]}]`) as string[]) : [];\n const services: string[] = servicesMatch?.[1]\n ? (JSON.parse(`[${servicesMatch[1]}]`) as string[])\n : ['unknown-service'];\n const analysis: LLMAnalysis = {\n probable_cause: probableCause,\n impacted_services: services,\n recommended_steps: steps,\n urgency_level: urgency as LLMAnalysis['urgency_level'],\n requires_rollback: rollbackMatch?.[1] === 'true',\n };\n return LLMAnalysisSchema.parse(analysis);\n }\n\n const lowerRaw = raw.toLowerCase();\n let urgencyLevel: LLMAnalysis['urgency_level'] = 'medium';\n if (lowerRaw.includes('critical') || lowerRaw.includes('severity 1')) urgencyLevel = 'critical';\n else if (lowerRaw.includes('high') || lowerRaw.includes('severity 2')) urgencyLevel = 'high';\n else if (lowerRaw.includes('low')) urgencyLevel = 'low';\n\n return LLMAnalysisSchema.parse({\n probable_cause: 'Analysis in progress - check logs for details',\n impacted_services: ['unknown-service'],\n recommended_steps: ['Review incident details in logs'],\n urgency_level: urgencyLevel,\n requires_rollback: lowerRaw.includes('rollback') || lowerRaw.includes('revert'),\n });\n}\n\n/**\n * Gemini LLM provider using Google Generative AI SDK.\n * Wrapped with circuit breaker for resilience.\n */\nexport class GeminiProvider implements ILLMProvider {\n private readonly breaker: InstanceType<typeof Breaker.default>;\n\n constructor(\n private readonly apiKey: string,\n private readonly model: string = LLM_MODELS.Gemini,\n ) {\n this.breaker = new Breaker.default(this.analyzeRaw.bind(this), BREAKER_OPTIONS);\n }\n\n async analyze(cluster: AlertCluster, traces: Record<string, unknown>[]): Promise<LLMAnalysis> {\n try {\n return (await this.breaker.fire(cluster, traces)) as LLMAnalysis;\n } catch {\n return this.analyzeRaw(cluster, traces);\n }\n }\n\n private async analyzeRaw(\n cluster: AlertCluster,\n traces: Record<string, unknown>[],\n ): Promise<LLMAnalysis> {\n const { GoogleGenerativeAI } = await import('@google/generative-ai');\n const genAI = new GoogleGenerativeAI(this.apiKey);\n const gemini = genAI.getGenerativeModel({\n model: this.model,\n systemInstruction: SYSTEM_PROMPT,\n });\n\n const result = await gemini.generateContent(buildUserPrompt(cluster, traces));\n return parseAnalysis(result.response.text());\n }\n}\n\n/**\n * Claude LLM provider using Anthropic SDK.\n * Supports Claude Haiku and other models.\n */\nexport class ClaudeProvider implements ILLMProvider {\n constructor(\n private readonly apiKey: string,\n private readonly model: string = LLM_MODELS.Claude,\n ) {}\n\n async analyze(cluster: AlertCluster, traces: Record<string, unknown>[]): Promise<LLMAnalysis> {\n const Anthropic = (await import('@anthropic-ai/sdk')).default;\n const client = new Anthropic({ apiKey: this.apiKey });\n\n const message = await client.messages.create({\n model: this.model,\n max_tokens: LLM_MAX_TOKENS,\n system: SYSTEM_PROMPT,\n messages: [{ role: 'user', content: buildUserPrompt(cluster, traces) }],\n });\n\n const text = message.content.find((b) => b.type === 'text')?.text ?? '';\n return parseAnalysis(text);\n }\n}\n\n/**\n * Mock LLM provider for testing and local development.\n * Returns deterministic responses without external API calls.\n */\nexport class MockLLMProvider implements ILLMProvider {\n readonly callLog: Array<{ cluster: AlertCluster }> = [];\n\n async analyze(cluster: AlertCluster, _traces: Record<string, unknown>[]): Promise<LLMAnalysis> {\n this.callLog.push({ cluster });\n return {\n probable_cause: `Mock: ${cluster.alertType} on ${cluster.serviceName}`,\n impacted_services: [cluster.serviceName],\n recommended_steps: ['Check the logs', 'Verify the deployment'],\n urgency_level: 'high',\n requires_rollback: false,\n };\n }\n}\n\n/**\n * Options for configuring the OpenRouter fallback chain.\n * Infra-internal — not exported.\n */\ninterface FallbackOptions {\n fallbackModels?: string[];\n fallbackTimeoutMs?: number;\n}\n\n/**\n * OpenRouter LLM provider using OpenAI-compatible API.\n * Supports various open models (Qwen, etc.) via OpenRouter gateway.\n * When the primary model exhausts 429 retries, cycles through fallbackModels.\n */\nexport class OpenRouterProvider implements ILLMProvider {\n private readonly fallbackModels: string[];\n private readonly fallbackTimeoutMs: number;\n\n constructor(\n private readonly apiKey: string,\n private readonly model: string = LLM_MODELS.OpenRouter,\n fallbackModels: string[] = [],\n fallbackTimeoutMs: number = LLM_FALLBACK_DEFAULTS.TimeoutMs,\n ) {\n // Deduplicate: remove primary model from fallback list at construction time\n this.fallbackModels = fallbackModels.filter((m) => m !== model);\n this.fallbackTimeoutMs = fallbackTimeoutMs;\n }\n\n async analyze(\n cluster: AlertCluster,\n traces: Record<string, unknown>[],\n correlationId?: string,\n ): Promise<LLMAnalysis> {\n const prompt = buildUserPrompt(cluster, traces);\n logger.debug({ model: this.model, promptLength: prompt.length, correlationId }, 'llm:request:start');\n\n const startMs = Date.now();\n\n // Retry once on 429 using the Retry-After header from OpenRouter\n for (let attempt = 0; attempt < 2; attempt++) {\n const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n 'HTTP-Referer': process.env['APP_URL'] ?? 'https://junando.app',\n 'X-Title': 'Junando SRE',\n },\n body: JSON.stringify({\n model: this.model,\n messages: [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: prompt },\n ],\n // Note: json_object response_format is NOT supported by all OpenRouter models.\n // Qwen free tier ignores it or returns an error — rely on prompt instructions only.\n }),\n });\n\n const latencyMs = Date.now() - startMs;\n const raw = await res.json();\n\n if (!res.ok) {\n const retryAfter = Number(\n (raw as { error?: { metadata?: { retry_after_seconds?: number } } })\n ?.error?.metadata?.retry_after_seconds ?? 0,\n );\n\n logger.warn(\n { status: res.status, body: raw, model: this.model, correlationId, attempt, retryAfter },\n 'llm:request:failed',\n );\n\n if (res.status === 429 && attempt === 0) {\n // Some providers (Google AI Studio) do NOT return retry_after_seconds.\n // Default to a 5s backoff in that case. Cap at 30s so Lambda doesn't time out.\n const waitMs = retryAfter > 0 ? Math.min(retryAfter * 1000, 30_000) : 5_000;\n logger.info({ waitMs, retryAfter, correlationId }, 'llm:retry:waiting');\n await new Promise((r) => setTimeout(r, waitMs));\n continue;\n }\n\n if (res.status === 429) {\n if (this.fallbackModels.length > 0) {\n // Primary model exhausted — try fallback chain\n const deadlineMs = Date.now() + this.fallbackTimeoutMs;\n return this.analyzeFallback(prompt, correlationId, deadlineMs, this.model);\n }\n llmInferenceTotal.inc({ status: 'rate_limited' });\n throw new Error(`OpenRouter API failed: ${res.status}`);\n }\n\n llmInferenceTotal.inc({ status: 'error' });\n throw new Error(`OpenRouter API failed: ${res.status}`);\n }\n\n const parsed = OpenRouterResponseSchema.safeParse(raw);\n if (!parsed.success) {\n logger.warn({ errors: parsed.error.format(), correlationId }, 'llm:validation:failed');\n }\n\n const text = parsed.success ? (parsed.data.choices?.[0]?.message?.content ?? '') : '';\n const analysis = parseAnalysis(text, correlationId);\n\n if (parsed.success && parsed.data.usage) {\n const { prompt_tokens, completion_tokens, total_tokens } = parsed.data.usage;\n logger.info(\n {\n model: this.model,\n usage: { promptTokens: prompt_tokens, completionTokens: completion_tokens, totalTokens: total_tokens },\n latencyMs,\n correlationId,\n },\n 'llm:request:success',\n );\n }\n\n llmInferenceTotal.inc({ status: 'success' });\n llmInferenceDuration.observe({ model: this.model }, latencyMs / 1000);\n\n return analysis;\n }\n\n throw new Error('OpenRouter API failed after retry');\n }\n\n private async analyzeFallback(\n prompt: string,\n correlationId: string | undefined,\n deadlineMs: number,\n fromModel: string,\n ): Promise<LLMAnalysis> {\n for (const toModel of this.fallbackModels) {\n if (Date.now() >= deadlineMs) {\n throw new Error('OpenRouter fallback chain timed out');\n }\n\n logger.info({ from_model: fromModel, to_model: toModel, reason: '429', correlationId }, 'llm:fallback:hop');\n\n const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n 'HTTP-Referer': process.env['APP_URL'] ?? 'https://junando.app',\n 'X-Title': 'Junando SRE',\n },\n body: JSON.stringify({\n model: toModel,\n messages: [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: prompt },\n ],\n }),\n });\n\n const raw = await res.json();\n\n if (!res.ok) {\n if (res.status === 429) {\n fromModel = toModel;\n continue;\n }\n throw new Error(`OpenRouter API failed: ${res.status}`);\n }\n\n const parsed = OpenRouterResponseSchema.safeParse(raw);\n const text = parsed.success ? (parsed.data.choices?.[0]?.message?.content ?? '') : '';\n return parseAnalysis(text, correlationId);\n }\n\n throw new Error('OpenRouter API exhausted all models');\n }\n}\n\n/**\n * Factory type for creating LLM providers.\n * Takes API key and optional model override.\n */\ntype LLMFactory = (apiKey: string, model?: string, options?: FallbackOptions) => ILLMProvider;\n\n/**\n * Registry mapping provider names to their factory functions.\n * Used by createLLMProvider to instantiate the appropriate LLM client.\n */\nconst LLM_PROVIDER_REGISTRY: ReadonlyMap<string, LLMFactory> = new Map<string, LLMFactory>([\n [LLMProviderType.Gemini, (apiKey, model) => new GeminiProvider(apiKey, model)],\n [LLMProviderType.Claude, (apiKey, model) => new ClaudeProvider(apiKey, model)],\n [LLMProviderType.OpenRouter, (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs)],\n [LLMProviderType.Qwen, (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs)],\n]);\n\nexport function createLLMProvider(provider: string, apiKey: string, model?: string, options?: FallbackOptions): ILLMProvider {\n const factory = LLM_PROVIDER_REGISTRY.get(provider);\n if (!factory) {\n const supported = Array.from(LLM_PROVIDER_REGISTRY.keys()).join(', ');\n throw new Error(`Unknown LLM_PROVIDER: \"${provider}\". Supported: ${supported}`);\n }\n return factory(apiKey, model, options);\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// FactoryRegistry — generic factory registry with string keys.\n// No switch/case — register adapters by key, resolve by key.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class FactoryRegistry<T> {\n private readonly _factories = new Map<string, () => T>();\n private _default: () => T = () => {\n throw new Error(`No factory registered and no default available`);\n };\n\n /**\n * Register a factory for a given key.\n * Overwrites any existing registration for that key.\n */\n register(key: string, factory: () => T): void {\n this._factories.set(key, factory);\n }\n\n /**\n * Set the default factory to use when no key matches.\n */\n registerDefault(factory: () => T): void {\n this._default = factory;\n }\n\n /**\n * Resolve the factory for a given key.\n * Returns the default if no specific factory is registered for that key.\n */\n resolve(key: string): T {\n const factory = this._factories.get(key);\n if (factory) {\n return factory();\n }\n return this._default();\n }\n\n /**\n * Check if a factory is registered for a given key.\n */\n has(key: string): boolean {\n return this._factories.has(key);\n }\n\n /**\n * Return all registered keys.\n */\n keys(): string[] {\n return Array.from(this._factories.keys());\n }\n}","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { INotifier } from '../../domain/ports/index.js';\nimport { HTTP_TIMEOUT_MS, SLACK_API_URL, URGENCY_EMOJI } from '../../shared/constants.js';\nimport { createLogger } from '../../shared/logger/index.js';\nimport { notificationsTotal } from '../../shared/metrics/index.js';\n\nconst logger = createLogger();\n\n/**\n * Sanitizes endpointPath for safe rendering in Slack Block Kit.\n * Strips backticks (prevent markup injection) and limits length.\n */\nfunction sanitizeEndpointPath(endpointPath: string | undefined): string {\n if (!endpointPath) return 'unknown';\n return endpointPath.replaceAll('`', '').slice(0, 200);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SlackNotifier — Infrastructure adapter.\n// Implements INotifier using Slack Block Kit.\n// Swap for TeamsNotifier without touching application or domain code.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class SlackNotifier implements INotifier {\n constructor(\n private readonly botToken: string,\n private readonly channel: string,\n ) {}\n\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null, _channel?: string): Promise<void> {\n const payload = analysis\n ? this.buildAnalysisMessage(cluster, analysis)\n : this.buildFallbackMessage(cluster);\n\n try {\n const res = await fetch(SLACK_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.botToken}`,\n },\n body: JSON.stringify({ channel: this.channel, ...payload }),\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS.Default),\n });\n\n if (!res.ok) throw new Error(`Slack API error: ${res.status}`);\n const body = (await res.json()) as { ok: boolean; error?: string };\n if (!body.ok) throw new Error(`Slack error: ${body.error}`);\n\n notificationsTotal.inc({ channel: 'slack', outcome: 'success' });\n } catch (err) {\n notificationsTotal.inc({ channel: 'slack', outcome: 'failure' });\n throw err;\n }\n }\n\n private buildAnalysisMessage(\n cluster: AlertCluster,\n analysis: LLMAnalysis,\n ): { blocks: unknown[] } {\n const emoji = URGENCY_EMOJI[analysis.urgency_level] ?? '⚪';\n const steps = analysis.recommended_steps.map((s, i) => `${i + 1}. ${s}`).join('\\n');\n\n const safeEndpointPath = sanitizeEndpointPath(cluster.endpointPath);\n\n return {\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `${emoji} Incident — ${cluster.serviceName}`,\n },\n },\n {\n type: 'section',\n fields: [\n { type: 'mrkdwn', text: `*Service*\\n${cluster.serviceName}` },\n { type: 'mrkdwn', text: `*Alerts*\\n${cluster.alertCount}` },\n { type: 'mrkdwn', text: `*Endpoint*\\n${safeEndpointPath}` },\n {\n type: 'mrkdwn',\n text: `*Urgency*\\n${emoji} ${analysis.urgency_level.toUpperCase()}`,\n },\n ],\n },\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `*Probable cause*\\n${analysis.probable_cause}`,\n },\n },\n {\n type: 'section',\n text: { type: 'mrkdwn', text: `*Recommended steps*\\n${steps}` },\n },\n { type: 'divider' },\n {\n type: 'actions',\n elements: [\n {\n type: 'button',\n text: { type: 'plain_text', text: '✅ Acknowledge' },\n style: 'primary',\n action_id: 'acknowledge',\n value: cluster.fingerprint,\n },\n ...(analysis.requires_rollback\n ? [\n {\n type: 'button',\n text: { type: 'plain_text', text: '⏪ Trigger Rollback' },\n style: 'danger',\n action_id: 'trigger_rollback',\n value: cluster.fingerprint,\n confirm: {\n title: { type: 'plain_text', text: 'Confirm rollback' },\n text: {\n type: 'plain_text',\n text: `Roll back ${cluster.serviceName}?`,\n },\n confirm: { type: 'plain_text', text: 'Yes, rollback' },\n deny: { type: 'plain_text', text: 'Cancel' },\n },\n },\n ]\n : []),\n ],\n },\n ],\n };\n }\n\n private buildFallbackMessage(cluster: AlertCluster): { blocks: unknown[] } {\n const safeEndpointPath = sanitizeEndpointPath(cluster.endpointPath);\n\n return {\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `⚠️ Incident — ${cluster.serviceName} (no AI diagnosis)`,\n },\n },\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `*${cluster.alertCount} alerts* on \\`${safeEndpointPath}\\` since ${cluster.firstSeenAt}\\nLLM analysis unavailable — manual investigation required.`,\n },\n },\n ],\n };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ConsoleNotifier — Local dev / test adapter. Prints to stdout.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ConsoleNotifier implements INotifier {\n readonly sent: Array<{\n cluster: AlertCluster;\n analysis: LLMAnalysis | null;\n }> = [];\n\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null, _channel?: string): Promise<void> {\n try {\n this.sent.push({ cluster, analysis });\n logger.info(\n {\n cluster: {\n serviceName: cluster.serviceName,\n alertType: cluster.alertType,\n },\n analysis: analysis ?? 'unavailable',\n },\n '--- Junando Notification ---',\n );\n notificationsTotal.inc({ channel: 'unknown', outcome: 'success' });\n } catch (err) {\n notificationsTotal.inc({ channel: 'unknown', outcome: 'failure' });\n throw err;\n }\n }\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { INotifier } from '../../domain/ports/index.js';\nimport { TEAMS_WEBHOOK_TIMEOUT_MS, URGENCY_EMOJI } from '../../shared/constants.js';\nimport { notificationsTotal } from '../../shared/metrics/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TeamsNotifierError — domain error type for Teams adapter failures.\n// Discriminable type for catch blocks.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class TeamsNotifierError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TeamsNotifierError';\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// sanitizeText — strips HTML, escapes Adaptive Card markdown chars,\n// collapses excess newlines, truncates to maxLength.\n// Pure function — no side effects.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function sanitizeText(s: string, maxLength = 4_000): string {\n // 1. Strip HTML tags\n let result = s.replace(/<[^>]*>/g, '');\n\n // 2. Escape Adaptive Card markdown special chars\n result = result.replace(/\\\\/g, '\\\\\\\\').replace(/\\*/g, '\\\\*').replace(/_/g, '\\\\_');\n\n // 3. Collapse excess newlines (>5 consecutive)\n result = result.replace(/(\\n){6,}/g, '\\n\\n\\n\\n\\n…');\n\n // 4. Truncate to maxLength\n if (result.length > maxLength) {\n result = result.slice(0, maxLength) + '…';\n }\n\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Adaptive Card payload builders\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction buildAnalysisCard(cluster: AlertCluster, analysis: LLMAnalysis): object {\n const emoji = URGENCY_EMOJI[analysis.urgency_level] ?? '⚪';\n const safeName = sanitizeText(cluster.serviceName);\n const safeEndpoint = sanitizeText(cluster.endpointPath ?? 'unknown');\n const safeCause = sanitizeText(analysis.probable_cause);\n const safeSteps = analysis.recommended_steps\n .map((s, i) => `${i + 1}. ${sanitizeText(s)}`)\n .join('\\n');\n const clusterUrl = `https://app.junando.io/clusters/${cluster.fingerprint}`;\n\n return {\n $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n type: 'AdaptiveCard',\n version: '1.5',\n body: [\n {\n type: 'TextBlock',\n text: `${emoji} Incident — ${safeName}`,\n size: 'Large',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'FactSet',\n facts: [\n { title: 'Service', value: safeName },\n { title: 'Alerts', value: String(cluster.alertCount) },\n { title: 'Endpoint', value: safeEndpoint },\n { title: 'Urgency', value: `${emoji} ${analysis.urgency_level}` },\n ],\n },\n {\n type: 'TextBlock',\n text: '**Probable cause**',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'TextBlock',\n text: safeCause,\n wrap: true,\n },\n {\n type: 'TextBlock',\n text: '**Recommended steps**',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'TextBlock',\n text: safeSteps,\n wrap: true,\n },\n ],\n actions: [\n {\n type: 'Action.OpenUrl',\n title: 'View in Junando',\n url: clusterUrl,\n },\n ],\n };\n}\n\nfunction buildFallbackCard(cluster: AlertCluster): object {\n const safeName = sanitizeText(cluster.serviceName);\n const clusterUrl = `https://app.junando.io/clusters/${cluster.fingerprint}`;\n\n return {\n $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n type: 'AdaptiveCard',\n version: '1.5',\n body: [\n {\n type: 'TextBlock',\n text: `⚠️ Incident — ${safeName} (no AI diagnosis)`,\n size: 'Large',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'FactSet',\n facts: [\n { title: 'Service', value: safeName },\n { title: 'Alerts', value: String(cluster.alertCount) },\n ],\n },\n ],\n actions: [\n {\n type: 'Action.OpenUrl',\n title: 'View in Junando',\n url: clusterUrl,\n },\n ],\n };\n}\n\nfunction buildAdaptiveCardPayload(card: object): object {\n return {\n type: 'message',\n attachments: [\n {\n contentType: 'application/vnd.microsoft.card.adaptive',\n content: card,\n },\n ],\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TeamsNotifier — Infrastructure adapter.\n// Implements INotifier using Microsoft Teams Adaptive Cards via webhook.\n// Swap for SlackNotifier without touching application or domain code.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class TeamsNotifier implements INotifier {\n // Pre-computed at construction so the error path never re-parses the URL.\n // If parsing happens inside the catch block and throws, the original error\n // context (timeout, network failure, etc.) would be lost.\n private readonly hostForErrors: string;\n\n constructor(\n private readonly webhookUrl: string,\n private readonly timeoutMs: number = TEAMS_WEBHOOK_TIMEOUT_MS,\n ) {\n let host = 'unknown';\n try {\n host = new URL(webhookUrl).hostname;\n } catch {\n // Leave as 'unknown'. Config validation should have rejected invalid URLs\n // upstream; this fallback exists only so the notifier can still produce\n // a meaningful error message instead of throwing in its own catch block.\n }\n this.hostForErrors = host;\n }\n\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null, _channel?: string): Promise<void> {\n const card = analysis ? buildAnalysisCard(cluster, analysis) : buildFallbackCard(cluster);\n const payload = buildAdaptiveCardPayload(card);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n try {\n const res = await fetch(this.webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n // Round 2 hardening: never include the response body in error messages.\n // Power Automate / Logic Apps echo parts of the request URL (including\n // SAS tokens like sig=, code=, sv=, sp=) and chasing every encoding\n // variant is a losing game. Status + host gives enough diagnostic\n // signal without any leak surface.\n throw new TeamsNotifierError(\n `Teams webhook error ${res.status} (host: ${this.hostForErrors})`,\n );\n }\n\n notificationsTotal.inc({ channel: 'teams', outcome: 'success' });\n } catch (err) {\n if (err instanceof TeamsNotifierError) {\n notificationsTotal.inc({ channel: 'teams', outcome: 'failure' });\n throw err;\n }\n if (err instanceof Error && err.name === 'AbortError') {\n // TNT-08: log host only — never full URL (no sig= or api-version= query).\n // TNT-10: host is pre-computed; no URL parsing in the error path.\n const timeoutErr = new TeamsNotifierError(\n `Teams webhook timed out after ${this.timeoutMs}ms (host: ${this.hostForErrors})`,\n );\n notificationsTotal.inc({ channel: 'teams', outcome: 'failure' });\n throw timeoutErr;\n }\n notificationsTotal.inc({ channel: 'teams', outcome: 'failure' });\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { INotifier } from '../../domain/ports/index.js';\nimport type { RuleAction } from '../../domain/entities/rule.js';\nimport { RuleActionType } from '../../domain/entities/rule.js';\nimport type { ChannelRegistry } from '../rules/channel-registry.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RoutingNotifier — wraps multiple INotifier instances via ChannelRegistry.\n// Implements INotifier for default-channel backward compatibility.\n//\n// No switch/case — uses Record<RuleActionType, handler> pattern for action dispatch.\n// Route actions override the default channel.\n// Escalate actions send additional notifications alongside default/route.\n// Tag actions are metadata-only (no notification side effect).\n// Suppress actions are handled by the caller (use case).\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ActionDispatchHandler = (\n action: RuleAction,\n ctx: ActionDispatchContext,\n) => Promise<void>;\n\ninterface ActionDispatchContext {\n cluster: AlertCluster;\n analysis: LLMAnalysis | null;\n registry: ChannelRegistry;\n defaultNotifier: INotifier;\n /** Accumulates channels to notify — populated by Route/Escalate handlers */\n routeChannels: Set<string>;\n escalationChannels: Set<string>;\n /** Set to true if a Suppress action is present — skips all notification */\n suppressed: boolean;\n}\n\n/**\n * Dispatch map: RuleActionType → handler function.\n * Record pattern — NO switch/case.\n */\nconst ACTION_DISPATCH: Record<string, ActionDispatchHandler> = {\n [RuleActionType.Suppress]: async (_action, ctx) => {\n // Suppress actions are handled by the caller (use case).\n // When present in sendWithActions, suppress all notification as a defensive measure.\n ctx.suppressed = true;\n },\n\n [RuleActionType.Route]: async (action, ctx) => {\n const routeAction = action as { type: RuleActionType.Route; channel: string };\n ctx.routeChannels.add(routeAction.channel);\n },\n\n [RuleActionType.Escalate]: async (action, ctx) => {\n const escalateAction = action as { type: RuleActionType.Escalate; channel: string };\n ctx.escalationChannels.add(escalateAction.channel);\n },\n\n [RuleActionType.Tag]: async (_action, _ctx) => {\n // Tag actions are metadata-only. No notification side effect.\n // The caller (use case) attaches tags to the cluster for observability.\n },\n};\n\nexport class RoutingNotifier implements INotifier {\n constructor(\n private readonly registry: ChannelRegistry,\n private readonly defaultNotifier: INotifier,\n ) {}\n\n /**\n * Implements INotifier.send — sends via default notifier.\n * Backward-compatible with existing call sites that don't use rule actions.\n */\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null): Promise<void> {\n await this.defaultNotifier.send(cluster, analysis);\n }\n\n /**\n * Dispatch notifications based on rule engine actions.\n *\n * - Route actions: send to the specified channel instead of default.\n * - Escalate actions: send additional notifications to escalation channels.\n * - Tag actions: metadata-only, no notification side effect.\n * - Suppress actions: skip all notification (defensive — caller should have already skipped).\n * - Unknown channels: fall back to default notifier.\n * - Empty actions or no Route/Escalate: send via default notifier.\n */\n async sendWithActions(\n cluster: AlertCluster,\n analysis: LLMAnalysis | null,\n actions: RuleAction[],\n ): Promise<void> {\n const ctx: ActionDispatchContext = {\n cluster,\n analysis,\n registry: this.registry,\n defaultNotifier: this.defaultNotifier,\n routeChannels: new Set(),\n escalationChannels: new Set(),\n suppressed: false,\n };\n\n // Dispatch all actions using Record pattern — NO switch/case\n for (const action of actions) {\n const handler = ACTION_DISPATCH[action.type];\n if (handler) {\n await handler(action, ctx);\n }\n }\n\n // If suppressed, skip all notification\n if (ctx.suppressed) {\n return;\n }\n\n // Resolve notifications to send\n const notifications: Promise<void>[] = [];\n\n const hasRoute = ctx.routeChannels.size > 0;\n\n if (hasRoute) {\n // Route overrides default — send to route channels\n for (const channel of ctx.routeChannels) {\n notifications.push(this.tryResolveAndSend(channel, cluster, analysis));\n }\n } else {\n // No route action — send via default notifier\n notifications.push(this.defaultNotifier.send(cluster, analysis));\n }\n\n // Escalation channels send in addition to default/route\n for (const channel of ctx.escalationChannels) {\n notifications.push(this.tryResolveAndSend(channel, cluster, analysis));\n }\n\n await Promise.all(notifications);\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n /**\n * Resolve a channel name to its notifier and send.\n * Falls back to default notifier if channel is unknown.\n */\n private async tryResolveAndSend(\n channel: string,\n cluster: AlertCluster,\n analysis: LLMAnalysis | null,\n ): Promise<void> {\n try {\n const notifier = this.registry.resolve(channel);\n await notifier.send(cluster, analysis);\n } catch {\n // ChannelRegistry.resolve throws if channel unknown and no default set.\n // Fall back to default notifier.\n await this.defaultNotifier.send(cluster, analysis);\n }\n }\n}\n","import { parse as parseYaml, YAMLParseError } from 'yaml';\nimport { RuleConfigurationSchema } from '../../domain/entities/rule.js';\nimport type { ValidatedRuleConfiguration } from '../../domain/entities/rule.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// YamlRuleLoader — reads rules.yaml, validates with Zod, returns RuleConfiguration.\n// No switch/case. Pure validation function for testability.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Parse and validate a YAML string into a RuleConfiguration.\n * Pure function — no I/O, no side effects. Fast-fails on invalid config.\n *\n * @throws {Error} if YAML is malformed or Zod validation fails\n */\nexport function parseRuleConfig(yamlString: string): ValidatedRuleConfiguration {\n let raw: unknown;\n try {\n raw = parseYaml(yamlString);\n } catch (err) {\n if (err instanceof YAMLParseError) {\n throw new Error(`Invalid YAML in rules config: ${err.message}`);\n }\n throw err;\n }\n\n const result = RuleConfigurationSchema.safeParse(raw);\n\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`Invalid rules configuration:\\n${issues}`);\n }\n\n return result.data;\n}\n","import type { INotifier } from '../../domain/ports/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ChannelRegistry — Map<string, INotifier> for named channel resolution.\n// No switch/case — pure Map-based lookup with fallback.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ChannelRegistry {\n private readonly _channels = new Map<string, INotifier>();\n private _default: INotifier | null = null;\n\n /**\n * Register a notifier for a given channel name.\n * Overwrites any existing registration for that name.\n */\n register(channel: string, notifier: INotifier): void {\n this._channels.set(channel, notifier);\n }\n\n /**\n * Set the default notifier to use when a channel is not found.\n */\n setDefault(notifier: INotifier): void {\n this._default = notifier;\n }\n\n /**\n * Resolve a channel name to its notifier instance.\n * Falls back to the default notifier if the channel is unknown.\n *\n * @throws {Error} if the channel is unknown and no default is set\n */\n resolve(channel: string): INotifier {\n const instance = this._channels.get(channel);\n if (instance) {\n return instance;\n }\n if (this._default) {\n return this._default;\n }\n throw new Error(\n `Unknown channel \"${channel}\" and no default notifier configured`,\n );\n }\n\n /**\n * Check if a channel is registered.\n */\n has(channel: string): boolean {\n return this._channels.has(channel);\n }\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { ValidatedRuleCondition } from '../../domain/entities/rule.js';\nimport { ALERT_TYPE_LABELS } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ConditionEvaluator — compile RuleCondition → predicate function.\n// No switch/case — uses Record<string, matcher> pattern.\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype Predicate = (cluster: AlertCluster, analysis?: LLMAnalysis) => boolean;\n\ntype MatcherFactory = (value: unknown) => Predicate;\n\n/**\n * Map of condition field names to matcher factories.\n * Each factory takes the condition value and returns a predicate function.\n * This is the Record<string, matcher> pattern — no switch/case.\n */\nconst MATCHER_MAP: Record<string, MatcherFactory> = {\n serviceName: (value) => {\n const target = (value as string).toLowerCase();\n return (cluster) => cluster.serviceName.toLowerCase() === target;\n },\n\n alertType: (value) => {\n return (cluster) => cluster.alertType === value;\n },\n\n severity: (value) => {\n return (cluster) => {\n const config = ALERT_TYPE_LABELS[cluster.alertType];\n return config?.severity === value;\n };\n },\n\n endpointPath: (value) => {\n return (cluster) => cluster.endpointPath === value;\n },\n\n alertCount: (value) => {\n const range = value as { min?: number; max?: number };\n return (cluster) => {\n const count = cluster.alertCount;\n if (range.min !== undefined && count < range.min) return false;\n if (range.max !== undefined && count > range.max) return false;\n return true;\n };\n },\n\n latencyP99Ms: (value) => {\n const range = value as { min?: number; max?: number };\n return (cluster) => {\n const latency = cluster.latencyP99Ms;\n if (latency === undefined) return false;\n if (range.min !== undefined && latency < range.min) return false;\n if (range.max !== undefined && latency > range.max) return false;\n return true;\n };\n },\n\n labels: (value) => {\n const expected = value as Record<string, string>;\n return (cluster) => {\n const clusterLabels = cluster.labels;\n if (!clusterLabels) return false;\n return Object.entries(expected).every(\n ([key, val]) => clusterLabels[key] === val,\n );\n };\n },\n\n urgencyLevel: (value) => {\n return (_cluster, analysis) => {\n if (!analysis) return false;\n return analysis.urgency_level === value;\n };\n },\n\n requiresRollback: (value) => {\n return (_cluster, analysis) => {\n if (!analysis) return false;\n return analysis.requires_rollback === value;\n };\n },\n\n impactedServices: (value) => {\n const targets = value as string[];\n return (_cluster, analysis) => {\n if (!analysis) return false;\n return targets.some((t) => analysis.impacted_services.includes(t));\n };\n },\n};\n\n/**\n * Compile a RuleCondition into a predicate function.\n * The returned function can be called with (cluster, analysis?) to evaluate the condition.\n * Pre-compilation ensures the field iteration and matcher assembly happen once at load time.\n *\n * All specified conditions must match (AND logic).\n * If no fields are specified, the predicate returns true (match-all).\n */\nexport function compileCondition(condition: ValidatedRuleCondition): Predicate {\n const predicates: Predicate[] = [];\n\n for (const [field, value] of Object.entries(condition)) {\n if (value === undefined) continue;\n\n const factory = MATCHER_MAP[field];\n if (factory) {\n predicates.push(factory(value));\n }\n }\n\n // If no conditions specified, match everything (pass-through)\n if (predicates.length === 0) {\n return () => true;\n }\n\n // AND logic: all predicates must pass\n return (cluster, analysis) => predicates.every((p) => p(cluster, analysis));\n}\n","import { RuleActionType } from '../../domain/entities/rule.js';\nimport type { RuleAction } from '../../domain/entities/rule.js';\nimport type { RuleActionResult } from '../../domain/ports/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ActionDispatcher — dispatch RuleAction[] → RuleActionResult.\n// No switch/case — uses Record<RuleActionType, handler> pattern.\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ActionHandler = (action: RuleAction, result: RuleActionResult) => void;\n\n/**\n * Map of action type to handler function.\n * Record<RuleActionType, handler> pattern — NO switch/case.\n * Each handler mutates the result object for the given action.\n */\nconst HANDLER_MAP: Record<string, ActionHandler> = {\n [RuleActionType.Suppress]: (_action, result) => {\n result.suppressed = true;\n },\n\n [RuleActionType.Route]: (action, result) => {\n result.actions.push(action);\n },\n\n [RuleActionType.Escalate]: (action, result) => {\n result.actions.push(action);\n },\n\n [RuleActionType.Tag]: (action, result) => {\n const tagAction = action as { type: RuleActionType.Tag; key: string; value: string };\n result.tags[tagAction.key] = tagAction.value;\n },\n};\n\n/**\n * Dispatch an array of RuleAction into a RuleActionResult.\n * All actions are processed in order.\n * The result accumulates: suppressed flag, route/escalate actions, and tags.\n *\n * Pure function — no I/O, no side effects beyond the returned result.\n */\nexport function dispatchActions(actions: RuleAction[]): RuleActionResult {\n const result: RuleActionResult = {\n suppressed: false,\n actions: [],\n tags: {},\n };\n\n for (const action of actions) {\n const handler = HANDLER_MAP[action.type];\n if (handler) {\n handler(action, result);\n }\n }\n\n return result;\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { IRuleEngine, RuleActionResult } from '../../domain/ports/index.js';\nimport type { ValidatedRule, ValidatedRuleConfiguration } from '../../domain/entities/rule.js';\nimport { RuleEvaluationPhase } from '../../domain/entities/rule.js';\nimport { compileCondition } from './condition-evaluator.js';\nimport { dispatchActions } from './action-dispatcher.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleEngine — implements IRuleEngine with first-match-wins evaluation.\n// Pre-compiles all rule conditions at construction time for hot-path performance.\n// No switch/case — delegates to compileCondition (Record<string, matcher>)\n// and dispatchActions (Record<RuleActionType, handler>).\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface CompiledRule {\n id: string;\n predicate: (cluster: AlertCluster, analysis?: LLMAnalysis) => boolean;\n result: ReturnType<typeof dispatchActions>;\n}\n\nexport class RuleEngine implements IRuleEngine {\n private readonly preLlmRules: CompiledRule[];\n private readonly postLlmRules: CompiledRule[];\n\n constructor(config: ValidatedRuleConfiguration) {\n this.preLlmRules = this.compileSection(config[RuleEvaluationPhase.PreLlm].rules);\n this.postLlmRules = this.compileSection(config[RuleEvaluationPhase.PostLlm].rules);\n }\n\n /**\n * Evaluate PRE-LLM rules against a cluster.\n * First-match-wins — returns result of first matching rule.\n * If no rule matches, returns pass-through (suppressed=false, no actions).\n */\n evaluatePreLlm(cluster: AlertCluster): RuleActionResult {\n return this.evaluateRules(this.preLlmRules, cluster);\n }\n\n /**\n * Evaluate POST-LLM rules against a cluster and LLM analysis.\n * First-match-wins — returns result of first matching rule.\n * If no rule matches, returns pass-through.\n */\n evaluatePostLlm(\n cluster: AlertCluster,\n analysis: LLMAnalysis,\n ): RuleActionResult {\n return this.evaluateRules(this.postLlmRules, cluster, analysis);\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private compileSection(rules: ValidatedRule[]): CompiledRule[] {\n return rules.map((rule) => ({\n id: rule.id,\n predicate: compileCondition(rule.condition),\n result: dispatchActions(rule.actions),\n }));\n }\n\n private evaluateRules(\n rules: CompiledRule[],\n cluster: AlertCluster,\n analysis?: LLMAnalysis,\n ): RuleActionResult {\n for (const rule of rules) {\n if (rule.predicate(cluster, analysis)) {\n return {\n ...rule.result,\n matchedRuleId: rule.id,\n };\n }\n }\n\n // No rule matched — pass-through\n return {\n suppressed: false,\n actions: [],\n tags: {},\n };\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { FactoryRegistry } from '../../shared/factory-registry.js';\nimport type { Config } from '../../shared/config/index.js';\nimport type { INotifier } from '../../domain/ports/index.js';\nimport type { IRuleEngine } from '../../domain/ports/index.js';\nimport { SlackNotifier } from './slack.adapter.js';\nimport { TeamsNotifier } from './teams.adapter.js';\nimport { RoutingNotifier } from './routing-notifier.js';\nimport { parseRuleConfig } from '../rules/yaml-rule-loader.js';\nimport { ChannelRegistry } from '../rules/channel-registry.js';\nimport { RuleEngine } from '../rules/rule-engine.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// createNotifier — single instantiation point, no switch/case.\n// Registry holds factories, resolve picks the right one.\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction buildNotifierRegistry(config: Config): FactoryRegistry<INotifier> {\n const registry = new FactoryRegistry<INotifier>();\n\n registry.register('teams', () => {\n if (!config.teamsWebhookUrl) {\n throw new Error('NOTIFIER_TYPE=teams requires TEAMS_WEBHOOK_URL to be set');\n }\n return new TeamsNotifier(config.teamsWebhookUrl);\n });\n\n registry.register('slack', () => {\n if (!config.slackBotToken || !config.slackChannel) {\n throw new Error('NOTIFIER_TYPE=slack requires SLACK_BOT_TOKEN and SLACK_CHANNEL to be set');\n }\n return new SlackNotifier(config.slackBotToken, config.slackChannel);\n });\n\n // Default: Slack (matches prior switch behavior where default was Slack)\n registry.registerDefault(() => new SlackNotifier('dummy-token', '#alerts'));\n\n return registry;\n}\n\n/**\n * Creates the notifier for the application.\n *\n * When `config.rulesConfigPath` is set:\n * - Reads and validates the rules YAML config\n * - Creates a ChannelRegistry with the default notifier as fallback\n * - Wraps the default notifier with a RoutingNotifier for multi-channel dispatch\n *\n * When `config.rulesConfigPath` is NOT set:\n * - Returns the default notifier directly (backward-compatible)\n */\nexport function createNotifier(config: Config): INotifier {\n const registry = buildNotifierRegistry(config);\n const defaultNotifier = registry.resolve(config.notifierType);\n\n if (!config.rulesConfigPath) {\n console.info('[createNotifier] RULES_CONFIG_PATH not set — rule engine disabled, using default notifier');\n return defaultNotifier;\n }\n\n // Read and parse rules YAML\n const yamlContent = readFileSync(config.rulesConfigPath, 'utf-8');\n parseRuleConfig(yamlContent); // Validate — throws on invalid config\n\n // Create channel registry with default notifier as fallback\n const channelRegistry = new ChannelRegistry();\n channelRegistry.setDefault(defaultNotifier);\n\n // Wrap with routing notifier for multi-channel dispatch\n return new RoutingNotifier(channelRegistry, defaultNotifier);\n}\n\n/**\n * Creates the RuleEngine from a YAML rules config file.\n *\n * Returns undefined when `config.rulesConfigPath` is not set,\n * meaning rule evaluation is disabled (pass-through behavior).\n */\nexport function createRuleEngine(config: Config): IRuleEngine | undefined {\n if (!config.rulesConfigPath) {\n return undefined;\n }\n\n const yamlContent = readFileSync(config.rulesConfigPath, 'utf-8');\n const ruleConfig = parseRuleConfig(yamlContent);\n return new RuleEngine(ruleConfig);\n}\n","import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';\nimport type { IAlertQueue } from '../../domain/ports/index.js';\nimport type { NormalizedAlert } from '../../domain/entities/alert.js';\nimport { randomUUID } from 'node:crypto';\nimport { createLogger } from '../../shared/logger/index.js';\nimport { Fingerprint } from '../../domain/value-objects/fingerprint.js';\n\nconst logger = createLogger();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SQSAlertQueue — Infrastructure adapter.\n// Implements IAlertQueue by publishing to AWS SQS.\n// SQSClient is initialized lazily (singleton) on first use to avoid\n// module-level AWS credential errors in local dev.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SendMessageParams {\n messageBody: string;\n messageGroupId: string;\n messageDeduplicationId: string;\n}\n\nexport class SQSAlertQueue implements IAlertQueue {\n private sqsClient: SQSClient | null = null;\n\n constructor(\n private readonly queueUrl: string,\n private readonly region?: string,\n ) {}\n\n private getClient(): SQSClient {\n if (!this.sqsClient) {\n this.sqsClient = new SQSClient(this.region ? { region: this.region } : {});\n }\n return this.sqsClient;\n }\n\n async sendMessage(params: SendMessageParams): Promise<void> {\n await this.getClient().send(\n new SendMessageCommand({\n QueueUrl: this.queueUrl,\n MessageBody: params.messageBody,\n MessageGroupId: params.messageGroupId,\n MessageDeduplicationId: params.messageDeduplicationId,\n }),\n );\n }\n\n async publish(alert: NormalizedAlert): Promise<void> {\n const correlationId = randomUUID();\n const fingerprint = Fingerprint.fromAlert(alert).toString();\n\n try {\n await this.sendMessage({\n messageBody: JSON.stringify({ correlationId, alerts: [alert] }),\n messageGroupId: fingerprint,\n messageDeduplicationId: correlationId,\n });\n } catch (err) {\n logger.error({ err, alert: fingerprint }, 'Failed to publish alert to SQS');\n throw err;\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// InMemoryAlertQueue — Local dev / test adapter.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class InMemoryAlertQueue implements IAlertQueue {\n readonly published: NormalizedAlert[] = [];\n\n async publish(alert: NormalizedAlert): Promise<void> {\n this.published.push(alert);\n const fingerprint = Fingerprint.fromAlert(alert).toString();\n logger.info({ alert: fingerprint }, 'Mocked publishing alert to InMemoryAlertQueue');\n }\n}\n","import { SQSClient, GetQueueAttributesCommand } from '@aws-sdk/client-sqs';\nimport { sqsQueueLag } from '../../shared/metrics/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// startSqsLagPoller — Background poller for SQS queue depth.\n// Polls ApproximateNumberOfMessages and updates the sqsQueueLag gauge.\n// Must NOT be called in the webhook critical path — worker module scope only.\n//\n// Returns a cleanup function (clearInterval) for test teardown / Lambda shutdown.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function startSqsLagPoller(\n queueUrl: string,\n intervalMs: number,\n region?: string,\n): () => void {\n const client = new SQSClient(region ? { region } : {});\n\n const poll = async (): Promise<void> => {\n try {\n const result = await client.send(\n new GetQueueAttributesCommand({\n QueueUrl: queueUrl,\n AttributeNames: ['ApproximateNumberOfMessages'],\n }),\n );\n const raw = result.Attributes?.['ApproximateNumberOfMessages'];\n if (raw !== undefined) {\n sqsQueueLag.set({ queue_name: 'alerts' }, parseInt(raw, 10));\n }\n } catch {\n // Swallow errors: gauge retains last value; process must not exit.\n }\n };\n\n const timer = setInterval(() => {\n void poll();\n }, intervalMs);\n\n return () => clearInterval(timer);\n}\n","import type { ITraceRepository } from '../../domain/ports/index.js';\nimport { HTTP_TIMEOUT_MS } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// LokiTraceRepository — Infrastructure adapter.\n// Implements ITraceRepository using Loki's HTTP query API.\n// Swap for DatadogTraceRepository, JaegerTraceRepository, etc.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class LokiTraceRepository implements ITraceRepository {\n constructor(\n private readonly lokiUrl: string,\n private readonly apiKey?: string,\n ) {}\n\n async findByTraceId(traceId: string): Promise<Record<string, unknown>[]> {\n const query = encodeURIComponent(`{trace_id=\"${traceId}\"}`);\n const url = `${this.lokiUrl}/loki/api/v1/query_range?query=${query}&limit=50`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n\n const res = await fetch(url, {\n headers,\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS.Default),\n });\n if (!res.ok) throw new Error(`Loki query failed: ${res.status} ${res.statusText}`);\n\n const body = (await res.json()) as LokiResponse;\n return this.parseResponse(body);\n }\n\n private parseResponse(body: LokiResponse): Record<string, unknown>[] {\n return body.data.result.flatMap((stream) =>\n stream.values.map(([ts, line]) => ({\n timestamp: ts,\n ...this.tryParseJSON(line),\n })),\n );\n }\n\n private tryParseJSON(line: string): Record<string, unknown> {\n try {\n return JSON.parse(line) as Record<string, unknown>;\n } catch {\n return { message: line };\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MockTraceRepository — Test adapter. Returns predictable fake traces.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class MockTraceRepository implements ITraceRepository {\n constructor(private readonly fixtures: Map<string, Record<string, unknown>[]> = new Map()) {}\n\n async findByTraceId(traceId: string): Promise<Record<string, unknown>[]> {\n return this.fixtures.get(traceId) ?? [];\n }\n\n addFixture(traceId: string, spans: Record<string, unknown>[]): void {\n this.fixtures.set(traceId, spans);\n }\n}\n\n// Internal types for Loki response shape\ninterface LokiResponse {\n data: {\n result: Array<{\n stream: Record<string, string>;\n values: Array<[string, string]>;\n }>;\n };\n}\n","import { GetParametersCommand, SSMClient } from '@aws-sdk/client-ssm';\nimport { z } from 'zod';\nimport { LLM_FALLBACK_DEFAULTS } from '../constants.js';\nimport { createLogger } from '../logger/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Config — reads and validates all env vars at startup.\n// The process exits immediately if a required variable is missing.\n// No silent failures, no undefined values in the codebase.\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Load secrets from SSM using SSM_PREFIX (AWS Lambda deployment)\nasync function loadSecretsFromSSM(): Promise<void> {\n const prefix = process.env.SSM_PREFIX;\n // Only run in AWS (when SSM_PREFIX is set), skip in local dev\n if (!prefix) {\n return;\n }\n\n const client = new SSMClient({});\n const names = [\n `${prefix}/llm-provider`,\n `${prefix}/llm-api-key`,\n `${prefix}/llm-model`,\n `${prefix}/slack-bot-token`,\n `${prefix}/slack-signing-secret`,\n `${prefix}/slack-channel`,\n `${prefix}/loki-url`,\n `${prefix}/redis-url`,\n `${prefix}/llm-fallback-models`,\n `${prefix}/llm-fallback-timeout-ms`,\n ];\n\n try {\n const result = await client.send(\n new GetParametersCommand({\n Names: names,\n WithDecryption: true,\n }),\n );\n\n for (const param of result.Parameters ?? []) {\n if (param.Name && param.Value) {\n // Convert /junando/llm-provider -> LLM_PROVIDER\n const key = param.Name.replace(`${prefix}/`, '').replaceAll('-', '_').toUpperCase();\n process.env[key] = param.Value;\n }\n }\n } catch (err) {\n createLogger().error({ err }, 'Failed to load SSM parameters');\n }\n}\n\nconst ConfigSchema = z\n .object({\n llmProvider: z.enum(['gemini', 'claude', 'openrouter', 'qwen']),\n llmApiKey: z.string().min(1),\n llmModel: z.string().optional().transform((v) => v === '' ? undefined : v),\n // Notifier selector — defaults to 'slack' for backward compatibility\n notifierType: z.enum(['slack', 'teams']).default('slack'),\n // Slack fields — optional at schema level; superRefine enforces them conditionally\n slackBotToken: z.string().startsWith('xoxb-').optional(),\n slackSigningSecret: z.string().min(1).optional(),\n slackChannel: z.string().startsWith('#').optional(),\n // Teams field\n teamsWebhookUrl: z.string().url().optional(),\n lokiUrl: z.string().optional().transform((v) => v === '' ? undefined : v), // URL with embedded credentials — skip .url() which rejects user:pass@ format. Optional: containers may run without Loki; logger falls back to stdout. Empty string is coerced to undefined (env var unset vs empty are equivalent).\n redisUrl: z.string().url(),\n sqsQueueUrl: z.string().url().optional().or(z.literal('')),\n dedupTtlSeconds: z.coerce.number().int().positive().default(300),\n clusterWindowMs: z.coerce.number().int().positive().default(120_000),\n logLevel: z.enum(['trace', 'debug', 'info', 'warn', 'error']).default('info'),\n nodeEnv: z.enum(['development', 'test', 'production']).default('development'),\n llmFallbackModels: z\n .string()\n .optional()\n .transform((v) => {\n if (v === undefined) return LLM_FALLBACK_DEFAULTS.Models;\n if (!v) return [];\n return v.split(',').map((s) => s.trim()).filter(Boolean);\n }),\n llmFallbackTimeoutMs: z.coerce.number().int().positive().default(LLM_FALLBACK_DEFAULTS.TimeoutMs),\n // Optional path to rules.yaml for business rules engine. When not set, rule engine is disabled.\n rulesConfigPath: z\n .string()\n .optional()\n .transform((v) => (v === '' ? undefined : v)),\n })\n .superRefine((data, ctx) => {\n if (data.notifierType === 'slack') {\n if (!data.slackBotToken) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slackBotToken'],\n message: '[notifierType: slack] SLACK_BOT_TOKEN is required and must start with xoxb-',\n });\n }\n if (!data.slackChannel) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slackChannel'],\n message: '[notifierType: slack] SLACK_CHANNEL is required and must start with #',\n });\n }\n if (!data.slackSigningSecret) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slackSigningSecret'],\n message: '[notifierType: slack] SLACK_SIGNING_SECRET is required (used to verify Slack interactivity callbacks)',\n });\n }\n }\n if (data.notifierType === 'teams') {\n if (!data.teamsWebhookUrl) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['teamsWebhookUrl'],\n message: '[notifierType: teams] TEAMS_WEBHOOK_URL is required',\n });\n } else {\n // Parse the URL and require api-version as a real query parameter,\n // not just any substring (which would accept e.g. an api-version=\n // segment baked into the URL path).\n let parsed: URL | undefined;\n try {\n parsed = new URL(data.teamsWebhookUrl);\n } catch {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['teamsWebhookUrl'],\n message: '[notifierType: teams] TEAMS_WEBHOOK_URL must be a valid URL',\n });\n }\n if (parsed && !parsed.searchParams.has('api-version')) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['teamsWebhookUrl'],\n message: '[notifierType: teams] TEAMS_WEBHOOK_URL must include api-version= as a query parameter',\n });\n }\n }\n }\n });\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport async function loadConfig(): Promise<Config> {\n await loadSecretsFromSSM();\n\n const result = ConfigSchema.safeParse({\n llmProvider: process.env['LLM_PROVIDER'],\n llmApiKey: process.env['LLM_API_KEY'],\n llmModel: process.env['LLM_MODEL'],\n notifierType: process.env['NOTIFIER_TYPE'],\n slackBotToken: process.env['SLACK_BOT_TOKEN'],\n slackSigningSecret: process.env['SLACK_SIGNING_SECRET'],\n slackChannel: process.env['SLACK_CHANNEL'],\n teamsWebhookUrl: process.env['TEAMS_WEBHOOK_URL'],\n lokiUrl: process.env['LOKI_URL'],\n redisUrl: process.env['REDIS_URL'],\n sqsQueueUrl: process.env['SQS_QUEUE_URL'],\n dedupTtlSeconds: process.env['DEDUP_TTL_SECONDS'],\n clusterWindowMs: process.env['CLUSTER_WINDOW_MS'],\n logLevel: process.env['LOG_LEVEL'],\n nodeEnv: process.env['NODE_ENV'],\n llmFallbackModels: process.env['LLM_FALLBACK_MODELS'],\n llmFallbackTimeoutMs: process.env['LLM_FALLBACK_TIMEOUT_MS'],\n rulesConfigPath: process.env['RULES_CONFIG_PATH'],\n });\n\n if (!result.success) {\n const errorMessages = result.error.issues.map(\n (issue) => `${issue.path.join('.')}: ${issue.message}`,\n );\n throw new Error(`Invalid configuration:\\n - ${errorMessages.join('\\n - ')}`);\n }\n\n return result.data;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["logger","logger","logger","parseYaml"],"sources":["../src/shared/constants.ts","../src/domain/entities/alert.ts","../src/domain/entities/cluster.ts","../src/domain/entities/incident.ts","../src/domain/entities/rule.ts","../src/domain/value-objects/fingerprint.ts","../src/domain/ports/index.ts","../src/domain/services/clustering.service.ts","../src/application/dtos/normalize-payload.ts","../src/shared/logger/loki-transport.ts","../src/shared/logger/wide-event-builder.ts","../src/shared/logger/sampling.ts","../src/shared/logger/redaction.ts","../src/shared/logger/enums.ts","../src/shared/logger/index.ts","../src/application/use-cases/process-incident.use-case.ts","../src/infrastructure/dedup/redis-dedup.adapter.ts","../src/infrastructure/indexer/opensearch.adapter.ts","../src/infrastructure/llm/llm.adapter.ts","../src/shared/factory-registry.ts","../src/infrastructure/notifier/slack.adapter.ts","../src/infrastructure/notifier/teams.adapter.ts","../src/infrastructure/notifier/routing-notifier.ts","../src/infrastructure/rules/yaml-rule-loader.ts","../src/infrastructure/rules/channel-registry.ts","../src/infrastructure/rules/condition-evaluator.ts","../src/infrastructure/rules/action-dispatcher.ts","../src/infrastructure/rules/rule-engine.ts","../src/infrastructure/notifier/factory.ts","../src/infrastructure/queue/sqs.adapter.ts","../src/infrastructure/queue/sqs-lag-poller.ts","../src/infrastructure/traces/loki-trace.adapter.ts","../src/shared/config/index.ts"],"sourcesContent":["// ─────────────────────────────────────────────────────────────────────────────\n// constants.ts — Centralized constants and enums.\n// No magic numbers. No hardcoded strings. Everything typed and named.\n// ─────────────────────────────────────────────────────────────────────────────\n\n// ── Alert Types (domain enum) ──────────────────────────────────────────────────\nexport enum AlertType {\n Error = 'http_500',\n Warning = 'latency_spike',\n Success = 'recovery',\n}\n\ninterface AlertTypeConfig {\n readonly alertName: string;\n readonly severity: string;\n readonly summary: (service: string, i: number, count: number) => string;\n}\n\nconst _alertTypeConfigs: Record<AlertType, AlertTypeConfig> = {\n [AlertType.Error]: {\n alertName: 'HighErrorRate',\n severity: 'critical',\n summary: (service, i, count) => `High error rate on ${service} — alert ${i + 1}/${count}`,\n },\n [AlertType.Warning]: {\n alertName: 'HighLatency',\n severity: 'warning',\n summary: (service, i, count) => `High latency detected on ${service} — alert ${i + 1}/${count}`,\n },\n [AlertType.Success]: {\n alertName: 'ServiceRecovered',\n severity: 'info',\n summary: (service, i, count) =>\n `Service ${service} has recovered and is operating normally — alert ${i + 1}/${count}`,\n },\n};\n\nexport const ALERT_TYPE_LABELS: Readonly<typeof _alertTypeConfigs> =\n Object.freeze(_alertTypeConfigs);\n\n// ── LLM Provider ───────────────────────────────────────────────────────────────\nexport enum LLMProviderType {\n Gemini = 'gemini',\n Claude = 'claude',\n OpenRouter = 'openrouter',\n Qwen = 'qwen',\n}\n\n// ── HTTP / Timeout Constants ───────────────────────────────────────────────────\nexport const HTTP_TIMEOUT_MS = Object.freeze({\n Default: 5_000,\n LLM: 30_000,\n});\n\nexport const CIRCUIT_BREAKER = Object.freeze({\n Timeout: 10_000,\n ErrorThresholdPercentage: 70,\n ResetTimeoutMs: 30_000,\n});\n\nexport const LLM_MAX_TOKENS = 1_024;\n\n// ── Rate Limiter Constants ─────────────────────────────────────────────────────\nexport const RATE_LIMITER = Object.freeze({\n MinTimeMs: 100,\n MaxConcurrent: 5,\n});\n\n// ── Dev Server ────────────────────────────────────────────────────────────────\nexport const DEV_SERVER_PORT = 4_000;\n\n// ── Deduplication ─────────────────────────────────────────────────────────────\nexport const DEDUP_TTL_MS_MULTIPLIER = 1_000;\n\n// ── Time Conversions ───────────────────────────────────────────────────────────\nexport const HOUR_MS = 3_600_000;\n\n// ── LLM Fallback Defaults ─────────────────────────────────────────────────\nexport const LLM_FALLBACK_DEFAULTS = Object.freeze({\n TimeoutMs: 60_000,\n Models: [\n 'google/gemma-4-31b-it:free',\n 'meta-llama/llama-3.3-70b-instruct:free',\n 'mistralai/mistral-7b-instruct:free',\n ] as string[],\n});\n\n// ── LLM Models ────────────────────────────────────────────────────────────────\nexport const LLM_MODELS = Object.freeze({\n Gemini: 'gemini-2.0-flash',\n Claude: 'claude-haiku-4-5',\n OpenRouter: 'qwen/qwen-2.5-72b-instruct',\n});\n\n// ── Slack ─────────────────────────────────────────────────────────────────────\nexport const SLACK_API_URL = 'https://slack.com/api/chat.postMessage';\n\n// ── Teams ─────────────────────────────────────────────────────────────────────\nexport const TEAMS_WEBHOOK_TIMEOUT_MS = 10_000;\n\nconst _urgencyEmoji: Record<string, string> = {\n critical: '🔴',\n high: '🟠',\n medium: '🟡',\n low: '🟢',\n};\nexport const URGENCY_EMOJI: Readonly<typeof _urgencyEmoji> = Object.freeze(_urgencyEmoji);\n\n// ── Redis Keys ────────────────────────────────────────────────────────────────\nexport const REDIS_KEY_PREFIX = 'junando:dedup:';\n\n// ── Webhook Defaults ──────────────────────────────────────────────────────────\nexport const WEBHOOK_DEFAULTS = Object.freeze({\n AlertmanagerUrl: 'http://localhost:9093',\n WebhookUrl: 'http://localhost:4000/webhook/alert',\n});\n\nexport const PAYLOAD_DEFAULTS = Object.freeze({\n Version: '4',\n TruncatedAlerts: 0,\n Receiver: 'junando',\n});\n","import { z } from 'zod';\nimport { AlertType } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Alert — the core domain entity.\n// Represents a single normalized alert from any source (Alertmanager, etc.)\n// The domain doesn't care where it came from — only what it means.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const AlertStatusSchema = z.enum(['firing', 'resolved']);\n\nexport const NormalizedAlertSchema = z.object({\n fingerprint: z.string(),\n alertName: z.string(),\n status: AlertStatusSchema,\n serviceName: z.string(),\n alertType: z.nativeEnum(AlertType),\n endpointPath: z.string(),\n traceId: z.string().optional(),\n startsAt: z.string().datetime(),\n latencyMs: z.number().optional(),\n labels: z.record(z.string(), z.string()),\n annotations: z.record(z.string(), z.string()),\n});\n\n// Raw Alertmanager webhook payload — validated at the boundary (Lambda A)\n// then normalized into NormalizedAlert before entering the domain.\nexport const AlertmanagerPayloadSchema = z.object({\n version: z.string().default('4'),\n groupKey: z.string(),\n truncatedAlerts: z.number().default(0),\n status: AlertStatusSchema,\n receiver: z.string(),\n groupLabels: z.record(z.string(), z.string()),\n commonLabels: z.record(z.string(), z.string()),\n commonAnnotations: z.record(z.string(), z.string()),\n externalURL: z.string().url(),\n alerts: z\n .array(\n z.object({\n status: AlertStatusSchema,\n labels: z.record(z.string(), z.string()),\n annotations: z.record(z.string(), z.string()).default({}),\n startsAt: z.string().datetime(),\n endsAt: z.string().datetime(),\n fingerprint: z.string().optional(),\n }),\n )\n .min(1),\n});\n\nexport type AlertStatus = z.infer<typeof AlertStatusSchema>;\nexport type NormalizedAlert = z.infer<typeof NormalizedAlertSchema>;\nexport type AlertmanagerPayload = z.infer<typeof AlertmanagerPayloadSchema>;\n\n// Backward-compat alias — errorType is now alertType\nexport type AlertErrorType = NormalizedAlert['alertType'];\n","import { z } from 'zod';\nimport { AlertType } from '../../shared/constants.js';\n\n/**\n * Severity level enum values — defined inline to avoid circular dependency\n * (cluster.ts → rule.ts → incident.ts → cluster.ts).\n * Must stay in sync with SeverityLevel in domain/entities/rule.ts.\n */\nconst SEVERITY_VALUES = ['critical', 'high', 'medium', 'low'] as const;\n\nexport const AlertClusterSchema = z.object({\n fingerprint: z.string(),\n serviceName: z.string(),\n alertType: z.nativeEnum(AlertType), // typed, not raw string\n endpointPath: z.string(),\n alertCount: z.number().int().positive(),\n representativeTraceIds: z.array(z.string()).max(2),\n firstSeenAt: z.string().datetime(),\n latencyP99Ms: z.number().optional(),\n /** Severity level — can be derived from ALERT_TYPE_LABELS[alertType].severity */\n severity: z.enum(SEVERITY_VALUES).optional(),\n /** Arbitrary key-value labels passed through from alerts */\n labels: z.record(z.string(), z.string()).optional(),\n});\n\nexport type AlertCluster = z.infer<typeof AlertClusterSchema>;\n","import { z } from 'zod';\nimport { AlertClusterSchema } from './cluster.js';\n\nexport const UrgencyLevelSchema = z.enum(['low', 'medium', 'high', 'critical']);\n\n// Strict JSON schema the LLM MUST return. Validated with Zod after parsing.\nexport const LLMAnalysisSchema = z.object({\n probable_cause: z.string().min(1),\n impacted_services: z.array(z.string()).min(1),\n recommended_steps: z.array(z.string()).min(1).max(5),\n urgency_level: UrgencyLevelSchema,\n requires_rollback: z.boolean(),\n});\n\nexport const IncidentSchema = z.object({\n cluster: AlertClusterSchema,\n traces: z.array(z.record(z.string(), z.unknown())).optional(),\n analysis: LLMAnalysisSchema.optional(), // absent if LLM failed gracefully\n processedAt: z.string().datetime(),\n});\n\nexport type UrgencyLevel = z.infer<typeof UrgencyLevelSchema>;\nexport type LLMAnalysis = z.infer<typeof LLMAnalysisSchema>;\nexport type Incident = z.infer<typeof IncidentSchema>;\n","import { z } from 'zod';\nimport { AlertType } from '../../shared/constants.js';\nimport { UrgencyLevelSchema } from './incident.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Enums — single source of truth for repeated values\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum RuleActionType {\n Suppress = 'suppress',\n Route = 'route',\n Escalate = 'escalate',\n Tag = 'tag',\n}\n\nexport enum SeverityLevel {\n Critical = 'critical',\n High = 'high',\n Medium = 'medium',\n Low = 'low',\n}\n\n/** Rule evaluation points in the pipeline */\nexport enum RuleEvaluationPhase {\n PreLlm = 'pre-llm',\n PostLlm = 'post-llm',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleCondition — what can be matched in a rule\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RuleCondition {\n serviceName?: string;\n alertType?: AlertType;\n severity?: SeverityLevel;\n labels?: Record<string, string>;\n endpointPath?: string;\n alertCount?: { min?: number; max?: number };\n latencyP99Ms?: { min?: number; max?: number };\n /** POST-LLM only (analysis must be present) */\n urgencyLevel?: z.infer<typeof UrgencyLevelSchema>;\n requiresRollback?: boolean;\n impactedServices?: string[];\n}\n\nconst AlertCountSchema = z.object({\n min: z.number().optional(),\n max: z.number().optional(),\n});\n\nconst LatencySchema = z.object({\n min: z.number().optional(),\n max: z.number().optional(),\n});\n\nexport const RuleConditionSchema = z.object({\n serviceName: z.string().optional(),\n alertType: z.nativeEnum(AlertType).optional(),\n severity: z.nativeEnum(SeverityLevel).optional(),\n labels: z.record(z.string(), z.string()).optional(),\n endpointPath: z.string().optional(),\n alertCount: AlertCountSchema.optional(),\n latencyP99Ms: LatencySchema.optional(),\n urgencyLevel: UrgencyLevelSchema.optional(),\n requiresRollback: z.boolean().optional(),\n impactedServices: z.array(z.string()).optional(),\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleAction — discriminated union\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type RuleAction =\n | { type: RuleActionType.Suppress }\n | { type: RuleActionType.Route; channel: string }\n | { type: RuleActionType.Escalate; channel: string }\n | { type: RuleActionType.Tag; key: string; value: string };\n\nconst SUPPRESS_SCHEMA = z.object({ type: z.literal(RuleActionType.Suppress) });\nconst ROUTE_SCHEMA = z.object({ type: z.literal(RuleActionType.Route), channel: z.string().min(1) });\nconst ESCALATE_SCHEMA = z.object({ type: z.literal(RuleActionType.Escalate), channel: z.string().min(1) });\nconst TAG_SCHEMA = z.object({ type: z.literal(RuleActionType.Tag), key: z.string().min(1), value: z.string().min(1) });\n\nexport const RuleActionSchema = z.discriminatedUnion('type', [\n SUPPRESS_SCHEMA,\n ROUTE_SCHEMA,\n ESCALATE_SCHEMA,\n TAG_SCHEMA,\n]);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Rule — single rule definition\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Rule {\n id: string;\n name?: string;\n condition: RuleCondition;\n actions: RuleAction[];\n /** POST-LLM only (can only be used in post-llm section) */\n urgencyLevel?: z.infer<typeof UrgencyLevelSchema>;\n requiresRollback?: boolean;\n}\n\nexport const RuleSchema = z.object({\n id: z.string().min(1),\n name: z.string().optional(),\n condition: RuleConditionSchema,\n actions: z.array(RuleActionSchema).min(1),\n urgencyLevel: UrgencyLevelSchema.optional(),\n requiresRollback: z.boolean().optional(),\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleSection — pre-llm or post-llm rules\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RuleSection {\n rules: Rule[];\n}\n\nexport const RuleSectionSchema = z.object({\n rules: z.array(RuleSchema).default([]),\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleConfiguration — full YAML shape\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface RuleConfiguration {\n [RuleEvaluationPhase.PreLlm]: RuleSection;\n [RuleEvaluationPhase.PostLlm]: RuleSection;\n}\n\nexport const RuleConfigurationSchema = z.object({\n [RuleEvaluationPhase.PreLlm]: RuleSectionSchema,\n [RuleEvaluationPhase.PostLlm]: RuleSectionSchema,\n});\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Validated types (from Zod schemas)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type ValidatedRuleCondition = z.infer<typeof RuleConditionSchema>;\nexport type ValidatedRuleAction = z.infer<typeof RuleActionSchema>;\nexport type ValidatedRule = z.infer<typeof RuleSchema>;\nexport type ValidatedRuleSection = z.infer<typeof RuleSectionSchema>;\nexport type ValidatedRuleConfiguration = z.infer<typeof RuleConfigurationSchema>;\n","import { createHash } from 'node:crypto';\nimport type { NormalizedAlert } from '../entities/alert.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Fingerprint — a Value Object in DDD terms.\n// Immutable, identity based on value not reference.\n// Encapsulates the hashing algorithm so it's swappable in one place.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class Fingerprint {\n private constructor(readonly value: string) {}\n\n static fromAlert(alert: NormalizedAlert): Fingerprint {\n const input = [\n alert.serviceName.toLowerCase().trim(),\n alert.alertType.toLowerCase().trim(),\n alert.endpointPath.toLowerCase().trim(),\n ].join('|');\n\n const hash = createHash('sha256').update(input).digest('hex');\n return new Fingerprint(hash);\n }\n\n equals(other: Fingerprint): boolean {\n return this.value === other.value;\n }\n\n toString(): string {\n return this.value;\n }\n}\n","import type { NormalizedAlert } from '../entities/alert.js';\nimport type { AlertCluster } from '../entities/cluster.js';\nimport type { LLMAnalysis } from '../entities/incident.js';\nimport type { RuleAction } from '../entities/rule.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PORTS — interfaces defined by the domain.\n// The domain owns these. Infrastructure implements them.\n// Never import a concrete class in this file.\n//\n// Swapping Redis for DynamoDB = new IDeduplicationStore adapter\n// Swapping Loki for Datadog = new ITraceRepository adapter\n// Swapping Gemini for Claude = new ILLMProvider adapter\n// Swapping Slack for Teams = new INotifier adapter\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Structured result of a deduplication check.\n * Feeds the `dedup` section of the wide event.\n */\nexport interface DedupResult {\n isNew: boolean;\n ttlSeconds: number;\n /** Fail-open error message when the store was unreachable (e.g. Redis down). */\n error?: string;\n}\n\n/**\n * Deduplication store.\n * Determines whether an alert fingerprint is new within a rolling TTL window.\n * Implementations: RedisDeduplicationStore, InMemoryDeduplicationStore (tests)\n */\nexport interface IDeduplicationStore {\n isNew(fingerprint: string, ttlSeconds: number): Promise<DedupResult>;\n reset(fingerprint: string): Promise<void>;\n}\n\n/**\n * Alert queue.\n * Publishes normalized alerts for async processing.\n * Implementations: SQSAlertQueue, BullMQAlertQueue, InMemoryAlertQueue (tests)\n */\nexport interface IAlertQueue {\n publish(alert: NormalizedAlert): Promise<void>;\n}\n\n/**\n * Trace repository.\n * Fetches distributed trace context by trace ID.\n * Implementations: LokiTraceRepository, DatadogTraceRepository, MockTraceRepository (tests)\n */\nexport interface ITraceRepository {\n findByTraceId(traceId: string): Promise<Record<string, unknown>[]>;\n}\n\n/**\n * Structured result of an LLM analysis call.\n * Carries the diagnosis plus the observability metadata that feeds the\n * `llm` section of the wide event (urgency comes from analysis.urgency_level;\n * total tokens = promptTokens + completionTokens).\n */\nexport interface LLMResult {\n analysis: LLMAnalysis;\n provider: string;\n model: string;\n latencyMs: number;\n promptTokens: number;\n completionTokens: number;\n}\n\n/**\n * LLM provider.\n * Analyzes an incident cluster and returns a structured diagnosis.\n * Implementations: GeminiProvider, ClaudeProvider, OpenAIProvider, MockLLMProvider (tests)\n */\nexport interface ILLMProvider {\n analyze(cluster: AlertCluster, traces: Record<string, unknown>[]): Promise<LLMResult>;\n}\n\n/**\n * Terminal outcome of a single notification send.\n * Feeds the `notify` section of the wide event.\n */\nexport const NotifyOutcome = {\n Success: 'success',\n Failure: 'failure',\n} as const;\nexport type NotifyOutcome = (typeof NotifyOutcome)[keyof typeof NotifyOutcome];\n\n/**\n * Structured result of a notification send.\n * Adapters throw on failure (the caller records NotifyOutcome.Failure and\n * rethrows for the queue retry), so a resolved promise always carries\n * outcome=success.\n */\nexport interface NotifyResult {\n outcome: NotifyOutcome;\n latencyMs: number;\n /** Concrete channels the notification was delivered to. */\n channels: string[];\n}\n\n/**\n * Notifier.\n * Delivers incident diagnoses to a ChatOps channel.\n * Implementations: SlackNotifier, TeamsNotifier, ConsoleNotifier (local dev/tests)\n */\nexport interface INotifier {\n /**\n * Deliver an incident diagnosis to a ChatOps channel.\n * @param channel — optional channel override for multi-channel routing.\n * When provided, implementations MAY route to the specified channel\n * instead of their default. Backward-compatible — existing call sites\n * work unchanged.\n */\n send(cluster: AlertCluster, analysis: LLMAnalysis | null, channel?: string): Promise<NotifyResult>;\n}\n\n/**\n * Indexer.\n * Persists a typed document into a searchable index/store.\n * Implementations: OpenSearchIndexer, InMemoryIndexer (tests)\n */\nexport interface IIndexer<TDocument> {\n index(doc: TDocument): Promise<void>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Rule engine — evaluate rules at pipeline hooks\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Result of rule engine evaluation.\n */\nexport interface RuleActionResult {\n suppressed: boolean; // true = don't proceed to next stage\n actions: RuleAction[]; // actions to execute\n matchedRuleId?: string; // which rule matched (for debugging)\n tags: Record<string, string>; // tags to attach to cluster\n}\n\n/**\n * Rule engine port — evaluates rules at pipeline hooks.\n */\nexport interface IRuleEngine {\n /**\n * PRE-LLM: evaluated after dedup, before LLM.\n * Returns actions to apply, or suppressed=true if should not proceed.\n */\n evaluatePreLlm(cluster: AlertCluster): RuleActionResult;\n\n /**\n * POST-LLM: evaluated after LLM analysis.\n * Returns additional actions based on analysis (escalate, tag, etc.).\n */\n evaluatePostLlm(cluster: AlertCluster, analysis: LLMAnalysis): RuleActionResult;\n}\n","import type { NormalizedAlert } from '../entities/alert.js';\nimport type { AlertCluster } from '../entities/cluster.js';\nimport { Fingerprint } from '../value-objects/fingerprint.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ClusteringService — Domain Service.\n// Pure business logic. No I/O, no external deps.\n// Groups alerts and selects representative samples.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ClusteringService {\n /**\n * Groups alerts by fingerprint and builds AlertCluster objects.\n * 300 alerts with the same root cause → 1 cluster with 2 representative traces.\n */\n buildClusters(alerts: NormalizedAlert[]): AlertCluster[] {\n const groups = new Map<string, NormalizedAlert[]>();\n\n for (const alert of alerts) {\n const fp = Fingerprint.fromAlert(alert).toString();\n const group = groups.get(fp) ?? [];\n group.push(alert);\n groups.set(fp, group);\n }\n\n return Array.from(groups.entries()).map(([fp, group]) => this.buildCluster(fp, group));\n }\n\n private buildCluster(fingerprint: string, alerts: NormalizedAlert[]): AlertCluster {\n const sorted = [...alerts].sort(\n (a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),\n );\n\n const first = sorted[0];\n if (!first) throw new Error('Empty alert group — this should never happen');\n const traceIds = this.sampleTraceIds(alerts);\n const latencies = alerts.map((a) => a.latencyMs ?? 0);\n const sortedLatencies = [...latencies].sort((a, b) => a - b);\n const p99Index = Math.floor(sortedLatencies.length * 0.99);\n const p99 = sortedLatencies[Math.min(p99Index, sortedLatencies.length - 1)] ?? 0;\n\n return {\n fingerprint,\n serviceName: first.serviceName,\n alertType: first.alertType,\n endpointPath: first.endpointPath,\n alertCount: alerts.length,\n representativeTraceIds: traceIds,\n firstSeenAt: first.startsAt,\n latencyP99Ms: p99,\n };\n }\n\n private sampleTraceIds(alerts: NormalizedAlert[]): string[] {\n const withTraces = alerts.filter(\n (a): a is NormalizedAlert & { traceId: string } =>\n typeof a.traceId === 'string' && a.traceId.length > 0,\n );\n if (withTraces.length === 0) return [];\n\n const sorted = [...withTraces].sort(\n (a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime(),\n );\n const first = sorted[0];\n if (!first) throw new Error('Empty alert group with traces — this should never happen');\n const slowest = withTraces.reduce(\n (max, a) => ((a.latencyMs ?? 0) > (max.latencyMs ?? 0) ? a : max),\n first,\n );\n\n return slowest.traceId === first.traceId ? [first.traceId] : [first.traceId, slowest.traceId];\n }\n}\n","import type { AlertmanagerPayload, NormalizedAlert } from '../../domain/entities/alert.js';\nimport { AlertType } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// normalizePayload()\n// Maps the raw Alertmanager webhook payload → domain NormalizedAlert[].\n// This is the anti-corruption layer: external format never leaks into domain.\n// If Alertmanager changes its payload shape, only this file needs updating.\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst ERROR_TYPE_TO_ALERT_TYPE: Record<string, AlertType> = {\n http_500: AlertType.Error,\n latency_spike: AlertType.Warning,\n recovery: AlertType.Success,\n};\n\nfunction toAlertType(raw: string): AlertType {\n return ERROR_TYPE_TO_ALERT_TYPE[raw] ?? AlertType.Error;\n}\n\nexport function normalizePayload(payload: AlertmanagerPayload): NormalizedAlert[] {\n return payload.alerts\n .filter((a) => a.status === 'firing') // ignore resolved alerts in MVP\n .map(\n (a): NormalizedAlert => ({\n fingerprint:\n a.fingerprint ?? `${a.labels['alertname']}-${a.labels['service']}-${Date.now()}`,\n alertName: a.labels['alertname'] ?? 'unknown',\n status: a.status,\n serviceName: a.labels['service'] ?? a.labels['job'] ?? 'unknown-service',\n alertType: toAlertType(a.labels['error_type'] ?? a.labels['alertname'] ?? ''),\n endpointPath: a.labels['endpoint'] ?? a.annotations['endpoint'] ?? '/',\n traceId: a.labels['trace_id'] ?? a.annotations['trace_id'],\n startsAt: a.startsAt,\n latencyMs: a.labels['latency_ms'] ? Number(a.labels['latency_ms']) : undefined,\n labels: a.labels,\n annotations: a.annotations,\n }),\n );\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// LokiBuffer — synchronous in-process Loki transport for Lambda.\n//\n// pino-abstract-transport runs in a worker_thread that Lambda kills before\n// the fetch completes. This module buffers log entries in-process and flushes\n// them in a single HTTP request at the END of the handler, before Lambda exits.\n//\n// Usage:\n// 1. LokiBuffer captures pino log lines via a WritableStream destination.\n// 2. At the end of the handler, call `flushLoki()` to push all buffered logs.\n// ─────────────────────────────────────────────────────────────────────────────\n\nimport { Writable } from 'node:stream';\n\ninterface LokiConfig {\n host: string;\n username: string;\n password: string;\n labels: Record<string, string>;\n}\n\ninterface LokiStream {\n stream: Record<string, string>;\n values: [string, string][];\n}\n\n/**\n * Maximum number of buffered log entries before the oldest is dropped.\n *\n * Acts as a ring buffer to prevent memory leaks if `flushLoki()` is never called\n * (e.g. a new handler forgets to wire it in) or if Loki pushes fail repeatedly.\n * When the buffer is full, the oldest entry is dropped to keep memory bounded.\n *\n * 1000 lines × ~1KB/line ≈ 1MB worst case, well within Lambda memory limits.\n */\nconst MAX_BUFFER_ENTRIES = 1000;\n\nlet _config: LokiConfig | null = null;\nconst _buffer: [string, string][] = []; // [nanosTimestamp, line]\n\n/**\n * Initialize the Loki buffer with connection config.\n * Call this once after loadConfig() sets LOKI_URL.\n */\nexport function initLokiBuffer(config: LokiConfig): void {\n _config = config;\n _buffer.length = 0;\n}\n\n/**\n * Returns a pino-compatible Writable destination that buffers log lines.\n * Pass this as the second argument to pino().\n */\nexport function createLokiDestination(): Writable {\n return new Writable({\n write(chunk: Buffer, _encoding, callback) {\n try {\n const line = chunk.toString().trim();\n if (!line) {\n callback();\n return;\n }\n // Loki requires nanosecond timestamps as STRINGS.\n // pino can emit `time` as either ISO string (isoTime) or number (epochTime).\n // Handle both, and fall back to Date.now() if missing/unparseable.\n let tsNs: string;\n try {\n const parsed = JSON.parse(line) as { time?: number | string };\n let ms = Date.now();\n if (typeof parsed.time === 'number' && Number.isFinite(parsed.time)) {\n ms = parsed.time;\n } else if (typeof parsed.time === 'string') {\n const parsedMs = Date.parse(parsed.time);\n if (Number.isFinite(parsedMs)) ms = parsedMs;\n }\n tsNs = String(ms * 1_000_000);\n } catch {\n tsNs = String(Date.now() * 1_000_000);\n }\n // Ring buffer: drop oldest entry when full to prevent unbounded growth.\n if (_buffer.length >= MAX_BUFFER_ENTRIES) {\n _buffer.shift();\n }\n _buffer.push([tsNs, line]);\n } catch {\n // never fail the logger\n }\n callback();\n },\n objectMode: false,\n });\n}\n\n/**\n * Flush all buffered log entries to Loki in a single HTTP request.\n * Call this at the END of the Lambda handler, after all business logic completes.\n * Errors are swallowed — Loki is best-effort; CloudWatch is the primary sink.\n */\nexport async function flushLoki(): Promise<void> {\n if (!_config || _buffer.length === 0) return;\n\n const { host, username, password, labels } = _config;\n const values = [..._buffer];\n _buffer.length = 0;\n\n const stream: LokiStream = { stream: labels, values };\n\n try {\n const res = await fetch(`${host}/loki/api/v1/push`, {\n method: 'POST',\n signal: AbortSignal.timeout(5_000),\n headers: {\n 'Content-Type': 'application/json',\n Authorization: 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'),\n },\n body: JSON.stringify({ streams: [stream] }),\n });\n if (!res.ok) {\n process.stderr.write(`[junando] Loki flush failed: ${res.status} ${await res.text()}\\n`);\n }\n } catch (err) {\n process.stderr.write(`[junando] Loki flush error: ${(err as Error).message}\\n`);\n }\n}\n","import type { Component, Outcome } from './enums.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// WideEvent — one canonical structured log line per processing unit.\n//\n// Entry points (webhook, worker, ingest) create a WideEventBuilder at request\n// start; pipeline stages accumulate results into it; flush() emits the final\n// event. Unset optional fields are omitted from the output.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Maximum serialized event size: 256 KB. Events beyond this are truncated. */\nconst MAX_EVENT_BYTES = 256 * 1024;\n\n/**\n * Per-string cap applied when an event exceeds MAX_EVENT_BYTES.\n * ~256 strings × 1 KB would still fit; real events have far fewer fields.\n */\nconst OVERSIZED_STRING_CAP = 1024;\n\nexport interface ClusterSection {\n fingerprint: string;\n serviceName: string;\n alertCount: number;\n spanCount: number;\n /** Number of representative traces that failed to fetch (fail-open). */\n traceErrors?: number;\n}\n\nexport interface DedupSection {\n isNew: boolean;\n ttlSeconds: number;\n /** Fail-open error message when the store was unreachable (e.g. Redis down). */\n error?: string;\n}\n\nexport interface RuleSection {\n matched: boolean;\n suppressed: boolean;\n /** ID of the rule that matched, when the engine reports one. */\n matchedRuleId?: string;\n}\n\nexport interface LlmSection {\n provider: string;\n model: string;\n latencyMs: number;\n urgency: string;\n tokens: number;\n}\n\nexport interface NotifySection {\n channels: string[];\n outcome: string;\n latencyMs: number;\n}\n\nexport interface ErrorSection {\n message: string;\n name?: string;\n stack?: string;\n}\n\nexport interface WideEvent {\n requestId: string;\n correlationId?: string;\n timestamp: string;\n component: Component;\n version?: string;\n outcome?: Outcome;\n cluster?: ClusterSection;\n dedup?: DedupSection;\n rule?: RuleSection;\n llm?: LlmSection;\n notify?: NotifySection;\n durationMs?: number;\n error?: ErrorSection;\n /** Present only when the 256 KB guard truncated the event. */\n _truncated?: boolean;\n}\n\n/** Fields the builder owns — callers cannot set or merge them. */\ntype BuilderOwnedKey = 'requestId' | 'component' | 'timestamp' | '_truncated';\n\n/** Keys callers may write via set()/merge(). */\ntype SettableKey = Exclude<keyof WideEvent, BuilderOwnedKey>;\n\nfunction serializedBytes(value: unknown): number {\n return Buffer.byteLength(JSON.stringify(value), 'utf8');\n}\n\nfunction shrinkStrings(value: unknown, cap: number): unknown {\n if (typeof value === 'string') {\n return value.length > cap ? value.slice(0, cap) : value;\n }\n if (Array.isArray(value)) {\n return value.map((item) => shrinkStrings(item, cap));\n }\n if (value !== null && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, shrinkStrings(item, cap)]),\n );\n }\n return value;\n}\n\nexport class WideEventBuilder {\n private fields: Partial<WideEvent> = {};\n\n constructor(\n private readonly requestId: string,\n private readonly component: Component,\n ) {}\n\n set<K extends SettableKey>(key: K, value: WideEvent[K]): this {\n this.fields = { ...this.fields, [key]: value };\n return this;\n }\n\n merge(obj: Partial<WideEvent>): this {\n const { requestId: _r, component: _c, timestamp: _t, _truncated: _x, ...rest } = obj;\n this.fields = { ...this.fields, ...rest };\n return this;\n }\n\n flush(): WideEvent {\n const event: WideEvent = {\n requestId: this.requestId,\n component: this.component,\n timestamp: new Date().toISOString(),\n ...this.fields,\n };\n return this.enforceSizeLimit(event);\n }\n\n private enforceSizeLimit(event: WideEvent): WideEvent {\n if (serializedBytes(event) <= MAX_EVENT_BYTES) {\n return event;\n }\n const shrunk = shrinkStrings(event, OVERSIZED_STRING_CAP) as WideEvent;\n return { ...shrunk, _truncated: true };\n }\n}\n","import type { WideEvent } from './wide-event-builder.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tail sampling — decided at flush time so errors and slow events are never\n// lost: errors 100%, durationMs > 10s 100%, everything else ~5%.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Events slower than this (ms) are always sampled. */\nexport const SLOW_EVENT_THRESHOLD_MS = 10_000;\n\n/** Probability of sampling a normal (non-error, non-slow) event. */\nexport const NORMAL_SAMPLE_RATE = 0.05;\n\n/**\n * Decides whether a wide event should be emitted.\n *\n * Pure function over the event — the only non-determinism is Math.random()\n * for the normal path, which tests can stub.\n */\nexport function shouldSample(event: WideEvent): boolean {\n if (event.error != null) {\n return true;\n }\n if (event.durationMs !== undefined && event.durationMs > SLOW_EVENT_THRESHOLD_MS) {\n return true;\n }\n return Math.random() < NORMAL_SAMPLE_RATE;\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// PII redaction — whitelist strategy: only schema-known fields survive.\n// Anything outside the WideEvent schema is replaced with [REDACTED]; a\n// blacklist would leak the moment a new sensitive field name appears.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Replacement value for any field outside the whitelist. */\nexport const REDACTED = '[REDACTED]';\n\n/** Whitelisted strings longer than this are cut and suffixed. */\nexport const MAX_STRING_CHARS = 1000;\n\n/** Suffix appended to truncated strings so truncation is visible in queries. */\nexport const TRUNCATION_SUFFIX = '...[truncated]';\n\nconst ERROR_KEY = 'error';\nconst MESSAGE_KEY = 'message';\nconst NAME_KEY = 'name';\nconst STACK_KEY = 'stack';\nconst DEVELOPMENT = 'development';\nconst OUTCOME_KEY = 'outcome';\n\n/**\n * Top-level fields allowed to pass through. `cluster`, `dedup`, `rule`,\n * `llm` and `notify` are schema-known subtrees whose nested values are safe.\n */\nconst SAFE_FIELDS: ReadonlySet<string> = new Set([\n 'requestId',\n 'correlationId',\n 'timestamp',\n 'component',\n 'version',\n OUTCOME_KEY,\n 'cluster',\n 'dedup',\n 'rule',\n 'llm',\n 'notify',\n 'durationMs',\n ERROR_KEY,\n]);\n\nfunction truncateString(value: string): string {\n return value.length > MAX_STRING_CHARS\n ? value.slice(0, MAX_STRING_CHARS) + TRUNCATION_SUFFIX\n : value;\n}\n\nfunction redactValue(value: unknown): unknown {\n if (typeof value === 'string') {\n return truncateString(value);\n }\n if (Array.isArray(value)) {\n return value.map(redactValue);\n }\n if (value !== null && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, redactValue(item)]),\n );\n }\n return value;\n}\n\n/**\n * The error section keeps only message and name; the stack is kept solely in\n * development, where it cannot reach production log stores.\n */\nfunction redactError(error: Record<string, unknown>): Record<string, unknown> {\n const safe: Record<string, unknown> = {};\n if (typeof error[MESSAGE_KEY] === 'string') {\n safe[MESSAGE_KEY] = truncateString(error[MESSAGE_KEY]);\n }\n if (typeof error[NAME_KEY] === 'string') {\n safe[NAME_KEY] = truncateString(error[NAME_KEY]);\n }\n if (process.env['NODE_ENV'] === DEVELOPMENT && typeof error[STACK_KEY] === 'string') {\n safe[STACK_KEY] = truncateString(error[STACK_KEY]);\n }\n return safe;\n}\n\n/**\n * Deep-redacts an object against the wide-event whitelist.\n *\n * Returns a new object — the input is never mutated. Whitelisted values keep\n * their structure with over-long strings truncated; everything else becomes\n * [REDACTED].\n */\nexport function redact(obj: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(obj).map(([key, value]) => {\n if (!SAFE_FIELDS.has(key)) {\n return [key, REDACTED];\n }\n if (key === ERROR_KEY && value !== null && typeof value === 'object' && !Array.isArray(value)) {\n return [key, redactError(value as Record<string, unknown>)];\n }\n return [key, redactValue(value)];\n }),\n );\n}\n","/**\n * Pipeline component taxonomy.\n *\n * `component` (not `service`) distinguishes pipeline stages in wide events.\n * `service` stays constant (\"junando\"); `component` tells you WHERE the event\n * was emitted from.\n */\nexport const Component = {\n Webhook: 'webhook',\n Worker: 'worker',\n UseCase: 'useCase',\n Llm: 'llm',\n Notifier: 'notifier',\n Dedup: 'dedup',\n Traces: 'traces',\n Ingest: 'ingest',\n} as const;\nexport type Component = (typeof Component)[keyof typeof Component];\n\n/**\n * Pipeline stages that write their results into the WideEventBuilder.\n */\nexport const Stage = {\n Dedup: 'dedup',\n RulesPre: 'rulesPre',\n Traces: 'traces',\n Llm: 'llm',\n RulesPost: 'rulesPost',\n Notify: 'notify',\n} as const;\nexport type Stage = (typeof Stage)[keyof typeof Stage];\n\n/**\n * Terminal outcomes for a wide event across all entry points.\n */\nexport const Outcome = {\n Success: 'success',\n Suppressed: 'suppressed',\n Degraded: 'degraded',\n Error: 'error',\n Accepted: 'accepted',\n Empty: 'empty',\n ParseError: 'parse_error',\n} as const;\nexport type Outcome = (typeof Outcome)[keyof typeof Outcome];\n\n/**\n * Reason recorded for a tail-sampling decision.\n */\nexport const SamplingDecision = {\n Error: 'error',\n Slow: 'slow',\n Random: 'random',\n Skipped: 'skipped',\n} as const;\nexport type SamplingDecision = (typeof SamplingDecision)[keyof typeof SamplingDecision];\n","import pino from 'pino';\nimport { createLokiDestination, initLokiBuffer } from './loki-transport.js';\n\nexport type Logger = pino.Logger;\n\n// Wide events: canonical one-line-per-processing-unit logging.\nexport { WideEventBuilder } from './wide-event-builder.js';\nexport type {\n WideEvent,\n ClusterSection,\n DedupSection,\n RuleSection,\n LlmSection,\n NotifySection,\n ErrorSection,\n} from './wide-event-builder.js';\nexport { shouldSample, SLOW_EVENT_THRESHOLD_MS, NORMAL_SAMPLE_RATE } from './sampling.js';\nexport { redact, REDACTED, MAX_STRING_CHARS, TRUNCATION_SUFFIX } from './redaction.js';\nexport { Component, Stage, Outcome, SamplingDecision } from './enums.js';\n\nexport interface LoggerOptions {\n level?: string;\n name?: string;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Proxy Logger — solves the Lambda cold-start problem.\n//\n// Module-level code runs BEFORE loadConfig() sets LOKI_URL. If we create pino\n// loggers at import time, they always get stdout (no Loki).\n//\n// Solution: every createLogger() call returns a Proxy that forwards all method\n// calls to the *current* root logger. When reinitLogger() is called after\n// loadConfig(), it swaps the root — and ALL existing proxy instances instantly\n// start writing to both stdout and Loki without needing to be recreated.\n// ─────────────────────────────────────────────────────────────────────────────\n\nlet _root: pino.Logger = buildLogger({});\n\nfunction buildLogger(opts: LoggerOptions): pino.Logger {\n const level = opts.level ?? 'info';\n const name = opts.name ?? 'junando';\n const lokiUrl = process.env['LOKI_URL'];\n\n if (lokiUrl) {\n const parsed = new URL(lokiUrl);\n\n // Initialize the in-process Loki buffer.\n // flushLoki() must be called at the end of every Lambda handler invocation.\n initLokiBuffer({\n host: `${parsed.protocol}//${parsed.host}`,\n username: parsed.username,\n password: parsed.password,\n labels: {\n service_name: name,\n environment: process.env['NODE_ENV'] ?? 'production',\n },\n });\n\n const lokiDest = createLokiDestination();\n\n // multistream: stdout (CloudWatch) always reliable; Loki via in-process buffer.\n return pino(\n {\n level,\n base: { service: name },\n timestamp: pino.stdTimeFunctions.isoTime,\n },\n pino.multistream([{ stream: process.stdout }, { stream: lokiDest }]),\n );\n }\n\n return pino({\n level,\n base: { service: name },\n timestamp: pino.stdTimeFunctions.isoTime,\n });\n}\n\n/**\n * Returns a Proxy logger that always delegates to the current root logger.\n * Module-level callers get a proxy that automatically starts writing to Loki\n * once reinitLogger() is called inside the handler after loadConfig().\n */\nexport function createLogger(levelOrOptions?: string | LoggerOptions): Logger {\n const opts: LoggerOptions =\n typeof levelOrOptions === 'string'\n ? { level: levelOrOptions }\n : (levelOrOptions ?? {});\n\n // Non-default options create a dedicated logger (not proxied to root)\n if (opts.level !== undefined || opts.name !== undefined) {\n return buildLogger(opts);\n }\n\n // Return a Proxy that always reads from the CURRENT _root at call time.\n // This means reinitLogger() affects all existing module-level loggers instantly.\n // Note: read-only by design. pino loggers must not be mutated externally;\n // a `set` trap here would silently propagate writes to the global root and\n // affect every other proxy instance.\n return new Proxy({} as pino.Logger, {\n get(_target, prop) {\n const value = (_root as unknown as Record<string | symbol, unknown>)[prop];\n if (typeof value === 'function') {\n return (value as Function).bind(_root);\n }\n return value;\n },\n });\n}\n\n/**\n * Re-creates the root logger with current env vars (including LOKI_URL).\n * Call this inside your Lambda handler immediately after loadConfig():\n *\n * @example\n * const config = await loadConfig();\n * reinitLogger(); // all module-level proxy loggers now write to Loki\n */\nexport function reinitLogger(opts?: LoggerOptions): void {\n _root = buildLogger(opts ?? {});\n}\n","import type { NormalizedAlert } from '../../domain/entities/alert.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport { RuleActionType } from '../../domain/entities/rule.js';\nimport type {\n IDeduplicationStore,\n ILLMProvider,\n INotifier,\n IRuleEngine,\n ITraceRepository,\n} from '../../domain/ports/index.js';\nimport { NotifyOutcome } from '../../domain/ports/index.js';\nimport { ClusteringService } from '../../domain/services/clustering.service.js';\nimport type { Logger } from '../../shared/logger/index.js';\nimport {\n Component,\n Outcome,\n WideEventBuilder,\n redact,\n shouldSample,\n} from '../../shared/logger/index.js';\nimport type { ErrorSection, WideEvent } from '../../shared/logger/index.js';\nimport { dedupNew, dedupDuplicate, suppressedClusters } from '../../shared/metrics/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ProcessIncidentUseCase — Application layer.\n// Orchestrates the full pipeline using only domain interfaces (ports).\n// Never imports a concrete infrastructure class directly.\n//\n// Observability: exactly ONE wide event per processed cluster. Stage results\n// accumulate into a WideEventBuilder; a single redacted, tail-sampled line is\n// emitted at the end of each cluster. Duplicates emit nothing (only metrics).\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Source label for dedup counter metrics. */\nconst DEDUP_METRIC_SOURCE = 'alertmanager';\n\ninterface Dependencies {\n dedup: IDeduplicationStore;\n traces: ITraceRepository;\n llm: ILLMProvider;\n notifier: INotifier;\n logger: Logger;\n dedupTtlSeconds: number;\n clustering?: ClusteringService;\n onClustersBuilt?: (count: number) => void;\n ruleEngine?: IRuleEngine;\n}\n\nfunction toErrorSection(err: unknown): ErrorSection {\n if (err instanceof Error) {\n return {\n message: err.message,\n name: err.name,\n ...(err.stack !== undefined && { stack: err.stack }),\n };\n }\n return { message: String(err) };\n}\n\ninterface OutcomeSignals {\n llmError: unknown | null;\n notifyError: unknown | null;\n}\n\n/**\n * Terminal outcome for a processed cluster. Early returns — no switch/case.\n * Notify failure is fatal (the batch is retried via SQS); LLM failure is\n * degraded (notification still went out without a diagnosis).\n */\nfunction resolveOutcome({ llmError, notifyError }: OutcomeSignals): Outcome {\n if (notifyError != null) return Outcome.Error;\n if (llmError != null) return Outcome.Degraded;\n return Outcome.Success;\n}\n\nexport class ProcessIncidentUseCase {\n private readonly clustering: ClusteringService;\n private readonly wideEventsEnabled: boolean;\n\n constructor(private readonly deps: Dependencies) {\n this.clustering = deps.clustering ?? new ClusteringService();\n this.wideEventsEnabled = process.env['WIDE_EVENTS_ENABLED'] !== 'false';\n }\n\n async execute(alerts: NormalizedAlert[], correlationId: string): Promise<void> {\n const { dedup, traces, llm, notifier, dedupTtlSeconds, ruleEngine } = this.deps;\n\n // 1. Cluster alerts by fingerprint. The entry point (worker) owns the\n // batch-level wide event; this use case owns one event per cluster.\n const clusters = this.clustering.buildClusters(alerts);\n this.deps.onClustersBuilt?.(clusters.length);\n\n for (const cluster of clusters) {\n const clusterStartMs = Date.now();\n const builder = new WideEventBuilder(\n `${correlationId}:${cluster.fingerprint}`,\n Component.UseCase,\n )\n .set('correlationId', correlationId)\n .set('cluster', {\n fingerprint: cluster.fingerprint,\n serviceName: cluster.serviceName,\n alertCount: cluster.alertCount,\n spanCount: 0,\n });\n\n // 2. Deduplicate — skip if seen recently\n const dedupResult = await dedup.isNew(cluster.fingerprint, dedupTtlSeconds);\n builder.set('dedup', {\n isNew: dedupResult.isNew,\n ttlSeconds: dedupResult.ttlSeconds,\n ...(dedupResult.error !== undefined && { error: dedupResult.error }),\n });\n if (!dedupResult.isNew) {\n dedupDuplicate.inc({ source: DEDUP_METRIC_SOURCE });\n continue; // Spec: one wide event per NON-DUPLICATE cluster — emit nothing here.\n }\n dedupNew.inc({ source: DEDUP_METRIC_SOURCE });\n\n // 3. PRE-LLM rule engine hook — evaluate rules before LLM\n // ────────────────────────────────────────────────────────────────────\n let preLlmRouteChannels: string[] = [];\n let preLlmEscalateChannels: string[] = [];\n\n if (ruleEngine) {\n const preResult = ruleEngine.evaluatePreLlm(cluster);\n builder.set('rule', {\n matched: preResult.matchedRuleId != null,\n suppressed: preResult.suppressed,\n ...(preResult.matchedRuleId !== undefined && { matchedRuleId: preResult.matchedRuleId }),\n });\n\n if (preResult.suppressed) {\n if (preResult.matchedRuleId) {\n suppressedClusters.inc({ rule_id: preResult.matchedRuleId });\n }\n this.emit(builder, Outcome.Suppressed, clusterStartMs);\n continue; // Skip LLM, traces, and notification entirely\n }\n\n // Collect route and escalate channels from PRE-LLM actions\n for (const action of preResult.actions) {\n if (action.type === RuleActionType.Route && 'channel' in action) {\n preLlmRouteChannels.push(action.channel);\n }\n if (action.type === RuleActionType.Escalate && 'channel' in action) {\n preLlmEscalateChannels.push(action.channel);\n }\n }\n }\n\n // 4. Extract representative traces from the trace repository.\n // Per-trace failures fail open and are counted on the event instead of\n // being logged as scattered warn lines.\n let traceErrors = 0;\n const spanLists = await Promise.all(\n cluster.representativeTraceIds.map((id) =>\n traces.findByTraceId(id).catch(() => {\n traceErrors++;\n return [];\n }),\n ),\n );\n const allSpans = spanLists.flat();\n builder.set('cluster', {\n fingerprint: cluster.fingerprint,\n serviceName: cluster.serviceName,\n alertCount: cluster.alertCount,\n spanCount: allSpans.length,\n ...(traceErrors > 0 && { traceErrors }),\n });\n\n // 5. LLM inference — fail gracefully, notify anyway with null analysis\n let analysis: LLMAnalysis | null = null;\n let llmError: unknown | null = null;\n try {\n const llmResult = await llm.analyze(cluster, allSpans);\n analysis = llmResult.analysis;\n builder.set('llm', {\n provider: llmResult.provider,\n model: llmResult.model,\n latencyMs: llmResult.latencyMs,\n urgency: llmResult.analysis.urgency_level,\n tokens: llmResult.promptTokens + llmResult.completionTokens,\n });\n } catch (err) {\n llmError = err;\n }\n\n // 6. POST-LLM rule engine hook — evaluate rules after LLM analysis\n // ────────────────────────────────────────────────────────────────────\n let postLlmEscalateChannels: string[] = [];\n\n if (ruleEngine && analysis) {\n const postResult = ruleEngine.evaluatePostLlm(cluster, analysis);\n\n // Collect escalate channels from POST-LLM actions\n for (const action of postResult.actions) {\n if (action.type === RuleActionType.Escalate && 'channel' in action) {\n postLlmEscalateChannels.push(action.channel);\n }\n }\n\n // Apply tags from postResult to cluster\n if (postResult.tags && Object.keys(postResult.tags).length > 0) {\n cluster.labels = { ...cluster.labels, ...postResult.tags };\n }\n }\n\n // 7. Notify via ChatOps — with rule-based routing\n // ────────────────────────────────────────────────────────────────────\n const escalateChannels = [...preLlmEscalateChannels, ...postLlmEscalateChannels];\n const notifyStartMs = Date.now();\n try {\n const primaryChannel = preLlmRouteChannels[0]; // First route wins\n\n // Send primary notification (to route channel or default), then\n // escalation notifications (in addition to primary).\n const results = [await notifier.send(cluster, analysis, primaryChannel)];\n for (const channel of escalateChannels) {\n results.push(await notifier.send(cluster, analysis, channel));\n }\n\n builder.set('notify', {\n channels: results.flatMap((r) => r.channels),\n outcome: NotifyOutcome.Success,\n latencyMs: Date.now() - notifyStartMs,\n });\n } catch (err) {\n builder.set('notify', {\n channels: [...preLlmRouteChannels, ...escalateChannels],\n outcome: NotifyOutcome.Failure,\n latencyMs: Date.now() - notifyStartMs,\n });\n // The fatal error owns the error section (it triggers the SQS retry);\n // a prior LLM failure is shadowed here but already marked the event degraded-eligible.\n builder.set('error', toErrorSection(err));\n this.emit(builder, Outcome.Error, clusterStartMs);\n throw err; // let the worker retry via SQS\n }\n\n if (llmError != null) {\n builder.set('error', toErrorSection(llmError));\n }\n this.emit(builder, resolveOutcome({ llmError, notifyError: null }), clusterStartMs);\n }\n }\n\n /**\n * Flushes the builder into a final event, applies tail sampling, redacts\n * PII, and emits the single canonical log line for the cluster.\n */\n private emit(builder: WideEventBuilder, outcome: Outcome, startMs: number): void {\n if (!this.wideEventsEnabled) return;\n\n const event: WideEvent = builder\n .set('outcome', outcome)\n .set('durationMs', Date.now() - startMs)\n .flush();\n\n // Tail sampling: errors and slow events always survive; the rest ~5%.\n if (!shouldSample(event)) {\n return;\n }\n\n this.deps.logger.info(redact(event as unknown as Record<string, unknown>));\n }\n}\n","import type { Redis } from 'ioredis';\nimport type { DedupResult, IDeduplicationStore } from '../../domain/ports/index.js';\nimport { dedupRedisFailoverTotal } from '../../shared/metrics/index.js';\nimport { createLogger } from '../../shared/logger/index.js';\n\nconst logger = createLogger();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RedisDeduplicationStore — Infrastructure adapter.\n// Implements IDeduplicationStore using Redis SET NX.\n// Swap this for DynamoDBDeduplicationStore or InMemoryDeduplicationStore\n// without touching a single line of domain or application code.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class RedisDeduplicationStore implements IDeduplicationStore {\n private readonly keyPrefix = 'junando:dedup:';\n\n constructor(private readonly redis: Redis) {}\n\n async isNew(fingerprint: string, ttlSeconds: number): Promise<DedupResult> {\n try {\n const result = await this.redis.set(\n `${this.keyPrefix}${fingerprint}`,\n '1',\n 'EX',\n ttlSeconds,\n 'NX',\n );\n return { isNew: result === 'OK', ttlSeconds };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.warn({ err, fingerprint }, 'Redis dedup check failed, failing open');\n dedupRedisFailoverTotal.inc();\n // Fail open: Redis down → treat every alert as new (noisy but safe).\n // The error rides the result so the wide event can record the failover.\n return { isNew: true, ttlSeconds, error: message };\n }\n }\n\n async reset(fingerprint: string): Promise<void> {\n await this.redis.del(`${this.keyPrefix}${fingerprint}`);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// InMemoryDeduplicationStore — Test adapter.\n// Zero dependencies. Use in unit tests and local dev without Redis.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class InMemoryDeduplicationStore implements IDeduplicationStore {\n private readonly store = new Map<string, number>(); // fingerprint → expiry timestamp\n\n async isNew(fingerprint: string, ttlSeconds: number): Promise<DedupResult> {\n const expiry = this.store.get(fingerprint);\n const now = Date.now();\n\n if (expiry !== undefined && expiry > now) {\n return { isNew: false, ttlSeconds };\n }\n\n this.store.set(fingerprint, now + ttlSeconds * 1000);\n return { isNew: true, ttlSeconds };\n }\n\n async reset(fingerprint: string): Promise<void> {\n this.store.delete(fingerprint);\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n","import type { IIndexer } from '../../domain/ports/index.js';\nimport type { TraceabilityDocument } from '../../domain/entities/traceability.js';\nexport type { TraceabilityDocument };\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Transport contract — an HTTP fetcher that performs the (already-prepared)\n// request and returns a minimal response shape. Default implementation signs\n// with SigV4; tests inject a stub.\n// ─────────────────────────────────────────────────────────────────────────────\nexport interface SignedHttpRequest {\n method: string;\n url: string;\n headers: Record<string, string>;\n body: string;\n}\n\nexport interface OpenSearchHttpResponse {\n status: number;\n body: string;\n}\n\nexport type OpenSearchHttpFetcher = (request: SignedHttpRequest) => Promise<OpenSearchHttpResponse>;\n\nexport interface OpenSearchIndexerDeps {\n endpoint: string;\n indexName: string;\n region: string;\n fetcher: OpenSearchHttpFetcher;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OpenSearchIndexer — Infrastructure adapter.\n// Indexes a TraceabilityDocument into an OpenSearch domain.\n// SigV4 signing is delegated to the injected fetcher so this class stays\n// transport-agnostic and trivially testable.\n// ─────────────────────────────────────────────────────────────────────────────\nexport class OpenSearchIndexer implements IIndexer<TraceabilityDocument> {\n private readonly endpoint: string;\n private readonly indexName: string;\n private readonly region: string;\n private readonly fetcher: OpenSearchHttpFetcher;\n\n constructor(deps: OpenSearchIndexerDeps) {\n this.endpoint = deps.endpoint.replace(/\\/+$/, '');\n this.indexName = deps.indexName;\n this.region = deps.region;\n this.fetcher = deps.fetcher;\n }\n\n async index(doc: TraceabilityDocument): Promise<void> {\n const url = `${this.endpoint}/${this.indexName}/_doc`;\n const body = JSON.stringify(doc);\n\n const response = await this.fetcher({\n method: 'POST',\n url,\n headers: {\n 'content-type': 'application/json',\n },\n body,\n });\n\n if (response.status < 200 || response.status >= 300) {\n throw new Error(\n `OpenSearch index failed: status=${response.status} region=${this.region} body=${response.body}`,\n );\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// InMemoryIndexer — Test/dev double.\n// ─────────────────────────────────────────────────────────────────────────────\nexport class InMemoryIndexer implements IIndexer<TraceabilityDocument> {\n readonly indexed: TraceabilityDocument[] = [];\n\n async index(doc: TraceabilityDocument): Promise<void> {\n this.indexed.push(doc);\n }\n}\n","import * as Breaker from 'opossum';\nimport { z } from 'zod';\nimport type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport { LLMAnalysisSchema } from '../../domain/entities/incident.js';\nimport type { ILLMProvider, LLMResult } from '../../domain/ports/index.js';\nimport {\n CIRCUIT_BREAKER,\n LLM_FALLBACK_DEFAULTS,\n LLM_MAX_TOKENS,\n LLM_MODELS,\n LLMProviderType,\n} from '../../shared/constants.js';\nimport { createLogger } from '../../shared/logger/index.js';\nimport { llmInferenceDuration, llmInferenceTotal } from '../../shared/metrics/index.js';\n\nconst logger = createLogger();\n\n/** Provider name reported by MockLLMProvider results. */\nconst MOCK_PROVIDER_NAME = 'mock';\n\n/**\n * Internal carrier: what each provider's raw call produces before the\n * shared metadata (provider, model, latencyMs) is attached.\n */\ninterface LlmRawResult {\n analysis: LLMAnalysis;\n promptTokens: number;\n completionTokens: number;\n}\n\n/**\n * Schema for OpenRouter API response validation.\n * Ensures type safety at the external boundary.\n */\nexport const OpenRouterResponseSchema = z.object({\n id: z.string().optional(),\n choices: z.array(\n z.object({\n index: z.number(),\n message: z.object({\n role: z.string(),\n content: z.string().optional(),\n }),\n finish_reason: z.string().optional(),\n }),\n ),\n usage: z\n .object({\n prompt_tokens: z.number().optional(),\n completion_tokens: z.number().optional(),\n total_tokens: z.number().optional(),\n })\n .optional(),\n});\n\nexport type OpenRouterResponse = z.infer<typeof OpenRouterResponseSchema>;\n\nconst SYSTEM_PROMPT = `You are a senior Site Reliability Engineer.\nRespond ONLY with raw JSON, no markdown, no text before or after:\n{\"probable_cause\":\"string\",\"impacted_services\":[\"string\"],\"recommended_steps\":[\"string\"],\"urgency_level\":\"low|medium|high|critical\",\"requires_rollback\":true|false}`;\n\n/**\n * Pre-compiled regex patterns for parsing LLM responses.\n * Hoisted to module level to avoid recompilation on every parseAnalysis call.\n * Matches JSON field extraction from raw LLM output.\n */\nconst RE_PROBABLE_CAUSE = /\"probable_cause\"\\s*:\\s*\"([^\"]+)\"/;\nconst RE_URGENCY_LEVEL = /\"urgency_level\"\\s*:\\s*\"([^\"]+)\"/;\nconst RE_REQUIRES_ROLLBACK = /\"requires_rollback\"\\s*:\\s*(true|false)/;\nconst RE_RECOMMENDED_STEPS = /\"recommended_steps\"\\s*:\\s*\\[([^\\]]+)\\]/;\nconst RE_IMPACTED_SERVICES = /\"impacted_services\"\\s*:\\s*\\[([^\\]]+)\\]/;\n\nconst BREAKER_OPTIONS = {\n timeout: CIRCUIT_BREAKER.Timeout,\n errorThresholdPercentage: CIRCUIT_BREAKER.ErrorThresholdPercentage,\n resetTimeout: CIRCUIT_BREAKER.ResetTimeoutMs,\n};\n\n/**\n * Builds the user-facing prompt sent to the LLM for analysis.\n * Includes cluster summary and trace count for context.\n */\nfunction buildUserPrompt(cluster: AlertCluster, traces: Record<string, unknown>[]): string {\n return `Service:${cluster.serviceName} Error:${cluster.alertType} Alerts:${cluster.alertCount} Latency:${cluster.latencyP99Ms ?? 'N/A'} Traces:${traces.length}`;\n}\n\n/**\n * Extracts LLMAnalysis from raw LLM response text.\n * Uses multi-stage parsing: JSON → regex fallback → heuristics.\n * Returns validated LLMAnalysis or falls back to default values.\n */\nfunction parseAnalysis(raw: string, correlationId?: string): LLMAnalysis {\n const startIdx = raw.indexOf('{');\n const endIdx = raw.lastIndexOf('}');\n\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n try {\n return LLMAnalysisSchema.parse(JSON.parse(raw.slice(startIdx, endIdx + 1)));\n } catch {\n logger.warn(\n { rawResponse: raw.slice(0, 500), correlationId },\n 'llm:parse:failed',\n );\n }\n }\n\n const probableCauseMatch = RE_PROBABLE_CAUSE.exec(raw);\n const urgencyMatch = RE_URGENCY_LEVEL.exec(raw);\n const rollbackMatch = RE_REQUIRES_ROLLBACK.exec(raw);\n const stepsMatch = RE_RECOMMENDED_STEPS.exec(raw);\n const servicesMatch = RE_IMPACTED_SERVICES.exec(raw);\n\n const probableCause = probableCauseMatch?.[1];\n const urgency = urgencyMatch?.[1];\n\n if (probableCause && urgency) {\n const steps: string[] = stepsMatch?.[1] ? (JSON.parse(`[${stepsMatch[1]}]`) as string[]) : [];\n const services: string[] = servicesMatch?.[1]\n ? (JSON.parse(`[${servicesMatch[1]}]`) as string[])\n : ['unknown-service'];\n const analysis: LLMAnalysis = {\n probable_cause: probableCause,\n impacted_services: services,\n recommended_steps: steps,\n urgency_level: urgency as LLMAnalysis['urgency_level'],\n requires_rollback: rollbackMatch?.[1] === 'true',\n };\n return LLMAnalysisSchema.parse(analysis);\n }\n\n const lowerRaw = raw.toLowerCase();\n let urgencyLevel: LLMAnalysis['urgency_level'] = 'medium';\n if (lowerRaw.includes('critical') || lowerRaw.includes('severity 1')) urgencyLevel = 'critical';\n else if (lowerRaw.includes('high') || lowerRaw.includes('severity 2')) urgencyLevel = 'high';\n else if (lowerRaw.includes('low')) urgencyLevel = 'low';\n\n return LLMAnalysisSchema.parse({\n probable_cause: 'Analysis in progress - check logs for details',\n impacted_services: ['unknown-service'],\n recommended_steps: ['Review incident details in logs'],\n urgency_level: urgencyLevel,\n requires_rollback: lowerRaw.includes('rollback') || lowerRaw.includes('revert'),\n });\n}\n\n/**\n * Gemini LLM provider using Google Generative AI SDK.\n * Wrapped with circuit breaker for resilience.\n */\nexport class GeminiProvider implements ILLMProvider {\n private readonly breaker: InstanceType<typeof Breaker.default>;\n\n constructor(\n private readonly apiKey: string,\n private readonly model: string = LLM_MODELS.Gemini,\n ) {\n this.breaker = new Breaker.default(this.analyzeRaw.bind(this), BREAKER_OPTIONS);\n }\n\n async analyze(cluster: AlertCluster, traces: Record<string, unknown>[]): Promise<LLMResult> {\n const startMs = Date.now();\n const raw = await this.analyzeWithBreaker(cluster, traces);\n return {\n ...raw,\n provider: LLMProviderType.Gemini,\n model: this.model,\n latencyMs: Date.now() - startMs,\n };\n }\n\n private async analyzeWithBreaker(\n cluster: AlertCluster,\n traces: Record<string, unknown>[],\n ): Promise<LlmRawResult> {\n try {\n return (await this.breaker.fire(cluster, traces)) as LlmRawResult;\n } catch {\n return this.analyzeRaw(cluster, traces);\n }\n }\n\n private async analyzeRaw(\n cluster: AlertCluster,\n traces: Record<string, unknown>[],\n ): Promise<LlmRawResult> {\n const { GoogleGenerativeAI } = await import('@google/generative-ai');\n const genAI = new GoogleGenerativeAI(this.apiKey);\n const gemini = genAI.getGenerativeModel({\n model: this.model,\n systemInstruction: SYSTEM_PROMPT,\n });\n\n const result = await gemini.generateContent(buildUserPrompt(cluster, traces));\n const usage = (\n result.response as {\n usageMetadata?: { promptTokenCount?: number; candidatesTokenCount?: number };\n }\n ).usageMetadata;\n return {\n analysis: parseAnalysis(result.response.text()),\n promptTokens: usage?.promptTokenCount ?? 0,\n completionTokens: usage?.candidatesTokenCount ?? 0,\n };\n }\n}\n\n/**\n * Claude LLM provider using Anthropic SDK.\n * Supports Claude Haiku and other models.\n */\nexport class ClaudeProvider implements ILLMProvider {\n constructor(\n private readonly apiKey: string,\n private readonly model: string = LLM_MODELS.Claude,\n ) {}\n\n async analyze(cluster: AlertCluster, traces: Record<string, unknown>[]): Promise<LLMResult> {\n const startMs = Date.now();\n const Anthropic = (await import('@anthropic-ai/sdk')).default;\n const client = new Anthropic({ apiKey: this.apiKey });\n\n const message = await client.messages.create({\n model: this.model,\n max_tokens: LLM_MAX_TOKENS,\n system: SYSTEM_PROMPT,\n messages: [{ role: 'user', content: buildUserPrompt(cluster, traces) }],\n });\n\n const text = message.content.find((b) => b.type === 'text')?.text ?? '';\n return {\n analysis: parseAnalysis(text),\n provider: LLMProviderType.Claude,\n model: this.model,\n latencyMs: Date.now() - startMs,\n promptTokens: message.usage?.input_tokens ?? 0,\n completionTokens: message.usage?.output_tokens ?? 0,\n };\n }\n}\n\n/**\n * Mock LLM provider for testing and local development.\n * Returns deterministic responses without external API calls.\n */\nexport class MockLLMProvider implements ILLMProvider {\n readonly callLog: Array<{ cluster: AlertCluster }> = [];\n\n async analyze(cluster: AlertCluster, _traces: Record<string, unknown>[]): Promise<LLMResult> {\n this.callLog.push({ cluster });\n return {\n analysis: {\n probable_cause: `Mock: ${cluster.alertType} on ${cluster.serviceName}`,\n impacted_services: [cluster.serviceName],\n recommended_steps: ['Check the logs', 'Verify the deployment'],\n urgency_level: 'high',\n requires_rollback: false,\n },\n provider: MOCK_PROVIDER_NAME,\n model: MOCK_PROVIDER_NAME,\n latencyMs: 0,\n promptTokens: 0,\n completionTokens: 0,\n };\n }\n}\n\n/**\n * Options for configuring the OpenRouter fallback chain.\n * Infra-internal — not exported.\n */\ninterface FallbackOptions {\n fallbackModels?: string[];\n fallbackTimeoutMs?: number;\n}\n\n/**\n * OpenRouter LLM provider using OpenAI-compatible API.\n * Supports various open models (Qwen, etc.) via OpenRouter gateway.\n * When the primary model exhausts 429 retries, cycles through fallbackModels.\n */\nexport class OpenRouterProvider implements ILLMProvider {\n private readonly fallbackModels: string[];\n private readonly fallbackTimeoutMs: number;\n private readonly providerName: string;\n\n constructor(\n private readonly apiKey: string,\n private readonly model: string = LLM_MODELS.OpenRouter,\n fallbackModels: string[] = [],\n fallbackTimeoutMs: number = LLM_FALLBACK_DEFAULTS.TimeoutMs,\n providerName: string = LLMProviderType.OpenRouter,\n ) {\n // Deduplicate: remove primary model from fallback list at construction time\n this.fallbackModels = fallbackModels.filter((m) => m !== model);\n this.fallbackTimeoutMs = fallbackTimeoutMs;\n this.providerName = providerName;\n }\n\n async analyze(\n cluster: AlertCluster,\n traces: Record<string, unknown>[],\n correlationId?: string,\n ): Promise<LLMResult> {\n const prompt = buildUserPrompt(cluster, traces);\n logger.debug({ model: this.model, promptLength: prompt.length, correlationId }, 'llm:request:start');\n\n const startMs = Date.now();\n\n // Retry once on 429 using the Retry-After header from OpenRouter\n for (let attempt = 0; attempt < 2; attempt++) {\n const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n 'HTTP-Referer': process.env['APP_URL'] ?? 'https://junando.app',\n 'X-Title': 'Junando SRE',\n },\n body: JSON.stringify({\n model: this.model,\n messages: [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: prompt },\n ],\n // Note: json_object response_format is NOT supported by all OpenRouter models.\n // Qwen free tier ignores it or returns an error — rely on prompt instructions only.\n }),\n });\n\n const latencyMs = Date.now() - startMs;\n const raw = await res.json();\n\n if (!res.ok) {\n const retryAfter = Number(\n (raw as { error?: { metadata?: { retry_after_seconds?: number } } })\n ?.error?.metadata?.retry_after_seconds ?? 0,\n );\n\n logger.warn(\n { status: res.status, body: raw, model: this.model, correlationId, attempt, retryAfter },\n 'llm:request:failed',\n );\n\n if (res.status === 429 && attempt === 0) {\n // Some providers (Google AI Studio) do NOT return retry_after_seconds.\n // Default to a 5s backoff in that case. Cap at 30s so Lambda doesn't time out.\n const waitMs = retryAfter > 0 ? Math.min(retryAfter * 1000, 30_000) : 5_000;\n logger.info({ waitMs, retryAfter, correlationId }, 'llm:retry:waiting');\n await new Promise((r) => setTimeout(r, waitMs));\n continue;\n }\n\n if (res.status === 429) {\n if (this.fallbackModels.length > 0) {\n // Primary model exhausted — try fallback chain\n const deadlineMs = Date.now() + this.fallbackTimeoutMs;\n return this.analyzeFallback(prompt, correlationId, deadlineMs, this.model, startMs);\n }\n llmInferenceTotal.inc({ status: 'rate_limited' });\n throw new Error(`OpenRouter API failed: ${res.status}`);\n }\n\n llmInferenceTotal.inc({ status: 'error' });\n throw new Error(`OpenRouter API failed: ${res.status}`);\n }\n\n const parsed = OpenRouterResponseSchema.safeParse(raw);\n if (!parsed.success) {\n logger.warn({ errors: parsed.error.format(), correlationId }, 'llm:validation:failed');\n }\n\n const text = parsed.success ? (parsed.data.choices?.[0]?.message?.content ?? '') : '';\n const analysis = parseAnalysis(text, correlationId);\n const usage = parsed.success ? parsed.data.usage : undefined;\n\n if (usage) {\n const { prompt_tokens, completion_tokens, total_tokens } = usage;\n logger.info(\n {\n model: this.model,\n usage: { promptTokens: prompt_tokens, completionTokens: completion_tokens, totalTokens: total_tokens },\n latencyMs,\n correlationId,\n },\n 'llm:request:success',\n );\n }\n\n llmInferenceTotal.inc({ status: 'success' });\n llmInferenceDuration.observe({ model: this.model }, latencyMs / 1000);\n\n return {\n analysis,\n provider: this.providerName,\n model: this.model,\n latencyMs,\n promptTokens: usage?.prompt_tokens ?? 0,\n completionTokens: usage?.completion_tokens ?? 0,\n };\n }\n\n throw new Error('OpenRouter API failed after retry');\n }\n\n private async analyzeFallback(\n prompt: string,\n correlationId: string | undefined,\n deadlineMs: number,\n fromModel: string,\n startMs: number,\n ): Promise<LLMResult> {\n for (const toModel of this.fallbackModels) {\n if (Date.now() >= deadlineMs) {\n throw new Error('OpenRouter fallback chain timed out');\n }\n\n logger.info({ from_model: fromModel, to_model: toModel, reason: '429', correlationId }, 'llm:fallback:hop');\n\n const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n 'HTTP-Referer': process.env['APP_URL'] ?? 'https://junando.app',\n 'X-Title': 'Junando SRE',\n },\n body: JSON.stringify({\n model: toModel,\n messages: [\n { role: 'system', content: SYSTEM_PROMPT },\n { role: 'user', content: prompt },\n ],\n }),\n });\n\n const raw = await res.json();\n\n if (!res.ok) {\n if (res.status === 429) {\n fromModel = toModel;\n continue;\n }\n throw new Error(`OpenRouter API failed: ${res.status}`);\n }\n\n const parsed = OpenRouterResponseSchema.safeParse(raw);\n const text = parsed.success ? (parsed.data.choices?.[0]?.message?.content ?? '') : '';\n const usage = parsed.success ? parsed.data.usage : undefined;\n return {\n analysis: parseAnalysis(text, correlationId),\n provider: this.providerName,\n model: toModel,\n latencyMs: Date.now() - startMs,\n promptTokens: usage?.prompt_tokens ?? 0,\n completionTokens: usage?.completion_tokens ?? 0,\n };\n }\n\n throw new Error('OpenRouter API exhausted all models');\n }\n}\n\n/**\n * Factory type for creating LLM providers.\n * Takes API key and optional model override.\n */\ntype LLMFactory = (apiKey: string, model?: string, options?: FallbackOptions) => ILLMProvider;\n\n/**\n * Registry mapping provider names to their factory functions.\n * Used by createLLMProvider to instantiate the appropriate LLM client.\n */\nconst LLM_PROVIDER_REGISTRY: ReadonlyMap<string, LLMFactory> = new Map<string, LLMFactory>([\n [LLMProviderType.Gemini, (apiKey, model) => new GeminiProvider(apiKey, model)],\n [LLMProviderType.Claude, (apiKey, model) => new ClaudeProvider(apiKey, model)],\n [LLMProviderType.OpenRouter, (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs, LLMProviderType.OpenRouter)],\n [LLMProviderType.Qwen, (apiKey, model, options) => new OpenRouterProvider(apiKey, model, options?.fallbackModels, options?.fallbackTimeoutMs, LLMProviderType.Qwen)],\n]);\n\nexport function createLLMProvider(provider: string, apiKey: string, model?: string, options?: FallbackOptions): ILLMProvider {\n const factory = LLM_PROVIDER_REGISTRY.get(provider);\n if (!factory) {\n const supported = Array.from(LLM_PROVIDER_REGISTRY.keys()).join(', ');\n throw new Error(`Unknown LLM_PROVIDER: \"${provider}\". Supported: ${supported}`);\n }\n return factory(apiKey, model, options);\n}\n","// ─────────────────────────────────────────────────────────────────────────────\n// FactoryRegistry — generic factory registry with string keys.\n// No switch/case — register adapters by key, resolve by key.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class FactoryRegistry<T> {\n private readonly _factories = new Map<string, () => T>();\n private _default: () => T = () => {\n throw new Error(`No factory registered and no default available`);\n };\n\n /**\n * Register a factory for a given key.\n * Overwrites any existing registration for that key.\n */\n register(key: string, factory: () => T): void {\n this._factories.set(key, factory);\n }\n\n /**\n * Set the default factory to use when no key matches.\n */\n registerDefault(factory: () => T): void {\n this._default = factory;\n }\n\n /**\n * Resolve the factory for a given key.\n * Returns the default if no specific factory is registered for that key.\n */\n resolve(key: string): T {\n const factory = this._factories.get(key);\n if (factory) {\n return factory();\n }\n return this._default();\n }\n\n /**\n * Check if a factory is registered for a given key.\n */\n has(key: string): boolean {\n return this._factories.has(key);\n }\n\n /**\n * Return all registered keys.\n */\n keys(): string[] {\n return Array.from(this._factories.keys());\n }\n}","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { INotifier, NotifyResult } from '../../domain/ports/index.js';\nimport { NotifyOutcome } from '../../domain/ports/index.js';\nimport { HTTP_TIMEOUT_MS, SLACK_API_URL, URGENCY_EMOJI } from '../../shared/constants.js';\nimport { createLogger } from '../../shared/logger/index.js';\nimport { notificationsTotal } from '../../shared/metrics/index.js';\n\nconst logger = createLogger();\n\n/**\n * Sanitizes endpointPath for safe rendering in Slack Block Kit.\n * Strips backticks (prevent markup injection) and limits length.\n */\nfunction sanitizeEndpointPath(endpointPath: string | undefined): string {\n if (!endpointPath) return 'unknown';\n return endpointPath.replaceAll('`', '').slice(0, 200);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SlackNotifier — Infrastructure adapter.\n// Implements INotifier using Slack Block Kit.\n// Swap for TeamsNotifier without touching application or domain code.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class SlackNotifier implements INotifier {\n constructor(\n private readonly botToken: string,\n private readonly channel: string,\n ) {}\n\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null, _channel?: string): Promise<NotifyResult> {\n const payload = analysis\n ? this.buildAnalysisMessage(cluster, analysis)\n : this.buildFallbackMessage(cluster);\n\n const startMs = Date.now();\n\n try {\n const res = await fetch(SLACK_API_URL, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.botToken}`,\n },\n body: JSON.stringify({ channel: this.channel, ...payload }),\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS.Default),\n });\n\n if (!res.ok) throw new Error(`Slack API error: ${res.status}`);\n const body = (await res.json()) as { ok: boolean; error?: string };\n if (!body.ok) throw new Error(`Slack error: ${body.error}`);\n\n notificationsTotal.inc({ channel: 'slack', outcome: 'success' });\n // Report the channel actually posted to — the override is handled one\n // level up by RoutingNotifier, so this adapter always targets this.channel.\n return {\n outcome: NotifyOutcome.Success,\n latencyMs: Date.now() - startMs,\n channels: [this.channel],\n };\n } catch (err) {\n notificationsTotal.inc({ channel: 'slack', outcome: 'failure' });\n throw err;\n }\n }\n\n private buildAnalysisMessage(\n cluster: AlertCluster,\n analysis: LLMAnalysis,\n ): { blocks: unknown[] } {\n const emoji = URGENCY_EMOJI[analysis.urgency_level] ?? '⚪';\n const steps = analysis.recommended_steps.map((s, i) => `${i + 1}. ${s}`).join('\\n');\n\n const safeEndpointPath = sanitizeEndpointPath(cluster.endpointPath);\n\n return {\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `${emoji} Incident — ${cluster.serviceName}`,\n },\n },\n {\n type: 'section',\n fields: [\n { type: 'mrkdwn', text: `*Service*\\n${cluster.serviceName}` },\n { type: 'mrkdwn', text: `*Alerts*\\n${cluster.alertCount}` },\n { type: 'mrkdwn', text: `*Endpoint*\\n${safeEndpointPath}` },\n {\n type: 'mrkdwn',\n text: `*Urgency*\\n${emoji} ${analysis.urgency_level.toUpperCase()}`,\n },\n ],\n },\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `*Probable cause*\\n${analysis.probable_cause}`,\n },\n },\n {\n type: 'section',\n text: { type: 'mrkdwn', text: `*Recommended steps*\\n${steps}` },\n },\n { type: 'divider' },\n {\n type: 'actions',\n elements: [\n {\n type: 'button',\n text: { type: 'plain_text', text: '✅ Acknowledge' },\n style: 'primary',\n action_id: 'acknowledge',\n value: cluster.fingerprint,\n },\n ...(analysis.requires_rollback\n ? [\n {\n type: 'button',\n text: { type: 'plain_text', text: '⏪ Trigger Rollback' },\n style: 'danger',\n action_id: 'trigger_rollback',\n value: cluster.fingerprint,\n confirm: {\n title: { type: 'plain_text', text: 'Confirm rollback' },\n text: {\n type: 'plain_text',\n text: `Roll back ${cluster.serviceName}?`,\n },\n confirm: { type: 'plain_text', text: 'Yes, rollback' },\n deny: { type: 'plain_text', text: 'Cancel' },\n },\n },\n ]\n : []),\n ],\n },\n ],\n };\n }\n\n private buildFallbackMessage(cluster: AlertCluster): { blocks: unknown[] } {\n const safeEndpointPath = sanitizeEndpointPath(cluster.endpointPath);\n\n return {\n blocks: [\n {\n type: 'header',\n text: {\n type: 'plain_text',\n text: `⚠️ Incident — ${cluster.serviceName} (no AI diagnosis)`,\n },\n },\n {\n type: 'section',\n text: {\n type: 'mrkdwn',\n text: `*${cluster.alertCount} alerts* on \\`${safeEndpointPath}\\` since ${cluster.firstSeenAt}\\nLLM analysis unavailable — manual investigation required.`,\n },\n },\n ],\n };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ConsoleNotifier — Local dev / test adapter. Prints to stdout.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ConsoleNotifier implements INotifier {\n readonly sent: Array<{\n cluster: AlertCluster;\n analysis: LLMAnalysis | null;\n }> = [];\n\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null, _channel?: string): Promise<NotifyResult> {\n const startMs = Date.now();\n try {\n this.sent.push({ cluster, analysis });\n logger.info(\n {\n cluster: {\n serviceName: cluster.serviceName,\n alertType: cluster.alertType,\n },\n analysis: analysis ?? 'unavailable',\n },\n '--- Junando Notification ---',\n );\n notificationsTotal.inc({ channel: 'unknown', outcome: 'success' });\n return {\n outcome: NotifyOutcome.Success,\n latencyMs: Date.now() - startMs,\n channels: ['console'],\n };\n } catch (err) {\n notificationsTotal.inc({ channel: 'unknown', outcome: 'failure' });\n throw err;\n }\n }\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { INotifier, NotifyResult } from '../../domain/ports/index.js';\nimport { NotifyOutcome } from '../../domain/ports/index.js';\nimport { TEAMS_WEBHOOK_TIMEOUT_MS, URGENCY_EMOJI } from '../../shared/constants.js';\nimport { notificationsTotal } from '../../shared/metrics/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TeamsNotifierError — domain error type for Teams adapter failures.\n// Discriminable type for catch blocks.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class TeamsNotifierError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'TeamsNotifierError';\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// sanitizeText — strips HTML, escapes Adaptive Card markdown chars,\n// collapses excess newlines, truncates to maxLength.\n// Pure function — no side effects.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function sanitizeText(s: string, maxLength = 4_000): string {\n // 1. Strip HTML tags\n let result = s.replace(/<[^>]*>/g, '');\n\n // 2. Escape Adaptive Card markdown special chars\n result = result.replace(/\\\\/g, '\\\\\\\\').replace(/\\*/g, '\\\\*').replace(/_/g, '\\\\_');\n\n // 3. Collapse excess newlines (>5 consecutive)\n result = result.replace(/(\\n){6,}/g, '\\n\\n\\n\\n\\n…');\n\n // 4. Truncate to maxLength\n if (result.length > maxLength) {\n result = result.slice(0, maxLength) + '…';\n }\n\n return result;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Adaptive Card payload builders\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction buildAnalysisCard(cluster: AlertCluster, analysis: LLMAnalysis): object {\n const emoji = URGENCY_EMOJI[analysis.urgency_level] ?? '⚪';\n const safeName = sanitizeText(cluster.serviceName);\n const safeEndpoint = sanitizeText(cluster.endpointPath ?? 'unknown');\n const safeCause = sanitizeText(analysis.probable_cause);\n const safeSteps = analysis.recommended_steps\n .map((s, i) => `${i + 1}. ${sanitizeText(s)}`)\n .join('\\n');\n const clusterUrl = `https://app.junando.io/clusters/${cluster.fingerprint}`;\n\n return {\n $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n type: 'AdaptiveCard',\n version: '1.5',\n body: [\n {\n type: 'TextBlock',\n text: `${emoji} Incident — ${safeName}`,\n size: 'Large',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'FactSet',\n facts: [\n { title: 'Service', value: safeName },\n { title: 'Alerts', value: String(cluster.alertCount) },\n { title: 'Endpoint', value: safeEndpoint },\n { title: 'Urgency', value: `${emoji} ${analysis.urgency_level}` },\n ],\n },\n {\n type: 'TextBlock',\n text: '**Probable cause**',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'TextBlock',\n text: safeCause,\n wrap: true,\n },\n {\n type: 'TextBlock',\n text: '**Recommended steps**',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'TextBlock',\n text: safeSteps,\n wrap: true,\n },\n ],\n actions: [\n {\n type: 'Action.OpenUrl',\n title: 'View in Junando',\n url: clusterUrl,\n },\n ],\n };\n}\n\nfunction buildFallbackCard(cluster: AlertCluster): object {\n const safeName = sanitizeText(cluster.serviceName);\n const clusterUrl = `https://app.junando.io/clusters/${cluster.fingerprint}`;\n\n return {\n $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n type: 'AdaptiveCard',\n version: '1.5',\n body: [\n {\n type: 'TextBlock',\n text: `⚠️ Incident — ${safeName} (no AI diagnosis)`,\n size: 'Large',\n weight: 'Bolder',\n wrap: true,\n },\n {\n type: 'FactSet',\n facts: [\n { title: 'Service', value: safeName },\n { title: 'Alerts', value: String(cluster.alertCount) },\n ],\n },\n ],\n actions: [\n {\n type: 'Action.OpenUrl',\n title: 'View in Junando',\n url: clusterUrl,\n },\n ],\n };\n}\n\nfunction buildAdaptiveCardPayload(card: object): object {\n return {\n type: 'message',\n attachments: [\n {\n contentType: 'application/vnd.microsoft.card.adaptive',\n content: card,\n },\n ],\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TeamsNotifier — Infrastructure adapter.\n// Implements INotifier using Microsoft Teams Adaptive Cards via webhook.\n// Swap for SlackNotifier without touching application or domain code.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class TeamsNotifier implements INotifier {\n // Pre-computed at construction so the error path never re-parses the URL.\n // If parsing happens inside the catch block and throws, the original error\n // context (timeout, network failure, etc.) would be lost.\n private readonly hostForErrors: string;\n\n constructor(\n private readonly webhookUrl: string,\n private readonly timeoutMs: number = TEAMS_WEBHOOK_TIMEOUT_MS,\n ) {\n let host = 'unknown';\n try {\n host = new URL(webhookUrl).hostname;\n } catch {\n // Leave as 'unknown'. Config validation should have rejected invalid URLs\n // upstream; this fallback exists only so the notifier can still produce\n // a meaningful error message instead of throwing in its own catch block.\n }\n this.hostForErrors = host;\n }\n\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null, _channel?: string): Promise<NotifyResult> {\n const card = analysis ? buildAnalysisCard(cluster, analysis) : buildFallbackCard(cluster);\n const payload = buildAdaptiveCardPayload(card);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n const startMs = Date.now();\n\n try {\n const res = await fetch(this.webhookUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n if (!res.ok) {\n // Round 2 hardening: never include the response body in error messages.\n // Power Automate / Logic Apps echo parts of the request URL (including\n // SAS tokens like sig=, code=, sv=, sp=) and chasing every encoding\n // variant is a losing game. Status + host gives enough diagnostic\n // signal without any leak surface.\n throw new TeamsNotifierError(\n `Teams webhook error ${res.status} (host: ${this.hostForErrors})`,\n );\n }\n\n notificationsTotal.inc({ channel: 'teams', outcome: 'success' });\n return {\n outcome: NotifyOutcome.Success,\n latencyMs: Date.now() - startMs,\n channels: ['teams'],\n };\n } catch (err) {\n if (err instanceof TeamsNotifierError) {\n notificationsTotal.inc({ channel: 'teams', outcome: 'failure' });\n throw err;\n }\n if (err instanceof Error && err.name === 'AbortError') {\n // TNT-08: log host only — never full URL (no sig= or api-version= query).\n // TNT-10: host is pre-computed; no URL parsing in the error path.\n const timeoutErr = new TeamsNotifierError(\n `Teams webhook timed out after ${this.timeoutMs}ms (host: ${this.hostForErrors})`,\n );\n notificationsTotal.inc({ channel: 'teams', outcome: 'failure' });\n throw timeoutErr;\n }\n notificationsTotal.inc({ channel: 'teams', outcome: 'failure' });\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { INotifier, NotifyResult } from '../../domain/ports/index.js';\nimport type { RuleAction } from '../../domain/entities/rule.js';\nimport { RuleActionType } from '../../domain/entities/rule.js';\nimport type { ChannelRegistry } from '../rules/channel-registry.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RoutingNotifier — wraps multiple INotifier instances via ChannelRegistry.\n// Implements INotifier for default-channel backward compatibility.\n//\n// No switch/case — uses Record<RuleActionType, handler> pattern for action dispatch.\n// Route actions override the default channel.\n// Escalate actions send additional notifications alongside default/route.\n// Tag actions are metadata-only (no notification side effect).\n// Suppress actions are handled by the caller (use case).\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ActionDispatchHandler = (\n action: RuleAction,\n ctx: ActionDispatchContext,\n) => Promise<void>;\n\ninterface ActionDispatchContext {\n cluster: AlertCluster;\n analysis: LLMAnalysis | null;\n registry: ChannelRegistry;\n defaultNotifier: INotifier;\n /** Accumulates channels to notify — populated by Route/Escalate handlers */\n routeChannels: Set<string>;\n escalationChannels: Set<string>;\n /** Set to true if a Suppress action is present — skips all notification */\n suppressed: boolean;\n}\n\n/**\n * Dispatch map: RuleActionType → handler function.\n * Record pattern — NO switch/case.\n */\nconst ACTION_DISPATCH: Record<string, ActionDispatchHandler> = {\n [RuleActionType.Suppress]: async (_action, ctx) => {\n // Suppress actions are handled by the caller (use case).\n // When present in sendWithActions, suppress all notification as a defensive measure.\n ctx.suppressed = true;\n },\n\n [RuleActionType.Route]: async (action, ctx) => {\n const routeAction = action as { type: RuleActionType.Route; channel: string };\n ctx.routeChannels.add(routeAction.channel);\n },\n\n [RuleActionType.Escalate]: async (action, ctx) => {\n const escalateAction = action as { type: RuleActionType.Escalate; channel: string };\n ctx.escalationChannels.add(escalateAction.channel);\n },\n\n [RuleActionType.Tag]: async (_action, _ctx) => {\n // Tag actions are metadata-only. No notification side effect.\n // The caller (use case) attaches tags to the cluster for observability.\n },\n};\n\nexport class RoutingNotifier implements INotifier {\n constructor(\n private readonly registry: ChannelRegistry,\n private readonly defaultNotifier: INotifier,\n ) {}\n\n /**\n * Implements INotifier.send — sends via default notifier.\n * Backward-compatible with existing call sites that don't use rule actions.\n */\n async send(cluster: AlertCluster, analysis: LLMAnalysis | null): Promise<NotifyResult> {\n return this.defaultNotifier.send(cluster, analysis);\n }\n\n /**\n * Dispatch notifications based on rule engine actions.\n *\n * - Route actions: send to the specified channel instead of default.\n * - Escalate actions: send additional notifications to escalation channels.\n * - Tag actions: metadata-only, no notification side effect.\n * - Suppress actions: skip all notification (defensive — caller should have already skipped).\n * - Unknown channels: fall back to default notifier.\n * - Empty actions or no Route/Escalate: send via default notifier.\n */\n async sendWithActions(\n cluster: AlertCluster,\n analysis: LLMAnalysis | null,\n actions: RuleAction[],\n ): Promise<void> {\n const ctx: ActionDispatchContext = {\n cluster,\n analysis,\n registry: this.registry,\n defaultNotifier: this.defaultNotifier,\n routeChannels: new Set(),\n escalationChannels: new Set(),\n suppressed: false,\n };\n\n // Dispatch all actions using Record pattern — NO switch/case\n for (const action of actions) {\n const handler = ACTION_DISPATCH[action.type];\n if (handler) {\n await handler(action, ctx);\n }\n }\n\n // If suppressed, skip all notification\n if (ctx.suppressed) {\n return;\n }\n\n // Resolve notifications to send\n const notifications: Promise<unknown>[] = [];\n\n const hasRoute = ctx.routeChannels.size > 0;\n\n if (hasRoute) {\n // Route overrides default — send to route channels\n for (const channel of ctx.routeChannels) {\n notifications.push(this.tryResolveAndSend(channel, cluster, analysis));\n }\n } else {\n // No route action — send via default notifier\n notifications.push(this.defaultNotifier.send(cluster, analysis));\n }\n\n // Escalation channels send in addition to default/route\n for (const channel of ctx.escalationChannels) {\n notifications.push(this.tryResolveAndSend(channel, cluster, analysis));\n }\n\n await Promise.all(notifications);\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n /**\n * Resolve a channel name to its notifier and send.\n * Falls back to default notifier if channel is unknown.\n */\n private async tryResolveAndSend(\n channel: string,\n cluster: AlertCluster,\n analysis: LLMAnalysis | null,\n ): Promise<void> {\n try {\n const notifier = this.registry.resolve(channel);\n await notifier.send(cluster, analysis);\n } catch {\n // ChannelRegistry.resolve throws if channel unknown and no default set.\n // Fall back to default notifier.\n await this.defaultNotifier.send(cluster, analysis);\n }\n }\n}\n","import { parse as parseYaml, YAMLParseError } from 'yaml';\nimport { RuleConfigurationSchema } from '../../domain/entities/rule.js';\nimport type { ValidatedRuleConfiguration } from '../../domain/entities/rule.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// YamlRuleLoader — reads rules.yaml, validates with Zod, returns RuleConfiguration.\n// No switch/case. Pure validation function for testability.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Parse and validate a YAML string into a RuleConfiguration.\n * Pure function — no I/O, no side effects. Fast-fails on invalid config.\n *\n * @throws {Error} if YAML is malformed or Zod validation fails\n */\nexport function parseRuleConfig(yamlString: string): ValidatedRuleConfiguration {\n let raw: unknown;\n try {\n raw = parseYaml(yamlString);\n } catch (err) {\n if (err instanceof YAMLParseError) {\n throw new Error(`Invalid YAML in rules config: ${err.message}`);\n }\n throw err;\n }\n\n const result = RuleConfigurationSchema.safeParse(raw);\n\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` - ${i.path.join('.')}: ${i.message}`)\n .join('\\n');\n throw new Error(`Invalid rules configuration:\\n${issues}`);\n }\n\n return result.data;\n}\n","import type { INotifier } from '../../domain/ports/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ChannelRegistry — Map<string, INotifier> for named channel resolution.\n// No switch/case — pure Map-based lookup with fallback.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ChannelRegistry {\n private readonly _channels = new Map<string, INotifier>();\n private _default: INotifier | null = null;\n\n /**\n * Register a notifier for a given channel name.\n * Overwrites any existing registration for that name.\n */\n register(channel: string, notifier: INotifier): void {\n this._channels.set(channel, notifier);\n }\n\n /**\n * Set the default notifier to use when a channel is not found.\n */\n setDefault(notifier: INotifier): void {\n this._default = notifier;\n }\n\n /**\n * Resolve a channel name to its notifier instance.\n * Falls back to the default notifier if the channel is unknown.\n *\n * @throws {Error} if the channel is unknown and no default is set\n */\n resolve(channel: string): INotifier {\n const instance = this._channels.get(channel);\n if (instance) {\n return instance;\n }\n if (this._default) {\n return this._default;\n }\n throw new Error(\n `Unknown channel \"${channel}\" and no default notifier configured`,\n );\n }\n\n /**\n * Check if a channel is registered.\n */\n has(channel: string): boolean {\n return this._channels.has(channel);\n }\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { ValidatedRuleCondition } from '../../domain/entities/rule.js';\nimport { ALERT_TYPE_LABELS } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ConditionEvaluator — compile RuleCondition → predicate function.\n// No switch/case — uses Record<string, matcher> pattern.\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype Predicate = (cluster: AlertCluster, analysis?: LLMAnalysis) => boolean;\n\ntype MatcherFactory = (value: unknown) => Predicate;\n\n/**\n * Map of condition field names to matcher factories.\n * Each factory takes the condition value and returns a predicate function.\n * This is the Record<string, matcher> pattern — no switch/case.\n */\nconst MATCHER_MAP: Record<string, MatcherFactory> = {\n serviceName: (value) => {\n const target = (value as string).toLowerCase();\n return (cluster) => cluster.serviceName.toLowerCase() === target;\n },\n\n alertType: (value) => {\n return (cluster) => cluster.alertType === value;\n },\n\n severity: (value) => {\n return (cluster) => {\n const config = ALERT_TYPE_LABELS[cluster.alertType];\n return config?.severity === value;\n };\n },\n\n endpointPath: (value) => {\n return (cluster) => cluster.endpointPath === value;\n },\n\n alertCount: (value) => {\n const range = value as { min?: number; max?: number };\n return (cluster) => {\n const count = cluster.alertCount;\n if (range.min !== undefined && count < range.min) return false;\n if (range.max !== undefined && count > range.max) return false;\n return true;\n };\n },\n\n latencyP99Ms: (value) => {\n const range = value as { min?: number; max?: number };\n return (cluster) => {\n const latency = cluster.latencyP99Ms;\n if (latency === undefined) return false;\n if (range.min !== undefined && latency < range.min) return false;\n if (range.max !== undefined && latency > range.max) return false;\n return true;\n };\n },\n\n labels: (value) => {\n const expected = value as Record<string, string>;\n return (cluster) => {\n const clusterLabels = cluster.labels;\n if (!clusterLabels) return false;\n return Object.entries(expected).every(\n ([key, val]) => clusterLabels[key] === val,\n );\n };\n },\n\n urgencyLevel: (value) => {\n return (_cluster, analysis) => {\n if (!analysis) return false;\n return analysis.urgency_level === value;\n };\n },\n\n requiresRollback: (value) => {\n return (_cluster, analysis) => {\n if (!analysis) return false;\n return analysis.requires_rollback === value;\n };\n },\n\n impactedServices: (value) => {\n const targets = value as string[];\n return (_cluster, analysis) => {\n if (!analysis) return false;\n return targets.some((t) => analysis.impacted_services.includes(t));\n };\n },\n};\n\n/**\n * Compile a RuleCondition into a predicate function.\n * The returned function can be called with (cluster, analysis?) to evaluate the condition.\n * Pre-compilation ensures the field iteration and matcher assembly happen once at load time.\n *\n * All specified conditions must match (AND logic).\n * If no fields are specified, the predicate returns true (match-all).\n */\nexport function compileCondition(condition: ValidatedRuleCondition): Predicate {\n const predicates: Predicate[] = [];\n\n for (const [field, value] of Object.entries(condition)) {\n if (value === undefined) continue;\n\n const factory = MATCHER_MAP[field];\n if (factory) {\n predicates.push(factory(value));\n }\n }\n\n // If no conditions specified, match everything (pass-through)\n if (predicates.length === 0) {\n return () => true;\n }\n\n // AND logic: all predicates must pass\n return (cluster, analysis) => predicates.every((p) => p(cluster, analysis));\n}\n","import { RuleActionType } from '../../domain/entities/rule.js';\nimport type { RuleAction } from '../../domain/entities/rule.js';\nimport type { RuleActionResult } from '../../domain/ports/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ActionDispatcher — dispatch RuleAction[] → RuleActionResult.\n// No switch/case — uses Record<RuleActionType, handler> pattern.\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ActionHandler = (action: RuleAction, result: RuleActionResult) => void;\n\n/**\n * Map of action type to handler function.\n * Record<RuleActionType, handler> pattern — NO switch/case.\n * Each handler mutates the result object for the given action.\n */\nconst HANDLER_MAP: Record<string, ActionHandler> = {\n [RuleActionType.Suppress]: (_action, result) => {\n result.suppressed = true;\n },\n\n [RuleActionType.Route]: (action, result) => {\n result.actions.push(action);\n },\n\n [RuleActionType.Escalate]: (action, result) => {\n result.actions.push(action);\n },\n\n [RuleActionType.Tag]: (action, result) => {\n const tagAction = action as { type: RuleActionType.Tag; key: string; value: string };\n result.tags[tagAction.key] = tagAction.value;\n },\n};\n\n/**\n * Dispatch an array of RuleAction into a RuleActionResult.\n * All actions are processed in order.\n * The result accumulates: suppressed flag, route/escalate actions, and tags.\n *\n * Pure function — no I/O, no side effects beyond the returned result.\n */\nexport function dispatchActions(actions: RuleAction[]): RuleActionResult {\n const result: RuleActionResult = {\n suppressed: false,\n actions: [],\n tags: {},\n };\n\n for (const action of actions) {\n const handler = HANDLER_MAP[action.type];\n if (handler) {\n handler(action, result);\n }\n }\n\n return result;\n}\n","import type { AlertCluster } from '../../domain/entities/cluster.js';\nimport type { LLMAnalysis } from '../../domain/entities/incident.js';\nimport type { IRuleEngine, RuleActionResult } from '../../domain/ports/index.js';\nimport type { ValidatedRule, ValidatedRuleConfiguration } from '../../domain/entities/rule.js';\nimport { RuleEvaluationPhase } from '../../domain/entities/rule.js';\nimport { compileCondition } from './condition-evaluator.js';\nimport { dispatchActions } from './action-dispatcher.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// RuleEngine — implements IRuleEngine with first-match-wins evaluation.\n// Pre-compiles all rule conditions at construction time for hot-path performance.\n// No switch/case — delegates to compileCondition (Record<string, matcher>)\n// and dispatchActions (Record<RuleActionType, handler>).\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface CompiledRule {\n id: string;\n predicate: (cluster: AlertCluster, analysis?: LLMAnalysis) => boolean;\n result: ReturnType<typeof dispatchActions>;\n}\n\nexport class RuleEngine implements IRuleEngine {\n private readonly preLlmRules: CompiledRule[];\n private readonly postLlmRules: CompiledRule[];\n\n constructor(config: ValidatedRuleConfiguration) {\n this.preLlmRules = this.compileSection(config[RuleEvaluationPhase.PreLlm].rules);\n this.postLlmRules = this.compileSection(config[RuleEvaluationPhase.PostLlm].rules);\n }\n\n /**\n * Evaluate PRE-LLM rules against a cluster.\n * First-match-wins — returns result of first matching rule.\n * If no rule matches, returns pass-through (suppressed=false, no actions).\n */\n evaluatePreLlm(cluster: AlertCluster): RuleActionResult {\n return this.evaluateRules(this.preLlmRules, cluster);\n }\n\n /**\n * Evaluate POST-LLM rules against a cluster and LLM analysis.\n * First-match-wins — returns result of first matching rule.\n * If no rule matches, returns pass-through.\n */\n evaluatePostLlm(\n cluster: AlertCluster,\n analysis: LLMAnalysis,\n ): RuleActionResult {\n return this.evaluateRules(this.postLlmRules, cluster, analysis);\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private compileSection(rules: ValidatedRule[]): CompiledRule[] {\n return rules.map((rule) => ({\n id: rule.id,\n predicate: compileCondition(rule.condition),\n result: dispatchActions(rule.actions),\n }));\n }\n\n private evaluateRules(\n rules: CompiledRule[],\n cluster: AlertCluster,\n analysis?: LLMAnalysis,\n ): RuleActionResult {\n for (const rule of rules) {\n if (rule.predicate(cluster, analysis)) {\n return {\n ...rule.result,\n matchedRuleId: rule.id,\n };\n }\n }\n\n // No rule matched — pass-through\n return {\n suppressed: false,\n actions: [],\n tags: {},\n };\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { FactoryRegistry } from '../../shared/factory-registry.js';\nimport type { Config } from '../../shared/config/index.js';\nimport type { INotifier } from '../../domain/ports/index.js';\nimport type { IRuleEngine } from '../../domain/ports/index.js';\nimport { SlackNotifier } from './slack.adapter.js';\nimport { TeamsNotifier } from './teams.adapter.js';\nimport { RoutingNotifier } from './routing-notifier.js';\nimport { parseRuleConfig } from '../rules/yaml-rule-loader.js';\nimport { ChannelRegistry } from '../rules/channel-registry.js';\nimport { RuleEngine } from '../rules/rule-engine.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// createNotifier — single instantiation point, no switch/case.\n// Registry holds factories, resolve picks the right one.\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction buildNotifierRegistry(config: Config): FactoryRegistry<INotifier> {\n const registry = new FactoryRegistry<INotifier>();\n\n registry.register('teams', () => {\n if (!config.teamsWebhookUrl) {\n throw new Error('NOTIFIER_TYPE=teams requires TEAMS_WEBHOOK_URL to be set');\n }\n return new TeamsNotifier(config.teamsWebhookUrl);\n });\n\n registry.register('slack', () => {\n if (!config.slackBotToken || !config.slackChannel) {\n throw new Error('NOTIFIER_TYPE=slack requires SLACK_BOT_TOKEN and SLACK_CHANNEL to be set');\n }\n return new SlackNotifier(config.slackBotToken, config.slackChannel);\n });\n\n // Default: Slack (matches prior switch behavior where default was Slack)\n registry.registerDefault(() => new SlackNotifier('dummy-token', '#alerts'));\n\n return registry;\n}\n\n/**\n * Creates the notifier for the application.\n *\n * When `config.rulesConfigPath` is set:\n * - Reads and validates the rules YAML config\n * - Creates a ChannelRegistry with the default notifier as fallback\n * - Wraps the default notifier with a RoutingNotifier for multi-channel dispatch\n *\n * When `config.rulesConfigPath` is NOT set:\n * - Returns the default notifier directly (backward-compatible)\n */\nexport function createNotifier(config: Config): INotifier {\n const registry = buildNotifierRegistry(config);\n const defaultNotifier = registry.resolve(config.notifierType);\n\n if (!config.rulesConfigPath) {\n console.info('[createNotifier] RULES_CONFIG_PATH not set — rule engine disabled, using default notifier');\n return defaultNotifier;\n }\n\n // Read and parse rules YAML\n const yamlContent = readFileSync(config.rulesConfigPath, 'utf-8');\n parseRuleConfig(yamlContent); // Validate — throws on invalid config\n\n // Create channel registry with default notifier as fallback\n const channelRegistry = new ChannelRegistry();\n channelRegistry.setDefault(defaultNotifier);\n\n // Wrap with routing notifier for multi-channel dispatch\n return new RoutingNotifier(channelRegistry, defaultNotifier);\n}\n\n/**\n * Creates the RuleEngine from a YAML rules config file.\n *\n * Returns undefined when `config.rulesConfigPath` is not set,\n * meaning rule evaluation is disabled (pass-through behavior).\n */\nexport function createRuleEngine(config: Config): IRuleEngine | undefined {\n if (!config.rulesConfigPath) {\n return undefined;\n }\n\n const yamlContent = readFileSync(config.rulesConfigPath, 'utf-8');\n const ruleConfig = parseRuleConfig(yamlContent);\n return new RuleEngine(ruleConfig);\n}\n","import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';\nimport type { IAlertQueue } from '../../domain/ports/index.js';\nimport type { NormalizedAlert } from '../../domain/entities/alert.js';\nimport { randomUUID } from 'node:crypto';\nimport { createLogger } from '../../shared/logger/index.js';\nimport { Fingerprint } from '../../domain/value-objects/fingerprint.js';\n\nconst logger = createLogger();\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SQSAlertQueue — Infrastructure adapter.\n// Implements IAlertQueue by publishing to AWS SQS.\n// SQSClient is initialized lazily (singleton) on first use to avoid\n// module-level AWS credential errors in local dev.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface SendMessageParams {\n messageBody: string;\n messageGroupId: string;\n messageDeduplicationId: string;\n}\n\nexport class SQSAlertQueue implements IAlertQueue {\n private sqsClient: SQSClient | null = null;\n\n constructor(\n private readonly queueUrl: string,\n private readonly region?: string,\n ) {}\n\n private getClient(): SQSClient {\n if (!this.sqsClient) {\n this.sqsClient = new SQSClient(this.region ? { region: this.region } : {});\n }\n return this.sqsClient;\n }\n\n async sendMessage(params: SendMessageParams): Promise<void> {\n await this.getClient().send(\n new SendMessageCommand({\n QueueUrl: this.queueUrl,\n MessageBody: params.messageBody,\n MessageGroupId: params.messageGroupId,\n MessageDeduplicationId: params.messageDeduplicationId,\n }),\n );\n }\n\n async publish(alert: NormalizedAlert): Promise<void> {\n const correlationId = randomUUID();\n const fingerprint = Fingerprint.fromAlert(alert).toString();\n\n try {\n await this.sendMessage({\n messageBody: JSON.stringify({ correlationId, alerts: [alert] }),\n messageGroupId: fingerprint,\n messageDeduplicationId: correlationId,\n });\n } catch (err) {\n logger.error({ err, alert: fingerprint }, 'Failed to publish alert to SQS');\n throw err;\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// InMemoryAlertQueue — Local dev / test adapter.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class InMemoryAlertQueue implements IAlertQueue {\n readonly published: NormalizedAlert[] = [];\n\n async publish(alert: NormalizedAlert): Promise<void> {\n this.published.push(alert);\n const fingerprint = Fingerprint.fromAlert(alert).toString();\n logger.info({ alert: fingerprint }, 'Mocked publishing alert to InMemoryAlertQueue');\n }\n}\n","import { SQSClient, GetQueueAttributesCommand } from '@aws-sdk/client-sqs';\nimport { sqsQueueLag } from '../../shared/metrics/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// startSqsLagPoller — Background poller for SQS queue depth.\n// Polls ApproximateNumberOfMessages and updates the sqsQueueLag gauge.\n// Must NOT be called in the webhook critical path — worker module scope only.\n//\n// Returns a cleanup function (clearInterval) for test teardown / Lambda shutdown.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function startSqsLagPoller(\n queueUrl: string,\n intervalMs: number,\n region?: string,\n): () => void {\n const client = new SQSClient(region ? { region } : {});\n\n const poll = async (): Promise<void> => {\n try {\n const result = await client.send(\n new GetQueueAttributesCommand({\n QueueUrl: queueUrl,\n AttributeNames: ['ApproximateNumberOfMessages'],\n }),\n );\n const raw = result.Attributes?.['ApproximateNumberOfMessages'];\n if (raw !== undefined) {\n sqsQueueLag.set({ queue_name: 'alerts' }, parseInt(raw, 10));\n }\n } catch {\n // Swallow errors: gauge retains last value; process must not exit.\n }\n };\n\n const timer = setInterval(() => {\n void poll();\n }, intervalMs);\n\n return () => clearInterval(timer);\n}\n","import type { ITraceRepository } from '../../domain/ports/index.js';\nimport { HTTP_TIMEOUT_MS } from '../../shared/constants.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// LokiTraceRepository — Infrastructure adapter.\n// Implements ITraceRepository using Loki's HTTP query API.\n// Swap for DatadogTraceRepository, JaegerTraceRepository, etc.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class LokiTraceRepository implements ITraceRepository {\n constructor(\n private readonly lokiUrl: string,\n private readonly apiKey?: string,\n ) {}\n\n async findByTraceId(traceId: string): Promise<Record<string, unknown>[]> {\n const query = encodeURIComponent(`{trace_id=\"${traceId}\"}`);\n const url = `${this.lokiUrl}/loki/api/v1/query_range?query=${query}&limit=50`;\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n\n const res = await fetch(url, {\n headers,\n signal: AbortSignal.timeout(HTTP_TIMEOUT_MS.Default),\n });\n if (!res.ok) throw new Error(`Loki query failed: ${res.status} ${res.statusText}`);\n\n const body = (await res.json()) as LokiResponse;\n return this.parseResponse(body);\n }\n\n private parseResponse(body: LokiResponse): Record<string, unknown>[] {\n return body.data.result.flatMap((stream) =>\n stream.values.map(([ts, line]) => ({\n timestamp: ts,\n ...this.tryParseJSON(line),\n })),\n );\n }\n\n private tryParseJSON(line: string): Record<string, unknown> {\n try {\n return JSON.parse(line) as Record<string, unknown>;\n } catch {\n return { message: line };\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MockTraceRepository — Test adapter. Returns predictable fake traces.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class MockTraceRepository implements ITraceRepository {\n constructor(private readonly fixtures: Map<string, Record<string, unknown>[]> = new Map()) {}\n\n async findByTraceId(traceId: string): Promise<Record<string, unknown>[]> {\n return this.fixtures.get(traceId) ?? [];\n }\n\n addFixture(traceId: string, spans: Record<string, unknown>[]): void {\n this.fixtures.set(traceId, spans);\n }\n}\n\n// Internal types for Loki response shape\ninterface LokiResponse {\n data: {\n result: Array<{\n stream: Record<string, string>;\n values: Array<[string, string]>;\n }>;\n };\n}\n","import { GetParametersCommand, SSMClient } from '@aws-sdk/client-ssm';\nimport { z } from 'zod';\nimport { LLM_FALLBACK_DEFAULTS } from '../constants.js';\nimport { createLogger } from '../logger/index.js';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Config — reads and validates all env vars at startup.\n// The process exits immediately if a required variable is missing.\n// No silent failures, no undefined values in the codebase.\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Load secrets from SSM using SSM_PREFIX (AWS Lambda deployment)\nasync function loadSecretsFromSSM(): Promise<void> {\n const prefix = process.env.SSM_PREFIX;\n // Only run in AWS (when SSM_PREFIX is set), skip in local dev\n if (!prefix) {\n return;\n }\n\n const client = new SSMClient({});\n const names = [\n `${prefix}/llm-provider`,\n `${prefix}/llm-api-key`,\n `${prefix}/llm-model`,\n `${prefix}/slack-bot-token`,\n `${prefix}/slack-signing-secret`,\n `${prefix}/slack-channel`,\n `${prefix}/loki-url`,\n `${prefix}/redis-url`,\n `${prefix}/llm-fallback-models`,\n `${prefix}/llm-fallback-timeout-ms`,\n ];\n\n try {\n const result = await client.send(\n new GetParametersCommand({\n Names: names,\n WithDecryption: true,\n }),\n );\n\n for (const param of result.Parameters ?? []) {\n if (param.Name && param.Value) {\n // Convert /junando/llm-provider -> LLM_PROVIDER\n const key = param.Name.replace(`${prefix}/`, '').replaceAll('-', '_').toUpperCase();\n process.env[key] = param.Value;\n }\n }\n } catch (err) {\n createLogger().error({ err }, 'Failed to load SSM parameters');\n }\n}\n\nconst ConfigSchema = z\n .object({\n llmProvider: z.enum(['gemini', 'claude', 'openrouter', 'qwen']),\n llmApiKey: z.string().min(1),\n llmModel: z.string().optional().transform((v) => v === '' ? undefined : v),\n // Notifier selector — defaults to 'slack' for backward compatibility\n notifierType: z.enum(['slack', 'teams']).default('slack'),\n // Slack fields — optional at schema level; superRefine enforces them conditionally\n slackBotToken: z.string().startsWith('xoxb-').optional(),\n slackSigningSecret: z.string().min(1).optional(),\n slackChannel: z.string().startsWith('#').optional(),\n // Teams field\n teamsWebhookUrl: z.string().url().optional(),\n lokiUrl: z.string().optional().transform((v) => v === '' ? undefined : v), // URL with embedded credentials — skip .url() which rejects user:pass@ format. Optional: containers may run without Loki; logger falls back to stdout. Empty string is coerced to undefined (env var unset vs empty are equivalent).\n redisUrl: z.string().url(),\n sqsQueueUrl: z.string().url().optional().or(z.literal('')),\n dedupTtlSeconds: z.coerce.number().int().positive().default(300),\n clusterWindowMs: z.coerce.number().int().positive().default(120_000),\n logLevel: z.enum(['trace', 'debug', 'info', 'warn', 'error']).default('info'),\n nodeEnv: z.enum(['development', 'test', 'production']).default('development'),\n llmFallbackModels: z\n .string()\n .optional()\n .transform((v) => {\n if (v === undefined) return LLM_FALLBACK_DEFAULTS.Models;\n if (!v) return [];\n return v.split(',').map((s) => s.trim()).filter(Boolean);\n }),\n llmFallbackTimeoutMs: z.coerce.number().int().positive().default(LLM_FALLBACK_DEFAULTS.TimeoutMs),\n // Optional path to rules.yaml for business rules engine. When not set, rule engine is disabled.\n rulesConfigPath: z\n .string()\n .optional()\n .transform((v) => (v === '' ? undefined : v)),\n })\n .superRefine((data, ctx) => {\n if (data.notifierType === 'slack') {\n if (!data.slackBotToken) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slackBotToken'],\n message: '[notifierType: slack] SLACK_BOT_TOKEN is required and must start with xoxb-',\n });\n }\n if (!data.slackChannel) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slackChannel'],\n message: '[notifierType: slack] SLACK_CHANNEL is required and must start with #',\n });\n }\n if (!data.slackSigningSecret) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['slackSigningSecret'],\n message: '[notifierType: slack] SLACK_SIGNING_SECRET is required (used to verify Slack interactivity callbacks)',\n });\n }\n }\n if (data.notifierType === 'teams') {\n if (!data.teamsWebhookUrl) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['teamsWebhookUrl'],\n message: '[notifierType: teams] TEAMS_WEBHOOK_URL is required',\n });\n } else {\n // Parse the URL and require api-version as a real query parameter,\n // not just any substring (which would accept e.g. an api-version=\n // segment baked into the URL path).\n let parsed: URL | undefined;\n try {\n parsed = new URL(data.teamsWebhookUrl);\n } catch {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['teamsWebhookUrl'],\n message: '[notifierType: teams] TEAMS_WEBHOOK_URL must be a valid URL',\n });\n }\n if (parsed && !parsed.searchParams.has('api-version')) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['teamsWebhookUrl'],\n message: '[notifierType: teams] TEAMS_WEBHOOK_URL must include api-version= as a query parameter',\n });\n }\n }\n }\n });\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport async function loadConfig(): Promise<Config> {\n await loadSecretsFromSSM();\n\n const result = ConfigSchema.safeParse({\n llmProvider: process.env['LLM_PROVIDER'],\n llmApiKey: process.env['LLM_API_KEY'],\n llmModel: process.env['LLM_MODEL'],\n notifierType: process.env['NOTIFIER_TYPE'],\n slackBotToken: process.env['SLACK_BOT_TOKEN'],\n slackSigningSecret: process.env['SLACK_SIGNING_SECRET'],\n slackChannel: process.env['SLACK_CHANNEL'],\n teamsWebhookUrl: process.env['TEAMS_WEBHOOK_URL'],\n lokiUrl: process.env['LOKI_URL'],\n redisUrl: process.env['REDIS_URL'],\n sqsQueueUrl: process.env['SQS_QUEUE_URL'],\n dedupTtlSeconds: process.env['DEDUP_TTL_SECONDS'],\n clusterWindowMs: process.env['CLUSTER_WINDOW_MS'],\n logLevel: process.env['LOG_LEVEL'],\n nodeEnv: process.env['NODE_ENV'],\n llmFallbackModels: process.env['LLM_FALLBACK_MODELS'],\n llmFallbackTimeoutMs: process.env['LLM_FALLBACK_TIMEOUT_MS'],\n rulesConfigPath: process.env['RULES_CONFIG_PATH'],\n });\n\n if (!result.success) {\n const errorMessages = result.error.issues.map(\n (issue) => `${issue.path.join('.')}: ${issue.message}`,\n );\n throw new Error(`Invalid configuration:\\n - ${errorMessages.join('\\n - ')}`);\n }\n\n return result.data;\n}\n"],"mappings":"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"}
|