@jr2/orchestrator 0.5.1 → 0.7.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 (350) hide show
  1. package/console/components/app.ts +55 -15
  2. package/console/components/bottom-drawer.ts +259 -0
  3. package/console/components/drawer.ts +14 -6
  4. package/console/components/fleet.ts +36 -13
  5. package/console/components/machine-pane.ts +61 -5
  6. package/console/components/nav.ts +54 -8
  7. package/console/conversation.ts +161 -0
  8. package/console/login.ts +123 -0
  9. package/console/main.ts +600 -60
  10. package/console/store.ts +611 -64
  11. package/console/style.css +231 -0
  12. package/docs/agents-block.md +2 -2
  13. package/docs/concepts/agents-and-turns.md +142 -29
  14. package/docs/concepts/architecture.md +60 -34
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +13 -8
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +7 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +133 -50
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +14 -4
  23. package/docs/concepts/pools-and-sources.md +193 -17
  24. package/docs/concepts/secrets-and-trust.md +186 -62
  25. package/docs/concepts/the-menu.md +14 -0
  26. package/docs/concepts/workspaces.md +135 -56
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
  29. package/docs/get-started/connect-a-model.md +1 -1
  30. package/docs/get-started/create-an-instance.md +17 -14
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  32. package/docs/get-started/install.md +4 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +72 -15
  34. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  35. package/docs/guides/answer-a-gate.md +74 -37
  36. package/docs/guides/bind-open-parts.md +29 -12
  37. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  38. package/docs/guides/cancel-a-run.md +26 -16
  39. package/docs/guides/cut-a-worktree.md +560 -0
  40. package/docs/guides/fix-a-failed-converge.md +19 -7
  41. package/docs/guides/give-a-caller-a-scope.md +203 -0
  42. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  43. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  44. package/docs/guides/let-an-agent-write-a-chart.md +14 -7
  45. package/docs/guides/look-inside-a-sandbox.md +115 -42
  46. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  47. package/docs/guides/open-the-console.md +63 -14
  48. package/docs/guides/push-and-sign-a-branch.md +67 -21
  49. package/docs/guides/read-a-conversation.md +225 -0
  50. package/docs/guides/run-a-chart-by-name.md +17 -16
  51. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  52. package/docs/guides/run-one-worker-per-item.md +34 -27
  53. package/docs/guides/set-an-agents-access-and-dials.md +69 -22
  54. package/docs/guides/size-a-workspace.md +19 -6
  55. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  56. package/docs/guides/start-a-run-from-a-run.md +18 -16
  57. package/docs/guides/start-a-run.md +24 -18
  58. package/docs/guides/steer-an-agent.md +332 -0
  59. package/docs/guides/use-a-hosted-provider.md +7 -1
  60. package/docs/guides/watch-a-run.md +146 -117
  61. package/docs/guides/write-a-hook-kind.md +238 -0
  62. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  63. package/docs/guides/write-a-workflow.md +18 -8
  64. package/docs/llms.txt +110 -23
  65. package/docs/reference/authoring-surface/agent.md +35 -12
  66. package/docs/reference/authoring-surface/answer.md +20 -4
  67. package/docs/reference/authoring-surface/arg.md +28 -6
  68. package/docs/reference/authoring-surface/basic.md +1 -1
  69. package/docs/reference/authoring-surface/bearer.md +1 -1
  70. package/docs/reference/authoring-surface/branchname.md +15 -0
  71. package/docs/reference/authoring-surface/carry.md +23 -0
  72. package/docs/reference/authoring-surface/chartschema.md +1 -1
  73. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  74. package/docs/reference/authoring-surface/command.md +37 -22
  75. package/docs/reference/authoring-surface/customize.md +4 -1
  76. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  77. package/docs/reference/authoring-surface/defineevent.md +1 -1
  78. package/docs/reference/authoring-surface/dispatch.md +5 -4
  79. package/docs/reference/authoring-surface/fail.md +18 -5
  80. package/docs/reference/authoring-surface/file.md +37 -17
  81. package/docs/reference/authoring-surface/frompick.md +1 -1
  82. package/docs/reference/authoring-surface/gate.md +118 -0
  83. package/docs/reference/authoring-surface/group-agents.md +15 -0
  84. package/docs/reference/authoring-surface/group-charts.md +16 -0
  85. package/docs/reference/authoring-surface/group-composition.md +25 -0
  86. package/docs/reference/authoring-surface/group-config.md +17 -0
  87. package/docs/reference/authoring-surface/group-effects.md +48 -0
  88. package/docs/reference/authoring-surface/group-failure.md +13 -0
  89. package/docs/reference/authoring-surface/group-gates.md +16 -0
  90. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  91. package/docs/reference/authoring-surface/group-steering.md +12 -0
  92. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  93. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  94. package/docs/reference/authoring-surface/http.md +109 -9
  95. package/docs/reference/authoring-surface/isfailure.md +1 -1
  96. package/docs/reference/authoring-surface/isready.md +14 -0
  97. package/docs/reference/authoring-surface/jr2setup.md +14 -4
  98. package/docs/reference/authoring-surface/mcp.md +107 -0
  99. package/docs/reference/authoring-surface/open.md +41 -8
  100. package/docs/reference/authoring-surface/openbody.md +1 -1
  101. package/docs/reference/authoring-surface/paramsof.md +23 -5
  102. package/docs/reference/authoring-surface/pool.md +79 -11
  103. package/docs/reference/authoring-surface/push.md +31 -13
  104. package/docs/reference/authoring-surface/readyof.md +21 -0
  105. package/docs/reference/authoring-surface/reread.md +19 -0
  106. package/docs/reference/authoring-surface/secret.md +26 -5
  107. package/docs/reference/authoring-surface/sentby.md +1 -1
  108. package/docs/reference/authoring-surface/skip.md +22 -0
  109. package/docs/reference/authoring-surface/source.md +59 -4
  110. package/docs/reference/authoring-surface/steer.md +93 -0
  111. package/docs/reference/authoring-surface/text.md +1 -1
  112. package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
  113. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  114. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  115. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  116. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  117. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  118. package/docs/reference/authoring-surface/type-binding.md +6 -7
  119. package/docs/reference/authoring-surface/type-caller.md +40 -0
  120. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  121. package/docs/reference/authoring-surface/type-carry.md +19 -0
  122. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  123. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  124. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  125. package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
  126. package/docs/reference/authoring-surface/type-customize.md +20 -3
  127. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  128. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  129. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  130. package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
  131. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  132. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  133. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  134. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  135. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  136. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  137. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  138. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  139. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  140. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  141. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  142. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  143. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  144. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  145. package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
  146. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  147. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
  149. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  150. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  151. package/docs/reference/authoring-surface/type-lends.md +5 -1
  152. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  153. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  154. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  155. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  156. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  157. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  158. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  159. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  160. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  161. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  162. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  163. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  164. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  165. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  166. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  167. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  168. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  169. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  170. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  171. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  172. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  173. package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
  174. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  175. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  176. package/docs/reference/authoring-surface/type-reposlot.md +6 -6
  177. package/docs/reference/authoring-surface/type-reread.md +18 -0
  178. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  179. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  180. package/docs/reference/authoring-surface/type-sender.md +26 -1
  181. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  182. package/docs/reference/authoring-surface/type-size.md +1 -1
  183. package/docs/reference/authoring-surface/type-skip.md +19 -0
  184. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  185. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  186. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  187. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  188. package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
  189. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  190. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  191. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  192. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  193. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  194. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  195. package/docs/reference/authoring-surface/type-workspaced.md +30 -9
  196. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  197. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  198. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  199. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  200. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  201. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  202. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  203. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  204. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  205. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  206. package/docs/reference/authoring-surface/workspace.md +66 -38
  207. package/docs/reference/authoring-surface/worktree.md +71 -0
  208. package/docs/reference/authoring-surface.md +88 -147
  209. package/docs/reference/cli.md +413 -74
  210. package/docs/reference/config.md +248 -6
  211. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  212. package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
  213. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  214. package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
  215. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  216. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  217. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  218. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  219. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  220. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
  221. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
  222. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  223. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
  224. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  225. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
  226. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  227. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
  228. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
  229. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  230. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
  231. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
  232. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  233. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
  234. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
  235. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  236. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  237. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
  238. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  239. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
  240. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  241. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
  242. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
  243. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  244. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  245. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  246. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  247. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  248. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  249. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
  250. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
  251. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
  252. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
  253. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
  254. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  255. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  256. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
  257. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
  258. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
  259. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  260. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
  261. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  262. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  263. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  264. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
  265. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
  266. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  267. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  268. package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
  269. package/docs/reference/decisions.md +9 -4
  270. package/docs/reference/glossary.md +138 -45
  271. package/docs/reference/hooks.md +1077 -0
  272. package/docs/reference/http-api.md +988 -123
  273. package/docs/reference/machines.md +0 -1
  274. package/docs/why.md +7 -5
  275. package/package.json +10 -3
  276. package/src/actor.ts +194 -42
  277. package/src/agent.ts +56 -6
  278. package/src/ambient.ts +3 -5
  279. package/src/assembly-checks.ts +530 -4
  280. package/src/callers.ts +245 -0
  281. package/src/chart.ts +238 -26
  282. package/src/charts.ts +9 -2
  283. package/src/config.ts +566 -7
  284. package/src/conversations.ts +453 -0
  285. package/src/custodian.lua +17 -5
  286. package/src/custodian.ts +257 -77
  287. package/src/customize.ts +154 -7
  288. package/src/dispatch-effect.ts +142 -26
  289. package/src/durability.ts +2 -0
  290. package/src/duration.ts +20 -0
  291. package/src/effect-checks.ts +52 -4
  292. package/src/effect.ts +401 -132
  293. package/src/effector-wire.ts +278 -30
  294. package/src/fail.ts +17 -4
  295. package/src/gate-brand.ts +16 -0
  296. package/src/gate.ts +634 -48
  297. package/src/harness-client.ts +84 -35
  298. package/src/harness-env.ts +33 -0
  299. package/src/held-secrets.ts +197 -39
  300. package/src/holds.ts +5 -2
  301. package/src/hook-kinds/discord.ts +92 -0
  302. package/src/hook-kinds/door.ts +107 -0
  303. package/src/hook-kinds/github.ts +89 -0
  304. package/src/hook-kinds/gitlab.ts +72 -0
  305. package/src/hook-kinds/hook.ts +7 -0
  306. package/src/hook-kinds/index.ts +465 -0
  307. package/src/hook-kinds/slack.ts +151 -0
  308. package/src/hook-kinds/webhook.ts +113 -0
  309. package/src/hooks.ts +506 -0
  310. package/src/http-effect.ts +164 -70
  311. package/src/http.ts +933 -99
  312. package/src/index.ts +30 -5
  313. package/src/instance.ts +196 -26
  314. package/src/internal.ts +23 -3
  315. package/src/kube-client.ts +6 -3
  316. package/src/machine-doc.ts +29 -5
  317. package/src/mcp-effect.ts +424 -0
  318. package/src/model-keys.ts +24 -0
  319. package/src/mount-digest.ts +41 -7
  320. package/src/names.ts +82 -7
  321. package/src/notices.ts +576 -59
  322. package/src/open-body.ts +110 -30
  323. package/src/open-effect.ts +17 -4
  324. package/src/open.ts +40 -7
  325. package/src/parts.ts +125 -35
  326. package/src/party.ts +35 -1
  327. package/src/pick.ts +43 -9
  328. package/src/pool.ts +1172 -172
  329. package/src/registration.ts +249 -17
  330. package/src/repo-identity.ts +7 -3
  331. package/src/repos.ts +20 -2
  332. package/src/run-host.ts +1614 -190
  333. package/src/run-list.ts +191 -0
  334. package/src/sandbox-effect.ts +217 -66
  335. package/src/sandbox-kube.ts +116 -21
  336. package/src/secret-placement.ts +248 -35
  337. package/src/server.ts +53 -4
  338. package/src/setup.ts +158 -38
  339. package/src/snapshot-store.ts +565 -48
  340. package/src/steer.ts +111 -0
  341. package/src/stub-harness.ts +6 -2
  342. package/src/tokens.ts +362 -46
  343. package/src/vocabulary.ts +23 -4
  344. package/src/wire.ts +27 -10
  345. package/src/workspace.ts +314 -259
  346. package/src/worktree.ts +249 -0
  347. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  348. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
  349. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  350. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -0,0 +1,1077 @@
