@jr2/orchestrator 0.6.0 → 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 +86 -9
- package/docs/concepts/architecture.md +41 -28
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +7 -5
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +4 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +50 -22
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +5 -4
- package/docs/concepts/pools-and-sources.md +187 -11
- package/docs/concepts/secrets-and-trust.md +73 -33
- package/docs/concepts/the-menu.md +11 -0
- package/docs/concepts/workspaces.md +6 -5
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/guides/add-tools-to-the-sandbox.md +55 -11
- 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 +24 -8
- 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/fix-a-failed-converge.md +18 -6
- 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 +9 -6
- package/docs/guides/look-inside-a-sandbox.md +101 -31
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-one-worker-per-item.md +15 -9
- package/docs/guides/set-an-agents-access-and-dials.md +2 -0
- 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 +1 -1
- package/docs/guides/watch-a-run.md +127 -121
- 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 +17 -7
- package/docs/llms.txt +81 -15
- package/docs/reference/authoring-surface/agent.md +1 -1
- package/docs/reference/authoring-surface/answer.md +1 -1
- package/docs/reference/authoring-surface/arg.md +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/file.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +1 -1
- package/docs/reference/authoring-surface/jr2setup.md +13 -3
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +5 -3
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +1 -1
- package/docs/reference/authoring-surface/pool.md +77 -9
- package/docs/reference/authoring-surface/push.md +1 -1
- package/docs/reference/authoring-surface/readyof.md +1 -1
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +1 -1
- 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 +56 -1
- 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 +14 -1
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
- package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-binding.md +1 -1
- 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-commandoutput.md +1 -1
- package/docs/reference/authoring-surface/type-customize.md +16 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-framepath.md +1 -1
- 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 +1 -1
- package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
- 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-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
- 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 +1 -1
- 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-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-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 +16 -7
- 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 +1 -1
- package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
- package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
- package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
- package/docs/reference/authoring-surface/workspace.md +1 -1
- package/docs/reference/authoring-surface/worktree.md +1 -1
- package/docs/reference/authoring-surface.md +88 -167
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +235 -2
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
- package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
- 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 +9 -2
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
- 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 +18 -14
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
- 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 +32 -18
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
- 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 +116 -34
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
- 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 +7 -6
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
- 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 -18
- 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 +4 -3
- 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 +5 -4
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- 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 +7 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
- 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 +51 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
- 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 +17 -13
- 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/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
- 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 +6 -4
- package/docs/reference/glossary.md +96 -42
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +976 -149
- package/docs/why.md +2 -1
- package/package.json +10 -3
- package/src/actor.ts +36 -10
- package/src/agent.ts +50 -0
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +52 -7
- package/src/config.ts +550 -1
- package/src/conversations.ts +453 -0
- package/src/custodian.ts +8 -2
- package/src/customize.ts +143 -4
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +2 -1
- package/src/effect.ts +60 -7
- package/src/effector-wire.ts +272 -27
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +10 -4
- package/src/held-secrets.ts +72 -23
- package/src/holds.ts +2 -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 +76 -38
- package/src/http.ts +889 -94
- package/src/index.ts +16 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +22 -3
- package/src/machine-doc.ts +26 -1
- package/src/mcp-effect.ts +424 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +64 -2
- package/src/notices.ts +138 -10
- package/src/open-body.ts +25 -11
- package/src/open-effect.ts +17 -4
- package/src/open.ts +4 -2
- package/src/parts.ts +87 -9
- package/src/party.ts +2 -1
- package/src/pick.ts +2 -2
- package/src/pool.ts +1142 -163
- package/src/registration.ts +203 -13
- package/src/run-host.ts +1509 -171
- package/src/run-list.ts +191 -0
- package/src/sandbox-kube.ts +53 -5
- package/src/secret-placement.ts +239 -33
- package/src/server.ts +53 -4
- package/src/setup.ts +72 -32
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +263 -22
- package/src/wire.ts +17 -0
- package/src/workspace.ts +50 -6
- package/src/worktree.ts +3 -3
- 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
|
@@ -86,7 +86,7 @@ jr2 up — instance "my-instance" → context kind-jr2 / namespace my-instance
|
|
|
86
86
|
typecheck: tsc --noEmit (the instance's own compiler)
|
|
87
87
|
agents: (none) (carried by 1 workflow machine(s))
|
|
88
88
|
assembly: staging
|
|
89
|
-
assembly:
|
|
89
|
+
assembly: 50e6f81e3bfb (kit v<version>; 1 workflow(s): ping)
|
|
90
90
|
kubernetes: v1.36.1 (floor 1.35, ADR-0037)
|
|
91
91
|
first contact: deploy instance "my-instance" to context kind-jr2 (create namespace "my-instance")? [y/N] y
|
|
92
92
|
platforms: linux/amd64 (from 1 schedulable node(s))
|
|
@@ -94,14 +94,15 @@ images: installed kit — the published v<version> Harness, operator and Orchest
|
|
|
94
94
|
…
|
|
95
95
|
orchestrator: waiting for rollout
|
|
96
96
|
orchestrator: verified — 1 pod(s) running ghcr.io/snapwich/jr2-orchestrator:<version>
|
|
97
|
-
instance effector: none (no Effect is placed there)
|
|
98
|
-
assembly: publishing
|
|
99
|
-
assembly:
|
|
97
|
+
instance effector: none (no Effect is placed there, and no Hook needs it)
|
|
98
|
+
assembly: publishing 50e6f81e3bfb (no current Assembly)
|
|
99
|
+
assembly: 50e6f81e3bfb is current — workflows: ping
|
|
100
100
|
swept nothing — every labeled image is named by a live root
|
|
101
|
+
…
|
|
101
102
|
converged — `jr2 run <workflow>` when ready
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
`<version>` is the Kit version, such as `0.4.1`. `
|
|
105
|
+
`<version>` is the Kit version, such as `0.4.1`. `50e6f81e3bfb` is the hash of the
|
|
105
106
|
[Assembly](../reference/glossary.md#assembly) of your Instance: your Workflows and your config. Your hash can be
|
|
106
107
|
different. It changes each time that your Workflows or your config change.
|
|
107
108
|
|
|
@@ -122,9 +123,9 @@ JSON:
|
|
|
122
123
|
|
|
123
124
|
```text
|
|
124
125
|
→ context kind-jr2 / namespace my-instance
|
|
125
|
-
run
|
|
126
|
+
run db1fd16f-166f-4575-b8fb-60cd80321308 (ping) — attached
|
|
126
127
|
→ done "done"
|
|
127
|
-
{"runId":"
|
|
128
|
+
{"runId":"db1fd16f-166f-4575-b8fb-60cd80321308","workflow":"ping","instanceId":"71af068c-e150-4802-8b1c-6d07d3bb4a08","startedAt":"2026-11-06T19:15:47.436Z","status":"done","value":"done","context":{"message":"hi","reply":"pong: hi"},"children":[],"waiting":[],"held":[],"assembly":"50e6f81e3bfb","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}
|
|
128
129
|
```
|
|
129
130
|
|
|
130
131
|
Your run id and your [Instance ID](../reference/glossary.md#instance-id) are not the same as the ones here. The run is
|
|
@@ -155,16 +156,17 @@ Click `ping`. The center shows its Machine, with the states `responding` and `do
|
|
|
155
156
|
## Unlock the Console
|
|
156
157
|
|
|
157
158
|
Without a token, you can look at the runs in the Console, but you cannot start a run or answer a
|
|
158
|
-
[Gate](../reference/glossary.md#gate). For these, you need the
|
|
159
|
+
[Gate](../reference/glossary.md#gate). For these, you need the token of a Caller with a scope. Here, use the Instance
|
|
160
|
+
token. In the first terminal, run:
|
|
159
161
|
|
|
160
162
|
```sh
|
|
161
163
|
kubectl -n my-instance get secret jr2-instance -o jsonpath='{.data.JR2_INSTANCE_TOKEN}' | base64 -d
|
|
162
164
|
```
|
|
163
165
|
|
|
164
|
-
The command prints the token, 48 hexadecimal characters, with no newline after it. Paste the token in the
|
|
165
|
-
|
|
166
|
-
`
|
|
167
|
-
keeps the same token each time that it runs.
|
|
166
|
+
The command prints the token, 48 hexadecimal characters, with no newline after it. Paste the token in the `Caller token`
|
|
167
|
+
box in the Console nav. Push Enter. The Console checks the token. Then the badge after the box shows `live`, the nav
|
|
168
|
+
shows `as token/instance`, and a `start` button shows next to `ping`. The browser tab keeps the token until you close
|
|
169
|
+
the tab. `jr2 up` keeps the same token each time that it runs.
|
|
168
170
|
|
|
169
171
|
Keep the port forward open. The next pages use the Console. [Open the Console](../guides/open-the-console.md) gives more
|
|
170
172
|
about the Console.
|
|
@@ -181,8 +183,9 @@ each time that you change the folder. [Converge](../concepts/converge.md) explai
|
|
|
181
183
|
it. [Machines and Workflows](../concepts/machines-and-workflows.md) explains the two words.
|
|
182
184
|
|
|
183
185
|
The Console is a page that the Orchestrator serves, at [`GET /`](../reference/http-api.md#get). Anyone who can reach it
|
|
184
|
-
can see the runs. Only
|
|
185
|
-
|
|
186
|
+
can see the runs. Only a [Caller](../reference/glossary.md#caller) with a scope, such as the holder of the Instance
|
|
187
|
+
token, can start a run or answer a Gate
|
|
188
|
+
([ADR-0032](../reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md)).
|
|
186
189
|
|
|
187
190
|
`jr2 init` wrote `AGENTS.md` for your Assistant. It points to the docs that the Kit installs in
|
|
188
191
|
`node_modules/@jr2/orchestrator/docs/`. These are the docs of your Kit version
|
|
@@ -12,16 +12,17 @@ Put the programs that your [Agents](../reference/glossary.md#agent) and
|
|
|
12
12
|
A [Workspace](../reference/glossary.md#workspace) runs its body in a [Sandbox](../reference/glossary.md#sandbox): a pod
|
|
13
13
|
of its own. The Sandbox Image is the image of the pod's `harness` container. The Agent's
|
|
14
14
|
[Working tools](../reference/glossary.md#working-tools) run in that container, and so do the `command` Effects and the
|
|
15
|
-
shell that you get with `
|
|
15
|
+
shell that you get with `jr2 workspace shell`. Thus a program that is in the image is a program that they can run.
|
|
16
16
|
|
|
17
17
|
Use this table to choose:
|
|
18
18
|
|
|
19
|
-
| You want | Do this
|
|
20
|
-
| ---------------------------------------------- |
|
|
21
|
-
| The same tools for each Workspace | Change `images/default/Dockerfile`.
|
|
22
|
-
| Different tools for the Workspaces of one file | Put a docker context beside the workflow module, and name it in the `image` option.
|
|
23
|
-
| An image that you build and push yourself | Write its registry ref in the `image` option.
|
|
24
|
-
| A service that runs beside the Agent | Name its image in the `user` option. It runs in the [User Container](../reference/glossary.md#user-container).
|
|
19
|
+
| You want | Do this |
|
|
20
|
+
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| The same tools for each Workspace | Change `images/default/Dockerfile`. |
|
|
22
|
+
| Different tools for the Workspaces of one file | Put a docker context beside the workflow module, and name it in the `image` option. |
|
|
23
|
+
| An image that you build and push yourself | Write its registry ref in the `image` option. |
|
|
24
|
+
| A service that runs beside the Agent | Name its image in the `user` option. It runs in the [User Container](../reference/glossary.md#user-container). |
|
|
25
|
+
| A tool that the model calls by name | Put an MCP server in the image, and declare it in `jr2.config.ts`. See [Write a tool as an MCP server](write-a-tool-as-an-mcp-server.md). |
|
|
25
26
|
|
|
26
27
|
## Add a tool to the default image
|
|
27
28
|
|
|
@@ -239,17 +240,23 @@ RUN apk add --no-cache git
|
|
|
239
240
|
# The Worktrees under /work belong to a different user. Tell git to trust them.
|
|
240
241
|
RUN git config --system --add safe.directory '*'
|
|
241
242
|
|
|
242
|
-
# jr2 does not replace this command. It must keep running: when it ends,
|
|
243
|
+
# jr2 does not replace this command. It must keep running: when it ends, Kubernetes starts the container again.
|
|
243
244
|
CMD ["sh", "-c", "while true; do sleep 3600; done"]
|
|
244
245
|
```
|
|
245
246
|
|
|
246
247
|
Save it as `workflows/shell/Dockerfile`. Name it in the `workspace()` options with
|
|
247
248
|
`user: import.meta.resolve("./shell")`. `user` takes the same two forms as `image`, and `jr2 up` builds a `file:`
|
|
248
|
-
context in the same way. Open the shell with `
|
|
249
|
+
context in the same way. Open the shell with [`jr2 workspace shell`](../reference/cli.md#workspace-shell) and `--user`.
|
|
250
|
+
[Look inside a Sandbox](look-inside-a-sandbox.md#find-the-sandbox) tells how to find `<workspace>`:
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
jr2 workspace shell <workspace> --user
|
|
254
|
+
```
|
|
249
255
|
|
|
250
256
|
The User Container is not a second Sandbox Image:
|
|
251
257
|
|
|
252
|
-
- Its own `ENTRYPOINT` and `CMD` run. Its process must keep running
|
|
258
|
+
- Its own `ENTRYPOINT` and `CMD` run. Its process must keep running. When it ends, Kubernetes starts the container
|
|
259
|
+
again, so a `CMD` that ends at once restarts again and again.
|
|
253
260
|
- jr2 gives it `/work`, which it can write, and `/repos` and `/opt/jr2`, which it can only read. jr2 gives it no other
|
|
254
261
|
mount, no environment variable and no command.
|
|
255
262
|
- It has no floor: an Alpine base is correct here. It runs as the `USER` of its image, and root is permitted.
|
|
@@ -266,6 +273,42 @@ container, the [Sandbox Effector](../reference/glossary.md#sandbox-effector). By
|
|
|
266
273
|
[Run a command or call a URL with an Effect](run-a-command-or-call-a-url.md) and
|
|
267
274
|
[Push and sign a branch](push-and-sign-a-branch.md).
|
|
268
275
|
|
|
276
|
+
### Reach a service in the User Container
|
|
277
|
+
|
|
278
|
+
No Service of the cluster goes to the User Container. To reach a port in the Sandbox pod, run
|
|
279
|
+
[`jr2 workspace forward`](../reference/cli.md#workspace-forward). It forwards a port on your computer to the port in the
|
|
280
|
+
pod, through the API server of the cluster, until you push Ctrl-C:
|
|
281
|
+
|
|
282
|
+
```sh
|
|
283
|
+
jr2 workspace forward <workspace> 3000
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```text
|
|
287
|
+
→ context <context> / namespace <namespace>
|
|
288
|
+
127.0.0.1:36779
|
|
289
|
+
forwarding 127.0.0.1:36779 → 7a0e8b50/x:0:3000 — Ctrl-C ends it
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The address on stdout is the local address. The port changes on each run.
|
|
293
|
+
|
|
294
|
+
When the User Container runs an sshd on port `22`, `ssh` can go through `jr2 workspace forward --stdio`. Add this entry
|
|
295
|
+
to `~/.ssh/config`. Replace `<namespace>` with the namespace of your Instance:
|
|
296
|
+
|
|
297
|
+
```text
|
|
298
|
+
Host *.jr2
|
|
299
|
+
ProxyCommand jr2 workspace forward %h 22 --stdio -n <namespace>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Then connect with the name of the Sandbox, which `jr2 workspace ls` prints, and `.jr2`:
|
|
303
|
+
|
|
304
|
+
```sh
|
|
305
|
+
ssh <sandbox>.jr2
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
With `-n`, `jr2 workspace forward` needs no Instance folder, so `ssh` can run it from each folder. The sshd and its keys
|
|
309
|
+
are yours: jr2 gives the User Container no key. With `ssh -A`, your `ssh-agent` goes only to the User Container, and the
|
|
310
|
+
Agent runs nothing there.
|
|
311
|
+
|
|
269
312
|
## Rules
|
|
270
313
|
|
|
271
314
|
- `workspace()` refuses an `image` that is empty or not a string:
|
|
@@ -303,6 +346,7 @@ container, the [Sandbox Effector](../reference/glossary.md#sandbox-effector). By
|
|
|
303
346
|
[`UserContainer`](../reference/authoring-surface/type-usercontainer.md), [`jr2 up`](../reference/cli.md#up)
|
|
304
347
|
- [ADR-0037](../reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md): the Instance
|
|
305
348
|
builds its Sandbox Images, and jr2 injects the Harness.
|
|
306
|
-
- [ADR-0005](../reference/decisions/0005-sandbox-pod-composition.md): the containers of the Sandbox pod
|
|
349
|
+
- [ADR-0005](../reference/decisions/0005-sandbox-pod-composition.md): the containers of the Sandbox pod, and how you
|
|
350
|
+
reach the User Container.
|
|
307
351
|
- [ADR-0049](../reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md): a Machine carries its
|
|
308
352
|
images.
|
|
@@ -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
|