@smthrs/notifications 0.0.0-stage → 1.0.0-rc.12

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 (146) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/LICENSE +21 -0
  3. package/README.md +93 -2
  4. package/dist/cjs/AlertPolicy.d.ts +284 -0
  5. package/dist/cjs/AlertPolicy.d.ts.map +1 -0
  6. package/dist/cjs/AlertPolicy.js +124 -0
  7. package/dist/cjs/AlertPolicy.js.map +7 -0
  8. package/dist/cjs/AlertRuntime.d.ts +90 -0
  9. package/dist/cjs/AlertRuntime.d.ts.map +1 -0
  10. package/dist/cjs/AlertRuntime.js +166 -0
  11. package/dist/cjs/AlertRuntime.js.map +7 -0
  12. package/dist/cjs/AlertSink.d.ts +101 -0
  13. package/dist/cjs/AlertSink.d.ts.map +1 -0
  14. package/dist/cjs/AlertSink.js +44 -0
  15. package/dist/cjs/AlertSink.js.map +7 -0
  16. package/dist/cjs/Alerts.d.ts +57 -0
  17. package/dist/cjs/Alerts.d.ts.map +1 -0
  18. package/dist/cjs/Alerts.js +46 -0
  19. package/dist/cjs/Alerts.js.map +7 -0
  20. package/dist/cjs/Notification.d.ts +214 -0
  21. package/dist/cjs/Notification.d.ts.map +1 -0
  22. package/dist/cjs/Notification.js +62 -0
  23. package/dist/cjs/Notification.js.map +7 -0
  24. package/dist/cjs/NotificationEvent.d.ts +214 -0
  25. package/dist/cjs/NotificationEvent.d.ts.map +1 -0
  26. package/dist/cjs/NotificationEvent.js +84 -0
  27. package/dist/cjs/NotificationEvent.js.map +7 -0
  28. package/dist/cjs/NotificationQueue.d.ts +194 -0
  29. package/dist/cjs/NotificationQueue.d.ts.map +1 -0
  30. package/dist/cjs/NotificationQueue.js +477 -0
  31. package/dist/cjs/NotificationQueue.js.map +7 -0
  32. package/dist/cjs/NotificationState.d.ts +169 -0
  33. package/dist/cjs/NotificationState.d.ts.map +1 -0
  34. package/dist/cjs/NotificationState.js +110 -0
  35. package/dist/cjs/NotificationState.js.map +7 -0
  36. package/dist/cjs/Projection.d.ts +30 -0
  37. package/dist/cjs/Projection.d.ts.map +1 -0
  38. package/dist/cjs/Projection.js +53 -0
  39. package/dist/cjs/Projection.js.map +7 -0
  40. package/dist/cjs/SteerPayload.d.ts +177 -0
  41. package/dist/cjs/SteerPayload.d.ts.map +1 -0
  42. package/dist/cjs/SteerPayload.js +61 -0
  43. package/dist/cjs/SteerPayload.js.map +7 -0
  44. package/dist/cjs/index.d.ts +41 -0
  45. package/dist/cjs/index.d.ts.map +1 -0
  46. package/dist/cjs/index.js +47 -0
  47. package/dist/cjs/index.js.map +7 -0
  48. package/dist/cjs/internal/foldCache.d.ts +36 -0
  49. package/dist/cjs/internal/foldCache.d.ts.map +1 -0
  50. package/dist/cjs/internal/foldCache.js +54 -0
  51. package/dist/cjs/internal/foldCache.js.map +7 -0
  52. package/dist/cjs/internal/replay.d.ts +22 -0
  53. package/dist/cjs/internal/replay.d.ts.map +1 -0
  54. package/dist/cjs/internal/replay.js +46 -0
  55. package/dist/cjs/internal/replay.js.map +7 -0
  56. package/dist/cjs/layerWebhook.d.ts +60 -0
  57. package/dist/cjs/layerWebhook.d.ts.map +1 -0
  58. package/dist/cjs/layerWebhook.js +145 -0
  59. package/dist/cjs/layerWebhook.js.map +7 -0
  60. package/dist/cjs/package.json +1 -0
  61. package/dist/esm/AlertPolicy.d.ts +284 -0
  62. package/dist/esm/AlertPolicy.d.ts.map +1 -0
  63. package/dist/esm/AlertPolicy.js +284 -0
  64. package/dist/esm/AlertPolicy.js.map +1 -0
  65. package/dist/esm/AlertRuntime.d.ts +90 -0
  66. package/dist/esm/AlertRuntime.d.ts.map +1 -0
  67. package/dist/esm/AlertRuntime.js +191 -0
  68. package/dist/esm/AlertRuntime.js.map +1 -0
  69. package/dist/esm/AlertSink.d.ts +101 -0
  70. package/dist/esm/AlertSink.d.ts.map +1 -0
  71. package/dist/esm/AlertSink.js +63 -0
  72. package/dist/esm/AlertSink.js.map +1 -0
  73. package/dist/esm/Alerts.d.ts +57 -0
  74. package/dist/esm/Alerts.d.ts.map +1 -0
  75. package/dist/esm/Alerts.js +57 -0
  76. package/dist/esm/Alerts.js.map +1 -0
  77. package/dist/esm/Notification.d.ts +214 -0
  78. package/dist/esm/Notification.d.ts.map +1 -0
  79. package/dist/esm/Notification.js +121 -0
  80. package/dist/esm/Notification.js.map +1 -0
  81. package/dist/esm/NotificationEvent.d.ts +214 -0
  82. package/dist/esm/NotificationEvent.d.ts.map +1 -0
  83. package/dist/esm/NotificationEvent.js +158 -0
  84. package/dist/esm/NotificationEvent.js.map +1 -0
  85. package/dist/esm/NotificationQueue.d.ts +194 -0
  86. package/dist/esm/NotificationQueue.d.ts.map +1 -0
  87. package/dist/esm/NotificationQueue.js +625 -0
  88. package/dist/esm/NotificationQueue.js.map +1 -0
  89. package/dist/esm/NotificationState.d.ts +169 -0
  90. package/dist/esm/NotificationState.d.ts.map +1 -0
  91. package/dist/esm/NotificationState.js +191 -0
  92. package/dist/esm/NotificationState.js.map +1 -0
  93. package/dist/esm/Projection.d.ts +30 -0
  94. package/dist/esm/Projection.d.ts.map +1 -0
  95. package/dist/esm/Projection.js +47 -0
  96. package/dist/esm/Projection.js.map +1 -0
  97. package/dist/esm/SteerPayload.d.ts +177 -0
  98. package/dist/esm/SteerPayload.d.ts.map +1 -0
  99. package/dist/esm/SteerPayload.js +139 -0
  100. package/dist/esm/SteerPayload.js.map +1 -0
  101. package/dist/esm/index.d.ts +41 -0
  102. package/dist/esm/index.d.ts.map +1 -0
  103. package/dist/esm/index.js +41 -0
  104. package/dist/esm/index.js.map +1 -0
  105. package/dist/esm/internal/foldCache.d.ts +36 -0
  106. package/dist/esm/internal/foldCache.d.ts.map +1 -0
  107. package/dist/esm/internal/foldCache.js +58 -0
  108. package/dist/esm/internal/foldCache.js.map +1 -0
  109. package/dist/esm/internal/replay.d.ts +22 -0
  110. package/dist/esm/internal/replay.d.ts.map +1 -0
  111. package/dist/esm/internal/replay.js +31 -0
  112. package/dist/esm/internal/replay.js.map +1 -0
  113. package/dist/esm/layerWebhook.d.ts +60 -0
  114. package/dist/esm/layerWebhook.d.ts.map +1 -0
  115. package/dist/esm/layerWebhook.js +186 -0
  116. package/dist/esm/layerWebhook.js.map +1 -0
  117. package/docs/README.md +146 -0
  118. package/docs/api.md +443 -0
  119. package/docs/concepts/admission-and-promotion.md +144 -0
  120. package/docs/concepts/alerting.md +138 -0
  121. package/docs/concepts/journal-records.md +132 -0
  122. package/docs/guides/alert-on-a-stuck-run.md +140 -0
  123. package/docs/guides/drain-at-a-turn-boundary.md +95 -0
  124. package/docs/guides/handle-a-full-queue.md +102 -0
  125. package/docs/guides/report-pending-notifications.md +92 -0
  126. package/docs/guides/send-alerts-to-a-webhook.md +155 -0
  127. package/docs/guides/steer-a-run.md +127 -0
  128. package/docs/guides/testing.md +161 -0
  129. package/docs/installation.md +123 -0
  130. package/docs/quickstart.md +159 -0
  131. package/docs/troubleshooting.md +254 -0
  132. package/package.json +182 -3
  133. package/src/AlertPolicy.ts +377 -0
  134. package/src/AlertRuntime.ts +280 -0
  135. package/src/AlertSink.ts +105 -0
  136. package/src/Alerts.ts +71 -0
  137. package/src/Notification.ts +171 -0
  138. package/src/NotificationEvent.ts +213 -0
  139. package/src/NotificationQueue.ts +856 -0
  140. package/src/NotificationState.ts +295 -0
  141. package/src/Projection.ts +48 -0
  142. package/src/SteerPayload.ts +198 -0
  143. package/src/index.ts +47 -0
  144. package/src/internal/foldCache.ts +72 -0
  145. package/src/internal/replay.ts +39 -0
  146. package/src/layerWebhook.ts +239 -0
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var layerWebhook_exports = {};
30
+ __export(layerWebhook_exports, {
31
+ defaultWebhookTimeout: () => defaultWebhookTimeout,
32
+ layerWebhook: () => layerWebhook
33
+ });
34
+ module.exports = __toCommonJS(layerWebhook_exports);
35
+ var import_effect = require("effect");
36
+ var FetchHttpClient = __toESM(require("effect/http/FetchHttpClient"), 1);
37
+ var Headers = __toESM(require("effect/http/Headers"), 1);
38
+ var HttpClient = __toESM(require("effect/http/HttpClient"), 1);
39
+ var HttpClientRequest = __toESM(require("effect/http/HttpClientRequest"), 1);
40
+ var import_AlertPolicy = require("./AlertPolicy.js");
41
+ var import_AlertSink = require("./AlertSink.js");
42
+ const defaultWebhookTimeout = import_effect.Duration.seconds(10);
43
+ const webhookUrl = (raw) => import_effect.Effect.suspend(() => {
44
+ const misconfigured = new import_AlertSink.AlertError({
45
+ code: "sink_misconfigured",
46
+ message: "Alert webhook url must be an absolute http: or https: URL"
47
+ });
48
+ let parsed;
49
+ try {
50
+ parsed = new URL(raw);
51
+ } catch {
52
+ return import_effect.Effect.fail(misconfigured);
53
+ }
54
+ return parsed.protocol === "http:" || parsed.protocol === "https:" ? import_effect.Effect.succeed(raw) : import_effect.Effect.fail(misconfigured);
55
+ });
56
+ const followingNoRedirects = (effect) => import_effect.Effect.flatMap(
57
+ import_effect.Effect.serviceOption(FetchHttpClient.RequestInit),
58
+ (init) => import_effect.Effect.provideService(effect, FetchHttpClient.RequestInit, {
59
+ ...import_effect.Option.isSome(init) ? init.value : {},
60
+ redirect: "manual"
61
+ })
62
+ );
63
+ const rejected = (status) => new import_AlertSink.AlertError({ code: "sink_rejected", status, message: `Alert webhook answered ${status}` });
64
+ const originAttributes = /* @__PURE__ */ new Set([
65
+ "http.request.method",
66
+ "url.scheme",
67
+ "server.address",
68
+ "server.port",
69
+ "http.response.status_code",
70
+ "error.type"
71
+ ]);
72
+ const isHeaderAttribute = (key) => key.startsWith("http.request.header.") || key.startsWith("http.response.header.");
73
+ const redirectHeaderNames = ["location", "content-location"];
74
+ const withoutRequest = (tracer) => import_effect.Tracer.make({
75
+ ...tracer,
76
+ span(options) {
77
+ const span = tracer.span(options);
78
+ const end = span.end.bind(span);
79
+ const attribute = span.attribute.bind(span);
80
+ span.attribute = (key, value) => {
81
+ if (originAttributes.has(key) || isHeaderAttribute(key)) attribute(key, value);
82
+ };
83
+ span.end = (endTime, exit) => {
84
+ if (exit._tag === "Success") return end(endTime, import_effect.Exit.void);
85
+ if (import_effect.Cause.hasInterruptsOnly(exit.cause)) return end(endTime, exit);
86
+ const status = span.attributes.get("http.response.status_code");
87
+ return end(
88
+ endTime,
89
+ import_effect.Exit.fail(
90
+ typeof status === "number" ? rejected(status) : new import_AlertSink.AlertError({ code: "sink_unreachable", message: "Alert webhook could not be reached" })
91
+ )
92
+ );
93
+ };
94
+ return span;
95
+ }
96
+ });
97
+ const layerWebhook = (options) => import_effect.Layer.effect(
98
+ import_AlertSink.Sink,
99
+ import_effect.Effect.gen(function* () {
100
+ const url = yield* import_effect.Effect.orDie(webhookUrl(options.url));
101
+ const client = HttpClient.withScope(yield* HttpClient.HttpClient);
102
+ const timeout = options.timeout ?? defaultWebhookTimeout;
103
+ const redactedNames = [
104
+ ...Object.keys(options.headers ?? {}).map((name) => name.toLowerCase()),
105
+ ...redirectHeaderNames
106
+ ];
107
+ return {
108
+ deliver: (alert) => client.execute(
109
+ HttpClientRequest.post(url).pipe(
110
+ (request) => options.headers === void 0 ? request : HttpClientRequest.setHeaders(request, options.headers),
111
+ HttpClientRequest.setHeader("Idempotency-Key", (0, import_AlertPolicy.alertId)(alert)),
112
+ HttpClientRequest.bodyJsonUnsafe({ ...alert, alertId: (0, import_AlertPolicy.alertId)(alert) })
113
+ )
114
+ ).pipe(
115
+ followingNoRedirects,
116
+ import_effect.Effect.updateService(Headers.CurrentRedactedNames, (names) => [...names, ...redactedNames]),
117
+ // A host may name HTTP spans for their url. This one is named for
118
+ // its method, as the client names it by default.
119
+ import_effect.Effect.provideService(HttpClient.SpanNameGenerator, (request) => request.method),
120
+ import_effect.Effect.updateService(import_effect.Tracer.Tracer, withoutRequest),
121
+ import_effect.Effect.flatMap(
122
+ (response) => response.status >= 200 && response.status < 300 ? import_effect.Effect.void : import_effect.Effect.fail(rejected(response.status))
123
+ ),
124
+ import_effect.Effect.timeout(timeout),
125
+ import_effect.Effect.scoped,
126
+ import_effect.Effect.catchTag("TimeoutError", () => import_effect.Effect.fail(
127
+ new import_AlertSink.AlertError({
128
+ code: "sink_timeout",
129
+ message: `Alert webhook did not answer within ${import_effect.Duration.toMillis(timeout)} ms`
130
+ })
131
+ )),
132
+ // The transport error holds the request, and the request holds the
133
+ // caller's headers. Only its reason tag crosses into the failure.
134
+ import_effect.Effect.catchTag("HttpClientError", (cause) => import_effect.Effect.fail(
135
+ new import_AlertSink.AlertError({
136
+ code: "sink_unreachable",
137
+ reason: cause.reason._tag,
138
+ message: "Alert webhook could not be reached"
139
+ })
140
+ ))
141
+ )
142
+ };
143
+ })
144
+ ).pipe(import_effect.Layer.provide(FetchHttpClient.layer));
145
+ //# sourceMappingURL=layerWebhook.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/layerWebhook.ts"],
4
+ "sourcesContent": ["/**\n * The webhook alert sink: one POST per alert, keyed so the receiver can\n * dedupe on it.\n *\n * The transport rules live here rather than beside the policy, because which\n * endpoint is paged, over which scheme, carrying which credential is a\n * deployment's decision and the delays are not.\n *\n * @since 1.0.0\n */\n\nimport { Cause, Duration, Effect, Exit, Layer, Option, Tracer } from \"effect\"\nimport * as FetchHttpClient from \"effect/http/FetchHttpClient\"\nimport * as Headers from \"effect/http/Headers\"\nimport * as HttpClient from \"effect/http/HttpClient\"\nimport * as HttpClientRequest from \"effect/http/HttpClientRequest\"\nimport { alertId } from \"./AlertPolicy.ts\"\nimport { AlertError, Sink } from \"./AlertSink.ts\"\n\n/**\n * How long the webhook sink waits for an answer before it calls the page\n * refused.\n *\n * @category constants\n * @since 1.0.0\n */\nexport const defaultWebhookTimeout: Duration.Duration = Duration.seconds(10)\n\n/**\n * The one check the webhook endpoint gets before an alert is ever built on\n * it.\n *\n * The url is operator config rather than runtime input, so there is no SSRF\n * vector to close here; the scheme bound is what keeps a credential the\n * deployment set in `headers` from being handed to a scheme that is not HTTP\n * at all. A `file:` or `javascript:` url is a config bug, and it fails the\n * composition the way an impossible policy rule does. The error names no\n * url: the value can carry basic-auth credentials, and an error is logged.\n */\nconst webhookUrl = (raw: string): Effect.Effect<string, AlertError> =>\n Effect.suspend(() => {\n const misconfigured = new AlertError({\n code: \"sink_misconfigured\",\n message: \"Alert webhook url must be an absolute http: or https: URL\"\n })\n let parsed: URL\n try {\n parsed = new URL(raw)\n } catch {\n return Effect.fail(misconfigured)\n }\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\"\n ? Effect.succeed(raw)\n : Effect.fail(misconfigured)\n })\n\n/**\n * Runs a webhook request with redirect following switched off at the fetch\n * transport.\n *\n * The request carries whatever credential the deployment put in `headers`,\n * and a transport that follows a 3xx re-sends those headers to an origin the\n * deployment never named. Every other fetch default the composition set is\n * kept; only the redirect mode is forced. A 3xx therefore surfaces to the\n * status rule, which calls it `sink_rejected`: a redirecting pager endpoint\n * is a misconfiguration to fix, not an address to forward credentials to.\n */\nconst followingNoRedirects = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Effect.flatMap(\n Effect.serviceOption(FetchHttpClient.RequestInit),\n (init) =>\n Effect.provideService(effect, FetchHttpClient.RequestInit, {\n ...(Option.isSome(init) ? init.value : {}),\n redirect: \"manual\"\n })\n )\n\nconst rejected = (status: number): AlertError =>\n new AlertError({ code: \"sink_rejected\", status, message: `Alert webhook answered ${status}` })\n\n/**\n * The span attributes of a delivery that say nothing of the url's path, query\n * or userinfo: the method, the origin and the answer.\n */\nconst originAttributes: ReadonlySet<string> = new Set([\n \"http.request.method\",\n \"url.scheme\",\n \"server.address\",\n \"server.port\",\n \"http.response.status_code\",\n \"error.type\"\n])\n\nconst isHeaderAttribute = (key: string): boolean =>\n key.startsWith(\"http.request.header.\") || key.startsWith(\"http.response.header.\")\n\n/**\n * The headers of a redirect that name where the endpoint moved, which is the\n * same secret under another address.\n */\nconst redirectHeaderNames: ReadonlyArray<string> = [\"location\", \"content-location\"]\n\n/**\n * The tracer a delivery opens its HTTP span on: the caller's own, with nothing\n * of the request left on the span.\n *\n * A webhook url is a credential. Slack, Discord and PagerDuty put the secret\n * in the path, others take it in the query, and a url may carry basic-auth\n * userinfo. The HTTP client writes the whole url on its span and again in the\n * failure it ends the span with, and the answer or the transport error it ends\n * with holds the request. So only the attributes that name the method, the\n * origin and the answer are kept, with the headers a host chose to record, and\n * the span ends with the outcome alone: nothing on success, the refusing\n * status, or that the endpoint could not be reached.\n */\nconst withoutRequest = (tracer: Tracer.Tracer): Tracer.Tracer =>\n Tracer.make({\n ...tracer,\n span(options) {\n const span = tracer.span(options)\n const end = span.end.bind(span)\n const attribute = span.attribute.bind(span)\n span.attribute = (key, value) => {\n if (originAttributes.has(key) || isHeaderAttribute(key)) attribute(key, value)\n }\n span.end = (endTime, exit) => {\n if (exit._tag === \"Success\") return end(endTime, Exit.void)\n if (Cause.hasInterruptsOnly(exit.cause)) return end(endTime, exit)\n const status = span.attributes.get(\"http.response.status_code\")\n return end(\n endTime,\n Exit.fail(\n typeof status === \"number\"\n ? rejected(status)\n : new AlertError({ code: \"sink_unreachable\", message: \"Alert webhook could not be reached\" })\n )\n )\n }\n return span\n }\n })\n\n/**\n * A sink that POSTs each alert to one webhook.\n *\n * The body is the alert plus its {@link alertId}, and the same id is sent as\n * an `Idempotency-Key` header, because the package requires the receiver to\n * dedupe on it and a body without it cannot. The header is set after the\n * caller's headers, so it is the one this sink sends.\n *\n * The endpoint must be an absolute `http:` or `https:` URL, checked when the\n * layer is built, and the request is made with redirect following disabled:\n * the caller's headers can carry a credential, and a followed redirect would\n * re-send them to an origin the deployment never named. A 3xx answer is a\n * refusal like any other non-2xx, not a forwarding instruction. The sink owns\n * its Fetch transport; an externally supplied `HttpClient` is not used.\n * Each delivery has its own scope, which aborts the request and releases any\n * unread response body on completion, failure, timeout, or interruption.\n *\n * The url is treated as a credential, because a webhook's secret usually is\n * its path or query. The delivery's HTTP span is named for the method and\n * records the scheme, host, port and answering status, never the path, the\n * query or userinfo, and it fails with the same url-free reason the sink\n * returns. A host that records HTTP headers on spans gets every name in\n * `headers`, and a redirect's `location`, as `<redacted>`.\n *\n * A non-2xx response is a failure, not a delivery, and an endpoint that never\n * answers is a failure after `timeout`. Paging is exactly the case where a\n * silently dropped request is worse than a retry, and a hung request is worse\n * than either: it is indistinguishable from silence, so it must not be\n * possible to wait on one forever.\n *\n * @param options the endpoint, any headers it needs, and how long to wait\n * @category layers\n * @since 0.1.0\n */\nexport const layerWebhook = (\n options: {\n readonly url: string\n readonly headers?: Readonly<Record<string, string>> | undefined\n readonly timeout?: Duration.Duration | undefined\n }\n): Layer.Layer<Sink> =>\n Layer.effect(\n Sink,\n Effect.gen(function*() {\n const url = yield* Effect.orDie(webhookUrl(options.url))\n const client = HttpClient.withScope(yield* HttpClient.HttpClient)\n const timeout = options.timeout ?? defaultWebhookTimeout\n // `Headers.isRedactedName` compares a string matcher against an\n // already-lowercased header name without lowercasing the matcher.\n const redactedNames = [\n ...Object.keys(options.headers ?? {}).map((name) => name.toLowerCase()),\n ...redirectHeaderNames\n ]\n return {\n deliver: (alert) =>\n client.execute(\n HttpClientRequest.post(url).pipe(\n (request) =>\n options.headers === undefined ? request : HttpClientRequest.setHeaders(request, options.headers),\n HttpClientRequest.setHeader(\"Idempotency-Key\", alertId(alert)),\n HttpClientRequest.bodyJsonUnsafe({ ...alert, alertId: alertId(alert) })\n )\n ).pipe(\n followingNoRedirects,\n Effect.updateService(Headers.CurrentRedactedNames, (names) => [...names, ...redactedNames]),\n // A host may name HTTP spans for their url. This one is named for\n // its method, as the client names it by default.\n Effect.provideService(HttpClient.SpanNameGenerator, (request) => request.method),\n Effect.updateService(Tracer.Tracer, withoutRequest),\n Effect.flatMap((response) =>\n response.status >= 200 && response.status < 300\n ? Effect.void\n : Effect.fail(rejected(response.status))\n ),\n Effect.timeout(timeout),\n Effect.scoped,\n Effect.catchTag(\"TimeoutError\", () =>\n Effect.fail(\n new AlertError({\n code: \"sink_timeout\",\n message: `Alert webhook did not answer within ${Duration.toMillis(timeout)} ms`\n })\n )),\n // The transport error holds the request, and the request holds the\n // caller's headers. Only its reason tag crosses into the failure.\n Effect.catchTag(\"HttpClientError\", (cause) =>\n Effect.fail(\n new AlertError({\n code: \"sink_unreachable\",\n reason: cause.reason._tag,\n message: \"Alert webhook could not be reached\"\n })\n ))\n )\n }\n })\n ).pipe(Layer.provide(FetchHttpClient.layer))\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Alert policy: what a condition is, when a journal leaves one open, and
3
+ * which open conditions have outlived their delay.
4
+ *
5
+ * Everything here is pure and a function of journal time, so the same entries
6
+ * decide the same alerts in any process at any instant past the delay. The
7
+ * transport that pages about them is `./layerWebhook.ts` and the journal-backed
8
+ * tick that drives them is `./AlertRuntime.ts`; `./Alerts.ts` is the module a
9
+ * consumer imports.
10
+ *
11
+ * @since 1.0.0
12
+ */
13
+ import type { JournalEvent } from "@smthrs/journal";
14
+ import { Schema } from "effect";
15
+ /**
16
+ * The journal event type one delivered alert is recorded under.
17
+ *
18
+ * @category constants
19
+ * @since 0.1.0
20
+ */
21
+ export declare const deliveredEventType = "flows.alerts.delivered";
22
+ /**
23
+ * The journal event type one failed delivery attempt is recorded under.
24
+ *
25
+ * One entry is written per alert per failure code, never one per tick: a
26
+ * webhook that stays down for an hour leaves one record, not a hundred and
27
+ * twenty that every later tick has to read past.
28
+ *
29
+ * @category constants
30
+ * @since 0.1.0
31
+ */
32
+ export declare const failedEventType = "flows.alerts.failed";
33
+ /**
34
+ * How loud an alert is.
35
+ *
36
+ * @category models
37
+ * @since 0.1.0
38
+ */
39
+ export declare const Severity: Schema.Literals<readonly ["info", "warning", "critical"]>;
40
+ /**
41
+ * How loud an alert is.
42
+ *
43
+ * @category models
44
+ * @since 0.1.0
45
+ */
46
+ export type Severity = typeof Severity.Type;
47
+ /**
48
+ * How a condition is recognized in the journal.
49
+ *
50
+ * `field` names a payload key, `value` the value that means the condition
51
+ * holds, and `eventTypes` narrows which entries are consulted at all. An
52
+ * `eventTypes` item ending in `.*` names a family: `control.*` consults
53
+ * `control.run.parked` and `control.monitor.beat` alike. An entry that carries
54
+ * the field with a different value CLOSES the condition, which is what makes a
55
+ * resume clear an approval alert without a second vocabulary for "cleared".
56
+ *
57
+ * @category models
58
+ * @since 0.1.0
59
+ */
60
+ export declare const Detector: Schema.Struct<{
61
+ readonly field: Schema.NonEmptyString;
62
+ readonly value: Schema.NonEmptyString;
63
+ readonly eventTypes: Schema.optional<Schema.$Array<Schema.NonEmptyString>>;
64
+ }>;
65
+ /**
66
+ * How a condition is recognized in the journal.
67
+ *
68
+ * @category models
69
+ * @since 0.1.0
70
+ */
71
+ export type Detector = typeof Detector.Type;
72
+ /**
73
+ * What to do about one condition, and after how long.
74
+ *
75
+ * `afterMs` is a whole, non-negative number of milliseconds. The bound is the
76
+ * schema's job because the delay is also arithmetic on journal time: a `NaN`
77
+ * delay fires on the first tick and stamps a `firedAt` that JSON writes as
78
+ * `null`, and a negative one fires with a `firedAt` earlier than the condition
79
+ * it describes.
80
+ *
81
+ * @category models
82
+ * @since 0.1.0
83
+ */
84
+ export declare const Rule: Schema.Struct<{
85
+ readonly afterMs: Schema.Int;
86
+ readonly severity: Schema.optional<Schema.Literals<readonly ["info", "warning", "critical"]>>;
87
+ readonly owner: Schema.optional<Schema.String>;
88
+ readonly runbook: Schema.optional<Schema.String>;
89
+ }>;
90
+ /**
91
+ * What to do about one condition, and after how long.
92
+ *
93
+ * @category models
94
+ * @since 0.1.0
95
+ */
96
+ export type Rule = typeof Rule.Type;
97
+ /**
98
+ * The conditions a policy alerts on and the delays it alerts after.
99
+ *
100
+ * `defaults` fills in what a rule leaves out, so a policy states the delay per
101
+ * condition and the ownership once.
102
+ *
103
+ * @category models
104
+ * @since 0.1.0
105
+ */
106
+ export declare const Policy: Schema.Struct<{
107
+ readonly defaults: Schema.optional<Schema.Struct<{
108
+ readonly severity: Schema.optional<Schema.Literals<readonly ["info", "warning", "critical"]>>;
109
+ readonly owner: Schema.optional<Schema.String>;
110
+ readonly runbook: Schema.optional<Schema.String>;
111
+ }>>;
112
+ readonly rules: Schema.$Record<Schema.String, Schema.Struct<{
113
+ readonly afterMs: Schema.Int;
114
+ readonly severity: Schema.optional<Schema.Literals<readonly ["info", "warning", "critical"]>>;
115
+ readonly owner: Schema.optional<Schema.String>;
116
+ readonly runbook: Schema.optional<Schema.String>;
117
+ }>>;
118
+ /** Detectors for conditions this deployment names itself. */
119
+ readonly detectors: Schema.optional<Schema.$Record<Schema.String, Schema.Struct<{
120
+ readonly field: Schema.NonEmptyString;
121
+ readonly value: Schema.NonEmptyString;
122
+ readonly eventTypes: Schema.optional<Schema.$Array<Schema.NonEmptyString>>;
123
+ }>>>;
124
+ }>;
125
+ /**
126
+ * The conditions a policy alerts on and the delays it alerts after.
127
+ *
128
+ * @category models
129
+ * @since 0.1.0
130
+ */
131
+ export type Policy = typeof Policy.Type;
132
+ /**
133
+ * The four conditions a control plane journals out of the box.
134
+ *
135
+ * `status` rides on every `control.run.*` entry, and `health` on every
136
+ * `control.monitor.beat`. A deployment whose supervisor journals a park reason
137
+ * gets `quota-parked` from the same field the run summary reports it under.
138
+ *
139
+ * Each consults the `control.*` family only. A run's journal is shared with
140
+ * flow steps, agent traces, and anything else that writes to the run, and a
141
+ * `status` or `health` field on one of those entries would otherwise open or
142
+ * close a condition the control plane never reported.
143
+ *
144
+ * @category constants
145
+ * @since 0.1.0
146
+ */
147
+ export declare const defaultDetectors: Readonly<Record<string, Detector>>;
148
+ /**
149
+ * A condition that is open on a run, and when it opened.
150
+ *
151
+ * @category models
152
+ * @since 0.1.0
153
+ */
154
+ export interface Open {
155
+ readonly runId: string;
156
+ readonly condition: string;
157
+ /** The journal time of the entry that opened the condition. */
158
+ readonly since: number;
159
+ }
160
+ /**
161
+ * One alert a policy decided to raise.
162
+ *
163
+ * @category models
164
+ * @since 0.1.0
165
+ */
166
+ export interface Alert {
167
+ readonly runId: string;
168
+ readonly condition: string;
169
+ readonly since: number;
170
+ /**
171
+ * The journal instant the condition outlived its delay: `since` plus the
172
+ * rule's `afterMs`, never the wall clock of whichever tick noticed.
173
+ */
174
+ readonly firedAt: number;
175
+ readonly severity: Severity;
176
+ readonly coalescingKey: string;
177
+ readonly owner?: string | undefined;
178
+ readonly runbook?: string | undefined;
179
+ }
180
+ /**
181
+ * The journal source the alerter writes one outcome of one alert under.
182
+ *
183
+ * A delivery record counts as evidence that an alert went out only under this
184
+ * source, so another producer's `flows.alerts.delivered` record with a
185
+ * matching `alertId` does not suppress a page. This scopes records; it does
186
+ * not authenticate the writer, because the journal does not bind a source to
187
+ * a writer.
188
+ *
189
+ * @param id the alert's {@link alertId}
190
+ * @param outcome `delivered`, `refused`, or `failed/<code>`
191
+ * @private
192
+ * @since 1.0.0
193
+ */
194
+ export declare const recordSourceId: (id: string, outcome: string) => string;
195
+ /**
196
+ * The key an alert coalesces on: one open condition on one run.
197
+ *
198
+ * Each component is percent-encoded before it is joined, so a run id or a
199
+ * condition name containing the separator cannot forge another pair's key and
200
+ * suppress a page that belongs to somebody else. Both components are values a
201
+ * deployment chooses, which is why the encoding is not optional.
202
+ *
203
+ * @param runId the run the condition is open on
204
+ * @param condition the condition name the policy uses
205
+ * @category getters
206
+ * @since 1.0.0
207
+ */
208
+ export declare const coalescingKey: (runId: string, condition: string) => string;
209
+ /**
210
+ * The identity a delivery is recorded under.
211
+ *
212
+ * The opening time is part of it on purpose. A condition that clears and
213
+ * re-opens is a NEW alert, because the second approval wait is not the first
214
+ * one, and a key without the time would suppress it forever.
215
+ *
216
+ * @param alert the alert
217
+ * @category getters
218
+ * @since 0.1.0
219
+ */
220
+ export declare const alertId: (alert: Pick<Alert, "coalescingKey" | "since">) => string;
221
+ /**
222
+ * The detectors one policy decides with: the four this package ships, with
223
+ * the deployment's own overriding them by name.
224
+ *
225
+ * @private
226
+ * @since 1.0.0
227
+ */
228
+ export declare const detectorsOf: (policy: Policy) => Readonly<Record<string, Detector>>;
229
+ /**
230
+ * One entry's payload when it is a record a detector can read a field off,
231
+ * and `undefined` for the arrays and primitives that carry no fields at all.
232
+ *
233
+ * @private
234
+ * @since 1.0.0
235
+ */
236
+ export declare const payloadRecord: (payload: unknown) => Readonly<Record<string, unknown>> | undefined;
237
+ /**
238
+ * Folds one entry into the open-condition map. Shared by {@link conditions}
239
+ * and the runtime's incremental read, so both decide a condition the same
240
+ * way.
241
+ *
242
+ * The alerter's own records are never evidence about a condition. They are
243
+ * written into the same journal they are read from, and they carry the alert's
244
+ * own vocabulary: a refusal reports the answering HTTP `status`, which a
245
+ * detector watching the run's `status` would read as the condition clearing.
246
+ * A page would then close the condition it paged about and the next tick would
247
+ * re-open it, so a webhook that answered 503 once would alert forever.
248
+ *
249
+ * @private
250
+ * @since 1.0.0
251
+ */
252
+ export declare const observe: (policy: Policy, detectors: Readonly<Record<string, Detector>>, entry: JournalEvent.Entry, payload: Readonly<Record<string, unknown>>, open: Map<string, number>) => void;
253
+ /**
254
+ * Every condition a run's journal leaves open, with the time each opened.
255
+ *
256
+ * An entry that carries a detector's field opens the condition when the value
257
+ * matches and closes it when it does not. Entries the detector does not name
258
+ * are ignored entirely, so a monitor beat cannot close an approval wait and an
259
+ * approval cannot close a stall.
260
+ *
261
+ * @param policy the policy whose detectors decide what counts
262
+ * @param runId the run the entries belong to
263
+ * @param entries the run's journal, oldest first
264
+ * @category projections
265
+ * @since 0.1.0
266
+ */
267
+ export declare const conditions: (policy: Policy, runId: string, entries: ReadonlyArray<JournalEvent.Entry>) => ReadonlyArray<Open>;
268
+ /**
269
+ * The alerts a policy raises for the conditions open at `now`.
270
+ *
271
+ * Pure, and a function of journal time: the same journal raises the same
272
+ * alerts in any process, at any instant past the delay. `now` decides WHETHER
273
+ * an alert is raised; it never appears in one. A condition that has been open
274
+ * for less than its rule's delay raises nothing, which is the whole point of
275
+ * the delay, because most stalls clear themselves.
276
+ *
277
+ * @param policy the policy
278
+ * @param open the conditions the journal left open
279
+ * @param now the instant to judge them at
280
+ * @category projections
281
+ * @since 0.1.0
282
+ */
283
+ export declare const decide: (policy: Policy, open: ReadonlyArray<Open>, now: number) => ReadonlyArray<Alert>;
284
+ //# sourceMappingURL=AlertPolicy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AlertPolicy.d.ts","sourceRoot":"","sources":["../../src/AlertPolicy.ts"],"names":[],"mappings":"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"}