@jr2/orchestrator 0.4.1 → 0.5.1

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 (327) 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 +442 -0
  10. package/docs/concepts/architecture.md +230 -0
  11. package/docs/concepts/charts.md +143 -0
  12. package/docs/concepts/composition.md +357 -0
  13. package/docs/concepts/converge.md +254 -0
  14. package/docs/concepts/durable-runs.md +189 -0
  15. package/docs/concepts/effects.md +603 -0
  16. package/docs/concepts/gates.md +208 -0
  17. package/docs/concepts/machines-and-workflows.md +289 -0
  18. package/docs/concepts/pools-and-sources.md +230 -0
  19. package/docs/concepts/secrets-and-trust.md +222 -0
  20. package/docs/concepts/the-menu.md +378 -0
  21. package/docs/concepts/workspaces.md +678 -0
  22. package/docs/get-started/a-model-decides.md +563 -0
  23. package/docs/get-started/an-agent-in-a-sandbox.md +381 -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 +922 -0
  27. package/docs/get-started/install.md +126 -0
  28. package/docs/guides/add-tools-to-the-sandbox.md +295 -0
  29. package/docs/guides/answer-a-gate.md +304 -0
  30. package/docs/guides/bind-open-parts.md +223 -0
  31. package/docs/guides/cancel-a-run.md +146 -0
  32. package/docs/guides/clean-up.md +206 -0
  33. package/docs/guides/fix-a-failed-converge.md +361 -0
  34. package/docs/guides/give-the-cluster-git-credentials.md +222 -0
  35. package/docs/guides/iterate-on-a-machine-on-kind.md +190 -0
  36. package/docs/guides/let-an-agent-write-a-chart.md +226 -0
  37. package/docs/guides/look-inside-a-sandbox.md +174 -0
  38. package/docs/guides/mirror-the-kit-images.md +176 -0
  39. package/docs/guides/move-from-kind-to-a-shared-cluster.md +302 -0
  40. package/docs/guides/open-the-console.md +91 -0
  41. package/docs/guides/publish-a-machine-as-an-npm-package.md +350 -0
  42. package/docs/guides/push-and-sign-a-branch.md +486 -0
  43. package/docs/guides/run-a-chart-by-name.md +270 -0
  44. package/docs/guides/run-a-command-or-call-a-url.md +544 -0
  45. package/docs/guides/run-one-worker-per-item.md +321 -0
  46. package/docs/guides/set-an-agents-access-and-dials.md +375 -0
  47. package/docs/guides/size-a-workspace.md +204 -0
  48. package/docs/guides/start-a-run-from-a-run.md +237 -0
  49. package/docs/guides/start-a-run.md +207 -0
  50. package/docs/guides/upgrade-the-kit-version.md +240 -0
  51. package/docs/guides/use-a-hosted-provider.md +208 -0
  52. package/docs/guides/use-a-local-model.md +313 -0
  53. package/docs/guides/watch-a-run.md +326 -0
  54. package/docs/guides/write-a-workflow.md +318 -0
  55. package/docs/index.md +44 -0
  56. package/docs/llms.txt +216 -0
  57. package/docs/reference/authoring-surface/agent.md +128 -0
  58. package/docs/reference/authoring-surface/answer.md +140 -0
  59. package/docs/reference/authoring-surface/arg.md +60 -0
  60. package/docs/reference/authoring-surface/basic.md +62 -0
  61. package/docs/reference/authoring-surface/bearer.md +53 -0
  62. package/docs/reference/authoring-surface/chartschema.md +23 -0
  63. package/docs/reference/authoring-surface/chartschemafor.md +48 -0
  64. package/docs/reference/authoring-surface/command.md +191 -0
  65. package/docs/reference/authoring-surface/customize.md +64 -0
  66. package/docs/reference/authoring-surface/defineconfig.md +41 -0
  67. package/docs/reference/authoring-surface/defineevent.md +114 -0
  68. package/docs/reference/authoring-surface/dispatch.md +126 -0
  69. package/docs/reference/authoring-surface/fail.md +83 -0
  70. package/docs/reference/authoring-surface/file.md +122 -0
  71. package/docs/reference/authoring-surface/frompick.md +277 -0
  72. package/docs/reference/authoring-surface/http.md +159 -0
  73. package/docs/reference/authoring-surface/isfailure.md +58 -0
  74. package/docs/reference/authoring-surface/jr2setup.md +237 -0
  75. package/docs/reference/authoring-surface/open.md +74 -0
  76. package/docs/reference/authoring-surface/openbody.md +86 -0
  77. package/docs/reference/authoring-surface/paramsof.md +125 -0
  78. package/docs/reference/authoring-surface/pool.md +186 -0
  79. package/docs/reference/authoring-surface/push.md +112 -0
  80. package/docs/reference/authoring-surface/secret.md +118 -0
  81. package/docs/reference/authoring-surface/sentby.md +83 -0
  82. package/docs/reference/authoring-surface/source.md +51 -0
  83. package/docs/reference/authoring-surface/text.md +62 -0
  84. package/docs/reference/authoring-surface/type-agentdeclaration.md +27 -0
  85. package/docs/reference/authoring-surface/type-agentdefinition.md +80 -0
  86. package/docs/reference/authoring-surface/type-agentlogic.md +15 -0
  87. package/docs/reference/authoring-surface/type-agentslots.md +12 -0
  88. package/docs/reference/authoring-surface/type-answeraction.md +12 -0
  89. package/docs/reference/authoring-surface/type-arg.md +19 -0
  90. package/docs/reference/authoring-surface/type-argnames.md +12 -0
  91. package/docs/reference/authoring-surface/type-binding.md +62 -0
  92. package/docs/reference/authoring-surface/type-bindingof.md +12 -0
  93. package/docs/reference/authoring-surface/type-bodyacceptsdoor.md +15 -0
  94. package/docs/reference/authoring-surface/type-bodyacceptsslots.md +17 -0
  95. package/docs/reference/authoring-surface/type-ceilings.md +41 -0
  96. package/docs/reference/authoring-surface/type-chainlink.md +19 -0
  97. package/docs/reference/authoring-surface/type-chartaction.md +46 -0
  98. package/docs/reference/authoring-surface/type-chartagent.md +28 -0
  99. package/docs/reference/authoring-surface/type-chartevent.md +20 -0
  100. package/docs/reference/authoring-surface/type-chartguard.md +20 -0
  101. package/docs/reference/authoring-surface/type-chartinvoke.md +28 -0
  102. package/docs/reference/authoring-surface/type-chartjson.md +14 -0
  103. package/docs/reference/authoring-surface/type-chartstate.md +46 -0
  104. package/docs/reference/authoring-surface/type-charttext.md +40 -0
  105. package/docs/reference/authoring-surface/type-charttransition.md +22 -0
  106. package/docs/reference/authoring-surface/type-charttransitions.md +14 -0
  107. package/docs/reference/authoring-surface/type-chartvalue.md +15 -0
  108. package/docs/reference/authoring-surface/type-childat.md +12 -0
  109. package/docs/reference/authoring-surface/type-childslots.md +12 -0
  110. package/docs/reference/authoring-surface/type-commandenv.md +15 -0
  111. package/docs/reference/authoring-surface/type-commandinput.md +19 -0
  112. package/docs/reference/authoring-surface/type-commandoptions.md +78 -0
  113. package/docs/reference/authoring-surface/type-commandoutput.md +104 -0
  114. package/docs/reference/authoring-surface/type-customize.md +109 -0
  115. package/docs/reference/authoring-surface/type-dispatchdefinition.md +39 -0
  116. package/docs/reference/authoring-surface/type-dispatchinput.md +14 -0
  117. package/docs/reference/authoring-surface/type-dispatchopenparts.md +14 -0
  118. package/docs/reference/authoring-surface/type-dispatchoutput.md +65 -0
  119. package/docs/reference/authoring-surface/type-duration.md +15 -0
  120. package/docs/reference/authoring-surface/type-effectbinding.md +93 -0
  121. package/docs/reference/authoring-surface/type-effectlogic.md +12 -0
  122. package/docs/reference/authoring-surface/type-envopenparts.md +14 -0
  123. package/docs/reference/authoring-surface/type-eventaudience.md +27 -0
  124. package/docs/reference/authoring-surface/type-eventdef.md +45 -0
  125. package/docs/reference/authoring-surface/type-eventfrom.md +16 -0
  126. package/docs/reference/authoring-surface/type-eventsemantics.md +26 -0
  127. package/docs/reference/authoring-surface/type-execoutcome.md +34 -0
  128. package/docs/reference/authoring-surface/type-failslot.md +14 -0
  129. package/docs/reference/authoring-surface/type-failure.md +19 -0
  130. package/docs/reference/authoring-surface/type-failureinput.md +20 -0
  131. package/docs/reference/authoring-surface/type-filebody.md +41 -0
  132. package/docs/reference/authoring-surface/type-frominput.md +14 -0
  133. package/docs/reference/authoring-surface/type-hostinjectedinput.md +58 -0
  134. package/docs/reference/authoring-surface/type-httpdefinition.md +101 -0
  135. package/docs/reference/authoring-surface/type-httpinput.md +12 -0
  136. package/docs/reference/authoring-surface/type-httpmethod.md +14 -0
  137. package/docs/reference/authoring-surface/type-httpopenparts.md +12 -0
  138. package/docs/reference/authoring-surface/type-httpoutput.md +89 -0
  139. package/docs/reference/authoring-surface/type-httprequestparts.md +33 -0
  140. package/docs/reference/authoring-surface/type-jr2config.md +126 -0
  141. package/docs/reference/authoring-surface/type-lends.md +38 -0
  142. package/docs/reference/authoring-surface/type-logicat.md +12 -0
  143. package/docs/reference/authoring-surface/type-markedif.md +12 -0
  144. package/docs/reference/authoring-surface/type-mergedactors.md +12 -0
  145. package/docs/reference/authoring-surface/type-namedsecret.md +25 -0
  146. package/docs/reference/authoring-surface/type-noslotspec.md +26 -0
  147. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +32 -0
  148. package/docs/reference/authoring-surface/type-openbodydeclaration.md +40 -0
  149. package/docs/reference/authoring-surface/type-openbodylogic.md +14 -0
  150. package/docs/reference/authoring-surface/type-openbodyoutput.md +42 -0
  151. package/docs/reference/authoring-surface/type-openbodyslot.md +18 -0
  152. package/docs/reference/authoring-surface/type-opencommandinput.md +17 -0
  153. package/docs/reference/authoring-surface/type-openeffect.md +12 -0
  154. package/docs/reference/authoring-surface/type-openeffectslots.md +12 -0
  155. package/docs/reference/authoring-surface/type-openof.md +12 -0
  156. package/docs/reference/authoring-surface/type-opensecret.md +23 -0
  157. package/docs/reference/authoring-surface/type-opensecretmark.md +12 -0
  158. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +31 -0
  159. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +32 -0
  160. package/docs/reference/authoring-surface/type-poolmachine.md +28 -0
  161. package/docs/reference/authoring-surface/type-poolspec.md +60 -0
  162. package/docs/reference/authoring-surface/type-pushinput.md +15 -0
  163. package/docs/reference/authoring-surface/type-pushoptions.md +79 -0
  164. package/docs/reference/authoring-surface/type-pushoutput.md +124 -0
  165. package/docs/reference/authoring-surface/type-pushwireoutput.md +12 -0
  166. package/docs/reference/authoring-surface/type-quantity.md +14 -0
  167. package/docs/reference/authoring-surface/type-reached.md +12 -0
  168. package/docs/reference/authoring-surface/type-reposlot.md +70 -0
  169. package/docs/reference/authoring-surface/type-reposlotsof.md +16 -0
  170. package/docs/reference/authoring-surface/type-sandboxoptions.md +77 -0
  171. package/docs/reference/authoring-surface/type-secret.md +25 -0
  172. package/docs/reference/authoring-surface/type-sender.md +19 -0
  173. package/docs/reference/authoring-surface/type-sentbyguards.md +12 -0
  174. package/docs/reference/authoring-surface/type-size.md +85 -0
  175. package/docs/reference/authoring-surface/type-slothandles.md +32 -0
  176. package/docs/reference/authoring-surface/type-sourcenextresult.md +17 -0
  177. package/docs/reference/authoring-surface/type-sourcespec.md +44 -0
  178. package/docs/reference/authoring-surface/type-syncstate.md +12 -0
  179. package/docs/reference/authoring-surface/type-text.md +19 -0
  180. package/docs/reference/authoring-surface/type-textpart.md +24 -0
  181. package/docs/reference/authoring-surface/type-thinkinglevel.md +39 -0
  182. package/docs/reference/authoring-surface/type-toparameterizedobject.md +14 -0
  183. package/docs/reference/authoring-surface/type-toprovidedactor.md +14 -0
  184. package/docs/reference/authoring-surface/type-unknown.md +21 -0
  185. package/docs/reference/authoring-surface/type-usercontainer.md +77 -0
  186. package/docs/reference/authoring-surface/type-withoutemptyindex.md +20 -0
  187. package/docs/reference/authoring-surface/type-workflowevent.md +46 -0
  188. package/docs/reference/authoring-surface/type-workspaceaccess.md +16 -0
  189. package/docs/reference/authoring-surface/type-workspaced.md +94 -0
  190. package/docs/reference/authoring-surface/type-workspacehandles.md +23 -0
  191. package/docs/reference/authoring-surface/type-workspacemachine.md +29 -0
  192. package/docs/reference/authoring-surface/type-workspaceof.md +13 -0
  193. package/docs/reference/authoring-surface/type-workspaceoptions.md +39 -0
  194. package/docs/reference/authoring-surface/type-workspacespec.md +37 -0
  195. package/docs/reference/authoring-surface/workspace.md +259 -0
  196. package/docs/reference/authoring-surface.md +197 -0
  197. package/docs/reference/cli.md +433 -0
  198. package/docs/reference/config.md +756 -0
  199. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +113 -0
  200. package/docs/reference/decisions/0002-duplex-streaming-actor.md +110 -0
  201. package/docs/reference/decisions/0004-worktree-provisioning-model.md +124 -0
  202. package/docs/reference/decisions/0005-sandbox-pod-composition.md +230 -0
  203. package/docs/reference/decisions/0006-agent-control-surface.md +47 -0
  204. package/docs/reference/decisions/0007-durable-machine-state.md +109 -0
  205. package/docs/reference/decisions/0008-orchestrator-as-deployed-app.md +60 -0
  206. package/docs/reference/decisions/0009-cli-and-instance-interface.md +275 -0
  207. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +160 -0
  208. package/docs/reference/decisions/0011-workflow-defined-events.md +141 -0
  209. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +103 -0
  210. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +157 -0
  211. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +95 -0
  212. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +265 -0
  213. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +124 -0
  214. package/docs/reference/decisions/0017-pool-over-a-source-port.md +69 -0
  215. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +111 -0
  216. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +275 -0
  217. package/docs/reference/decisions/0020-private-ca-trust-is-a-named-concept.md +60 -0
  218. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +100 -0
  219. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +103 -0
  220. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +176 -0
  221. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +439 -0
  222. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +65 -0
  223. package/docs/reference/decisions/0026-a-turn-that-is-over-is-never-prompted.md +62 -0
  224. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +237 -0
  225. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +89 -0
  226. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +154 -0
  227. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +100 -0
  228. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +190 -0
  229. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +61 -0
  230. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +127 -0
  231. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +57 -0
  232. package/docs/reference/decisions/0035-a-runaway-turn-is-ended-rerolled-once-then-a-fault.md +92 -0
  233. package/docs/reference/decisions/0036-a-turn-compacts-its-own-context-mid-flight.md +138 -0
  234. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +196 -0
  235. package/docs/reference/decisions/0038-jr2-up-builds-every-image-it-deploys.md +170 -0
  236. package/docs/reference/decisions/0039-image-garbage-collects-by-reachability.md +115 -0
  237. package/docs/reference/decisions/0041-a-build-the-host-already-holds-is-not-spent-again.md +64 -0
  238. package/docs/reference/decisions/0042-ready-is-not-routable-an-admission-retries-its-connection.md +142 -0
  239. package/docs/reference/decisions/0043-the-kit-is-tested-as-installed-a-local-registry-stands-in-for-npm.md +170 -0
  240. package/docs/reference/decisions/0044-kit-images-live-at-a-canonical-home-a-self-host-mirrors-it.md +85 -0
  241. package/docs/reference/decisions/0045-the-platform-joins-the-image-address-and-the-cluster-chooses-it.md +82 -0
  242. package/docs/reference/decisions/0046-a-converge-failure-carries-its-diagnosis.md +34 -0
  243. package/docs/reference/decisions/0047-the-git-ssh-key-source-is-the-users-choice.md +47 -0
  244. package/docs/reference/decisions/0048-the-orchestrator-boots-without-its-repos.md +36 -0
  245. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +126 -0
  246. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +73 -0
  247. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +227 -0
  248. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +81 -0
  249. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +111 -0
  250. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +126 -0
  251. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +107 -0
  252. package/docs/reference/decisions/0056-a-global-jr2-hands-off-to-the-instances-own-and-an-instance-has-one-kit-version.md +62 -0
  253. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +152 -0
  254. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +106 -0
  255. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +384 -0
  256. package/docs/reference/decisions/0060-a-workspace-states-its-size-and-jr2-reserves-all-of-it.md +111 -0
  257. package/docs/reference/decisions/0061-a-memory-kill-ends-the-agents-processes-not-the-harness.md +83 -0
  258. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +212 -0
  259. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +83 -0
  260. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +79 -0
  261. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +65 -0
  262. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +550 -0
  263. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +430 -0
  264. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +464 -0
  265. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +180 -0
  266. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +323 -0
  267. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +183 -0
  268. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +144 -0
  269. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +122 -0
  270. package/docs/reference/decisions/0074-an-unrouted-mechanism-event-is-refused-at-load-and-faults-the-run.md +76 -0
  271. package/docs/reference/decisions.md +83 -0
  272. package/docs/reference/glossary.md +648 -0
  273. package/docs/reference/http-api.md +1348 -0
  274. package/docs/reference/machines.md +190 -0
  275. package/docs/reference/mechanism-events.md +72 -0
  276. package/docs/why.md +206 -0
  277. package/package.json +5 -3
  278. package/src/actor.ts +541 -79
  279. package/src/agent.ts +114 -38
  280. package/src/ambient.ts +2 -1
  281. package/src/assembly-checks.ts +189 -7
  282. package/src/cause.ts +234 -0
  283. package/src/chart.ts +169 -37
  284. package/src/charts.ts +34 -6
  285. package/src/config.ts +430 -178
  286. package/src/custodian.ts +74 -22
  287. package/src/customize.ts +188 -46
  288. package/src/dispatch-effect.ts +945 -0
  289. package/src/door-compat.ts +217 -0
  290. package/src/durability.ts +11 -4
  291. package/src/effect.ts +341 -44
  292. package/src/effector-wire.ts +160 -11
  293. package/src/fail.ts +209 -0
  294. package/src/harness-client.ts +541 -47
  295. package/src/http-effect.ts +495 -53
  296. package/src/http.ts +427 -16
  297. package/src/index.ts +60 -82
  298. package/src/instance.ts +68 -8
  299. package/src/internal.ts +84 -0
  300. package/src/machine-doc.ts +21 -3
  301. package/src/mechanism-routes.ts +168 -0
  302. package/src/model-keys.generated.ts +53 -0
  303. package/src/model-keys.ts +18 -58
  304. package/src/notices.ts +66 -13
  305. package/src/open-body.ts +159 -23
  306. package/src/open-effect.ts +81 -8
  307. package/src/open.ts +59 -6
  308. package/src/parts.ts +184 -13
  309. package/src/party.ts +68 -2
  310. package/src/pick.ts +463 -11
  311. package/src/pool.ts +247 -36
  312. package/src/registration.ts +83 -4
  313. package/src/repos.ts +2 -1
  314. package/src/run-host.ts +565 -31
  315. package/src/run-output.ts +38 -0
  316. package/src/sandbox-effect.ts +273 -38
  317. package/src/sandbox-kube.ts +3 -1
  318. package/src/secret-placement.ts +52 -17
  319. package/src/server.ts +3 -0
  320. package/src/setup.ts +239 -56
  321. package/src/size.ts +121 -9
  322. package/src/snapshot-store.ts +54 -10
  323. package/src/stub-harness.ts +44 -16
  324. package/src/timers.ts +14 -1
  325. package/src/vocabulary.ts +69 -14
  326. package/src/wire.ts +90 -16
  327. package/src/workspace.ts +697 -175
