@jr2/orchestrator 0.4.1 → 0.6.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 (361) hide show
  1. package/README.md +20 -2
  2. package/console/components/app.ts +2 -0
  3. package/console/components/fleet.ts +14 -1
  4. package/console/components/machine-pane.ts +41 -2
  5. package/console/main.ts +13 -1
  6. package/console/store.ts +102 -6
  7. package/console/style.css +21 -0
  8. package/docs/agents-block.md +5 -0
  9. package/docs/concepts/agents-and-turns.md +478 -0
  10. package/docs/concepts/architecture.md +243 -0
  11. package/docs/concepts/charts.md +146 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +257 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +658 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +298 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +306 -0
  20. package/docs/concepts/the-menu.md +381 -0
  21. package/docs/concepts/workspaces.md +756 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +383 -0
  24. package/docs/get-started/connect-a-model.md +248 -0
  25. package/docs/get-started/create-an-instance.md +194 -0
  26. package/docs/get-started/have-your-assistant-write-a-machine.md +993 -0
  27. package/docs/get-started/install.md +130 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +308 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +224 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/cut-a-worktree.md +560 -0
  34. package/docs/guides/fix-a-failed-converge.md +361 -0
  35. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  36. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  37. package/docs/guides/let-an-agent-write-a-chart.md +230 -0
  38. package/docs/guides/look-inside-a-sandbox.md +177 -0
  39. package/docs/guides/mirror-the-kit-images.md +176 -0
  40. package/docs/guides/move-from-kind-to-a-shared-cluster.md +318 -0
  41. package/docs/guides/open-the-console.md +91 -0
  42. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  43. package/docs/guides/push-and-sign-a-branch.md +532 -0
  44. package/docs/guides/run-a-chart-by-name.md +270 -0
  45. package/docs/guides/run-a-command-or-call-a-url.md +587 -0
  46. package/docs/guides/run-one-worker-per-item.md +322 -0
  47. package/docs/guides/set-an-agents-access-and-dials.md +420 -0
  48. package/docs/guides/size-a-workspace.md +217 -0
  49. package/docs/guides/start-a-run-from-a-run.md +237 -0
  50. package/docs/guides/start-a-run.md +207 -0
  51. package/docs/guides/upgrade-the-kit-version.md +240 -0
  52. package/docs/guides/use-a-hosted-provider.md +214 -0
  53. package/docs/guides/use-a-local-model.md +313 -0
  54. package/docs/guides/watch-a-run.md +349 -0
  55. package/docs/guides/write-a-workflow.md +318 -0
  56. package/docs/index.md +44 -0
  57. package/docs/llms.txt +237 -0
  58. package/docs/reference/authoring-surface/agent.md +151 -0
  59. package/docs/reference/authoring-surface/answer.md +156 -0
  60. package/docs/reference/authoring-surface/arg.md +82 -0
  61. package/docs/reference/authoring-surface/basic.md +62 -0
  62. package/docs/reference/authoring-surface/bearer.md +53 -0
  63. package/docs/reference/authoring-surface/branchname.md +15 -0
  64. package/docs/reference/authoring-surface/chartschema.md +23 -0
  65. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  66. package/docs/reference/authoring-surface/command.md +206 -0
  67. package/docs/reference/authoring-surface/customize.md +64 -0
  68. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  69. package/docs/reference/authoring-surface/defineevent.md +114 -0
  70. package/docs/reference/authoring-surface/dispatch.md +126 -0
  71. package/docs/reference/authoring-surface/fail.md +96 -0
  72. package/docs/reference/authoring-surface/file.md +142 -0
  73. package/docs/reference/authoring-surface/frompick.md +277 -0
  74. package/docs/reference/authoring-surface/http.md +259 -0
  75. package/docs/reference/authoring-surface/isfailure.md +58 -0
  76. package/docs/reference/authoring-surface/isready.md +14 -0
  77. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  78. package/docs/reference/authoring-surface/open.md +105 -0
  79. package/docs/reference/authoring-surface/openbody.md +86 -0
  80. package/docs/reference/authoring-surface/paramsof.md +143 -0
  81. package/docs/reference/authoring-surface/pool.md +186 -0
  82. package/docs/reference/authoring-surface/push.md +130 -0
  83. package/docs/reference/authoring-surface/readyof.md +21 -0
  84. package/docs/reference/authoring-surface/secret.md +139 -0
  85. package/docs/reference/authoring-surface/sentby.md +83 -0
  86. package/docs/reference/authoring-surface/source.md +51 -0
  87. package/docs/reference/authoring-surface/text.md +62 -0
  88. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  89. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  90. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  91. package/docs/reference/authoring-surface/type-agentslots.md +15 -0
  92. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  93. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  94. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  95. package/docs/reference/authoring-surface/type-arg.md +19 -0
  96. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  97. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  98. package/docs/reference/authoring-surface/type-binding.md +61 -0
  99. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  100. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  101. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  102. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  103. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  104. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  105. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  106. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  107. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  108. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  109. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  110. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  111. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  112. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  113. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  114. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  115. package/docs/reference/authoring-surface/type-childat.md +12 -0
  116. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  117. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  118. package/docs/reference/authoring-surface/type-commandinput.md +20 -0
  119. package/docs/reference/authoring-surface/type-commandoptions.md +56 -0
  120. package/docs/reference/authoring-surface/type-commandoutput.md +122 -0
  121. package/docs/reference/authoring-surface/type-customize.md +111 -0
  122. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  123. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  124. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  125. package/docs/reference/authoring-surface/type-dispatchoutput.md +72 -0
  126. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  127. package/docs/reference/authoring-surface/type-duration.md +15 -0
  128. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  129. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  130. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  131. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  132. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  133. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  134. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  135. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  136. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  137. package/docs/reference/authoring-surface/type-failure.md +19 -0
  138. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  139. package/docs/reference/authoring-surface/type-filebody.md +37 -0
  140. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  141. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  142. package/docs/reference/authoring-surface/type-hostinjectedinput.md +56 -0
  143. package/docs/reference/authoring-surface/type-httpdefinition.md +104 -0
  144. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  145. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  146. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  147. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +95 -0
  149. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  150. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  151. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  152. package/docs/reference/authoring-surface/type-lends.md +38 -0
  153. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  154. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  155. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  156. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  157. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  158. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +27 -0
  159. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  160. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  161. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  162. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  163. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  164. package/docs/reference/authoring-surface/type-opencommandinput.md +22 -0
  165. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  166. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  167. package/docs/reference/authoring-surface/type-openof.md +12 -0
  168. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  169. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  170. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +26 -0
  171. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +27 -0
  172. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  173. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  174. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  175. package/docs/reference/authoring-surface/type-pushinput.md +34 -0
  176. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  177. package/docs/reference/authoring-surface/type-pushoutput.md +149 -0
  178. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  179. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  180. package/docs/reference/authoring-surface/type-reached.md +12 -0
  181. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  182. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  183. package/docs/reference/authoring-surface/type-sandboxoptions.md +84 -0
  184. package/docs/reference/authoring-surface/type-secret.md +25 -0
  185. package/docs/reference/authoring-surface/type-sender.md +19 -0
  186. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  187. package/docs/reference/authoring-surface/type-size.md +85 -0
  188. package/docs/reference/authoring-surface/type-slothandles.md +24 -0
  189. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  190. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  191. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  192. package/docs/reference/authoring-surface/type-text.md +19 -0
  193. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  194. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  195. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  196. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  197. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  198. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  199. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  200. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  201. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  202. package/docs/reference/authoring-surface/type-workspaced.md +115 -0
  203. package/docs/reference/authoring-surface/type-workspacehandles.md +22 -0
  204. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  205. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  206. package/docs/reference/authoring-surface/type-workspaceoptions.md +27 -0
  207. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  208. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  209. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  210. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  211. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  212. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  213. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  214. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  215. package/docs/reference/authoring-surface/workspace.md +287 -0
  216. package/docs/reference/authoring-surface/worktree.md +71 -0
  217. package/docs/reference/authoring-surface.md +217 -0
  218. package/docs/reference/cli.md +433 -0
  219. package/docs/reference/config.md +765 -0
  220. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +114 -0
  221. package/docs/reference/decisions/0002-duplex-streaming-actor.md +117 -0
  222. package/docs/reference/decisions/0004-worktree-provisioning-model.md +130 -0
  223. package/docs/reference/decisions/0005-sandbox-pod-composition.md +240 -0
  224. package/docs/reference/decisions/0006-agent-control-surface.md +50 -0
  225. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  226. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  227. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  228. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  229. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  230. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  231. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +175 -0
  232. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  233. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +271 -0
  234. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +126 -0
  235. package/docs/reference/decisions/0017-pool-over-a-source-port.md +71 -0
  236. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  237. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +287 -0
  238. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  239. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +101 -0
  240. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  241. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +175 -0
  242. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +436 -0
  243. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  244. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  245. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +245 -0
  246. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +90 -0
  247. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  248. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  249. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +193 -0
  250. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  251. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +126 -0
  252. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  253. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  254. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  255. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  256. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  257. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  258. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  259. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  260. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  261. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  262. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  263. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  264. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  265. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  266. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  267. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  268. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +238 -0
  269. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +86 -0
  270. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +123 -0
  271. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  272. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  273. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  274. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  275. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +108 -0
  276. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +427 -0
  277. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  278. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  279. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +239 -0
  280. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +82 -0
  281. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  282. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  283. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +552 -0
  284. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +458 -0
  285. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +475 -0
  286. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  287. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +334 -0
  288. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  289. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  290. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  291. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  292. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +295 -0
  293. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +320 -0
  294. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  295. package/docs/reference/decisions.md +86 -0
  296. package/docs/reference/glossary.md +687 -0
  297. package/docs/reference/http-api.md +1386 -0
  298. package/docs/reference/machines.md +189 -0
  299. package/docs/reference/mechanism-events.md +72 -0
  300. package/docs/why.md +207 -0
  301. package/package.json +5 -3
  302. package/src/actor.ts +694 -106
  303. package/src/agent.ts +114 -38
  304. package/src/ambient.ts +3 -4
  305. package/src/assembly-checks.ts +189 -7
  306. package/src/cause.ts +234 -0
  307. package/src/chart.ts +345 -46
  308. package/src/charts.ts +43 -8
  309. package/src/config.ts +440 -178
  310. package/src/custodian.lua +17 -5
  311. package/src/custodian.ts +325 -99
  312. package/src/customize.ts +199 -49
  313. package/src/dispatch-effect.ts +945 -0
  314. package/src/door-compat.ts +217 -0
  315. package/src/durability.ts +11 -4
  316. package/src/effect-checks.ts +50 -3
  317. package/src/effect.ts +641 -128
  318. package/src/effector-wire.ts +165 -13
  319. package/src/fail.ts +222 -0
  320. package/src/harness-client.ts +587 -50
  321. package/src/harness-env.ts +33 -0
  322. package/src/held-secrets.ts +125 -16
  323. package/src/holds.ts +3 -0
  324. package/src/http-effect.ts +556 -58
  325. package/src/http.ts +480 -30
  326. package/src/index.ts +74 -82
  327. package/src/instance.ts +68 -8
  328. package/src/internal.ts +85 -0
  329. package/src/kube-client.ts +6 -3
  330. package/src/machine-doc.ts +24 -7
  331. package/src/mechanism-routes.ts +168 -0
  332. package/src/model-keys.generated.ts +53 -0
  333. package/src/model-keys.ts +42 -58
  334. package/src/names.ts +19 -6
  335. package/src/notices.ts +483 -41
  336. package/src/open-body.ts +242 -40
  337. package/src/open-effect.ts +81 -8
  338. package/src/open.ts +90 -6
  339. package/src/parts.ts +213 -30
  340. package/src/party.ts +101 -2
  341. package/src/pick.ts +497 -11
  342. package/src/pool.ts +282 -50
  343. package/src/registration.ts +133 -12
  344. package/src/repo-identity.ts +7 -3
  345. package/src/repos.ts +22 -3
  346. package/src/run-host.ts +665 -45
  347. package/src/run-output.ts +38 -0
  348. package/src/sandbox-effect.ts +457 -71
  349. package/src/sandbox-kube.ts +66 -17
  350. package/src/secret-placement.ts +59 -17
  351. package/src/server.ts +3 -0
  352. package/src/setup.ts +325 -62
  353. package/src/size.ts +121 -9
  354. package/src/snapshot-store.ts +54 -10
  355. package/src/stub-harness.ts +44 -16
  356. package/src/timers.ts +14 -1
  357. package/src/tokens.ts +107 -32
  358. package/src/vocabulary.ts +88 -14
  359. package/src/wire.ts +100 -26
  360. package/src/workspace.ts +771 -238
  361. package/src/worktree.ts +249 -0
