@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
@@ -9,6 +9,15 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
9
9
 
10
10
  ## Decision
11
11
 
12
+ - **What an Effector is for.** It holds the secrets that the Orchestrator must not hold, and it makes every call that
13
+ jr2 makes to the outside for a Machine or a Hook: every `http` and `mcp` Effect runs in an Effector
14
+ ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)), and the
15
+ Orchestrator's one call out of the Instance is the fetch of a JWT issuer's public keys (and of its discovery document,
16
+ which names where the keys are), which spends no secret
17
+ ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)). It serves two clients: a
18
+ Machine's Effects, in both placements, and the Orchestrator's Hook door, in the Instance Effector only, which uses two
19
+ operations: the `mac` check and one `http` call (below). It knows no Hook kind and no service. The reason above holds
20
+ for both clients, and a Hook's secret adds one: it can forge a delivery that answers a Gate.
12
21
  - **One static Go binary, `effector`.** A top-level Go module, `effector/`, beside `operator/`: the same toolchain and
13
22
  lint, `make test` and `make lint`, the standard library only. Go because the Effector is the trust boundary, and the
14
23
  safe file opener (`os.Root`, `openat2`) is Go's. It is configured by its env, which names where its own mount holds
@@ -48,21 +57,46 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
48
57
  (`the remote could not be reached: ssh: …`), with both in the detail: git's own words for an ssh failure name no
49
58
  cause.
50
59
 
51
- - **Two placements, one binary.** The Instance Effector runs `http` with no Worktree file, and `dispatch` toward another
52
- Orchestrator ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), the other Orchestrator's Instance
53
- token being an Effect secret it holds. A Sandbox Effector — the binary with `JR2_EFFECTOR_WORK` set, a read-only view
54
- of the Agent's `/work` — runs an `http` whose body is a Worktree file, `push` and a credentialed `command` (ADR-0067);
55
- the Instance Effector refuses the last two, and a file body, as a defect, and a Sandbox Effector refuses a `dispatch`.
56
- Its env names where its mount holds the rest: `JR2_EFFECTOR_REPOS` (the node's Repo caches, read-only),
57
- `JR2_EFFECTOR_GIT` (the push credentials: one directory per `git.credentials` entry the Sandbox's pushes spend, the
58
- entry's own Secret as the operator projects it — `entry.json`, what the entry is; `identity` and `known_hosts` for an
59
- ssh key; `password` for a token; `sign.identity` or `sign.private.asc` for the signing key; see the Sandbox Effector's
60
- objects below), `JR2_EFFECTOR_SCRATCH` (its own writable scratch). What jr2 guarantees is stated per kind: for `push`
61
- and a file body, git never takes config or hooks from the Agent's repository and no program the Agent's files name
62
- runs — the clean-repository procedure of ADR-0067 and the opener below. jr2's own programs (`git`, `ssh`,
63
- `ssh-keygen`, `gpg`, `gpg-agent`) run by an absolute path found in one fixed PATH, `/usr/local/bin:/usr/bin:/bin`,
64
- never by the PATH the image set: an image that puts a directory of its own first does not choose which `git` runs. For
65
- a credentialed `command` the Agent's files are the author's choice to run, and the claims are the next bullet's.
60
+ - **Two placements, one binary.** The Instance Effector runs `http` with no Worktree file, `mcp`
61
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)), and
62
+ `dispatch` toward another Orchestrator ([ADR-0071](0071-a-run-starts-a-run-through-a-dispatch-effect.md)), the other
63
+ Orchestrator's Caller token being an Effect secret it holds. It also serves the Hook door (below). A Sandbox Effector
64
+ — the binary with `JR2_EFFECTOR_WORK` set, a read-only view of the Agent's `/work` — runs an `http` whose body is a
65
+ Worktree file, `push` and a credentialed `command` (ADR-0067); the Instance Effector refuses the last two, and a file
66
+ body, as a defect, and a Sandbox Effector refuses a `dispatch` and an `mcp` Effect. Under Repo Slots each of the three
67
+ needs a cut's settled row in the run's ledger, because a path alone does not name the target of a credential: the
68
+ Orchestrator reads the row before it starts the Effect, and with no row it sends nothing and settles the kind's own
69
+ outcome ([ADR-0075](0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md)). Its env names where its mount
70
+ holds the rest: `JR2_EFFECTOR_REPOS` (the node's Repo caches, read-only), `JR2_EFFECTOR_GIT` (the push credentials:
71
+ one directory per `git.credentials` entry the Sandbox's pushes spend, the entry's own Secret as the operator projects
72
+ it — `entry.json`, what the entry is; `identity` and `known_hosts` for an ssh key; `password` for a token;
73
+ `sign.identity` or `sign.private.asc` for the signing key; see the Sandbox Effector's objects below),
74
+ `JR2_EFFECTOR_SCRATCH` (its own writable scratch). What jr2 guarantees is stated per kind: for `push` and a file body,
75
+ git never takes config or hooks from the Agent's repository and no program the Agent's files name runs — the
76
+ clean-repository procedure of ADR-0067 and the opener below. jr2's own programs (`git`, `ssh`, `ssh-keygen`, `gpg`,
77
+ `gpg-agent`) run by an absolute path found in one fixed PATH, `/usr/local/bin:/usr/bin:/bin`, never by the PATH the
78
+ image set: an image that puts a directory of its own first does not choose which `git` runs. For a credentialed
79
+ `command` the Agent's files are the author's choice to run, and the claims are the next bullet's.
80
+ - **The Instance Effector serves the Hook door with two operations, and the Orchestrator never holds a Hook's secret.**
81
+ A Hook with a `mac` verifier
82
+ ([ADR-0078](0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md)) names its secret as an
83
+ Effect names one, and the secret is placed in the Instance Effector alone.
84
+ - **The `mac` check.** The Orchestrator sends, on the signed wire (below), the bytes to check, the signature, the
85
+ secret's name and the algorithm. The Hook's kind gives the bytes, and the door makes every check that needs no
86
+ secret itself, so the Effector reads no header and no timestamp. The Effector computes an HMAC over the bytes with
87
+ that secret, compares it with the signature in constant time, and answers only "match" or "no match". It returns no
88
+ secret and no digest of one.
89
+ - **One `http` call**: a Hook kind's follow-up, such as the reply to a Slack click, which the door posts to the
90
+ click's `response_url` ([ADR-0011](0011-workflow-defined-events.md)). It is the `http` kind on the same wire, with
91
+ no secret, to a host that the Hook's kind declares, which `jr2 up` writes into `effector.json` for each Hook of that
92
+ kind, and it is sent once. It has no slot, no ledger row and no run.
93
+
94
+ `jr2 up` converges the Instance Effector when a Hook has a `mac` verifier or its kind declares a follow-up host, as it
95
+ does when an Effect is placed there. The `token` and `ed25519` verifiers run in the Orchestrator: a digest and a
96
+ public key cannot sign anything, so they need no Effector, but a follow-up of their kind goes through it. The check is
97
+ `POST /mac`, beside the Effects' routes and never an Effect: it has no id, starts nothing and is answered at once, and
98
+ a secret the Effector does not hold is a defect (400), as an Effect's is. A Sandbox Effector does not serve the route.
99
+
66
100
  - **A credentialed `command`'s child is confined with Landlock.** The child runs as the Effector's own uid in the