package/src/effect.ts CHANGED
@@ -4,9 +4,11 @@
4
4
  // route on, the input kept in the persisted snapshot, a `src` typo as a compile error, and
5
5
  // `.provide()` as the unit-test seam.
6
6
  //
7
- // Two kinds exist: `command`, run in the Harness container of the enclosing Workspace's Sandbox
8
- // (the Harness's `/exec/:id`, ADR-0067), here; and `http`, run in the Instance Effector
9
- // (http-effect.ts, ADR-0068). What this module adds to xstate is what makes an Effect safe to
7
+ // The kinds: `command`, run in the Harness container of the enclosing Workspace's Sandbox (the
8
+ // Harness's `/exec/:id`, ADR-0067), here; `http`, run in the Instance Effector (http-effect.ts,
9
+ // ADR-0068); `push`, in the Sandbox Effector (sandbox-effect.ts); and `dispatch`, which starts a run
10
+ // of a registered Workflow, in the Orchestrator's own process or, toward another Orchestrator, in
11
+ // the Instance Effector (dispatch-effect.ts, ADR-0071). What this module adds to xstate is what makes an Effect safe to
10
12
  // restart:
11
13
  //
12
14
  // - THE LEDGER. The run host keeps one row per Effect, in the run's blob beside the snapshot
