@jr2/orchestrator 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/console/components/app.ts +55 -15
  2. package/console/components/bottom-drawer.ts +259 -0
  3. package/console/components/drawer.ts +14 -6
  4. package/console/components/fleet.ts +36 -13
  5. package/console/components/machine-pane.ts +61 -5
  6. package/console/components/nav.ts +54 -8
  7. package/console/conversation.ts +161 -0
  8. package/console/login.ts +123 -0
  9. package/console/main.ts +600 -60
  10. package/console/store.ts +611 -64
  11. package/console/style.css +231 -0
  12. package/docs/agents-block.md +2 -2
  13. package/docs/concepts/agents-and-turns.md +142 -29
  14. package/docs/concepts/architecture.md +60 -34
  15. package/docs/concepts/callers-and-hooks.md +205 -0
  16. package/docs/concepts/charts.md +13 -8
  17. package/docs/concepts/composition.md +10 -1
  18. package/docs/concepts/converge.md +7 -2
  19. package/docs/concepts/durable-runs.md +43 -16
  20. package/docs/concepts/effects.md +133 -50
  21. package/docs/concepts/gates.md +95 -25
  22. package/docs/concepts/machines-and-workflows.md +14 -4
  23. package/docs/concepts/pools-and-sources.md +193 -17
  24. package/docs/concepts/secrets-and-trust.md +186 -62
  25. package/docs/concepts/the-menu.md +14 -0
  26. package/docs/concepts/workspaces.md +135 -56
  27. package/docs/get-started/a-model-decides.md +14 -13
  28. package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
  29. package/docs/get-started/connect-a-model.md +1 -1
  30. package/docs/get-started/create-an-instance.md +17 -14
  31. package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
  32. package/docs/get-started/install.md +4 -0
  33. package/docs/guides/add-tools-to-the-sandbox.md +72 -15
  34. package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
  35. package/docs/guides/answer-a-gate.md +74 -37
  36. package/docs/guides/bind-open-parts.md +29 -12
  37. package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
  38. package/docs/guides/cancel-a-run.md +26 -16
  39. package/docs/guides/cut-a-worktree.md +560 -0
  40. package/docs/guides/fix-a-failed-converge.md +19 -7
  41. package/docs/guides/give-a-caller-a-scope.md +203 -0
  42. package/docs/guides/give-an-agent-mcp-tools.md +422 -0
  43. package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
  44. package/docs/guides/let-an-agent-write-a-chart.md +14 -7
  45. package/docs/guides/look-inside-a-sandbox.md +115 -42
  46. package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
  47. package/docs/guides/open-the-console.md +63 -14
  48. package/docs/guides/push-and-sign-a-branch.md +67 -21
  49. package/docs/guides/read-a-conversation.md +225 -0
  50. package/docs/guides/run-a-chart-by-name.md +17 -16
  51. package/docs/guides/run-a-command-or-call-a-url.md +84 -41
  52. package/docs/guides/run-one-worker-per-item.md +34 -27
  53. package/docs/guides/set-an-agents-access-and-dials.md +69 -22
  54. package/docs/guides/size-a-workspace.md +19 -6
  55. package/docs/guides/start-a-run-for-each-alert.md +290 -0
  56. package/docs/guides/start-a-run-from-a-run.md +18 -16
  57. package/docs/guides/start-a-run.md +24 -18
  58. package/docs/guides/steer-an-agent.md +332 -0
  59. package/docs/guides/use-a-hosted-provider.md +7 -1
  60. package/docs/guides/watch-a-run.md +146 -117
  61. package/docs/guides/write-a-hook-kind.md +238 -0
  62. package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
  63. package/docs/guides/write-a-workflow.md +18 -8
  64. package/docs/llms.txt +110 -23
  65. package/docs/reference/authoring-surface/agent.md +35 -12
  66. package/docs/reference/authoring-surface/answer.md +20 -4
  67. package/docs/reference/authoring-surface/arg.md +28 -6
  68. package/docs/reference/authoring-surface/basic.md +1 -1
  69. package/docs/reference/authoring-surface/bearer.md +1 -1
  70. package/docs/reference/authoring-surface/branchname.md +15 -0
  71. package/docs/reference/authoring-surface/carry.md +23 -0
  72. package/docs/reference/authoring-surface/chartschema.md +1 -1
  73. package/docs/reference/authoring-surface/chartschemafor.md +1 -1
  74. package/docs/reference/authoring-surface/command.md +37 -22
  75. package/docs/reference/authoring-surface/customize.md +4 -1
  76. package/docs/reference/authoring-surface/defineconfig.md +1 -1
  77. package/docs/reference/authoring-surface/defineevent.md +1 -1
  78. package/docs/reference/authoring-surface/dispatch.md +5 -4
  79. package/docs/reference/authoring-surface/fail.md +18 -5
  80. package/docs/reference/authoring-surface/file.md +37 -17
  81. package/docs/reference/authoring-surface/frompick.md +1 -1
  82. package/docs/reference/authoring-surface/gate.md +118 -0
  83. package/docs/reference/authoring-surface/group-agents.md +15 -0
  84. package/docs/reference/authoring-surface/group-charts.md +16 -0
  85. package/docs/reference/authoring-surface/group-composition.md +25 -0
  86. package/docs/reference/authoring-surface/group-config.md +17 -0
  87. package/docs/reference/authoring-surface/group-effects.md +48 -0
  88. package/docs/reference/authoring-surface/group-failure.md +13 -0
  89. package/docs/reference/authoring-surface/group-gates.md +16 -0
  90. package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
  91. package/docs/reference/authoring-surface/group-steering.md +12 -0
  92. package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
  93. package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
  94. package/docs/reference/authoring-surface/http.md +109 -9
  95. package/docs/reference/authoring-surface/isfailure.md +1 -1
  96. package/docs/reference/authoring-surface/isready.md +14 -0
  97. package/docs/reference/authoring-surface/jr2setup.md +14 -4
  98. package/docs/reference/authoring-surface/mcp.md +107 -0
  99. package/docs/reference/authoring-surface/open.md +41 -8
  100. package/docs/reference/authoring-surface/openbody.md +1 -1
  101. package/docs/reference/authoring-surface/paramsof.md +23 -5
  102. package/docs/reference/authoring-surface/pool.md +79 -11
  103. package/docs/reference/authoring-surface/push.md +31 -13
  104. package/docs/reference/authoring-surface/readyof.md +21 -0
  105. package/docs/reference/authoring-surface/reread.md +19 -0
  106. package/docs/reference/authoring-surface/secret.md +26 -5
  107. package/docs/reference/authoring-surface/sentby.md +1 -1
  108. package/docs/reference/authoring-surface/skip.md +22 -0
  109. package/docs/reference/authoring-surface/source.md +59 -4
  110. package/docs/reference/authoring-surface/steer.md +93 -0
  111. package/docs/reference/authoring-surface/text.md +1 -1
  112. package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
  113. package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
  114. package/docs/reference/authoring-surface/type-agentslots.md +4 -1
  115. package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
  116. package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
  117. package/docs/reference/authoring-surface/type-bindactors.md +14 -0
  118. package/docs/reference/authoring-surface/type-binding.md +6 -7
  119. package/docs/reference/authoring-surface/type-caller.md +40 -0
  120. package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
  121. package/docs/reference/authoring-surface/type-carry.md +19 -0
  122. package/docs/reference/authoring-surface/type-chainlink.md +3 -3
  123. package/docs/reference/authoring-surface/type-commandinput.md +4 -3
  124. package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
  125. package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
  126. package/docs/reference/authoring-surface/type-customize.md +20 -3
  127. package/docs/reference/authoring-surface/type-delivered.md +17 -0
  128. package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
  129. package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
  130. package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
  131. package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
  132. package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
  133. package/docs/reference/authoring-surface/type-filebody.md +4 -8
  134. package/docs/reference/authoring-surface/type-framepath.md +14 -0
  135. package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
  136. package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
  137. package/docs/reference/authoring-surface/type-gatelook.md +37 -0
  138. package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
  139. package/docs/reference/authoring-surface/type-gatereply.md +19 -0
  140. package/docs/reference/authoring-surface/type-gateslot.md +21 -0
  141. package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
  142. package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
  143. package/docs/reference/authoring-surface/type-gateslots.md +12 -0
  144. package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
  145. package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
  146. package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
  147. package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
  148. package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
  149. package/docs/reference/authoring-surface/type-jr2config.md +37 -2
  150. package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
  151. package/docs/reference/authoring-surface/type-lends.md +5 -1
  152. package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
  153. package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
  154. package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
  155. package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
  156. package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
  157. package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
  158. package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
  159. package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
  160. package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
  161. package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
  162. package/docs/reference/authoring-surface/type-namestart.md +77 -0
  163. package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
  164. package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
  165. package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
  166. package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
  167. package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
  168. package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
  169. package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
  170. package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
  171. package/docs/reference/authoring-surface/type-poolspec.md +11 -6
  172. package/docs/reference/authoring-surface/type-pushinput.md +22 -3
  173. package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
  174. package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
  175. package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
  176. package/docs/reference/authoring-surface/type-reposlot.md +6 -6
  177. package/docs/reference/authoring-surface/type-reread.md +18 -0
  178. package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
  179. package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
  180. package/docs/reference/authoring-surface/type-sender.md +26 -1
  181. package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
  182. package/docs/reference/authoring-surface/type-size.md +1 -1
  183. package/docs/reference/authoring-surface/type-skip.md +19 -0
  184. package/docs/reference/authoring-surface/type-slothandles.md +5 -13
  185. package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
  186. package/docs/reference/authoring-surface/type-sourcein.md +25 -0
  187. package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
  188. package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
  189. package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
  190. package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
  191. package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
  192. package/docs/reference/authoring-surface/type-steerargs.md +20 -0
  193. package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
  194. package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
  195. package/docs/reference/authoring-surface/type-workspaced.md +30 -9
  196. package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
  197. package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
  198. package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
  199. package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
  200. package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
  201. package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
  202. package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
  203. package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
  204. package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
  205. package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
  206. package/docs/reference/authoring-surface/workspace.md +66 -38
  207. package/docs/reference/authoring-surface/worktree.md +71 -0
  208. package/docs/reference/authoring-surface.md +88 -147
  209. package/docs/reference/cli.md +413 -74
  210. package/docs/reference/config.md +248 -6
  211. package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
  212. package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
  213. package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
  214. package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
  215. package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
  216. package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
  217. package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
  218. package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
  219. package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
  220. package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
  221. package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
  222. package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
  223. package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
  224. package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
  225. package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
  226. package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
  227. package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
  228. package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
  229. package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
  230. package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
  231. package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
  232. package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
  233. package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
  234. package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
  235. package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
  236. package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
  237. package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
  238. package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
  239. package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
  240. package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
  241. package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
  242. package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
  243. package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
  244. package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
  245. package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
  246. package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
  247. package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
  248. package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
  249. package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
  250. package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
  251. package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
  252. package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
  253. package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
  254. package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
  255. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
  256. package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +53 -23
  257. package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
  258. package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
  259. package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
  260. package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
  261. package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
  262. package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
  263. package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
  264. package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
  265. package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
  266. package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
  267. package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
  268. package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
  269. package/docs/reference/decisions.md +9 -4
  270. package/docs/reference/glossary.md +138 -45
  271. package/docs/reference/hooks.md +1077 -0
  272. package/docs/reference/http-api.md +988 -123
  273. package/docs/reference/machines.md +0 -1
  274. package/docs/why.md +7 -5
  275. package/package.json +10 -3
  276. package/src/actor.ts +194 -42
  277. package/src/agent.ts +56 -6
  278. package/src/ambient.ts +3 -5
  279. package/src/assembly-checks.ts +530 -4
  280. package/src/callers.ts +245 -0
  281. package/src/chart.ts +238 -26
  282. package/src/charts.ts +9 -2
  283. package/src/config.ts +566 -7
  284. package/src/conversations.ts +453 -0
  285. package/src/custodian.lua +17 -5
  286. package/src/custodian.ts +257 -77
  287. package/src/customize.ts +154 -7
  288. package/src/dispatch-effect.ts +142 -26
  289. package/src/durability.ts +2 -0
  290. package/src/duration.ts +20 -0
  291. package/src/effect-checks.ts +52 -4
  292. package/src/effect.ts +401 -132
  293. package/src/effector-wire.ts +278 -30
  294. package/src/fail.ts +17 -4
  295. package/src/gate-brand.ts +16 -0
  296. package/src/gate.ts +634 -48
  297. package/src/harness-client.ts +84 -35
  298. package/src/harness-env.ts +33 -0
  299. package/src/held-secrets.ts +197 -39
  300. package/src/holds.ts +5 -2
  301. package/src/hook-kinds/discord.ts +92 -0
  302. package/src/hook-kinds/door.ts +107 -0
  303. package/src/hook-kinds/github.ts +89 -0
  304. package/src/hook-kinds/gitlab.ts +72 -0
  305. package/src/hook-kinds/hook.ts +7 -0
  306. package/src/hook-kinds/index.ts +465 -0
  307. package/src/hook-kinds/slack.ts +151 -0
  308. package/src/hook-kinds/webhook.ts +113 -0
  309. package/src/hooks.ts +506 -0
  310. package/src/http-effect.ts +164 -70
  311. package/src/http.ts +933 -99
  312. package/src/index.ts +30 -5
  313. package/src/instance.ts +196 -26
  314. package/src/internal.ts +23 -3
  315. package/src/kube-client.ts +6 -3
  316. package/src/machine-doc.ts +29 -5
  317. package/src/mcp-effect.ts +424 -0
  318. package/src/model-keys.ts +24 -0
  319. package/src/mount-digest.ts +41 -7
  320. package/src/names.ts +82 -7
  321. package/src/notices.ts +576 -59
  322. package/src/open-body.ts +110 -30
  323. package/src/open-effect.ts +17 -4
  324. package/src/open.ts +40 -7
  325. package/src/parts.ts +125 -35
  326. package/src/party.ts +35 -1
  327. package/src/pick.ts +43 -9
  328. package/src/pool.ts +1172 -172
  329. package/src/registration.ts +249 -17
  330. package/src/repo-identity.ts +7 -3
  331. package/src/repos.ts +20 -2
  332. package/src/run-host.ts +1614 -190
  333. package/src/run-list.ts +191 -0
  334. package/src/sandbox-effect.ts +217 -66
  335. package/src/sandbox-kube.ts +116 -21
  336. package/src/secret-placement.ts +248 -35
  337. package/src/server.ts +53 -4
  338. package/src/setup.ts +158 -38
  339. package/src/snapshot-store.ts +565 -48
  340. package/src/steer.ts +111 -0
  341. package/src/stub-harness.ts +6 -2
  342. package/src/tokens.ts +362 -46
  343. package/src/vocabulary.ts +23 -4
  344. package/src/wire.ts +27 -10
  345. package/src/workspace.ts +314 -259
  346. package/src/worktree.ts +249 -0
  347. package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
  348. package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
  349. package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
  350. package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