67
101
  Effector's container, where the placement key, every placed secret and the push and signing keys are files that uid
68
102
  can read, and its program is often the Agent's code (`npm run deploy`, `make`, `./scripts/release.sh`, a tool's
@@ -74,28 +108,34 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
74
108
  The thread is locked from the probe to the `execve`, so the domain is the one `execve` keeps. Landlock is an
75
109
  allow-list the kernel enforces and every descendant inherits, and a confined process cannot ptrace a process outside
76
110
  its domain, so `/proc/<effector>/mem` and `/proc/<effector>/environ` are closed too. The ruleset handles every
77
- filesystem right of ABI 1, and `REFER` and `TRUNCATE` when the kernel offers ABI 2 and 3. It allows: read and execute
78
- on the image's system paths — every top-level directory the image holds that is not a denied name, whole, so the rules
79
- follow the image and break on no layout (the usual `/usr`, `/bin`, `/etc`, `/opt`, and a tool tree of the image's own
80
- such as `/google-cloud-sdk` or `/nix` alike; a link such as `/lib -> usr/lib` is followed when what it names holds
81
- none of the Effector's own paths); read and write on the devices (`/dev/null`, `/dev/zero`, `/dev/full`,
82
- `/dev/random`, `/dev/urandom`, `/dev/tty`) and the Effect's own scratch, one per Effect (the checkout, `$HOME` and
83
- `$TMPDIR`); read on each secret file the command names, by `secret("x")` and `.path` alike. The denied names — the
84
- kernel and process pseudo-filesystems (`/proc`, `/sys`, the rest of `/dev`), the runtime tree where jr2 mounts the
85
- Effector's own files (`/run`, the key and config under `/run/jr2/effector`), and the volatile and per-user trees that
86
- hold no tool an image ships (`/home`, `/root`, `/var`, `/srv`, `/mnt`, `/media`, `/lost+found`) — are denied, and so
87
- is every path named in the Effector's own deny list: the secrets the command does not name, the push credentials
88
- (`@git`), the Worktree (`/work`), the node caches (`/repos`), the rest of its scratch (another Effect's, and a push's
89
- copied keys). Every jr2 mount in the Effector's container that holds a secret or the Agent's files is under a denied
90
- name (`/run`: the key, the config, the secrets, `@git`) or in that deny list (`/work`, `/repos`, the scratch). The two
91
- others hold only public data and the child may read them: the runtime volume at `/opt/jr2`, which holds the programs
92
- jr2 ships, and the instance's caBundle ConfigMap at `/etc/jr2/effector-ca` (ADR-0020). So a jr2 mount that holds a
93
- secret goes under `/run/jr2`; one under `/etc`, `/opt` or a new top-level name is readable by the child. A top-level
94
- directory that holds one of the Effector's own paths is allowed one level down at a time around it, so a mount placed
95
- inside one stays denied. Network is not restricted. Any failure before `execve` ends the confiner with exit 126 and
96
- its reason on stderr: nothing runs unconfined. The design needs ABI 1; measured from a hardened non-root pod (every
111
+ filesystem right of ABI 1, `REFER` (ABI 2) and `TRUNCATE` (ABI 3). At ABI 6 it also sets the scopes of abstract Unix
112
+ sockets and of signals: the child cannot connect to an abstract socket that a process outside its domain made, or
113
+ signal such a process. It allows: read and execute on the image's system paths — every top-level directory the image
114
+ holds that is not a denied name, whole, so the rules follow the image and break on no layout (the usual `/usr`,
115
+ `/bin`, `/etc`, `/opt`, and a tool tree of the image's own such as `/google-cloud-sdk` or `/nix` alike; a link such as
116
+ `/lib -> usr/lib` gets no rule, and works through the rule on what it names, because the kernel checks the path that a
117
+ link resolves to; the confiner opens each rule's path with no link on it, and refuses to start when one is there);
118
+ read and write on the devices (`/dev/null`, `/dev/zero`, `/dev/full`, `/dev/random`, `/dev/urandom`, `/dev/tty`) and
119
+ the Effect's own scratch, one per Effect (the checkout, `$HOME` and `$TMPDIR`); read on each secret file the command
120
+ names, by `secret("x")` and `.path` alike. The denied names — the kernel and process pseudo-filesystems (`/proc`,
121
+ `/sys`, the rest of `/dev`), the runtime tree where jr2 mounts the Effector's own files (`/run`, the key and config
122
+ under `/run/jr2/effector`), and the volatile and per-user trees that hold no tool an image ships (`/home`, `/root`,
123
+ `/var`, `/srv`, `/mnt`, `/media`, `/lost+found`) — are denied, and so is every path named in the Effector's own deny
124
+ list: the secrets the command does not name, the push credentials (`@git`), the Worktree (`/work`), the node caches
125
+ (`/repos`), the rest of its scratch (another Effect's, and a push's copied keys). Every jr2 mount in the Effector's
126
+ container that holds a secret or the Agent's files is under a denied name (`/run`: the key, the config, the secrets,
127
+ `@git`) or in that deny list (`/work`, `/repos`, the scratch). The two others hold only public data and the child may
128
+ read them: the runtime volume at `/opt/jr2`, which holds the programs jr2 ships, and the instance's caBundle ConfigMap
129
+ at `/etc/jr2/effector-ca` (ADR-0020). So a jr2 mount that holds a secret goes under `/run/jr2`; one under `/etc`,
130
+ `/opt` or a new top-level name is readable by the child. A top-level directory that holds one of the Effector's own
131
+ paths is allowed one level down at a time around it, so a mount placed inside one stays denied. Network is not
132
+ restricted. Any failure before `execve` ends the confiner with exit 126 and its reason on stderr: nothing runs
133
+ unconfined. The design needs ABI 3, the minimum of jr2 (ADR-0077); measured from a hardened non-root pod (every
97
134
  capability dropped, `RuntimeDefault` seccomp): kind on WSL2 6.6 — ABI 3; the home cluster's arm64 nodes (6.8) — ABI 4;