@@ -53,8 +55,58 @@ import {
53
55
 
54
56
  // --- Parts: what `jr2 up` and the Machine doc read off a slot, without running anything ---------
55
57
 
56
- /** One argv entry an Effect's input fills: ONE argument, whatever its bytes (never shell text). */
58
+ // ONE argument, whatever its bytes: never shell text.
59
+ /** One entry of a command's argv that the input of the Effect fills, as `arg("name")` makes it. It
60
+ * is one argument, whatever its text. No shell reads it. */
57
61
  export type Arg<N extends string = string> = { readonly arg: N };
62
+
63
+ /**
64
+ * Mark one argv entry of a `command` that the input of the Effect fills. `arg("file")` is one
65
+ * argument: the value of the `file` key of the input, whatever its characters. No shell reads it.
66
+ *
67
+ * The input of the Effect then has a string key for each `arg`. Give it in the `input` of the
68
+ * `invoke`, or use `fromPick` to give it from a pick.
69
+ *
70
+ * When it starts, with no string in the input for an entry, the Effect fails (its `onError`
71
+ * route): `command "<slot>": argv slot <<name>> has no string value in its input (got <type>) —
72
+ * map it in the invoke's input`. The angle brackets around the name are in the text.
73
+ *
74
+ * @param name - The key of the input that fills this entry.
75
+ * @returns The argv entry, for the argv list of `command`.
76
+ *
77
+ * @example
78
+ * ```ts
79
+ * import { arg, command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";
80
+ *
81
+ * type Input = Workspaced<{ file: string }, "app">;
82
+ *
83
+ * const body = jr2Setup({
84
+ * types: {} as { context: Input; input: Input },
85
+ * actors: { lint: command(["npx", "eslint", arg("file")]), fail: fail() },
86
+ * }).createMachine({
87
+ * id: "lint",
88
+ * context: ({ input }) => input,
89
+ * on: { "workspace.lost": ".lost" },
90
+ * initial: "lint",
91
+ * states: {
92
+ * lint: {
93
+ * invoke: { src: "lint", input: ({ context }) => ({ file: context.file }), onDone: "done" },
94
+ * },
95
+ * done: { type: "final" },
96
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
97
+ * },
98
+ * });
99
+ *
100
+ * export const machine = workspace(body, {
101
+ * repos: { app: { url: "https://github.com/you/app.git" } },
102
+ * spec: () => ({ branch: "lint" }),
103
+ * });
104
+ * ```
105
+ *
106
+ * @see docs/concepts/effects.md
107
+ * @see docs/guides/run-a-command-or-call-a-url.md
108
+ * @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
109
+ */
58
110
  export const arg = <const N extends string>(name: N): Arg<N> => ({ arg: name });
59
111
 
60
112
  /** A `command` Effect's plain data. */
@@ -103,10 +155,10 @@ export type HttpParts = {
103
155
  readonly maxBody: number;
104
156
  /** Marked by the author: a restore that cannot find the Effect sends it again. */
105
157
  readonly idempotent: boolean;
106
- /** The body is a Worktree file (`body: file(...)`, ADR-0067): the Effect runs in the Sandbox Effector
107
- * of the Workspace whose body invokes it. `slot` is the Repo Slot whose Worktree holds the file —
108
- * needed only where the Workspace carries several. Absent: a json, text or no body, in the
109
- * Instance Effector. */
158
+ /** The body is a Worktree file (`body: file(...)`, ADR-0067): the Effect runs in the Sandbox
159
+ * Effector of the Workspace whose body invokes it. `slot` is the Repo Slot whose Worktree holds
160
+ * the file — needed only where the Workspace carries several. Absent: a json, text or no body, in
161
+ * the Instance Effector. */
110
162
  readonly file?: { readonly slot?: string; readonly contentType?: string };
111
163
  };
112
164
 
@@ -128,6 +180,33 @@ export type PushParts = {
128
180
  readonly inputKeys: readonly string[];
129
181
  };
130
182
 
183
+ /** A `dispatch` Effect's target: another Orchestrator's origin and the secret that holds its
184
+ * Instance token, by name (ADR-0071). */
185
+ export type DispatchTarget = { readonly url: string; readonly token: string };
186
+
187
+ /**
188
+ * A `dispatch` Effect's plain data (ADR-0071): the Workflow it starts a run of, as `jr2 run` names
189
+ * it (`review`, or `<wrapper>/<name>` for a named Chart), whether it waits for that run to end, the
190
+ * bound on the start (or the wait), and the target. No target: it runs in the Orchestrator's own
191
+ * process, through the run host, with no wire and no token. A target: it runs in the Instance
192
+ * Effector, which holds the token as an Effect secret.
193
+ */
194
+ export type DispatchParts = {
195
+ readonly kind: "dispatch";
196
+ readonly workflow: string;
197
+ readonly wait: boolean;
198
+ readonly timeoutMs: number;
199
+ readonly target?: DispatchTarget;
200
+ /** The keys of the slot's input schema: what a pick param may fill. Undefined with no schema:
201
+ * the callee's door alone checks the body, so the load walk reads everything (ADR-0071). */
202
+ readonly inputKeys: readonly string[] | undefined;
203
+ /** `[target.token]`, or none in-process: what places the token (ADR-0068). */
204
+ readonly secrets: readonly string[];
205
+ /** Always false: a target's token is never `secret(open)` — the whole target is the Open part
206
+ * (ADR-0071). */
207
+ readonly openSecret: boolean;
208
+ };
209
+
131
210
  /**
132
211
  * An Effect whose url or argv a package left Open (ADR-0054): the same parts with the sentinel in
133
212
  * that one place. The kind, the placement, the timeout and the secrets the other parts name are
@@ -140,21 +219,36 @@ export type OpenHttpParts = Omit<HttpParts, "url" | "target"> & {
140
219
  readonly target: typeof open;
141
220
  };
142
221
 
222
+ /** A `dispatch` whose target a package left Open (ADR-0071): the composer binds the url and the
223
+ * token together, once. */
224
+ export type OpenDispatchParts = Omit<DispatchParts, "target"> & { readonly target: typeof open };
225
+
143
226
  /** Every kind's parts, bound or with an Open part. */
144
- export type EffectParts = CommandParts | HttpParts | PushParts | OpenCommandParts | OpenHttpParts;
227
+ export type EffectParts =
228
+ | CommandParts
229
+ | HttpParts
230
+ | PushParts
231
+ | OpenCommandParts
232
+ | OpenHttpParts
233
+ | DispatchParts
234
+ | OpenDispatchParts;
145
235
 
146
236
  /** The kinds, as their parts name them. */
147
- export const EFFECT_KINDS: ReadonlySet<string> = new Set(["command", "http", "push"]);
237
+ export const EFFECT_KINDS: ReadonlySet<string> = new Set(["command", "http", "push", "dispatch"]);
148
238
 
149
239
  /**
150
240
  * Where an Effect runs, derived from its kind and what it names, never authored (ADR-0067,
151
241
  * ADR-0068): a `command` that names no secret in the Harness container of its Workspace; an
152
242
  * `http` Effect with no Worktree file in the Instance Effector; an `http` whose body is a Worktree
153
- * file, a `push` and a `command` that names a secret in the Sandbox Effector of its Workspace.
243
+ * file, a `push` and a `command` that names a secret in the Sandbox Effector of its Workspace; a
244
+ * `dispatch` with no target in the Orchestrator's own process, and one with a target (an Open one
245
+ * included: it has one once bound) in the Instance Effector (ADR-0071).
154
246
  */
155
- export type Placement = "harness" | "instance" | "sandbox";
247
+ export type Placement = "harness" | "instance" | "sandbox" | "orchestrator";
156
248
  export function placementOf(parts: EffectParts): Placement {
157
249
  switch (parts.kind) {
250
+ case "dispatch":
251
+ return parts.target === undefined ? "orchestrator" : "instance";
158
252
  case "http":
159
253
  return parts.file ? "sandbox" : "instance";
160
254
  case "push":
@@ -192,9 +286,12 @@ export function targetText(parts: { readonly target: HttpParts["target"] | typeo
192
286
  }
193
287
 
194
288
  /** The keys an Effect's input has, read off its parts: a command's argv slot names (and `sha` with
195
- * `pin`); an http Effect's input schema keys; a push's `sha` and `message`. Undefined for an Open
196
- * argv: its slots are the composer's, unknown until `customize()` binds it (ADR-0054). */
289
+ * `pin`); an http Effect's input schema keys; a push's `sha` and `message`; a dispatch's input
290
+ * schema keys. Undefined for an Open argv: its slots are the composer's, unknown until
291
+ * `customize()` binds it (ADR-0054); and for a dispatch with no input schema: the callee's door is
292
+ * the only check, so nothing here says what it reads (ADR-0071). */
197
293
  export function inputKeysOf(parts: EffectParts): string[] | undefined {
294
+ if (parts.kind === "dispatch") return parts.inputKeys === undefined ? undefined : [...parts.inputKeys];
198
295
  if (parts.kind !== "command") return [...parts.inputKeys];
199
296
  if (parts.argv === open) return undefined;
200
297
  return [...parts.argv.flatMap((a) => (typeof a === "string" ? [] : [a.arg])), ...(parts.pin ? ["sha"] : [])];
@@ -202,14 +299,111 @@ export function inputKeysOf(parts: EffectParts): string[] | undefined {
202
299
 
203
300
  // --- Output -------------------------------------------------------------------------------------
204
301
 
205
- /** The outside world may have acted, and jr2 cannot tell: the Harness lost the exec, or its state
206
- * exited while it ran. Output the Machine routes on; never repeated by jr2. */
302
+ /** The output `{ outcome: "unknown", reason }`: the Effect may have acted, and jr2 cannot tell.
303
+ * For example, the Harness lost the exec, or its state exited while it ran. Route on it. jr2 does
304
+ * not run the Effect again. */
207
305
  export type Unknown = { outcome: "unknown"; reason: string };
208
306
 
209
- /** A `command` Effect's output: it exited, its timeout killed it, a signal killed it, or unknown. A
210
- * credentialed command's says `dirty` when the Worktree held edits it did not see (ADR-0067). */
307
+ // `dirty` is a credentialed command's alone (ADR-0067): it runs in the Sandbox Effector against
308
+ // the pinned commit, never the live Worktree.
309
+ /**
310
+ * The output of a `command` Effect: what the `onDone` of its state gets as `event.output`. Every
311
+ * outcome is output. A failed program is output too. Route on `outcome`:
312
+ *
313
+ * - `exited`: the program ended. `code` is its exit code. A program that the container does not
314
+ * have is `exited` with code 127.
315
+ * - `timeout`: the command ran past its `timeout`, and jr2 killed it.
316
+ * - `killed`: a signal ended it. `signal` names the signal.
317
+ * - `unknown`: jr2 cannot tell what the command did, for example because the Harness restarted
318
+ * while it ran. `reason` tells why. jr2 does not run it again.
319
+ *
320
+ * `tail` is the end of what the command wrote, at most the `tail` option of `command` in
321
+ * characters. `dirty` is `true` when a command that names an Effect secret ran against a commit,
322
+ * and the Worktree held changes that it did not see.
323
+ *
324
+ * @example
325
+ * ```ts
326
+ * import {
327
+ * command,
328
+ * type CommandOutput,
329
+ * fail,
330
+ * jr2Setup,
331
+ * workspace,
332
+ * type Workspaced,
333
+ * } from "@jr2/orchestrator";
334
+ *
335
+ * type Input = Workspaced<{}, "app">;
336
+ *
337
+ * const passed = (output: CommandOutput): boolean =>
338
+ * output.outcome === "exited" && output.code === 0;
339
+ *
340
+ * const body = jr2Setup({
341
+ * types: {} as { context: Input; input: Input },
342
+ * actors: { tests: command(["npm", "test"], { timeout: "15m" }), fail: fail() },
343
+ * }).createMachine({
344
+ * id: "tests",
345
+ * context: ({ input }) => input,
346
+ * on: { "workspace.lost": ".lost" },
347
+ * initial: "testing",
348
+ * states: {
349
+ * testing: {
350
+ * invoke: {
351
+ * src: "tests",
352
+ * onDone: [
353
+ * { guard: ({ event }) => passed(event.output), target: "green" },
354
+ * { target: "red" },
355
+ * ],
356
+ * },
357
+ * },
358
+ * green: { type: "final" },
359
+ * // Not a final state: a final state means `done`, and the run ends `failed` here.
360
+ * red: { invoke: { src: "fail", input: { reason: "npm test did not pass" } } },
361
+ * lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
362
+ * },
363
+ * });
364
+ *
365
+ * export const machine = workspace(body, {
366
+ * repos: { app: { url: "https://github.com/you/app.git" } },
367
+ * spec: () => ({ branch: "tests" }),
368
+ * });
369
+ * ```
370
+ *
371
+ * @see docs/concepts/effects.md
372
+ * @see docs/guides/run-a-command-or-call-a-url.md
373
+ * @see docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md
374
+ */
211
375
  export type CommandOutput = (ExecOutcome & { dirty?: boolean }) | Unknown;
212
376
 
377
+ // Reach is output, as an http Effect's is (ADR-0067): a body a model wrote can be fixed through
378
+ // the Chain, so a refusal is output too.
379
+ /**
380
+ * The output of a `dispatch` Effect. Route on `outcome`:
381
+ *
382
+ * - `started`: the Orchestrator accepted the new run. `run` is its id.
383
+ * - `settled`: with `wait`, the new run ended. `status` is `done`, `failed`, `error`, `cancelled`,
384
+ * `lost` or `drifted`, and `output` is the output of its Machine. For `failed`, `output` is the
385
+ * Failure, `{ reason, detail? }`. `status` is `live` when the timeout came first, and the run
386
+ * continues.
387
+ * - `refused`: the Orchestrator started no run. `status` is the HTTP status: 400 for an input that
388
+ * the Workflow does not take, 404 for a Workflow that it does not have, 409 for a named Chart that
389
+ * cannot run now, 422 for a Chart that it refuses. `report` holds the lines of the refusal.
390
+ * - `unreachable`: the Orchestrator of the target did not answer. No run started.
391
+ * - `unknown`: jr2 cannot tell if a run started.
392
+ *
393
+ * @see docs/concepts/effects.md
394
+ */
395
+ export type DispatchOutput =
396
+ | { outcome: "started"; run: string }
397
+ | {
398
+ outcome: "settled";
399
+ run: string;
400
+ status: "done" | "failed" | "error" | "cancelled" | "lost" | "drifted" | "live";
401
+ output?: unknown;
402
+ }
403
+ | { outcome: "refused"; status: number; report: string[] }
404
+ | { outcome: "unreachable"; reason: string }
405
+ | Unknown;
406
+
213
407
  // --- The ledger row (persisted in the run's blob, ADR-0007) --------------------------------------
214
408
 
215
409
  /**
@@ -238,6 +432,19 @@ export type EffectRow =
238
432
  pinned?: string;
239
433
  at: string;
240
434
  }
435
+ /** A dispatch's intent (ADR-0071): where it runs, and — once the start's answer names it — the
436
+ * run it started. A dispatch is idempotent by construction: its ledger id rides every start as
437
+ * the door's key, so a restore sends it again and gets the same run. */
438
+ | {
439
+ state: "intent";
440
+ kind: "dispatch";
441
+ slot: string;
442
+ placement: "orchestrator" | "instance";
443
+ idempotent: true;
444
+ wait: boolean;
445
+ run?: string;
446
+ at: string;
447
+ }
241
448
  /** `sandbox` and `repo`: the Workspace and the Repo Slot an Effect of the Sandbox Effector ran on
242
449
  * — every later push of its own Workspace and Repo Slot (one branch, one remote) sends its pair,
243
450
  * and the newest push's unfinished sync runs again in that slot's Worktree. */
@@ -250,7 +457,9 @@ export type EffectRow =
250
457
  output: unknown;
251
458
  at: string;
252
459
  }
253
- | { state: "aborted"; kind: EffectParts["kind"]; slot: string; at: string };
460
+ /** `run`: for a dispatch, the run it started, kept past the abort — the callee runs on, and the
461
+ * caller's row still names it (ADR-0071). */
462
+ | { state: "aborted"; kind: EffectParts["kind"]; slot: string; run?: string; at: string };
254
463
 
255
464
  /**
256
465
  * Retention (ADR-0067): drop, in place, the output tail of every settled row once a save on disk
@@ -297,26 +506,33 @@ const TAILS: Record<EffectRow["kind"], readonly string[]> = {
297
506
  // every later push of its Repo Slot sends the pair (`pushPairsOf`), and the newest push's sync
298
507
  // runs again when it did not finish. The sync's bundle and git's words are its tail.
299
508
  push: ["bundle", "detail"],
509
+ // A dispatch's tail is the callee's output, which `GET /runs/<id>` serves for as long as the run
510
+ // is kept, and a refusal's report, which can carry the callee door's whole schema: the feed's
511
+ // settled marker carries both whole. The row keeps the outcome, the run id, its status and the
512
+ // door's HTTP status (ADR-0071).
513
+ dispatch: ["output", "report"],
300
514
  };
301
515
 
302
- /** A kind's tail for one output. The NEWEST push of its Repo Slot whose sync did not finish
303
- * (`busy`, `not-synced`) keeps its bundle: the sync that runs again before the next push of that
304
- * slot moves the branch from it, and the Agent's store may not hold the published commits without it.
305
- * Once a sync finished, or once a newer push of the slot published, the bundle goes: only the
306
- * newest push's sync runs again (`lastPushOf`), so an older bundle has no reader. */
516
+ /** A kind's tail for one output. The NEWEST push of its Repo Slot whose sync did not finish (`busy`,
517
+ * `not-synced`) keeps its bundle: the sync that runs again before the next push of that slot moves
518
+ * the branch from it, and the Agent's store may not hold the published commits without it. Once a
519
+ * sync finished, or once a newer push of the slot published, the bundle goes: only the newest
520
+ * push's sync runs again (`lastPushOf`), so an older bundle has no reader. */
307
521
  function tailsOf(kind: EffectRow["kind"], output: Record<string, unknown>, newest: boolean): readonly string[] {
308
522
  if (kind === "push" && newest && (output.sync === "busy" || output.sync === "not-synced")) return ["detail"];
309
523
  return TAILS[kind];
310
524
  }
311
525
 
312
- /** A settled push row that published: its pair, and its sync's answer and bundle when it has them. */
526
+ /** A settled push row that published: its pair, and its sync's answer and bundle when it has them.
527
+ */
313
528
  export type PushedRow = { published: string; source: string; sync?: SyncAnswer["sync"]; bundle?: string } & Record<
314
529
  string,
315
530
  unknown
316
531
  >;
317
532
 
318
533
  /** The ledger's last push of a Workspace's Repo Slot that published: its row, by `at`, never this
319
- * Effect's (`self`). A Workspace with two Repo Slots has two branches on two remotes, so two pairs. */
534
+ * Effect's (`self`). A Workspace with two Repo Slots has two branches on two remotes, so two pairs.
535
+ */
320
536
  export function lastPushOf(
321
537
  rows: Readonly<Record<string, EffectRow>>,
322
538
  sandbox: string,
@@ -386,14 +602,15 @@ export type EffectMarker = {
386
602
  effect: string;
387
603
  stage: "started" | "output" | "settled" | "aborted";
388
604
  text?: string;
389
- output?: CommandOutput | HttpOutput;
605
+ output?: CommandOutput | HttpOutput | DispatchOutput;
390
606
  };
391
607
 
392
608
  // --- The port -----------------------------------------------------------------------------------
393
609
 
394
610
  /** The Harness's exec, as the Effect drives it. `read` answers undefined for an exec the Harness
395
611
  * does not know (404): it restarted, and jr2 settles `unknown`. `start` rejects with `ExecNotSent`
396
- * only when the request certainly never reached the Harness — the one failure it is sent again on. */
612
+ * only when the request certainly never reached the Harness — the one failure it is sent again on.
613
+ */
397
614
  export interface ExecPort {
398
615
  start(id: string, req: ExecRequest, signal: AbortSignal): Promise<void>;
399
616
  read(id: string, offset: number | undefined, signal: AbortSignal): Promise<ExecRead | undefined>;
@@ -416,9 +633,13 @@ type Fault = { type: "jr2.effect.error"; error: unknown };
416
633
 
417
634
  /** An Effect slot's logic: xstate `ActorLogic`, typed by its input and output, branded with its
418
635
  * parts. The brand is a runtime property (`isEffect`) and part of the type, as `definition` is
419
- * not on an Agent's: nothing substitutes a fake for an Effect's parts. */
636
+ * not on an Agent's: nothing substitutes a fake for an Effect's parts.
637
+ *
638
+ * @internal
639
+ */
420
640
  export type EffectLogic<I, O> = ActorLogic<EffectSnapshot<O, I>, EventObject, I> & EffectSlot;
421
641
 
642
+ /** @internal */
422
643
  type ArgNames<A extends readonly unknown[]> = A[number] extends infer E ? (E extends Arg<infer N> ? N : never) : never;
423
644
  /** A command's input: one string per argv slot, and with `pin` the approved commit (`{ sha }`);
424
645
  * none when there is neither. */
@@ -428,36 +649,110 @@ export type CommandInput<A extends readonly unknown[], P = false> = [
428
649
  ? void
429
650
  : { [K in ArgNames<A> | ([P] extends [true] ? "sha" : never)]: string };
430
651
 
431
- /** An Open argv's input (ADR-0054): its slots are the composer's, so any key is one string — what
432
- * the package's invoke hands it, read by the argv the composer binds. The compiler checks no slot
433
- * against it: one the invoke does not supply faults when the Effect starts (`argvOf`). */
652
+ // An Open argv's slots are the composer's (ADR-0054), so any key is one string — what the
653
+ // package's invoke hands it, read by the argv the composer binds (`argvOf`).
654
+ /** The input of a `command(open)`: a string for each `arg()` of the argv that the Instance binds,
655
+ * and with `pin`, the commit (`{ sha }`). The compiler cannot check the names. When the input has
656
+ * no string for an `arg()`, the Effect fails when it starts. */
434
657
  export type OpenCommandInput<P = false> = Readonly<Record<string, string>> &
435
658
  ([P] extends [true] ? { sha: string } : unknown);
436
659
 
660
+ /** A length of time: a positive number and a unit, `s`, `m` or `h`. For example `"90s"`, `"10m"`
661
+ * or `"1h"`. */
437
662
  export type Duration = `${number}s` | `${number}m` | `${number}h`;
438
663
 
664
+ /** The options of `command()`: the Repo Slot it runs in, how long it can run, how much of its
665
+ * output it keeps, its Effect secrets, and the commit it runs against. */
439
666
  export type CommandOptions = {
440
- /** The Repo Slot whose Worktree is the cwd — needed only where the Workspace carries several. */
667
+ /**
668
+ * The Repo Slot whose Worktree is the working directory of the command. Write it only when the
669
+ * Workspace has more than one Repo Slot. In a Workspace with no Repo Slot, the command starts in
670
+ * `/work`, the folder that the steps of the Workspace share.
671
+ *
672
+ * The command starts in the root of that Worktree. To run it in a subfolder, give the path as an
673
+ * argument, or write `["sh", "-c", "cd <dir> && <program>"]`.
674
+ *
675
+ * When it starts, it refuses:
676
+ * - no `slot` under a Workspace with more than one Repo Slot: `command "<slot>" runs under a
677
+ * Workspace carrying more than one Repo Slot (<slots>) and names none — say which Worktree it
678
+ * runs in: command(argv, { slot: "<slot>" }) (ADR-0051)`. When `env` names an Effect secret,
679
+ * the text is `command "<slot>" runs under a Workspace carrying more than one Repo Slot
680
+ * (<slots>) — say which: { slot: "<slot>" } (ADR-0051)`.
681
+ * - with an Effect secret in `env`, a Workspace with no Repo Slot: `command "<slot>" runs under
682
+ * a Workspace with no Repo Slot: a command with an Effect secret runs against one commit of a
683
+ * Repo Slot's branch, and there is none — declare a Repo Slot, or name no secret in its env, and
684
+ * it runs in /work beside the Agent (ADR-0051)`.
685
+ * - a `slot` that the Workspace does not have: `command "<slot>" names Repo Slot "<name>", which
686
+ * its Workspace does not carry (<slots>)`.
687
+ */
441
688
  slot?: string;
442
- /** Default 10m. On expiry the Harness kills the process group: output `timeout`. */
689
+ /**
690
+ * How long the command can run: a number and a unit, such as `"90s"`, `"10m"` or `"1h"`.
691
+ * Default `"10m"`. When the time ends, jr2 kills the process group, and the output is
692
+ * `timeout`. It refuses a value that is not a positive duration: `command: timeout "<timeout>"
693
+ * is not a positive duration (like "90s", "10m", "1h")`.
694
+ */
443
695
  timeout?: Duration;
444
- /** Characters of the stream's end kept in the output. Default 4096. */
696
+ /** The number of characters that `tail` keeps from the end of what the command wrote. Default
697
+ * 4096. */
445
698
  tail?: number;
446
- /** The env, each a secret's value — `secret("x")` — or its file's path — `secret("x").path`
447
- * (ADR-0067). Naming a secret places the command in the Sandbox Effector, against the pinned
448
- * commit; with none it runs in the Harness container. */
699
+ // Naming a secret is the placement (ADR-0067, ADR-0068): there is no placement key.
700
+ /**
701
+ * The environment variables of the command, each an Effect secret. `secret("x")` gives the
702
+ * value, and `secret("x").path` gives the path of a file that holds the value. When `env` names
703
+ * one, the command runs in the Sandbox Effector against one commit, not in the Worktree that the
704
+ * Agent changes. Without `env`, it runs in the Harness container, beside the Agent.
705
+ *
706
+ * It refuses a name that is not a variable name, and `PATH`, `HOME` and `TMPDIR`: ``command(argv,
707
+ * { env }): "<name>" is not a variable the author sets — PATH, HOME and TMPDIR are the
708
+ * Effector's (ADR-0067)``.
709
+ */
449
710
  env?: CommandEnv;
450
- /** A credentialed command's commit: the Effect's input names it (`{ sha }`, an approved one);
451
- * absent, the Agent's branch tip when it starts. */
711
+ /**
712
+ * Only for a command whose `env` names an Effect secret. `true`: the input of the Effect names
713
+ * the commit to run against (`{ sha }`, a full commit id, for example one that a Gate approved).
714
+ * Default: the tip of the Agent's branch when the Effect starts.
715
+ *
716
+ * It refuses `pin: true` on a command that names no Effect secret: ``command(argv, { pin: true
717
+ * }) names no secret in its env: `pin` chooses the commit a credentialed command runs against in
718
+ * the Sandbox Effector, and a command that names no secret runs in the live Worktree (ADR-0067)
719
+ * — name its secret, command(argv, { env: { NAME: secret("x") }, pin: true }), or drop `pin` ``.
720
+ *
721
+ * When it starts, it refuses an input `sha` that is not a full commit id: `command "<slot>"
722
+ * (pin): its input's sha is not a full commit id`.
723
+ */
452
724
  pin?: boolean;
453
725
  };
454
726
 
455
727
  /** The Open part a command's options state (ADR-0054): its secret, when its env holds
456
- * `secret(open)` or its `.path`. */
728
+ * `secret(open)` or its `.path`.
729
+ *
730
+ * @internal
731
+ */
457
732
  export type EnvOpenParts<Opt> = Opt extends { readonly env: infer E }
458
733
  ? OpenSecretIn<E extends Readonly<Record<string, infer V>> ? V : never>
459
734
  : never;
460
735
 
736
+ /**
737
+ * Why an Effect kind cannot run in a Workspace with no Repo Slot (ADR-0051, ADR-0067), or undefined
738
+ * when it can: a `push` and a credentialed `command` run on a commit of a slot's branch, and that
739
+ * Workspace has none. The invoke and the `jr2 up` walk refuse with the same words.
740
+ */
741
+ export function needsASlot(kind: string): string | undefined {
742
+ if (kind === "push")
743
+ return 'a push publishes a Repo Slot\'s branch, and there is none — declare one: repos: { app: "<url>" }';
744
+ if (kind === "command") {
745
+ return (
746
+ "a command with an Effect secret runs against one commit of a Repo Slot's branch, and there is none — " +
747
+ "declare a Repo Slot, or name no secret in its env, and it runs in /work beside the Agent"
748
+ );
749
+ }
750
+ return undefined;
751
+ }
752
+
753
+ /** The work root of a Sandbox pod (ADR-0004): where a command runs in a Workspace with no Repo Slot. */
754
+ const WORK_ROOT = "/work";
755
+
461
756
  const DEFAULT_TIMEOUT_MS = 10 * 60_000;
462
757
  const DEFAULT_TAIL = 4096;
463
758
  /** How long a re-asked request waits, at most, after a transport failure. */
@@ -852,8 +1147,10 @@ function argvOf(slot: string, parts: CommandParts, input: unknown): string[] {
852
1147
 
853
1148
  /**
854
1149
  * The cwd: a Repo Slot's Worktree (ADR-0051). Named by `slot`, or the one slot where the Workspace
855
- * carries one; a Workspace with none keeps the Harness's own (`/work`). Several and none named is
856
- * refused, as a Turn's Frame is (ADR-0057): the kit gives no slot a meaning.
1150
+ * carries one; a Workspace with none runs it in `/work`, which its steps share. Named, never left to
1151
+ * the Harness: a spawn with no cwd runs in the Sandbox Image's own `WORKDIR`, which is the image's.
1152
+ * Several and none named is refused, as a Turn's Frame is (ADR-0057): the kit gives no slot a
1153
+ * meaning.
857
1154
  */
858
1155
  function cwdOf(slot: string, parts: CommandParts, ambient: AmbientHandles): { cwd?: string } {
859
1156
  const slots = Object.keys(ambient.repos);
@@ -867,7 +1164,7 @@ function cwdOf(slot: string, parts: CommandParts, ambient: AmbientHandles): { cw
867
1164
  }
868
1165
  return { cwd };
869
1166
  }
870
- if (slots.length === 0) return {};
1167
+ if (slots.length === 0) return { cwd: WORK_ROOT };
871
1168
  if (slots.length === 1) return { cwd: ambient.repos[slots[0]!]! };
872
1169
  throw new Error(
873
1170
  `command "${slot}" runs under a Workspace carrying more than one Repo Slot (${slots.join(", ")}) and names ` +