adispatchd 0.0.0-stage → 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 (396) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -2
  3. package/dist/api/errors.d.ts +27 -0
  4. package/dist/api/errors.js +80 -0
  5. package/dist/api/errors.js.map +1 -0
  6. package/dist/api/index.d.ts +10 -0
  7. package/dist/api/index.js +11 -0
  8. package/dist/api/index.js.map +1 -0
  9. package/dist/api/logs.d.ts +30 -0
  10. package/dist/api/logs.js +88 -0
  11. package/dist/api/logs.js.map +1 -0
  12. package/dist/api/serialize.d.ts +65 -0
  13. package/dist/api/serialize.js +135 -0
  14. package/dist/api/serialize.js.map +1 -0
  15. package/dist/api/server.d.ts +89 -0
  16. package/dist/api/server.js +751 -0
  17. package/dist/api/server.js.map +1 -0
  18. package/dist/api/sse.d.ts +90 -0
  19. package/dist/api/sse.js +225 -0
  20. package/dist/api/sse.js.map +1 -0
  21. package/dist/app.d.ts +173 -0
  22. package/dist/app.js +488 -0
  23. package/dist/app.js.map +1 -0
  24. package/dist/cli/config-validate.d.ts +6 -0
  25. package/dist/cli/config-validate.js +39 -0
  26. package/dist/cli/config-validate.js.map +1 -0
  27. package/dist/cli/control.d.ts +31 -0
  28. package/dist/cli/control.js +186 -0
  29. package/dist/cli/control.js.map +1 -0
  30. package/dist/cli/http-client.d.ts +57 -0
  31. package/dist/cli/http-client.js +174 -0
  32. package/dist/cli/http-client.js.map +1 -0
  33. package/dist/cli/index.d.ts +14 -0
  34. package/dist/cli/index.js +14 -0
  35. package/dist/cli/index.js.map +1 -0
  36. package/dist/cli/init-templates.d.ts +20 -0
  37. package/dist/cli/init-templates.js +275 -0
  38. package/dist/cli/init-templates.js.map +1 -0
  39. package/dist/cli/init.d.ts +16 -0
  40. package/dist/cli/init.js +107 -0
  41. package/dist/cli/init.js.map +1 -0
  42. package/dist/cli/main.d.ts +2 -0
  43. package/dist/cli/main.js +25 -0
  44. package/dist/cli/main.js.map +1 -0
  45. package/dist/cli/program.d.ts +102 -0
  46. package/dist/cli/program.js +133 -0
  47. package/dist/cli/program.js.map +1 -0
  48. package/dist/cli/service.d.ts +27 -0
  49. package/dist/cli/service.js +252 -0
  50. package/dist/cli/service.js.map +1 -0
  51. package/dist/cli/start.d.ts +22 -0
  52. package/dist/cli/start.js +133 -0
  53. package/dist/cli/start.js.map +1 -0
  54. package/dist/cli/workflow-dry-run.d.ts +13 -0
  55. package/dist/cli/workflow-dry-run.js +144 -0
  56. package/dist/cli/workflow-dry-run.js.map +1 -0
  57. package/dist/config/field-catalog.d.ts +33 -0
  58. package/dist/config/field-catalog.js +172 -0
  59. package/dist/config/field-catalog.js.map +1 -0
  60. package/dist/config/filter-schema.d.ts +15 -0
  61. package/dist/config/filter-schema.js +110 -0
  62. package/dist/config/filter-schema.js.map +1 -0
  63. package/dist/config/index.d.ts +15 -0
  64. package/dist/config/index.js +16 -0
  65. package/dist/config/index.js.map +1 -0
  66. package/dist/config/issues.d.ts +27 -0
  67. package/dist/config/issues.js +37 -0
  68. package/dist/config/issues.js.map +1 -0
  69. package/dist/config/loader.d.ts +32 -0
  70. package/dist/config/loader.js +250 -0
  71. package/dist/config/loader.js.map +1 -0
  72. package/dist/config/manager.d.ts +153 -0
  73. package/dist/config/manager.js +276 -0
  74. package/dist/config/manager.js.map +1 -0
  75. package/dist/config/model.d.ts +334 -0
  76. package/dist/config/model.js +2 -0
  77. package/dist/config/model.js.map +1 -0
  78. package/dist/config/normalize.d.ts +20 -0
  79. package/dist/config/normalize.js +200 -0
  80. package/dist/config/normalize.js.map +1 -0
  81. package/dist/config/parameters.d.ts +37 -0
  82. package/dist/config/parameters.js +71 -0
  83. package/dist/config/parameters.js.map +1 -0
  84. package/dist/config/public.d.ts +6 -0
  85. package/dist/config/public.js +18 -0
  86. package/dist/config/public.js.map +1 -0
  87. package/dist/config/schemas.d.ts +538 -0
  88. package/dist/config/schemas.js +321 -0
  89. package/dist/config/schemas.js.map +1 -0
  90. package/dist/config/snapshot.d.ts +38 -0
  91. package/dist/config/snapshot.js +57 -0
  92. package/dist/config/snapshot.js.map +1 -0
  93. package/dist/config/validate.d.ts +33 -0
  94. package/dist/config/validate.js +346 -0
  95. package/dist/config/validate.js.map +1 -0
  96. package/dist/core/clock.d.ts +59 -0
  97. package/dist/core/clock.js +133 -0
  98. package/dist/core/clock.js.map +1 -0
  99. package/dist/core/duration.d.ts +19 -0
  100. package/dist/core/duration.js +93 -0
  101. package/dist/core/duration.js.map +1 -0
  102. package/dist/core/errors.d.ts +62 -0
  103. package/dist/core/errors.js +72 -0
  104. package/dist/core/errors.js.map +1 -0
  105. package/dist/core/ids.d.ts +57 -0
  106. package/dist/core/ids.js +88 -0
  107. package/dist/core/ids.js.map +1 -0
  108. package/dist/core/index.d.ts +13 -0
  109. package/dist/core/index.js +14 -0
  110. package/dist/core/index.js.map +1 -0
  111. package/dist/core/logger.d.ts +100 -0
  112. package/dist/core/logger.js +207 -0
  113. package/dist/core/logger.js.map +1 -0
  114. package/dist/core/paths.d.ts +84 -0
  115. package/dist/core/paths.js +138 -0
  116. package/dist/core/paths.js.map +1 -0
  117. package/dist/core/template.d.ts +136 -0
  118. package/dist/core/template.js +261 -0
  119. package/dist/core/template.js.map +1 -0
  120. package/dist/core/types.d.ts +544 -0
  121. package/dist/core/types.js +172 -0
  122. package/dist/core/types.js.map +1 -0
  123. package/dist/core/worker-protocol.d.ts +102 -0
  124. package/dist/core/worker-protocol.js +75 -0
  125. package/dist/core/worker-protocol.js.map +1 -0
  126. package/dist/executors/classify.d.ts +25 -0
  127. package/dist/executors/classify.js +54 -0
  128. package/dist/executors/classify.js.map +1 -0
  129. package/dist/executors/dispatcher.d.ts +35 -0
  130. package/dist/executors/dispatcher.js +68 -0
  131. package/dist/executors/dispatcher.js.map +1 -0
  132. package/dist/executors/env.d.ts +43 -0
  133. package/dist/executors/env.js +66 -0
  134. package/dist/executors/env.js.map +1 -0
  135. package/dist/executors/event-parser.d.ts +41 -0
  136. package/dist/executors/event-parser.js +106 -0
  137. package/dist/executors/event-parser.js.map +1 -0
  138. package/dist/executors/index.d.ts +12 -0
  139. package/dist/executors/index.js +13 -0
  140. package/dist/executors/index.js.map +1 -0
  141. package/dist/executors/log-capture.d.ts +57 -0
  142. package/dist/executors/log-capture.js +155 -0
  143. package/dist/executors/log-capture.js.map +1 -0
  144. package/dist/executors/process-identity.d.ts +124 -0
  145. package/dist/executors/process-identity.js +283 -0
  146. package/dist/executors/process-identity.js.map +1 -0
  147. package/dist/executors/shell-executor.d.ts +35 -0
  148. package/dist/executors/shell-executor.js +478 -0
  149. package/dist/executors/shell-executor.js.map +1 -0
  150. package/dist/executors/stream-redactor.d.ts +40 -0
  151. package/dist/executors/stream-redactor.js +89 -0
  152. package/dist/executors/stream-redactor.js.map +1 -0
  153. package/dist/executors/workspace.d.ts +10 -0
  154. package/dist/executors/workspace.js +47 -0
  155. package/dist/executors/workspace.js.map +1 -0
  156. package/dist/filters/evaluate.d.ts +77 -0
  157. package/dist/filters/evaluate.js +187 -0
  158. package/dist/filters/evaluate.js.map +1 -0
  159. package/dist/filters/fields.d.ts +35 -0
  160. package/dist/filters/fields.js +75 -0
  161. package/dist/filters/fields.js.map +1 -0
  162. package/dist/filters/index.d.ts +11 -0
  163. package/dist/filters/index.js +12 -0
  164. package/dist/filters/index.js.map +1 -0
  165. package/dist/filters/iso-date.d.ts +10 -0
  166. package/dist/filters/iso-date.js +42 -0
  167. package/dist/filters/iso-date.js.map +1 -0
  168. package/dist/filters/predicate.d.ts +24 -0
  169. package/dist/filters/predicate.js +178 -0
  170. package/dist/filters/predicate.js.map +1 -0
  171. package/dist/filters/priority.d.ts +51 -0
  172. package/dist/filters/priority.js +91 -0
  173. package/dist/filters/priority.js.map +1 -0
  174. package/dist/filters/pushdown.d.ts +12 -0
  175. package/dist/filters/pushdown.js +52 -0
  176. package/dist/filters/pushdown.js.map +1 -0
  177. package/dist/filters/shell-check.d.ts +110 -0
  178. package/dist/filters/shell-check.js +304 -0
  179. package/dist/filters/shell-check.js.map +1 -0
  180. package/dist/identities/github-app.d.ts +55 -0
  181. package/dist/identities/github-app.js +160 -0
  182. package/dist/identities/github-app.js.map +1 -0
  183. package/dist/identities/index.d.ts +6 -0
  184. package/dist/identities/index.js +7 -0
  185. package/dist/identities/index.js.map +1 -0
  186. package/dist/identities/jwt.d.ts +24 -0
  187. package/dist/identities/jwt.js +56 -0
  188. package/dist/identities/jwt.js.map +1 -0
  189. package/dist/lifecycle/context.d.ts +106 -0
  190. package/dist/lifecycle/context.js +170 -0
  191. package/dist/lifecycle/context.js.map +1 -0
  192. package/dist/lifecycle/engine.d.ts +185 -0
  193. package/dist/lifecycle/engine.js +551 -0
  194. package/dist/lifecycle/engine.js.map +1 -0
  195. package/dist/lifecycle/errors.d.ts +48 -0
  196. package/dist/lifecycle/errors.js +71 -0
  197. package/dist/lifecycle/errors.js.map +1 -0
  198. package/dist/lifecycle/index.d.ts +9 -0
  199. package/dist/lifecycle/index.js +9 -0
  200. package/dist/lifecycle/index.js.map +1 -0
  201. package/dist/lifecycle/performers.d.ts +66 -0
  202. package/dist/lifecycle/performers.js +179 -0
  203. package/dist/lifecycle/performers.js.map +1 -0
  204. package/dist/notifications/index.d.ts +6 -0
  205. package/dist/notifications/index.js +7 -0
  206. package/dist/notifications/index.js.map +1 -0
  207. package/dist/notifications/service.d.ts +115 -0
  208. package/dist/notifications/service.js +364 -0
  209. package/dist/notifications/service.js.map +1 -0
  210. package/dist/persistence/actions.d.ts +101 -0
  211. package/dist/persistence/actions.js +196 -0
  212. package/dist/persistence/actions.js.map +1 -0
  213. package/dist/persistence/blocks.d.ts +85 -0
  214. package/dist/persistence/blocks.js +140 -0
  215. package/dist/persistence/blocks.js.map +1 -0
  216. package/dist/persistence/cron-firings.d.ts +55 -0
  217. package/dist/persistence/cron-firings.js +70 -0
  218. package/dist/persistence/cron-firings.js.map +1 -0
  219. package/dist/persistence/database.d.ts +60 -0
  220. package/dist/persistence/database.js +124 -0
  221. package/dist/persistence/database.js.map +1 -0
  222. package/dist/persistence/errors.d.ts +39 -0
  223. package/dist/persistence/errors.js +67 -0
  224. package/dist/persistence/errors.js.map +1 -0
  225. package/dist/persistence/events.d.ts +72 -0
  226. package/dist/persistence/events.js +83 -0
  227. package/dist/persistence/events.js.map +1 -0
  228. package/dist/persistence/index.d.ts +18 -0
  229. package/dist/persistence/index.js +19 -0
  230. package/dist/persistence/index.js.map +1 -0
  231. package/dist/persistence/instance-lock.d.ts +26 -0
  232. package/dist/persistence/instance-lock.js +117 -0
  233. package/dist/persistence/instance-lock.js.map +1 -0
  234. package/dist/persistence/lock-keys.d.ts +16 -0
  235. package/dist/persistence/lock-keys.js +41 -0
  236. package/dist/persistence/lock-keys.js.map +1 -0
  237. package/dist/persistence/notifications.d.ts +113 -0
  238. package/dist/persistence/notifications.js +213 -0
  239. package/dist/persistence/notifications.js.map +1 -0
  240. package/dist/persistence/persistence.d.ts +46 -0
  241. package/dist/persistence/persistence.js +42 -0
  242. package/dist/persistence/persistence.js.map +1 -0
  243. package/dist/persistence/retention.d.ts +46 -0
  244. package/dist/persistence/retention.js +68 -0
  245. package/dist/persistence/retention.js.map +1 -0
  246. package/dist/persistence/runs.d.ts +215 -0
  247. package/dist/persistence/runs.js +417 -0
  248. package/dist/persistence/runs.js.map +1 -0
  249. package/dist/persistence/schema.d.ts +16 -0
  250. package/dist/persistence/schema.js +183 -0
  251. package/dist/persistence/schema.js.map +1 -0
  252. package/dist/persistence/sqlite.d.ts +49 -0
  253. package/dist/persistence/sqlite.js +131 -0
  254. package/dist/persistence/sqlite.js.map +1 -0
  255. package/dist/runs/controller.d.ts +307 -0
  256. package/dist/runs/controller.js +1078 -0
  257. package/dist/runs/controller.js.map +1 -0
  258. package/dist/runs/errors.d.ts +20 -0
  259. package/dist/runs/errors.js +26 -0
  260. package/dist/runs/errors.js.map +1 -0
  261. package/dist/runs/event-bus.d.ts +106 -0
  262. package/dist/runs/event-bus.js +105 -0
  263. package/dist/runs/event-bus.js.map +1 -0
  264. package/dist/runs/execution.d.ts +39 -0
  265. package/dist/runs/execution.js +146 -0
  266. package/dist/runs/execution.js.map +1 -0
  267. package/dist/runs/index.d.ts +12 -0
  268. package/dist/runs/index.js +13 -0
  269. package/dist/runs/index.js.map +1 -0
  270. package/dist/runs/retention.d.ts +64 -0
  271. package/dist/runs/retention.js +232 -0
  272. package/dist/runs/retention.js.map +1 -0
  273. package/dist/runs/workspace.d.ts +20 -0
  274. package/dist/runs/workspace.js +51 -0
  275. package/dist/runs/workspace.js.map +1 -0
  276. package/dist/scheduler/checks.d.ts +41 -0
  277. package/dist/scheduler/checks.js +111 -0
  278. package/dist/scheduler/checks.js.map +1 -0
  279. package/dist/scheduler/cron.d.ts +32 -0
  280. package/dist/scheduler/cron.js +83 -0
  281. package/dist/scheduler/cron.js.map +1 -0
  282. package/dist/scheduler/dry-run.d.ts +42 -0
  283. package/dist/scheduler/dry-run.js +167 -0
  284. package/dist/scheduler/dry-run.js.map +1 -0
  285. package/dist/scheduler/errors.d.ts +22 -0
  286. package/dist/scheduler/errors.js +22 -0
  287. package/dist/scheduler/errors.js.map +1 -0
  288. package/dist/scheduler/format.d.ts +3 -0
  289. package/dist/scheduler/format.js +73 -0
  290. package/dist/scheduler/format.js.map +1 -0
  291. package/dist/scheduler/index.d.ts +13 -0
  292. package/dist/scheduler/index.js +14 -0
  293. package/dist/scheduler/index.js.map +1 -0
  294. package/dist/scheduler/planner.d.ts +88 -0
  295. package/dist/scheduler/planner.js +255 -0
  296. package/dist/scheduler/planner.js.map +1 -0
  297. package/dist/scheduler/scheduler.d.ts +160 -0
  298. package/dist/scheduler/scheduler.js +680 -0
  299. package/dist/scheduler/scheduler.js.map +1 -0
  300. package/dist/scheduler/state.d.ts +26 -0
  301. package/dist/scheduler/state.js +25 -0
  302. package/dist/scheduler/state.js.map +1 -0
  303. package/dist/scheduler/types.d.ts +197 -0
  304. package/dist/scheduler/types.js +3 -0
  305. package/dist/scheduler/types.js.map +1 -0
  306. package/dist/service/index.d.ts +6 -0
  307. package/dist/service/index.js +7 -0
  308. package/dist/service/index.js.map +1 -0
  309. package/dist/service/install.d.ts +93 -0
  310. package/dist/service/install.js +250 -0
  311. package/dist/service/install.js.map +1 -0
  312. package/dist/service/unit.d.ts +50 -0
  313. package/dist/service/unit.js +171 -0
  314. package/dist/service/unit.js.map +1 -0
  315. package/dist/sources/github/adapter.d.ts +99 -0
  316. package/dist/sources/github/adapter.js +336 -0
  317. package/dist/sources/github/adapter.js.map +1 -0
  318. package/dist/sources/github/fields.d.ts +8 -0
  319. package/dist/sources/github/fields.js +72 -0
  320. package/dist/sources/github/fields.js.map +1 -0
  321. package/dist/sources/github/http.d.ts +100 -0
  322. package/dist/sources/github/http.js +288 -0
  323. package/dist/sources/github/http.js.map +1 -0
  324. package/dist/sources/github/index.d.ts +6 -0
  325. package/dist/sources/github/index.js +7 -0
  326. package/dist/sources/github/index.js.map +1 -0
  327. package/dist/sources/github/pushdown.d.ts +14 -0
  328. package/dist/sources/github/pushdown.js +47 -0
  329. package/dist/sources/github/pushdown.js.map +1 -0
  330. package/dist/sources/github/refs.d.ts +17 -0
  331. package/dist/sources/github/refs.js +61 -0
  332. package/dist/sources/github/refs.js.map +1 -0
  333. package/dist/sources/index.d.ts +6 -0
  334. package/dist/sources/index.js +7 -0
  335. package/dist/sources/index.js.map +1 -0
  336. package/dist/ui/index.d.ts +12 -0
  337. package/dist/ui/index.js +28 -0
  338. package/dist/ui/index.js.map +1 -0
  339. package/dist/ui/public/app.css +239 -0
  340. package/dist/ui/public/favicon.svg +1 -0
  341. package/dist/ui/public/index.html +27 -0
  342. package/dist/ui/public/js/client/api.js +68 -0
  343. package/dist/ui/public/js/client/app.js +146 -0
  344. package/dist/ui/public/js/client/dom.js +34 -0
  345. package/dist/ui/public/js/client/page.js +9 -0
  346. package/dist/ui/public/js/client/pages/actions.js +35 -0
  347. package/dist/ui/public/js/client/pages/blocked.js +58 -0
  348. package/dist/ui/public/js/client/pages/config.js +126 -0
  349. package/dist/ui/public/js/client/pages/dashboard.js +175 -0
  350. package/dist/ui/public/js/client/pages/run.js +367 -0
  351. package/dist/ui/public/js/client/pages/runs.js +72 -0
  352. package/dist/ui/public/js/client/pages/views.js +80 -0
  353. package/dist/ui/public/js/client/pages/workflows.js +291 -0
  354. package/dist/ui/public/js/client/types.js +1 -0
  355. package/dist/ui/public/js/client/ui.js +152 -0
  356. package/dist/ui/public/js/shared/dom.js +77 -0
  357. package/dist/ui/public/js/shared/events.js +88 -0
  358. package/dist/ui/public/js/shared/format.js +213 -0
  359. package/dist/ui/public/js/shared/logs.js +136 -0
  360. package/dist/ui/public/js/shared/params.js +90 -0
  361. package/dist/ui/public/js/shared/route.js +36 -0
  362. package/dist/ui/public/js/shared/sort.js +70 -0
  363. package/dist/ui/public/js/shared/sse.js +193 -0
  364. package/dist/ui/public/js/shared/throttle.js +65 -0
  365. package/dist/views/index.d.ts +6 -0
  366. package/dist/views/index.js +7 -0
  367. package/dist/views/index.js.map +1 -0
  368. package/dist/views/service.d.ts +131 -0
  369. package/dist/views/service.js +264 -0
  370. package/dist/views/service.js.map +1 -0
  371. package/docs/api.md +239 -0
  372. package/docs/configuration-reference.md +1785 -0
  373. package/docs/cron-semantics.md +96 -0
  374. package/docs/manual-tests/github-fixture/config.yaml +30 -0
  375. package/docs/manual-tests/github-fixture/helpers.sh +93 -0
  376. package/docs/manual-tests/github-fixture/identities.yaml +18 -0
  377. package/docs/manual-tests/github-fixture/scripts/test-worker.sh +100 -0
  378. package/docs/manual-tests/github-fixture/sources.yaml +13 -0
  379. package/docs/manual-tests/github-fixture/views/dependencies.yaml +17 -0
  380. package/docs/manual-tests/github-fixture/views/needs-maintainer.yaml +17 -0
  381. package/docs/manual-tests/github-fixture/workflows/coder-test.yaml +76 -0
  382. package/docs/manual-tests/github-fixture/workflows/commenter-test.yaml +63 -0
  383. package/docs/manual-tests/github-fixture/workflows/reviewer-test.yaml +60 -0
  384. package/docs/manual-tests/github.md +1308 -0
  385. package/docs/manual-tests/process-supervision.md +64 -0
  386. package/docs/manual-tests/systemd.md +68 -0
  387. package/docs/operations.md +271 -0
  388. package/docs/worker-protocol.md +145 -0
  389. package/examples/config/config.yaml +35 -0
  390. package/examples/config/identities.yaml +18 -0
  391. package/examples/config/sources.yaml +13 -0
  392. package/examples/config/views/needs-maintainer.yaml +15 -0
  393. package/examples/config/workflows/coder-project-a.yaml +86 -0
  394. package/examples/config/workflows/deploy.yaml +15 -0
  395. package/examples/config/workflows/nightly-maintenance.yaml +13 -0
  396. package/package.json +75 -4
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Error model of lifecycle actions: every failed attempt is persisted as an
3
+ * {@link ActionErrorInfo} (`action_attempts.error_json`, `run_actions.last_error_json`).
4
+ * The `retryable` flag decides whether further automatic attempts are made.
5
+ */
6
+ import { OrchestratorError, SourceError, TemplateError, errorMessage, redactValue, } from '../core/index.js';
7
+ /** Error raised by the lifecycle engine itself (action execution, invalid input). */
8
+ export class LifecycleError extends OrchestratorError {
9
+ retryable;
10
+ outcomeUnknown;
11
+ constructor(code, message, options = {}) {
12
+ super(`lifecycle_${code}`, message, {
13
+ ...(options.details !== undefined ? { details: options.details } : {}),
14
+ ...(options.cause !== undefined ? { cause: options.cause } : {}),
15
+ });
16
+ this.retryable = options.retryable ?? false;
17
+ this.outcomeUnknown = options.outcomeUnknown ?? false;
18
+ }
19
+ }
20
+ /** Thrown when a chain of the same run is already being executed in this process. */
21
+ export class LifecycleBusyError extends LifecycleError {
22
+ constructor(runId) {
23
+ super('busy', `Lifecycle actions of run ${runId} are already being executed`, { details: { runId } });
24
+ }
25
+ }
26
+ /**
27
+ * Convert any thrown value into an {@link ActionErrorInfo}.
28
+ *
29
+ * - `SourceError`: keeps `kind`, `retryable`, `outcomeUnknown`, `status`, `retryAfterMs`.
30
+ * - `TemplateError` and other `OrchestratorError`s: not retryable (configuration problem).
31
+ * - {@link LifecycleError}: its own flags.
32
+ * - Anything else (unexpected exception): retryable, so the configured attempts apply.
33
+ */
34
+ export function describeActionError(err, redact = (s) => s) {
35
+ const message = redact(errorMessage(err));
36
+ if (err instanceof SourceError) {
37
+ return compact({
38
+ code: err.code,
39
+ message,
40
+ retryable: err.retryable,
41
+ outcomeUnknown: err.outcomeUnknown || undefined,
42
+ kind: err.kind,
43
+ status: err.status,
44
+ retryAfterMs: err.retryAfterMs,
45
+ });
46
+ }
47
+ if (err instanceof LifecycleError) {
48
+ return compact({
49
+ code: err.code,
50
+ message,
51
+ retryable: err.retryable,
52
+ outcomeUnknown: err.outcomeUnknown || undefined,
53
+ details: err.details ? redactValue(err.details, redact) : undefined,
54
+ });
55
+ }
56
+ if (err instanceof TemplateError) {
57
+ return compact({ code: err.code, message, retryable: false, details: err.details });
58
+ }
59
+ if (err instanceof OrchestratorError) {
60
+ return compact({ code: err.code, message, retryable: false });
61
+ }
62
+ return { code: 'lifecycle_unexpected_error', message, retryable: true };
63
+ }
64
+ function compact(info) {
65
+ const out = {};
66
+ for (const [key, value] of Object.entries(info))
67
+ if (value !== undefined)
68
+ out[key] = value;
69
+ return out;
70
+ }
71
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lifecycle/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Component `lifecycle` – public API (WI-07): lifecycle hook chains with
3
+ * action journal, retries, manual retry/skip and recovery (Spec §10).
4
+ * Other components import from this module only.
5
+ */
6
+ export { LifecycleEngine, LIFECYCLE_ACTION_EVENT, LIFECYCLE_CHAIN_EVENT, DEFAULT_ACTION_RETRY, actionConfig, aggregateLifecycleStatus, stoppedAt, type HookContext, type HookResult, type LifecycleActionFailedPayload, type LifecycleEngineOptions, } from './engine.js';
7
+ export { LifecycleError, LifecycleBusyError, describeActionError, type ActionErrorInfo } from './errors.js';
8
+ export { parseRunSnapshot, resolveActionIdentity, hookTemplateScope, buildHookTemplateValues, buildShellActionContext, renderHookText, type ShellActionContext, } from './context.js';
9
+ export type { SourceResolver } from './performers.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Component `lifecycle` – public API (WI-07): lifecycle hook chains with
3
+ * action journal, retries, manual retry/skip and recovery (Spec §10).
4
+ * Other components import from this module only.
5
+ */
6
+ export { LifecycleEngine, LIFECYCLE_ACTION_EVENT, LIFECYCLE_CHAIN_EVENT, DEFAULT_ACTION_RETRY, actionConfig, aggregateLifecycleStatus, stoppedAt, } from './engine.js';
7
+ export { LifecycleError, LifecycleBusyError, describeActionError } from './errors.js';
8
+ export { parseRunSnapshot, resolveActionIdentity, hookTemplateScope, buildHookTemplateValues, buildShellActionContext, renderHookText, } from './context.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lifecycle/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,EACZ,wBAAwB,EACxB,SAAS,GAKV,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,EAAwB,MAAM,aAAa,CAAC;AAC5G,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,EACvB,uBAAuB,EACvB,cAAc,GAEf,MAAM,cAAc,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { type Executor, type HookName, type SourceAdapter, type SourceItem } from '../core/index.js';
2
+ import type { ShellActionConfig, SourceCommentActionConfig, SourceUpdateActionConfig, WorkflowSnapshot } from '../config/index.js';
3
+ import type { RunActionRecord, RunRecord } from '../persistence/index.js';
4
+ /** Resolves the adapter of a configured source ID (`undefined` if unknown). */
5
+ export type SourceResolver = (sourceId: string) => SourceAdapter | undefined;
6
+ /** Everything one attempt needs. */
7
+ export interface AttemptInput {
8
+ run: RunRecord;
9
+ snapshot: WorkflowSnapshot;
10
+ hook: HookName;
11
+ action: RunActionRecord;
12
+ /** Number of this attempt (1-based, over the whole journal history of the action). */
13
+ attempt: number;
14
+ /** True if the action had attempts before this one (→ reconcile comments first). */
15
+ hadPriorAttempts: boolean;
16
+ /** Fresh item handed in by the caller, if any. */
17
+ item: SourceItem | undefined;
18
+ signal: AbortSignal | undefined;
19
+ now: Date;
20
+ }
21
+ /** Successful attempt. */
22
+ export interface AttemptResult {
23
+ /** External reference (comment ID), persisted as `external_key`. */
24
+ externalKey: string | null;
25
+ /** Journal/event details, e.g. applied labels, comment URL, `reconciled: true`. */
26
+ details: Record<string, unknown>;
27
+ }
28
+ /** Dependencies of the performers. */
29
+ export interface PerformerDeps {
30
+ sources: SourceResolver;
31
+ executor: Executor;
32
+ dataDir: string;
33
+ shellCancelGraceMs: number;
34
+ maxShellLogBytes: number;
35
+ }
36
+ interface SourceTarget {
37
+ adapter: SourceAdapter;
38
+ item: SourceItem;
39
+ }
40
+ /** Adapter + item for source actions. Uses the caller's item if it matches the run, else re-reads it. */
41
+ export declare function resolveSourceTarget(deps: Pick<PerformerDeps, 'sources'>, run: RunRecord, snapshot: WorkflowSnapshot, given: SourceItem | undefined): Promise<SourceTarget>;
42
+ /** `source.update`: idempotent label change via the adapter (re-reads the item before writing). */
43
+ export declare function performSourceUpdate(deps: PerformerDeps, input: AttemptInput, config: SourceUpdateActionConfig): Promise<AttemptResult>;
44
+ /**
45
+ * `source.comment`: renders the message, then posts it with the marker
46
+ * `commentMarker(runId, actionId)`. If the action had attempts before
47
+ * (failed, outcome unknown, interrupted), an existing comment with the marker
48
+ * is searched first and, when found, taken as success instead of posting a duplicate.
49
+ */
50
+ export declare function performSourceComment(deps: PerformerDeps, input: AttemptInput, config: SourceCommentActionConfig): Promise<AttemptResult>;
51
+ /**
52
+ * Neutralize HTML comment openers in a comment body (`<!--` → `&lt;!--`).
53
+ * Rendered values such as `{result.message}` are untrusted worker output and
54
+ * must not be able to smuggle idempotency markers (`<!-- adispatchd:… -->`) or
55
+ * hidden content into the comment; the adapter appends the only real marker.
56
+ */
57
+ export declare function neutralizeHtmlComments(body: string): string;
58
+ /**
59
+ * `shell`: runs the command via the executor with the minimal environment
60
+ * (no controller env), the rendered `env`/`args`, a JSON context file with
61
+ * run + result (`ADISPATCH_CONTEXT_FILE`) and the action timeout. Exit code 0 =
62
+ * success; anything else (exit ≠ 0, signal, timeout, start error) fails the attempt.
63
+ * Working directory: the run workspace if it exists, else a per-attempt directory.
64
+ */
65
+ export declare function performShell(deps: PerformerDeps, input: AttemptInput, config: ShellActionConfig): Promise<AttemptResult>;
66
+ export {};
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Execution of single lifecycle action attempts (Spec §10):
3
+ * `source.update`, `source.comment` (with idempotency marker) and `shell`.
4
+ * One call = one attempt; retry policy and journal handling live in the engine.
5
+ */
6
+ import { stat } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { commentMarker, dataPaths, } from '../core/index.js';
9
+ import { buildHookTemplateValues, buildShellActionContext, hookTemplateScope, isSourceWorkflow, itemFromRun, renderHookText, resolveActionIdentity, } from './context.js';
10
+ import { LifecycleError } from './errors.js';
11
+ /** Adapter + item for source actions. Uses the caller's item if it matches the run, else re-reads it. */
12
+ export async function resolveSourceTarget(deps, run, snapshot, given) {
13
+ if (!isSourceWorkflow(snapshot) || !snapshot.source) {
14
+ throw new LifecycleError('no_source', `Workflow "${snapshot.workflow.id}" has no source; source actions are not possible`);
15
+ }
16
+ const adapter = deps.sources(snapshot.source.id);
17
+ if (!adapter) {
18
+ throw new LifecycleError('source_unavailable', `Source "${snapshot.source.id}" is not available`, {
19
+ details: { sourceId: snapshot.source.id },
20
+ });
21
+ }
22
+ const derived = itemFromRun(run);
23
+ if (!derived)
24
+ throw new LifecycleError('no_item', `Run ${run.id} is not bound to a source item`);
25
+ if (given && given.key === derived.key)
26
+ return { adapter, item: given };
27
+ const item = await adapter.getItem(derived.itemId);
28
+ if (!item) {
29
+ throw new LifecycleError('item_not_found', `Item ${derived.key} no longer exists or is not visible`, {
30
+ details: { itemKey: derived.key },
31
+ });
32
+ }
33
+ return { adapter, item };
34
+ }
35
+ function requireIdentity(input, config) {
36
+ const identity = resolveActionIdentity(config, input.snapshot);
37
+ if (!identity)
38
+ throw new LifecycleError('no_identity', `No identity for action "${config.id}" (action → workflow → source)`);
39
+ return identity;
40
+ }
41
+ /** `source.update`: idempotent label change via the adapter (re-reads the item before writing). */
42
+ export async function performSourceUpdate(deps, input, config) {
43
+ const identity = requireIdentity(input, config);
44
+ const { adapter, item } = await resolveSourceTarget(deps, input.run, input.snapshot, input.item);
45
+ const result = await adapter.applyUpdate(item, { labels: { add: config.labels.add, remove: config.labels.remove } }, identity);
46
+ return { externalKey: null, details: { changed: result.changed, ...(result.applied ? { applied: result.applied } : {}) } };
47
+ }
48
+ /**
49
+ * `source.comment`: renders the message, then posts it with the marker
50
+ * `commentMarker(runId, actionId)`. If the action had attempts before
51
+ * (failed, outcome unknown, interrupted), an existing comment with the marker
52
+ * is searched first and, when found, taken as success instead of posting a duplicate.
53
+ */
54
+ export async function performSourceComment(deps, input, config) {
55
+ const identity = requireIdentity(input, config);
56
+ // Render before any API call: template errors are permanent and must not post anything.
57
+ const scope = hookTemplateScope(input.snapshot, input.hook);
58
+ const probeValues = buildHookTemplateValues({ ...input, item: input.item ?? itemFromRun(input.run) });
59
+ renderHookText(config.message, probeValues, scope);
60
+ const { adapter, item } = await resolveSourceTarget(deps, input.run, input.snapshot, input.item);
61
+ const marker = commentMarker(input.run.id, input.action.id);
62
+ if (input.hadPriorAttempts) {
63
+ const existing = await adapter.findCommentByMarker(item, marker, identity);
64
+ if (existing) {
65
+ return {
66
+ externalKey: existing.id,
67
+ details: { reconciled: true, marker, ...(existing.url ? { url: existing.url } : {}) },
68
+ };
69
+ }
70
+ }
71
+ const values = buildHookTemplateValues({ ...input, item, ...(adapter.repository ? { repository: adapter.repository } : {}) });
72
+ const body = neutralizeHtmlComments(renderHookText(config.message, values, scope));
73
+ const ref = await adapter.postComment(item, body, marker, identity);
74
+ return { externalKey: ref.id, details: { marker, ...(ref.url ? { url: ref.url } : {}) } };
75
+ }
76
+ /**
77
+ * Neutralize HTML comment openers in a comment body (`<!--` → `&lt;!--`).
78
+ * Rendered values such as `{result.message}` are untrusted worker output and
79
+ * must not be able to smuggle idempotency markers (`<!-- adispatchd:… -->`) or
80
+ * hidden content into the comment; the adapter appends the only real marker.
81
+ */
82
+ export function neutralizeHtmlComments(body) {
83
+ return body.replace(/<!--/g, '&lt;!--');
84
+ }
85
+ async function isDirectory(p) {
86
+ if (!p)
87
+ return false;
88
+ try {
89
+ return (await stat(p)).isDirectory();
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ /**
96
+ * `shell`: runs the command via the executor with the minimal environment
97
+ * (no controller env), the rendered `env`/`args`, a JSON context file with
98
+ * run + result (`ADISPATCH_CONTEXT_FILE`) and the action timeout. Exit code 0 =
99
+ * success; anything else (exit ≠ 0, signal, timeout, start error) fails the attempt.
100
+ * Working directory: the run workspace if it exists, else a per-attempt directory.
101
+ */
102
+ export async function performShell(deps, input, config) {
103
+ const { run, snapshot, hook, action } = input;
104
+ const item = input.item ?? itemFromRun(run);
105
+ const scope = hookTemplateScope(snapshot, hook);
106
+ const values = buildHookTemplateValues({ run, snapshot, hook, item, now: input.now });
107
+ const args = config.args.map((a) => renderHookText(a, values, scope));
108
+ const env = {};
109
+ for (const [name, template] of Object.entries(config.env))
110
+ env[name] = renderHookText(template, values, scope);
111
+ const paths = dataPaths(deps.dataDir).run(run.id);
112
+ const attemptDir = path.join(paths.runDir, 'actions', String(action.id), `attempt-${input.attempt}`);
113
+ const logDir = path.join(paths.logDir, 'actions', String(action.id), `attempt-${input.attempt}`);
114
+ const files = {
115
+ contextFile: path.join(attemptDir, 'context.json'),
116
+ resultFile: path.join(attemptDir, 'result.json'),
117
+ stdoutLog: path.join(logDir, 'stdout.log'),
118
+ stderrLog: path.join(logDir, 'stderr.log'),
119
+ };
120
+ const useWorkspace = await isDirectory(run.workspacePath);
121
+ const workingDirectory = useWorkspace && run.workspacePath ? run.workspacePath : path.join(attemptDir, 'cwd');
122
+ const context = buildShellActionContext({
123
+ run,
124
+ snapshot,
125
+ hook,
126
+ item,
127
+ action: { id: action.id, name: action.name, ordinal: action.ordinal, attempt: input.attempt },
128
+ });
129
+ const proc = await deps.executor.start({
130
+ runId: run.id,
131
+ workflowId: snapshot.workflow.id,
132
+ command: config.command,
133
+ args,
134
+ workingDirectory,
135
+ createDirectory: !useWorkspace,
136
+ env,
137
+ secrets: {},
138
+ context,
139
+ files,
140
+ timeouts: { maxDurationMs: config.timeoutMs },
141
+ cancelGraceMs: deps.shellCancelGraceMs,
142
+ maxLogBytes: deps.maxShellLogBytes,
143
+ });
144
+ const onAbort = () => {
145
+ proc.cancel().catch(() => undefined);
146
+ };
147
+ input.signal?.addEventListener('abort', onAbort, { once: true });
148
+ if (input.signal?.aborted)
149
+ onAbort();
150
+ const outcome = await proc.wait().finally(() => input.signal?.removeEventListener('abort', onAbort));
151
+ const logs = { stdout: files.stdoutLog, stderr: files.stderrLog };
152
+ const base = { exitCode: outcome.exitCode, signal: outcome.signal, logs };
153
+ if (outcome.spawnError) {
154
+ throw new LifecycleError('shell_start_error', `Shell action could not be started: ${outcome.spawnError.message}`, {
155
+ retryable: true,
156
+ details: { ...base, spawnError: outcome.spawnError },
157
+ });
158
+ }
159
+ if (outcome.cancelled) {
160
+ throw new LifecycleError('shell_cancelled', 'Shell action was cancelled', { retryable: true, details: base });
161
+ }
162
+ if (outcome.timedOut) {
163
+ throw new LifecycleError('shell_timeout', `Shell action exceeded its timeout (${config.timeoutMs} ms)`, {
164
+ retryable: true,
165
+ details: base,
166
+ });
167
+ }
168
+ if (outcome.signal) {
169
+ throw new LifecycleError('shell_signal', `Shell action was terminated by ${outcome.signal}`, { retryable: true, details: base });
170
+ }
171
+ if (outcome.exitCode !== 0) {
172
+ throw new LifecycleError('shell_exit', `Shell action exited with code ${String(outcome.exitCode)}`, {
173
+ retryable: true,
174
+ details: base,
175
+ });
176
+ }
177
+ return { externalKey: null, details: { exitCode: 0, logs } };
178
+ }
179
+ //# sourceMappingURL=performers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performers.js","sourceRoot":"","sources":["../../src/lifecycle/performers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Component `notifications` – public API (WI-10): persisted central
3
+ * orchestrator notifications with asynchronous webhook delivery (Spec §18).
4
+ * Other components import from this module only.
5
+ */
6
+ export { NotificationService, DEFAULT_NOTIFICATION_RETRY_DELAYS_MS, DEFAULT_NOTIFICATION_TIMEOUT_MS, summarize, webhookBody, type NotificationServiceOptions, type NotificationView, } from './service.js';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Component `notifications` – public API (WI-10): persisted central
3
+ * orchestrator notifications with asynchronous webhook delivery (Spec §18).
4
+ * Other components import from this module only.
5
+ */
6
+ export { NotificationService, DEFAULT_NOTIFICATION_RETRY_DELAYS_MS, DEFAULT_NOTIFICATION_TIMEOUT_MS, summarize, webhookBody, } from './service.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/notifications/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,mBAAmB,EACnB,oCAAoC,EACpC,+BAA+B,EAC/B,SAAS,EACT,WAAW,GAGZ,MAAM,cAAc,CAAC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Central orchestrator notifications (Spec §18): `retry_block_created`,
3
+ * `lifecycle_action_failed`, `recovery_failed`.
4
+ *
5
+ * Flow: {@link NotificationService.notify} persists the notification plus one
6
+ * delivery row per subscribed channel synchronously (a few SQLite writes,
7
+ * never network I/O) and wakes the dispatcher; the dispatcher delivers due
8
+ * deliveries asynchronously as JSON `POST` to the webhook channels with a
9
+ * timeout, retries with backoff and stores every attempt. Delivery problems
10
+ * are logged and persisted, never propagated: orchestration must not hang or
11
+ * fail because a webhook is down.
12
+ *
13
+ * Secrets: webhook URLs (literal or from env) and header values are
14
+ * registered with the shared {@link SecretRedactor}; error texts never
15
+ * contain the URL and are redacted before they are stored or logged.
16
+ */
17
+ import { SecretRedactor, type Clock, type FetchLike, type Logger, type NotificationEventType, type Notifier, type Timers } from '../core/index.js';
18
+ import type { NotificationsConfig } from '../config/index.js';
19
+ import type { NotificationDeliveryRecord, NotificationRecord, Repositories } from '../persistence/index.js';
20
+ /** Default backoff between delivery attempts (attempt n failed → wait `delays[n-1]`). */
21
+ export declare const DEFAULT_NOTIFICATION_RETRY_DELAYS_MS: readonly number[];
22
+ /** Default request timeout of one webhook call. */
23
+ export declare const DEFAULT_NOTIFICATION_TIMEOUT_MS = 10000;
24
+ /** Notification with its channel deliveries (API / SSE shape). */
25
+ export interface NotificationView extends NotificationRecord {
26
+ deliveries: NotificationDeliveryRecord[];
27
+ }
28
+ /** Options of {@link NotificationService}. */
29
+ export interface NotificationServiceOptions {
30
+ repositories: Pick<Repositories, 'notifications'>;
31
+ /** Live notification settings (`config.global.notifications`). */
32
+ settings: () => NotificationsConfig;
33
+ /** Environment for `url_env` / `headers_env` (default `process.env`). */
34
+ env?: Readonly<Record<string, string | undefined>>;
35
+ /** HTTP client (default global `fetch`). */
36
+ fetch?: FetchLike;
37
+ clock?: Clock;
38
+ timers?: Timers;
39
+ logger?: Logger;
40
+ /** Shared redactor (URLs/header values are registered here). */
41
+ redactor?: SecretRedactor;
42
+ /** Request timeout per attempt (default 10 s). */
43
+ timeoutMs?: number;
44
+ /** Backoff delays; max attempts = `delays.length + 1` (default 5 s, 30 s, 2 min, 10 min, 30 min). */
45
+ retryDelaysMs?: readonly number[];
46
+ /** Called (after commit) whenever a notification or one of its deliveries changed (SSE). */
47
+ onChange?: (notification: NotificationView) => void;
48
+ }
49
+ /** See module docs. Implements the core {@link Notifier} contract. */
50
+ export declare class NotificationService implements Notifier {
51
+ private readonly options;
52
+ private readonly repos;
53
+ private readonly clock;
54
+ private readonly timers;
55
+ private readonly logger;
56
+ private readonly redactor;
57
+ private readonly fetchFn;
58
+ private readonly env;
59
+ private readonly timeoutMs;
60
+ private readonly delays;
61
+ private started;
62
+ private stopped;
63
+ private timer;
64
+ private running;
65
+ private again;
66
+ private readonly inflight;
67
+ constructor(options: NotificationServiceOptions);
68
+ /** Max attempts per delivery. */
69
+ get maxAttempts(): number;
70
+ /**
71
+ * Persist a notification (if its event type is enabled) and schedule the
72
+ * delivery. Synchronous, never throws.
73
+ */
74
+ notify(type: NotificationEventType, payload: Record<string, unknown>): void;
75
+ /** Start dispatching (also delivers notifications left pending by a previous process). */
76
+ start(): void;
77
+ /**
78
+ * Stop the dispatcher: no new attempts; in-flight requests get `graceMs`
79
+ * to finish, then they are aborted (they stay `pending` and are retried by
80
+ * the next process).
81
+ */
82
+ stop(graceMs?: number): Promise<void>;
83
+ /** Resolves when no dispatcher pass is running (tests). */
84
+ whenIdle(): Promise<void>;
85
+ /** Notifications (newest first) with their deliveries. */
86
+ list(options?: {
87
+ state?: NotificationRecord['deliveryState'];
88
+ limit?: number;
89
+ offset?: number;
90
+ }): NotificationView[];
91
+ /** One notification with deliveries, or `null`. */
92
+ get(id: number): NotificationView | null;
93
+ /**
94
+ * Manually re-queue all `failed` deliveries of a notification. Returns the
95
+ * updated notification, or `null` if it does not exist.
96
+ */
97
+ retry(id: number): {
98
+ notification: NotificationView;
99
+ requeued: number;
100
+ } | null;
101
+ /** Wake the dispatcher (no-op before `start()` / after `stop()`). */
102
+ kick(): void;
103
+ private armTimer;
104
+ private dispatch;
105
+ private deliver;
106
+ private post;
107
+ private registerChannelSecrets;
108
+ /** Re-register channel secrets after a configuration reload. */
109
+ refreshSecrets(): void;
110
+ private emit;
111
+ }
112
+ /** JSON body posted to webhook channels (Slack-compatible `text` + structured data). */
113
+ export declare function webhookBody(n: NotificationRecord): Record<string, unknown>;
114
+ /** One-line human-readable summary of a notification. */
115
+ export declare function summarize(type: NotificationEventType, p: Record<string, unknown>): string;