1
+ ---
2
+ title: Hook kinds
3
+ description: "Every name that @jr2/orchestrator/hooks exports: the interface of a Hook kind, the kinds that jr2 ships, and webhook()."
4
+ ---
5
+
6
+ # Hook kinds
7
+
8
+ `@jr2/orchestrator/hooks` is the interface of a [Hook kind](glossary.md#hook-kind), and the kinds that jr2 ships. `jr2.config.ts` imports a kind to declare a [Hook](glossary.md#hook), and a workflow module imports a kind's types for the handlers of a Gate slot or a Source. It is part of `@jr2/orchestrator`, so an Instance installs nothing more. A kind from another package has a peer dependency on `@jr2/orchestrator` and states `api: 1`. Each group of this page is one module that you import.
9
+
10
+ ## Contents
11
+
12
+ - `@jr2/orchestrator/hooks`: [`HookRequest`](#type-hookrequest), [`Json`](#type-json), [`json`](#json), [`form`](#form), [`at`](#at), [`Signed`](#type-signed), [`Unsigned`](#type-unsigned), [`MacAlg`](#type-macalg), [`Verify`](#type-verify), [`HookSecret`](#type-hooksecret), [`CredentialOf`](#type-credentialof), [`HookCredential`](#type-hookcredential), [`Answer`](#type-answer), [`Handshake`](#type-handshake), [`Delivery`](#type-delivery), [`HookKindApi`](#type-hookkindapi), [`HOOK_KIND_APIS`](#hook-kind-apis), [`Events`](#type-events), [`HandlerKey`](#type-handlerkey), [`Named`](#type-named), [`Payloads`](#type-payloads), [`HookKind`](#type-hookkind), [`HookMay`](#type-hookmay), [`HookParts`](#type-hookparts), [`Hook`](#type-hook), [`HookKindOf`](#type-hookkindof), [`defineHookKind`](#definehookkind), [`HookKinds`](#type-hookkinds), [`EventsOf`](#type-eventsof), [`NoWildcardKeys`](#type-nowildcardkeys), [`PayloadOf`](#type-payloadof), [`KindHooks`](#type-kindhooks)
13
+ - `@jr2/orchestrator/hooks/github`: [`githubEvents`](#githubevents), [`github`](#github)
14
+ - `@jr2/orchestrator/hooks/gitlab`: [`gitlabEvents`](#gitlabevents), [`gitlab`](#gitlab)
15
+ - `@jr2/orchestrator/hooks/slack`: [`slackEvents`](#slackevents), [`slack`](#slack)
16
+ - `@jr2/orchestrator/hooks/discord`: [`discordEvents`](#discordevents), [`discord`](#discord)
17
+ - `@jr2/orchestrator/hooks/webhook`: [`Field`](#type-field), [`WebhookVerify`](#type-webhookverify), [`webhook`](#webhook)
18
+
19
+ ## `@jr2/orchestrator/hooks`
20
+
21
+ The interface between the door of a Hook and a kind: `defineHookKind`, which makes a kind, the types of a kind, the helpers that read a request, and `HookKinds`, to which each kind adds its id and its payloads.
22
+
23
+ Each name in this group is declared in `@jr2/orchestrator/src/hook-kinds/index.ts`.
24
+
25
+ <a id="type-hookrequest"></a>
26
+
27
+ ### `HookRequest`
28
+
29
+ Type.
30
+
31
+ ```ts
32
+ type HookRequest = {
33
+ readonly headers: { readonly [name: string]: string | undefined };
34
+ readonly body: Uint8Array;
35
+ readonly text: string;
36
+ };
37
+ ```
38
+
39
+ One delivery, as the door got it. All of it is data. Header names are lowercase.
40
+
41
+ For a kind with a `token` verifier, `headers` does not hold the token's header: the door removes
42
+ it before it calls the kind. For `mac` and `ed25519`, no part of the request is secret: a
43
+ signature cannot make another signature.
44
+
45
+ **Fields**
46
+
47
+ - `headers`: `object`
48
+ - `body`: `Uint8Array`
49
+
50
+ The raw body. A `mac` or `ed25519` signature covers these bytes.
51
+ - `text`: `string`
52
+
53
+ The raw body as UTF-8.
54
+
55
+ <a id="type-json"></a>
56
+
57
+ ### `Json`
58
+
59
+ Type.
60
+
61
+ ```ts
62
+ type Json = null | boolean | number | string | readonly Json[] | { readonly [k: string]: Json };
63
+ ```
64
+
65
+ A JSON value: what a kind puts in an answer.
66
+
67
+ <a id="json"></a>
68
+
69
+ ### `json`
70
+
71
+ Function.
72
+
73
+ ```ts
74
+ function json(req: HookRequest): unknown
75
+ ```
76
+
77
+ The body as JSON. It throws when the body is not JSON. A throw from `read` is a 400, and the
78
+ hook log keeps the error.
79
+
80
+ **Parameters**
81
+
82
+ - `req` ([`HookRequest`](#type-hookrequest))
83
+
84
+ <a id="form"></a>
85
+
86
+ ### `form`
87
+
88
+ Function.
89
+
90
+ ```ts
91
+ function form(req: HookRequest): { readonly [field: string]: string | undefined }
92
+ ```
93
+
94
+ The body as `application/x-www-form-urlencoded` fields. When a field occurs more than one time,
95
+ the first value is the field's value.
96
+
97
+ **Parameters**
98
+
99
+ - `req` ([`HookRequest`](#type-hookrequest))
100
+
101
+ <a id="at"></a>
102
+
103
+ ### `at`
104
+
105
+ Function.
106
+
107
+ ```ts
108
+ function at(value: unknown, path: string): unknown
109
+ ```
110
+
111
+ The value at a dotted path (`sender.login`, `actions.0.action_id`) of a decoded body, or
112
+ `undefined`. It reads own properties only.
113
+
114
+ **Parameters**
115
+
116
+ - `value`
117
+ - `path`
118
+
119
+ <a id="type-signed"></a>
120
+
121
+ ### `Signed`
122
+
123
+ Type.
124
+
125
+ ```ts
126
+ type Signed = {
127
+ readonly bytes: readonly (string | Uint8Array)[];
128
+ readonly signature: { readonly hex: string } | { readonly base64: string };
129
+ readonly at?: number;
130
+ };
131
+ ```
132
+
133
+ What the service signed, from one request.
134
+
135
+ The door makes the checks. For `mac`, it first checks `at` against its clock, then sends the
136
+ bytes, the signature, the algorithm and the NAME of the Hook's secret to the Instance Effector,
137
+ which answers "match" or "no match" ([ADR-0068](decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). For `ed25519`, it checks `at`, then the
138
+ signature against the Hook's public key, in the Orchestrator.
139
+
140
+ **Fields**
141
+
142
+ - `bytes`: `readonly (string | Uint8Array)[]`
143
+
144
+ The bytes that the service signed, in sequence. The door joins them. A string is UTF-8.
145
+ GitHub: `[req.body]`. Slack: `["v0:", timestamp, ":", req.body]`.
146
+ - `signature`: `{ readonly hex: string; } | { readonly base64: string; }`
147
+
148
+ The signature, with no prefix (`sha256=`, `v0=`): the kind removes the prefix.
149
+ - `at` (optional): `number`
150
+
151
+ The service's time of the delivery, in seconds since the epoch. Give it ONLY when the
152
+ signature covers it (Slack, Discord). The door refuses a delivery whose `at` is more than
153
+ 300 s from its clock: 401. A kind cannot change the window.
154
+
155
+ <a id="type-unsigned"></a>
156
+
157
+ ### `Unsigned`
158
+
159
+ Type.
160
+
161
+ ```ts
162
+ type Unsigned = { readonly unsigned: string };
163
+ ```
164
+
165
+ The request does not hold what the verifier needs. The door answers 401 and logs `unsigned`.
166
+
167
+ **Fields**
168
+
169
+ - `unsigned`: `string`
170
+
171
+ <a id="type-macalg"></a>
172
+
173
+ ### `MacAlg`
174
+
175
+ Type.
176
+
177
+ ```ts
178
+ type MacAlg = "sha256" | "sha1" | "sha512";
179
+ ```
180
+
181
+ The HMAC algorithms that a `mac` verifier can name.
182
+
183
+ <a id="type-verify"></a>
184
+
185
+ ### `Verify`
186
+
187
+ Type.
188
+
189
+ ```ts
190
+ type Verify =
191
+ | {
192
+ readonly mac: {
193
+ readonly alg: MacAlg;
194
+ readonly signed: (req: HookRequest) => Signed | Unsigned;
195
+ };
196
+ }
197
+ | { readonly ed25519: { readonly signed: (req: HookRequest) => Signed | Unsigned } }
198
+ | { readonly token: { readonly header: string } };
199
+ ```
200
+
201
+ How the service signs. One of three.
202
+
203
+ **Forms**: it is one of these objects.
204
+
205
+ - Form 1: `mac`
206
+
207
+ - `mac`: `object`
208
+
209
+ - `alg`: [`MacAlg`](#type-macalg)
210
+ - `signed`: `(req: HookRequest) => Signed | Unsigned`
211
+ - Form 2: `ed25519`
212
+
213
+ - `ed25519`: `object`
214
+
215
+ - `signed`: `(req: HookRequest) => Signed | Unsigned`
216
+ - Form 3: `token`
217
+
218
+ - `token`: `object`
219
+
220
+ - `header`: `string`
221
+
222
+ <a id="type-hooksecret"></a>
223
+
224
+ ### `HookSecret`
225
+
226
+ Type.
227
+
228
+ ```ts
229
+ type HookSecret = { readonly secret: string };
230
+ ```
231
+
232
+ A named secret: the value of `secret("NAME")` from `@jr2/orchestrator`. The config holds the
233
+ name only. The value is in the Instance Effector (`mac`), or its digest is in the Orchestrator
234
+ (`token`).
235
+
236
+ **Fields**
237
+
238
+ - `secret`: `string`
239
+
240
+ <a id="type-credentialof"></a>
241
+
242
+ ### `CredentialOf`
243
+
244
+ Type.
245
+
246
+ ```ts
247
+ type CredentialOf<V extends Verify> = V extends { readonly ed25519: unknown }
248
+ ? { readonly publicKey: string }
249
+ : { readonly secret: HookSecret };
250
+ ```
251
+
252
+ What the Hook's config gives for each verifier. The kind never gets it.
253
+
254
+ <a id="type-hookcredential"></a>
255
+
256
+ ### `HookCredential`
257
+
258
+ Type.
259
+
260
+ ```ts
261
+ type HookCredential = { readonly secret: HookSecret } | { readonly publicKey: string };
262
+ ```
263
+
264
+ The credential of a Hook, for each of the three verifiers.
265
+
266
+ **Forms**: it is one of these objects.
267
+
268
+ - Form 1: `secret`
269
+
270
+ - `secret`: [`HookSecret`](#type-hooksecret)
271
+ - Form 2: `publicKey`
272
+
273
+ - `publicKey`: `string`
274
+
275
+ <a id="type-answer"></a>
276
+
277
+ ### `Answer`
278
+
279
+ Type.
280
+
281
+ ```ts
282
+ type Answer = {
283
+ readonly body: Json;
284
+ readonly then?: { readonly post: `https://${string}`; readonly json: Json };
285
+ };
286
+ ```
287
+
288
+ An answer that the kind writes. Its status is always 200: the door owns every other status.
289
+ `body`: an object or an array is JSON, a string is `text/plain`, `null` is an empty body.
290
+ `then`: one POST that the door makes AFTER the answer, through the Instance Effector's `http`,
291
+ with no secret, one time (the `response_url` of a Slack click). Its url must start with one of
292
+ the kind's `posts`. A failed post is a line in the hook log.
293
+
294
+ **Fields**
295
+
296
+ - `body`: [`Json`](#type-json)
297
+ - `then` (optional): `object`
298
+
299
+ - `post`: `` `https://${string}` ``
300
+ - `json`: [`Json`](#type-json)
301
+
302
+ <a id="type-handshake"></a>
303
+
304
+ ### `Handshake`
305
+
306
+ Type.
307
+
308
+ ```ts
309
+ type Handshake = { readonly handshake: Answer };
310
+ ```
311
+
312
+ The service's handshake (Slack `url_verification`, Discord `PING`, GitHub `ping`). It comes
313
+ after the verifier and gets this answer. It reaches no handler, and the hook log keeps it as
314
+ `handshake`.
315
+
316
+ **Fields**
317
+
318
+ - `handshake`: [`Answer`](#type-answer)
319
+
320
+ <a id="type-delivery"></a>
321
+
322
+ ### `Delivery`
323
+
324
+ Type.
325
+
326
+ ```ts
327
+ type Delivery = {
328
+ readonly key: string;
329
+ readonly id: string;
330
+ readonly payload: unknown;
331
+ readonly onBehalfOf?: string;
332
+ readonly readBy?: "service" | "person";
333
+ };
334
+ ```
335
+
336
+ One delivery, which the door gives to the Gates and the Sources whose handlers hear its kind and
337
+ `key`. The kind's `events` gives the schema for each key. A key that `events` does not name is
338
+ still a delivery: the door does not parse it, and no handler can hear it, so it reaches nothing.
339
+
340
+ **Fields**
341
+
342
+ - `key`: `string`
343
+
344
+ The event key. GitHub: `pull_request_review.submitted`.
345
+ - `id`: `string`
346
+
347
+ The delivery id. The door's repeat check uses it AND the digest of the body.
348
+ - `payload`: `unknown`
349
+
350
+ The decoded body. The signature covers it. The door parses it with the kind's schema for
351
+ `key` (see `events`), and a handler gets the schema's output.
352
+ - `onBehalfOf` (optional): `string`
353
+
354
+ The party that the service says acted. The signature covers it, so jr2 marks it checked.
355
+ - `readBy` (optional): `"service" | "person"`
356
+
357
+ Who reads the answer. `"service"` (the default): the answer is the outcome JSON with the
358
+ status that [ADR-0011](decisions/0011-workflow-defined-events.md) gives (429, 503). `"person"` (a chat command or a click): the door joins
359
+ the `reply` texts, cuts them to `maxText`, and calls the kind's `answer`. The status is 200.
360
+
361
+ <a id="type-hookkindapi"></a>
362
+
363
+ ### `HookKindApi`
364
+
365
+ Type.
366
+
367
+ ```ts
368
+ type HookKindApi = 1;
369
+ ```
370
+
371
+ The version of this interface. jr2 1.x calls each kind of `api: 1` as this module says.
372
+
373
+ <a id="hook-kind-apis"></a>
374
+
375
+ ### `HOOK_KIND_APIS`
376
+
377
+ Constant.
378
+
379
+ ```ts
380
+ const HOOK_KIND_APIS: readonly HookKindApi[]
381
+ ```
382
+
383
+ The versions of the interface that this copy of `@jr2/orchestrator/hooks` knows.
384
+
385
+ <a id="type-events"></a>
386
+
387
+ ### `Events`
388
+
389
+ Type.
390
+
391
+ ```ts
392
+ type Events = { readonly [key: string]: z.ZodType };
393
+ ```
394
+
395
+ The schema of each event key: `{ "pull_request_review.submitted": z.looseObject({ … }) }`.
396
+
397
+ - The door parses the payload of a delivery with the schema of its key, after the verifier and
398
+ before the fan-out. A failed parse reaches no handler: the hook log keeps a `payload-refused`
399
+ line with zod's issues, and the answer names the kind and the key. The status is 200, because
400
+ a second try fails in the same way.
401
+ - Use `z.looseObject` for each object. A loose object keeps the fields that it does not name, so
402
+ a new field from the service breaks nothing. A named field that is missing, or that has the
403
+ wrong type, fails the parse. Name only the fields that handlers need. A value that the service
404
+ can extend (a review's `state`) is a string, not an enum.
405
+ - A key that ends in `.*` is the schema of each key with that prefix: `"block_actions.*"` types
406
+ `block_actions.approve` and `block_actions.reject`. An exact key comes first, then the longest
407
+ prefix.
408
+
409
+ <a id="type-handlerkey"></a>
410
+
411
+ ### `HandlerKey`
412
+
413
+ Type.
414
+
415
+ ```ts
416
+ type HandlerKey<K> = K extends `${infer P}.*` ? `${P}.${string}` : K;
417
+ ```
418
+
419
+ A key of `events` as a handler's key: `"block_actions.*"` → `` `block_actions.${string}` ``.
420
+
421
+ <a id="type-named"></a>
422
+
423
+ ### `Named`
424
+
425
+ Type.
426
+
427
+ ```ts
428
+ type Named<T> = T extends readonly unknown[]
429
+ ? { readonly [I in keyof T]: Named<T[I]> }
430
+ : T extends object
431
+ ? { readonly [K in keyof T as string extends K ? never : K]: Named<T[K]> }
432
+ : T;
433
+ ```
434
+
435
+ The schema's output with only its named fields. The output of a loose object also has
436
+ `[field: string]: unknown`. The fields that it does not name stay in the value, but this type
437
+ removes the index signature, so a handler cannot read a field that its schema does not name.
438
+
439
+ <a id="type-payloads"></a>
440
+
441
+ ### `Payloads`
442
+
443
+ Type.
444
+
445
+ ```ts
446
+ type Payloads<E extends Events> = {
447
+ readonly [K in keyof E & string as HandlerKey<K>]: Named<z.output<E[K]>>;
448
+ };
449
+ ```
450
+
451
+ The payload type of each handler key of `events`.
452
+
453
+ <a id="type-hookkind"></a>
454
+
455
+ ### `HookKind`
456
+
457
+ Type.
458
+
459
+ ```ts
460
+ type HookKind<Id extends string = string, E extends Events = Events, V extends Verify = Verify> = {
461
+ readonly api: HookKindApi;
462
+ readonly id: Id;
463
+ readonly events: E;
464
+ readonly verify: V;
465
+ readonly read: (req: HookRequest) => Handshake | Delivery;
466
+ readonly answer?: (text: string, delivery: Delivery) => Answer;
467
+ readonly maxText?: number;
468
+ readonly posts?: readonly `https://${string}/`[];
469
+ };
470
+ ```
471
+
472
+ A Hook kind: one outside service's protocol.
473
+
474
+ - `api`: the version of this interface that the kind was written for. Only `1` exists.
475
+ - `id`: the key of the `hooks` of a Gate or a Source. One id is one kind in an Assembly.
476
+ Lowercase letters, digits and `-`, and it starts with a letter.
477
+ - `events`: a zod schema for each event key that a handler can hear ([`Events`](#type-events)).
478
+ - `verify`: one of the three verifiers, and what the service signed.
479
+ - `read`: the verified request, as a handshake or a delivery. A throw is 400, and the hook log
480
+ keeps the error.
481
+ - `answer`: the envelope of the text for a delivery with `readBy: "person"`. The door gives the
482
+ joined text, already cut to `maxText`. The kind escapes it. A kind with no `answer` cannot give
483
+ `readBy: "person"`: the type of `read` refuses it ([`defineHookKind`](#definehookkind)).
484
+ - `maxText`: the service's limit on the length of the text (Discord: 2000).
485
+ - `posts`: the url prefixes of `Answer.then`. `jr2 up` adds them to the Instance Effector's fence
486
+ for each Hook of this kind ([ADR-0068](decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)). The door refuses a `then` to another url.
487
+
488
+ **Fields**
489
+
490
+ - `api`: [`HookKindApi`](#type-hookkindapi)
491
+ - `id`: `Id`
492
+ - `events`: `E`
493
+ - `verify`: `V`
494
+ - `read`: `(req: HookRequest) => Handshake | Delivery`
495
+ - `answer` (optional): `(text: string, delivery: Delivery) => Answer`
496
+ - `maxText` (optional): `number`
497
+ - `posts` (optional): ``readonly `https://${string}/`[]``
498
+
499
+ <a id="type-hookmay"></a>
500
+
501
+ ### `HookMay`
502
+
503
+ Type.
504
+
505
+ ```ts
506
+ type HookMay = { readonly answer: "all" | readonly string[] };
507
+ ```
508
+
509
+ The Workflows whose Gates a Hook may answer ([ADR-0078](decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). The default is `"all"`. A Hook never
510
+ starts a run.
511
+
512
+ **Fields**
513
+
514
+ - `answer`: `"all" | readonly string[]`
515
+
516
+ <a id="type-hookparts"></a>
517
+
518
+ ### `HookParts`
519
+
520
+ Type.
521
+
522
+ ```ts
523
+ type HookParts<K extends HookKind = HookKind> = {
524
+ readonly kind: K;
525
+ readonly credential: HookCredential;
526
+ readonly may: HookMay;
527
+ };
528
+ ```
529
+
530
+ The parts of a Hook, which only the door reads.
531
+
532
+ **Fields**
533
+
534
+ - `kind`: `K`
535
+ - `credential`: [`HookCredential`](#type-hookcredential)
536
+ - `may`: [`HookMay`](#type-hookmay)
537
+
538
+ <a id="type-hook"></a>
539
+
540
+ ### `Hook`
541
+
542
+ Type.
543
+
544
+ ```ts
545
+ type Hook<K extends HookKind = HookKind> = { readonly [HOOK]: HookParts<K> };
546
+ ```
547
+
548
+ A Hook: a kind, a credential and `may`. It is the value of one entry of `hooks` in
549
+ `jr2.config.ts`, and the entry's key is its name, the last segment of `POST /hooks/:name`. Only
550
+ the door reads it.
551
+
552
+ **Fields**
553
+
554
+ - `[HOOK]`: `HookParts<K>`
555
+
556
+ <a id="type-hookkindof"></a>
557
+
558
+ ### `HookKindOf`
559
+
560
+ Type.
561
+
562
+ ```ts
563
+ type HookKindOf<Id extends string, E extends Events, V extends Verify> = HookKind<Id, E, V> &
564
+ ((hook: CredentialOf<V> & { readonly may?: HookMay }) => Hook<HookKind<Id, E, V>>);
565
+ ```
566
+
567
+ A kind is also the constructor of its Hooks: `github({ secret: secret("GH_HOOK"), may })`.
568
+
569
+ <a id="definehookkind"></a>
570
+
571
+ ### `defineHookKind`
572
+
573
+ Function.
574
+
575
+ #### Overload 1
576
+
577
+ ```ts
578
+ function defineHookKind<
579
+ const Id extends string,
580
+ const E extends Events,
581
+ const V extends Verify,
582
+ >(
583
+ kind: HookKind<Id, E, V> & { readonly answer: NonNullable<HookKind["answer"]> },
584
+ ): HookKindOf<Id, E, V>
585
+ ```
586
+
587
+ Define a kind. jr2 makes the constructor, so the credential goes from the config to the door and
588
+ never through code of the kind.
589
+
590
+ It throws on an `api` that it does not know, an `id` that is not lowercase, an `events` entry
591
+ that is not a schema, a verifier that is not one of the three, a `posts` entry that is not an
592
+ `https://` url that ends in `/`, and a `maxText` that is not a positive integer. Two different
593
+ kinds with one `id` fail the typecheck (`HookKinds`) and `jr2 up`.
594
+
595
+ Two overloads: a kind with `answer` can give `readBy: "person"`; a kind with no `answer` cannot.
596
+
597
+ **Parameters**
598
+
599
+ - `kind`
600
+
601
+ - `api`: [`HookKindApi`](#type-hookkindapi)
602
+ - `id`: `Id`
603
+ - `events`: `E`
604
+ - `verify`: `V`
605
+ - `read`: `(req: HookRequest) => Handshake | Delivery`
606
+ - `answer`: `(text: string, delivery: Delivery) => Answer`
607
+ - `maxText` (optional): `number`
608
+ - `posts` (optional): ``readonly `https://${string}/`[]``
609
+
610
+ #### Overload 2
611
+
612
+ ```ts
613
+ function defineHookKind<
614
+ const Id extends string,
615
+ const E extends Events,
616
+ const V extends Verify,
617
+ >(
618
+ kind: Omit<HookKind<Id, E, V>, "read" | "answer" | "maxText" | "posts"> & {
619
+ readonly read: (req: HookRequest) => Handshake | (Delivery & { readonly readBy?: "service" });
620
+ },
621
+ ): HookKindOf<Id, E, V>
622
+ ```
623
+
624
+ **Parameters**
625
+
626
+ - `kind`
627
+
628
+ - `id`: `Id`
629
+ - `api`: [`HookKindApi`](#type-hookkindapi)
630
+ - `events`: `E`
631
+ - `verify`: `V`
632
+ - `read`: `(req: HookRequest) => Handshake | (Delivery & { readonly readBy?: "service"; })`
633
+
634
+ <a id="type-hookkinds"></a>
635
+
636
+ ### `HookKinds`
637
+
638
+ Type.
639
+
640
+ #### Overload 1
641
+
642
+ ```ts
643
+ interface HookKinds {}
644
+ ```
645
+
646
+ Each kind module adds its id and its kind here by declaration merging.
647
+
648
+ #### Overload 2
649
+
650
+ ```ts
651
+ interface HookKinds {
652
+ github: typeof github;
653
+ }
654
+ ```
655
+
656
+ **Fields**
657
+
658
+ - `github`: `typeof github`
659
+
660
+ #### Overload 3
661
+
662
+ ```ts
663
+ interface HookKinds {
664
+ gitlab: typeof gitlab;
665
+ }
666
+ ```
667
+
668
+ **Fields**
669
+
670
+ - `gitlab`: `typeof gitlab`
671
+
672
+ #### Overload 4
673
+
674
+ ```ts
675
+ interface HookKinds {
676
+ slack: typeof slack;
677
+ }
678
+ ```
679
+
680
+ **Fields**
681
+
682
+ - `slack`: `typeof slack`
683
+
684
+ #### Overload 5
685
+
686
+ ```ts
687
+ interface HookKinds {
688
+ discord: typeof discord;
689
+ }
690
+ ```
691
+
692
+ **Fields**
693
+
694
+ - `discord`: `typeof discord`
695
+
696
+ <a id="type-eventsof"></a>
697
+
698
+ ### `EventsOf`
699
+
700
+ Type.
701
+
702
+ ```ts
703
+ type EventsOf<K> = K extends HookKind<string, infer E, Verify> ? Payloads<E> : never;
704
+ ```
705
+
706
+ The payload map of a kind: handler key → the output of its schema.
707
+
708
+ <a id="type-nowildcardkeys"></a>
709
+
710
+ ### `NoWildcardKeys`
711
+
712
+ Type.
713
+
714
+ ```ts
715
+ type NoWildcardKeys<K> =
716
+ K extends HookKind<string, infer E, Verify>
717
+ ? { readonly [Key in keyof E & string as Key extends `${string}*${string}` ? Key : never]?: never }
718
+ : never;
719
+ ```
720
+
721
+ A kind's `events` keys with `*`, which take no handler: the door matches a key exactly ([ADR-0011](decisions/0011-workflow-defined-events.md)).
722
+
723
+ <a id="type-payloadof"></a>
724
+
725
+ ### `PayloadOf`
726
+
727
+ Type.
728
+
729
+ ```ts
730
+ type PayloadOf<Id extends keyof HookKinds, Key extends keyof EventsOf<HookKinds[Id]>> = EventsOf<
731
+ HookKinds[Id]
732
+ >[Key];
733
+ ```
734
+
735
+ The payload of one handler key of a kind in `HookKinds`: `PayloadOf<"github", "issue_comment.created">`.
736
+ A forge part types its handlers with it.
737
+
738
+ <a id="type-kindhooks"></a>
739
+
740
+ ### `KindHooks`
741
+
742
+ Type.
743
+
744
+ ```ts
745
+ type KindHooks<In, Out> = {
746
+ readonly [Id in keyof HookKinds as HookKinds[Id] extends { readonly id: Id } ? Id : never]?: {
747
+ readonly [Key in keyof EventsOf<HookKinds[Id]>]?: (
748
+ d: In & { readonly payload: EventsOf<HookKinds[Id]>[Key] },
749
+ ) => Out;
750
+ } & NoWildcardKeys<HookKinds[Id]>;
751
+ };
752
+ ```
753
+
754
+ Handlers by kind id, then by event key. An entry of `HookKinds` whose key is not its kind's `id`
755
+ is left out, so no handler is keyed by a name that the door never matches. `In` is what the Gate
756
+ or the Source gives a handler beside `payload`; `Out` is what the handler gives. A key that the
757
+ kind's payloads do not name is an excess property: it does not compile. A key that holds `*`
758
+ does not compile ([`NoWildcardKeys`](#type-nowildcardkeys)).
759
+
760
+ **Fields**
761
+
762
+ - `github` (optional): `typeof github`
763
+ - `gitlab` (optional): `typeof gitlab`
764
+ - `slack` (optional): `typeof slack`
765
+ - `discord` (optional): `typeof discord`
766
+
767
+ ## `@jr2/orchestrator/hooks/github`
768
+
769
+ The kind of GitHub. It verifies with `mac`, over the raw body.
770
+
771
+ Each name in this group is declared in `@jr2/orchestrator/src/hook-kinds/github.ts`.
772
+
773
+ <a id="githubevents"></a>
774
+
775
+ ### `githubEvents`
776
+
777
+ Constant.
778
+
779
+ ```ts
780
+ const githubEvents: { "pull_request_review.submitted": …; "issue_comment.created": …; "check_suite.completed": … }
781
+ ```
782
+
783
+ The schema of each event key. Each names only the fields that handlers read; a loose object keeps
784
+ the rest. A value that GitHub can extend (a review's `state`) is a string, not an enum, so a new
785
+ value fails no parse.
786
+
787
+ <a id="github"></a>
788
+
789
+ ### `github`
790
+
791
+ Constant.
792
+
793
+ ```ts
794
+ const github: HookKindOf<…>
795
+ ```
796
+
797
+ Type: [`HookKindOf`](#type-hookkindof).
798
+
799
+ A GitHub repository, organization or App webhook, with the content type `application/json`. (A
800
+ webhook with the content type `application/x-www-form-urlencoded` gets 400.)
801
+ - verifier: `mac`, HMAC-SHA256 of the raw body, `X-Hub-Signature-256: sha256=<hex>`.
802
+ - handshake: the `ping` event (sent when the webhook is made) gets an empty 200.
803
+ - event key: `<X-GitHub-Event>.<action>`, or `<X-GitHub-Event>` when the body has no action.
804
+ - delivery id: `X-GitHub-Delivery`. `onBehalfOf`: `sender.login`.
805
+ - answer: the outcome (the default). No person reads it.
806
+
807
+ ## `@jr2/orchestrator/hooks/gitlab`
808
+
809
+ The kind of GitLab. It verifies with `token`, in the header `X-Gitlab-Token`.
810
+
811
+ Each name in this group is declared in `@jr2/orchestrator/src/hook-kinds/gitlab.ts`.
812
+
813
+ <a id="gitlabevents"></a>
814
+
815
+ ### `gitlabEvents`
816
+
817
+ Constant.
818
+
819
+ ```ts
820
+ const gitlabEvents: { "merge_request.approved": …; "note.MergeRequest": …; "pipeline.success": …; "pipeline.failed": … }
821
+ ```
822
+
823
+ The schema of each event key: the fields that handlers read, in loose objects.
824
+
825
+ <a id="gitlab"></a>
826
+
827
+ ### `gitlab`
828
+
829
+ Constant.
830
+
831
+ ```ts
832
+ const gitlab: HookKindOf<…>
833
+ ```
834
+
835
+ Type: [`HookKindOf`](#type-hookkindof).
836
+
837
+ A GitLab project or group webhook.
838
+ - verifier: `token`, the secret token in `X-Gitlab-Token`.
839
+ - handshake: none. A "Test" delivery from GitLab's settings is an ordinary delivery.
840
+ - event key: `<object_kind>.<the second part>`, or `<object_kind>` when it has no second part.
841
+ The second part is `object_attributes.noteable_type` for a `note`, and else
842
+ `object_attributes.action`, then `object_attributes.status`: `merge_request.approved`,
843
+ `note.MergeRequest`, `pipeline.success`.
844
+ - delivery id: `Idempotency-Key` (the same on a retry), else `X-Gitlab-Event-UUID`. (From
845
+ GitLab's docs; not checked against a real delivery.)
846
+ - `onBehalfOf`: `user.username`.
847
+ - answer: the outcome (the default). No person reads it.
848
+
849
+ ## `@jr2/orchestrator/hooks/slack`
850
+
851
+ The kind of one Slack app: its slash commands, its clicks and its Events API. It verifies with `mac`, over the signed time and the body.
852
+
853
+ Each name in this group is declared in `@jr2/orchestrator/src/hook-kinds/slack.ts`.
854
+
855
+ <a id="slackevents"></a>
856
+
857
+ ### `slackEvents`
858
+
859
+ Constant.
860
+
861
+ ```ts
862
+ const slackEvents: { "slash_command.*": …; "block_actions.*": …; "event_callback.app_mention": … }
863
+ ```
864
+
865
+ The schema of each event key. A key with `.*` covers a family: the command or the action id is
866
+ part of the key, so one schema types `block_actions.approve` and `block_actions.reject`.
867
+
868
+ <a id="slack"></a>
869
+
870
+ ### `slack`
871
+
872
+ Constant.
873
+
874
+ ```ts
875
+ const slack: HookKindOf<…>
876
+ ```
877
+
878
+ Type: [`HookKindOf`](#type-hookkindof).
879
+
880
+ - verifier: `mac`, HMAC-SHA256 of `v0:<X-Slack-Request-Timestamp>:<raw body>`,
881
+ `X-Slack-Signature: v0=<hex>`. The signature covers the timestamp, so the kind gives it as
882
+ `at`, and the door's 300 s window applies (Slack asks for 5 minutes).
883
+ - handshake: `url_verification` gets `{ challenge }`. The door verifies it first. (Slack's docs
884
+ do not say that Slack signs it; check it at the first real setup.)
885
+ - body: a command is a form; a click is a form with JSON in `payload`; an event is JSON.
886
+ - event keys: `slash_command./jr2`, `block_actions.<action_id>`, `event_callback.<event.type>`.
887
+ - delivery id: `event_id` for an event, `trigger_id` for a command or a click. (Slack's docs do
888
+ not say that a `trigger_id` is unique for each request; the door's repeat check also uses the
889
+ digest of the body.)
890
+ - `onBehalfOf`: the Slack user id, never a display name.
891
+ - who reads the answer: a person for a command or a click; Slack for an event (so a 503 stays a
892
+ 503, and Slack sends the event again).
893
+ - answer: ephemeral text. The answer to a click can only be an empty 200 (Block Kit), so the text
894
+ goes to the click's `response_url` as the answer's `then`.
895
+ - text limit: 4000 characters, the length that Slack's docs advise for a message's `text`. (Not
896
+ checked for the answer to a command or a `response_url` post.)
897
+
898
+ ## `@jr2/orchestrator/hooks/discord`
899
+
900
+ The kind of a Discord application's Interactions Endpoint URL. It verifies with `ed25519`, over the signed time and the body.
901
+
902
+ Each name in this group is declared in `@jr2/orchestrator/src/hook-kinds/discord.ts`.
903
+
904
+ <a id="discordevents"></a>
905
+
906
+ ### `discordEvents`
907
+
908
+ Constant.
909
+
910
+ ```ts
911
+ const discordEvents: { "application_command.*": …; message_component: … }
912
+ ```
913
+
914
+ The schema of each event key. A command's name is part of its key, so `.*` covers them all.
915
+
916
+ <a id="discord"></a>
917
+
918
+ ### `discord`
919
+
920
+ Constant.
921
+
922
+ ```ts
923
+ const discord: HookKindOf<…>
924
+ ```
925
+
926
+ Type: [`HookKindOf`](#type-hookkindof).
927
+
928
+ - verifier: `ed25519` over `<X-Signature-Timestamp><raw body>`, `X-Signature-Ed25519` in hex,
929
+ checked against the application's PUBLIC key in the Orchestrator. The signature covers the
930
+ timestamp, so the kind gives it as `at`. Discord states no window; the door's 300 s window
931
+ applies to each signed `at`, so it applies here too.
932
+ - handshake: `PING` (type 1) gets `{ type: 1 }` (PONG).
933
+ - event keys: `application_command.<data.name>` (type 2), `message_component` (type 3). Another
934
+ type (an autocomplete, a modal) gets the key `interaction.<type>`, which has no schema, so it
935
+ reaches nothing.
936
+ - delivery id: the interaction's `id`. `onBehalfOf`: `member.user.id`, or `user.id` in a direct
937
+ message. (Discord's docs do not say that Discord sends an interaction again.)
938
+ - who reads the answer: a person, always.
939
+ - answer: `{ type: 4, data: { content, flags: 64 } }`, an ephemeral message. Discord shows
940
+ markdown, not HTML, so the text needs no escape. Text limit: 2000 characters.
941
+
942
+ ## `@jr2/orchestrator/hooks/webhook`
943
+
944
+ `webhook()` makes a kind from data, for a service that has no kind: a verifier over the raw body, the event key, the delivery id, `onBehalfOf`, and a schema for each event.
945
+
946
+ Each name in this group is declared in `@jr2/orchestrator/src/hook-kinds/webhook.ts`.
947
+
948
+ <a id="type-field"></a>
949
+
950
+ ### `Field`
951
+
952
+ Type.
953
+
954
+ ```ts
955
+ type Field = { readonly header: string } | { readonly json: string };
956
+ ```
957
+
958
+ Where a value is in a delivery: a header (lowercase), or a dotted path in the JSON body.
959
+
960
+ **Forms**: it is one of these objects.
961
+
962
+ - Form 1: `header`
963
+
964
+ - `header`: `string`
965
+ - Form 2: `json`
966
+
967
+ - `json`: `string`
968
+
969
+ <a id="type-webhookverify"></a>
970
+
971
+ ### `WebhookVerify`
972
+
973
+ Type.
974
+
975
+ ```ts
976
+ type WebhookVerify =
977
+ | {
978
+ readonly mac: {
979
+ readonly header: string;
980
+ readonly alg?: MacAlg;
981
+ readonly prefix?: string;
982
+ readonly encoding?: "hex" | "base64";
983
+ };
984
+ }
985
+ | { readonly token: { readonly header: string } };
986
+ ```
987
+
988
+ How the general kind verifies. `mac` is over the raw body only.
989
+
990
+ **Forms**: it is one of these objects.
991
+
992
+ - Form 1: `mac`
993
+
994
+ - `mac`: `object`
995
+
996
+ - `header`: `string`
997
+
998
+ The header of the signature (lowercase).
999
+ - `alg` (optional): [`MacAlg`](#type-macalg)
1000
+
1001
+ `sha256` when it is not given.
1002
+ - `prefix` (optional): `string`
1003
+
1004
+ The text before the signature in the header's value, such as `sha256=`.
1005
+ - `encoding` (optional): `"hex" | "base64"`
1006
+
1007
+ `hex` when it is not given.
1008
+ - Form 2: `token`
1009
+
1010
+ - `token`: `object`
1011
+
1012
+ - `header`: `string`
1013
+
1014
+ <a id="webhook"></a>
1015
+
1016
+ ### `webhook`
1017
+
1018
+ Function.
1019
+
1020
+ ```ts
1021
+ function webhook<
1022
+ const Id extends string,
1023
+ const E extends Events,
1024
+ const W extends WebhookVerify,
1025
+ >(
1026
+ spec: {
1027
+ readonly id: Id;
1028
+ readonly events: E;
1029
+ readonly verify: W;
1030
+ readonly key: Field | { readonly name: Field; readonly action?: Field };
1031
+ readonly delivery: Field;
1032
+ readonly onBehalfOf?: Field;
1033
+ },
1034
+ ): HookKindOf<Id, E, VerifyOf<W>>
1035
+ ```
1036
+
1037
+ A kind from data.
1038
+ - `id`: the kind's id. The Instance chooses it; it must not be the id of another kind.
1039
+ - `events`: a zod schema for each event key that a handler can hear. The door parses with it, as
1040
+ for each kind (`Events` in `@jr2/orchestrator/hooks`).
1041
+ - `verify`: `mac` over the raw body (the header, its prefix, the algorithm, the encoding), or
1042
+ `token` in a header.
1043
+ - `key`: the event key, from one field; or `{ name, action }`, which gives `<name>.<action>`, or
1044
+ `<name>` when the delivery has no action.
1045
+ - `delivery`: the delivery id. `onBehalfOf`: optional.
1046
+
1047
+ The answer is the outcome JSON.
1048
+
1049
+ **Parameters**
1050
+
1051
+ - `spec`
1052
+
1053
+ - `id`: `Id`
1054
+ - `events`: `E`
1055
+ - `verify`: `W`
1056
+ - `key`: `Field | { readonly name: Field; readonly action?: Field; }`
1057
+ - `delivery`: [`Field`](#type-field)
1058
+ - `onBehalfOf` (optional): [`Field`](#type-field)
1059
+
1060
+ ## Types these signatures name
1061
+
1062
+ A signature above names these types, or a type here names them. They are not exports of `@jr2/orchestrator`, so a workflow module does not import them by name. With them, each kit type that this page shows has a definition on a Reference page.
1063
+
1064
+ <a id="type-verifyof"></a>
1065
+
1066
+ ### `VerifyOf`
1067
+
1068
+ ```ts
1069
+ type VerifyOf<W extends WebhookVerify> = W extends { readonly mac: unknown }
1070
+ ? Extract<Verify, { readonly mac: unknown }>
1071
+ : Extract<Verify, { readonly token: unknown }>;
1072
+ ```
1073
+
1074
+ These names come from a dependency:
1075
+
1076
+ - `z.ZodType`: from `zod`
1077
+ - `z.output`: from `zod`