@@ -0,0 +1,249 @@
1
+ // `worktree()`, the kit Effect that cuts a Worktree, and the
2
+ // path types that carry a body's Repo Slots to every Worktree input.
3
+ //
4
+ // A Worktree is a folder, `/work/<slot>/<name>`. The Workspace clones only; the Machine cuts. The cut
5
+ // runs on the `command` machinery: a kit program (`packages/harness/src/cut.ts`) on the runtime
6
+ // volume, no new wire kind, marked idempotent, so a restore that finds no exec sends it again with
7
+ // the same id. What an Agent's param can cause settles as `refused`, never `onError` (ADR-0067). A
8
+ // settled `ready` row keeps the slot, the path and the branch as fields: a push reads its branch
9
+ // there, because the row's output drops its tail and a name in an input is a Chart's word.
10
+
11
+ import { z } from "zod";
12
+ import type { Duration, EffectLogic, ExecPortFactory, LaterOutcome, Unknown } from "./effect.ts";
13
+ import { commandWith } from "./effect.ts";
14
+ import { createExecPort } from "./harness-client.ts";
15
+
16
+ /** What a Worktree's name may start with: neither `.` nor `-`, and not the end of the path. */
17
+ type NameStart =
18
+ | "a"
19
+ | "b"
20
+ | "c"
21
+ | "d"
22
+ | "e"
23
+ | "f"
24
+ | "g"
25
+ | "h"
26
+ | "i"
27
+ | "j"
28
+ | "k"
29
+ | "l"
30
+ | "m"
31
+ | "n"
32
+ | "o"
33
+ | "p"
34
+ | "q"
35
+ | "r"
36
+ | "s"
37
+ | "t"
38
+ | "u"
39
+ | "v"
40
+ | "w"
41
+ | "x"
42
+ | "y"
43
+ | "z"
44
+ | "A"
45
+ | "B"
46
+ | "C"
47
+ | "D"
48
+ | "E"
49
+ | "F"
50
+ | "G"
51
+ | "H"
52
+ | "I"
53
+ | "J"
54
+ | "K"
55
+ | "L"
56
+ | "M"
57
+ | "N"
58
+ | "O"
59
+ | "P"
60
+ | "Q"
61
+ | "R"
62
+ | "S"
63
+ | "T"
64
+ | "U"
65
+ | "V"
66
+ | "W"
67
+ | "X"
68
+ | "Y"
69
+ | "Z"
70
+ | "0"
71
+ | "1"
72
+ | "2"
73
+ | "3"
74
+ | "4"
75
+ | "5"
76
+ | "6"
77
+ | "7"
78
+ | "8"
79
+ | "9"
80
+ | "_";
81
+
82
+ /** A Worktree of any Repo Slot: what a kit factory states, before `jr2Setup` narrows it to the
83
+ * slots of the body. */
84
+ export type AnyWorktreePath = `/work/${string}/${string}`;
85
+ /** A Worktree of the Repo Slots `S`. One segment below the slot, that starts with a letter, a
86
+ * digit or `_`: `${context.workspace.repos.app}/run`. The compiler sees the start of the name; a
87
+ * `/` further on is the Effect's own refusal. */
88
+ export type WorktreePath<S extends string> = `/work/${S}/${NameStart}${string}`;
89
+ /** A Turn's Frame: a Worktree, or `/work`, stated. `jr2Setup` narrows it to the body's slots. */
90
+ export type AnyFramePath = "/work" | `/work/${string}`;
91
+ /** A Turn's Frame in a Workspace with the Repo Slots `S`. */
92
+ export type FramePath<S extends string> = WorktreePath<S> | "/work";
93
+
94
+ /** A pick param that names a branch: the rule the cut and the push share, as one JSON-schema
95
+ * pattern, so that a bad name is refused at the Menu, and the Agent can correct it. */
96
+ export const BRANCH_NAME =
97
+ /^(?!\/)(?!HEAD$)(?!refs\/)(?!origin\/)(?![0-9a-f]{7,40}$)(?!.*\.\.)(?!.*\/\/)(?!(?:.*\/)?[.-])(?!.*\.lock(?:\/|$))(?!.*[./]$)[A-Za-z0-9._/-]+$/;
98
+
99
+ /** A kit schema for a pick param that names a branch. For example:
100
+ * `input: z.object({ branch: branchName })`. */
101
+ export const branchName = z.string().regex(BRANCH_NAME, "a branch name git and jr2's push both take");
102
+
103
+ /** The input of a `worktree()`: a branch Worktree, or a detached one. */
104
+ export type WorktreeInput =
105
+ | {
106
+ /** The root of the Worktree: `${context.workspace.repos.app}/<name>`, a constant word. */
107
+ path: AnyWorktreePath;
108
+ /** The branch. It resolves as a local branch, then `origin/<branch>` (tracked), then a new
109
+ * branch. */
110
+ branch: string;
111
+ /** Where a new branch starts. Default: the Binding's base. On a branch that exists, it must
112
+ * be that branch's tip, or the cut is `refused`. */
113
+ from?: string;
114
+ detach?: never;
115
+ expect?: never;
116
+ }
117
+ | {
118
+ path: AnyWorktreePath;
119
+ /** The commit to check out, with no branch. The Worktree is forced to it and cleaned on each
120
+ * cut. Ignored files stay, so that a reviewer can run the tests. */
121
+ detach: string;
122
+ branch?: never;
123
+ from?: never;
124
+ expect?: never;
125
+ };
126
+
127
+ /** What a cut found. `held`: a cut made this Worktree earlier, so what is in it is earlier work;
128
+ * `local`, a local branch; `remote`, `origin/<branch>`, now tracked; `none`, a new branch with no
129
+ * upstream; `commit`, a detached cut. Open: a later value does not break an author's code. */
130
+ export type WorktreeFound = "held" | "local" | "remote" | "none" | "commit" | (string & {});
131
+
132
+ export type WorktreeReady<S extends string = string> = {
133
+ outcome: "ready";
134
+ slot: S;
135
+ path: WorktreePath<S>;
136
+ /** The branch of the cut, from git and the cut's record, never from the folder or HEAD. `null`
137
+ * when the Worktree is detached. */
138
+ branch: string | null;
139
+ /** The tip of `branch` (of the commit, when detached). Never HEAD. */
140
+ head: string;
141
+ found: WorktreeFound;
142
+ /** ADR-0053's words when the remote did not answer. The cut used what the clone holds. */
143
+ stale?: string;
144
+ };
145
+
146
+ /** No Worktree, and nothing changed. The cause is a bad path, a name git or the push refuses, a
147
+ * re-cut that does not agree with the first, a branch that another Worktree holds, an `expect` that does not hold, or a
148
+ * name that the remote cannot confirm. */
149
+ export type WorktreeRefused = { outcome: "refused"; reason: string };
150
+
151
+ /** The output of a `worktree()`. `unknown`: its state exited while it ran, or the cut did not end. */
152
+ export type WorktreeOutput<S extends string = string> = WorktreeReady<S> | WorktreeRefused | Unknown | LaterOutcome;
153
+
154
+ /** Is it `ready`? For a guard. A Chart's form is the guard `jr2.ready`. */
155
+ export const isReady = <O extends { outcome: string }>(output: O): output is Extract<O, { outcome: "ready" }> =>
156
+ output.outcome === "ready";
157
+
158
+ /** The `ready` output of an `onDone` arm that `isReady` guards. xstate carries no guard's narrowing
159
+ * to its actions, so an `assign` reads the output through this. */
160
+ export function readyOf<O extends { outcome: string }>(output: O): Extract<O, { outcome: "ready" }> {
161
+ if (output.outcome !== "ready") throw new Error("readyOf: the output is not ready: guard the arm with isReady");
162
+ return output as Extract<O, { outcome: "ready" }>;
163
+ }
164
+
165
+ /** The options of `worktree()`. */
166
+ export type WorktreeOptions = {
167
+ /** `"new"`: refuse a branch that exists, local or on the remote. For a name that a planner or a
168
+ * door chose for new work: a collision is a refusal, not someone else's branch. `"existing"`:
169
+ * refuse a branch that does not exist, and make nothing. For a run that must continue a branch.
170
+ * A Chart cannot change it, and a cut lent to a Chart must state it. */
171
+ expect?: "new" | "existing";
172
+ /** How long the fetch and the cut can take. Default `"10m"`. */
173
+ timeout?: Duration;
174
+ };
175
+
176
+ /** Over an injected port: the seam of the tests. */
177
+ export function worktreeWith(
178
+ portFactory: ExecPortFactory,
179
+ options: WorktreeOptions = {},
180
+ ): EffectLogic<WorktreeInput, WorktreeOutput> {
181
+ return commandWith(
182
+ portFactory,
183
+ ["/opt/jr2/bin/node", "/opt/jr2/src/cut.ts"],
184
+ { timeout: options.timeout ?? "10m" },
185
+ undefined,
186
+ { ...(options.expect !== undefined ? { expect: options.expect } : {}) },
187
+ ) as unknown as EffectLogic<WorktreeInput, WorktreeOutput>;
188
+ }
189
+
190
+ /**
191
+ * Make a `worktree` Effect: it cuts a Worktree of a Repo Slot, or reads the one that is there. Put it
192
+ * in `actors`, and invoke it from a `workspace()` body.
193
+ *
194
+ * With `{ path, branch, from? }`, the branch resolves in this order, after a fetch: a local branch;
195
+ * `origin/<branch>`, tracked; a new branch from `from` or the Binding's base, with no upstream. With
196
+ * `{ path, detach }`, the Worktree is forced to that commit and cleaned, each time. `path` is exactly
197
+ * `/work/<slot>/<name>`. Each outcome is output: `ready`, `refused`, `unknown`.
198
+ *
199
+ * `expect` says if the branch can exist: `worktree({ expect: "new" })` refuses a branch that exists,
200
+ * and `worktree({ expect: "existing" })` refuses a branch that does not exist. A run cannot change it.
201
+ *
202
+ * A cut needs a Repo Slot. Under a Workspace with no Repo Slot, `jr2 up` refuses it, and the invoke
203
+ * fails with `worktree "<slot>" runs under a Workspace with no Repo Slot: a cut makes a Worktree of a
204
+ * Repo Slot, and there is none — declare one: repos: { app: "<url>" } (ADR-0075)`.
205
+ *
206
+ * @param options - Optional. `expect`, and `timeout`, how long the fetch and the cut can take.
207
+ * Default: `"10m"`.
208
+ * @returns The actor logic of the Effect's slot. Its output is a {@link WorktreeOutput}.
209
+ *
210
+ * @example
211
+ * ```ts
212
+ * import { fail, isReady, jr2Setup, worktree, type Workspaced } from "@jr2/orchestrator";
213
+ *
214
+ * type BodyInput = Workspaced<{ branch: string }, "app">;
215
+ * const tree = (context: BodyInput) => `${context.workspace.repos.app}/fix` as const;
216
+ *
217
+ * export const body = jr2Setup({
218
+ * types: {} as { input: BodyInput; context: BodyInput },
219
+ * actors: { cut: worktree({ expect: "new" }), fail: fail() },
220
+ * }).createMachine({
221
+ * context: ({ input }) => input,
222
+ * on: { "workspace.lost": ".notCut" },
223
+ * initial: "cutting",
224
+ * states: {
225
+ * cutting: {
226
+ * invoke: {
227
+ * src: "cut",
228
+ * input: ({ context }) => ({ path: tree(context), branch: context.branch }),
229
+ * // The last arm has no guard: it takes `refused`, `unknown` and each later outcome.
230
+ * onDone: [{ guard: ({ event }) => isReady(event.output), target: "done" }, { target: "notCut" }],
231
+ * },
232
+ * },
233
+ * done: { type: "final" },
234
+ * notCut: { invoke: { src: "fail", input: { reason: "the Worktree was not cut" } } },
235
+ * },
236
+ * });
237
+ * ```
238
+ *
239
+ * @see docs/guides/cut-a-worktree.md
240
+ * @see docs/concepts/workspaces.md
241
+ * @see docs/concepts/effects.md
242
+ * @see docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md
243
+ */
244
+ export function worktree(options: WorktreeOptions = {}): EffectLogic<WorktreeInput, WorktreeOutput> {
245
+ return worktreeWith(
246
+ (endpoint, bearer) => createExecPort({ baseUrl: endpoint, ...(bearer ? { token: bearer } : {}) }),
247
+ options,
248
+ );
249
+ }
@@ -1,26 +0,0 @@
1
- ---
2
- title: NoSlotSpec
3
- description: "What the `spec` of a `workspace()` with no Repo Slot returns: only `workGroup`."
4
- ---
5
-
6
- # `NoSlotSpec`
7
-
8
- Type, in `@jr2/orchestrator/src/workspace.ts`. The signature of an export, or a type that it names, names this type. The [Authoring surface](../authoring-surface.md) page lists each export. This type is not an export of `@jr2/orchestrator`, so a workflow module does not import it by name.
9
-
10
- ```ts
11
- type NoSlotSpec = Pick<WorkspaceSpec, "workGroup"> & { branch?: never; reviewSha?: never };
12
- ```
13
-
14
- What the `spec` of a `workspace()` with no Repo Slot returns: only `workGroup`. There is no
15
- branch and no review Worktree, because the Workspace has no Repo Slot.
16
-
17
- **Fields**
18
-
19
- - `workGroup` (optional): `number`
20
-
21
- The group id that owns the files in `/work`. With it, the Sandbox Image and the User
22
- Container can both write those files when they run as different users. Default: 2000. Set it
23
- to a group id that the User Container's sessions already have, for example when a root sshd
24
- builds their groups from `/etc/group`. A non-negative integer.
25
- - `branch` (optional): `never`
26
- - `reviewSha` (optional): `never`
@@ -1,37 +0,0 @@
1
- ---
2
- title: WorkspaceSpec
3
- description: "What the `spec` of a `workspace()` returns: the branch that the Workspace works on, and two optional fields."
4
- ---
5
-
6
- # `WorkspaceSpec`
7
-
8
- Type, in `@jr2/orchestrator/src/workspace.ts`. The signature of an export, or a type that it names, names this type. The [Authoring surface](../authoring-surface.md) page lists each export. This type is not an export of `@jr2/orchestrator`, so a workflow module does not import it by name.
9
-
10
- ```ts
11
- type WorkspaceSpec = {
12
- branch: string;
13
- workGroup?: number;
14
- reviewSha?: string;
15
- };
16
- ```
17
-
18
- What the `spec` of a `workspace()` returns: the branch that the Workspace works on, and two
19
- optional fields. jr2 calls `spec` with the run input when the run enters the Workspace.
20
-
21
- **Fields**
22
-
23
- - `branch`: `string`
24
-
25
- The branch that the Workspace works on: each Repo Slot gets a Worktree for it, at
26
- `/work/<slot>/<branch>`, with each `/` in the branch written as `-`. Required, and not
27
- `default`, which is the directory of the clone. Your Machine decides its name.
28
- - `workGroup` (optional): `number`
29
-
30
- The group id that owns the files in `/work`. With it, the Sandbox Image and the User
31
- Container can both write those files when they run as different users. Default: 2000. Set it
32
- to a group id that the User Container's sessions already have, for example when a root sshd
33
- builds their groups from `/etc/group`. A non-negative integer.
34
- - `reviewSha` (optional): `string`
35
-
36
- A commit to check out in a second, detached Worktree beside each branch Worktree, for a
37
- reviewer: `/work/<slot>/<branch>-review`. The body gets its paths as `workspace.review`.
@@ -1,61 +0,0 @@
1
- # The Console unlocks with the Instance token, and the bands do not move
2
-
3
- [ADR-0014](0014-observation-is-open-run-state-is-not.md) built the three-band HTTP surface on one premise: the
4
- visualizer page is a browser, and a browser holds no token — so it got a projection instead of a credential, and "give
5
- the page a token" was explicitly rejected. That page is now growing into the **Console**: it starts runs and answers
6
- Gates, both of which are writes on the guarded surface. This ADR records how a browser earns those writes without moving
7
- a single band — and it can stay this small because of a fact ADR-0014 did not lean on: the Instance token is already one
8
- `kubectl get secret` away for exactly the audience the Console serves. There is no new distribution problem, only a new
9
- presentation surface.
10
-
11
- ## Decision
12
-
13
- - **The human presents the token; the page never ships with one.** The Console's nav carries a password field. Empty,
14
- the page is precisely ADR-0014's observer: open structure and observation, no control rendered. Filled, the page sends
15
- `Authorization: Bearer` on the guarded surface — which unlocks both directions at once: the writes
16
- (`POST /workflows/:name/runs`, `POST /runs/:id/gates/:gate/events`) and the `instanceOnly` reads the control UI needs
17
- anyway (`GET /runs`, and `GET /runs/:id` — where `GateView` lives). The page as _served_ stays open and inert;
18
- ADR-0014's rejection stands, because what it rejected was baking the credential into the page.
19
- - **The token lives in `sessionStorage`** — survives a reload, dies with the tab, never a cookie (no ambient credential,
20
- no CSRF surface). It is validated on entry with a cheap guarded read so the field shows live/invalid immediately; any
21
- later 401 drops the UI back to observer mode rather than failing per-widget.
22
- - **Gate discovery is a frame-triggered guarded read, not an open-band extension.** A Gate exists exactly while its
23
- invoking state is entered (ADR-0011), and every entry/exit lands a status frame on the open workflow feed (ADR-0022) —
24
- so on each frame, a token-holding Console re-fetches `GET /runs/:id` for the affected run and folds `gates` into its
25
- store. Perfectly synchronized, no polling loop, and the observation projection is untouched. A delivery's success is
26
- likewise confirmed by the next frame emptying the card, never by local bookkeeping.
27
- - **Page addresses content-negotiate; JSON is the default dialect.** `/` and `/workflows/:name` serve the Console shell
28
- to `Accept: text/html` and JSON to everything else — so the workflow-detail resource keeps its natural address (the
29
- run-input schema's home, [ADR-0033](0033-a-machine-declares-the-input-that-starts-it.md)) and no future page has to
30
- squat on a mangled sibling path. Nothing existing moves: EventSource asks `text/event-stream`, and the Harness's Menu
31
- and the CLI never ask for HTML. `/viz/*` is deleted (greenfield, no redirects); assets move to `/assets/*`, which also
32
- retires the "a workflow named `assets` is shadowed" hazard.
33
-
34
- ## Considered options
35
-
36
- - **`jr2 ui` — the CLI opens the browser through an authenticated local proxy.** A convenience layer worth building _on
37
- top of_ the token field later, not a substitute: it has no answer for the deployed jr2 Application behind an ingress,
38
- where the browser talks to the Service directly and the CLI is not in the path.
39
- - **A fourth, scoped credential** ("may answer gates, may not cancel or read context"). Still rejected, as in ADR-0014 —
40
- but the calculus has narrowed: it becomes worth writing only when gate-answering is delegated to people who must _not_
41
- hold cluster RBAC. That is a real future (an approval inbox for non-operators); it is not this one.
42
- - **Announce gates on the open observation feed** (even just `{runId, gate}`). Rejected: it widens the projection for a
43
- convenience the token already solves, and the open band has an unresolved exposure question as it stands (spawn ids —
44
- the ADR-0014 open call). The projection stays the one place the line is drawn.
45
- - **A `/ui` path prefix, or moving the API under `/api`.** Rejected for negotiation: the first is ceremony on the
46
- address a human types, the second churns the Harness's Menu, the Custodian's route allowlist, the CLI, and the
47
- ADR-0009/0013 route tables for cosmetics.
48
-
49
- ## Consequences
50
-
51
- - **Zero new server surface except negotiation.** No new token, no new band, no new route class; `auth.test.ts`'s
52
- boundary assertions are untouched. The server-side diff is: negotiate two addresses, serve the shell, retire `/viz/*`.
53
- - **Possession remains cluster RBAC.** Whoever can read the instance Secret could already do everything the Console
54
- unlocks; the field changes where the token is typed, not who holds it.
55
- - **The XSS caveat is the real one and is bounded by what the page is:** a token in `sessionStorage` is readable by
56
- scripts on the page, so the Console must keep serving only its own assets — no CDN, no third-party script, exactly the
57
- constraint the viz page already lives under.
58
- - **GLOSSARY.md**: **Console** becomes a glossary term (visualizer, dashboard, UI, viz on its Avoid list).
59
- - **The Console's UX gets a browser tier** — `@console`-tagged Cucumber scenarios holding a Playwright page, in
60
- `@jr2/e2e`, excluded from the default profile ([ADR-0010](0010-bdd-acceptance-tests.md) as amended). What the page
61
- _believes_ stays unit-tested in `console-store.test.ts`; the browser tier asserts what a user does and sees.
@@ -1,65 +0,0 @@
1
- # A persist survives a crash, and history is a week
2
-
3
- Every transition of a run persists its whole snapshot (ADR-0007). The scaling review (R6) read the store and listed its
4
- costs: `JSON.stringify` of the whole run, a synchronous upsert with an fsync on the event loop, a Pool's run as one blob
5
- holding every worker, terminal rows never deleted, and `restore()` parsing every row before the server listens. It also
6
- found the words "Postgres opt-in" in GLOSSARY.md, ADR-0008 and ADR-0009, with no such store in the code. Measured on the
7
- home cluster's chaos Instance (2026-09-30, `local-path` volume): a persist costs **4.5 ms, whatever the blob's size** (1
8
- KB or 100 KB), because the cost is the fsync, not the JSON; with the write-ahead log and `synchronous=NORMAL` the same
9
- upserts cost 0.04 ms (1 KB) and 0.18 ms (100 KB). A run's blob is about 1 KB; a Pool of 40 workers is about 100 KB.
10
-
11
- ## Decision
12
-
13
- - **A persist survives a process crash, not a power loss.** The store runs sqlite in WAL mode with `synchronous=NORMAL`:
14
- a write is durable once the OS holds it, and sqlite fsyncs only at a checkpoint. A node's power loss can lose the
15
- transitions since the last checkpoint; the file stays consistent, and `restore()` treats the older snapshot as it
16
- treats any restart — reconcile against the live world, re-attach, or `lost`. The window in which a crash re-POSTs a
17
- prompt (ADR-0007's at-most-once sliver) widens from one microtask to one checkpoint, and only on a power loss, which
18
- also ends every Sandbox on that node.
19
- - **The write stays synchronous and on the event loop.** At 0.2 ms for a Pool's blob there is nothing to move off the
20
- loop, and the ordering matters: the row is on disk before the Machine's next side effect runs, which is what lets an
21
- epoch bump (ADR-0057), an admission (ADR-0016) or an Effect's intent row (ADR-0067) be saved "at the write" — the
22
- intent row before its call leaves. A worker thread, an asynchronous driver or a network store would land the row after
23
- the next `invoke` fires, on every persist.
24
- - **One blob per run stays.** The snapshot is a tree that xstate restores from its root, and one row keeps it atomic —
25
- with the ledgers beside it (admissions, epochs, notices, timers, outstanding picks, Effects); a Pool's cost at 40
26
- workers is 0.2 ms, so its workers do not become rows of their own. The Effect ledger keeps a row for every Effect the
27
- run started (ADR-0067), so a Machine that loops through an Effect grows its blob by one row per pass: id, kind and
28
- outcome. The output's tail is dropped once the save that carries the Effect's transition is on disk; a push row keeps
29
- its published commit, its source, its `squashed` and its sync's answer whole — every later push of its Repo Slot sends
30
- them as one of its pairs — and its bundle only while it is its Repo Slot's newest push and its sync did not finish —
31
- one bundle per Repo Slot at most. An outstanding pick's hold is the one place tails accumulate: its `chain` keeps
32
- every reply since the pick whole until the pick is answered, then it is dropped (ADR-0067).
33
- - **`restore()` reads live rows only.** The store answers `WHERE status = 'live'`; terminal rows are not parsed at boot.
34
- - **A finished run's row lives 7 days, fixed.** Every non-live status — `done`, `failed`, `error`, `cancelled`, `lost`,
35
- `drifted` — ages from its terminal write, and a sweep at boot and every hour deletes what is older. A `live` row is
36
- never swept: a run parked on a Gate for a month stays and is restored. The value is fixed like the Sandbox idle
37
- timeout, not config: jr2 is not the record of a run's results (a Machine sends its output where it belongs — a PR, a
38
- Source), and `jr2 status` on an old run is a debugging act. A swept id frees its abbreviations (ADR-0009).
39
- - **The store is sqlite on the Instance's volume. No Postgres is owed.** The `SnapshotStore` interface stays as the
40
- seam, with one implementation. `DATABASE_URL` is not a thing jr2 reads.
41
-
42
- ## Considered options
43
-
44
- - **Keep `synchronous=FULL`, move the write to a worker thread.** Rejected: the thread hides 4.5 ms from the loop but
45
- does not make the disk faster, so writes queue under load and the loop no longer knows how far behind the store is —
46
- and it opens the ordering gap on every write, which NORMAL opens only on a power loss.
47
- - **Batch persists in a window.** Rejected: one persist per macrostep already coalesces a transition's events, and with
48
- the fsync gone there is nothing left to amortize.
49
- - **A row per worker.** Rejected: it splits an atomic tree across rows (a worker's row saved, the Pool's not), makes
50
- restore reassemble what xstate restores whole, and buys nothing measurable at 0.2 ms per blob.
51
- - **Configurable retention (`runs.retain`), or a count cap per workflow.** Rejected: a count cap does not bound a busy
52
- workflow's disk and punishes a quiet one; a config key is owed only when a history surface (a list, a query) exists to
53
- use it, and none does — `jr2 runs` and the Console list live runs.
54
- - **Build the Postgres store.** Rejected: the only thing it buys is an Orchestrator pod that reschedules without its
55
- volume, which is a storage-class question (a network RWO volume follows the pod) and belongs to the Orchestrator pod's
56
- own decision (R10), not to the store. Its cost is a dependency the owner operates, a second store in every test tier,
57
- and the asynchronous write above.
58
-
59
- ## Consequences
60
-
61
- - ADR-0008 and ADR-0009 lose "Postgres opt-in" and `DATABASE_URL`; ADR-0008's Orchestrator persists its snapshots "to
62
- sqlite on its volume". ADR-0007 points here for the store's durability.
63
- - The Instance's volume gains `state.db-wal` and `state.db-shm` beside `state.db`.
64
- - A `drifted` run (ADR-0030) is swept a week after the deploy that changed its Machine, not a week after it ended; a
65
- week is the time a human has to read it.