@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.
- package/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +142 -29
- package/docs/concepts/architecture.md +60 -34
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +13 -8
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +7 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +133 -50
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +14 -4
- package/docs/concepts/pools-and-sources.md +193 -17
- package/docs/concepts/secrets-and-trust.md +186 -62
- package/docs/concepts/the-menu.md +14 -0
- package/docs/concepts/workspaces.md +135 -56
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +46 -35
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/get-started/have-your-assistant-write-a-machine.md +111 -40
- package/docs/get-started/install.md +4 -0
- package/docs/guides/add-tools-to-the-sandbox.md +72 -15
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +29 -12
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/cut-a-worktree.md +560 -0
- package/docs/guides/fix-a-failed-converge.md +19 -7
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +14 -7
- package/docs/guides/look-inside-a-sandbox.md +115 -42
- package/docs/guides/move-from-kind-to-a-shared-cluster.md +16 -0
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/push-and-sign-a-branch.md +67 -21
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-a-command-or-call-a-url.md +84 -41
- package/docs/guides/run-one-worker-per-item.md +34 -27
- package/docs/guides/set-an-agents-access-and-dials.md +69 -22
- package/docs/guides/size-a-workspace.md +19 -6
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +7 -1
- package/docs/guides/watch-a-run.md +146 -117
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +18 -8
- package/docs/llms.txt +110 -23
- package/docs/reference/authoring-surface/agent.md +35 -12
- package/docs/reference/authoring-surface/answer.md +20 -4
- package/docs/reference/authoring-surface/arg.md +28 -6
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +15 -0
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +37 -22
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +18 -5
- package/docs/reference/authoring-surface/file.md +37 -17
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +109 -9
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +14 -0
- package/docs/reference/authoring-surface/jr2setup.md +14 -4
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +41 -8
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +23 -5
- package/docs/reference/authoring-surface/pool.md +79 -11
- package/docs/reference/authoring-surface/push.md +31 -13
- package/docs/reference/authoring-surface/readyof.md +21 -0
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +26 -5
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +59 -4
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +16 -3
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-agentslots.md +4 -1
- package/docs/reference/authoring-surface/type-anyframepath.md +14 -0
- package/docs/reference/authoring-surface/type-anyworktreepath.md +15 -0
- package/docs/reference/authoring-surface/type-bindactors.md +14 -0
- package/docs/reference/authoring-surface/type-binding.md +6 -7
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandinput.md +4 -3
- package/docs/reference/authoring-surface/type-commandoptions.md +0 -22
- package/docs/reference/authoring-surface/type-commandoutput.md +23 -5
- package/docs/reference/authoring-surface/type-customize.md +20 -3
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +9 -2
- package/docs/reference/authoring-surface/type-doorlessinput.md +19 -0
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-filebody.md +4 -8
- package/docs/reference/authoring-surface/type-framepath.md +14 -0
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +3 -5
- package/docs/reference/authoring-surface/type-httpdefinition.md +6 -3
- package/docs/reference/authoring-surface/type-httpfileinput.md +20 -0
- package/docs/reference/authoring-surface/type-httpoutput.md +8 -2
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +21 -0
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-namestart.md +77 -0
- package/docs/reference/authoring-surface/type-noslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-notworktreekeys.md +12 -0
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-opencommandinput.md +7 -2
- package/docs/reference/authoring-surface/type-permissivenoslotworkspaceoptions.md +1 -6
- package/docs/reference/authoring-surface/type-permissiveworkspaceoptions.md +5 -10
- package/docs/reference/authoring-surface/type-poolnextinput.md +22 -0
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushinput.md +22 -3
- package/docs/reference/authoring-surface/type-pushoutput.md +33 -8
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +6 -6
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sandboxoptions.md +11 -4
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-slothandles.md +5 -13
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +26 -17
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +30 -9
- package/docs/reference/authoring-surface/type-workspacehandles.md +5 -6
- package/docs/reference/authoring-surface/type-workspaceoptions.md +1 -13
- package/docs/reference/authoring-surface/type-worktreefound.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeinput.md +56 -0
- package/docs/reference/authoring-surface/type-worktreekey.md +14 -0
- package/docs/reference/authoring-surface/type-worktreeoptions.md +29 -0
- package/docs/reference/authoring-surface/type-worktreeoutput.md +45 -0
- package/docs/reference/authoring-surface/type-worktreepath.md +16 -0
- package/docs/reference/authoring-surface/type-worktreeready.md +37 -0
- package/docs/reference/authoring-surface/type-worktreerefused.md +21 -0
- package/docs/reference/authoring-surface/workspace.md +66 -38
- package/docs/reference/authoring-surface/worktree.md +71 -0
- package/docs/reference/authoring-surface.md +88 -147
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +248 -6
- package/docs/reference/decisions/0001-operator-owned-generic-sandbox.md +10 -9
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +81 -14
- package/docs/reference/decisions/0004-worktree-provisioning-model.md +27 -21
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +39 -25
- package/docs/reference/decisions/0006-agent-control-surface.md +6 -3
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +39 -32
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +54 -30
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +26 -16
- package/docs/reference/decisions/0016-agent-turn-mechanics-are-internal.md +9 -7
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -10
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +53 -27
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +16 -2
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +124 -43
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +36 -38
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +16 -7
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +36 -19
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -15
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +22 -22
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +13 -12
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +23 -23
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0051-a-repo-is-a-slot-on-the-workspace-and-a-cache-on-the-node.md +57 -46
- package/docs/reference/decisions/0052-a-sandbox-lands-where-an-ordinary-pod-lands-and-the-cache-follows-it.md +5 -0
- package/docs/reference/decisions/0053-a-fetch-inside-the-pod-asks-the-node-cache-and-the-cache-asks-the-remote.md +20 -8
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +31 -24
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +18 -10
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +137 -86
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +165 -91
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +7 -7
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- 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
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +221 -153
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +161 -96
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +48 -33
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0073-a-machine-fails-a-run-with-fail-and-an-uncaught-failure-ends-it-failed.md +1 -1
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +296 -0
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +347 -0
- package/docs/reference/decisions/0077-jr2-requires-landlock-on-every-sandbox-node.md +79 -0
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- 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
- package/docs/reference/decisions.md +9 -4
- package/docs/reference/glossary.md +138 -45
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +988 -123
- package/docs/reference/machines.md +0 -1
- package/docs/why.md +7 -5
- package/package.json +10 -3
- package/src/actor.ts +194 -42
- package/src/agent.ts +56 -6
- package/src/ambient.ts +3 -5
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +238 -26
- package/src/charts.ts +9 -2
- package/src/config.ts +566 -7
- package/src/conversations.ts +453 -0
- package/src/custodian.lua +17 -5
- package/src/custodian.ts +257 -77
- package/src/customize.ts +154 -7
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +52 -4
- package/src/effect.ts +401 -132
- package/src/effector-wire.ts +278 -30
- package/src/fail.ts +17 -4
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +84 -35
- package/src/harness-env.ts +33 -0
- package/src/held-secrets.ts +197 -39
- package/src/holds.ts +5 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +164 -70
- package/src/http.ts +933 -99
- package/src/index.ts +30 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +23 -3
- package/src/kube-client.ts +6 -3
- package/src/machine-doc.ts +29 -5
- package/src/mcp-effect.ts +424 -0
- package/src/model-keys.ts +24 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +82 -7
- package/src/notices.ts +576 -59
- package/src/open-body.ts +110 -30
- package/src/open-effect.ts +17 -4
- package/src/open.ts +40 -7
- package/src/parts.ts +125 -35
- package/src/party.ts +35 -1
- package/src/pick.ts +43 -9
- package/src/pool.ts +1172 -172
- package/src/registration.ts +249 -17
- package/src/repo-identity.ts +7 -3
- package/src/repos.ts +20 -2
- package/src/run-host.ts +1614 -190
- package/src/run-list.ts +191 -0
- package/src/sandbox-effect.ts +217 -66
- package/src/sandbox-kube.ts +116 -21
- package/src/secret-placement.ts +248 -35
- package/src/server.ts +53 -4
- package/src/setup.ts +158 -38
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +362 -46
- package/src/vocabulary.ts +23 -4
- package/src/wire.ts +27 -10
- package/src/workspace.ts +314 -259
- package/src/worktree.ts +249 -0
- package/docs/reference/authoring-surface/type-noslotspec.md +0 -26
- package/docs/reference/authoring-surface/type-workspacespec.md +0 -37
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Answer a Gate from a forge
|
|
3
|
+
description: Make a Gate that a review on GitHub or GitLab answers, and that looks for the review it waits for.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Answer a Gate from a forge
|
|
7
|
+
|
|
8
|
+
Make a [Gate](../reference/glossary.md#gate) that a review on a forge, such as GitHub, answers through a
|
|
9
|
+
[Hook](../reference/glossary.md#hook), and that looks for the review when no delivery comes.
|
|
10
|
+
|
|
11
|
+
A Gate slot is a Gate that hears a Hook. You declare it with `gate({ … })` in `actors`, and a state invokes it by its
|
|
12
|
+
key. It is the same Gate as `src: "gate"`: it has the same id, the same events, the same card in the
|
|
13
|
+
[Console](../reference/glossary.md#console), and `jr2 send` can answer it too. It adds four parts:
|
|
14
|
+
|
|
15
|
+
- `meta`: a zod object of the data that the state gives the Gate. The Gate checks the data when it opens.
|
|
16
|
+
- `delivers`: the events that the Gate can give from a delivery or from its look.
|
|
17
|
+
- `hooks`: a handler for each [Hook kind](../reference/glossary.md#hook-kind) and event key. A handler reads the payload
|
|
18
|
+
of a delivery, and gives one event of `delivers`, or `undefined` when the delivery is not for this Gate.
|
|
19
|
+
- `look`: a read of the fact that the Gate waits for, with an `http` Effect that is marked `idempotent: true`.
|
|
20
|
+
|
|
21
|
+
The example is the [Workflow](../reference/glossary.md#workflow) `review`. A run waits for a review of one commit of one
|
|
22
|
+
pull request. The `github` kind is `@jr2/orchestrator/hooks/github`, so your
|
|
23
|
+
[Instance](../reference/glossary.md#instance) installs nothing more.
|
|
24
|
+
|
|
25
|
+
Save the file as `workflows/review.ts`:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// workflows/review.ts: a run that waits for a review of one pull request on GitHub. A review on
|
|
29
|
+
// GitHub answers the Gate, through a Hook of the `github` kind. The look reads the reviews of the
|
|
30
|
+
// pull request, so a review that came before the Gate opened, or while the Orchestrator was down,
|
|
31
|
+
// also answers it.
|
|
32
|
+
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
import type {} from "@jr2/orchestrator/hooks/github";
|
|
35
|
+
import { bearer, defineEvent, gate, http, jr2Setup, secret, type HttpOutput } from "@jr2/orchestrator";
|
|
36
|
+
|
|
37
|
+
const approve = defineEvent({
|
|
38
|
+
name: "approve",
|
|
39
|
+
description: "A reviewer approved the commit.",
|
|
40
|
+
input: z.object({ by: z.string() }),
|
|
41
|
+
});
|
|
42
|
+
const changes = defineEvent({
|
|
43
|
+
name: "changes",
|
|
44
|
+
description: "A reviewer asked for changes.",
|
|
45
|
+
input: z.object({ by: z.string(), notes: z.string() }),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
type Meta = { owner: string; repo: string; pr: number; url: string; sha: string };
|
|
49
|
+
|
|
50
|
+
// The reviews of the pull request, from the GitHub API. The look reads them again, so the call is
|
|
51
|
+
// marked idempotent.
|
|
52
|
+
const reviews = http({
|
|
53
|
+
method: "GET",
|
|
54
|
+
url: "https://api.github.com",
|
|
55
|
+
auth: bearer(secret("github-token")),
|
|
56
|
+
headers: { accept: "application/vnd.github+json" },
|
|
57
|
+
idempotent: true,
|
|
58
|
+
input: z.object({ owner: z.string(), repo: z.string(), pr: z.number() }),
|
|
59
|
+
request: ({ owner, repo, pr }) => ({ path: ["repos", owner, repo, "pulls", String(pr), "reviews"] }),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** The last verdict on the commit, from the API's answer. */
|
|
63
|
+
function verdictOf(output: HttpOutput, meta: Meta) {
|
|
64
|
+
if (output.outcome !== "response" || output.status !== 200 || output.body === undefined) return undefined;
|
|
65
|
+
const list = JSON.parse(output.body) as Array<{
|
|
66
|
+
state: string;
|
|
67
|
+
commit_id: string;
|
|
68
|
+
body: string | null;
|
|
69
|
+
user: { login: string };
|
|
70
|
+
}>;
|
|
71
|
+
const last = list.filter((r) => r.commit_id === meta.sha && r.state !== "COMMENTED").at(-1);
|
|
72
|
+
if (last?.state === "APPROVED") return { type: "approve" as const, by: last.user.login };
|
|
73
|
+
if (last?.state === "CHANGES_REQUESTED")
|
|
74
|
+
return { type: "changes" as const, by: last.user.login, notes: last.body ?? "" };
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const review = gate({
|
|
79
|
+
meta: z.object({ owner: z.string(), repo: z.string(), pr: z.number(), url: z.string(), sha: z.string() }),
|
|
80
|
+
delivers: [approve, changes],
|
|
81
|
+
hooks: {
|
|
82
|
+
github: {
|
|
83
|
+
"pull_request_review.submitted": ({ payload, meta }) => {
|
|
84
|
+
// A review of another pull request, or of an older commit, is not for this Gate.
|
|
85
|
+
if (payload.pull_request.html_url !== meta.url || payload.review.commit_id !== meta.sha) return undefined;
|
|
86
|
+
if (payload.review.state === "approved") return { type: "approve", by: payload.review.user.login };
|
|
87
|
+
if (payload.review.state === "changes_requested") {
|
|
88
|
+
return { type: "changes", by: payload.review.user.login, notes: payload.review.body ?? "" };
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
look: {
|
|
95
|
+
read: reviews,
|
|
96
|
+
input: (meta) => ({ owner: meta.owner, repo: meta.repo, pr: meta.pr }),
|
|
97
|
+
every: "10m",
|
|
98
|
+
until: verdictOf,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
type Input = Meta;
|
|
103
|
+
|
|
104
|
+
export const machine = jr2Setup({
|
|
105
|
+
types: {} as { context: Input & { notes?: string }; input: Input },
|
|
106
|
+
events: [approve, changes],
|
|
107
|
+
actors: { review },
|
|
108
|
+
}).createMachine({
|
|
109
|
+
id: "review",
|
|
110
|
+
input: z.object({ owner: z.string(), repo: z.string(), pr: z.number(), url: z.string(), sha: z.string() }),
|
|
111
|
+
context: ({ input }) => input,
|
|
112
|
+
initial: "inReview",
|
|
113
|
+
states: {
|
|
114
|
+
inReview: {
|
|
115
|
+
invoke: {
|
|
116
|
+
src: "review",
|
|
117
|
+
input: ({ context }) => ({
|
|
118
|
+
meta: { owner: context.owner, repo: context.repo, pr: context.pr, url: context.url, sha: context.sha },
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
on: { approve: "approved", changes: "changesAsked" },
|
|
122
|
+
},
|
|
123
|
+
approved: { type: "final" },
|
|
124
|
+
changesAsked: { type: "final" },
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The `import type {} from "@jr2/orchestrator/hooks/github"` line gives the handlers their types: the event key
|
|
130
|
+
`pull_request_review.submitted` and its payload. A key that no kind in scope gives does not compile. A handler reads
|
|
131
|
+
only the fields that the kind's schema names.
|
|
132
|
+
|
|
133
|
+
## The handler
|
|
134
|
+
|
|
135
|
+
A delivery of the kind and the event key goes to each open Gate whose handlers hear them. Thus each handler compares the
|
|
136
|
+
payload with the Gate's `meta`. In the example, the handler gives `undefined` for a review of another pull request or of
|
|
137
|
+
an older commit. A handler is pure and synchronous: it does no I/O and reads no clock.
|
|
138
|
+
|
|
139
|
+
The event of a handler has the Gate as its [Sender](../reference/glossary.md#sender), with the Hook as its
|
|
140
|
+
[Caller](../reference/glossary.md#caller). The run takes the event only when it takes it from this Gate now, as for
|
|
141
|
+
`jr2 send`.
|
|
142
|
+
|
|
143
|
+
## The look
|
|
144
|
+
|
|
145
|
+
A delivery can come before the Gate opens, or it can be lost while the
|
|
146
|
+
[Orchestrator](../reference/glossary.md#orchestrator) is down. The look reads the fact, so these cases come to the same
|
|
147
|
+
read:
|
|
148
|
+
|
|
149
|
+
- The look reads once after the Gate opens, again after each restart of the Orchestrator, and every `every` while the
|
|
150
|
+
Gate is open. The default `every` is `"10m"`.
|
|
151
|
+
- `until` gives one event of `delivers` from the read's output, or `undefined` for "not yet".
|
|
152
|
+
- When the run takes the event, the look stops until the Gate opens again. The run's feed shows `looked`. A looked event
|
|
153
|
+
has the Gate as its Sender and no Caller.
|
|
154
|
+
- The `http` Effect of the look is an Effect, so the secret that it names is an entry of the `secrets` list, as
|
|
155
|
+
`github-token` is in the example's `jr2.config.ts`. Put its value in `.env` as `GITHUB_TOKEN`.
|
|
156
|
+
|
|
157
|
+
A state that goes back to its own Gate must change the `meta`, for example to a new `sha`. If not, the look reads the
|
|
158
|
+
same verdict again.
|
|
159
|
+
|
|
160
|
+
Give `look: "none"` to a Gate that hears a command, such as a `/retry` comment. A command is not a fact that a read can
|
|
161
|
+
find, so a lost delivery of a command stays lost.
|
|
162
|
+
|
|
163
|
+
## The Hook
|
|
164
|
+
|
|
165
|
+
The Gate hears a delivery only when the [Instance](../reference/glossary.md#instance) declares a Hook of its kind. Each
|
|
166
|
+
Hook is a name in the `hooks` block of `jr2.config.ts`, and its address is `POST /hooks/<name>`. Set up the service with
|
|
167
|
+
this address one time:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { defineConfig, secret } from "@jr2/orchestrator";
|
|
171
|
+
import { github } from "@jr2/orchestrator/hooks/github";
|
|
172
|
+
import { gitlab } from "@jr2/orchestrator/hooks/gitlab";
|
|
173
|
+
|
|
174
|
+
export default defineConfig({
|
|
175
|
+
// Each Hook's secret is an entry of the list. jr2 up places GH_HOOK in the Instance Effector, and
|
|
176
|
+
// gives the Orchestrator only the sha-256 of GL_HOOK.
|
|
177
|
+
secrets: [
|
|
178
|
+
{ name: "GH_HOOK", value: process.env.GH_HOOK },
|
|
179
|
+
{ name: "GL_HOOK", value: process.env.GL_HOOK },
|
|
180
|
+
// The look of review reads the reviews with this token. jr2 up places it in the Instance Effector.
|
|
181
|
+
{ name: "github-token", value: process.env.GITHUB_TOKEN },
|
|
182
|
+
],
|
|
183
|
+
hooks: {
|
|
184
|
+
// POST /hooks/forge: GitHub signs each delivery with the secret GH_HOOK. The Hook may answer
|
|
185
|
+
// the Gates of review alone.
|
|
186
|
+
forge: github({ secret: secret("GH_HOOK"), may: { answer: ["review"] } }),
|
|
187
|
+
// POST /hooks/lab: GitLab sends the secret GL_HOOK as a token in a header.
|
|
188
|
+
lab: gitlab({ secret: secret("GL_HOOK"), may: { answer: ["review"] } }),
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
A Hook takes a credential and `may.answer`. `may.answer` is the list of Workflows whose Gates the Hook can answer. The
|
|
194
|
+
default is every Workflow. A Hook never starts a run. The kind tells how the service signs:
|
|
195
|
+
|
|
196
|
+
- `github` and `slack` sign with a shared secret (`mac`). The
|
|
197
|
+
[Instance Effector](../reference/glossary.md#instance-effector) holds the secret and checks the signature. The
|
|
198
|
+
Orchestrator does not hold the secret.
|
|
199
|
+
- `gitlab` sends a shared token in a header (`token`). The Orchestrator keeps only the sha-256 of the token, and removes
|
|
200
|
+
the header before the kind reads the delivery.
|
|
201
|
+
- `discord` signs with a key pair (`ed25519`). The config holds the public key, which cannot sign.
|
|
202
|
+
|
|
203
|
+
Each secret that a Hook names is an entry of the `secrets` list. For a `mac` Hook, [`jr2 up`](../reference/cli.md#up)
|
|
204
|
+
places the secret in the Instance Effector alone, as it places an
|
|
205
|
+
[Effect secret](../reference/glossary.md#effect-secret), and it converges the Instance Effector for the Hook. For a
|
|
206
|
+
`token` Hook, `jr2 up` writes the sha-256 of the secret into the Secret `jr2-hook-digests`, which the Orchestrator
|
|
207
|
+
mounts. The Orchestrator reads it again when it changes, so a new value needs no restart. The kubelet updates the mount
|
|
208
|
+
in about one minute, and `jr2 up` waits for it before it says `converged`. A kind that posts a follow-up, such as the
|
|
209
|
+
reply to a Slack click, declares the hosts of the post. `jr2 up` adds these hosts to the Instance Effector's list of
|
|
210
|
+
targets, with no secret. The output of `jr2 up` names the Hook as `hook forge`, in the line of the secret and in the
|
|
211
|
+
line of the Instance Effector.
|
|
212
|
+
|
|
213
|
+
When a delivery comes, the door does these steps in this sequence:
|
|
214
|
+
|
|
215
|
+
1. The door refuses a body that is larger than 2 MiB. It reads no more of the body than this limit.
|
|
216
|
+
2. The verifier checks the delivery. A signed time that is more than 300 seconds from the Orchestrator's clock fails the
|
|
217
|
+
check.
|
|
218
|
+
3. The kind reads the delivery. A handshake (a GitHub `ping`, a Discord `PING`, a Slack `url_verification`) gets the
|
|
219
|
+
kind's answer and goes to no Gate.
|
|
220
|
+
4. The kind's schema parses the payload.
|
|
221
|
+
5. The door looks for a repeat: a delivery whose id or body this Hook got before.
|
|
222
|
+
6. The delivery goes to each open Gate, in each live run of a Workflow that the Hook may answer, whose handlers hear its
|
|
223
|
+
kind and event key.
|
|
224
|
+
|
|
225
|
+
The answer is the outcome:
|
|
226
|
+
`{ delivery, seen, delivered: [{ run, workflow, gate, event }], refused: [{ run, gate, event?, reason }], ignored, sources }`.
|
|
227
|
+
`ignored` counts the open Gates whose handler gave `undefined`. A Gate whose handler throws, or gives an event that the
|
|
228
|
+
run does not take now, is in `refused`, and the run's feed shows `hook-refused`. The run continues.
|
|
229
|
+
|
|
230
|
+
To see what the door did, print the Hook's log. It keeps each delivery for `runs.retain` (30 days by default), newest
|
|
231
|
+
first:
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
jr2 hooks forge
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## A Gate in a package
|
|
238
|
+
|
|
239
|
+
A [packaged Machine](publish-a-machine-as-an-npm-package.md) writes its handlers, because a forge's payload is public.
|
|
240
|
+
It leaves its look `open`, because the look needs a url and a token. The composer binds the look with `customize()`, and
|
|
241
|
+
can add, replace or remove a handler by kind and event key, where `null` removes one:
|
|
242
|
+
`customize(machine, { gates: { review: { look, hooks: { gitlab: { "note.MergeRequest": null } } } } })`.
|
|
243
|
+
|
|
244
|
+
## Rules
|
|
245
|
+
|
|
246
|
+
- The door refuses a delivery that fails the verifier, or whose signed time is more than 300 seconds from the
|
|
247
|
+
Orchestrator's clock, with 401. Nothing else occurs, and the Hook's log keeps no line.
|
|
248
|
+
- The door refuses a body that is larger than 2 MiB with 413, before the verifier:
|
|
249
|
+
`the delivery's body is larger than 2097152 bytes, the most the door reads` The Hook's log keeps a `too-large` line,
|
|
250
|
+
which does not claim the delivery's id.
|
|
251
|
+
- The door refuses a name that the config does not declare with 404:
|
|
252
|
+
`no Hook named "<name>" in the current Assembly's config`
|
|
253
|
+
- With no current Assembly, the door answers 503:
|
|
254
|
+
`the Orchestrator has no current Assembly, so no Hook is declared now — ask again`
|
|
255
|
+
- `jr2 up` refuses a `mac` Hook whose secret is not in the `secrets` list:
|
|
256
|
+
``hooks.<name> names secret("<NAME>"), which the `secrets` list does not hold — add it: secrets: [{ name: "<NAME>", value: process.env.<NAME> }] (ADR-0068)``
|
|
257
|
+
It refuses a `token` Hook whose secret is not in the list with the same text, which ends with `(ADR-0078)`.
|
|
258
|
+
- `jr2 up` names the secret of a `token` Hook: `secret <NAME>: a digest in the Orchestrator (hook <name>)`
|
|
259
|
+
- A `mac` Hook whose check the Instance Effector cannot answer gets 503:
|
|
260
|
+
`the Hook's signature cannot be checked now: no Instance Effector (ADR-0068)`
|
|
261
|
+
- A request that the kind cannot read gets 400: `the <kind> kind cannot read the delivery: <error>`
|
|
262
|
+
- A payload that the kind's schema refuses gets 200, with `payloadRefused: { kind, key, issues }`. It goes to no Gate.
|
|
263
|
+
When the service sends it again, the door parses it again.
|
|
264
|
+
- A delivery whose id or body the Hook got before gets 200 with `seen: true`, and goes to no Gate.
|
|
265
|
+
- The config load refuses a Hook's name that is not lowercase letters, digits and `-`, at most 40 characters:
|
|
266
|
+
`<file> at hooks.<name>: a Hook's name is lowercase letters, digits and -, at most 40, starts with a letter and ends with a letter or digit (ADR-0078)`
|
|
267
|
+
- The config load refuses a value that no kind made:
|
|
268
|
+
`<file> at hooks.<name>: not a Hook — make it with its kind, such as github({ secret: secret("GH_HOOK") }) (ADR-0078)`
|
|
269
|
+
- `jr2 hooks <name>` needs the Instance token.
|
|
270
|
+
|
|
271
|
+
- `gate()` refuses a part that it does not know:
|
|
272
|
+
`gate(): "<key>" is not a part of a Gate slot (meta, delivers, hooks, look, reply) (ADR-0011)`
|
|
273
|
+
- `gate()` refuses an empty `delivers`: `gate(): its delivers lists at least one event that defineEvent made (ADR-0011)`
|
|
274
|
+
- `gate()` refuses a look whose read is a different Effect:
|
|
275
|
+
``gate(): its look reads with <an http Effect that is not marked idempotent: true | a <kind> Effect> — a look reads again after each restore and every `every`, so its read is an http Effect marked idempotent: true, or a promise (ADR-0067) (ADR-0011)``
|
|
276
|
+
- `gate()` refuses an `every` that is not a duration:
|
|
277
|
+
`gate(): its look's every "<every>" is not a positive duration (like "90s", "10m", "2h") (ADR-0011)`
|
|
278
|
+
- `createMachine` refuses an event of `delivers` that the Machine does not declare:
|
|
279
|
+
`machine "<id>": Gate slot "<slot>" delivers "<event>", which no def in jr2Setup({ events }) declares (declared: <events>) — list it in events (ADR-0011)`
|
|
280
|
+
- `createMachine` refuses an event of `delivers` that the Gate does not take at the state that invokes it:
|
|
281
|
+
`machine "<id>": state "<state>" invokes Gate slot "<slot>", which delivers "<event>", and its Gate does not accept it (accepts: <events>) — no handler or look could deliver it: handle it on that state, a state above it or a plain child, or drop it from delivers (ADR-0011)`
|
|
282
|
+
- The Gate does not open when its `meta` does not agree with the schema, and the invoke fails:
|
|
283
|
+
`gate "<gate>": its meta does not match the Gate slot's meta schema — <path>: <message>`
|
|
284
|
+
- `customize()` refuses a slot that holds no Gate slot:
|
|
285
|
+
`customize(): machine "<id>" carries no Gate slot "<slot>" — its Gate slots are: <slots> (ADR-0011). Only declared parts can be bound.`
|
|
286
|
+
- `customize()` refuses `open` as a look:
|
|
287
|
+
``customize(): Gate slot "<slot>"'s look is bound once, to a look or "none" — not `open` again (ADR-0054)``
|
|
288
|
+
- The Chart door refuses a Chart that invokes a lent Gate slot whose `delivers` holds an event that is not in the
|
|
289
|
+
Chart's `events`:
|
|
290
|
+
``<path>: the Gate slot "<slot>" delivers "<event>", which is not in the Chart's `events` (events: <events>); declare it in `events` and handle it on this state``
|
|
291
|
+
- `jr2 up` and `jr2 check` warn, and do not refuse, about a Gate slot that hears a Hook and has no look and no `after`
|
|
292
|
+
on its state:
|
|
293
|
+
``warning: workflow "<workflow>": Gate slot "<slot>" on "<state>" hears a Hook and has no look and no `after` on its state — a delivery lost in an outage stops the run until a person answers the Gate: give it a look, or look: "none" for a Gate that hears a command (ADR-0011)``
|
|
294
|
+
- `jr2 up` and `jr2 check` warn about a look that a package left `open` and that nobody bound:
|
|
295
|
+
`warning: workflow "<workflow>": Gate slot "<slot>" on "<state>" leaves its look open — a delivery lost in an outage stops the run until a person answers the Gate: bind it with customize(machine, { gates: { <slot>: { look } } }), or give it look: "none" (ADR-0011, ADR-0054)`
|
|
296
|
+
|
|
297
|
+
## Read next
|
|
298
|
+
|
|
299
|
+
- [Gates](../concepts/gates.md) and [Callers and Hooks](../concepts/callers-and-hooks.md)
|
|
300
|
+
- [Write a Hook kind](write-a-hook-kind.md), for a service that jr2 ships no kind for
|
|
301
|
+
- [Answer a Gate](answer-a-gate.md) and [Bind Open parts](bind-open-parts.md)
|
|
302
|
+
- [ADR-0011](../reference/decisions/0011-workflow-defined-events.md): a Gate hears a Hook, and looks before it waits
|
|
303
|
+
- [ADR-0078](../reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md): a
|
|
304
|
+
Hook is a Caller that can only answer a Gate or bring items to a Source
|
|
@@ -84,7 +84,7 @@ jr2 run release --input '{"version":"1.4.0"}'
|
|
|
84
84
|
|
|
85
85
|
```text
|
|
86
86
|
→ context <context> / namespace <namespace>
|
|
87
|
-
run
|
|
87
|
+
run 0c948d65-fd6f-453a-87fa-71ec00c7fe68 (release) — attached
|
|
88
88
|
→ active "review"
|
|
89
89
|
```
|
|
90
90
|
|
|
@@ -96,12 +96,13 @@ next steps in a second terminal. [Start a run](start-a-run.md) gives the other w
|
|
|
96
96
|
Run [`jr2 status`](../reference/cli.md#status) with the run id:
|
|
97
97
|
|
|
98
98
|
```sh
|
|
99
|
-
jr2 status
|
|
99
|
+
jr2 status 0c94
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
```text
|
|
103
103
|
→ context <context> / namespace <namespace>
|
|
104
|
-
{"runId":"
|
|
104
|
+
{"runId":"0c948d65-fd6f-453a-87fa-71ec00c7fe68","workflow":"release","instanceId":"6db54032-0b87-4005-9583-049f7335b75f","startedAt":"2026-11-06T19:15:39.018Z","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"status":"active","value":"review","context":{"version":"1.4.0"},"children":[],"waiting":[],"held":[],"assembly":"19d0bf0cbe10","gates":[{"gate":"review","path":["review"],"accepts":[{"name":"approve","description":"Release this version.","input":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"additionalProperties":false}},{"name":"reject","description":"Do not release this version. Say why.","input":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reason":{"type":"string","minLength":1}},"required":["reason"],"additionalProperties":false}}],"acceptsNow":["approve","reject"],"meta":{"version":"1.4.0"}}]}
|
|
105
|
+
started by: token/instance on behalf of kubernetes-admin
|
|
105
106
|
```
|
|
106
107
|
|
|
107
108
|
`gates` holds each open Gate of the run, as a [`GateView`](../reference/http-api.md#type-gateview):
|
|
@@ -114,18 +115,37 @@ jr2 status 2319
|
|
|
114
115
|
|
|
115
116
|
A run that has no open Gate has `"gates":[]`.
|
|
116
117
|
|
|
118
|
+
To find the open Gates of all the live runs, run [`jr2 gates`](../reference/cli.md#gates). It prints the Gates that you
|
|
119
|
+
can answer, the oldest first. Give `--workflow <name>` to see only the Gates of the runs of one Workflow:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
jr2 gates --workflow release
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```text
|
|
126
|
+
→ context <context> / namespace <namespace>
|
|
127
|
+
[{"runId":"0c948d65-fd6f-453a-87fa-71ec00c7fe68","workflow":"release","gate":"review","path":["review"],"accepts":[{"name":"approve","description":"Release this version.","input":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{},"additionalProperties":false}},{"name":"reject","description":"Do not release this version. Say why.","input":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reason":{"type":"string","minLength":1}},"required":["reason"],"additionalProperties":false}}],"acceptsNow":["approve","reject"],"meta":{"version":"1.4.0"},"openedAt":"2026-11-06T19:15:39.019Z"}]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Each entry has the fields of a Gate in `gates`, and also `runId`, `workflow` and `openedAt`, the time when the Gate
|
|
131
|
+
opened. When the Gate hears a [Hook](../reference/glossary.md#hook), `hook` gives the
|
|
132
|
+
[Hook kinds](../reference/glossary.md#hook-kind). An entry never holds the `context` of the run. A Gate that takes no
|
|
133
|
+
event from a [Caller](../reference/glossary.md#caller), such as the `source` Gate of a
|
|
134
|
+
[Pool](../reference/glossary.md#pool) that hears only Hooks, is not in the list. `jr2 gates` reads
|
|
135
|
+
[`GET /gates`](../reference/http-api.md#get-gates), and it needs the `answer` scope.
|
|
136
|
+
|
|
117
137
|
## Answer from the CLI
|
|
118
138
|
|
|
119
139
|
Run [`jr2 send`](../reference/cli.md#send). Give the Gate in `--gate`, the event in `--event`, and the input of the
|
|
120
140
|
event as a JSON object in `--input`:
|
|
121
141
|
|
|
122
142
|
```sh
|
|
123
|
-
jr2 send
|
|
143
|
+
jr2 send 0c94 --gate review --event reject --input '{"reason":"The changelog is not ready."}'
|
|
124
144
|
```
|
|
125
145
|
|
|
126
146
|
```text
|
|
127
147
|
→ context <context> / namespace <namespace>
|
|
128
|
-
delivered reject to gate "review" on
|
|
148
|
+
delivered reject to gate "review" on 0c948d65-fd6f-453a-87fa-71ec00c7fe68
|
|
129
149
|
```
|
|
130
150
|
|
|
131
151
|
`jr2 send` prints `delivered` only when the run took the event. In the first terminal, the run ends. A `reject` ends the
|
|
@@ -136,7 +156,7 @@ ends `failed`. Its output is the [Failure](../reference/glossary.md#failure), an
|
|
|
136
156
|
→ active "rejected"
|
|
137
157
|
→ failed "rejected"
|
|
138
158
|
failed: The changelog is not ready.
|
|
139
|
-
{"runId":"
|
|
159
|
+
{"runId":"0c948d65-fd6f-453a-87fa-71ec00c7fe68","workflow":"release","instanceId":"6db54032-0b87-4005-9583-049f7335b75f","startedAt":"2026-11-06T19:15:39.018Z","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"},"status":"failed","value":"rejected","context":{"version":"1.4.0","reason":"The changelog is not ready."},"children":[],"waiting":[],"held":[],"answers":[{"gate":"review","type":"reject","at":"2026-11-06T19:15:52.901Z","answeredBy":{"via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}],"assembly":"19d0bf0cbe10","output":{"reason":"The changelog is not ready."}}
|
|
140
160
|
```
|
|
141
161
|
|
|
142
162
|
The run ended, so its Gate closed. Each example below answers a run that waits. Before each example, start a new run in
|
|
@@ -154,24 +174,25 @@ jr2 send <run id> --gate review --event approve
|
|
|
154
174
|
|
|
155
175
|
```text
|
|
156
176
|
→ context <context> / namespace <namespace>
|
|
157
|
-
delivered approve to gate "review" on
|
|
177
|
+
delivered approve to gate "review" on 342fef91-53a1-4640-87bf-6fb4395beebf
|
|
158
178
|
```
|
|
159
179
|
|
|
160
180
|
`jr2 send` prints the full run id, also when you give a prefix.
|
|
161
181
|
|
|
162
182
|
## Answer from the Console
|
|
163
183
|
|
|
164
|
-
You need
|
|
165
|
-
|
|
184
|
+
You need a credential with the `answer` scope for the Workflow, such as the Instance token, to answer a Gate from the
|
|
185
|
+
Console. [Open the Console](open-the-console.md) tells how to open it, how to get the token and how to log in.
|
|
166
186
|
|
|
167
187
|
1. Open the Console.
|
|
168
|
-
2. Type the
|
|
188
|
+
2. Type the token in the `Caller token` box in the nav, or click `log in`.
|
|
169
189
|
3. Push Enter. The badge beside the box shows `…`, then `live`.
|
|
170
190
|
4. Click the `Attention` tab in the drawer on the right. It shows a card for each open Gate of the Workflow that you
|
|
171
|
-
selected. To see the Gates of all the Workflows, select
|
|
172
|
-
all.
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
selected and that your credential can answer, the oldest first. To see the Gates of all the Workflows, select
|
|
192
|
+
`all workflows`. When you select no Workflow, it shows them all. The Console reads the cards from
|
|
193
|
+
[`GET /gates`](../reference/http-api.md#get-gates).
|
|
194
|
+
5. Find the card. Its top line shows `⚑` and the id of the Gate at the left. At the right, it shows the Workflow, the
|
|
195
|
+
first 8 characters of the run id and `open since <time>`. Below it, the card shows the `meta` of the Gate.
|
|
175
196
|
6. The card has a form for each event that the Gate accepts. Fill in the form of the event that you send. A field that
|
|
176
197
|
has `*` after its name is necessary.
|
|
177
198
|
7. Click `send` in that form.
|
|
@@ -184,9 +205,13 @@ The `⚑` badge in the nav gives the number of open Gates in all the Workflows.
|
|
|
184
205
|
## Answer from a webhook or CI
|
|
185
206
|
|
|
186
207
|
A webhook handler or a CI job answers a Gate with
|
|
187
|
-
[`POST /runs/:runId/gates/:gate/events`](../reference/http-api.md#post-runs-runid-gates-gate-events) and the
|
|
188
|
-
|
|
189
|
-
|
|
208
|
+
[`POST /runs/:runId/gates/:gate/events`](../reference/http-api.md#post-runs-runid-gates-gate-events) and the token of a
|
|
209
|
+
named Caller with the `answer` scope for the Workflow. A CI job has no access to the cluster, so it cannot read the
|
|
210
|
+
Instance token, and it must not have it: the Instance token can do everything.
|
|
211
|
+
[Give a Caller a scope](give-a-caller-a-scope.md) makes the Caller token. A service that a Hook kind covers, such as a
|
|
212
|
+
forge, sends its webhook to a Hook: see [Answer a Gate from a forge](answer-a-gate-from-a-forge.md). Any other handler
|
|
213
|
+
or CI job uses the Gate route. It reads its own event, finds the run and the Gate, and sends the call. The body is the
|
|
214
|
+
event: `type` is the name of the event, and the other properties are its input.
|
|
190
215
|
|
|
191
216
|
`jr2 up` makes the Service `jr2-orchestrator` on port 4000, with no address outside the cluster. Thus a caller outside
|
|
192
217
|
the cluster uses a port forward, or an address that you make for the Service.
|
|
@@ -197,7 +222,8 @@ In a new terminal, forward a port to the Orchestrator. Keep this command open:
|
|
|
197
222
|
kubectl -n <namespace> port-forward svc/jr2-orchestrator 4000:4000
|
|
198
223
|
```
|
|
199
224
|
|
|
200
|
-
In a different terminal,
|
|
225
|
+
In a different terminal, put a token into a variable. A CI job uses its Caller token. To try the route from your own
|
|
226
|
+
terminal, you can use the Instance token:
|
|
201
227
|
|
|
202
228
|
```sh
|
|
203
229
|
TOKEN=$(kubectl -n <namespace> get secret jr2-instance -o jsonpath='{.data.JR2_INSTANCE_TOKEN}' | base64 -d)
|
|
@@ -217,9 +243,9 @@ curl -s -X POST http://localhost:4000/runs/<run id>/gates/review/events \
|
|
|
217
243
|
```
|
|
218
244
|
|
|
219
245
|
A webhook handler often knows a fact, such as a version, but not the run id. It can find the run from the `meta` of the
|
|
220
|
-
Gate. [`GET /
|
|
221
|
-
|
|
222
|
-
|
|
246
|
+
Gate. [`GET /gates`](../reference/http-api.md#get-gates) gives the open Gates of all the live runs that the token can
|
|
247
|
+
answer, each with its run id and its `meta`. It needs only the `answer` scope. This script approves the `release` run
|
|
248
|
+
that waits for one version. It needs `curl` and `jq`. Save it as `approve-release.sh`:
|
|
223
249
|
|
|
224
250
|
```sh
|
|
225
251
|
#!/bin/sh
|
|
@@ -229,15 +255,13 @@ set -eu
|
|
|
229
255
|
|
|
230
256
|
auth="Authorization: Bearer $JR2_TOKEN"
|
|
231
257
|
|
|
232
|
-
# The
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
echo
|
|
240
|
-
fi
|
|
258
|
+
# The open Gates of the live release runs that this token can answer, with their meta.
|
|
259
|
+
runs=$(curl -fsS -H "$auth" "$JR2_URL/gates?workflow=release" |
|
|
260
|
+
jq -r --arg v "$VERSION" '.gates[] | select(.gate == "review" and .meta.version == $v) | .runId')
|
|
261
|
+
for run in $runs; do
|
|
262
|
+
curl -fsS -X POST -H "$auth" -H "Content-Type: application/json" \
|
|
263
|
+
-d '{"type":"approve"}' "$JR2_URL/runs/$run/gates/review/events"
|
|
264
|
+
echo
|
|
241
265
|
done
|
|
242
266
|
```
|
|
243
267
|
|
|
@@ -258,15 +282,15 @@ JR2_URL=http://localhost:4000 JR2_TOKEN=$TOKEN jr2 send <run id> --gate review -
|
|
|
258
282
|
|
|
259
283
|
```text
|
|
260
284
|
→ http://localhost:4000
|
|
261
|
-
delivered approve to gate "review" on
|
|
285
|
+
delivered approve to gate "review" on cad78069-0d8d-4c04-9cf4-3f771693274f
|
|
262
286
|
```
|
|
263
287
|
|
|
264
288
|
## Rules
|
|
265
289
|
|
|
266
290
|
- The Orchestrator refuses a Gate that is not open on the run, with the status code `404`:
|
|
267
291
|
`no open gate "<gate>" on run "<run id>" (open: <gates>)`. The part `(open: <gates>)` shows only when the run has an
|
|
268
|
-
open Gate. When an event takes the run out of the state of the Gate, the Gate closes. Thus
|
|
269
|
-
refusal.
|
|
292
|
+
open Gate that the Caller can answer. When an event takes the run out of the state of the Gate, the Gate closes. Thus
|
|
293
|
+
a second answer gets this refusal.
|
|
270
294
|
- The Orchestrator refuses an event that the Gate does not accept, with the status code `400`:
|
|
271
295
|
`"<gate>" does not accept "<event>" (accepts: <events>)`
|
|
272
296
|
- The Orchestrator refuses an input that does not agree with the schema of the event, with the status code `400`:
|
|
@@ -278,10 +302,23 @@ delivered approve to gate "review" on c44c6e49-452d-4150-8ce4-4d036363d2a0
|
|
|
278
302
|
`event body must carry a string "type" (one of the gate's accepted names)`
|
|
279
303
|
- The Orchestrator refuses a body that holds `sender`, with the status code `400`:
|
|
280
304
|
`` `sender` is written by the Orchestrator on every delivered event; a caller cannot send it ``
|
|
281
|
-
- The Orchestrator refuses an event
|
|
305
|
+
- The Orchestrator refuses an event with no valid credential, with the status code `401`: `unauthorized`. A credential
|
|
306
|
+
is the Instance token, or that of a [Caller](../reference/glossary.md#caller) with the `answer` scope.
|
|
307
|
+
[Give a Caller a scope](give-a-caller-a-scope.md) makes one.
|
|
282
308
|
- The Orchestrator refuses an event with the token of a [Sandbox](../reference/glossary.md#sandbox), with the status
|
|
283
|
-
code `403`:
|
|
284
|
-
|
|
309
|
+
code `403`:
|
|
310
|
+
`this route needs a Caller, and a Sandbox token is not one: no Agent reaches run state, run control or a Gate`. Thus
|
|
311
|
+
an Agent cannot answer a Gate.
|
|
312
|
+
- The Orchestrator refuses an event from a Caller that has the `answer` scope for no Workflow, with the status code
|
|
313
|
+
`403`, before it reads the run: `the Caller <via>/<name> has no "answer" scope for any Workflow (ADR-0078)`. The body
|
|
314
|
+
also holds `scope`.
|
|
315
|
+
- The Orchestrator refuses an event from a Caller that does not have the `answer` scope for the Workflow of the run,
|
|
316
|
+
with the status code `403`: `the Caller <via>/<name> needs the "answer" scope for Workflow "<workflow>" (ADR-0078)`
|
|
317
|
+
- The Orchestrator refuses an event to an open Gate from a Caller whose `answer` scope names other Gates of the
|
|
318
|
+
Workflow, with the status code `403`:
|
|
319
|
+
`the Caller <via>/<name> needs the "answer" scope for Gate "<name>" of Workflow "<workflow>" (ADR-0078)`. `<name>` is
|
|
320
|
+
the name of the Gate, its invoke id. The body also holds `scope`, `workflow` and `gate`.
|
|
321
|
+
[Give a Caller a scope](give-a-caller-a-scope.md) tells how a scope names single Gates.
|
|
285
322
|
- `jr2 send` prints each refusal of the Orchestrator as `error: <refusal>`, and exits with `1`. After a `404` refusal,
|
|
286
323
|
it also prints the version of the Instance in one more line:
|
|
287
324
|
``instance: <version> (<hash>) — if it predates this CLI, `jr2 up` converges the cluster to this kit``. `<hash>` is
|
|
@@ -298,7 +335,7 @@ delivered approve to gate "review" on c44c6e49-452d-4150-8ce4-4d036363d2a0
|
|
|
298
335
|
- [ADR-0011](../reference/decisions/0011-workflow-defined-events.md): a Gate, its events, and its `meta`.
|
|
299
336
|
- [ADR-0013](../reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md): why an
|
|
300
337
|
Agent cannot answer a Gate, and why a refused event is not delivered.
|
|
301
|
-
- [ADR-0032](../reference/decisions/0032-the-console-unlocks-with-
|
|
338
|
+
- [ADR-0032](../reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md): a Caller's credential in the
|
|
302
339
|
Console.
|
|
303
340
|
- [Start a run](start-a-run.md)
|
|
304
341
|
- [Watch a run](watch-a-run.md)
|
|
@@ -121,9 +121,10 @@ jr2 run fix-app --input '{"prompt":"Make the failing test in test/greet.test.ts
|
|
|
121
121
|
The key of each Repo Slot is the name of the slot, and the directory `/work/<slot>` in the
|
|
122
122
|
[Sandbox](../reference/glossary.md#sandbox). The value is one of these:
|
|
123
123
|
|
|
124
|
-
- a url: `"https://github.com/<you>/handbook.git"`. The
|
|
125
|
-
|
|
126
|
-
- a [Binding](../reference/glossary.md#binding): `{ url, ref }`. The
|
|
124
|
+
- a url: `"https://github.com/<you>/handbook.git"`. The clone stays on the default branch of the Repo, and a new
|
|
125
|
+
[Worktree](../reference/glossary.md#worktree) branch starts from it.
|
|
126
|
+
- a [Binding](../reference/glossary.md#binding): `{ url, ref }`. The clone stays on `ref`, a branch, a tag or a commit,
|
|
127
|
+
and a new branch starts from it.
|
|
127
128
|
- a function of the run input: `({ input }) => url`, or `({ input }) => ({ url, ref })`. Each run chooses its Repo. The
|
|
128
129
|
url must agree with an entry of [`git.credentials`](../reference/config.md#git-credentials) in `jr2.config.ts`.
|
|
129
130
|
- `open`, to leave the slot Open for the Instance that registers your Machine.
|
|
@@ -136,7 +137,9 @@ others. A Machine that names its slots, such as `repos: { app: open }`, takes on
|
|
|
136
137
|
|
|
137
138
|
`agents` takes the fields of the definition of an Agent, by slot. Each field goes over the field that the author wrote.
|
|
138
139
|
Thus `{ model }` changes only the model, and the coder keeps the instructions of `task`. You can also change other
|
|
139
|
-
fields, such as `thinkingLevel` or `instructions`.
|
|
140
|
+
fields, such as `thinkingLevel` or `instructions`. `{ mcp }` replaces the whole list of the MCP servers of the Agent.
|
|
141
|
+
`jr2 up` refuses a server name that the `mcp` block of `jr2.config.ts` does not declare, and prints the line with `mcp`
|
|
142
|
+
(see [Give an Agent MCP tools](give-an-agent-mcp-tools.md#bind-the-servers-of-a-packaged-machine)).
|
|
140
143
|
[`AgentDefinition`](../reference/authoring-surface/type-agentdefinition.md) gives the fields.
|
|
141
144
|
|
|
142
145
|
## Bind the parts of a Machine that you compose
|
|
@@ -163,19 +166,29 @@ When `task` is a child of your Machine, give it `branch` in its input. Only the
|
|
|
163
166
|
|
|
164
167
|
## Bind the Open parts of an Effect
|
|
165
168
|
|
|
166
|
-
A packaged Machine can leave the url of an `http` [Effect](../reference/glossary.md#effect), the argv of a `command`,
|
|
167
|
-
the name of a secret Open. Bind them with `effects`, by the slot of the
|
|
168
|
-
`customize(machine, { effects: { notify: { url } } })
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
A packaged Machine can leave the url of an `http` [Effect](../reference/glossary.md#effect), the argv of a `command`,
|
|
170
|
+
the name of a secret, or the server and the tool of an `mcp` Effect Open. Bind them with `effects`, by the slot of the
|
|
171
|
+
Effect: `customize(machine, { effects: { notify: { url } } })`, or
|
|
172
|
+
`customize(machine, { effects: { post: { server: "slack-acme", tool: "post_message" } } })`. One object binds all the
|
|
173
|
+
Open parts of one Effect. You bind each part one time, to a value. No run chooses it.
|
|
174
|
+
[`EffectBinding`](../reference/authoring-surface/type-effectbinding.md) gives the values, and
|
|
175
|
+
[Run a command or call a URL with an Effect](run-a-command-or-call-a-url.md) shows an Effect. A bound server is a name
|
|
176
|
+
of the `mcp` block. Its secret comes from that block, so an `mcp` Effect has no Open secret.
|
|
177
|
+
|
|
178
|
+
## Bind the look of a Gate
|
|
179
|
+
|
|
180
|
+
A packaged Machine can leave the look of a Gate slot Open: `look: open`. Bind it with `gates`, by the slot of the Gate:
|
|
181
|
+
`customize(machine, { gates: { review: { look } } })`. The same key adds, replaces or removes a handler of the Gate, by
|
|
182
|
+
Hook kind and event key, where `null` removes one. `jr2 up` does not refuse an Open look. It warns, because a person can
|
|
183
|
+
still answer the Gate with `jr2 send`. [Answer a Gate from a forge](answer-a-gate-from-a-forge.md) shows a Gate slot.
|
|
171
184
|
|
|
172
185
|
## Rules
|
|
173
186
|
|
|
174
187
|
- `jr2 up` refuses a registered Machine that leaves a part Open. It changes nothing in the cluster:
|
|
175
188
|
`refusing: workflow "<name>" leaves <part> open — bind it where the Machine is registered: export const machine = customize(<import>, <binding>) (<adr>)`.
|
|
176
189
|
`<part>` is one of `its Repo Slots`, `Repo Slot "<slot>"`, `Agent "<slot>"`, or `the <parts> of Effect "<slot>"`,
|
|
177
|
-
where `<parts>` is such as `url` or `
|
|
178
|
-
comes after `open`.
|
|
190
|
+
where `<parts>` is such as `url`, `url and secret`, `server` or `server and tool`. For a child Machine,
|
|
191
|
+
`(on the Machine composed as "<slot>")` comes after `open`.
|
|
179
192
|
- `jr2 up` refuses an Open part on a Machine that a state writes in its `invoke.src`, because no `customize()` gets to
|
|
180
193
|
it. Put that Machine under `actors`:
|
|
181
194
|
`refusing: workflow "<name>" leaves <part> open on a Machine invoked inline — no customize() reaches an actor without a slot key; declare that Machine under setup({ actors }) and bind it there (ADR-0049, <adr>)`
|
|
@@ -188,10 +201,11 @@ gives the values, and [Run a command or call a URL with an Effect](run-a-command
|
|
|
188
201
|
- `customize(): machine "<id>" composes no Machine under "<slot>" — the Machines it composes are: <slots> (ADR-0049).`
|
|
189
202
|
- `customize(): machine "<id>" declares no Repo Slot "<slot>" — its slots are: <slots> (ADR-0051)`
|
|
190
203
|
- `customize(): machine "<id>" carries no Effect slot "<slot>" — its Effects are: <slots> (ADR-0067). Only declared parts can be bound.`
|
|
204
|
+
- `customize(): machine "<id>" carries no Gate slot "<slot>" — its Gate slots are: <slots> (ADR-0011). Only declared parts can be bound.`
|
|
191
205
|
- `customize()` refuses an Open map that you bind with no slot:
|
|
192
206
|
``customize(): machine "<id>" declares its Repo Slots open as a map — name at least one: `repos: { app: "https://…" }` (ADR-0051).``
|
|
193
207
|
- `customize()` refuses a Repo Slot key that is not a directory name. The key becomes `/work/<slot>`:
|
|
194
|
-
``customize(): Repo Slot key "<slot>" is not a directory name — a slot becomes `/work/<slot
|
|
208
|
+
``customize(): Repo Slot key "<slot>" is not a directory name — a slot becomes `/work/<slot>`, `/work/.jr2/` is reserved for jr2, and its order matters, so it starts with a letter and matches /^[A-Za-z][A-Za-z0-9._-]*$/ (ADR-0051, ADR-0075).``
|
|
195
209
|
- `customize()` refuses an empty url:
|
|
196
210
|
``customize(): Repo Slot "<slot>" is bound to an empty url (got <value>) — a slot is `open`, a url, `{ url, ref? }`, or a mapper `({ input }) => url | { url, ref? }` over the door (ADR-0051).``
|
|
197
211
|
For an empty `ref`, the text says `bound with an empty ref`.
|
|
@@ -200,6 +214,9 @@ gives the values, and [Run a command or call a URL with an Effect](run-a-command
|
|
|
200
214
|
- `customize()` refuses `open` or a function as the value of an Effect's part:
|
|
201
215
|
``customize(): the <kind> Effect "<slot>"'s <part> is bound once, to a value — not `open` again: no run chooses an Effect's <part> (ADR-0054)``.
|
|
202
216
|
For a function, the text says `a function of the run` in place of `` `open` again ``.
|
|
217
|
+
- `jr2 up` refuses an Agent that names an MCP server that the `mcp` block does not declare:
|
|
218
|
+
`refusing: workflow "<name>": Agent "<slot>" names the MCP server "<server>", which the mcp block declares "<names>" — bind the Agent to a declared server where the Machine is registered: export const machine = customize(<import>, { agents: { <slot>: { mcp: [<entries>] } } }); or declare it in jr2.config.ts: mcp: { "<server>": { url: "https://<host>/mcp", secret: "<SECRET>" } } (ADR-0079)`.
|
|
219
|
+
For a child Machine, the line names the child, and `customize()` goes through `actors`.
|
|
203
220
|
- An Agent does not start a [Turn](../reference/glossary.md#turn) while its model is Open:
|
|
204
221
|
``agent "<slot>" has an Open model — nobody bound it; bind it where the Machine is registered (`customize(<machine>, { agents: { <slot>: { model: "<provider>/<model>" } } })`), because a packaged Machine cannot pick a model on your behalf (ADR-0054)``.
|
|
205
222
|
`jr2 up` refuses this case first.
|