@antasphere/chassis-server 1.1.0 → 1.3.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 (187) hide show
  1. package/dist/api/apikeys.d.ts +4 -1
  2. package/dist/api/apikeys.d.ts.map +1 -1
  3. package/dist/api/apikeys.js +11 -1
  4. package/dist/api/apikeys.js.map +1 -1
  5. package/dist/api/create-api.d.ts +3 -0
  6. package/dist/api/create-api.d.ts.map +1 -1
  7. package/dist/api/create-api.js +8 -3
  8. package/dist/api/create-api.js.map +1 -1
  9. package/dist/api/invitations.d.ts.map +1 -1
  10. package/dist/api/invitations.js +6 -0
  11. package/dist/api/invitations.js.map +1 -1
  12. package/dist/api/members.d.ts +5 -2
  13. package/dist/api/members.d.ts.map +1 -1
  14. package/dist/api/members.js +1 -0
  15. package/dist/api/members.js.map +1 -1
  16. package/dist/api/projects.d.ts +3 -0
  17. package/dist/api/projects.d.ts.map +1 -1
  18. package/dist/api/projects.js +13 -1
  19. package/dist/api/projects.js.map +1 -1
  20. package/dist/api/webhooks.d.ts +24 -0
  21. package/dist/api/webhooks.d.ts.map +1 -0
  22. package/dist/api/webhooks.js +170 -0
  23. package/dist/api/webhooks.js.map +1 -0
  24. package/dist/app.d.ts.map +1 -1
  25. package/dist/app.js +11 -5
  26. package/dist/app.js.map +1 -1
  27. package/dist/boot.d.ts.map +1 -1
  28. package/dist/boot.js +47 -4
  29. package/dist/boot.js.map +1 -1
  30. package/dist/email/index.d.ts +1 -0
  31. package/dist/email/index.d.ts.map +1 -1
  32. package/dist/email/index.js +1 -0
  33. package/dist/email/index.js.map +1 -1
  34. package/dist/env.d.ts +9 -0
  35. package/dist/env.d.ts.map +1 -1
  36. package/dist/env.js +11 -0
  37. package/dist/env.js.map +1 -1
  38. package/dist/identity/demo-pass-rules.d.ts.map +1 -1
  39. package/dist/identity/demo-pass-rules.js +6 -1
  40. package/dist/identity/demo-pass-rules.js.map +1 -1
  41. package/dist/identity/hub-reconcile.d.ts +2 -2
  42. package/dist/identity/hub-reconcile.d.ts.map +1 -1
  43. package/dist/identity/hub-reconcile.js.map +1 -1
  44. package/dist/index.d.ts +2 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/jobs/index.d.ts +1 -1
  47. package/dist/jobs/index.d.ts.map +1 -1
  48. package/dist/jobs/index.js.map +1 -1
  49. package/dist/jobs/pgboss.d.ts +34 -0
  50. package/dist/jobs/pgboss.d.ts.map +1 -1
  51. package/dist/jobs/pgboss.js +32 -9
  52. package/dist/jobs/pgboss.js.map +1 -1
  53. package/dist/mcp/index.d.ts +1 -0
  54. package/dist/mcp/index.d.ts.map +1 -1
  55. package/dist/mcp/index.js +1 -0
  56. package/dist/mcp/index.js.map +1 -1
  57. package/dist/mcp/server.d.ts +1 -1
  58. package/dist/mcp/server.d.ts.map +1 -1
  59. package/dist/mcp/server.js +11 -2
  60. package/dist/mcp/server.js.map +1 -1
  61. package/dist/mcp/tool-kit.d.ts +2 -0
  62. package/dist/mcp/tool-kit.d.ts.map +1 -1
  63. package/dist/mcp/tool-kit.js +5 -1
  64. package/dist/mcp/tool-kit.js.map +1 -1
  65. package/dist/mcp/webhooks.d.ts +16 -0
  66. package/dist/mcp/webhooks.d.ts.map +1 -0
  67. package/dist/mcp/webhooks.js +226 -0
  68. package/dist/mcp/webhooks.js.map +1 -0
  69. package/dist/members/removal.d.ts +10 -2
  70. package/dist/members/removal.d.ts.map +1 -1
  71. package/dist/members/removal.js +3 -0
  72. package/dist/members/removal.js.map +1 -1
  73. package/dist/middleware/idempotency.d.ts.map +1 -1
  74. package/dist/middleware/idempotency.js +3 -1
  75. package/dist/middleware/idempotency.js.map +1 -1
  76. package/dist/middleware/index.d.ts +1 -0
  77. package/dist/middleware/index.d.ts.map +1 -1
  78. package/dist/middleware/index.js +1 -0
  79. package/dist/middleware/index.js.map +1 -1
  80. package/dist/middleware/scopes.d.ts +9 -1
  81. package/dist/middleware/scopes.d.ts.map +1 -1
  82. package/dist/middleware/scopes.js +8 -1
  83. package/dist/middleware/scopes.js.map +1 -1
  84. package/dist/middleware/static-assets.d.ts +63 -0
  85. package/dist/middleware/static-assets.d.ts.map +1 -0
  86. package/dist/middleware/static-assets.js +125 -0
  87. package/dist/middleware/static-assets.js.map +1 -0
  88. package/dist/secret-box.d.ts +10 -0
  89. package/dist/secret-box.d.ts.map +1 -0
  90. package/dist/secret-box.js +87 -0
  91. package/dist/secret-box.js.map +1 -0
  92. package/dist/testing/helpers.d.ts +8 -0
  93. package/dist/testing/helpers.d.ts.map +1 -1
  94. package/dist/testing/helpers.js.map +1 -1
  95. package/dist/tool-definition.d.ts +24 -0
  96. package/dist/tool-definition.d.ts.map +1 -1
  97. package/dist/tool-definition.js.map +1 -1
  98. package/dist/webhooks/catalog.d.ts +80 -0
  99. package/dist/webhooks/catalog.d.ts.map +1 -0
  100. package/dist/webhooks/catalog.js +134 -0
  101. package/dist/webhooks/catalog.js.map +1 -0
  102. package/dist/webhooks/index.d.ts +9 -0
  103. package/dist/webhooks/index.d.ts.map +1 -0
  104. package/dist/webhooks/index.js +9 -0
  105. package/dist/webhooks/index.js.map +1 -0
  106. package/dist/webhooks/jobs.d.ts +12 -0
  107. package/dist/webhooks/jobs.d.ts.map +1 -0
  108. package/dist/webhooks/jobs.js +77 -0
  109. package/dist/webhooks/jobs.js.map +1 -0
  110. package/dist/webhooks/mail.d.ts +24 -0
  111. package/dist/webhooks/mail.d.ts.map +1 -0
  112. package/dist/webhooks/mail.js +21 -0
  113. package/dist/webhooks/mail.js.map +1 -0
  114. package/dist/webhooks/match.d.ts +22 -0
  115. package/dist/webhooks/match.d.ts.map +1 -0
  116. package/dist/webhooks/match.js +40 -0
  117. package/dist/webhooks/match.js.map +1 -0
  118. package/dist/webhooks/safe-fetch.d.ts +94 -0
  119. package/dist/webhooks/safe-fetch.d.ts.map +1 -0
  120. package/dist/webhooks/safe-fetch.js +263 -0
  121. package/dist/webhooks/safe-fetch.js.map +1 -0
  122. package/dist/webhooks/service.d.ts +245 -0
  123. package/dist/webhooks/service.d.ts.map +1 -0
  124. package/dist/webhooks/service.js +861 -0
  125. package/dist/webhooks/service.js.map +1 -0
  126. package/dist/webhooks/signing.d.ts +40 -0
  127. package/dist/webhooks/signing.d.ts.map +1 -0
  128. package/dist/webhooks/signing.js +108 -0
  129. package/dist/webhooks/signing.js.map +1 -0
  130. package/dist/webhooks/tx-send.d.ts +31 -0
  131. package/dist/webhooks/tx-send.d.ts.map +1 -0
  132. package/dist/webhooks/tx-send.js +22 -0
  133. package/dist/webhooks/tx-send.js.map +1 -0
  134. package/package.json +7 -3
  135. package/src/api/apikeys.ts +12 -1
  136. package/src/api/create-api.ts +17 -3
  137. package/src/api/invitations.ts +6 -0
  138. package/src/api/members.ts +6 -2
  139. package/src/api/projects.ts +16 -1
  140. package/src/api/webhooks.ts +205 -0
  141. package/src/app.ts +17 -4
  142. package/src/boot.ts +48 -4
  143. package/src/email/index.ts +1 -0
  144. package/src/env.ts +11 -0
  145. package/src/identity/demo-pass-rules.ts +6 -1
  146. package/src/identity/hub-reconcile.ts +2 -2
  147. package/src/index.ts +13 -1
  148. package/src/jobs/index.ts +1 -0
  149. package/src/jobs/pgboss.ts +71 -9
  150. package/src/mcp/index.ts +1 -0
  151. package/src/mcp/server.ts +10 -2
  152. package/src/mcp/tool-kit.ts +7 -1
  153. package/src/mcp/webhooks.ts +370 -0
  154. package/src/members/removal.ts +21 -1
  155. package/src/middleware/idempotency.ts +3 -1
  156. package/src/middleware/index.ts +11 -0
  157. package/src/middleware/scopes.ts +14 -0
  158. package/src/middleware/static-assets.ts +144 -0
  159. package/src/secret-box.ts +93 -0
  160. package/src/testing/helpers.ts +8 -0
  161. package/src/tool-definition.ts +29 -0
  162. package/src/webhooks/catalog.ts +205 -0
  163. package/src/webhooks/index.ts +58 -0
  164. package/src/webhooks/jobs.ts +92 -0
  165. package/src/webhooks/mail.ts +48 -0
  166. package/src/webhooks/match.ts +52 -0
  167. package/src/webhooks/safe-fetch.ts +335 -0
  168. package/src/webhooks/service.ts +1089 -0
  169. package/src/webhooks/signing.ts +132 -0
  170. package/src/webhooks/tx-send.ts +53 -0
  171. package/test/host/drizzle/0002_webhooks.sql +49 -0
  172. package/test/host/drizzle/meta/0002_snapshot.json +3952 -0
  173. package/test/host/drizzle/meta/_journal.json +8 -1
  174. package/test/host/identity.ts +6 -1
  175. package/test/host/minimal-host.ts +2 -1
  176. package/test/host/minimal-tool.ts +32 -4
  177. package/test/integration/mcp-webhooks.test.ts +360 -0
  178. package/test/integration/webhooks.test.ts +1684 -0
  179. package/test/unit/secret-box.test.ts +124 -0
  180. package/test/unit/static-assets.test.ts +227 -0
  181. package/test/unit/tx-send.test.ts +23 -0
  182. package/test/unit/webhook-catalog.test.ts +151 -0
  183. package/test/unit/webhook-env.test.ts +60 -0
  184. package/test/unit/webhook-jobs.test.ts +92 -0
  185. package/test/unit/webhook-match.test.ts +81 -0
  186. package/test/unit/webhook-safe-fetch.test.ts +330 -0
  187. package/test/unit/webhook-signing.test.ts +169 -0
