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,250 @@
1
+ /**
2
+ * `service install | status | uninstall` (Spec §4, §14, §20.1): plans as
3
+ * plain data (pure, testable, printable with `--dry-run`) plus an executor
4
+ * that applies a plan through an injectable {@link SystemRunner}.
5
+ *
6
+ * Install never touches the contents of config-dir/data-dir beyond ownership
7
+ * and permissions, never overwrites an existing environment file and never
8
+ * deletes anything on uninstall except the unit file.
9
+ */
10
+ import { execFile } from 'node:child_process';
11
+ import { existsSync } from 'node:fs';
12
+ import { chmod, rm, writeFile } from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import { OrchestratorError, errorMessage } from '../core/index.js';
15
+ import { renderSystemdUnit } from './unit.js';
16
+ /** Error of the service commands (`code` = `service_*`). */
17
+ export class ServiceError extends OrchestratorError {
18
+ }
19
+ /** Path of the unit file. */
20
+ export function unitFilePath(unitDir, unitName) {
21
+ return path.join(unitDir, `${unitName}.service`);
22
+ }
23
+ /** Template of a new environment file (no secrets, only placeholders). */
24
+ export function environmentFileTemplate(unitName) {
25
+ return [
26
+ `# Environment of the ${unitName} service (loaded via EnvironmentFile=).`,
27
+ '# Keep this file at mode 0640, owner root, group = service group. Never commit it.',
28
+ '# GitHub App private keys referenced by identities.yaml (private_key_env), PEM with \\n escapes or quoted:',
29
+ '# GITHUB_MAINTAINER_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----"',
30
+ '# Webhook URLs/headers referenced by notifications.channels (url_env / headers_env):',
31
+ '# SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...',
32
+ '',
33
+ ].join('\n');
34
+ }
35
+ /**
36
+ * System directories that must never be config-dir or data-dir (`install`
37
+ * runs `chown -R` / `chmod -R` on both).
38
+ */
39
+ export const PROTECTED_PATHS = [
40
+ '/', '/bin', '/boot', '/dev', '/etc', '/home', '/lib', '/lib32', '/lib64', '/libx32', '/media', '/mnt', '/opt', '/proc',
41
+ '/root', '/run', '/sbin', '/snap', '/srv', '/sys', '/tmp', '/usr', '/usr/bin', '/usr/lib', '/usr/lib64', '/usr/libexec',
42
+ '/usr/local', '/usr/local/bin', '/usr/local/lib', '/usr/sbin', '/usr/share', '/var', '/var/cache', '/var/lib', '/var/log',
43
+ '/var/opt', '/var/run', '/var/spool', '/var/tmp', '/Users', '/Library', '/System', '/Applications', '/private', '/private/var',
44
+ '/private/tmp', '/private/etc', '/etc/systemd', '/etc/systemd/system', '/etc/default',
45
+ ];
46
+ /** Directories whose direct children are user homes (a home itself must not be chowned either). */
47
+ const HOME_ROOTS = ['/home', '/Users'];
48
+ function depth(p) {
49
+ return p.split('/').filter(Boolean).length;
50
+ }
51
+ function isInside(child, parent) {
52
+ const rel = path.relative(parent, child);
53
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
54
+ }
55
+ /**
56
+ * Reject config-dir/data-dir values on which a recursive `chown`/`chmod`
57
+ * would be harmful: system directories, top-level directories (depth < 2),
58
+ * user home directories, and config-dir/data-dir that are equal or nested.
59
+ * Throws {@link ServiceError} (`service_unsafe_path`).
60
+ */
61
+ export function assertSafeServicePaths(dirs) {
62
+ const fail = (msg) => {
63
+ throw new ServiceError('service_unsafe_path', msg);
64
+ };
65
+ for (const [label, raw] of [
66
+ ['config dir', dirs.configDir],
67
+ ['data dir', dirs.dataDir],
68
+ ]) {
69
+ const p = path.resolve(raw);
70
+ if (PROTECTED_PATHS.includes(p))
71
+ fail(`${label} ${p} is a system directory – use a dedicated directory such as /etc/adispatchd or /var/lib/adispatchd`);
72
+ if (depth(p) < 2)
73
+ fail(`${label} ${p} is too close to the file system root (at least two levels, e.g. /srv/adispatchd)`);
74
+ for (const home of HOME_ROOTS) {
75
+ if (path.dirname(p) === home)
76
+ fail(`${label} ${p} is a user home directory – use a dedicated subdirectory`);
77
+ }
78
+ }
79
+ const c = path.resolve(dirs.configDir);
80
+ const d = path.resolve(dirs.dataDir);
81
+ if (isInside(c, d) || isInside(d, c))
82
+ fail(`config dir ${c} and data dir ${d} must be separate, non-nested directories (different owners/permissions)`);
83
+ }
84
+ /** Plan `service install`. Throws {@link ServiceError} if the user is missing and `createUser` is false (not in dry runs). */
85
+ export function planInstall(input) {
86
+ const { unit } = input;
87
+ assertSafeServicePaths(unit);
88
+ const content = renderSystemdUnit(unit);
89
+ const steps = [];
90
+ const notes = [];
91
+ const owner = `${unit.user}:${unit.group}`;
92
+ if (!input.userExists) {
93
+ if (input.createUser) {
94
+ steps.push({
95
+ kind: 'exec',
96
+ description: `create system user ${unit.user}`,
97
+ argv: [
98
+ 'useradd',
99
+ '--system',
100
+ ...(unit.group === unit.user ? ['--user-group'] : ['--gid', unit.group]),
101
+ '--home-dir',
102
+ unit.dataDir,
103
+ '--no-create-home',
104
+ '--shell',
105
+ '/usr/sbin/nologin',
106
+ unit.user,
107
+ ],
108
+ });
109
+ }
110
+ else {
111
+ const hint = `user "${unit.user}" does not exist – rerun with --create-user or create it yourself: ` +
112
+ `useradd --system --user-group --home-dir ${unit.dataDir} --no-create-home --shell /usr/sbin/nologin ${unit.user}`;
113
+ if (!input.dryRun)
114
+ throw new ServiceError('service_user_missing', hint);
115
+ notes.push(hint);
116
+ }
117
+ }
118
+ steps.push({
119
+ kind: 'exec',
120
+ description: `create data dir ${unit.dataDir} (owner ${owner}, 0750)`,
121
+ argv: ['install', '-d', '-m', '0750', '-o', unit.user, '-g', unit.group, unit.dataDir],
122
+ }, {
123
+ kind: 'exec',
124
+ description: `data dir owned by the service user (database, logs, runs, workspaces)`,
125
+ argv: ['chown', '-R', owner, unit.dataDir],
126
+ }, {
127
+ kind: 'exec',
128
+ description: `config dir owned by root, readable by group ${unit.group} (the service cannot modify its own configuration/scripts)`,
129
+ argv: ['chown', '-R', `root:${unit.group}`, unit.configDir],
130
+ }, {
131
+ kind: 'exec',
132
+ description: 'config dir permissions: dirs 0750, files 0640 (executable scripts keep their x bit for the group)',
133
+ argv: ['chmod', '-R', 'u=rwX,g=rX,o=', unit.configDir],
134
+ });
135
+ if (unit.envFile !== null) {
136
+ if (!input.envFileExists) {
137
+ steps.push({
138
+ kind: 'write',
139
+ description: `create environment file template ${unit.envFile}`,
140
+ path: unit.envFile,
141
+ content: environmentFileTemplate(unit.unitName),
142
+ mode: 0o640,
143
+ });
144
+ }
145
+ else {
146
+ notes.push(`environment file ${unit.envFile} exists and is kept unchanged (only owner/mode are set)`);
147
+ }
148
+ steps.push({ kind: 'exec', description: `environment file owned by root:${unit.group}`, argv: ['chown', `root:${unit.group}`, unit.envFile] }, { kind: 'exec', description: 'environment file mode 0640', argv: ['chmod', '0640', unit.envFile] });
149
+ }
150
+ const file = unitFilePath(input.unitDir, unit.unitName);
151
+ steps.push({ kind: 'write', description: `write unit file ${file}`, path: file, content, mode: 0o644 }, { kind: 'exec', description: 'reload systemd units', argv: ['systemctl', 'daemon-reload'] }, {
152
+ kind: 'exec',
153
+ description: input.startNow ? `enable and start ${unit.unitName}` : `enable ${unit.unitName} (start at boot)`,
154
+ argv: ['systemctl', 'enable', ...(input.startNow ? ['--now'] : []), `${unit.unitName}.service`],
155
+ });
156
+ if (!input.startNow)
157
+ notes.push(`start the service with: systemctl start ${unit.unitName}`);
158
+ return { steps, notes };
159
+ }
160
+ /** Plan `service uninstall`: stop + disable, remove the unit file, reload. Config, data and user stay. */
161
+ export function planUninstall(input) {
162
+ const file = unitFilePath(input.unitDir, input.unitName);
163
+ return {
164
+ steps: [
165
+ {
166
+ kind: 'exec',
167
+ description: `stop (SIGTERM → drain) and disable ${input.unitName}`,
168
+ argv: ['systemctl', 'disable', '--now', `${input.unitName}.service`],
169
+ optional: true,
170
+ },
171
+ { kind: 'remove', description: `remove unit file ${file}`, path: file },
172
+ { kind: 'exec', description: 'reload systemd units', argv: ['systemctl', 'daemon-reload'] },
173
+ ],
174
+ notes: [
175
+ 'config dir, data dir (database, logs, workspaces) and the service user are kept; remove them manually if no longer needed',
176
+ `for a graceful stop of long runs, run "adispatch drain --wait" before uninstalling`,
177
+ ],
178
+ };
179
+ }
180
+ /** Human-readable listing of a plan (for `--dry-run`). */
181
+ export function formatPlan(plan) {
182
+ const out = [];
183
+ plan.steps.forEach((s, i) => {
184
+ const n = `${i + 1}.`;
185
+ if (s.kind === 'exec')
186
+ out.push(`${n} ${s.description}\n $ ${s.argv.map(shellQuote).join(' ')}`);
187
+ else if (s.kind === 'write')
188
+ out.push(`${n} ${s.description} (mode ${s.mode.toString(8).padStart(4, '0')})`);
189
+ else
190
+ out.push(`${n} ${s.description}\n $ rm -f ${shellQuote(s.path)}`);
191
+ });
192
+ return `${out.join('\n')}\n`;
193
+ }
194
+ function shellQuote(s) {
195
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
196
+ }
197
+ /** Real runner (`execFile`, no shell). */
198
+ export const systemRunner = {
199
+ exec: (argv) => new Promise((resolve) => {
200
+ const [cmd, ...args] = argv;
201
+ if (!cmd) {
202
+ resolve({ code: 127, stdout: '', stderr: 'empty command' });
203
+ return;
204
+ }
205
+ execFile(cmd, args, { encoding: 'utf8' }, (err, stdout, stderr) => {
206
+ if (!err) {
207
+ resolve({ code: 0, stdout, stderr });
208
+ return;
209
+ }
210
+ const code = typeof err.code === 'number' ? (err.code) : 127;
211
+ resolve({ code, stdout: stdout ?? '', stderr: stderr || errorMessage(err) });
212
+ });
213
+ }),
214
+ writeFile: async (file, content, mode) => {
215
+ await writeFile(file, content, { mode });
216
+ await chmod(file, mode);
217
+ },
218
+ remove: (file) => rm(file, { force: true }),
219
+ exists: (file) => existsSync(file),
220
+ };
221
+ /** Apply a plan step by step; stops at the first failing non-optional step. */
222
+ export async function executePlan(plan, runner, log) {
223
+ for (const step of plan.steps) {
224
+ log(`- ${step.description}\n`);
225
+ try {
226
+ if (step.kind === 'exec') {
227
+ const r = await runner.exec(step.argv);
228
+ if (r.code !== 0) {
229
+ const msg = `${step.argv.join(' ')} exited with ${r.code}: ${r.stderr.trim()}`;
230
+ if (step.optional) {
231
+ log(` (ignored) ${msg}\n`);
232
+ continue;
233
+ }
234
+ return { ok: false, failedStep: step, error: msg };
235
+ }
236
+ }
237
+ else if (step.kind === 'write') {
238
+ await runner.writeFile(step.path, step.content, step.mode);
239
+ }
240
+ else {
241
+ await runner.remove(step.path);
242
+ }
243
+ }
244
+ catch (err) {
245
+ return { ok: false, failedStep: step, error: errorMessage(err) };
246
+ }
247
+ }
248
+ return { ok: true };
249
+ }
250
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/service/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /** Exit code of `start` for configuration/startup errors that a restart cannot fix (sysexits EX_CONFIG). */
2
+ export declare const EXIT_CONFIG = 78;
3
+ /** Default unit / service name. */
4
+ export declare const DEFAULT_UNIT_NAME = "adispatchd";
5
+ /** Default dedicated service user (and group). */
6
+ export declare const DEFAULT_SERVICE_USER = "adispatchd";
7
+ /** Default directory for unit files. */
8
+ export declare const DEFAULT_UNIT_DIR = "/etc/systemd/system";
9
+ /** Extra time on top of drain + cancel + grace before systemd kills the cgroup. */
10
+ export declare const DEFAULT_STOP_BUFFER_MS = 60000;
11
+ /** Input of {@link renderSystemdUnit}. */
12
+ export interface UnitOptions {
13
+ /** Unit name without `.service`. */
14
+ unitName: string;
15
+ description?: string;
16
+ /** Dedicated service user and group. */
17
+ user: string;
18
+ group: string;
19
+ /** Absolute path of the node executable. */
20
+ nodePath: string;
21
+ /** Absolute path of the installed package entry point (`…/dist/cli/main.js`). */
22
+ entryPath: string;
23
+ /** Absolute config and data directories. */
24
+ configDir: string;
25
+ dataDir: string;
26
+ /** Optional environment file with secrets (GitHub App keys, webhook URLs); `null` = none. */
27
+ envFile: string | null;
28
+ /** Drain timeout on SIGTERM before cancel-all (passed to `start --drain-timeout`). */
29
+ drainTimeoutMs: number;
30
+ /** Time for cancelled runs incl. `on_cancel` (passed to `start --cancel-timeout`). */
31
+ cancelTimeoutMs: number;
32
+ /** Largest `execute.cancel_grace` of all workflows (SIGTERM → SIGKILL of a worker group). */
33
+ maxCancelGraceMs: number;
34
+ /** Additional stop buffer (default {@link DEFAULT_STOP_BUFFER_MS}). */
35
+ stopBufferMs?: number;
36
+ }
37
+ /** `TimeoutStopSec` in whole seconds: drain + cancel + grace + buffer, rounded up. */
38
+ export declare function computeTimeoutStopSec(opts: Pick<UnitOptions, 'drainTimeoutMs' | 'cancelTimeoutMs' | 'maxCancelGraceMs' | 'stopBufferMs'>): number;
39
+ /** Duration in the CLI's own syntax (`10m`, `90s`, `1500ms`), parseable by `parseDuration`. */
40
+ export declare function formatCliDuration(ms: number): string;
41
+ /**
42
+ * Quote one `ExecStart` word for systemd: `%` → `%%` (specifiers), `$` → `$$`
43
+ * (no variable expansion); words with whitespace, quotes or backslashes are
44
+ * wrapped in double quotes with C-style escaping.
45
+ */
46
+ export declare function quoteSystemdArg(value: string): string;
47
+ /** Validate unit options; throws {@link ValidationError}. */
48
+ export declare function validateUnitOptions(opts: UnitOptions): void;
49
+ /** Render the systemd unit file. Pure: same input → same output. */
50
+ export declare function renderSystemdUnit(opts: UnitOptions): string;
@@ -0,0 +1,171 @@
1
+ /**
2
+ * systemd unit generation (Spec §4, §14, §20.1) – pure functions.
3
+ *
4
+ * The unit starts a fixed, globally installed package version directly with
5
+ * absolute paths (`<node> <package>/dist/cli/main.js start …`), never through
6
+ * `npx`. Stop behaviour is aligned with the orchestrator's shutdown contract
7
+ * (docs/handoff/WI-08.md, docs/operations.md):
8
+ *
9
+ * - `KillMode=mixed`: `systemctl stop` sends SIGTERM only to the main process
10
+ * (workers keep running while the orchestrator drains); after
11
+ * `TimeoutStopSec` systemd SIGKILLs the whole cgroup including workers.
12
+ * - `TimeoutStopSec` = drain timeout + cancel timeout + max. `cancel_grace`
13
+ * + buffer. The worker's SIGTERM → SIGKILL grace runs *inside* the cancel
14
+ * timeout (after it, hooks are interrupted and the orchestrator exits), so
15
+ * the grace term is only a safety margin; `cancel_grace` must be smaller
16
+ * than the cancel timeout to be effective (the CLI warns otherwise).
17
+ * - `Restart=on-failure`: an orderly shutdown (API drain / cancel-all, SIGTERM)
18
+ * exits with 0 and is not restarted; a crash (exit ≠ 0, signal) is.
19
+ * Configuration/start errors exit with {@link EXIT_CONFIG} (78, EX_CONFIG),
20
+ * excluded via `RestartPreventExitStatus=78` (no restart loop).
21
+ */
22
+ import path from 'node:path';
23
+ import { ValidationError } from '../core/index.js';
24
+ /** Exit code of `start` for configuration/startup errors that a restart cannot fix (sysexits EX_CONFIG). */
25
+ export const EXIT_CONFIG = 78;
26
+ /** Default unit / service name. */
27
+ export const DEFAULT_UNIT_NAME = 'adispatchd';
28
+ /** Default dedicated service user (and group). */
29
+ export const DEFAULT_SERVICE_USER = 'adispatchd';
30
+ /** Default directory for unit files. */
31
+ export const DEFAULT_UNIT_DIR = '/etc/systemd/system';
32
+ /** Extra time on top of drain + cancel + grace before systemd kills the cgroup. */
33
+ export const DEFAULT_STOP_BUFFER_MS = 60_000;
34
+ const NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
35
+ const USER_PATTERN = /^[a-z_][a-z0-9_-]{0,31}$/;
36
+ /** `TimeoutStopSec` in whole seconds: drain + cancel + grace + buffer, rounded up. */
37
+ export function computeTimeoutStopSec(opts) {
38
+ const total = opts.drainTimeoutMs + opts.cancelTimeoutMs + opts.maxCancelGraceMs + (opts.stopBufferMs ?? DEFAULT_STOP_BUFFER_MS);
39
+ return Math.ceil(total / 1000);
40
+ }
41
+ /** Duration in the CLI's own syntax (`10m`, `90s`, `1500ms`), parseable by `parseDuration`. */
42
+ export function formatCliDuration(ms) {
43
+ if (!Number.isSafeInteger(ms) || ms < 0)
44
+ throw new ValidationError(`Invalid duration ${ms}`);
45
+ if (ms === 0)
46
+ return '0s';
47
+ if (ms % 3_600_000 === 0)
48
+ return `${ms / 3_600_000}h`;
49
+ if (ms % 60_000 === 0)
50
+ return `${ms / 60_000}m`;
51
+ if (ms % 1000 === 0)
52
+ return `${ms / 1000}s`;
53
+ return `${ms}ms`;
54
+ }
55
+ /**
56
+ * Quote one `ExecStart` word for systemd: `%` → `%%` (specifiers), `$` → `$$`
57
+ * (no variable expansion); words with whitespace, quotes or backslashes are
58
+ * wrapped in double quotes with C-style escaping.
59
+ */
60
+ export function quoteSystemdArg(value) {
61
+ if (/[\0\n\r]/.test(value))
62
+ throw new ValidationError('Unit values must not contain NUL or line breaks');
63
+ const escaped = value.replace(/%/g, '%%').replace(/\$/g, '$$$$');
64
+ if (escaped !== '' && !/[\s"'\\;]/.test(escaped))
65
+ return escaped;
66
+ return `"${escaped.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
67
+ }
68
+ /** Value for `key=value` lines that take a single path (escapes specifiers). */
69
+ function pathValue(value) {
70
+ if (/[\0\n\r]/.test(value))
71
+ throw new ValidationError('Unit values must not contain NUL or line breaks');
72
+ return value.replace(/%/g, '%%');
73
+ }
74
+ function assertAbsolute(label, p) {
75
+ if (!path.isAbsolute(p))
76
+ throw new ValidationError(`${label} must be an absolute path (got "${p}")`);
77
+ }
78
+ /** Validate unit options; throws {@link ValidationError}. */
79
+ export function validateUnitOptions(opts) {
80
+ if (!NAME_PATTERN.test(opts.unitName))
81
+ throw new ValidationError(`Invalid unit name "${opts.unitName}"`);
82
+ if (!USER_PATTERN.test(opts.user))
83
+ throw new ValidationError(`Invalid user name "${opts.user}"`);
84
+ if (!USER_PATTERN.test(opts.group))
85
+ throw new ValidationError(`Invalid group name "${opts.group}"`);
86
+ if (opts.user === 'root')
87
+ throw new ValidationError('The service must run as a dedicated user, not root (Spec §20.1)');
88
+ assertAbsolute('node path', opts.nodePath);
89
+ assertAbsolute('package entry point', opts.entryPath);
90
+ assertAbsolute('config dir', opts.configDir);
91
+ assertAbsolute('data dir', opts.dataDir);
92
+ if (opts.envFile !== null)
93
+ assertAbsolute('environment file', opts.envFile);
94
+ for (const [label, p] of [
95
+ ['node path', opts.nodePath],
96
+ ['package entry point', opts.entryPath],
97
+ ]) {
98
+ if (/(^|\/)(npx|_npx)(\/|$)/.test(p)) {
99
+ throw new ValidationError(`${label} "${p}" points into an npx cache – install a fixed version permanently (npm install -g adispatchd@<version>, or a tarball from npm pack) and run "service install" from there`);
100
+ }
101
+ }
102
+ if (/\.ts$/.test(opts.entryPath)) {
103
+ throw new ValidationError(`package entry point "${opts.entryPath}" is a TypeScript source – build the package (npm run build) or install it`);
104
+ }
105
+ for (const [label, ms] of [
106
+ ['drain timeout', opts.drainTimeoutMs],
107
+ ['cancel timeout', opts.cancelTimeoutMs],
108
+ ['cancel grace', opts.maxCancelGraceMs],
109
+ ]) {
110
+ if (!Number.isSafeInteger(ms) || ms < 0)
111
+ throw new ValidationError(`Invalid ${label} ${ms}`);
112
+ }
113
+ }
114
+ /** Render the systemd unit file. Pure: same input → same output. */
115
+ export function renderSystemdUnit(opts) {
116
+ validateUnitOptions(opts);
117
+ const timeoutStopSec = computeTimeoutStopSec(opts);
118
+ const exec = [
119
+ opts.nodePath,
120
+ opts.entryPath,
121
+ 'start',
122
+ '--config-dir',
123
+ opts.configDir,
124
+ '--data-dir',
125
+ opts.dataDir,
126
+ '--drain-timeout',
127
+ formatCliDuration(opts.drainTimeoutMs),
128
+ '--cancel-timeout',
129
+ formatCliDuration(opts.cancelTimeoutMs),
130
+ ]
131
+ .map(quoteSystemdArg)
132
+ .join(' ');
133
+ const lines = [
134
+ '# Generated by "adispatch service install". Changes are overwritten by the next install.',
135
+ '# Stop contract: SIGTERM drains (drain timeout), then cancels all runs (cancel timeout);',
136
+ `# TimeoutStopSec = drain ${formatCliDuration(opts.drainTimeoutMs)} + cancel ${formatCliDuration(opts.cancelTimeoutMs)} + grace margin ${formatCliDuration(opts.maxCancelGraceMs)} + buffer ${formatCliDuration(opts.stopBufferMs ?? DEFAULT_STOP_BUFFER_MS)}.`,
137
+ '# For planned stops/updates run "adispatch drain --wait" first (see docs/operations.md).',
138
+ '[Unit]',
139
+ `Description=${opts.description ?? 'Agent Dispatch Daemon'}`,
140
+ 'After=network-online.target',
141
+ 'Wants=network-online.target',
142
+ '',
143
+ '[Service]',
144
+ 'Type=simple',
145
+ `User=${opts.user}`,
146
+ `Group=${opts.group}`,
147
+ `ExecStart=${exec}`,
148
+ `WorkingDirectory=${pathValue(opts.dataDir)}`,
149
+ ...(opts.envFile !== null ? [`EnvironmentFile=-${pathValue(opts.envFile)}`] : []),
150
+ 'Environment=NODE_ENV=production',
151
+ // Orderly shutdown → exit 0 → no restart; crash → restart → recovery.
152
+ 'Restart=on-failure',
153
+ 'RestartSec=5s',
154
+ // Configuration/start errors (invalid YAML, instance lock held, newer DB schema): do not restart in a loop.
155
+ `RestartPreventExitStatus=${EXIT_CONFIG}`,
156
+ // SIGTERM only to the orchestrator; workers stay alive while it drains.
157
+ 'KillMode=mixed',
158
+ 'KillSignal=SIGTERM',
159
+ `TimeoutStopSec=${timeoutStopSec}`,
160
+ 'UMask=0027',
161
+ 'NoNewPrivileges=true',
162
+ 'PrivateTmp=true',
163
+ 'LimitNOFILE=65536',
164
+ '',
165
+ '[Install]',
166
+ 'WantedBy=multi-user.target',
167
+ '',
168
+ ];
169
+ return lines.join('\n');
170
+ }
171
+ //# sourceMappingURL=unit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unit.js","sourceRoot":"","sources":["../../src/service/unit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Source adapter `github` (Spec §5.2, §6, §10): issues and pull requests of
3
+ * one repository, native "blocked by" dependencies, labels and comments.
4
+ */
5
+ import { type CommentRef, type FieldDefinition, type FieldResolution, type IdentityRef, type ListCandidatesOptions, type SourceAdapter, type SourceAdapterDeps, type SourceAdapterFactory, type SourceItem, type SourceUpdate, type SourceUpdateResult, type Timers } from '../../core/index.js';
6
+ import { GitHubHttpClient, type GitHubHttpClientOptions } from './http.js';
7
+ /** Adapter type name. */
8
+ export declare const GITHUB_SOURCE_TYPE = "github";
9
+ /** Validated configuration of one GitHub source (normalised, camelCase). */
10
+ export interface GitHubSourceConfig {
11
+ owner: string;
12
+ repository: string;
13
+ /** Default identity for reads (and fallback for writes). */
14
+ identity: IdentityRef;
15
+ /** REST API base URL; default `https://api.github.com`. */
16
+ apiBaseUrl?: string;
17
+ /** Web base URL for parsing item references; default `https://github.com`. */
18
+ webBaseUrl?: string;
19
+ }
20
+ /** Subset of the GitHub issue JSON used by the adapter. */
21
+ export interface GitHubIssueJson {
22
+ id?: number;
23
+ number: number;
24
+ title?: string;
25
+ state?: string;
26
+ html_url?: string;
27
+ repository_url?: string;
28
+ labels?: Array<{
29
+ name?: string;
30
+ } | string>;
31
+ assignees?: Array<{
32
+ login?: string;
33
+ }> | null;
34
+ user?: {
35
+ login?: string;
36
+ } | null;
37
+ pull_request?: unknown;
38
+ created_at?: string;
39
+ updated_at?: string;
40
+ repository?: {
41
+ full_name?: string;
42
+ } | null;
43
+ issue_dependencies_summary?: {
44
+ blocked_by?: number;
45
+ total_blocked_by?: number;
46
+ } | null;
47
+ }
48
+ /** Options beyond {@link SourceAdapterDeps}. */
49
+ export interface GitHubAdapterOptions {
50
+ timers?: Timers;
51
+ /** Pre-built HTTP client (tests / sharing); otherwise one is created from deps. */
52
+ http?: GitHubHttpClient;
53
+ /** Extra HTTP client settings (retries, timeouts). */
54
+ httpOptions?: Partial<Omit<GitHubHttpClientOptions, 'fetch' | 'clock' | 'timers' | 'logger' | 'baseUrl'>>;
55
+ }
56
+ /** {@link SourceAdapter} for one GitHub repository. */
57
+ export declare class GitHubSourceAdapter implements SourceAdapter {
58
+ readonly type = "github";
59
+ readonly sourceId: string;
60
+ readonly repository: string;
61
+ private readonly config;
62
+ private readonly http;
63
+ private readonly credentials;
64
+ private readonly logger;
65
+ private readonly repoPath;
66
+ constructor(sourceId: string, config: GitHubSourceConfig, deps: SourceAdapterDeps, options?: GitHubAdapterOptions);
67
+ fieldDefinitions(): readonly FieldDefinition[];
68
+ listCandidates(options?: ListCandidatesOptions): Promise<SourceItem[]>;
69
+ getItem(itemId: string, options?: {
70
+ signal?: AbortSignal;
71
+ }): Promise<SourceItem | null>;
72
+ parseItemRef(ref: string): string | null;
73
+ resolveFields(item: SourceItem, fields: readonly string[], options?: {
74
+ signal?: AbortSignal;
75
+ }): Promise<Record<string, FieldResolution>>;
76
+ applyUpdate(item: SourceItem, update: SourceUpdate, identity: IdentityRef): Promise<SourceUpdateResult>;
77
+ postComment(item: SourceItem, body: string, marker: string, identity: IdentityRef): Promise<CommentRef>;
78
+ findCommentByMarker(item: SourceItem, marker: string, identity: IdentityRef): Promise<CommentRef | null>;
79
+ /**
80
+ * Only comments written by the identity's own app count as carrying a
81
+ * marker; otherwise anybody could suppress an orchestrator comment by
82
+ * posting the marker. With a known app ID (CredentialProvider.describe) the
83
+ * comment must have `performed_via_github_app.id === appId`; without one,
84
+ * at least a bot author (`user.type === 'Bot'`) is required.
85
+ */
86
+ private authorPredicate;
87
+ private resolveDependencies;
88
+ private fetchIssue;
89
+ /** Run `fn` with a token; on 401 invalidate the cached token and retry once. */
90
+ private withToken;
91
+ private toItem;
92
+ private assertOwnItem;
93
+ }
94
+ /** Factory registered under type `github`. */
95
+ export declare function createGitHubSourceFactory(options?: GitHubAdapterOptions): SourceAdapterFactory<GitHubSourceConfig>;
96
+ /** Default factory (real timers). */
97
+ export declare const githubSourceFactory: SourceAdapterFactory<GitHubSourceConfig>;
98
+ /** Render the HTML comment that carries an idempotency marker. */
99
+ export declare function renderMarker(marker: string): string;