@adonis-agora/telescope 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/configure.d.ts +16 -0
  4. package/dist/configure.d.ts.map +1 -0
  5. package/dist/configure.js +75 -0
  6. package/dist/configure.js.map +1 -0
  7. package/dist/providers/telescope_ai_provider.d.ts +20 -0
  8. package/dist/providers/telescope_ai_provider.d.ts.map +1 -0
  9. package/dist/providers/telescope_ai_provider.js +45 -0
  10. package/dist/providers/telescope_ai_provider.js.map +1 -0
  11. package/dist/providers/telescope_alerts_provider.d.ts +23 -0
  12. package/dist/providers/telescope_alerts_provider.d.ts.map +1 -0
  13. package/dist/providers/telescope_alerts_provider.js +72 -0
  14. package/dist/providers/telescope_alerts_provider.js.map +1 -0
  15. package/dist/providers/telescope_provider.d.ts +43 -0
  16. package/dist/providers/telescope_provider.d.ts.map +1 -0
  17. package/dist/providers/telescope_provider.js +103 -0
  18. package/dist/providers/telescope_provider.js.map +1 -0
  19. package/dist/providers/telescope_ui_provider.d.ts +21 -0
  20. package/dist/providers/telescope_ui_provider.d.ts.map +1 -0
  21. package/dist/providers/telescope_ui_provider.js +119 -0
  22. package/dist/providers/telescope_ui_provider.js.map +1 -0
  23. package/dist/providers/telescope_watchers_provider.d.ts +31 -0
  24. package/dist/providers/telescope_watchers_provider.d.ts.map +1 -0
  25. package/dist/providers/telescope_watchers_provider.js +116 -0
  26. package/dist/providers/telescope_watchers_provider.js.map +1 -0
  27. package/dist/src/ai/define_config.d.ts +56 -0
  28. package/dist/src/ai/define_config.d.ts.map +1 -0
  29. package/dist/src/ai/define_config.js +39 -0
  30. package/dist/src/ai/define_config.js.map +1 -0
  31. package/dist/src/ai/diagnoser.d.ts +34 -0
  32. package/dist/src/ai/diagnoser.d.ts.map +1 -0
  33. package/dist/src/ai/diagnoser.js +74 -0
  34. package/dist/src/ai/diagnoser.js.map +1 -0
  35. package/dist/src/ai/diagnosis_cache.d.ts +43 -0
  36. package/dist/src/ai/diagnosis_cache.d.ts.map +1 -0
  37. package/dist/src/ai/diagnosis_cache.js +56 -0
  38. package/dist/src/ai/diagnosis_cache.js.map +1 -0
  39. package/dist/src/ai/factory.d.ts +15 -0
  40. package/dist/src/ai/factory.d.ts.map +1 -0
  41. package/dist/src/ai/factory.js +24 -0
  42. package/dist/src/ai/factory.js.map +1 -0
  43. package/dist/src/ai/index.d.ts +14 -0
  44. package/dist/src/ai/index.d.ts.map +1 -0
  45. package/dist/src/ai/index.js +15 -0
  46. package/dist/src/ai/index.js.map +1 -0
  47. package/dist/src/ai/prompt.d.ts +31 -0
  48. package/dist/src/ai/prompt.d.ts.map +1 -0
  49. package/dist/src/ai/prompt.js +66 -0
  50. package/dist/src/ai/prompt.js.map +1 -0
  51. package/dist/src/ai/telescope_ai_diagnoser.d.ts +79 -0
  52. package/dist/src/ai/telescope_ai_diagnoser.d.ts.map +1 -0
  53. package/dist/src/ai/telescope_ai_diagnoser.js +111 -0
  54. package/dist/src/ai/telescope_ai_diagnoser.js.map +1 -0
  55. package/dist/src/alerts/alert_channel.d.ts +69 -0
  56. package/dist/src/alerts/alert_channel.d.ts.map +1 -0
  57. package/dist/src/alerts/alert_channel.js +114 -0
  58. package/dist/src/alerts/alert_channel.js.map +1 -0
  59. package/dist/src/alerts/alert_rule.d.ts +86 -0
  60. package/dist/src/alerts/alert_rule.d.ts.map +1 -0
  61. package/dist/src/alerts/alert_rule.js +2 -0
  62. package/dist/src/alerts/alert_rule.js.map +1 -0
  63. package/dist/src/alerts/alerter.d.ts +72 -0
  64. package/dist/src/alerts/alerter.d.ts.map +1 -0
  65. package/dist/src/alerts/alerter.js +248 -0
  66. package/dist/src/alerts/alerter.js.map +1 -0
  67. package/dist/src/alerts/define_config.d.ts +68 -0
  68. package/dist/src/alerts/define_config.d.ts.map +1 -0
  69. package/dist/src/alerts/define_config.js +57 -0
  70. package/dist/src/alerts/define_config.js.map +1 -0
  71. package/dist/src/alerts/exception_source.d.ts +44 -0
  72. package/dist/src/alerts/exception_source.d.ts.map +1 -0
  73. package/dist/src/alerts/exception_source.js +79 -0
  74. package/dist/src/alerts/exception_source.js.map +1 -0
  75. package/dist/src/alerts/index.d.ts +16 -0
  76. package/dist/src/alerts/index.d.ts.map +1 -0
  77. package/dist/src/alerts/index.js +17 -0
  78. package/dist/src/alerts/index.js.map +1 -0
  79. package/dist/src/alerts/new_exception_tracker.d.ts +50 -0
  80. package/dist/src/alerts/new_exception_tracker.d.ts.map +1 -0
  81. package/dist/src/alerts/new_exception_tracker.js +74 -0
  82. package/dist/src/alerts/new_exception_tracker.js.map +1 -0
  83. package/dist/src/alerts/parse_duration.d.ts +10 -0
  84. package/dist/src/alerts/parse_duration.d.ts.map +1 -0
  85. package/dist/src/alerts/parse_duration.js +27 -0
  86. package/dist/src/alerts/parse_duration.js.map +1 -0
  87. package/dist/src/alerts/slack_format.d.ts +60 -0
  88. package/dist/src/alerts/slack_format.d.ts.map +1 -0
  89. package/dist/src/alerts/slack_format.js +122 -0
  90. package/dist/src/alerts/slack_format.js.map +1 -0
  91. package/dist/src/context_accessor.d.ts +30 -0
  92. package/dist/src/context_accessor.d.ts.map +1 -0
  93. package/dist/src/context_accessor.js +20 -0
  94. package/dist/src/context_accessor.js.map +1 -0
  95. package/dist/src/define_config.d.ts +109 -0
  96. package/dist/src/define_config.d.ts.map +1 -0
  97. package/dist/src/define_config.js +38 -0
  98. package/dist/src/define_config.js.map +1 -0
  99. package/dist/src/diagnostics_registry.d.ts +46 -0
  100. package/dist/src/diagnostics_registry.d.ts.map +1 -0
  101. package/dist/src/diagnostics_registry.js +34 -0
  102. package/dist/src/diagnostics_registry.js.map +1 -0
  103. package/dist/src/diagnostics_watcher.d.ts +72 -0
  104. package/dist/src/diagnostics_watcher.d.ts.map +1 -0
  105. package/dist/src/diagnostics_watcher.js +119 -0
  106. package/dist/src/diagnostics_watcher.js.map +1 -0
  107. package/dist/src/entry.d.ts +81 -0
  108. package/dist/src/entry.d.ts.map +1 -0
  109. package/dist/src/entry.js +34 -0
  110. package/dist/src/entry.js.map +1 -0
  111. package/dist/src/exception_family_hash.d.ts +29 -0
  112. package/dist/src/exception_family_hash.d.ts.map +1 -0
  113. package/dist/src/exception_family_hash.js +30 -0
  114. package/dist/src/exception_family_hash.js.map +1 -0
  115. package/dist/src/exception_watcher.d.ts +66 -0
  116. package/dist/src/exception_watcher.d.ts.map +1 -0
  117. package/dist/src/exception_watcher.js +94 -0
  118. package/dist/src/exception_watcher.js.map +1 -0
  119. package/dist/src/extension/registry.d.ts +17 -0
  120. package/dist/src/extension/registry.d.ts.map +1 -0
  121. package/dist/src/extension/registry.js +56 -0
  122. package/dist/src/extension/registry.js.map +1 -0
  123. package/dist/src/extension/types.d.ts +158 -0
  124. package/dist/src/extension/types.d.ts.map +1 -0
  125. package/dist/src/extension/types.js +5 -0
  126. package/dist/src/extension/types.js.map +1 -0
  127. package/dist/src/index.d.ts +36 -0
  128. package/dist/src/index.d.ts.map +1 -0
  129. package/dist/src/index.js +28 -0
  130. package/dist/src/index.js.map +1 -0
  131. package/dist/src/redaction/redact.d.ts +93 -0
  132. package/dist/src/redaction/redact.d.ts.map +1 -0
  133. package/dist/src/redaction/redact.js +184 -0
  134. package/dist/src/redaction/redact.js.map +1 -0
  135. package/dist/src/redaction/redacting_store.d.ts +28 -0
  136. package/dist/src/redaction/redacting_store.d.ts.map +1 -0
  137. package/dist/src/redaction/redacting_store.js +49 -0
  138. package/dist/src/redaction/redacting_store.js.map +1 -0
  139. package/dist/src/registry.d.ts +26 -0
  140. package/dist/src/registry.d.ts.map +1 -0
  141. package/dist/src/registry.js +28 -0
  142. package/dist/src/registry.js.map +1 -0
  143. package/dist/src/request_watcher.d.ts +44 -0
  144. package/dist/src/request_watcher.d.ts.map +1 -0
  145. package/dist/src/request_watcher.js +37 -0
  146. package/dist/src/request_watcher.js.map +1 -0
  147. package/dist/src/service.d.ts +36 -0
  148. package/dist/src/service.d.ts.map +1 -0
  149. package/dist/src/service.js +65 -0
  150. package/dist/src/service.js.map +1 -0
  151. package/dist/src/store.d.ts +56 -0
  152. package/dist/src/store.d.ts.map +1 -0
  153. package/dist/src/store.js +2 -0
  154. package/dist/src/store.js.map +1 -0
  155. package/dist/src/stores/factory.d.ts +61 -0
  156. package/dist/src/stores/factory.d.ts.map +1 -0
  157. package/dist/src/stores/factory.js +42 -0
  158. package/dist/src/stores/factory.js.map +1 -0
  159. package/dist/src/stores/lucid.d.ts +138 -0
  160. package/dist/src/stores/lucid.d.ts.map +1 -0
  161. package/dist/src/stores/lucid.js +257 -0
  162. package/dist/src/stores/lucid.js.map +1 -0
  163. package/dist/src/stores/memory.d.ts +31 -0
  164. package/dist/src/stores/memory.d.ts.map +1 -0
  165. package/dist/src/stores/memory.js +117 -0
  166. package/dist/src/stores/memory.js.map +1 -0
  167. package/dist/src/telescope_middleware.d.ts +19 -0
  168. package/dist/src/telescope_middleware.d.ts.map +1 -0
  169. package/dist/src/telescope_middleware.js +56 -0
  170. package/dist/src/telescope_middleware.js.map +1 -0
  171. package/dist/src/ui/api.d.ts +49 -0
  172. package/dist/src/ui/api.d.ts.map +1 -0
  173. package/dist/src/ui/api.js +155 -0
  174. package/dist/src/ui/api.js.map +1 -0
  175. package/dist/src/ui/dashboard.d.ts +8 -0
  176. package/dist/src/ui/dashboard.d.ts.map +1 -0
  177. package/dist/src/ui/dashboard.html +626 -0
  178. package/dist/src/ui/dashboard.js +29 -0
  179. package/dist/src/ui/dashboard.js.map +1 -0
  180. package/dist/src/ui/define_config.d.ts +87 -0
  181. package/dist/src/ui/define_config.d.ts.map +1 -0
  182. package/dist/src/ui/define_config.js +104 -0
  183. package/dist/src/ui/define_config.js.map +1 -0
  184. package/dist/src/ui/extension_api.d.ts +23 -0
  185. package/dist/src/ui/extension_api.d.ts.map +1 -0
  186. package/dist/src/ui/extension_api.js +50 -0
  187. package/dist/src/ui/extension_api.js.map +1 -0
  188. package/dist/src/ui/guard.d.ts +33 -0
  189. package/dist/src/ui/guard.d.ts.map +1 -0
  190. package/dist/src/ui/guard.js +47 -0
  191. package/dist/src/ui/guard.js.map +1 -0
  192. package/dist/src/ui/http.d.ts +47 -0
  193. package/dist/src/ui/http.d.ts.map +1 -0
  194. package/dist/src/ui/http.js +43 -0
  195. package/dist/src/ui/http.js.map +1 -0
  196. package/dist/src/ui/index.d.ts +12 -0
  197. package/dist/src/ui/index.d.ts.map +1 -0
  198. package/dist/src/ui/index.js +13 -0
  199. package/dist/src/ui/index.js.map +1 -0
  200. package/dist/src/watchers/cache_watcher.d.ts +60 -0
  201. package/dist/src/watchers/cache_watcher.d.ts.map +1 -0
  202. package/dist/src/watchers/cache_watcher.js +72 -0
  203. package/dist/src/watchers/cache_watcher.js.map +1 -0
  204. package/dist/src/watchers/define_config.d.ts +38 -0
  205. package/dist/src/watchers/define_config.d.ts.map +1 -0
  206. package/dist/src/watchers/define_config.js +17 -0
  207. package/dist/src/watchers/define_config.js.map +1 -0
  208. package/dist/src/watchers/emitter.d.ts +32 -0
  209. package/dist/src/watchers/emitter.d.ts.map +1 -0
  210. package/dist/src/watchers/emitter.js +2 -0
  211. package/dist/src/watchers/emitter.js.map +1 -0
  212. package/dist/src/watchers/http_client_watcher.d.ts +74 -0
  213. package/dist/src/watchers/http_client_watcher.d.ts.map +1 -0
  214. package/dist/src/watchers/http_client_watcher.js +168 -0
  215. package/dist/src/watchers/http_client_watcher.js.map +1 -0
  216. package/dist/src/watchers/index.d.ts +19 -0
  217. package/dist/src/watchers/index.d.ts.map +1 -0
  218. package/dist/src/watchers/index.js +19 -0
  219. package/dist/src/watchers/index.js.map +1 -0
  220. package/dist/src/watchers/logs_watcher.d.ts +82 -0
  221. package/dist/src/watchers/logs_watcher.d.ts.map +1 -0
  222. package/dist/src/watchers/logs_watcher.js +145 -0
  223. package/dist/src/watchers/logs_watcher.js.map +1 -0
  224. package/dist/src/watchers/lucid_query_watcher.d.ts +64 -0
  225. package/dist/src/watchers/lucid_query_watcher.d.ts.map +1 -0
  226. package/dist/src/watchers/lucid_query_watcher.js +84 -0
  227. package/dist/src/watchers/lucid_query_watcher.js.map +1 -0
  228. package/dist/src/watchers/mail_watcher.d.ts +51 -0
  229. package/dist/src/watchers/mail_watcher.d.ts.map +1 -0
  230. package/dist/src/watchers/mail_watcher.js +93 -0
  231. package/dist/src/watchers/mail_watcher.js.map +1 -0
  232. package/dist/src/watchers/normalize_http_target.d.ts +17 -0
  233. package/dist/src/watchers/normalize_http_target.d.ts.map +1 -0
  234. package/dist/src/watchers/normalize_http_target.js +41 -0
  235. package/dist/src/watchers/normalize_http_target.js.map +1 -0
  236. package/dist/src/watchers/query_family_hash.d.ts +8 -0
  237. package/dist/src/watchers/query_family_hash.d.ts.map +1 -0
  238. package/dist/src/watchers/query_family_hash.js +31 -0
  239. package/dist/src/watchers/query_family_hash.js.map +1 -0
  240. package/dist/src/watchers/record.d.ts +22 -0
  241. package/dist/src/watchers/record.d.ts.map +1 -0
  242. package/dist/src/watchers/record.js +48 -0
  243. package/dist/src/watchers/record.js.map +1 -0
  244. package/dist/stubs/config/telescope.stub +56 -0
  245. package/dist/stubs/config/telescope_ai.stub +36 -0
  246. package/dist/stubs/config/telescope_alerts.stub +47 -0
  247. package/dist/stubs/config/telescope_ui.stub +40 -0
  248. package/dist/stubs/config/telescope_watchers.stub +30 -0
  249. package/dist/stubs/database/migrations/create_telescope_entries_table.stub +39 -0
  250. package/dist/stubs/main.d.ts +6 -0
  251. package/dist/stubs/main.d.ts.map +1 -0
  252. package/dist/stubs/main.js +7 -0
  253. package/dist/stubs/main.js.map +1 -0
  254. package/package.json +140 -0