98
- spark (6.11, arm64) — ABI 5.
135
+ spark (6.11, arm64) — ABI 5. Landlock is a requirement of jr2
136
+ ([ADR-0077](0077-jr2-requires-landlock-on-every-sandbox-node.md)). The confiner is shared: the Harness runs each
137
+ process it starts in `/work` through the same program, with its own rules
138
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)).
99
139
  - **One child cannot reach another.** Three rules, in the Effector:
100
140
  1. **The subreaper.** The Effector sets `PR_SET_CHILD_SUBREAPER` when it starts, so a descendant whose parent ends is
101
141
  reparented to the Effector, never to the container's init. Every child an Effect starts runs in its own session and
@@ -137,16 +177,16 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
137
177
  3. **A private TMPDIR.** Each credentialed child gets `TMPDIR` set to a directory of its Effect's own scratch, which
138
178
  goes when the outcome is read, and `/tmp` is not in the ruleset: a child neither reads nor writes it, so what one
139
179
  child writes no other child reads.
140
- - **Fail-closed at the pod, not at `jr2 up`.** A Sandbox Effector probes Landlock when it starts
141
- (`landlock_create_ruleset` with `LANDLOCK_CREATE_RULESET_VERSION`). When its Workspace holds a credentialed command
142
- (`effector.json`'s `commands`, which `jr2 up` writes) and the probe fails, it does not become ready: it ends, and its
143
- termination message — written to `/dev/termination-log`, the container's `FallbackToLogsOnError` the fallback — says
144
- `landlock: unavailable on node <node> (<why>)`, with the node from the downward API (`NODE_NAME`). The operator copies
145
- the Effector container's restarts and last end onto the Sandbox's status (`status.effector`), and the provision fails
146
- on an Effector that ended before the pod was Ready, in those words, before any Turn. A Workspace that holds no
147
- credentialed command starts on such a node and refuses a command start (400, with the same reason). No `jr2 up` probe:
148
- one probe Job reports one node, which says nothing in a mixed cluster, and in a uniform one the provision says the
149
- same one step later; a Node's `kernelVersion` does not say whether `landlock` is in its LSM list.
180
+ - **Fail-closed at `jr2 up` and at the pod.** Landlock is a requirement of jr2 (ADR-0077): `jr2 up` refuses to converge
181
+ while a Sandbox node lacks it, as the Repo cache agent on that node reports, and a Sandbox Effector probes it when it
182
+ starts (`landlock_create_ruleset` with `LANDLOCK_CREATE_RULESET_VERSION`), whatever its Workspace holds. The Instance
183
+ Effector confines nothing and does not probe. When the probe fails, the Sandbox Effector does not become ready: it
184
+ ends, and its termination message — written to `/dev/termination-log`, the container's `FallbackToLogsOnError` the
185
+ fallback — says `landlock: unavailable on node <node> (<why>)`, with the node from the downward API (`NODE_NAME`). The
186
+ operator copies the Sandbox Effector container's restarts and last end onto the Sandbox's status (`status.effector`),
187
+ and the provision fails on an Effector that ended before the pod was Ready, in those words, before any Turn. The pod's
188
+ probe is the backstop for a node that joins after the converge. A Node's `kernelVersion` does not say whether
189
+ `landlock` is in its LSM list, so only a probe on the node can tell.
150
190
  - **The wire.** `POST /effects/<id>` starts an Effect and answers at once (202, `state: running`). A second `POST` with
151
191
  the same id starts nothing and attaches (200). The answer to a `push` start names the commit it runs against
152
192
  (`pinned`), read before the answer, so the Orchestrator writes it to the ledger's intent row. `GET /effects/<id>`
@@ -194,8 +234,9 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
194
234
  `effector/internal/wire` and zod schemas in `@jr2/orchestrator`, the one definition each TypeScript type is inferred
195
235
  from. Go decodes with unknown fields disallowed. The golden fixtures in `effector/testdata/wire/` keep the two equal:
196
236
  one JSON file per request shape (`request.*`), per answer and outcome (`response.*`), per shape its decoder must
197
- refuse (`reject.*`), and the two signatures (`signature.*`). Go decodes each request and encodes each response; the
198
- TypeScript tests read the same files by a relative path, encode the requests and decode the responses.
237
+ refuse (`reject.*`), the two signatures (`signature.*`), and the `mac` check's request, answers and refusals
238
+ (`mac.*`). Go decodes each request and encodes each response; the TypeScript tests read the same files by a relative
239
+ path, encode the requests and decode the responses.
199
240
  - **Signed requests and signed answers, and no bearer.** The key is the placement's,
200
241
  `HMAC(signing key, "effector:" + placement)`, as ADR-0058 derives the Harness bearer: the Orchestrator stores nothing.
201
242
  The Effector reads it from a file in its own mount. A request carries `X-Jr2-Time` (Unix milliseconds), `X-Jr2-Nonce`,
@@ -273,9 +314,9 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
273
314
  `truncated` past it. A body that is not text is never returned: its size and type are.
274
315
  - `unreachable`: nothing was sent — the name did not resolve, the connection or TLS failed.
275
316
  - `refused`: the Effector refused it and sent nothing — a path segment that is `.`, `..` or empty (an Agent's param
276
- can cause it, so it is output, not a fault), or a host and secrets that no Effect placed there names. The
277
- Orchestrator settles an `http` start longer than 4 MiB `refused` the same way, before it sends anything; `refused`
278
- is an `http` outcome only.
317
+ can cause it, so it is output, not a fault), or a host and secrets that no Effect or Hook placed there names. The
318
+ Orchestrator settles an `http` start longer than 4 MiB `refused` the same way, before it sends anything, and a file
319
+ body with no cut's row (ADR-0075); a `push` and a `command` have no `refused`.
279
320
  - `unknown`: the connection was made and no answer came (the timeout, a reset): the host may have acted.
