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,41 @@
1
+ /**
2
+ * Lock-key semantics for the active-run uniqueness constraint
3
+ * `(workflow_id, lock_key)` (Spec §7, §11, §12).
4
+ *
5
+ * - Item-bound runs (trigger `source`, or `manual` with an item):
6
+ * `item:<sourceItemKey>` → at most one active run per workflow and item,
7
+ * also for manual starts (Spec §7: manual starts never bypass the lock).
8
+ * - Cron runs without item: `cron:<scheduled_for ISO>` → at most one run per
9
+ * cron slot (in addition to the `cron_firings` unique key). Overlap of
10
+ * different slots of the same workflow is governed by `max_concurrent`.
11
+ * - Manual runs without item: `manual:<runId>` → unique per run, never conflicts.
12
+ *
13
+ * Prefixes keep the namespaces disjoint (a source could be named `cron`).
14
+ * The retry-block / failure-counter scope is separate: the source item key,
15
+ * or the whole workflow for sourceless runs (see {@link blockScopeKey}).
16
+ */
17
+ import { iso } from './sqlite.js';
18
+ /** Compute the lock key for a run (see module docs). */
19
+ export function lockKeyFor(input) {
20
+ if (input.sourceItemKey)
21
+ return `item:${input.sourceItemKey}`;
22
+ switch (input.triggerType) {
23
+ case 'source':
24
+ throw new TypeError('Source-triggered runs require a sourceItemKey');
25
+ case 'schedule':
26
+ if (input.scheduledFor === undefined || input.scheduledFor === null) {
27
+ throw new TypeError('Scheduled runs without item require scheduledFor');
28
+ }
29
+ return `cron:${iso(input.scheduledFor)}`;
30
+ case 'manual':
31
+ return `manual:${input.runId}`;
32
+ }
33
+ }
34
+ /**
35
+ * Internal non-null scope key for failure counters/blocks: the item key, or
36
+ * `''` for workflow scope (sourceless runs).
37
+ */
38
+ export function blockScopeKey(itemKey) {
39
+ return itemKey ?? '';
40
+ }
41
+ //# sourceMappingURL=lock-keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock-keys.js","sourceRoot":"","sources":["../../src/persistence/lock-keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAYlC,wDAAwD;AACxD,MAAM,UAAU,UAAU,CAAC,KAAmB;IAC5C,IAAI,KAAK,CAAC,aAAa;QAAE,OAAO,QAAQ,KAAK,CAAC,aAAa,EAAE,CAAC;IAC9D,QAAQ,KAAK,CAAC,WAAW,EAAE,CAAC;QAC1B,KAAK,QAAQ;YACX,MAAM,IAAI,SAAS,CAAC,+CAA+C,CAAC,CAAC;QACvE,KAAK,UAAU;YACb,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,IAAI,KAAK,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;gBACpE,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;YAC1E,CAAC;YACD,OAAO,QAAQ,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,KAAK,QAAQ;YACX,OAAO,UAAU,KAAK,CAAC,KAAK,EAAE,CAAC;IACnC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAAkC;IAC9D,OAAO,OAAO,IAAI,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Central system notifications (Spec §18): persisted first, delivered
3
+ * asynchronously by WI-10; delivery attempts are stored.
4
+ *
5
+ * Since schema v2 every notification may have one delivery row per channel
6
+ * (`notification_deliveries`). For such notifications `deliveryState`,
7
+ * `attempts` and `lastError` are the aggregate over its deliveries:
8
+ * `pending` while any delivery is pending, else `failed` if any failed,
9
+ * else `delivered` (also when no channel was subscribed).
10
+ */
11
+ import type { DatabaseSync } from 'node:sqlite';
12
+ import type { Clock, NotificationEventType } from '../core/index.js';
13
+ /** Delivery state of a notification. */
14
+ export type DeliveryState = 'pending' | 'delivered' | 'failed';
15
+ /** A persisted notification. Payload must not contain secrets. */
16
+ export interface NotificationRecord {
17
+ id: number;
18
+ eventType: NotificationEventType;
19
+ payload: Record<string, unknown>;
20
+ deliveryState: DeliveryState;
21
+ attempts: number;
22
+ lastError: string | null;
23
+ nextAttemptAt: string | null;
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ deliveredAt: string | null;
27
+ }
28
+ /** Delivery of one notification to one channel (schema v2). */
29
+ export interface NotificationDeliveryRecord {
30
+ id: number;
31
+ notificationId: number;
32
+ channelId: string;
33
+ state: DeliveryState;
34
+ attempts: number;
35
+ /** Redacted error text of the last failed attempt. */
36
+ lastError: string | null;
37
+ /** HTTP status of the last attempt, if any. */
38
+ lastStatus: number | null;
39
+ nextAttemptAt: string | null;
40
+ deliveredAt: string | null;
41
+ createdAt: string;
42
+ updatedAt: string;
43
+ }
44
+ /** Outcome of one delivery attempt. */
45
+ export type DeliveryAttemptOutcome = {
46
+ success: true;
47
+ status?: number | null;
48
+ } | {
49
+ success: false;
50
+ error: string;
51
+ status?: number | null;
52
+ nextAttemptAt: Date | string | null;
53
+ };
54
+ /** Repository for `notifications`. */
55
+ export declare class NotificationRepository {
56
+ private readonly db;
57
+ private readonly clock;
58
+ constructor(db: DatabaseSync, clock: Clock);
59
+ /** Persist a new notification in state `pending` (due immediately). */
60
+ create(eventType: NotificationEventType, payload: Record<string, unknown>): NotificationRecord;
61
+ /** Notification by ID or `null`. */
62
+ get(id: number): NotificationRecord | null;
63
+ /** Notifications, newest first, optionally filtered by state. */
64
+ list(options?: {
65
+ state?: DeliveryState;
66
+ limit?: number;
67
+ offset?: number;
68
+ }): NotificationRecord[];
69
+ /** Pending notifications due at `now` (default: clock), oldest first. */
70
+ listDue(now?: Date, limit?: number): NotificationRecord[];
71
+ /**
72
+ * Record one delivery attempt. Success → `delivered`. Failure → stays
73
+ * `pending` with `nextAttemptAt`, or `failed` when `nextAttemptAt` is `null`
74
+ * (retries exhausted). `error` must already be redacted.
75
+ */
76
+ recordAttempt(id: number, outcome: {
77
+ success: true;
78
+ } | {
79
+ success: false;
80
+ error: string;
81
+ nextAttemptAt: Date | string | null;
82
+ }): NotificationRecord;
83
+ /**
84
+ * Persist a notification together with one `pending` delivery per channel
85
+ * (one transaction). Without channels the notification is stored as
86
+ * `delivered` (nothing to deliver).
87
+ */
88
+ createWithDeliveries(eventType: NotificationEventType, payload: Record<string, unknown>, channelIds: readonly string[]): {
89
+ notification: NotificationRecord;
90
+ deliveries: NotificationDeliveryRecord[];
91
+ };
92
+ /** Deliveries of a notification (by channel ID). */
93
+ listDeliveries(notificationId: number): NotificationDeliveryRecord[];
94
+ /** Delivery by ID or `null`. */
95
+ getDelivery(id: number): NotificationDeliveryRecord | null;
96
+ /** Pending deliveries due at `now`, oldest first. */
97
+ listDueDeliveries(now: Date, limit?: number): NotificationDeliveryRecord[];
98
+ /** Earliest `next_attempt_at` of all pending deliveries, or `null`. */
99
+ nextDeliveryDue(): string | null;
100
+ /** Number of notifications per aggregate state. */
101
+ countByState(): Record<DeliveryState, number>;
102
+ /**
103
+ * Record one attempt of a delivery (success → `delivered`; failure → stays
104
+ * `pending` with `nextAttemptAt`, or `failed` when it is `null`) and update
105
+ * the notification's aggregate. `error` must already be redacted.
106
+ */
107
+ recordDeliveryAttempt(deliveryId: number, outcome: DeliveryAttemptOutcome): NotificationDeliveryRecord;
108
+ /** Re-queue a `failed` delivery (manual retry); returns `null` if it is not `failed`. */
109
+ requeueDelivery(deliveryId: number): NotificationDeliveryRecord | null;
110
+ /** Recompute the aggregate columns of a notification from its deliveries. */
111
+ private refreshAggregate;
112
+ private require;
113
+ }
@@ -0,0 +1,213 @@
1
+ import { NotFoundError } from './errors.js';
2
+ import { all, get, iso, run, str, transaction } from './sqlite.js';
3
+ /** Repository for `notifications`. */
4
+ export class NotificationRepository {
5
+ db;
6
+ clock;
7
+ constructor(db, clock) {
8
+ this.db = db;
9
+ this.clock = clock;
10
+ }
11
+ /** Persist a new notification in state `pending` (due immediately). */
12
+ create(eventType, payload) {
13
+ const now = this.clock.now().toISOString();
14
+ const res = run(this.db, `INSERT INTO notifications (event_type, payload_json, delivery_state, attempts, next_attempt_at, created_at, updated_at)
15
+ VALUES (?, ?, 'pending', 0, ?, ?, ?)`, [eventType, JSON.stringify(payload), now, now, now]);
16
+ return this.require(res.lastInsertRowid);
17
+ }
18
+ /** Notification by ID or `null`. */
19
+ get(id) {
20
+ const row = get(this.db, 'SELECT * FROM notifications WHERE id = ?', [id]);
21
+ return row ? toNotification(row) : null;
22
+ }
23
+ /** Notifications, newest first, optionally filtered by state. */
24
+ list(options = {}) {
25
+ const params = [];
26
+ let sql = 'SELECT * FROM notifications';
27
+ if (options.state !== undefined) {
28
+ sql += ' WHERE delivery_state = ?';
29
+ params.push(options.state);
30
+ }
31
+ sql += ' ORDER BY created_at DESC, id DESC LIMIT ? OFFSET ?';
32
+ params.push(options.limit ?? 100, options.offset ?? 0);
33
+ return all(this.db, sql, params).map(toNotification);
34
+ }
35
+ /** Pending notifications due at `now` (default: clock), oldest first. */
36
+ listDue(now, limit = 100) {
37
+ return all(this.db, `SELECT * FROM notifications WHERE delivery_state = 'pending' AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
38
+ ORDER BY created_at ASC, id ASC LIMIT ?`, [iso(now ?? this.clock.now()), limit]).map(toNotification);
39
+ }
40
+ /**
41
+ * Record one delivery attempt. Success → `delivered`. Failure → stays
42
+ * `pending` with `nextAttemptAt`, or `failed` when `nextAttemptAt` is `null`
43
+ * (retries exhausted). `error` must already be redacted.
44
+ */
45
+ recordAttempt(id, outcome) {
46
+ return transaction(this.db, () => {
47
+ this.require(id);
48
+ const now = this.clock.now().toISOString();
49
+ if (outcome.success) {
50
+ run(this.db, `UPDATE notifications SET delivery_state = 'delivered', attempts = attempts + 1, delivered_at = ?, next_attempt_at = NULL,
51
+ updated_at = ? WHERE id = ?`, [now, now, id]);
52
+ }
53
+ else {
54
+ const next = outcome.nextAttemptAt === null ? null : iso(outcome.nextAttemptAt);
55
+ run(this.db, `UPDATE notifications SET delivery_state = ?, attempts = attempts + 1, last_error = ?, next_attempt_at = ?, updated_at = ?
56
+ WHERE id = ?`, [next === null ? 'failed' : 'pending', outcome.error, next, now, id]);
57
+ }
58
+ return this.require(id);
59
+ });
60
+ }
61
+ /**
62
+ * Persist a notification together with one `pending` delivery per channel
63
+ * (one transaction). Without channels the notification is stored as
64
+ * `delivered` (nothing to deliver).
65
+ */
66
+ createWithDeliveries(eventType, payload, channelIds) {
67
+ return transaction(this.db, () => {
68
+ const created = this.create(eventType, payload);
69
+ const now = this.clock.now().toISOString();
70
+ for (const channelId of new Set(channelIds)) {
71
+ run(this.db, `INSERT INTO notification_deliveries (notification_id, channel_id, state, attempts, next_attempt_at, created_at, updated_at)
72
+ VALUES (?, ?, 'pending', 0, ?, ?, ?)`, [created.id, channelId, now, now, now]);
73
+ }
74
+ this.refreshAggregate(created.id);
75
+ return { notification: this.require(created.id), deliveries: this.listDeliveries(created.id) };
76
+ });
77
+ }
78
+ /** Deliveries of a notification (by channel ID). */
79
+ listDeliveries(notificationId) {
80
+ return all(this.db, 'SELECT * FROM notification_deliveries WHERE notification_id = ? ORDER BY channel_id ASC, id ASC', [
81
+ notificationId,
82
+ ]).map(toDelivery);
83
+ }
84
+ /** Delivery by ID or `null`. */
85
+ getDelivery(id) {
86
+ const row = get(this.db, 'SELECT * FROM notification_deliveries WHERE id = ?', [id]);
87
+ return row ? toDelivery(row) : null;
88
+ }
89
+ /** Pending deliveries due at `now`, oldest first. */
90
+ listDueDeliveries(now, limit = 100) {
91
+ return all(this.db, `SELECT * FROM notification_deliveries WHERE state = 'pending' AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
92
+ ORDER BY next_attempt_at ASC, id ASC LIMIT ?`, [iso(now), limit]).map(toDelivery);
93
+ }
94
+ /** Earliest `next_attempt_at` of all pending deliveries, or `null`. */
95
+ nextDeliveryDue() {
96
+ const row = get(this.db, `SELECT MIN(next_attempt_at) AS t FROM notification_deliveries WHERE state = 'pending'`);
97
+ return str(row?.t);
98
+ }
99
+ /** Number of notifications per aggregate state. */
100
+ countByState() {
101
+ const counts = { pending: 0, delivered: 0, failed: 0 };
102
+ for (const row of all(this.db, 'SELECT delivery_state AS s, COUNT(*) AS n FROM notifications GROUP BY delivery_state')) {
103
+ counts[row.s] = Number(row.n);
104
+ }
105
+ return counts;
106
+ }
107
+ /**
108
+ * Record one attempt of a delivery (success → `delivered`; failure → stays
109
+ * `pending` with `nextAttemptAt`, or `failed` when it is `null`) and update
110
+ * the notification's aggregate. `error` must already be redacted.
111
+ */
112
+ recordDeliveryAttempt(deliveryId, outcome) {
113
+ return transaction(this.db, () => {
114
+ const delivery = this.getDelivery(deliveryId);
115
+ if (!delivery)
116
+ throw new NotFoundError('notification_delivery', deliveryId);
117
+ const now = this.clock.now().toISOString();
118
+ const status = outcome.status ?? null;
119
+ if (outcome.success) {
120
+ run(this.db, `UPDATE notification_deliveries SET state = 'delivered', attempts = attempts + 1, last_status = ?, delivered_at = ?,
121
+ next_attempt_at = NULL, updated_at = ? WHERE id = ?`, [status, now, now, deliveryId]);
122
+ }
123
+ else {
124
+ const next = outcome.nextAttemptAt === null ? null : iso(outcome.nextAttemptAt);
125
+ run(this.db, `UPDATE notification_deliveries SET state = ?, attempts = attempts + 1, last_error = ?, last_status = ?, next_attempt_at = ?,
126
+ updated_at = ? WHERE id = ?`, [next === null ? 'failed' : 'pending', outcome.error, status, next, now, deliveryId]);
127
+ }
128
+ this.refreshAggregate(delivery.notificationId);
129
+ return this.getDelivery(deliveryId);
130
+ });
131
+ }
132
+ /** Re-queue a `failed` delivery (manual retry); returns `null` if it is not `failed`. */
133
+ requeueDelivery(deliveryId) {
134
+ return transaction(this.db, () => {
135
+ const delivery = this.getDelivery(deliveryId);
136
+ if (!delivery)
137
+ throw new NotFoundError('notification_delivery', deliveryId);
138
+ if (delivery.state !== 'failed')
139
+ return null;
140
+ const now = this.clock.now().toISOString();
141
+ run(this.db, `UPDATE notification_deliveries SET state = 'pending', next_attempt_at = ?, updated_at = ? WHERE id = ?`, [
142
+ now,
143
+ now,
144
+ deliveryId,
145
+ ]);
146
+ this.refreshAggregate(delivery.notificationId);
147
+ return this.getDelivery(deliveryId);
148
+ });
149
+ }
150
+ /** Recompute the aggregate columns of a notification from its deliveries. */
151
+ refreshAggregate(notificationId) {
152
+ const deliveries = this.listDeliveries(notificationId);
153
+ const now = this.clock.now().toISOString();
154
+ const state = deliveries.some((d) => d.state === 'pending')
155
+ ? 'pending'
156
+ : deliveries.some((d) => d.state === 'failed')
157
+ ? 'failed'
158
+ : 'delivered';
159
+ const attempts = deliveries.reduce((sum, d) => sum + d.attempts, 0);
160
+ const lastError = [...deliveries]
161
+ .filter((d) => d.lastError !== null && d.state !== 'delivered')
162
+ .sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1))[0]?.lastError ?? null;
163
+ const nextAttempt = deliveries
164
+ .filter((d) => d.state === 'pending' && d.nextAttemptAt !== null)
165
+ .map((d) => d.nextAttemptAt)
166
+ .sort()[0] ?? null;
167
+ const deliveredAt = state === 'delivered'
168
+ ? (deliveries
169
+ .map((d) => d.deliveredAt)
170
+ .filter((t) => t !== null)
171
+ .sort()
172
+ .at(-1) ?? null)
173
+ : null;
174
+ run(this.db, `UPDATE notifications SET delivery_state = ?, attempts = ?, last_error = ?, next_attempt_at = ?, delivered_at = ?, updated_at = ?
175
+ WHERE id = ?`, [state, attempts, lastError, nextAttempt, deliveredAt, now, notificationId]);
176
+ }
177
+ require(id) {
178
+ const n = this.get(id);
179
+ if (!n)
180
+ throw new NotFoundError('notification', id);
181
+ return n;
182
+ }
183
+ }
184
+ function toDelivery(row) {
185
+ return {
186
+ id: Number(row.id),
187
+ notificationId: Number(row.notification_id),
188
+ channelId: String(row.channel_id),
189
+ state: row.state,
190
+ attempts: Number(row.attempts),
191
+ lastError: str(row.last_error),
192
+ lastStatus: row.last_status === null || row.last_status === undefined ? null : Number(row.last_status),
193
+ nextAttemptAt: str(row.next_attempt_at),
194
+ deliveredAt: str(row.delivered_at),
195
+ createdAt: String(row.created_at),
196
+ updatedAt: String(row.updated_at),
197
+ };
198
+ }
199
+ function toNotification(row) {
200
+ return {
201
+ id: Number(row.id),
202
+ eventType: row.event_type,
203
+ payload: JSON.parse(String(row.payload_json)),
204
+ deliveryState: row.delivery_state,
205
+ attempts: Number(row.attempts),
206
+ lastError: str(row.last_error),
207
+ nextAttemptAt: str(row.next_attempt_at),
208
+ createdAt: String(row.created_at),
209
+ updatedAt: String(row.updated_at),
210
+ deliveredAt: str(row.delivered_at),
211
+ };
212
+ }
213
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../src/persistence/notifications.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Composition root of the persistence component.
3
+ */
4
+ import type { DatabaseSync } from 'node:sqlite';
5
+ import { type Clock } from '../core/index.js';
6
+ import { ActionRepository } from './actions.js';
7
+ import { BlockRepository } from './blocks.js';
8
+ import { CronFiringRepository } from './cron-firings.js';
9
+ import { type MigrationResult, type OpenDatabaseOptions } from './database.js';
10
+ import { EventRepository } from './events.js';
11
+ import { NotificationRepository } from './notifications.js';
12
+ import { RetentionRepository } from './retention.js';
13
+ import { RunRepository } from './runs.js';
14
+ /** All repositories over one connection. */
15
+ export interface Repositories {
16
+ runs: RunRepository;
17
+ events: EventRepository;
18
+ actions: ActionRepository;
19
+ blocks: BlockRepository;
20
+ notifications: NotificationRepository;
21
+ cronFirings: CronFiringRepository;
22
+ retention: RetentionRepository;
23
+ /** Run `fn` in one transaction (nested repository calls join it via savepoints). */
24
+ transaction<T>(fn: () => T): T;
25
+ /**
26
+ * Run `cb` after the outermost open transaction commits (immediately if none);
27
+ * dropped on rollback. Publish to the in-process event bus / SSE only from here.
28
+ */
29
+ afterCommit(cb: () => void): void;
30
+ }
31
+ /** Create repositories over an already opened and migrated connection. */
32
+ export declare function createRepositories(db: DatabaseSync, options?: {
33
+ clock?: Clock;
34
+ }): Repositories;
35
+ /** Opened database plus repositories. */
36
+ export interface Persistence extends Repositories {
37
+ db: DatabaseSync;
38
+ path: string;
39
+ migration: MigrationResult;
40
+ close(): void;
41
+ }
42
+ /**
43
+ * Open `<dataDir>/adispatchd.sqlite`, migrate and create all repositories.
44
+ * Acquire the instance lock ({@link acquireInstanceLock}) before calling this.
45
+ */
46
+ export declare function openPersistence(dataDir: string, options?: OpenDatabaseOptions): Persistence;
@@ -0,0 +1,42 @@
1
+ import { systemClock } from '../core/index.js';
2
+ import { ActionRepository } from './actions.js';
3
+ import { BlockRepository } from './blocks.js';
4
+ import { CronFiringRepository } from './cron-firings.js';
5
+ import { openDatabase } from './database.js';
6
+ import { EventRepository } from './events.js';
7
+ import { NotificationRepository } from './notifications.js';
8
+ import { RetentionRepository } from './retention.js';
9
+ import { RunRepository } from './runs.js';
10
+ import { afterCommit, transaction } from './sqlite.js';
11
+ /** Create repositories over an already opened and migrated connection. */
12
+ export function createRepositories(db, options = {}) {
13
+ const clock = options.clock ?? systemClock;
14
+ const events = new EventRepository(db, clock);
15
+ const blocks = new BlockRepository(db, clock);
16
+ return {
17
+ runs: new RunRepository(db, clock, events, blocks),
18
+ events,
19
+ actions: new ActionRepository(db, clock),
20
+ blocks,
21
+ notifications: new NotificationRepository(db, clock),
22
+ cronFirings: new CronFiringRepository(db, clock),
23
+ retention: new RetentionRepository(db),
24
+ transaction: (fn) => transaction(db, fn),
25
+ afterCommit: (cb) => afterCommit(db, cb),
26
+ };
27
+ }
28
+ /**
29
+ * Open `<dataDir>/adispatchd.sqlite`, migrate and create all repositories.
30
+ * Acquire the instance lock ({@link acquireInstanceLock}) before calling this.
31
+ */
32
+ export function openPersistence(dataDir, options = {}) {
33
+ const opened = openDatabase(dataDir, options);
34
+ return {
35
+ ...createRepositories(opened.db, options.clock ? { clock: options.clock } : {}),
36
+ db: opened.db,
37
+ path: opened.path,
38
+ migration: opened.migration,
39
+ close: opened.close,
40
+ };
41
+ }
42
+ //# sourceMappingURL=persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.js","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAkD,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAoBvD,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB,CAAC,EAAgB,EAAE,OAAO,GAAsB,EAAE;IAClF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO;QACL,IAAI,EAAE,IAAI,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;QAClD,MAAM;QACN,OAAO,EAAE,IAAI,gBAAgB,CAAC,EAAE,EAAE,KAAK,CAAC;QACxC,MAAM;QACN,aAAa,EAAE,IAAI,sBAAsB,CAAC,EAAE,EAAE,KAAK,CAAC;QACpD,WAAW,EAAE,IAAI,oBAAoB,CAAC,EAAE,EAAE,KAAK,CAAC;QAChD,SAAS,EAAE,IAAI,mBAAmB,CAAC,EAAE,CAAC;QACtC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC;QACxC,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC;KACzC,CAAC;AACJ,CAAC;AAUD;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,OAAO,GAAwB,EAAE;IAChF,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,OAAO;QACL,GAAG,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/E,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Retention data access (Spec §21). Only queries and row deletion; the
3
+ * periodic job and file/workspace deletion belong to WI-10.
4
+ *
5
+ * Protection rules – a run is never a candidate (and never deleted) if
6
+ * - it is not terminal (active runs hold locks / may be recovered),
7
+ * - its lifecycle chain is unfinished: `lifecycle_status` is `pending`,
8
+ * `running` or `failed`, or any journaled action is `pending`/`running`,
9
+ * - an active (uncleared) retry block exists for its workflow and block scope.
10
+ */
11
+ import type { DatabaseSync } from 'node:sqlite';
12
+ /**
13
+ * Run retention categories (Spec §5.1, §21): `successful` = `completed` + `success`;
14
+ * `blocked` = technical failures counted into a retry block (the block's
15
+ * counting period, `retention.blocked_runs`); `failed` = every other terminal
16
+ * run (business failure, technical failure, cancelled).
17
+ */
18
+ export type RunRetentionCategory = 'successful' | 'failed' | 'blocked';
19
+ /** A run eligible for deletion. */
20
+ export interface RunRetentionCandidate {
21
+ runId: string;
22
+ workflowId: string;
23
+ finishedAt: string;
24
+ /** Workspace to delete (caller must check it lies within `workspace.base_dir`). */
25
+ workspacePath: string | null;
26
+ }
27
+ /** Retention queries and deletions. */
28
+ export declare class RetentionRepository {
29
+ private readonly db;
30
+ constructor(db: DatabaseSync);
31
+ /** Runs of a category finished before `olderThan`, oldest first, excluding protected runs. */
32
+ runCandidates(category: RunRetentionCategory, olderThan: Date | string, limit?: number): RunRetentionCandidate[];
33
+ /** True if the run exists and is protected by one of the rules above. */
34
+ isRunProtected(runId: string): boolean;
35
+ /**
36
+ * Delete a run row incl. events, actions and attempts (cascade). Re-checks
37
+ * protection in the same transaction; returns `false` if protected or missing.
38
+ */
39
+ deleteRun(runId: string): boolean;
40
+ /** Delete cleared blocks cleared before `olderThan`. Active blocks are never deleted. Returns count. */
41
+ deleteClearedBlocks(olderThan: Date | string): number;
42
+ /** Delete delivered/failed notifications created before `olderThan`. Pending ones are kept. Returns count. */
43
+ deleteNotifications(olderThan: Date | string): number;
44
+ /** Delete cron firings recorded before `olderThan`, keeping each workflow's latest one. Returns count. */
45
+ deleteCronFirings(olderThan: Date | string): number;
46
+ }
@@ -0,0 +1,68 @@
1
+ import { all, get, iso, run, str, transaction } from './sqlite.js';
2
+ const PROTECTION_SQL = `
3
+ r.status IN ('completed','crashed','start_error','timed_out','cancelled')
4
+ AND (r.lifecycle_status IS NULL OR r.lifecycle_status = 'succeeded')
5
+ AND NOT EXISTS (SELECT 1 FROM run_actions a WHERE a.run_id = r.id AND a.status IN ('pending','running'))
6
+ AND NOT EXISTS (SELECT 1 FROM blocks b WHERE b.cleared_at IS NULL AND b.workflow_id = r.workflow_id
7
+ AND COALESCE(b.source_item_key, '') = COALESCE(r.source_item_key, ''))
8
+ AND NOT EXISTS (SELECT 1 FROM locks l WHERE l.run_id = r.id)`;
9
+ /** Run belongs to the counting period of a (cleared or active) retry block. */
10
+ const BLOCKED_SQL = `(r.counted_technical_failure = 1 AND EXISTS (SELECT 1 FROM blocks b2 WHERE b2.workflow_id = r.workflow_id
11
+ AND COALESCE(b2.source_item_key, '') = COALESCE(r.source_item_key, '') AND b2.generation = r.block_generation))`;
12
+ const CATEGORY_SQL = {
13
+ successful: `(r.status = 'completed' AND r.business_result = 'success')`,
14
+ failed: `(NOT (r.status = 'completed' AND r.business_result = 'success') AND NOT ${BLOCKED_SQL})`,
15
+ blocked: BLOCKED_SQL,
16
+ };
17
+ /** Retention queries and deletions. */
18
+ export class RetentionRepository {
19
+ db;
20
+ constructor(db) {
21
+ this.db = db;
22
+ }
23
+ /** Runs of a category finished before `olderThan`, oldest first, excluding protected runs. */
24
+ runCandidates(category, olderThan, limit = 500) {
25
+ return all(this.db, `SELECT r.id, r.workflow_id, r.finished_at, r.workspace_path FROM runs r
26
+ WHERE ${CATEGORY_SQL[category]} AND r.finished_at IS NOT NULL AND r.finished_at < ? AND ${PROTECTION_SQL}
27
+ ORDER BY r.finished_at ASC, r.id ASC LIMIT ?`, [iso(olderThan), limit]).map((row) => ({
28
+ runId: String(row.id),
29
+ workflowId: String(row.workflow_id),
30
+ finishedAt: String(row.finished_at),
31
+ workspacePath: str(row.workspace_path),
32
+ }));
33
+ }
34
+ /** True if the run exists and is protected by one of the rules above. */
35
+ isRunProtected(runId) {
36
+ const exists = get(this.db, 'SELECT 1 FROM runs WHERE id = ?', [runId]);
37
+ if (!exists)
38
+ return false;
39
+ return !get(this.db, `SELECT 1 FROM runs r WHERE r.id = ? AND ${PROTECTION_SQL}`, [runId]);
40
+ }
41
+ /**
42
+ * Delete a run row incl. events, actions and attempts (cascade). Re-checks
43
+ * protection in the same transaction; returns `false` if protected or missing.
44
+ */
45
+ deleteRun(runId) {
46
+ return transaction(this.db, () => {
47
+ if (!get(this.db, `SELECT 1 FROM runs r WHERE r.id = ? AND ${PROTECTION_SQL}`, [runId]))
48
+ return false;
49
+ return run(this.db, 'DELETE FROM runs WHERE id = ?', [runId]).changes === 1;
50
+ });
51
+ }
52
+ /** Delete cleared blocks cleared before `olderThan`. Active blocks are never deleted. Returns count. */
53
+ deleteClearedBlocks(olderThan) {
54
+ return run(this.db, 'DELETE FROM blocks WHERE cleared_at IS NOT NULL AND cleared_at < ?', [iso(olderThan)]).changes;
55
+ }
56
+ /** Delete delivered/failed notifications created before `olderThan`. Pending ones are kept. Returns count. */
57
+ deleteNotifications(olderThan) {
58
+ return run(this.db, `DELETE FROM notifications WHERE delivery_state <> 'pending' AND created_at < ?`, [iso(olderThan)])
59
+ .changes;
60
+ }
61
+ /** Delete cron firings recorded before `olderThan`, keeping each workflow's latest one. Returns count. */
62
+ deleteCronFirings(olderThan) {
63
+ const params = [iso(olderThan)];
64
+ return run(this.db, `DELETE FROM cron_firings WHERE recorded_at < ?
65
+ AND scheduled_for < (SELECT MAX(c2.scheduled_for) FROM cron_firings c2 WHERE c2.workflow_id = cron_firings.workflow_id)`, params).changes;
66
+ }
67
+ }
68
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/persistence/retention.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,EAAyB,MAAM,aAAa,CAAC;AAmB1F,MAAM,cAAc,GAAG;;;;;;+DAMwC,CAAC;AAEhE,+EAA+E;AAC/E,MAAM,WAAW,GAAG;oHACgG,CAAC;AAErH,MAAM,YAAY,GAAyC;IACzD,UAAU,EAAE,4DAA4D;IACxE,MAAM,EAAE,2EAA2E,WAAW,GAAG;IACjG,OAAO,EAAE,WAAW;CACrB,CAAC;AAEF,uCAAuC;AACvC,MAAM,OAAO,mBAAmB;IACD,EAAE;IAA/B,YAA6B,EAAgB;kBAAhB,EAAE;IAAiB,CAAC;IAEjD,8FAA8F;IAC9F,aAAa,CAAC,QAA8B,EAAE,SAAwB,EAAE,KAAK,GAAG,GAAG;QACjF,OAAO,GAAG,CACR,IAAI,CAAC,EAAE,EACP;eACS,YAAY,CAAC,QAAQ,CAAC,4DAA4D,cAAc;oDAC3D,EAC9C,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CACxB,CAAC,GAAG,CAAC,CAAC,GAAQ,EAAE,EAAE,CAAC,CAAC;YACnB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;YACnC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;YACnC,aAAa,EAAE,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;SACvC,CAAC,CAAC,CAAC;IACN,CAAC;IAED,yEAAyE;IACzE,cAAc,CAAC,KAAa;QAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,iCAAiC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,2CAA2C,cAAc,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7F,CAAC;IAED;;;OAGG;IACH,SAAS,CAAC,KAAa;QACrB,OAAO,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE;YAC/B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,2CAA2C,cAAc,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;gBAAE,OAAO,KAAK,CAAC;YACtG,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,+BAA+B,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,wGAAwG;IACxG,mBAAmB,CAAC,SAAwB;QAC1C,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,oEAAoE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACtH,CAAC;IAED,8GAA8G;IAC9G,mBAAmB,CAAC,SAAwB;QAC1C,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,gFAAgF,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;aACpH,OAAO,CAAC;IACb,CAAC;IAED,0GAA0G;IAC1G,iBAAiB,CAAC,SAAwB;QACxC,MAAM,MAAM,GAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QACxC,OAAO,GAAG,CACR,IAAI,CAAC,EAAE,EACP;iIAC2H,EAC3H,MAAM,CACP,CAAC,OAAO,CAAC;IACZ,CAAC;CACF"}