@@ -14,6 +14,7 @@
14
14
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
15
15
  import { lookup } from "node:dns/promises";
16
16
  import { z } from "zod";
17
+ import type { LaterOutcome } from "./effect.ts";
17
18
 
18
19
  // --- The request (POST /effects/<id>) ------------------------------------------------------------
19
20
  //
@@ -21,11 +22,14 @@ import { z } from "zod";
21
22
 
22
23
  export const HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
23
24
  export const HttpMethodShape = z.enum(HTTP_METHODS);
25
+ /** The method of an `http` Effect: `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`. */
24
26
  export type HttpMethod = z.infer<typeof HttpMethodShape>;
25
27
 
26
28
  /** One part of a string the Effector builds: literal text, or a secret it holds, by name. A
27
29
  * secret's value is never on the wire. */
28
30
  export const TextPartShape = z.union([z.strictObject({ text: z.string() }), z.strictObject({ secret: z.string() })]);
31
+ /** One part of a {@link Text}: `{ text }`, literal text, or `{ secret }`, the name of an Effect
32
+ * secret. The `text` template makes the parts. */
29
33
  export type TextPart = z.infer<typeof TextPartShape>;
30
34
 
31
35
  export const WireAuthShape = z.union([
@@ -71,7 +75,8 @@ const HEADER_NAME = /^[a-z0-9!#$%&'*+.^_`|~-]+$/;
71
75
  export const isEffectHeader = (name: string): boolean =>
72
76
  HEADER_NAME.test(name) && !FRAMING_HEADERS.has(name) && !name.startsWith("proxy-");
73
77
 
74
- /** An `http` start; its body may be a Worktree file (ADR-0067), which only a Sandbox Effector serves. */
78
+ /** An `http` start; its body may be a Worktree file (ADR-0067), which only a Sandbox Effector
79
+ * serves. */
75
80
  export const HttpStartShape = z.strictObject({
76
81
  kind: z.literal("http"),
77
82
  method: HttpMethodShape,
@@ -100,10 +105,11 @@ export const PairShape = z.strictObject({
100
105
  });
101
106
  export type Pair = z.infer<typeof PairShape>;
102
107
 
103
- /** A `push` start: the Workspace's branch to the Repo Slot's Binding, with the push credential of
108
+ /** A `push` start: a cut Worktree's branch to the Repo Slot's Binding, with the push credential of
104
109
  * the `git.credentials` entry the url falls under — the Effector's config holds the entries, and
105
110
  * matches the url as the fence does (`credential` names one by its `match` instead). `pin` is the
106
- * commit; absent, the Effector reads the Agent's branch tip and its answer carries it (`pinned`). */
111
+ * commit; absent, the Effector reads the branch's tip and its answer carries it (`pinned`).
112
+ */
107
113
  export const PushStartShape = z.strictObject({
108
114
  kind: z.literal("push"),
109
115
  push: z.strictObject({
@@ -142,8 +148,71 @@ export const CommandStartShape = z.strictObject({
142
148
  });
143
149
  export type CommandStartRequest = z.infer<typeof CommandStartShape>;
144
150
 
145
- /** Every start: the http kinds, a push, a credentialed command. */
146
- export const StartRequestShape = z.union([HttpStartShape, PushStartShape, CommandStartShape]);
151
+ /** A string the Effector builds, as parts (the `text` template's wire form). */
152
+ export const TextShape = z.array(TextPartShape);
153
+
154
+ /** A secret the Effector holds, by name. */
155
+ export const SecretRefShape = z.strictObject({ secret: z.string() });
156
+
157
+ /** A Workflow's name as `jr2 run` names it (ADR-0071): a Machine's filename stem, or
158
+ * `<wrapper>/<name>` for a named Chart. The Go side's `Validate` holds the same rule. */
159
+ export const WORKFLOW_NAME = /^[a-z0-9][a-z0-9-]*(\/[a-z0-9][a-z0-9-]*)?$/;
160
+
161
+ /** A dispatch target's origin: a scheme and a host — a DNS name or IPv4 address, or a bracketed
162
+ * IPv6 address — a port when it is not the scheme's, and no path, query, fragment, userinfo or any
163
+ * other character no origin has — the Effector appends the door's path (ADR-0071). The Go side's
164
+ * `origin` is the same rule. */
165
+ export const ORIGIN = /^https?:\/\/(\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9.-]+)(:[0-9]{1,5})?$/;
166
+
167
+ /**
168
+ * Which run started a run, and its place in the chain (ADR-0071): the caller's run and slot, the
169
+ * caller's ledger row id (`effect`, the door's idempotency key), the dispatched run's depth — one
170
+ * below its caller, stated by the caller and recorded as sent, so at least 1 — and, for a caller
171
+ * on another Orchestrator, that Instance as opaque text.
172
+ */
173
+ export const DispatchedByShape = z.strictObject({
174
+ run: z.string(),
175
+ slot: z.string(),
176
+ effect: z.string(),
177
+ depth: z.number().int().positive(),
178
+ instance: z.string().optional(),
179
+ });
180
+
181
+ /**
182
+ * A `dispatch` start (ADR-0071), which only the Instance Effector serves: a run of `workflow` on the
183
+ * Orchestrator at `url` (its origin, one text part), the body `input` whole, the Instance token as a
184
+ * bearer by name, `dispatchedBy` as the `jr2-dispatched-by` header — `instance` set, since the
185
+ * caller is foreign by construction — and, with `wait`, a poll of `GET /runs/<id>` until the run
186
+ * ends or `timeoutMs` passes.
187
+ */
188
+ export const DispatchStartShape = z.strictObject({
189
+ kind: z.literal("dispatch"),
190
+ url: TextShape.refine(
191
+ (parts) => parts.length === 1 && "text" in parts[0]! && ORIGIN.test(parts[0].text),
192
+ "a dispatch url is one text part, an origin: http(s)://host[:port]",
193
+ ),
194
+ workflow: z.string().regex(WORKFLOW_NAME),
195
+ input: z.record(z.string(), z.unknown()),
196
+ auth: z.strictObject({ bearer: SecretRefShape }),
197
+ // Every id non-empty, as the door's header shape requires (http.ts): an empty one would be the
198
+ // door's 400, which reads as a defect of the input. The effect is a ledger row id, the key. The Go
199
+ // side's `validateDispatch` holds the same rule.
200
+ dispatchedBy: DispatchedByShape.extend({
201
+ run: z.string().min(1),
202
+ slot: z.string().min(1),
203
+ effect: z
204
+ .string()
205
+ .regex(/^[A-Za-z0-9._-]{1,128}$/)
206
+ .refine((id) => id !== "." && id !== ".."),
207
+ instance: z.string().min(1),
208
+ }),
209
+ wait: z.boolean(),
210
+ timeoutMs: z.number().int().positive(),
211
+ });
212
+ export type DispatchStartRequest = z.infer<typeof DispatchStartShape>;
213
+
214
+ /** Every start: the http kinds, a push, a credentialed command, a dispatch. */
215
+ export const StartRequestShape = z.union([HttpStartShape, PushStartShape, CommandStartShape, DispatchStartShape]);
147
216
  export type StartRequest = z.infer<typeof StartRequestShape>;
148
217
 
149
218
  /** The most bytes of a start's body the Effector reads (its `maxRequestBody`). A longer body
@@ -175,17 +244,70 @@ export const HttpOutputShape = z.union([
175
244
  HttpResponseShape,
176
245
  z.strictObject({ outcome: z.enum(["unreachable", "refused", "unknown"]), reason: z.string() }),
177
246
  ]);
178
- export type HttpOutput = z.infer<typeof HttpOutputShape>;
247
+ /**
248
+ * The output of an `http` Effect: what the `onDone` of its state gets as `event.output`. Every
249
+ * outcome is output. A 4xx or 5xx status is output too. Route on `outcome`:
250
+ *
251
+ * - `response`: an answer came back. `status` is its HTTP status. `headers`, `contentType` and
252
+ * `size` tell about the body. `body` is there when the body is text: at most `maxBody` bytes
253
+ * (`truncated` is `true` when jr2 cut it). A body that is not text gives only its size and
254
+ * content type. jr2 replaces each value of an Effect secret that the Effector holds, and its
255
+ * usual encodings, with `[secret:<name>]`, in the body and the headers.
256
+ * - `unreachable`: the call did not go out, for example because the name did not resolve, or the
257
+ * connection or TLS failed. `reason` tells why.
258
+ * - `refused`: jr2 or the Effector refused the call, and nothing was sent. For example, a path
259
+ * segment that is `.`, `..` or empty, or a request that is too large. `reason` tells why.
260
+ * - `unknown`: the call can have gone out, but no answer came, or jr2 cannot tell after a restart.
261
+ * `reason` tells why. jr2 does not send it again unless it is marked `idempotent`.
262
+ *
263
+ * The union is open (`LaterOutcome`): give the rest a last arm with no guard.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * import { fail, http, jr2Setup, type HttpOutput } from "@jr2/orchestrator";
268
+ *
269
+ * const ok = (output: HttpOutput): boolean => output.outcome === "response" && output.status < 300;
270
+ *
271
+ * export const machine = jr2Setup({
272
+ * actors: { health: http({ method: "GET", url: "https://app.example.com/healthz" }), fail: fail() },
273
+ * }).createMachine({
274
+ * id: "health",
275
+ * initial: "checking",
276
+ * states: {
277
+ * checking: {
278
+ * invoke: {
279
+ * src: "health",
280
+ * onDone: [
281
+ * { guard: ({ event }) => ok(event.output), target: "healthy" },
282
+ * { target: "unhealthy" },
283
+ * ],
284
+ * },
285
+ * },
286
+ * healthy: { type: "final" },
287
+ * // Not a final state: the run ends `failed`, and `jr2 run` exits 1.
288
+ * unhealthy: { invoke: { src: "fail", input: { reason: "the app is not healthy" } } },
289
+ * },
290
+ * });
291
+ * ```
292
+ *
293
+ * @see docs/concepts/effects.md
294
+ * @see docs/guides/run-a-command-or-call-a-url.md
295
+ * @see docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md
296
+ */
297
+ export type HttpOutput = z.infer<typeof HttpOutputShape> | LaterOutcome;
179
298
 
180
299
  /**
181
300
  * A `push` Effect's output (ADR-0067). `pushed`: the published commit, its source (the Agent's
182
301
  * commit), what the remote held before, how the new commits were found after a signed push
183
- * (`ancestry | pair | cherry | force`), `unchanged` when the remote held it already, `squashed`
184
- * when the published commit is one squashed commit (`signed | unsigned`; absent for signed
185
- * copies; what the published commit is, an earlier push's included, never only this push's
186
- * options), and — when the published commit is not the Agent's own — the published commits as a
187
- * bundle for the sync. `rejected`: a stage and git's words. `diverged`: the
188
- * published commit is not in the Agent's history and nothing paired it; nothing was pushed.
302
+ * (`ancestry | pair | cherry | force`), `unchanged` when the remote held it already, `newCommits`
303
+ * (how many commits the published commit adds to what jr2 found held, the remote's branch and the
304
+ * Repo cache; 0 when jr2 found the published commit already held, which the Agent reads in words,
305
+ * menu.ts), `squashed` when the published
306
+ * commit is one squashed commit (`signed | unsigned`; absent for signed copies; what the published
307
+ * commit is, an earlier push's included, never only this push's options), and — when the published
308
+ * commit is not the Agent's own — the published commits as a bundle for the sync. `rejected`: a
309
+ * stage and git's words. `diverged`: the published commit is not in the Agent's history and
310
+ * nothing paired it; nothing was pushed.
189
311
  */
190
312
  export const PushOutputShape = z.union([
191
313
  z.strictObject({
@@ -195,6 +317,9 @@ export const PushOutputShape = z.union([
195
317
  previous: z.string().optional(),
196
318
  via: z.string().optional(),
197
319
  unchanged: z.boolean().optional(),
320
+ // Optional: a live Sandbox keeps the runtime it started with (ADR-0037), so its older Effector
321
+ // sends no count after `jr2 up`. With no count the Harness makes no claim (menu.ts).
322
+ newCommits: z.number().int().nonnegative().optional(),
198
323
  squashed: z.enum(["signed", "unsigned"]).optional(),
199
324
  bundle: z.string().optional(),
200
325
  }),
@@ -207,6 +332,7 @@ export const PushOutputShape = z.union([
207
332
  z.strictObject({ outcome: z.literal("diverged"), reason: z.string(), published: z.string(), source: z.string() }),
208
333
  z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
209
334
  ]);
335
+ /** @internal */
210
336
  export type PushWireOutput = z.infer<typeof PushOutputShape>;
211
337
 
212
338
  /** A credentialed command's output, as the command kind's (ADR-0067), and `dirty` when the
@@ -233,6 +359,29 @@ export const CommandOutputShape = z.union([
233
359
  ]);
234
360
  export type CommandWireOutput = z.infer<typeof CommandOutputShape>;
235
361
 
362
+ /**
363
+ * A `dispatch` Effect's output on the wire (ADR-0071): its own outcome set. `settled.status` is the
364
+ * callee's status as the door served it, in the store's words, and `output` the callee Machine's,
365
+ * passed through scrubbed; `refused` is the door's status and its report, told from an http
366
+ * `refused` (which has a `reason`) by its fields.
367
+ */
368
+ export const DispatchOutputShape = z.discriminatedUnion("outcome", [
369
+ z.strictObject({ outcome: z.literal("started"), run: z.string() }),
370
+ z.strictObject({
371
+ outcome: z.literal("settled"),
372
+ run: z.string(),
373
+ // The store's words (ADR-0025) and the timeout's `live`, as `DispatchOutput` types them for the
374
+ // Machine: a door that serves another word (another Kit version) is a malformed answer, the
375
+ // invoke's error, never a status the Machine's type does not name (ADR-0071).
376
+ status: z.enum(["done", "failed", "error", "cancelled", "lost", "drifted", "live"]),
377
+ output: z.unknown().optional(),
378
+ }),
379
+ z.strictObject({ outcome: z.literal("refused"), status: z.number().int(), report: z.array(z.string()) }),
380
+ z.strictObject({ outcome: z.literal("unreachable"), reason: z.string() }),
381
+ z.strictObject({ outcome: z.literal("unknown"), reason: z.string() }),
382
+ ]);
383
+ export type DispatchWireOutput = z.infer<typeof DispatchOutputShape>;
384
+
236
385
  /** The replica that holds an Effect, and where to poll it. */
237
386
  export const ReplicaShape = z.strictObject({ name: z.string(), address: z.string() });
238
387
  export type Replica = z.infer<typeof ReplicaShape>;
@@ -245,7 +394,10 @@ export const EffectStateShape = z.strictObject({
245
394
  /** The commit a push or a credentialed command runs against, read when it started: the
246
395
  * Orchestrator writes it to the ledger's intent row. */
247
396
  pinned: z.string().optional(),
248
- output: z.union([HttpOutputShape, PushOutputShape, CommandOutputShape]).optional(),
397
+ /** The run a dispatch started, once the door answered and while its wait runs: the Orchestrator
398
+ * writes it to the ledger's intent row, so the caller names its callee during the wait (ADR-0071). */
399
+ run: z.string().optional(),
400
+ output: z.union([HttpOutputShape, PushOutputShape, CommandOutputShape, DispatchOutputShape]).optional(),
249
401
  });
250
402
  export type EffectState = z.infer<typeof EffectStateShape>;
251
403
 
package/src/fail.ts ADDED
@@ -0,0 +1,222 @@
1
+ // The `fail()` Part (ADR-0073): the end that a Machine chooses for work that did not succeed — a
2
+ // Failure (GLOSSARY.md). It is a Part and not an Effect, because it does no work outside the
3
+ // Machine: its actor throws an error that carries a brand, and xstate sends that error up through
4
+ // each parent Machine by its own rules. An `onError` on the way catches it; `isFailure` tells it
5
+ // from a defect. When no state catches it, the run host ends the run `failed`.
6
+ //
7
+ // The brand is a registry symbol (`Symbol.for`), not a module symbol, for the reason the walk is
8
+ // structural (parts.ts): an Instance resolves its OWN `@jr2/orchestrator`, so the copy that throws
9
+ // and the copy that reads the error (the run host, the CLI's walk) can be two module instances. The
10
+ // brand is not exported: an author makes a Failure only with `fail()`.
11
+
12
+ import { fromPromise, type PromiseActorLogic } from "xstate";
13
+
14
+ const FAILURE: unique symbol = Symbol.for("jr2.failure") as never;
15
+ const FAIL_SLOT: unique symbol = Symbol.for("jr2.fail.slot") as never;
16
+
17
+ /** What a Failure carries: `reason`, text for a person, and `detail`, optional JSON for a machine
18
+ * reader. The input of `fail()` and the output of a run that ends `failed`. */
19
+ export type FailureInput = { reason: string; detail?: unknown };
20
+
21
+ /** A Failure as `isFailure` narrows an error to it: the payload's fields, on the error itself. */
22
+ export type Failure = { readonly reason: string; readonly detail?: unknown };
23
+
24
+ /** Make the branded error. Only this module makes one. */
25
+ function failure(input: FailureInput): Error & Failure {
26
+ const error = new Error(input.reason) as Error & Failure & { [FAILURE]: true };
27
+ error.name = "Failure";
28
+ Object.defineProperty(error, "reason", { value: input.reason, enumerable: true });
29
+ if (input.detail !== undefined) Object.defineProperty(error, "detail", { value: input.detail, enumerable: true });
30
+ Object.defineProperty(error, FAILURE, { value: true });
31
+ return error;
32
+ }
33
+
34
+ /** Refuse an input that is not a Failure's. A defect of the Machine, not a Failure: it ends the run
35
+ * `error`, with this text as the fault. */
36
+ function checked(raw: unknown): FailureInput {
37
+ const input = raw as Partial<FailureInput> | undefined;
38
+ if (typeof input !== "object" || input === null || typeof input.reason !== "string" || input.reason === "") {
39
+ throw new Error(
40
+ `fail: the input must be { reason: string; detail?: unknown } with a reason that is not empty (got ${describe(raw)}) — ` +
41
+ `invoke: { src: "fail", input: { reason: "the tests failed" } } (ADR-0073)`,
42
+ );
43
+ }
44
+ return input.detail === undefined ? { reason: input.reason } : { reason: input.reason, detail: input.detail };
45
+ }
46
+
47
+ function describe(value: unknown): string {
48
+ try {
49
+ return JSON.stringify(value) ?? String(value);
50
+ } catch {
51
+ return String(value);
52
+ }
53
+ }
54
+
55
+ /** The actor logic that throws. Unbranded: what `workspace()` invokes to send a caught Failure up
56
+ * again, which is the wrapper's mechanism and no state of the author's that fails the run. */
57
+ export const failLogic: PromiseActorLogic<never, FailureInput> = fromPromise<never, FailureInput>(async ({ input }) => {
58
+ throw failure(checked(input));
59
+ });
60
+
61
+ /** The logic `fail()` returns: {@link failLogic} with the brand the walk reads. */
62
+ export type FailSlot = PromiseActorLogic<never, FailureInput> & { readonly [FAIL_SLOT]: true };
63
+
64
+ /**
65
+ * Fail the run: the Part a Machine invokes to end work that did not succeed. Put it in `actors`
66
+ * under a name, and invoke it from any state, at any depth: `actors: { fail: fail() }`, then
67
+ * `invoke: { src: "fail", input: { reason } }`.
68
+ *
69
+ * The input is `{ reason: string; detail?: unknown }`. `reason` is text for a person, and it must
70
+ * not be empty. `detail` is optional JSON for a machine reader.
71
+ *
72
+ * The Failure goes up from the state that invokes `fail`, through each parent Machine, as an error
73
+ * does. An `onError` on the way catches it; {@link isFailure} tells it from a defect. When no state
74
+ * catches it, the run ends `failed`: its output is `{ reason, detail }`, and `jr2 run` exits 1. A
75
+ * `workspace()` that a Failure goes through deletes its Sandbox first.
76
+ *
77
+ * To fail, invoke `fail`. Do not throw from a promise to fail: a throw is a defect, and the run
78
+ * ends `error`. A final state means `done`: do not use one for work that did not succeed. To keep
79
+ * the work for a person to read, wait at a Gate before the state that fails.
80
+ *
81
+ * It refuses an input that is not a Failure's, and the run ends `error`: ``fail: the input must be
82
+ * { reason: string; detail?: unknown } with a reason that is not empty (got <value>) — invoke:
83
+ * { src: "fail", input: { reason: "the tests failed" } } (ADR-0073)``.
84
+ *
85
+ * @returns The actor logic of the slot.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { z } from "zod";
90
+ * import { command, fail, isReady, jr2Setup, workspace, worktree, type Workspaced } from "@jr2/orchestrator";
91
+ *
92
+ * const runInput = z.object({ branch: z.string() });
93
+ * type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;
94
+ *
95
+ * const testTree = (context: BodyInput) => `${context.workspace.repos.app}/test` as const;
96
+ *
97
+ * const body = jr2Setup({
98
+ * types: {} as { input: BodyInput; context: BodyInput },
99
+ * actors: {
100
+ * cut: worktree(),
101
+ * tests: command(["npm", "test"], { timeout: "15m" }),
102
+ * fail: fail(),
103
+ * },
104
+ * }).createMachine({
105
+ * id: "check",
106
+ * context: ({ input }) => input,
107
+ * on: { "workspace.lost": ".lost" },
108
+ * initial: "cutting",
109
+ * states: {
110
+ * cutting: {
111
+ * invoke: {
112
+ * src: "cut",
113
+ * input: ({ context }) => ({ path: testTree(context), branch: context.branch }),
114
+ * onDone: [{ guard: ({ event }) => isReady(event.output), target: "testing" }, { target: "notCut" }],
115
+ * },
116
+ * },
117
+ * testing: {
118
+ * invoke: {
119
+ * src: "tests",
120
+ * input: ({ context }) => ({ worktree: testTree(context) }),
121
+ * onDone: [
122
+ * {
123
+ * guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
124
+ * target: "passed",
125
+ * },
126
+ * { target: "failing" },
127
+ * ],
128
+ * },
129
+ * },
130
+ * // Not a final state: the run ends `failed`, and the Workspace deletes the Sandbox.
131
+ * failing: { invoke: { src: "fail", input: { reason: "npm test did not pass" } } },
132
+ * passed: { type: "final" },
133
+ * notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
134
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
135
+ * },
136
+ * });
137
+ *
138
+ * export const machine = workspace(body, {
139
+ * input: runInput,
140
+ * repos: { app: "https://github.com/you/app.git" },
141
+ * });
142
+ * ```
143
+ *
144
+ * @see docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md
145
+ */
146
+ export function fail(): FailSlot {
147
+ // Logic of its own per slot, so the brand is on this slot's logic and on no other's.
148
+ const logic = fromPromise<never, FailureInput>(async ({ input }) => {
149
+ throw failure(checked(input));
150
+ });
151
+ return Object.defineProperty(logic, FAIL_SLOT, { value: true }) as FailSlot;
152
+ }
153
+
154
+ /** Is this actor logic a `fail()` slot? A test on the brand, like `isAgent`: what the walk reads to
155
+ * list each state that can fail the run (ADR-0073). */
156
+ export function isFailSlot(logic: unknown): logic is FailSlot {
157
+ return typeof logic === "object" && logic !== null && (logic as { [FAIL_SLOT]?: unknown })[FAIL_SLOT] === true;
158
+ }
159
+
160
+ /**
161
+ * Is this error a Failure, which a state invoked `fail` to make? A defect — any other error — is
162
+ * not. Use it in the guard of an `onError` to catch a Failure and let a defect go up, and read the
163
+ * Failure's `reason` and `detail` from the error it narrows.
164
+ *
165
+ * @param error - The error of an error event: `event.error` in an `onError`.
166
+ * @returns True for a Failure, with `error` narrowed to `{ reason, detail? }`.
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * import { z } from "zod";
171
+ * import { defineEvent, fail, isFailure, jr2Setup } from "@jr2/orchestrator";
172
+ *
173
+ * const retry = defineEvent({ name: "retry", description: "Try the plan again.", input: z.object({}) });
174
+ *
175
+ * const attempt = jr2Setup({ actors: { fail: fail() } }).createMachine({
176
+ * id: "attempt",
177
+ * initial: "stuck",
178
+ * states: { stuck: { invoke: { src: "fail", input: { reason: "no plan fits the ticket" } } } },
179
+ * });
180
+ *
181
+ * export const machine = jr2Setup({ events: [retry], actors: { attempt } }).createMachine({
182
+ * id: "planner",
183
+ * initial: "planning",
184
+ * states: {
185
+ * planning: {
186
+ * invoke: {
187
+ * src: "attempt",
188
+ * onDone: "planned",
189
+ * // A Failure waits for a person; a defect has no route, so it goes up and the run ends `error`.
190
+ * onError: { guard: ({ event }) => isFailure(event.error), target: "asking" },
191
+ * },
192
+ * },
193
+ * asking: { invoke: { src: "gate" }, on: { retry: "planning" } },
194
+ * planned: { type: "final" },
195
+ * },
196
+ * });
197
+ * ```
198
+ *
199
+ * @see docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md
200
+ */
201
+ export function isFailure(error: unknown): error is Failure {
202
+ return (
203
+ typeof error === "object" &&
204
+ error !== null &&
205
+ (error as { [FAILURE]?: unknown })[FAILURE] === true &&
206
+ typeof (error as { reason?: unknown }).reason === "string"
207
+ );
208
+ }
209
+
210
+ /** The name of the guard that every Chart has (ADR-0070, ADR-0073): {@link isFailure} on the
211
+ * event's `error`, so a Chart's `onError` can tell a Failure from a defect. It is a predicate and
212
+ * gives no capability, so it is never lent, as `jr2.sentBy` is not. */
213
+ export const FAILURE_GUARD = "jr2.failure";
214
+
215
+ /** The implementation of {@link FAILURE_GUARD}: true when the event's `error` is a Failure. */
216
+ export const failureGuard = ({ event }: { event: { error?: unknown } }): boolean => isFailure(event.error);
217
+
218
+ /** A Failure's payload as plain JSON data: what a failed run's output is, and what `workspace()`
219
+ * keeps to send it up again. */
220
+ export function failurePayload(error: Failure): FailureInput {
221
+ return error.detail === undefined ? { reason: error.reason } : { reason: error.reason, detail: error.detail };
222
+ }