280
321
 
281
322
  Each outcome other than `response` carries a `reason` in words. A pick that runs an `http` Effect is answered, by
@@ -284,28 +325,45 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
284
325
  ``Its answer: the output of `<slot>`: <the output's JSON>.``, already scrubbed. An `answer` action shapes it instead
285
326
  ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)).
286
327
 
328
+ - **The `mcp` kind.** The Effector holds a small MCP client of its own: Go, the standard library only, Streamable HTTP,
329
+ protocol `2025-11-25`
330
+ ([ADR-0079](0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md)). Each
331
+ Effect is one session: the handshake, one request (`tools/call`, `resources/read` or `prompts/get`), and a DELETE of
332
+ the session, so a replica keeps no session between Effects and the replica rules of `http` hold unchanged. The start
333
+ names the server and nothing else of it: the server's url, the name of its secret and its `timeout` are in
334
+ `effector.json`, which `jr2 up` writes from the `mcp` block, and the secret goes as `Authorization: Bearer <value>`.
335
+ The client declares no capability, so a server that asks for input gets none: the Effect settles `input-required`. An
336
+ answer is read as JSON, or as an event stream until the message that answers the request. A result's `text` and its
337
+ `structured` are each bounded to the start's `maxBody`, 64 KiB by default as for an `http` body; past it the Effector
338
+ cuts the text (a `structured` past the bound is dropped, not cut into JSON that does not parse) and sets `truncated`.
339
+ Every string of the output is scrubbed, as an `http` body is. A redirect is not followed. The outcomes and their
340
+ meaning are ADR-0067's.
287
341
  - **The host is fixed, in the Effector.** The Effector's config (`effector.json`, written by `jr2 up` from the Effects'
288
342
  plain data) lists each scheme, host and port an Effect placed there names, with the secrets that Effect names — the
289
- host its definition fixes, or for a url the package left Open, the one the composer's `customize()` bound
343
+ host its definition fixes, the host of an `mcp` Effect's server url in `jr2.config.ts`, or for a url the package left
344
+ Open, the one the composer's `customize()` bound
290
345
  ([ADR-0054](0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)); `jr2 up` refuses a Machine