@@ -0,0 +1,248 @@
1
+ import { NewExceptionTracker } from './new_exception_tracker.js';
2
+ import { durationToMs } from './parse_duration.js';
3
+ /** Stack frames carried in a `new-exception` alert (the Slack channel re-clips). */
4
+ const ALERT_STACK_FRAME_LIMIT = 12;
5
+ /**
6
+ * Wires a source of exception {@link Entry} objects → rule evaluation → channels.
7
+ *
8
+ * Two evaluation paths, both driven by {@link evaluate} (called once per poll with
9
+ * the exception entries observed since the last poll):
10
+ * 1. The `new-exception` rule fires the FIRST time a family hash is seen within
11
+ * its window (and again after the window elapses / a {@link resolveFamily}),
12
+ * deduped by {@link NewExceptionTracker} and rate-limited by the per-family
13
+ * cooldown.
14
+ * 2. The `exception-rate` rule fires when the running count of exception entries
15
+ * in its trailing window crosses the threshold, rate-limited by a per-rule
16
+ * cooldown.
17
+ *
18
+ * Every fired alert fans out to ALL configured channels concurrently; one channel
19
+ * failing never blocks the others. NEVER throws into the caller: a channel failure
20
+ * is warn-logged (rate-limited per channel) and otherwise swallowed.
21
+ */
22
+ export class Alerter {
23
+ deps;
24
+ now;
25
+ logger;
26
+ tracker;
27
+ /** Per-rule-index last-fired wall time for the rate-rule cooldown. */
28
+ lastFiredAt = new Map();
29
+ /** Per-family last-fired wall time for the `new-exception` cooldown. */
30
+ lastFiredFamily = new Map();
31
+ /** Channels we've already warned about (rate-limit failure logs by name). */
32
+ warnedChannels = new Set();
33
+ /** Sliding-window timestamps of recent exception entries (rate rule). */
34
+ recentExceptions = [];
35
+ constructor(deps) {
36
+ this.deps = deps;
37
+ this.now = deps.now ?? Date.now;
38
+ this.logger = deps.logger ?? ((message) => console.warn(message));
39
+ this.tracker = new NewExceptionTracker(deps.maxFamilies);
40
+ }
41
+ /**
42
+ * Evaluate every rule over a freshly-observed batch of exception entries. Safe
43
+ * to call with an empty batch (rate rules still re-check their window against
44
+ * already-recorded timestamps). Never throws into the caller.
45
+ */
46
+ async evaluate(exceptionEntries) {
47
+ if (!this.deps.alerts.enabled)
48
+ return;
49
+ const nowMs = this.now();
50
+ try {
51
+ // Record each exception at its OWN event time (not poll time), so the
52
+ // rate window reflects when errors actually happened. The poller feeds
53
+ // entries oldest-first, so timestamps stay ascending for the cutoff shift.
54
+ for (const entry of exceptionEntries) {
55
+ this.recentExceptions.push(entry.createdAt.getTime());
56
+ }
57
+ await this.evaluateNewException(exceptionEntries, nowMs);
58
+ await this.evaluateExceptionRate(nowMs);
59
+ }
60
+ catch (error) {
61
+ // A bug in evaluation must never break the caller's poll loop.
62
+ this.logger(`Telescope alert evaluation failed: ${asMessage(error)}`);
63
+ }
64
+ }
65
+ /**
66
+ * Explicitly forget a family so its next occurrence pages again immediately —
67
+ * the "resolved → re-occurred" path. Also clears its cooldown.
68
+ */
69
+ resolveFamily(familyHash) {
70
+ this.tracker.resolve(familyHash);
71
+ this.lastFiredFamily.delete(familyHash);
72
+ }
73
+ /** Number of error families currently tracked (test/observability seam). */
74
+ get trackedFamilies() {
75
+ return this.tracker.size;
76
+ }
77
+ async evaluateNewException(entries, nowMs) {
78
+ const result = this.findRule('new-exception');
79
+ if (result === null)
80
+ return;
81
+ const windowMs = durationToMs(result.rule.window);
82
+ // Count occurrences per family once (O(n)) instead of re-scanning the batch
83
+ // per entry (O(n²)). This is the count within this poll batch.
84
+ const occurrencesByFamily = new Map();
85
+ for (const entry of entries) {
86
+ if (entry.familyHash === null)
87
+ continue;
88
+ occurrencesByFamily.set(entry.familyHash, (occurrencesByFamily.get(entry.familyHash) ?? 0) + 1);
89
+ }
90
+ for (const entry of entries) {
91
+ if (entry.familyHash === null)
92
+ continue;
93
+ const isNew = this.tracker.observe(entry.familyHash, nowMs, windowMs);
94
+ if (!isNew)
95
+ continue;
96
+ if (this.familyInCooldown(entry.familyHash, nowMs))
97
+ continue;
98
+ this.lastFiredFamily.set(entry.familyHash, nowMs);
99
+ const occurrences = occurrencesByFamily.get(entry.familyHash) ?? 1;
100
+ await this.dispatch(this.buildNewExceptionPayload(result.rule, entry, occurrences, nowMs));
101
+ }
102
+ }
103
+ async evaluateExceptionRate(nowMs) {
104
+ const result = this.findRule('exception-rate');
105
+ if (result === null)
106
+ return;
107
+ const { rule, index } = result;
108
+ const windowMs = durationToMs(rule.window);
109
+ // Drop timestamps that have aged out of the window.
110
+ const cutoff = nowMs - windowMs;
111
+ while (this.recentExceptions.length > 0 && this.recentExceptions[0] < cutoff) {
112
+ this.recentExceptions.shift();
113
+ }
114
+ const value = this.recentExceptions.length;
115
+ if (value < rule.threshold)
116
+ return;
117
+ if (this.inCooldown(index, nowMs))
118
+ return;
119
+ this.lastFiredAt.set(index, nowMs);
120
+ await this.dispatch(this.buildRatePayload(rule, value, rule.threshold, nowMs));
121
+ }
122
+ /** Find a configured rule of `type` with its array index, or `null`. */
123
+ findRule(type) {
124
+ for (let index = 0; index < this.deps.alerts.rules.length; index++) {
125
+ const rule = this.deps.alerts.rules[index];
126
+ if (rule !== undefined && rule.type === type) {
127
+ return { rule: rule, index };
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ inCooldown(index, nowMs) {
133
+ const last = this.lastFiredAt.get(index);
134
+ if (last === undefined)
135
+ return false;
136
+ return nowMs - last < this.deps.alerts.cooldownMs;
137
+ }
138
+ familyInCooldown(familyHash, nowMs) {
139
+ const last = this.lastFiredFamily.get(familyHash);
140
+ if (last === undefined)
141
+ return false;
142
+ return nowMs - last < this.deps.alerts.cooldownMs;
143
+ }
144
+ buildRatePayload(rule, value, threshold, nowMs) {
145
+ return {
146
+ rule,
147
+ value,
148
+ threshold,
149
+ firedAt: new Date(nowMs).toISOString(),
150
+ instanceId: this.deps.alerts.instanceId,
151
+ ...(this.deps.alerts.dashboardUrl !== null
152
+ ? { dashboardUrl: this.deps.alerts.dashboardUrl }
153
+ : {}),
154
+ };
155
+ }
156
+ buildNewExceptionPayload(rule, entry, occurrences, nowMs) {
157
+ return {
158
+ rule,
159
+ value: occurrences,
160
+ threshold: 1,
161
+ firedAt: new Date(nowMs).toISOString(),
162
+ instanceId: this.deps.alerts.instanceId,
163
+ exception: buildExceptionContext(entry, occurrences),
164
+ ...(this.deps.alerts.dashboardUrl !== null
165
+ ? { dashboardUrl: this.deps.alerts.dashboardUrl }
166
+ : {}),
167
+ };
168
+ }
169
+ /**
170
+ * Fan the payload out to every channel concurrently. Each channel's failure is
171
+ * isolated (`Promise.allSettled`) and warn-logged ONCE per channel name so a
172
+ * persistently-down destination doesn't flood the logs. Never rejects.
173
+ */
174
+ async dispatch(payload) {
175
+ const results = await Promise.allSettled(this.deps.alerts.channels.map((channel) => channel.send(payload)));
176
+ results.forEach((result, index) => {
177
+ if (result.status === 'rejected') {
178
+ this.warnChannelFailure(this.deps.alerts.channels[index], result.reason);
179
+ }
180
+ });
181
+ }
182
+ warnChannelFailure(channel, reason) {
183
+ if (channel === undefined)
184
+ return;
185
+ if (this.warnedChannels.has(channel.name))
186
+ return;
187
+ this.warnedChannels.add(channel.name);
188
+ this.logger(`Telescope alert channel '${channel.name}' failed: ${asMessage(reason)}`);
189
+ }
190
+ }
191
+ /** Build the rich exception context from an exception {@link Entry}. */
192
+ function buildExceptionContext(entry, occurrences) {
193
+ const content = asContentRecord(entry.content);
194
+ return {
195
+ familyHash: entry.familyHash ?? '',
196
+ class: pickString(content, ['class', 'name']) ?? 'Error',
197
+ message: pickString(content, ['message']) ?? '',
198
+ stack: clipStackFrames(pickString(content, ['stack']) ?? null),
199
+ route: pickString(content, ['route', 'uri', 'url']),
200
+ method: pickString(content, ['method']),
201
+ statusCode: pickNumber(content, ['statusCode', 'status']),
202
+ user: userFromTags(entry.tags),
203
+ occurrences,
204
+ entryId: entry.id,
205
+ };
206
+ }
207
+ function asContentRecord(content) {
208
+ return typeof content === 'object' && content !== null
209
+ ? content
210
+ : {};
211
+ }
212
+ /** First string-valued field among `keys`, or `null`. */
213
+ function pickString(record, keys) {
214
+ for (const key of keys) {
215
+ const value = record[key];
216
+ if (typeof value === 'string')
217
+ return value;
218
+ }
219
+ return null;
220
+ }
221
+ /** First number-valued field among `keys`, or `null`. */
222
+ function pickNumber(record, keys) {
223
+ for (const key of keys) {
224
+ const value = record[key];
225
+ if (typeof value === 'number')
226
+ return value;
227
+ }
228
+ return null;
229
+ }
230
+ /** Keep at most the first N stack frames; `null` passes through. */
231
+ function clipStackFrames(stack) {
232
+ if (stack === null)
233
+ return null;
234
+ return stack.split('\n').slice(0, ALERT_STACK_FRAME_LIMIT).join('\n');
235
+ }
236
+ /** Extract the user id from a `user:<id>` tag, or `null` when none is present. */
237
+ function userFromTags(tags) {
238
+ for (const tag of tags) {
239
+ if (tag.startsWith('user:')) {
240
+ return tag.slice('user:'.length);
241
+ }
242
+ }
243
+ return null;
244
+ }
245
+ function asMessage(error) {
246
+ return error instanceof Error ? error.message : String(error);
247
+ }
248
+ //# sourceMappingURL=alerter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerter.js","sourceRoot":"","sources":["../../../src/alerts/alerter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import { type AlertChannel } from './alert_channel.js';
2
+ import type { AlertRule, ResolvedAlerts } from './alert_rule.js';
3
+ import type { SlackChannelOptions } from './slack_format.js';
4
+ /**
5
+ * A declarative channel descriptor — the JSON-friendly form usable directly in
6
+ * `config/telescope_alerts.ts`. Resolved into a runtime {@link AlertChannel} by
7
+ * {@link resolveConfig}. For anything custom (email, PagerDuty, …), pass a
8
+ * pre-built `AlertChannel` object instead (the union below accepts both).
9
+ */
10
+ export type ChannelSpec = {
11
+ type: 'slack';
12
+ url: string;
13
+ options?: SlackChannelOptions;
14
+ } | {
15
+ type: 'webhook';
16
+ url: string;
17
+ } | {
18
+ type: 'console';
19
+ };
20
+ /** Either a declarative spec or a ready-made channel object. */
21
+ export type ChannelConfig = ChannelSpec | AlertChannel;
22
+ /**
23
+ * The shape of `config/telescope_alerts.ts`. Everything is optional with sane
24
+ * defaults: alerting is on, polls every 30s, fires on a brand-new exception family
25
+ * (re-firing only after a 15m cooldown), and logs to the console channel.
26
+ */
27
+ export interface TelescopeAlertsConfig {
28
+ /** Master switch. When `false`, no alerting runs. Default `true`. */
29
+ enabled?: boolean;
30
+ /**
31
+ * Delivery destinations. Each fired alert is sent to every channel concurrently;
32
+ * one channel failing never blocks the others. Accepts declarative specs
33
+ * (`{ type: 'slack', url }`) or pre-built `AlertChannel` objects. Default:
34
+ * a single `console` channel.
35
+ */
36
+ channels?: ChannelConfig[];
37
+ /**
38
+ * Rules to evaluate. Default: a single `new-exception` rule over a 1h window.
39
+ */
40
+ rules?: AlertRule[];
41
+ /**
42
+ * External Telescope dashboard URL (e.g. `https://telescope.example.com/`). When
43
+ * set, channels that support deep links (Slack) build a link to the offending
44
+ * entry. Optional.
45
+ */
46
+ dashboardUrl?: string;
47
+ /** Poll cadence for the exception source, as a duration string. Default `'30s'`. */
48
+ every?: string;
49
+ /** Re-notify suppression per rule/family, as a duration string. Default `'15m'`. */
50
+ cooldown?: string;
51
+ /** Reporting instance identifier carried on every payload. Default `'telescope'`. */
52
+ instanceId?: string;
53
+ }
54
+ /** The default rule set — page on a brand-new exception family within the hour. */
55
+ export declare const DEFAULT_RULES: AlertRule[];
56
+ /**
57
+ * Identity helper giving `config/telescope_alerts.ts` full type-checking, mirroring
58
+ * the AdonisJS `defineConfig` convention.
59
+ */
60
+ export declare function defineConfig(config: TelescopeAlertsConfig): TelescopeAlertsConfig;
61
+ /**
62
+ * Validate + normalize a (possibly partial) config into the {@link ResolvedAlerts}
63
+ * the {@link Alerter} acts on. Durations are normalized to ms (an unparseable one
64
+ * throws — fail closed at boot). With no channels configured a `console` channel
65
+ * is used so an alert is never silently dropped.
66
+ */
67
+ export declare function resolveConfig(config?: TelescopeAlertsConfig): ResolvedAlerts;
68
+ //# sourceMappingURL=define_config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define_config.d.ts","sourceRoot":"","sources":["../../../src/alerts/define_config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAIlB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAM7D;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,mBAAmB,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB,gEAAgE;AAChE,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B;;OAEG;IACH,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mFAAmF;AACnF,eAAO,MAAM,aAAa,EAAE,SAAS,EAA8C,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,qBAAqB,GAAG,qBAAqB,CAEjF;AAoBD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,qBAA0B,GAAG,cAAc,CAsBhF"}
@@ -0,0 +1,57 @@
1
+ import { consoleChannel, slackChannel, webhookChannel, } from './alert_channel.js';
2
+ import { durationToMs } from './parse_duration.js';
3
+ const DEFAULT_INTERVAL = '30s';
4
+ const DEFAULT_COOLDOWN = '15m';
5
+ const DEFAULT_INSTANCE_ID = 'telescope';
6
+ /** The default rule set — page on a brand-new exception family within the hour. */
7
+ export const DEFAULT_RULES = [{ type: 'new-exception', window: '1h' }];
8
+ /**
9
+ * Identity helper giving `config/telescope_alerts.ts` full type-checking, mirroring
10
+ * the AdonisJS `defineConfig` convention.
11
+ */
12
+ export function defineConfig(config) {
13
+ return config;
14
+ }
15
+ /** Narrow a {@link ChannelConfig} to a ready-made {@link AlertChannel}. */
16
+ function isChannel(config) {
17
+ return typeof config.send === 'function';
18
+ }
19
+ /** Resolve one declarative spec (or pass-through object) to an {@link AlertChannel}. */
20
+ function resolveChannel(config) {
21
+ if (isChannel(config))
22
+ return config;
23
+ switch (config.type) {
24
+ case 'slack':
25
+ return slackChannel(config.url, config.options);
26
+ case 'webhook':
27
+ return webhookChannel(config.url);
28
+ case 'console':
29
+ return consoleChannel();
30
+ }
31
+ }
32
+ /**
33
+ * Validate + normalize a (possibly partial) config into the {@link ResolvedAlerts}
34
+ * the {@link Alerter} acts on. Durations are normalized to ms (an unparseable one
35
+ * throws — fail closed at boot). With no channels configured a `console` channel
36
+ * is used so an alert is never silently dropped.
37
+ */
38
+ export function resolveConfig(config = {}) {
39
+ const channels = (config.channels ?? [{ type: 'console' }]).map(resolveChannel);
40
+ const rules = config.rules !== undefined && config.rules.length > 0 ? config.rules : DEFAULT_RULES;
41
+ // Validate every rule window at boot — a typo surfaces here, not silently never.
42
+ for (const rule of rules) {
43
+ durationToMs(rule.window);
44
+ }
45
+ return {
46
+ enabled: config.enabled ?? true,
47
+ channels,
48
+ dashboardUrl: typeof config.dashboardUrl === 'string' && config.dashboardUrl.trim() !== ''
49
+ ? config.dashboardUrl
50
+ : null,
51
+ intervalMs: durationToMs(config.every ?? DEFAULT_INTERVAL),
52
+ cooldownMs: durationToMs(config.cooldown ?? DEFAULT_COOLDOWN),
53
+ instanceId: config.instanceId ?? DEFAULT_INSTANCE_ID,
54
+ rules,
55
+ };
56
+ }
57
+ //# sourceMappingURL=define_config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define_config.js","sourceRoot":"","sources":["../../../src/alerts/define_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACd,YAAY,EACZ,cAAc,GACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAC/B,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAC/B,MAAM,mBAAmB,GAAG,WAAW,CAAC;AAiDxC,mFAAmF;AACnF,MAAM,CAAC,MAAM,aAAa,GAAgB,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAEpF;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAA6B;IACxD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2EAA2E;AAC3E,SAAS,SAAS,CAAC,MAAqB;IACtC,OAAO,OAAQ,MAAuB,CAAC,IAAI,KAAK,UAAU,CAAC;AAC7D,CAAC;AAED,wFAAwF;AACxF,SAAS,cAAc,CAAC,MAAqB;IAC3C,IAAI,SAAS,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACrC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO;YACV,OAAO,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAClD,KAAK,SAAS;YACZ,OAAO,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpC,KAAK,SAAS;YACZ,OAAO,cAAc,EAAE,CAAC;IAC5B,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,SAAgC,EAAE;IAC9D,MAAM,QAAQ,GAAmB,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChG,MAAM,KAAK,GACT,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC;IAEvF,iFAAiF;IACjF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,IAAI;QAC/B,QAAQ;QACR,YAAY,EACV,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE;YAC1E,CAAC,CAAC,MAAM,CAAC,YAAY;YACrB,CAAC,CAAC,IAAI;QACV,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,gBAAgB,CAAC;QAC1D,UAAU,EAAE,YAAY,CAAC,MAAM,CAAC,QAAQ,IAAI,gBAAgB,CAAC;QAC7D,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,mBAAmB;QACpD,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { TelescopeStore } from '../store.js';
2
+ import type { Alerter } from './alerter.js';
3
+ export interface ExceptionPollerDeps {
4
+ store: TelescopeStore;
5
+ alerter: Alerter;
6
+ /** Poll cadence in ms. */
7
+ intervalMs: number;
8
+ /** Wall-clock seam (ms). Defaults to `Date.now`. */
9
+ now?: () => number;
10
+ /** Failure log sink. Defaults to `console.warn`. */
11
+ logger?: (message: string) => void;
12
+ }
13
+ /**
14
+ * The hook point. Telescope's headless `@adonis-agora/telescope` core does not expose a
15
+ * "new entry" event, so — by design, to avoid modifying core — this poller reads
16
+ * the {@link TelescopeStore} on an interval for `exception` entries recorded since
17
+ * the previous poll (a high-water-mark on `createdAt`) and feeds them to the
18
+ * {@link Alerter}. Polling keeps the hook fully testable (drive {@link pollOnce}
19
+ * directly) and decoupled from any specific watcher.
20
+ *
21
+ * The unref'd timer means the poller never keeps the process alive on its own.
22
+ * Every poll is wrapped so a store failure is logged, not thrown.
23
+ */
24
+ export declare class ExceptionPoller {
25
+ private readonly deps;
26
+ private readonly now;
27
+ private readonly logger;
28
+ private timer;
29
+ /** High-water mark: only entries strictly newer than this are new to us. */
30
+ private since;
31
+ constructor(deps: ExceptionPollerDeps);
32
+ /** Start the unref'd poll interval. Idempotent. */
33
+ start(): void;
34
+ /** Stop the poll interval. Idempotent. */
35
+ stop(): void;
36
+ /**
37
+ * Read exception entries recorded since the last poll, advance the high-water
38
+ * mark, and hand them to the alerter. Exposed for deterministic tests. Never
39
+ * throws — a store failure is logged and the watermark is left untouched so the
40
+ * next poll retries the same window.
41
+ */
42
+ pollOnce(): Promise<void>;
43
+ }
44
+ //# sourceMappingURL=exception_source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception_source.d.ts","sourceRoot":"","sources":["../../../src/alerts/exception_source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAK5C,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,oDAAoD;IACpD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAOd,OAAO,CAAC,QAAQ,CAAC,IAAI;IANjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,KAAK,CAA+C;IAC5D,4EAA4E;IAC5E,OAAO,CAAC,KAAK,CAAO;gBAES,IAAI,EAAE,mBAAmB;IAOtD,mDAAmD;IACnD,KAAK,IAAI,IAAI;IAUb,0CAA0C;IAC1C,IAAI,IAAI,IAAI;IAOZ;;;;;OAKG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAsBhC"}
@@ -0,0 +1,79 @@
1
+ import { EntryType } from '../entry.js';
2
+ /** Cap on entries pulled per poll — a coarse alert check never needs more. */
3
+ const POLL_SCAN_CAP = 1_000;
4
+ /**
5
+ * The hook point. Telescope's headless `@adonis-agora/telescope` core does not expose a
6
+ * "new entry" event, so — by design, to avoid modifying core — this poller reads
7
+ * the {@link TelescopeStore} on an interval for `exception` entries recorded since
8
+ * the previous poll (a high-water-mark on `createdAt`) and feeds them to the
9
+ * {@link Alerter}. Polling keeps the hook fully testable (drive {@link pollOnce}
10
+ * directly) and decoupled from any specific watcher.
11
+ *
12
+ * The unref'd timer means the poller never keeps the process alive on its own.
13
+ * Every poll is wrapped so a store failure is logged, not thrown.
14
+ */
15
+ export class ExceptionPoller {
16
+ deps;
17
+ now;
18
+ logger;
19
+ timer = null;
20
+ /** High-water mark: only entries strictly newer than this are new to us. */
21
+ since;
22
+ constructor(deps) {
23
+ this.deps = deps;
24
+ this.now = deps.now ?? Date.now;
25
+ this.logger = deps.logger ?? ((message) => console.warn(message));
26
+ // Start from "now" so we never alert on the entire pre-existing backlog at boot.
27
+ this.since = new Date(this.now());
28
+ }
29
+ /** Start the unref'd poll interval. Idempotent. */
30
+ start() {
31
+ if (this.timer !== null)
32
+ return;
33
+ this.timer = setInterval(() => {
34
+ this.pollOnce().catch((error) => {
35
+ this.logger(`Telescope alert poll failed: ${asMessage(error)}`);
36
+ });
37
+ }, this.deps.intervalMs);
38
+ this.timer.unref?.();
39
+ }
40
+ /** Stop the poll interval. Idempotent. */
41
+ stop() {
42
+ if (this.timer !== null) {
43
+ clearInterval(this.timer);
44
+ this.timer = null;
45
+ }
46
+ }
47
+ /**
48
+ * Read exception entries recorded since the last poll, advance the high-water
49
+ * mark, and hand them to the alerter. Exposed for deterministic tests. Never
50
+ * throws — a store failure is logged and the watermark is left untouched so the
51
+ * next poll retries the same window.
52
+ */
53
+ async pollOnce() {
54
+ let entries;
55
+ try {
56
+ entries = await this.deps.store.list({
57
+ type: EntryType.Exception,
58
+ after: this.since,
59
+ limit: POLL_SCAN_CAP,
60
+ });
61
+ }
62
+ catch (error) {
63
+ this.logger(`Telescope alert poll failed: ${asMessage(error)}`);
64
+ return;
65
+ }
66
+ if (entries.length > 0) {
67
+ // `list` returns newest-first; advance the mark to the newest seen.
68
+ const newest = entries[0];
69
+ if (newest !== undefined)
70
+ this.since = newest.createdAt;
71
+ }
72
+ // Feed oldest-first so occurrence ordering / first-seen detection is natural.
73
+ await this.deps.alerter.evaluate([...entries].reverse());
74
+ }
75
+ }
76
+ function asMessage(error) {
77
+ return error instanceof Error ? error.message : String(error);
78
+ }
79
+ //# sourceMappingURL=exception_source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exception_source.js","sourceRoot":"","sources":["../../../src/alerts/exception_source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,SAAS,EAAE,MAAM,aAAa,CAAC;AAIpD,8EAA8E;AAC9E,MAAM,aAAa,GAAG,KAAK,CAAC;AAa5B;;;;;;;;;;GAUG;AACH,MAAM,OAAO,eAAe;IAOG;IANZ,GAAG,CAAe;IAClB,MAAM,CAA4B;IAC3C,KAAK,GAA0C,IAAI,CAAC;IAC5D,4EAA4E;IACpE,KAAK,CAAO;IAEpB,YAA6B,IAAyB;QAAzB,SAAI,GAAJ,IAAI,CAAqB;QACpD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;QAClE,iFAAiF;QACjF,IAAI,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,mDAAmD;IACnD,KAAK;QACH,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;YAAE,OAAO;QAChC,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;gBACvC,IAAI,CAAC,MAAM,CAAC,gCAAgC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClE,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACvB,CAAC;IAED,0CAA0C;IAC1C,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ;QACZ,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBACnC,IAAI,EAAE,SAAS,CAAC,SAAS;gBACzB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,KAAK,EAAE,aAAa;aACrB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,gCAAgC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,oEAAoE;YACpE,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC;QAC1D,CAAC;QAED,8EAA8E;QAC9E,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3D,CAAC;CACF;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,16 @@
1
+ /** Keep in sync with this package's `version` in package.json. */
2
+ export declare const VERSION = "0.1.0";
3
+ export { consoleChannel, customChannel, slackChannel, webhookChannel, } from './alert_channel.js';
4
+ export type { AlertChannel, ChannelFetch, ConsoleSink } from './alert_channel.js';
5
+ export { formatSlackMessage } from './slack_format.js';
6
+ export type { SlackChannelOptions, SlackMessage } from './slack_format.js';
7
+ export type { AlertPayload, AlertRule, ExceptionAlertContext, ResolvedAlerts, } from './alert_rule.js';
8
+ export { DEFAULT_MAX_FAMILIES, NewExceptionTracker } from './new_exception_tracker.js';
9
+ export { Alerter } from './alerter.js';
10
+ export type { AlerterDeps } from './alerter.js';
11
+ export { ExceptionPoller } from './exception_source.js';
12
+ export type { ExceptionPollerDeps } from './exception_source.js';
13
+ export { durationToMs } from './parse_duration.js';
14
+ export { DEFAULT_RULES, defineConfig, resolveConfig } from './define_config.js';
15
+ export type { ChannelConfig, ChannelSpec, TelescopeAlertsConfig, } from './define_config.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/alerts/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,eAAO,MAAM,OAAO,UAAU,CAAC;AAG/B,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGlF,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG3E,YAAY,EACV,YAAY,EACZ,SAAS,EACT,qBAAqB,EACrB,cAAc,GACf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGvF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAGjE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAChF,YAAY,EACV,aAAa,EACb,WAAW,EACX,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,17 @@
1
+ /** Keep in sync with this package's `version` in package.json. */
2
+ export const VERSION = '0.1.0';
3
+ // — channels —
4
+ export { consoleChannel, customChannel, slackChannel, webhookChannel, } from './alert_channel.js';
5
+ // — slack formatting —
6
+ export { formatSlackMessage } from './slack_format.js';
7
+ // — new-exception dedupe —
8
+ export { DEFAULT_MAX_FAMILIES, NewExceptionTracker } from './new_exception_tracker.js';
9
+ // — alerter (source → tracker → channels) —
10
+ export { Alerter } from './alerter.js';
11
+ // — exception source (the polling hook) —
12
+ export { ExceptionPoller } from './exception_source.js';
13
+ // — duration helper —
14
+ export { durationToMs } from './parse_duration.js';
15
+ // — config —
16
+ export { DEFAULT_RULES, defineConfig, resolveConfig } from './define_config.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/alerts/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,eAAe;AACf,OAAO,EACL,cAAc,EACd,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,oBAAoB,CAAC;AAG5B,uBAAuB;AACvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAWvD,2BAA2B;AAC3B,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEvF,4CAA4C;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,0CAA0C;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,sBAAsB;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,aAAa;AACb,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Default cap on tracked error families. The map is the ONLY state the
3
+ * `new-exception` rule keeps, and it must stay bounded so a host that throws a
4
+ * long tail of unique error families (e.g. message-embedded ids that defeat the
5
+ * family hash) can never grow it without limit. At the cap we evict the OLDEST
6
+ * inserted family — the one least likely to still be "recently seen" — which is
7
+ * the correct bias for a recency-window dedup.
8
+ */
9
+ declare const DEFAULT_MAX_FAMILIES = 10000;
10
+ /**
11
+ * Bounded, in-memory "have we seen this error family recently?" tracker backing
12
+ * the `new-exception` rule. Per-process by design: it lives in process memory, so
13
+ * in a multi-replica deployment the SAME family may be reported as new once per
14
+ * pod (documented caveat — acceptable for v1).
15
+ *
16
+ * Eviction uses a `Map`'s insertion-order iteration: the first key is the oldest
17
+ * INSERTED entry. The window check already discards stale last-seen times, so
18
+ * insertion-order eviction is a sound, O(1) cap with no per-touch bookkeeping.
19
+ *
20
+ * Ported verbatim from the aviary telescope core.
21
+ */
22
+ export declare class NewExceptionTracker {
23
+ private readonly seen;
24
+ private readonly maxFamilies;
25
+ constructor(maxFamilies?: number);
26
+ /**
27
+ * Record an observation of `familyHash` at `nowMs` and report whether this is a
28
+ * NEW family for the `windowMs` window — i.e. it was either never seen or last
29
+ * seen longer ago than the window (the "re-occurring after being resolved"
30
+ * signal). Always updates the stored last-seen time so repeat occurrences
31
+ * refresh the window. Returns `true` only on a genuine first/expired occurrence
32
+ * (the signal that should fire the alert).
33
+ */
34
+ observe(familyHash: string, nowMs: number, windowMs: number): boolean;
35
+ /**
36
+ * Forget `familyHash` entirely, so its NEXT occurrence is reported as new again.
37
+ * This is the explicit "resolve" hook: after an operator marks a family resolved
38
+ * (or a self-heal job clears it), the very next occurrence pages again without
39
+ * waiting for the window to elapse.
40
+ */
41
+ resolve(familyHash: string): void;
42
+ /** Forget every tracked family. */
43
+ clear(): void;
44
+ /** Number of tracked families (test/observability seam). */
45
+ get size(): number;
46
+ /** Evict the oldest entries until the map is within its cap. */
47
+ private evictIfNeeded;
48
+ }
49
+ export { DEFAULT_MAX_FAMILIES };
50
+ //# sourceMappingURL=new_exception_tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new_exception_tracker.d.ts","sourceRoot":"","sources":["../../../src/alerts/new_exception_tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,QAAA,MAAM,oBAAoB,QAAS,CAAC;AAEpC;;;;;;;;;;;GAWG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,WAAW,GAAE,MAA6B;IAItD;;;;;;;OAOG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAWrE;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIjC,mCAAmC;IACnC,KAAK,IAAI,IAAI;IAIb,4DAA4D;IAC5D,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,gEAAgE;IAChE,OAAO,CAAC,aAAa;CAOtB;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}