@@ -1 +1 @@
1
- {"version":3,"file":"tool-definition.js","sourceRoot":"","sources":["../src/tool-definition.ts"],"names":[],"mappings":"AAkaA;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAA0B;IACvD,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC3E,KAAK,MAAM,IAAI,IAAI,CAAC,gBAAgB,EAAE,aAAa,EAAE,WAAW,CAAU,EAAE,CAAC;QAC3E,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,qCAAqC,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,iBAAiB,CAAU,EAAE,CAAC;QAC1E,IAAI,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,gCAAgC,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAkC;IACnE,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACnF,MAAM,CAAC,GAAG,QACe,CAAC;IAC1B,MAAM,MAAM,GAA6B;QACvC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;QAChB,CAAC,aAAa,EAAE,CAAC,CAAC,WAAW,CAAC;QAC9B,CAAC,cAAc,EAAE,CAAC,CAAC,YAAY,CAAC;QAChC,CAAC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC;QAC/B,CAAC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC;QACjC,CAAC,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC;QAC3C,CAAC,mBAAmB,EAAE,CAAC,CAAC,iBAAiB,CAAC;QAC1C,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC;QACvB,CAAC,eAAe,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC;QACnC,CAAC,qBAAqB,EAAE,CAAC,CAAC,GAAG,EAAE,eAAe,CAAC;QAC/C,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC;QACrC,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC;QACrC,CAAC,iBAAiB,EAAE,CAAC,CAAC,eAAe,CAAC;QACtC,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC;KAC3B,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,mCAAmC,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"tool-definition.js","sourceRoot":"","sources":["../src/tool-definition.ts"],"names":[],"mappings":"AA+bA;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,IAA0B;IACvD,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAC3E,KAAK,MAAM,IAAI,IAAI,CAAC,gBAAgB,EAAE,aAAa,EAAE,WAAW,CAAU,EAAE,CAAC;QAC3E,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,qCAAqC,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,iBAAiB,CAAU,EAAE,CAAC;QAC1E,IAAI,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YACpE,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,gCAAgC,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAkC;IACnE,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACnF,MAAM,CAAC,GAAG,QACe,CAAC;IAC1B,MAAM,MAAM,GAA6B;QACvC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;QAChB,CAAC,aAAa,EAAE,CAAC,CAAC,WAAW,CAAC;QAC9B,CAAC,cAAc,EAAE,CAAC,CAAC,YAAY,CAAC;QAChC,CAAC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC;QAC/B,CAAC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC;QACjC,CAAC,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC;QAC3C,CAAC,mBAAmB,EAAE,CAAC,CAAC,iBAAiB,CAAC;QAC1C,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC;QACvB,CAAC,eAAe,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC;QACnC,CAAC,qBAAqB,EAAE,CAAC,CAAC,GAAG,EAAE,eAAe,CAAC;QAC/C,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC;QACrC,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,EAAE,UAAU,CAAC;QACrC,CAAC,iBAAiB,EAAE,CAAC,CAAC,eAAe,CAAC;QACtC,CAAC,WAAW,EAAE,CAAC,CAAC,SAAS,CAAC;KAC3B,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,mCAAmC,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ import type { WebhookCatalog } from '@antasphere/chassis-contract';
3
+ /**
4
+ * The event catalog (PRDCT-3482): what an instance can deliver to a webhook
5
+ * endpoint. The TOOL declares its events through the `webhooks` slot of its
6
+ * definition (its subjects, its event types, the zod schema of each event's
7
+ * `data`); the chassis adds its own events, checks the whole at boot, matches
8
+ * subscriptions against it and serves it on `GET /webhooks/events` with the
9
+ * JSON Schema of every payload, so an agent discovers the types and the
10
+ * shapes without reading a page of documentation.
11
+ *
12
+ * A SUBJECT is a filterable key the tool exposes (`thing`, `project`): an
13
+ * endpoint narrows its subscription to a list of ids per subject, and the
14
+ * envelope carries each subject's value under the FIELD the tool names for it
15
+ * (`thingId`, `projectIds`). An event declares which subjects it carries. A
16
+ * subject's value in the envelope is one id, or always a list of ids when
17
+ * the subject is declared `list` (the shape never varies per event).
18
+ */
19
+ /** One filterable key of the tool, as the slot declares it. */
20
+ export interface WebhookSubjectDeclaration {
21
+ /** The envelope field the subject's value is written under (`thingId`, `projectIds`). */
22
+ field: string;
23
+ description: string;
24
+ /**
25
+ * The subject's value is a LIST of ids (a thing in several projects): the
26
+ * envelope always writes an array under the field, a single id included.
27
+ * Absent: one id, always a string; a list is refused at emit.
28
+ */
29
+ list?: boolean;
30
+ }
31
+ /** One event type of the tool, as the slot declares it. */
32
+ export interface WebhookEventDeclaration {
33
+ /** `thing.created`: a lowercase family, a dot, a lowercase verb. */
34
+ type: string;
35
+ description: string;
36
+ /** The subjects this event carries, by key; every one must be declared in `subjects`. */
37
+ subjects: readonly string[];
38
+ /** The schema of the event's `data`, validated at emit (a mismatch is a bug, logged and skipped). */
39
+ data: z.ZodType;
40
+ }
41
+ /** The `webhooks` slot of a tool definition. */
42
+ export interface WebhookCatalogDeclaration {
43
+ subjects: Readonly<Record<string, WebhookSubjectDeclaration>>;
44
+ events: readonly WebhookEventDeclaration[];
45
+ }
46
+ /** The value one emit gives a subject: one id, or a list (a thing in several projects). */
47
+ export type WebhookSubjectValue = string | readonly string[];
48
+ /** An event type: `family.verb`, lowercase, digits and underscores allowed after the first letter. */
49
+ export declare const WEBHOOK_EVENT_TYPE: RegExp;
50
+ /** The test ping every endpoint can be sent (`POST /webhooks/{id}/test`). */
51
+ export declare const WEBHOOK_PING_EVENT = "webhook.ping";
52
+ /**
53
+ * The chassis's own events: what the generic half knows happened, bridged
54
+ * from the in-process bus (member, workspace, key events) or emitted inside
55
+ * the write's transaction where the chassis owns it (projects, the ping).
56
+ * None names a tool. A tool may not re-declare one of these types.
57
+ */
58
+ export declare const CHASSIS_WEBHOOK_EVENTS: readonly WebhookEventDeclaration[];
59
+ /**
60
+ * The chassis's own subject: a project, which every tool has (the chassis
61
+ * owns projects). A tool that declares `project` itself may name another
62
+ * field for it (`projectIds`, a list); the chassis's project events follow.
63
+ */
64
+ export declare const CHASSIS_WEBHOOK_SUBJECTS: Readonly<Record<string, WebhookSubjectDeclaration>>;
65
+ /** The merged, checked catalog an instance runs on. */
66
+ export interface WebhookCatalogIndex {
67
+ subjects: Readonly<Record<string, WebhookSubjectDeclaration>>;
68
+ events: ReadonlyMap<string, WebhookEventDeclaration>;
69
+ }
70
+ /**
71
+ * Merge the chassis's events with the tool's and check the whole, at boot:
72
+ * a type that is not `family.verb`, a type declared twice (or re-declaring a
73
+ * chassis type), a subject key that is not a plain identifier, an event
74
+ * carrying a subject nobody declared, or the chassis's `project` subject
75
+ * declared under another field, stops the boot naming the fault.
76
+ */
77
+ export declare function buildWebhookCatalog(tool: WebhookCatalogDeclaration | undefined): WebhookCatalogIndex;
78
+ /** The catalog as `GET /webhooks/events` answers it: every event with the JSON Schema of its data. */
79
+ export declare function catalogToWire(catalog: WebhookCatalogIndex): WebhookCatalog;
80
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/webhooks/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE;;;;;;;;;;;;;;;GAeG;AAEH,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IACxC,yFAAyF;IACzF,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,qGAAqG;IACrG,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC;CACjB;AAED,gDAAgD;AAChD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAC9D,MAAM,EAAE,SAAS,uBAAuB,EAAE,CAAC;CAC5C;AAED,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;AAE7D,sGAAsG;AACtG,eAAO,MAAM,kBAAkB,QAAuC,CAAC;AAGvE,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,uBAAuB,EAmDpE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAExF,CAAC;AAEF,uDAAuD;AACvD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IAC9D,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;CACtD;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,yBAAyB,GAAG,SAAS,GAAG,mBAAmB,CA0CpG;AAED,sGAAsG;AACtG,wBAAgB,aAAa,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAkB1E"}
@@ -0,0 +1,134 @@
1
+ import { z } from 'zod';
2
+ /** An event type: `family.verb`, lowercase, digits and underscores allowed after the first letter. */
3
+ export const WEBHOOK_EVENT_TYPE = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
4
+ const SUBJECT_KEY = /^[a-z][a-z0-9_]*$/;
5
+ /** The test ping every endpoint can be sent (`POST /webhooks/{id}/test`). */
6
+ export const WEBHOOK_PING_EVENT = 'webhook.ping';
7
+ /**
8
+ * The chassis's own events: what the generic half knows happened, bridged
9
+ * from the in-process bus (member, workspace, key events) or emitted inside
10
+ * the write's transaction where the chassis owns it (projects, the ping).
11
+ * None names a tool. A tool may not re-declare one of these types.
12
+ */
13
+ export const CHASSIS_WEBHOOK_EVENTS = [
14
+ {
15
+ type: WEBHOOK_PING_EVENT,
16
+ description: 'A test delivery, sent on request from the endpoint page or the API.',
17
+ subjects: [],
18
+ data: z.object({ endpointId: z.string(), sentBy: z.string().nullable() })
19
+ },
20
+ {
21
+ type: 'member.joined',
22
+ description: 'A person joined the workspace by accepting an invitation.',
23
+ subjects: [],
24
+ data: z.object({ userId: z.string(), role: z.string() })
25
+ },
26
+ {
27
+ type: 'member.removed',
28
+ description: 'A person was removed from the workspace by an owner or an admin.',
29
+ subjects: [],
30
+ data: z.object({ userId: z.string() })
31
+ },
32
+ {
33
+ type: 'workspace.created',
34
+ description: 'A workspace was created from inside the product.',
35
+ subjects: [],
36
+ data: z.object({ ownerUserId: z.string() })
37
+ },
38
+ {
39
+ type: 'apikey.created',
40
+ description: 'An API key was created on the API keys page or through the API (not the command line’s own sign-in key).',
41
+ subjects: [],
42
+ data: z.object({ apiKeyId: z.string() })
43
+ },
44
+ {
45
+ type: 'apikey.revoked',
46
+ description: 'An API key of the workspace was revoked from the API keys page or through the API (not a key revoking itself at logout).',
47
+ subjects: [],
48
+ data: z.object({ apiKeyId: z.string() })
49
+ },
50
+ {
51
+ type: 'project.created',
52
+ description: 'A project was created in the workspace.',
53
+ subjects: ['project'],
54
+ data: z.object({ projectId: z.string(), name: z.string(), createdBy: z.string() })
55
+ },
56
+ {
57
+ type: 'project.archived',
58
+ description: 'A project of the workspace was archived.',
59
+ subjects: ['project'],
60
+ data: z.object({ projectId: z.string(), name: z.string() })
61
+ }
62
+ ];
63
+ /**
64
+ * The chassis's own subject: a project, which every tool has (the chassis
65
+ * owns projects). A tool that declares `project` itself may name another
66
+ * field for it (`projectIds`, a list); the chassis's project events follow.
67
+ */
68
+ export const CHASSIS_WEBHOOK_SUBJECTS = {
69
+ project: { field: 'projectId', description: 'A project of the workspace, by id' }
70
+ };
71
+ /**
72
+ * Merge the chassis's events with the tool's and check the whole, at boot:
73
+ * a type that is not `family.verb`, a type declared twice (or re-declaring a
74
+ * chassis type), a subject key that is not a plain identifier, an event
75
+ * carrying a subject nobody declared, or the chassis's `project` subject
76
+ * declared under another field, stops the boot naming the fault.
77
+ */
78
+ export function buildWebhookCatalog(tool) {
79
+ const subjects = { ...CHASSIS_WEBHOOK_SUBJECTS };
80
+ for (const [key, subject] of Object.entries(tool?.subjects ?? {})) {
81
+ if (!SUBJECT_KEY.test(key)) {
82
+ throw new Error(`tool definition: webhooks.subjects key "${key}" must match ${SUBJECT_KEY}`);
83
+ }
84
+ if (typeof subject.field !== 'string' || subject.field === '') {
85
+ throw new Error(`tool definition: webhooks.subjects.${key}.field is required (the envelope field)`);
86
+ }
87
+ // A tool may re-declare the chassis's `project` subject under its own
88
+ // field (a tool whose things belong to several projects writes a list
89
+ // under `projectIds`); the chassis's own project events then write
90
+ // their one id under that field too. A subject's value is one id or a
91
+ // list of ids, and the catalog says so.
92
+ subjects[key] = subject;
93
+ }
94
+ const events = new Map();
95
+ for (const event of [...CHASSIS_WEBHOOK_EVENTS, ...(tool?.events ?? [])]) {
96
+ if (!WEBHOOK_EVENT_TYPE.test(event.type)) {
97
+ throw new Error(`tool definition: webhooks event type "${event.type}" must be family.verb (${WEBHOOK_EVENT_TYPE})`);
98
+ }
99
+ if (events.has(event.type)) {
100
+ throw new Error(`tool definition: webhooks event type "${event.type}" is declared twice`);
101
+ }
102
+ if (event.type === '*' || event.type.endsWith('.*')) {
103
+ throw new Error(`tool definition: webhooks event type "${event.type}" is a pattern, not a type`);
104
+ }
105
+ for (const key of event.subjects) {
106
+ if (!(key in subjects)) {
107
+ throw new Error(`tool definition: webhooks event "${event.type}" carries the subject "${key}", which webhooks.subjects does not declare`);
108
+ }
109
+ }
110
+ if (typeof event.description !== 'string' || event.description === '') {
111
+ throw new Error(`tool definition: webhooks event "${event.type}" needs a description`);
112
+ }
113
+ events.set(event.type, event);
114
+ }
115
+ return { subjects, events };
116
+ }
117
+ /** The catalog as `GET /webhooks/events` answers it: every event with the JSON Schema of its data. */
118
+ export function catalogToWire(catalog) {
119
+ return {
120
+ subjects: Object.entries(catalog.subjects).map(([key, s]) => ({
121
+ key,
122
+ field: s.field,
123
+ description: s.description,
124
+ list: s.list === true
125
+ })),
126
+ events: [...catalog.events.values()].map((e) => ({
127
+ type: e.type,
128
+ description: e.description,
129
+ subjects: [...e.subjects],
130
+ schema: z.toJSONSchema(e.data, { target: 'draft-2020-12', unrepresentable: 'any' })
131
+ }))
132
+ };
133
+ }
134
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/webhooks/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { buildWebhookCatalog, catalogToWire, CHASSIS_WEBHOOK_EVENTS, CHASSIS_WEBHOOK_SUBJECTS, WEBHOOK_EVENT_TYPE, WEBHOOK_PING_EVENT, type WebhookCatalogDeclaration, type WebhookCatalogIndex, type WebhookEventDeclaration, type WebhookSubjectDeclaration, type WebhookSubjectValue } from './catalog.js';
2
+ export { endpointWants, matchesEventType, matchesFilters } from './match.js';
3
+ export { checkWebhookUrl, isPublicAddress, safePost, WebhookUrlRefusedError, type SafeFetchOptions, type SafeFetchPolicy, type SafeFetchResult, type SafeLookup, type SafeTransport, type WebhookUrlRefusal } from './safe-fetch.js';
4
+ export { generateWebhookSecret, signWebhook, SigningError, verifyWebhookSignature, WEBHOOK_SECRET_PREFIX, webhookHeaders, webhookSecretKey } from './signing.js';
5
+ export { DEFAULT_WEBHOOK_DIALS, deliveryToWire, endpointToWire, WEBHOOK_DELIVERIES_HELD_QUEUE, WEBHOOK_DELIVERIES_QUEUE, WEBHOOK_EVENTS_QUEUE, WEBHOOK_PURGE_QUEUE, WEBHOOK_SECRET_BOX_INFO, MAX_IN_FLIGHT_PER_ENDPOINT, WebhookDeliveryDeferred, WebhookDeliveryFailed, WebhookRefusedError, WebhookService, type WebhookDials, type WebhookEmit, type WebhookEmitter, type WebhookEventJob, type WebhookServiceDeps } from './service.js';
6
+ export { webhookJobs } from './jobs.js';
7
+ export { buildWebhookDisabledEmail, type WebhookDisabledEmailParams } from './mail.js';
8
+ export { connectionOf, drizzleExecutor, sendInTx } from './tx-send.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webhooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EACL,eAAe,EACf,eAAe,EACf,QAAQ,EACR,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,6BAA6B,EAC7B,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,EAC1B,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,KAAK,0BAA0B,EAAE,MAAM,WAAW,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { buildWebhookCatalog, catalogToWire, CHASSIS_WEBHOOK_EVENTS, CHASSIS_WEBHOOK_SUBJECTS, WEBHOOK_EVENT_TYPE, WEBHOOK_PING_EVENT } from './catalog.js';
2
+ export { endpointWants, matchesEventType, matchesFilters } from './match.js';
3
+ export { checkWebhookUrl, isPublicAddress, safePost, WebhookUrlRefusedError } from './safe-fetch.js';
4
+ export { generateWebhookSecret, signWebhook, SigningError, verifyWebhookSignature, WEBHOOK_SECRET_PREFIX, webhookHeaders, webhookSecretKey } from './signing.js';
5
+ export { DEFAULT_WEBHOOK_DIALS, deliveryToWire, endpointToWire, WEBHOOK_DELIVERIES_HELD_QUEUE, WEBHOOK_DELIVERIES_QUEUE, WEBHOOK_EVENTS_QUEUE, WEBHOOK_PURGE_QUEUE, WEBHOOK_SECRET_BOX_INFO, MAX_IN_FLIGHT_PER_ENDPOINT, WebhookDeliveryDeferred, WebhookDeliveryFailed, WebhookRefusedError, WebhookService } from './service.js';
6
+ export { webhookJobs } from './jobs.js';
7
+ export { buildWebhookDisabledEmail } from './mail.js';
8
+ export { connectionOf, drizzleExecutor, sendInTx } from './tx-send.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/webhooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,wBAAwB,EACxB,kBAAkB,EAClB,kBAAkB,EAMnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EACL,eAAe,EACf,eAAe,EACf,QAAQ,EACR,sBAAsB,EAOvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,6BAA6B,EAC7B,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,EAC1B,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EAMf,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAmC,MAAM,WAAW,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { JobDeclaration } from '../jobs/pgboss.js';
2
+ import type { Logger } from '../logger.js';
3
+ import { type WebhookService } from './service.js';
4
+ /**
5
+ * The four queues of the webhooks, declared to the job runtime like a tool's
6
+ * own (`createJobs` installs them in the same lock section, in this order):
7
+ * the held queue FIRST so the delivery queue can name it as its dead letter
8
+ * (pg-boss's job row holds a foreign key on it), then the fan-out, the
9
+ * delivery with its retry budget, and the nightly purge.
10
+ */
11
+ export declare function webhookJobs(service: WebhookService, logger: Logger, retentionDays: number): JobDeclaration[];
12
+ //# sourceMappingURL=jobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../src/webhooks/jobs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EASL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAEtB;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,cAAc,EAAE,CAkElB"}
@@ -0,0 +1,77 @@
1
+ import { WEBHOOK_DELIVERIES_HELD_QUEUE, WEBHOOK_DELIVERIES_QUEUE, WEBHOOK_EVENTS_QUEUE, WEBHOOK_PURGE_QUEUE, WebhookDeliveryDeferred, WebhookDeliveryFailed } from './service.js';
2
+ /**
3
+ * The four queues of the webhooks, declared to the job runtime like a tool's
4
+ * own (`createJobs` installs them in the same lock section, in this order):
5
+ * the held queue FIRST so the delivery queue can name it as its dead letter
6
+ * (pg-boss's job row holds a foreign key on it), then the fan-out, the
7
+ * delivery with its retry budget, and the nightly purge.
8
+ */
9
+ export function webhookJobs(service, logger, retentionDays) {
10
+ const { dials } = service;
11
+ return [
12
+ {
13
+ queue: WEBHOOK_DELIVERIES_HELD_QUEUE,
14
+ options: { retryLimit: 10, retryDelaySeconds: 60, retryBackoff: true },
15
+ handler: async (data) => {
16
+ const job = data;
17
+ if (!job?.deliveryId)
18
+ return;
19
+ await service.hold(job.deliveryId);
20
+ }
21
+ },
22
+ {
23
+ queue: WEBHOOK_EVENTS_QUEUE,
24
+ concurrency: 2,
25
+ pollingIntervalSeconds: 0.5,
26
+ handler: async (data) => {
27
+ const job = data;
28
+ if (!job?.id)
29
+ return;
30
+ const count = await service.fanOut(job);
31
+ logger.debug({ eventId: job.id, type: job.type, deliveries: count }, 'webhooks: event fanned out');
32
+ }
33
+ },
34
+ {
35
+ queue: WEBHOOK_DELIVERIES_QUEUE,
36
+ options: {
37
+ retryLimit: dials.retryLimit,
38
+ retryDelaySeconds: dials.retryDelaySeconds,
39
+ retryBackoff: true,
40
+ deadLetter: WEBHOOK_DELIVERIES_HELD_QUEUE
41
+ },
42
+ // Eight deliveries in flight per replica: a receiver that answers
43
+ // nothing holds one slot for its timeout, not the instance.
44
+ concurrency: 8,
45
+ pollingIntervalSeconds: 0.5,
46
+ handler: async (data) => {
47
+ const job = data;
48
+ if (!job?.deliveryId)
49
+ return;
50
+ try {
51
+ await service.attempt(job.deliveryId, { retryable: true });
52
+ }
53
+ catch (e) {
54
+ if (e instanceof WebhookDeliveryDeferred) {
55
+ // The endpoint already holds its share of the pollers: back in the
56
+ // queue in two seconds, its retry budget untouched.
57
+ await service.requeue(job.deliveryId, 2);
58
+ return;
59
+ }
60
+ if (e instanceof WebhookDeliveryFailed) {
61
+ logger.warn({ deliveryId: e.deliveryId, reason: e.message }, 'webhooks: a delivery attempt failed, will retry');
62
+ }
63
+ throw e;
64
+ }
65
+ }
66
+ },
67
+ {
68
+ queue: WEBHOOK_PURGE_QUEUE,
69
+ schedule: { cron: '0 3 * * *', enabled: retentionDays > 0 },
70
+ handler: async () => {
71
+ const deleted = await service.purge(retentionDays);
72
+ logger.info({ retentionDays, deleted }, 'webhook deliveries retention purge ran');
73
+ }
74
+ }
75
+ ];
76
+ }
77
+ //# sourceMappingURL=jobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/webhooks/jobs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type MailBrand } from '../email/shell.js';
2
+ /**
3
+ * The one mail of the webhooks: an endpoint was switched off after failing
4
+ * for days, sent once to the person who registered it. Env-free like the
5
+ * account mails (`email/templates.ts`): every value arrives as a parameter,
6
+ * so a tool's mail wall renders it standalone.
7
+ */
8
+ export interface WebhookDisabledEmailParams {
9
+ brand: MailBrand;
10
+ recipientName: string;
11
+ url: string;
12
+ description: string;
13
+ workspaceName: string;
14
+ consecutiveFailures: number;
15
+ firstFailureAt: Date;
16
+ /** The endpoint on its page in the dashboard: where it is re-enabled. */
17
+ manageUrl: string;
18
+ }
19
+ export declare function buildWebhookDisabledEmail(p: WebhookDisabledEmailParams): {
20
+ subject: string;
21
+ html: string;
22
+ text: string;
23
+ };
24
+ //# sourceMappingURL=mail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../../src/webhooks/mail.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4D,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE7G;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,SAAS,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,IAAI,CAAC;IACrB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,0BAA0B,GAAG;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAuBA"}
@@ -0,0 +1,21 @@
1
+ import { button, esc, fine, fmtDate, makeShell, para, spelledLink } from '../email/shell.js';
2
+ export function buildWebhookDisabledEmail(p) {
3
+ const name = p.brand.name;
4
+ const what = p.description ? `${p.description} (${p.url})` : p.url;
5
+ const subject = `A webhook of ${p.workspaceName} was switched off`;
6
+ const html = makeShell(p.brand)({
7
+ preheader: `${what} has not accepted a delivery since ${fmtDate(p.firstFailureAt)}.`,
8
+ eyebrow: 'Webhooks',
9
+ title: 'A webhook was switched off',
10
+ body: para(`${esc(name)} has stopped delivering to <strong>${esc(what)}</strong>, a webhook of
11
+ <strong>${esc(p.workspaceName)}</strong>: every delivery since ${fmtDate(p.firstFailureAt)}
12
+ failed, ${p.consecutiveFailures} in a row.`) +
13
+ para('The deliveries it missed are kept on its page. Once the receiver answers again, switch the webhook back on there and redeliver what matters.') +
14
+ button(p.manageUrl, 'Open the webhook') +
15
+ fine(`You receive this because you registered the webhook, ${esc(p.recipientName)}.`) +
16
+ spelledLink(p.manageUrl)
17
+ });
18
+ const text = `${name} has stopped delivering to ${what}, a webhook of ${p.workspaceName}: every delivery since ${fmtDate(p.firstFailureAt)} failed, ${p.consecutiveFailures} in a row.\n\nOpen the webhook: ${p.manageUrl}\n\nThe deliveries it missed are kept on its page. Once the receiver answers again, switch the webhook back on there and redeliver what matters.`;
19
+ return { subject, html, text };
20
+ }
21
+ //# sourceMappingURL=mail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/webhooks/mail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAoB7G,MAAM,UAAU,yBAAyB,CAAC,CAA6B;IAKrE,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACnE,MAAM,OAAO,GAAG,gBAAgB,CAAC,CAAC,aAAa,mBAAmB,CAAC;IACnE,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC9B,SAAS,EAAE,GAAG,IAAI,sCAAsC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,GAAG;QACpF,OAAO,EAAE,UAAU;QACnB,KAAK,EAAE,4BAA4B;QACnC,IAAI,EACF,IAAI,CACF,GAAG,GAAG,CAAC,IAAI,CAAC,sCAAsC,GAAG,CAAC,IAAI,CAAC;mBAChD,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,mCAAmC,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC;mBAChF,CAAC,CAAC,mBAAmB,YAAY,CAC7C;YACD,IAAI,CACF,8IAA8I,CAC/I;YACD,MAAM,CAAC,CAAC,CAAC,SAAS,EAAE,kBAAkB,CAAC;YACvC,IAAI,CAAC,wDAAwD,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC;YACrF,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;KAC3B,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,GAAG,IAAI,8BAA8B,IAAI,kBAAkB,CAAC,CAAC,aAAa,0BAA0B,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC,mBAAmB,mCAAmC,CAAC,CAAC,SAAS,kJAAkJ,CAAC;IAC5W,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { WebhookSubjectValue } from './catalog.js';
2
+ /**
3
+ * The subscription rule, pure and unit-tested (`webhook-match.test.ts`): an
4
+ * endpoint's `events` are matched against an event's type, its `filters`
5
+ * against the event's subjects. The chassis never knows what a subject IS,
6
+ * only that it is a key with one or several ids.
7
+ */
8
+ /** `*` matches every type; `family.*` matches the family; a type matches itself. */
9
+ export declare function matchesEventType(patterns: readonly string[], type: string): boolean;
10
+ /**
11
+ * AND across the filter's keys, OR within a key's ids. An event that does not
12
+ * carry a filtered subject does not match (a filter on `project` excludes an
13
+ * event with no project). An empty filter matches everything.
14
+ */
15
+ export declare function matchesFilters(filters: Readonly<Record<string, readonly string[]>>, subjects: Readonly<Record<string, WebhookSubjectValue>>): boolean;
16
+ /** Whether an endpoint takes this event: enabled, the type subscribed, the filters satisfied. */
17
+ export declare function endpointWants(endpoint: {
18
+ enabled: boolean;
19
+ events: readonly string[];
20
+ filters: Readonly<Record<string, readonly string[]>>;
21
+ }, type: string, subjects: Readonly<Record<string, WebhookSubjectValue>>): boolean;
22
+ //# sourceMappingURL=match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/webhooks/match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD;;;;;GAKG;AAEH,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,EACpD,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,GACtD,OAAO,CAST;AAED,iGAAiG;AACjG,wBAAgB,aAAa,CAC3B,QAAQ,EAAE;IACR,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CACtD,EACD,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,GACtD,OAAO,CAIT"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The subscription rule, pure and unit-tested (`webhook-match.test.ts`): an
3
+ * endpoint's `events` are matched against an event's type, its `filters`
4
+ * against the event's subjects. The chassis never knows what a subject IS,
5
+ * only that it is a key with one or several ids.
6
+ */
7
+ /** `*` matches every type; `family.*` matches the family; a type matches itself. */
8
+ export function matchesEventType(patterns, type) {
9
+ const family = type.slice(0, type.indexOf('.'));
10
+ for (const pattern of patterns) {
11
+ if (pattern === '*' || pattern === type)
12
+ return true;
13
+ if (pattern.endsWith('.*') && pattern.slice(0, -2) === family)
14
+ return true;
15
+ }
16
+ return false;
17
+ }
18
+ /**
19
+ * AND across the filter's keys, OR within a key's ids. An event that does not
20
+ * carry a filtered subject does not match (a filter on `project` excludes an
21
+ * event with no project). An empty filter matches everything.
22
+ */
23
+ export function matchesFilters(filters, subjects) {
24
+ for (const [key, ids] of Object.entries(filters)) {
25
+ if (ids.length === 0)
26
+ continue;
27
+ const value = subjects[key];
28
+ if (value === undefined)
29
+ return false;
30
+ const values = typeof value === 'string' ? [value] : value;
31
+ if (!values.some((v) => ids.includes(v)))
32
+ return false;
33
+ }
34
+ return true;
35
+ }
36
+ /** Whether an endpoint takes this event: enabled, the type subscribed, the filters satisfied. */
37
+ export function endpointWants(endpoint, type, subjects) {
38
+ return (endpoint.enabled && matchesEventType(endpoint.events, type) && matchesFilters(endpoint.filters, subjects));
39
+ }
40
+ //# sourceMappingURL=match.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/webhooks/match.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,QAA2B,EAAE,IAAY;IACxE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACrD,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;IAC7E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAoD,EACpD,QAAuD;IAEvD,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IACzD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,aAAa,CAC3B,QAIC,EACD,IAAY,EACZ,QAAuD;IAEvD,OAAO,CACL,QAAQ,CAAC,OAAO,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAC1G,CAAC;AACJ,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { type Readable } from 'node:stream';
2
+ /**
3
+ * The guarded outbound POST the webhook delivery worker uses. A webhook's
4
+ * address is somebody else's choice, so the one rule that is not negotiable:
5
+ * a POST never reaches the instance's own network unless the operator opened
6
+ * it (`allowPrivateNetworks`, a self-hoster's LAN).
7
+ *
8
+ * - Up front, before any request (`checkWebhookUrl`): `http:`/`https:` only
9
+ * (`https:` alone under `httpsOnly`), the scheme's default port only, no
10
+ * userinfo, a hostname, and a literal IP host must be public.
11
+ * - A name is resolved with ALL its addresses, and the connection is refused
12
+ * (`WebhookUrlRefusedError`) when ANY of them is not public. The check is
13
+ * the socket's own `lookup`, so the address connected to IS the one
14
+ * checked: no gap for a name that answers differently the second time.
15
+ * - No redirect is ever followed: a 3xx comes back as its status.
16
+ * - One timeout over the whole call (resolution, connection, the body read).
17
+ * - The response body is read up to `maxResponseBytes`, then the stream is
18
+ * destroyed.
19
+ */
20
+ export type WebhookUrlRefusal = 'scheme' | 'port' | 'userinfo' | 'private_address' | 'https_required' | 'unresolvable' | 'malformed';
21
+ /** A webhook URL the guard refused (a scheme, a port, userinfo, an address). */
22
+ export declare class WebhookUrlRefusedError extends Error {
23
+ readonly code: WebhookUrlRefusal;
24
+ constructor(code: WebhookUrlRefusal, message?: string);
25
+ }
26
+ export interface SafeFetchPolicy {
27
+ /** Private, loopback, link-local and other non-public addresses are reachable (a self-hoster's LAN). Default false. */
28
+ allowPrivateNetworks: boolean;
29
+ /** Only https: URLs are accepted (the cloud edition). Default false. */
30
+ httpsOnly: boolean;
31
+ }
32
+ export interface LookupAddress {
33
+ address: string;
34
+ family: number;
35
+ }
36
+ /** Every address a name resolves to (`dns.lookup(host, { all: true })` by default). */
37
+ export type SafeLookup = (hostname: string) => Promise<LookupAddress[]>;
38
+ export interface SafeFetchResponse {
39
+ status: number;
40
+ headers: Record<string, string | string[] | undefined>;
41
+ body: Readable;
42
+ }
43
+ /**
44
+ * One POST, no redirect followed. The default speaks `node:http`/`node:https`
45
+ * with the guarded lookup in the socket; a test hands its own, and the
46
+ * address check then runs before it.
47
+ */
48
+ export type SafeTransport = (url: URL, init: {
49
+ method: 'POST';
50
+ headers: Record<string, string>;
51
+ body: string;
52
+ }, signal: AbortSignal) => Promise<SafeFetchResponse>;
53
+ export interface SafeFetchOptions {
54
+ lookup?: SafeLookup;
55
+ transport?: SafeTransport;
56
+ /** Over the whole call. Default 10 000. */
57
+ timeoutMs?: number;
58
+ /** The response body is cut there. Default 65 536. */
59
+ maxResponseBytes?: number;
60
+ }
61
+ export interface SafeFetchResult {
62
+ status: number;
63
+ /** the response body, utf8, cut at maxResponseBytes */
64
+ bodyText: string;
65
+ durationMs: number;
66
+ }
67
+ /**
68
+ * Whether an address is on the public internet: false for loopback, private,
69
+ * link-local (the metadata address), carrier-grade NAT, multicast, reserved,
70
+ * and every IPv6 outside global unicast (`2000::/3`): `::`, `::1`, `fc00::/7`,
71
+ * `fe80::/10`, `ff00::/8`. An IPv4-mapped (`::ffff:a.b.c.d`) or a 6to4
72
+ * (`2002::/16`) address is judged by the IPv4 inside it. Unparseable: false.
73
+ */
74
+ export declare function isPublicAddress(ip: string): boolean;
75
+ /**
76
+ * The checks that need no lookup: `http:`/`https:` only (`https:` alone under
77
+ * `httpsOnly`), the scheme's default port only (`url.port === ''`, so an
78
+ * explicit default port, which the URL parser drops, passes), no userinfo, a
79
+ * non-empty hostname, a literal IP public unless `allowPrivateNetworks`.
80
+ * Throws `WebhookUrlRefusedError`; returns the parsed URL.
81
+ */
82
+ export declare function checkWebhookUrl(url: string, policy: SafeFetchPolicy): URL;
83
+ /**
84
+ * One POST, no redirect followed (a 3xx comes back as its status), the address
85
+ * check inside the socket's own lookup (ALL resolved addresses must be public
86
+ * unless `allowPrivateNetworks`; an empty resolution is `unresolvable`),
87
+ * `AbortSignal.timeout(timeoutMs)` over the whole call, the response body read
88
+ * up to `maxResponseBytes` then the stream destroyed. Rejects with
89
+ * `WebhookUrlRefusedError` on a refused address, with the transport's error
90
+ * (ECONNREFUSED, the timeout's AbortError…) otherwise. Sets no `user-agent`:
91
+ * that is the caller's.
92
+ */
93
+ export declare function safePost(url: string, headers: Record<string, string>, body: string, policy: SafeFetchPolicy, options?: SafeFetchOptions): Promise<SafeFetchResult>;
94
+ //# sourceMappingURL=safe-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch.d.ts","sourceRoot":"","sources":["../../src/webhooks/safe-fetch.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,iBAAiB,GAC3B,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,cAAc,GAAG,WAAW,CAAC;AAYvG,gFAAgF;AAChF,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;gBACrB,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,MAAM;CAKtD;AAED,MAAM,WAAW,eAAe;IAC9B,uHAAuH;IACvH,oBAAoB,EAAE,OAAO,CAAC;IAC9B,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,uFAAuF;AACvF,MAAM,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;AAExE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,EAAE,QAAQ,CAAC;CAChB;AACD;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,GAAG,EACR,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACvE,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEhC,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAkDD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAanD;AAOD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,GAAG,CAoBzE;AAuFD;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAmC1B"}