291
- whose Open url nothing bound, so no fence entry is ever a guess. A request to another target, or with another secret,
292
- is `refused`. A redirect is the response and is never followed: it would send the request, and its `Authorization`, to
293
- a host the definition did not fix. No proxy is read from the environment. The trust is the system roots and the
294
- instance's `caBundle` ([ADR-0020](0020-private-ca-trust-is-a-named-concept.md)).
346
+ whose Open url nothing bound, so no fence entry is ever a guess. Each Hook adds the hosts that its kind declares for a
347
+ follow-up (the `slack` kind: `https://hooks.slack.com`), with no secret (above). A request to another target, or with
348
+ another secret, is `refused`. A redirect is the response and is never followed: it would send the request, and its
349
+ `Authorization`, to a host the definition did not fix. No proxy is read from the environment. The trust is the system
350
+ roots and the instance's `caBundle` ([ADR-0020](0020-private-ca-trust-is-a-named-concept.md)).
295
351
  - **One list of secrets; placement is read, never declared, and naming is placement on both sides.** The top-level
296
352
  `secrets` list replaces `harness.heldSecrets`; its entry is ADR-0059's
297
353
  (`{ name, value | valueFrom.secretKeyRef, hosts?, headers?, paths? }`). A pod gets the secrets something in it names:
298
- a Workspace's pod the Held secrets its `workspace()` lists under `secrets: [...]` and the model keys its Agents'
299
- providers read; the Instance Harness the model keys of its `"none"` Agents only; an Effector what its Effects name
300
- with `secret("name")` (an `http` Effect: the Instance Effector; an `http` whose body is a Worktree file: the Sandbox
301
- Effector of the Workspace whose body invokes it; a credentialed command: that Sandbox Effector), and the name a
302
- composer binds to an Effect's `secret(open)` with `customize()` (ADR-0054) is placed the same way, in that Effect's
303
- Effector alone, once bound — `jr2 up` refuses an Open secret nothing bound. `hosts`, `headers` and `paths` are a Held
304
- secret's binding, never its placement (ADR-0059). One derivation (`secretPlacementsOf`) places both kinds, and the
305
- held manifest, the Custodian config and the held values' Secret of each Harness pod come from it. Both is both, and
306
- `jr2 up` reports it per pod. An entry nothing names goes nowhere, and `jr2 up` warns:
307
- `secrets[i] (<name>) is named by nothing; goes nowhere`. There is no `placement:` key: naming is the placement.
308
- `jr2 up` prints one line per secret with its placements, and one per pod with what it holds
354
+ a Workspace's pod the Held secrets its `workspace()` lists under `secrets: [...]`, the model keys its Agents'
355
+ providers read and the secrets of the MCP servers its Agents name; the Instance Harness the model keys and the MCP
356
+ server secrets of its `"none"` Agents only; an Effector what its Effects name with `secret("name")` (an `http` Effect:
357
+ the Instance Effector; an `mcp` Effect, which names its server's secret through the server: the Instance Effector; an
358
+ `http` whose body is a Worktree file: the Sandbox Effector of the Workspace whose body invokes it; a credentialed
359
+ command: that Sandbox Effector), and the name a composer binds to an Effect's `secret(open)` with `customize()`
360
+ (ADR-0054) is placed the same way, in that Effect's Effector alone, once bound — `jr2 up` refuses an Open secret
361
+ nothing bound. A Hook with a `mac` verifier places its secret in the Instance Effector the same way. `hosts`,
362
+ `headers` and `paths` are a Held secret's binding, never its placement (ADR-0059). One derivation
363
+ (`secretPlacementsOf`) places both kinds, and the held manifest, the Custodian config and the held values' Secret of
364
+ each Harness pod come from it. Both is both, and `jr2 up` reports it per pod. An entry nothing names goes nowhere, and
365
+ `jr2 up` warns: `secrets[i] (<name>) is named by nothing; goes nowhere`. There is no `placement:` key: naming is the
366
+ placement. `jr2 up` prints one line per secret with its placements, and one per pod with what it holds
309
367
  (`Workspace of workflow "coding": Custodian holds ANTHROPIC_API_KEY (provider), JIRA_TOKEN`,
310
368
  `the Instance Effector: holds jira`,
311
369
  `Workspace of workflow "coding": Sandbox Effector (image the Sandbox Image) holds aws; pushes Repo Slot "app" with git.credentials "*"`
@@ -320,9 +378,10 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
320
378
  short value, for a Secret edited after the converge. A secret reaches only the pods where something names it: a
321
379
  literal is written into the placement's own Secret, and a `valueFrom` is projected from the user's Secret by
322
380
  reference, never copied. `git.credentials` stays its own list: a fence keyed by url prefix, not a named value.
323
- - **The Instance Effector's objects.** `jr2 up` converges them when a registered Machine places an Effect there, and
324
- deletes them otherwise; it reports either (`instance effector: converging (<workflow>/<slot>; secrets …; grace …)` or
325
- `instance effector: none`). All four are named `jr2-instance-effector`:
381
+ - **The Instance Effector's objects.** `jr2 up` converges them when a registered Machine places an Effect there or a
382
+ Hook has a `mac` verifier or a kind that declares a follow-up host, and deletes them otherwise; it reports either
383
+ (`instance effector: converging (<workflow>/<slot> or hook <name>; secrets …; grace …)` or `instance effector: none`).
384
+ All four are named `jr2-instance-effector`:
326
385
  - A **Deployment**, one replica, rolling with surge and no gap, so a rollout runs the old pod beside the new one: the
327
386
  `jr2-harness` image, command `/opt/jr2/bin/effector`, non-root, no privilege escalation, every capability dropped, a
328
387
  read-only root filesystem, an emptyDir scratch as `$HOME`, no ServiceAccount token, the control tier's PriorityClass
@@ -337,12 +396,13 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
337
396
  - A **NetworkPolicy** of `jr2-sandbox-ingress`'s form ([ADR-0058](0058-a-harness-answers-the-orchestrator-alone.md)):
338
397
  ingress from this Instance's Orchestrator only. Egress stays open: the pod calls the hosts its Effects name, and
339
398
  holds no Custodian.
340
- - The **Secret**: each literal Effect secret placed there, `effector.json` (the targets), and the placement's key,
341
- `effectorKey(signing key, "instance")`, which `jr2 up` derives from the kept signing key. "Nothing stored" means
342
- nothing stored by the Orchestrator: the Kubernetes Secret is the store, as for every credential. The secrets are
343
- files under `/run/jr2/secrets`, mode 0400; the kubelet makes them readable by the pod's `fsGroup`, and the pod has
344
- one container. `harness.env` and `harness.envFrom` may not name this Secret, nor a Workspace's `jr2-effector-<pod>`,
345
- a Sandbox's `<sandbox>-effector`, or a `git.credentials` entry's `jr2-git-*` (ADR-0059's R13).
399
+ - The **Secret**: each literal Effect secret placed there, `effector.json` (the targets, and the MCP servers that its
400
+ `mcp` Effects name), and the placement's key, `effectorKey(signing key, "instance")`, which `jr2 up` derives from
401
+ the kept signing key. "Nothing stored" means nothing stored by the Orchestrator: the Kubernetes Secret is the store,
402
+ as for every credential. The secrets are files under `/run/jr2/secrets`, mode 0400; the kubelet makes them readable
403
+ by the pod's `fsGroup`, and the pod has one container. `harness.env` and `harness.envFrom` may not name this Secret,
404
+ nor a Workspace's `jr2-effector-<pod>`, a Sandbox's `<sandbox>-effector`, or a `git.credentials` entry's `jr2-git-*`
405
+ (ADR-0059's R13).
346
406
  - **A Workspace's Sandbox Effector's objects** (ADR-0059). `jr2 up` reads, for each `workspace()`, the Effects placed
347
407
  there below it, down to the next `workspace()` — every `push`, `http` whose body is a Worktree file and credentialed
348
408
  `command`, from their plain data, statically — and writes two objects for a Workspace with one, none for one without.
@@ -393,6 +453,10 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
393
453
  - **A StatefulSet for the Instance Effector**, as the Instance Harness is. Rejected: a one-replica StatefulSet deletes
394
454
  the old pod before it creates the new one, so a rollout would cut an Effect in flight; the Effector keeps nothing a
395
455
  stable name or volume would hold.
456
+ - **Check a Hook's `mac` in the Orchestrator.** Rejected: the Orchestrator runs every Machine's code, and an HMAC secret
457
+ in its process can sign a forged delivery. The check costs one call on the signed wire for each delivery.
458
+ - **A verifier container of its own** for the Hook door. Rejected: it would be the same binary, with the same secret
459
+ mount and the same signed wire, so a second pod adds a Deployment, a key and a NetworkPolicy and closes nothing.
396
460
  - **A `placement:` key on a secret.** Rejected: naming a secret in an Effect is what places it, and a key would be a
397
461
  second statement that can disagree with the first.
398
462
 
@@ -402,36 +466,37 @@ only asks. Its Role reads no Secret either: it may create and patch one, never g
402
466
  echo, a side channel. The claim is that an Effector does not return a secret it holds, in the forms it sent or in
403
467
  common encodings.
404
468
  - **What a credentialed command's child can still do.** It reads the secret the command names — the author put it there,
405
- and a file the Agent wrote that the argv runs reads it too — and egress is open, so the claim is the three of
406
- ADR-0067: the Agent never reads or spends the secret from its own container, the tool sees a pinned commit and not the
407
- live Worktree, and the output is scrubbed. It shares the Effector's uid, so it can signal the Effector (a fault of its
408
- own run, not a leak) and connect to its port, where nothing without the key is accepted. It cannot outlive its Effect
409
- (rule 1), share a temporary tree with another child (rule 3), or be alive beside a `gpg-agent` (rule 2).
469
+ and a file the Agent wrote that the argv runs reads it too — and egress is open but for the cloud metadata addresses
470
+ ([ADR-0076](0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md)), so the claim is the three
471
+ of ADR-0067: the Agent never reads or spends the secret from its own container, the tool sees a pinned commit and not
472
+ the live Worktree, and the output is scrubbed. It shares the Effector's uid, so it can signal the Effector (a fault of
473
+ its own run, not a leak) and connect to its port, where nothing without the key is accepted. It cannot outlive its
474
+ Effect (rule 1), share a temporary tree with another child (rule 3), or be alive beside a `gpg-agent` (rule 2).
410
475
  - **A long GPG push delays a concurrent credentialed command** by its duration, against that command's timeout, and the
411
476
  reverse. Rejected: refusing a credentialed command beside a GPG-signing push at `jr2 up` — the hole is open only while
412
477
  a GPG push signs, so the rule is that the two never overlap; and a second container per credentialed placement — its
413
478
  child would still share a uid with that container's Effector and could spend that container's key, so it would need
414
479
  Landlock anyway, plus a key, a Secret, a port and a NetworkPolicy per placement.
415
- - **A node with no Landlock runs no credentialed command.** Its Sandboxes that hold one fail at provision, naming the
416
- node; the fix is a kernel with `landlock` in its LSM list. The home cluster's nodes are arm64, so the Effector binary
417
- and the Kit images need `linux/arm64` builds there.
480
+ - **A node with no Landlock runs no Sandbox** (ADR-0077). A Sandbox placed there fails at provision, naming the node;
481
+ the fix is a kernel with `landlock` in its LSM list. The home cluster's nodes are arm64, so the Effector binary and
482
+ the Kit images need `linux/arm64` builds there.
418
483
  - An `unknown` outcome is the Machine's to route. jr2 sends an `http` call again only when the author marked it
419
484
  `idempotent` ([ADR-0067](0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)).
420
485
  - The Effector's Go tests run Landlock for real on the host's kernel: a child that reads the key, an unnamed secret or
421
486
  `@git`, or writes the Worktree, ends with a permission error and an answer with no secret in it; one that reads its
422
487
  named secret from env and from its file succeeds; a child of the child is confined too; a failing probe stops the
423
- start of a Workspace that holds a credentialed command, with the node in the reason. And the three rules of "One child
424
- cannot reach another" run with real processes: a `setsid` escapee, and an escapee of that escapee, is dead once its
425
- command has settled; a live command's daemon, under its keeper, lives through the settle of another Effect beside it;
426
- the keeper ends what its child left and answers the child's own end; a command started during a GPG push runs only
427
- after the agent is gone, and the reverse, with no sample of that Effector's processes seeing both alive — the
428
- command's child matched to its scratch, and the agent known from a witness in place of `gpg-agent` that tells the test
429
- before the launcher runs and, after it exits, which processes the agent left, so an agent that lives less than one
430
- sample is never missed; a waiter past its timeout settles timed out and never runs late; a child's `TMPDIR` write is
431
- invisible to the next child, and `/tmp` refuses both; a signed DELETE ends a credentialed command's group, `exited`
432
- with SIGTERM, a program that catches SIGTERM gives its code beside the signal, a child that ignores SIGTERM is killed
433
- past the grace (`exited` with SIGKILL), an unsigned DELETE stops nothing, a DELETE on a push or an `http` path answers
434
- 405, and a command start that arrives after a DELETE of its id runs nothing, while a push or an `http` start does.
488
+ start of the Effector, with the node in the reason. And the three rules of "One child cannot reach another" run with
489
+ real processes: a `setsid` escapee, and an escapee of that escapee, is dead once its command has settled; a live
490
+ command's daemon, under its keeper, lives through the settle of another Effect beside it; the keeper ends what its
491
+ child left and answers the child's own end; a command started during a GPG push runs only after the agent is gone, and
492
+ the reverse, with no sample of that Effector's processes seeing both alive — the command's child matched to its
493
+ scratch, and the agent known from a witness in place of `gpg-agent` that tells the test before the launcher runs and,
494
+ after it exits, which processes the agent left, so an agent that lives less than one sample is never missed; a waiter
495
+ past its timeout settles timed out and never runs late; a child's `TMPDIR` write is invisible to the next child, and
496
+ `/tmp` refuses both; a signed DELETE ends a credentialed command's group, `exited` with SIGTERM, a program that
497
+ catches SIGTERM gives its code beside the signal, a child that ignores SIGTERM is killed past the grace (`exited` with
498
+ SIGKILL), an unsigned DELETE stops nothing, a DELETE on a push or an `http` path answers 405, and a command start that
499
+ arrives after a DELETE of its id runs nothing, while a push or an `http` start does.
435
500
  - The `@kind` tier carries the proofs a unit test cannot: an echo server in the seed namespace answers each header and
436
501
  the url in every encoding, and the run's snapshot, its feed and the answer the model reads must hold no form of the
437
502
  value — the step decodes every base64 and percent-encoded run it reads, so a leak of what the Effector built
@@ -71,15 +71,16 @@ resolve a Workflow's name only to the new Machine. The grill of 2026-10-18 split
71
71
  an image cost: cluster write access. The token lives in the Instance's Secret beside the Instance token, `jr2 up`
72
72
  reads it through the kube API alone, and no env var and no `--url` path can supply it, so an ingress-exposed Instance
73
73
  exposes no publish door. The Instance token keeps its meaning
74
- ([ADR-0032](0032-the-console-unlocks-with-the-instance-token.md)): start runs, answer Gates, read. `GET /assembly`,
74
+ ([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)): start runs, answer Gates, read. `GET /assembly`,
75
75
  public like `GET /workflows`: the current hash and Kit version, the Assemblies held with the runs that hold them, and,
76
76
  when deployed, the digest of each ConfigMap the process reads through a mount (`jr2-images`, `jr2-held`), so a
77
- publisher can see what the process reads. `jr2 status` prints it; `/healthz` prints the current hash where it printed
78
- the content hash. A repeat publish of the current hash answers `200 { unchanged: true }` before the body is read;
79
- `--force` sends the PUT anyway and gets the same answer, because the hash names bytes the Orchestrator already holds.
80
- Rejected: an OCI artifact the Orchestrator pulls, which needs the registry that dropping docker removed; a ConfigMap,
81
- at 1 MiB; a git ref the Orchestrator clones, when [ADR-0048](0048-the-orchestrator-boots-without-its-repos.md) just
82
- took cloning out of the Orchestrator.
77
+ publisher can see what the process reads, and each standing Workflow's state and count of held items
78
+ ([ADR-0017](0017-pool-over-a-source-port.md)). `jr2 status` prints it; `/healthz` prints the current hash where it
79
+ printed the content hash. A repeat publish of the current hash answers `200 { unchanged: true }` before the body is
80
+ read; `--force` sends the PUT anyway and gets the same answer, because the hash names bytes the Orchestrator already
81
+ holds. Rejected: an OCI artifact the Orchestrator pulls, which needs the registry that dropping docker removed; a
82
+ ConfigMap, at 1 MiB; a git ref the Orchestrator clones, when
83
+ [ADR-0048](0048-the-orchestrator-boots-without-its-repos.md) just took cloning out of the Orchestrator.
83
84
  - **The walk runs twice, with different duties.** On the host, `jr2 up` keeps the typecheck
84
85
  ([ADR-0050](0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md)) and every duty that needs the
85
86
  host or the cluster: it loads the Instance folder with the same `loadWorkflows` and `partsOf` the Orchestrator runs,
@@ -121,8 +122,22 @@ resolve a Workflow's name only to the new Machine. The grill of 2026-10-18 split
121
122
  Effector manifests, the held-secret Secrets, and the roots of the image sweep. A held run that still needs one the
122
123
  current Assembly dropped fails at that provision, naming the held Assembly; so a publish strands no run whose Machine
123
124
  it changed, and a publish that drops a Sandbox Image context a parked run has not yet provisioned is the one edit that
124
- still can. The Console draws a run on the current Machine; a held run's `assembly` is in its status, and a Console
125
- that marks or draws held runs is a later decision. So a publish strands nothing: the "editing a workflow strands its
125
+ still can. A held run's `assembly` is in its status and in its `GET /runs` summary. The Console marks a run whose
126
+ Assembly is not the current one, "older Assembly · <hash>", and draws it on the run's own Machine, not on the current
127
+ Machine, from `GET /workflows/:name/runs/:runId/machine`; so the lit states are always states of the run's own Machine
128
+ ([ADR-0032](0032-the-console-unlocks-with-a-callers-credential.md)). For a live run the route reads the Machine from
129
+ the Assembly that the run holds. An ended run holds no Assembly, so the run's terminal write keeps its Machine, as
130
+ structure with the Charts that it bound last compiled in, in a table of its own in `state.db`, once for each distinct
131
+ doc: the key is a digest of the kept doc. The Machine digest of
132
+ [ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md) is not the key, because it hashes only what a
133
+ restore needs, and two Machines that differ in a guard, a description or an Effect's command would share one copy. The
134
+ Console draws every ended run from the route, because the status of an ended run binds no Chart. The run's row holds
135
+ only the key, so a thousand runs of one Workflow on one Assembly keep one copy, and the terminal write adds a row only
136
+ for a doc that the table does not have. The route serves that copy for as long as the row is kept
137
+ ([ADR-0065](0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md)), and the run sweep deletes a doc
138
+ that no kept row names. So the collection rule above does not change for history: an ended run needs no Assembly to be
139
+ drawn. A `drifted` run has no Machine to keep, because its Assembly could not load: the Console shows no graph for it,
140
+ and shows its drift reason in place of the graph. So a publish strands nothing: the "editing a workflow strands its
126
141
  parked runs" consequence of [ADR-0030](0030-a-snapshot-names-the-machine-it-was-written-under.md) is retired, and
127
142
  drift is what it always meant, "I can no longer read what I saved": an Assembly that cannot load after a Kit version
128
143
  bump, or a volume that lost it. Such a run is `drifted` with a reason naming the Assembly hash and the import error;
@@ -154,6 +169,14 @@ resolve a Workflow's name only to the new Machine. The grill of 2026-10-18 split
154
169
  The agent path is data: [ADR-0070](0070-a-machine-lends-its-parts-to-a-chart.md).
155
170
  - **Semver-compatible Assemblies.** Rejected above; one line to loosen later.
156
171
  - **Keep only the current Assembly and report the runs a publish will strand.** Rejected above.
172
+ - **Hold each Assembly that a kept run names, to draw an ended run.** Rejected: an ended run needs only its Machine's
173
+ structure, not the code of the Assembly, and a hold for each kept row would keep every Assembly for `runs.retain` and
174
+ grow the volume with each publish. The run's structure is kept instead.
175
+ - **Keep the Machine in each ended run's row.** Rejected: every run of a Workflow on one Assembly would store the same
176
+ structure again, the space guard never frees rows, and the copy would add work to the synchronous terminal write. One
177
+ copy for each distinct structure, named by the row, costs neither.
178
+ - **Draw no graph for an ended run whose Assembly is gone.** Rejected: the graph of a failed run is what a person reads
179
+ first, and the copy in the row is small.
157
180
 
158
181
  ## Consequences
159
182